No OneTemporary

File Metadata

Created
Wed, May 1, 7:01 PM
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt
index 995f5381f6..73348ef873 100644
--- a/3rdparty/CMakeLists.txt
+++ b/3rdparty/CMakeLists.txt
@@ -1,221 +1,212 @@
project (krita-and-all-its-deps)
#
# Build all dependencies for Krita and finally Krita itself.
# Parameters: EXTERNALS_DOWNLOAD_DIR place to download all packages
# INSTALL_ROOT place to install everything to
# MXE_TOOLCHAIN: the toolchain file to cross-compile using MXE
#
# Example usage: cmake ..\kritadeposx -DEXTERNALS_DOWNLOAD_DIR=/dev2/d -DINSTALL_ROOT=/dev2/i -DWIN64_BUILD=TRUE -DBOOST_LIBRARYDIR=/dev2/i/lib -G "Visual Studio 11 Win64"
cmake_minimum_required(VERSION 2.8.6)
if(NOT SUBMAKE_JOBS)
set(SUBMAKE_JOBS 1)
endif(NOT SUBMAKE_JOBS)
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(FATAL_ERROR "Compiling in the source directory is not supported. Use for example 'mkdir build; cd build; cmake ..'.")
endif (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
# Tools must be obtained to work with:
include (ExternalProject)
# allow specification of a directory with pre-downloaded
# requirements
if(NOT IS_DIRECTORY ${EXTERNALS_DOWNLOAD_DIR})
message(FATAL_ERROR "No externals download dir set. Use -DEXTERNALS_DOWNLOAD_DIR")
endif()
if(NOT IS_DIRECTORY ${INSTALL_ROOT})
message(FATAL_ERROR "No install dir set. Use -DINSTALL_ROOT")
endif()
set(TOP_INST_DIR ${INSTALL_ROOT})
set(EXTPREFIX "${TOP_INST_DIR}")
set(CMAKE_PREFIX_PATH "${EXTPREFIX}")
if (${CMAKE_GENERATOR} STREQUAL "Visual Studio 14 2015 Win64")
SET(GLOBAL_PROFILE
-DCMAKE_MODULE_LINKER_FLAGS=/machine:x64
-DCMAKE_EXE_LINKER_FLAGS=/machine:x64
-DCMAKE_SHARED_LINKER_FLAGS=/machine:x64
-DCMAKE_STATIC_LINKER_FLAGS=/machine:x64
)
endif ()
message( STATUS "CMAKE_GENERATOR: ${CMAKE_GENERATOR}")
message( STATUS "CMAKE_CL_64: ${CMAKE_CL_64}")
set(GLOBAL_BUILD_TYPE RelWithDebInfo)
set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DBUILD_TESTING=false)
if (MINGW)
option(QT_ENABLE_DEBUG_INFO "Build Qt with debug info included" OFF)
option(QT_ENABLE_DYNAMIC_OPENGL "Build Qt with dynamic ANGLE support '-opengl dynamic -angle' (needs env var 'WindowsSdkDir' set to path of Windows 10 SDK)" ON)
if (QT_ENABLE_DYNAMIC_OPENGL)
if (DEFINED ENV{WindowsSdkDir})
message(STATUS "WindowsSdkDir is set to '$ENV{WindowsSdkDir}'")
else (DEFINED ENV{WindowsSdkDir})
message(FATAL_ERROR "Environemnt variable 'WindowsSdkDir' not set! Please set it to path of Windows 10 SDK or disable QT_ENABLE_DYNAMIC_OPENGL")
endif (DEFINED ENV{WindowsSdkDir})
endif (QT_ENABLE_DYNAMIC_OPENGL)
endif (MINGW)
set(SECURITY_EXE_LINKER_FLAGS "")
set(SECURITY_SHARED_LINKER_FLAGS "")
set(SECURITY_MODULE_LINKER_FLAGS "")
if (MINGW)
option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON)
if (USE_MINGW_HARDENING_LINKER)
set(SECURITY_EXE_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base")
set(SECURITY_SHARED_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base")
set(SECURITY_MODULE_LINKER_FLAGS "-Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base")
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
# Enable high-entropy ASLR for 64-bit
# The image base has to be >4GB for HEASLR to be enabled.
# The values used here are kind of arbitrary.
set(SECURITY_EXE_LINKER_FLAGS "${SECURITY_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000")
set(SECURITY_SHARED_LINKER_FLAGS "${SECURITY_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000")
set(SECURITY_MODULE_LINKER_FLAGS "${SECURITY_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000")
set(GLOBAL_PROFILE ${GLOBAL_PROFILE}
-DCMAKE_EXE_LINKER_FLAGS=${SECURITY_EXE_LINKER_FLAGS}
-DCMAKE_SHARED_LINKER_FLAGS=${SECURITY_SHARED_LINKER_FLAGS}
-DCMAKE_MODULE_LINKER_FLAGS=${SECURITY_MODULE_LINKER_FLAGS}
)
endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
else (USE_MINGW_HARDENING_LINKER)
message(WARNING "Linker Security Flags not enabled!")
endif (USE_MINGW_HARDENING_LINKER)
endif (MINGW)
if (DEFINED EP_PREFIX)
set_directory_properties(PROPERTIES EP_PREFIX ${EP_PREFIX})
endif (DEFINED EP_PREFIX)
if (MSVC)
set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_EXE_LINKER_FLAGS=/PROFILE -DCMAKE_SHARED_LINKER_FLAGS=/PROFILE)
set(PATCH_COMMAND myptch)
endif()
if (MINGW)
set(PATCH_COMMAND myptch)
endif()
if (MSYS)
set(PATCH_COMMAND patch)
set(GLOBAL_PROFILE ${GLOBAL_PROFILE}
-DCMAKE_TOOLCHAIN_FILE=${MXE_TOOLCHAIN}
-DCMAKE_FIND_PREFIX_PATH=${CMAKE_PREFIX_PATH}
-DCMAKE_SYSTEM_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include
-DCMAKE_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include
-DCMAKE_LIBRARY_PATH=${CMAKE_PREFIX_PATH}/lib
-DZLIB_ROOT=${CMAKE_PREFIX_PATH}
)
set(GLOBAL_AUTOMAKE_PROFILE --host=i686-pc-mingw32 )
endif()
if (APPLE)
set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_MACOSX_RPATH=ON -DKDE_SKIP_RPATH_SETTINGS=ON -DBUILD_WITH_INSTALL_RPATH=ON -DAPPLE_SUPPRESS_X11_WARNING=ON)
set(PATCH_COMMAND patch)
endif ()
if (UNIX AND NOT APPLE)
set(LINUX true)
set(PATCH_COMMAND patch)
endif ()
function(TestCompileLinkPythonLibs OUTPUT_VARNAME)
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH})
set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES})
if (MINGW)
set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot)
endif (MINGW)
unset(${OUTPUT_VARNAME} CACHE)
CHECK_CXX_SOURCE_COMPILES("
#include <Python.h>
int main(int argc, char *argv[]) {
Py_InitializeEx(0);
}" ${OUTPUT_VARNAME})
endfunction()
if (MINGW)
option(ENABLE_PYTHON_DEPS "Enable Python deps (sip, pyqt)" ON)
if (ENABLE_PYTHON_DEPS)
- # First get the Python library path to make sure the selected Python
- # can be used for Building, then try to get the Python interpreter again
find_package(PythonInterp 3.6 EXACT)
find_package(PythonLibs 3.6 EXACT)
- if (PYTHONLIBS_FOUND)
- # sip and pyqt does not use the CMake variable "PYTHON_LIBRARIES".
- # We point to python.exe directly because we want to get the exact
- # Python build for the target architecture, so that sip and pyqt
- # will find the proper lib
- get_filename_component(PYTHON_DIR ${PYTHON_LIBRARIES} DIRECTORY)
- get_filename_component(PYTHON_DIR ${PYTHON_DIR} DIRECTORY)
- set(PYTHON_EXECUTABLE "${PYTHON_DIR}/python.exe")
- message(STATUS "Set Python executable: ${PYTHON_EXECUTABLE}")
- find_package(PythonInterp 3.6 EXACT)
- endif (PYTHONLIBS_FOUND)
if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND)
message(STATUS "Python requirements met.")
+ TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS)
+ if (NOT CAN_USE_PYTHON_LIBS)
+ message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct!")
+ endif (NOT CAN_USE_PYTHON_LIBS)
else (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND)
message(FATAL_ERROR "Python requirements not met. To disable Python deps, set ENABLE_PYTHON_DEPS to OFF.")
endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND)
- TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS)
- if (NOT CAN_USE_PYTHON_LIBS)
- message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct!")
- endif (NOT CAN_USE_PYTHON_LIBS)
endif (ENABLE_PYTHON_DEPS)
endif (MINGW)
# this list must be dependency-ordered
if (ENABLE_PYTHON_DEPS OR NOT MINGW)
add_subdirectory( ext_python )
endif (ENABLE_PYTHON_DEPS OR NOT MINGW)
if (MSVC)
add_subdirectory( ext_patch )
add_subdirectory( ext_png2ico )
endif (MSVC)
if (MINGW)
add_subdirectory( ext_patch )
add_subdirectory( ext_png2ico )
endif (MINGW)
add_subdirectory( ext_iconv )
add_subdirectory( ext_gettext )
add_subdirectory( ext_zlib )
add_subdirectory( ext_libxml2 )
add_subdirectory( ext_libxslt )
add_subdirectory( ext_boost )
add_subdirectory( ext_jpeg )
add_subdirectory( ext_tiff )
add_subdirectory( ext_png )
add_subdirectory( ext_eigen3 )
add_subdirectory( ext_expat ) # for exiv2
add_subdirectory( ext_exiv2 )
add_subdirectory( ext_ilmbase )
add_subdirectory( ext_lcms2 )
add_subdirectory( ext_openexr )
add_subdirectory( ext_vc )
add_subdirectory( ext_gsl )
add_subdirectory( ext_fftw3 )
add_subdirectory( ext_ocio )
if (MSVC)
add_subdirectory( ext_pthreads )
endif (MSVC)
add_subdirectory( ext_fontconfig)
add_subdirectory( ext_freetype)
add_subdirectory( ext_qt )
add_subdirectory( ext_poppler )
add_subdirectory( ext_libraw )
add_subdirectory( ext_frameworks )
if (ENABLE_PYTHON_DEPS OR NOT MINGW)
add_subdirectory( ext_sip )
add_subdirectory( ext_pyqt )
endif (ENABLE_PYTHON_DEPS OR NOT MINGW)
if (MSVC OR MINGW)
add_subdirectory( ext_drmingw )
endif (MSVC OR MINGW)
+
+if (NOT APPLE)
+ add_subdirectory( ext_gmic )
+endif (NOT APPLE)
diff --git a/3rdparty/README.md b/3rdparty/README.md
index 20b1098c3c..5bb44ce76a 100644
--- a/3rdparty/README.md
+++ b/3rdparty/README.md
@@ -1,246 +1,250 @@
= CMake external projects to build krita's dependencies on Linux, Windows or OSX =
If you need to build Krita's dependencies for the following reasons:
* you develop on Windows and aren't using Craft
* you develop on OSX and aren't using Homebrew
* you want to build a generic, distro-agnostic version of Krita for Linux
* you develop on Linux, but some dependencies aren't available for your distribution
and you know what you're doing, you can use the following guide to build
the dependencies that Krita needs.
If you develop on Linux and your distribution has all dependencies available,
YOU DO NOT NEED THIS GUIDE AND YOU SHOULD STOP READING NOW
Otherwise you risk major confusion.
== Prerequisites ==
Note: on all operating systems the entire procedure is done in a terminal window.
1. git: https://git-scm.com/downloads. Make sure git is in your path
2. CMake 3.3.2 or later: https://cmake.org/download/. Make sure cmake is in your path.
* CMake 3.9 does not build Krita properly at the moment, please use 3.8 instead.
3. Make sure you have a compiler:
* Linux: gcc, minimum version 4.8
* OSX: clang, you need to install xcode for this
* Windows: mingw-w64 7.1 (by mingw-builds)
- 32-bit (x86) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/7.1.0/threads-posix/dwarf/
- 64-bit (x64) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.1.0/threads-posix/seh/
Make sure mingw's bin folder is in your path. It might be a good
idea to create a batch file which sets the path and start cmd.
MSVC is *not* supported at the moment.
4. On Windows, you will also need Python 3.6.2 (technically any versions of 3.6 is fine, but it's not tested): https://www.python.org. Make sure to have that version of python.exe in your path. This version of Python will be used for two things: to configure Qt and to build the Python scripting module. Make sure the version you download is exactly python-3.6.2. Make sure that 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: https://www.python.org/downloads/windows/
5. On Windows, if you want to compile Qt with ANGLE support, you will need to install Windows 10 SDK and have the environment variable `WindowsSdkDir` set to it (typically `C:\Program Files (x86)\Windows Kits\10`)
== Setup your environment ==
== Prepare your directory layout ==
1. Make a toplevel build directory, say $HOME/dev or c:\dev. We'll refer to this directory as BUILDROOT. You can use a variable for this, on WINDOWS %BUILDROOT%, on OSX and Linux $BUILDROOT. You will have to replace BUILDROOT with $BUILDROOT or %BUILDROOT whenever you copy and paste a command, depending on your operating system.
2. Checkout krita in BUILDROOT
cd BUILDROOT
git clone git://anongit.kde.org/krita.git
3. Create the build directory
mkdir BUILDROOT/b
4. Create the downloads directory
mkdir BUILDROOT/d
5. Create the install directory
mkdir BUILDROOT/i
== Prepare the externals build ==
1. enter the BUILDROOT/b directory
2. run cmake:
* Linux:
export PATH=$BUILDROOT/i/bin:$PATH
export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python)
cmake ../krita/3rdparty \
-DINSTALL_ROOT=$BUILDROOT/i \
-DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \
-DCMAKE_INSTALL_PREFIX=BUILDROOT/i
* OSX:
export PATH=$BUILDROOT/i/bin:$PATH
export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python)
cmake ../krita/3rdparty/ \
-DCMAKE_INSTALL_PREFIX=$BUILDROOT/i \
-DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \
-DINSTALL_ROOT=$BUILDROOT/i
* Windows 32-bit / 64-bit:
Note that the cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d.
set PATH=%BUILDROOT%\i\bin\;%BUILDROOT%\i\lib;%PATH%
set 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=<n>` to the cmake command where <n> is the number of jobs to
run (if your PC has 4 CPU cores, you might want to set it to 5).
- 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:
With a judicious application of DEPENDS statements, it's possible to build it all in one go, but in my experience that fails always, so it's better to build the dependencies independently.
On Windows:
cmake --build . --config RelWithDebInfo --target ext_patch
cmake --build . --config RelWithDebInfo --target ext_png2ico
cmake --build . --config RelWithDebInfo --target ext_gettext
On OSX:
cmake --build . --config RelWithDebInfo --target ext_gettext
On all operating systems:
cmake --build . --config RelWithDebInfo --target ext_qt
cmake --build . --config RelWithDebInfo --target ext_zlib
cmake --build . --config RelWithDebInfo --target ext_boost
Note about boost: check if the headers are installed into i/include/boost, but not into i/include/boost-1.61/boost
cmake --build . --config RelWithDebInfo --target ext_eigen3
cmake --build . --config RelWithDebInfo --target ext_exiv2
cmake --build . --config RelWithDebInfo --target ext_fftw3
On all operating systems
cmake --build . --config RelWithDebInfo --target ext_ilmbase
cmake --build . --config RelWithDebInfo --target ext_jpeg
cmake --build . --config RelWithDebInfo --target ext_lcms2
cmake --build . --config RelWithDebInfo --target ext_ocio
cmake --build . --config RelWithDebInfo --target ext_openexr
Note for OSX:
On OSX, you need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again.
install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable
install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups
On All operating systems:
cmake --build . --config RelWithDebInfo --target ext_png
cmake --build . --config RelWithDebInfo --target ext_tiff
cmake --build . --config RelWithDebInfo --target ext_gsl
cmake --build . --config RelWithDebInfo --target ext_vc
cmake --build . --config RelWithDebInfo --target ext_libraw
On Windows
cmake --build . --config RelWithDebInfo --target ext_freetype
cmake --build . --config RelWithDebInfo --target ext_poppler
On Linux
cmake --build . --config RelWithDebInfo --target ext_kcrash
Everywhere else:
cmake --build . --config RelWithDebInfo --target ext_kwindowsystem
On Windows, if you want to include DrMingw for dumping backtrace on crash:
cmake --build . --config RelWithDebInfo --target ext_drmingw
On Windows, if you want to include Python scripting:
cmake --build . --config RelWithDebInfo --target ext_python
cmake --build . --config RelWithDebInfo --target ext_sip
cmake --build . --config RelWithDebInfo --target ext_pyqt
+On Windows and Linux, if you want to build gmic-qt:
+
+ cmake --build . --config RelWithDebInfo --target ext_gmic
+
Note: poppler should be buildable on Linux as well with a home-built freetype
and fontconfig, but I don't know how to make fontconfig find freetype, and on
Linux, fontconfig is needed for poppler. Poppler is needed for PDF import.
Note 2: if you want to build a release, you need to get the binary gettext
archives from files.kde.org/krita/build/dependencies:
http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-32.zip
http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-64.zip
Take care, these zips contain a libstdc++-6.dll that you don't want in your path when building.
== Build Krita ==
1. Make a krita build directory:
mkdir BUILDROOT/build
2. Enter the BUILDROOT/build
3. Run
On Windows
Depending on what you want to use, run this command for MSBuild:
cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1
Or this to use jom (faster compiling, uses all cores, ships with QtCreator/pre-built Qt binaries):
cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1
On Linux
cmake ../krita -DCMAKE_INSTALL_PREFIX=BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfobg
On OSX
cmake ../krita -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DBUNDLE_INSTALL_DIR=$BUILDROOT/i/bin -DCMAKE_BUILD_TYPE=RelWithDebInfo
4. Run
On Linux and OSX
make
make install
On Windows
Either use MSBuild to build (-- /m tells msbuild to use all your cores):
cmake --build . --config RelWithDebInfo --target INSTALL -- /m
Or use jom which should be in a path similar to C:\Qt\Qt5.6.0\Tools\QtCreator\bin\jom.exe.
So, from the same folder, instead of running cmake run:
"C:\Qt\Qt5.6.0\Tools\QtCreator\bin\jom.exe" install
6. Run krita:
On Linux
BUILDROOT/i/bin/krita
On Windows
BUILDROOT\i\bin\krita.exe
On OSX
BUILDROOT/i/bin/krita.app/Contents/MacOS/krita
== Packaging a Windows Build ==
If you want to create a stripped down version of Krita to distribute, after building everything just copy the package_2.cmd file from the "windows" folder inside krita root source folder to BUILDROOT and run it (most likely C:\dev\).
That script will copy the necessary files into the specified folder and leave out developer related files. After the script runs there will be two new ZIP files that contain a small portable version of Krita and a separate portable debug version.
diff --git a/3rdparty/ext_gmic/CMakeLists.txt b/3rdparty/ext_gmic/CMakeLists.txt
new file mode 100644
index 0000000000..7be84ac03a
--- /dev/null
+++ b/3rdparty/ext_gmic/CMakeLists.txt
@@ -0,0 +1,38 @@
+SET(PREFIX_ext_gmic "${EXTPREFIX}" )
+
+# Download the gmic sources
+ExternalProject_Add( ext_gmic_base
+ DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
+ URL http://gmic.eu/files/source/gmic_2.1.3.tar.gz
+ URL_HASH SHA1=f0832766f009a74287bb9fdbe013f4a84db1ad76
+
+ SOURCE_DIR gmic
+
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND ""
+ INSTALL_COMMAND ""
+
+ BUILD_IN_SOURCE 1
+)
+
+# Download and build gmic-qt
+# FIXME: Forcing CMAKE_BUILD_TYPE to Release
+ExternalProject_Add( ext_gmic_qt
+ DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
+ URL https://github.com/c-koi/gmic-qt/archive/v.213.tar.gz
+ DOWNLOAD_NAME gmic-qt_2.1.3.tar.gz
+ URL_HASH SHA1=8c6489da2ce44ae3eae6ab7e9cf7b45716c0aacd
+
+ SOURCE_DIR gmic-qt
+ INSTALL_DIR ${PREFIX_ext_gmic}
+
+ CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_gmic} -DGMIC_QT_HOST=krita -DCMAKE_BUILD_TYPE=Release ${GLOBAL_PROFILE}
+
+ UPDATE_COMMAND ""
+ INSTALL_COMMAND ${CMAKE_COMMAND} -E copy <BINARY_DIR>/gmic_krita_qt${CMAKE_EXECUTABLE_SUFFIX} <INSTALL_DIR>/bin/gmic_krita_qt${CMAKE_EXECUTABLE_SUFFIX}
+
+ DEPENDS ext_gmic_base
+)
+
+add_custom_target(ext_gmic)
+add_dependencies(ext_gmic ext_gmic_qt)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 60cf858568..4a120aa28b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,692 +1,687 @@
project(krita)
message(STATUS "Using CMake version: ${CMAKE_VERSION}")
cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
set(MIN_QT_VERSION 5.6.0)
option(OVERRIDE_QT_VERSION "Use this to make it possible to build with Qt < 5.6.0. There will be bugs." OFF)
if (OVERRIDE_QT_VERSION)
set(MIN_QT_VERSION 5.4.0)
endif()
set(MIN_FRAMEWORKS_VERSION 5.7.0)
if (POLICY CMP0002)
cmake_policy(SET CMP0002 OLD)
endif()
if (POLICY CMP0017)
cmake_policy(SET CMP0017 NEW)
endif ()
if (POLICY CMP0022)
cmake_policy(SET CMP0022 OLD)
endif ()
if (POLICY CMP0026)
cmake_policy(SET CMP0026 OLD)
endif()
if (POLICY CMP0042)
cmake_policy(SET CMP0042 NEW)
endif()
if (POLICY CMP0046)
cmake_policy(SET CMP0046 OLD)
endif ()
if (POLICY CMP0059)
cmake_policy(SET CMP0059 OLD)
endif()
if (POLICY CMP0063)
cmake_policy(SET CMP0063 OLD)
endif()
if (POLICY CMP0054)
cmake_policy(SET CMP0054 OLD)
endif()
if (POLICY CMP0064)
cmake_policy(SET CMP0064 OLD)
endif()
if (APPLE)
set(APPLE_SUPPRESS_X11_WARNING TRUE)
set(KDE_SKIP_RPATH_SETTINGS TRUE)
set(CMAKE_MACOSX_RPATH 1)
set(BUILD_WITH_INSTALL_RPATH 1)
add_definitions(-mmacosx-version-min=10.9 -Wno-macro-redefined -Wno-deprecated-register)
endif()
if (LINUX)
if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WINDOWS)
add_definitions(-Werror=delete-incomplete)
endif()
endif()
######################
#######################
## Constants defines ##
#######################
######################
# define common versions of Krita applications, used to generate kritaversion.h
# update these version for every release:
set(KRITA_VERSION_STRING "4.0.0-pre-alpha")
set(KRITA_STABLE_VERSION_MAJOR 4) # 3 for 3.x, 4 for 4.x, etc.
set(KRITA_STABLE_VERSION_MINOR 0) # 0 for 3.0, 1 for 3.1, etc.
set(KRITA_VERSION_RELEASE 0) # 88 for pre-alpha, 89 for Alpha, increase for next test releases, set 0 for first Stable, etc.
set(KRITA_ALPHA 1) # uncomment only for Alpha
#set(KRITA_BETA 1) # uncomment only for Beta
#set(KRITA_RC 1) # uncomment only for RC
set(KRITA_YEAR 2017) # update every year
if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC)
set(KRITA_STABLE 1) # do not edit
endif()
message(STATUS "Krita version: ${KRITA_VERSION_STRING}")
# Define the generic version of the Krita libraries here
# This makes it easy to advance it when the next Krita release comes.
# 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series
# (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series
if(KRITA_STABLE_VERSION_MAJOR EQUAL 4)
math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16")
else()
# let's make sure we won't forget to update the "16"
message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger")
endif()
set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0")
set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}")
LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro")
# fetch git revision for the current build
set(KRITA_GIT_SHA1_STRING "")
set(KRITA_GIT_BRANCH_STRING "")
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
get_git_branch(GIT_BRANCH)
if(GIT_SHA1 AND GIT_BRANCH)
string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1)
set(KRITA_GIT_SHA1_STRING ${GIT_SHA1})
set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH})
endif()
if(NOT DEFINED RELEASE_BUILD)
# estimate mode by CMAKE_BUILD_TYPE content if not set on cmdline
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER)
set(RELEASE_BUILD_TYPES "release" "relwithdebinfo" "minsizerel")
list(FIND RELEASE_BUILD_TYPES "${CMAKE_BUILD_TYPE_TOLOWER}" INDEX)
if (INDEX EQUAL -1)
set(RELEASE_BUILD FALSE)
else()
set(RELEASE_BUILD TRUE)
endif()
endif()
message(STATUS "Release build: ${RELEASE_BUILD}")
# create test make targets
enable_testing()
# collect list of broken tests, empty here to start fresh with each cmake run
set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS")
############
#############
## Options ##
#############
############
include(FeatureSummary)
if (WIN32)
option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON)
add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler")
if (MINGW)
option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON)
add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags")
if (USE_MINGW_HARDENING_LINKER)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base")
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
# Enable high-entropy ASLR for 64-bit
# The image base has to be >4GB for HEASLR to be enabled.
# The values used here are kind of arbitrary.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000")
endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
else (USE_MINGW_HARDENING_LINKER)
message(WARNING "Linker Security Flags not enabled!")
endif (USE_MINGW_HARDENING_LINKER)
endif (MINGW)
endif ()
option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON)
configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h)
add_feature_info("Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.")
option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF)
add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.")
option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF)
add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).")
include(MacroJPEG)
###########################################################
## Look for Python3. It is also searched by KF5, ##
## so we should request the correct version in advance ##
###########################################################
function(TestCompileLinkPythonLibs OUTPUT_VARNAME)
- include(CheckCXXSourceCompiles)
- set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH})
- set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES})
+ include(CheckCXXSourceCompiles)
+ set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH})
+ set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES})
if (MINGW)
set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot)
endif (MINGW)
- unset(${OUTPUT_VARNAME} CACHE)
- CHECK_CXX_SOURCE_COMPILES("
+ unset(${OUTPUT_VARNAME} CACHE)
+ CHECK_CXX_SOURCE_COMPILES("
#include <Python.h>
int main(int argc, char *argv[]) {
Py_InitializeEx(0);
}" ${OUTPUT_VARNAME})
endfunction()
if(MINGW)
- # First get the Python library path to make sure the selected Python
- # can be used for Building, then try to get the Python interpreter again
find_package(PythonInterp 3.6 EXACT)
find_package(PythonLibs 3.6 EXACT)
- if (PYTHONLIBS_FOUND)
- # Use the same Python as the library
- get_filename_component(PYTHON_DIR ${PYTHON_LIBRARIES} DIRECTORY)
- get_filename_component(PYTHON_DIR ${PYTHON_DIR} DIRECTORY)
- set(PYTHON_EXECUTABLE "${PYTHON_DIR}/python.exe")
- message(STATUS "Set Python executable: ${PYTHON_EXECUTABLE}")
- find_package(PythonInterp 3.6 EXACT)
- if (PYTHONINTERP_FOUND)
- find_package(PythonLibrary 3.6)
- else (PYTHONINTERP_FOUND)
- # This shouldn't happen on Windows...
- message(FATAL_ERROR "Python library found but python.exe not found!")
- endif (PYTHONINTERP_FOUND)
- TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS)
- if (NOT CAN_USE_PYTHON_LIBS)
- message(WARNING "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.")
- unset(PYTHONLIBS_FOUND CACHE)
- endif (NOT CAN_USE_PYTHON_LIBS)
- endif (PYTHONLIBS_FOUND)
+ if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND)
+ find_package(PythonLibrary 3.6)
+ TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS)
+ if (NOT CAN_USE_PYTHON_LIBS)
+ message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.")
+ endif (NOT CAN_USE_PYTHON_LIBS)
+ endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND)
else(MINGW)
find_package(PythonInterp 3.0)
find_package(PythonLibrary 3.0)
endif(MINGW)
########################
#########################
## Look for KDE and Qt ##
#########################
########################
find_package(ECM 5.19 REQUIRED NOMODULE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR})
include(ECMOptionalAddSubdirectory)
include(ECMAddAppIcon)
include(ECMSetupVersion)
include(ECMMarkNonGuiExecutable)
include(ECMGenerateHeaders)
include(GenerateExportHeader)
include(ECMMarkAsTest)
include(ECMInstallIcons)
include(CMakePackageConfigHelpers)
include(WriteBasicConfigVersionFile)
include(CheckFunctionExists)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings)
# do not reorder to be alphabetical: this is the order in which the frameworks
# depend on each other.
find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS
Archive
Config
WidgetsAddons
Completion
CoreAddons
GuiAddons
I18n
ItemModels
ItemViews
WindowSystem
)
# KConfig deprecated authorizeKAction. In order to be warning free,
# compile with the updated function when the dependency is new enough.
# Remove this (and the uses of the define) when the minimum KF5
# version is >= 5.24.0.
if (${KF5Config_VERSION} VERSION_LESS "5.24.0" )
message("Old KConfig (< 5.24.0) found.")
add_definitions(-DKCONFIG_BEFORE_5_24)
endif()
find_package(Qt5 ${MIN_QT_VERSION}
REQUIRED COMPONENTS
Core
Gui
Widgets
Xml
Network
PrintSupport
Svg
Test
Concurrent
)
include (MacroAddFileDependencies)
include (MacroBoolTo01)
include (MacroEnsureOutOfSourceBuild)
macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions")
# Note: OPTIONAL_COMPONENTS does not seem to be reliable
# (as of ECM 5.15.0, CMake 3.2)
find_package(Qt5Multimedia ${MIN_QT_VERSION})
set_package_properties(Qt5Multimedia PROPERTIES
DESCRIPTION "Qt multimedia integration"
URL "http://www.qt.io/"
TYPE OPTIONAL
PURPOSE "Optionally used to provide sound support for animations")
macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA)
configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h )
find_package(Qt5Quick ${MIN_QT_VERSION})
set_package_properties(Qt5Quick PROPERTIES
DESCRIPTION "QtQuick"
URL "http://www.qt.io/"
TYPE OPTIONAL
PURPOSE "Optionally used for the touch gui for Krita")
macro_bool_to_01(Qt5Quick_FOUND HAVE_QT_QUICK)
find_package(Qt5QuickWidgets ${MIN_QT_VERSION})
set_package_properties(Qt5QuickWidgets PROPERTIES
DESCRIPTION "QtQuickWidgets"
URL "http://www.qt.io/"
TYPE OPTIONAL
PURPOSE "Optionally used for the touch gui for Krita")
if (NOT WIN32 AND NOT APPLE)
find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras)
find_package(Qt5DBus ${MIN_QT_VERSION})
set(HAVE_DBUS ${Qt5DBus_FOUND})
set_package_properties(Qt5DBus PROPERTIES
DESCRIPTION "Qt DBUS integration"
URL "http://www.qt.io/"
TYPE OPTIONAL
PURPOSE "Optionally used to provide a dbus api on Linux")
find_package(KF5KIO ${MIN_FRAMEWORKS_VERSION})
macro_bool_to_01(KF5KIO_FOUND HAVE_KIO)
set_package_properties(KF5KIO PROPERTIES
DESCRIPTION "KDE's KIO Framework"
URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kio/html/index.html"
TYPE OPTIONAL
PURPOSE "Optionally used for recent document handling")
find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION})
macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH)
set_package_properties(KF5Crash PROPERTIES
DESCRIPTION "KDE's Crash Handler"
URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html"
TYPE OPTIONAL
PURPOSE "Optionally used to provide crash reporting on Linux")
find_package(X11 REQUIRED COMPONENTS Xinput)
set(HAVE_X11 TRUE)
add_definitions(-DHAVE_X11)
find_package(XCB COMPONENTS XCB ATOM)
set(HAVE_XCB ${XCB_FOUND})
else()
set(HAVE_DBUS FALSE)
set(HAVE_X11 FALSE)
set(HAVE_XCB FALSE)
endif()
add_definitions(
-DQT_USE_QSTRINGBUILDER
-DQT_STRICT_ITERATORS
-DQT_NO_SIGNALS_SLOTS_KEYWORDS
-DQT_USE_FAST_OPERATOR_PLUS
-DQT_USE_FAST_CONCATENATION
-DQT_NO_URL_CAST_FROM_STRING
- -DQT_DISABLE_DEPRECATED_BEFORE=0
)
+if (${Qt5_VERSION} VERSION_GREATER "5.8.0" )
+ add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900)
+elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" )
+ add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800)
+elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" )
+ add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700)
+else()
+ add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600)
+endif()
+
add_definitions(-DTRANSLATION_DOMAIN=\"krita\")
#
# The reason for this mode is that the Debug mode disable inlining
#
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS_KRITADEVS "-O3 -g" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals")
endif()
if(UNIX)
set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m")
endif()
if(WIN32)
if(MSVC)
# C4522: 'class' : multiple assignment operators specified
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522")
endif()
endif()
# enable exceptions globally
kde_enable_exceptions()
# only with this definition will all the FOO_TEST_EXPORT macro do something
# TODO: check if this can be moved to only those places which make use of it,
# to reduce global compiler definitions that would trigger a recompile of
# everything on a change (like adding/removing tests to/from the build)
if(BUILD_TESTING)
add_definitions(-DCOMPILING_TESTS)
endif()
set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/)
macro(macro_add_unittest_definitions)
add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/")
add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}")
add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}")
add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/")
endmacro()
# overcome some platform incompatibilities
if(WIN32)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks)
add_definitions(-D_USE_MATH_DEFINES)
add_definitions(-DNOMINMAX)
set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib)
endif()
# set custom krita plugin installdir
set(KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins)
###########################
############################
## Required dependencies ##
############################
###########################
find_package(PNG REQUIRED)
if (APPLE)
# this is not added correctly on OSX -- see http://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242
include_directories(SYSTEM ${PNG_INCLUDE_DIR})
endif()
add_definitions(-DBOOST_ALL_NO_LIB)
find_package(Boost 1.55 REQUIRED COMPONENTS system) # for pigment and stage
include_directories(${Boost_INCLUDE_DIRS})
##
## Test for GNU Scientific Library
##
find_package(GSL)
set_package_properties(GSL PROPERTIES
URL "http://www.gnu.org/software/gsl"
TYPE RECOMMENDED
PURPOSE "Required by Krita's Transform tool.")
macro_bool_to_01(GSL_FOUND HAVE_GSL)
configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h )
###########################
############################
## Optional dependencies ##
############################
###########################
##
## Check for OpenEXR
##
find_package(ZLIB)
set_package_properties(ZLIB PROPERTIES
DESCRIPTION "Compression library"
URL "http://www.zlib.net/"
TYPE OPTIONAL
PURPOSE "Optionally used by the G'Mic and the PSD plugins")
macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB)
find_package(OpenEXR)
set_package_properties(OpenEXR PROPERTIES
DESCRIPTION "High dynamic-range (HDR) image file format"
URL "http://www.openexr.com"
TYPE OPTIONAL
PURPOSE "Required by the Krita OpenEXR filter")
macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR)
set(LINK_OPENEXR_LIB)
if(OPENEXR_FOUND)
include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR})
set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES})
add_definitions(${OPENEXR_DEFINITIONS})
endif()
find_package(TIFF)
set_package_properties(TIFF PROPERTIES
DESCRIPTION "TIFF Library and Utilities"
URL "http://www.remotesensing.org/libtiff"
TYPE OPTIONAL
PURPOSE "Required by the Krita TIFF filter")
find_package(JPEG)
set_package_properties(JPEG PROPERTIES
DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported."
URL "http://www.libjpeg-turbo.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita JPEG filter")
set(LIBRAW_MIN_VERSION "0.16")
find_package(LibRaw ${LIBRAW_MIN_VERSION})
set_package_properties(LibRaw PROPERTIES
DESCRIPTION "Library to decode RAW images"
URL "http://www.libraw.org"
TYPE OPTIONAL
PURPOSE "Required to build the raw import plugin")
find_package(FFTW3)
set_package_properties(FFTW3 PROPERTIES
DESCRIPTION "A fast, free C FFT library"
URL "http://www.fftw.org/"
TYPE OPTIONAL
PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features")
macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3)
find_package(OCIO)
set_package_properties(OCIO PROPERTIES
DESCRIPTION "The OpenColorIO Library"
URL "http://www.opencolorio.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita LUT docker")
macro_bool_to_01(OCIO_FOUND HAVE_OCIO)
##
## Look for OpenGL
##
# TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes)
if(Qt5Gui_OPENGL_IMPLEMENTATION)
message(STATUS "Found QtGui OpenGL support")
else()
message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.")
endif()
##
## Test for eigen3
##
find_package(Eigen3 3.0 REQUIRED)
set_package_properties(Eigen3 PROPERTIES
DESCRIPTION "C++ template library for linear algebra"
URL "http://eigen.tuxfamily.org"
TYPE REQUIRED)
##
## Test for exiv2
##
find_package(Exiv2 0.16 REQUIRED)
set_package_properties(Exiv2 PROPERTIES
DESCRIPTION "Image metadata library and tools"
URL "http://www.exiv2.org"
PURPOSE "Required by Krita")
##
## Test for lcms
##
find_package(LCMS2 2.4 REQUIRED)
set_package_properties(LCMS2 PROPERTIES
DESCRIPTION "LittleCMS Color management engine"
URL "http://www.littlecms.com"
TYPE REQUIRED
PURPOSE "Will be used for color management and is necessary for Krita")
if(LCMS2_FOUND)
if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 )
set(HAVE_LCMS24 TRUE)
endif()
set(HAVE_REQUIRED_LCMS_VERSION TRUE)
set(HAVE_LCMS2 TRUE)
endif()
##
## Test for Vc
##
set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} )
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules )
set(HAVE_VC FALSE)
if( NOT MSVC)
find_package(Vc 1.1.0)
set_package_properties(Vc PROPERTIES
DESCRIPTION "Portable, zero-overhead SIMD library for C++"
URL "https://github.com/VcDevel/Vc"
TYPE OPTIONAL
PURPOSE "Required by the Krita for vectorization")
macro_bool_to_01(Vc_FOUND HAVE_VC)
endif()
configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h )
if(HAVE_VC)
message(STATUS "Vc found!")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_SOURCE_DIR}/cmake/vc")
include (VcMacros)
if(Vc_COMPILER_IS_CLANG)
set(ADDITIONAL_VC_FLAGS "-Wabi -ffp-contract=fast -fPIC")
elseif (NOT MSVC)
set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast -fPIC")
endif()
#Handle Vc master
if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG)
AddCompilerFlag("-std=c++11" _ok)
if(NOT _ok)
AddCompilerFlag("-std=c++0x" _ok)
endif()
endif()
macro(ko_compile_for_all_implementations_no_scalar _objs _src)
vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2)
endmacro()
macro(ko_compile_for_all_implementations _objs _src)
vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2)
endmacro()
endif()
set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} )
add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS})
if(WIN32)
set(LIB_INSTALL_DIR ${LIB_INSTALL_DIR}
RUNTIME DESTINATION ${BIN_INSTALL_DIR}
LIBRARY ${INSTALL_TARGETS_DEFAULT_ARGS}
ARCHIVE ${INSTALL_TARGETS_DEFAULT_ARGS} )
endif()
##
## Test endianess
##
include (TestBigEndian)
test_big_endian(CMAKE_WORDS_BIGENDIAN)
##
## Test for qt-poppler
##
find_package(Poppler COMPONENTS Qt5)
set_package_properties(Poppler PROPERTIES
DESCRIPTION "A PDF rendering library"
URL "http://poppler.freedesktop.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita PDF filter.")
############################
#############################
## Add Krita helper macros ##
#############################
############################
include(MacroKritaAddBenchmark)
####################
#####################
## Define includes ##
#####################
####################
# for config.h and <toplevel/foo.h> includes (if any?)
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}/interfaces
)
add_subdirectory(libs)
add_subdirectory(plugins)
add_subdirectory(benchmarks)
add_subdirectory(krita)
configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h )
configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h)
configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h )
check_function_exists(powf HAVE_POWF)
configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h)
message("\nBroken tests:")
foreach(tst ${KRITA_BROKEN_TESTS})
message(" * ${tst}")
endforeach()
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/krita/data/templates/animation/.directory b/krita/data/templates/animation/.directory
index 2a56c5142c..2c7012b907 100644
--- a/krita/data/templates/animation/.directory
+++ b/krita/data/templates/animation/.directory
@@ -1,20 +1,21 @@
[Desktop Entry]
Name=Animation Templates
Name[ca]=Plantilles d'animació
Name[ca@valencia]=Plantilles d'animació
Name[cs]=Šablony animací:
Name[de]=Animations-Vorlagen
+Name[el]=Πρότυπα εφέ κίνησης
Name[en_GB]=Animation Templates
Name[es]=Plantillas de animación
Name[gl]=Modelos de animación
Name[it]=Modelli di animazioni
Name[nl]=Animatiesjablonen
Name[pl]=Szablony animacji
Name[pt]=Modelos de Animações
Name[pt_BR]=Modelos de animação
Name[sv]=Animeringsmallar
Name[tr]=Canlandırma Şablonları
Name[uk]=Шаблони анімацій
Name[x-test]=xxAnimation Templatesxx
Name[zh_CN]=动画模板
X-KDE-DefaultTab=true
diff --git a/krita/data/templates/animation/Anim-Jp-EN.desktop b/krita/data/templates/animation/Anim-Jp-EN.desktop
index dcc2784fc4..56336b06bb 100644
--- a/krita/data/templates/animation/Anim-Jp-EN.desktop
+++ b/krita/data/templates/animation/Anim-Jp-EN.desktop
@@ -1,25 +1,26 @@
[Desktop Entry]
Type=Link
URL=.source/Anim-Jp-EN.kra
Icon=template_animation
Name=Animation-Japanese-En
Name[ca]=Animació-Japonès-EN
Name[ca@valencia]=Animació-Japonés-EN
Name[de]=Animation-Japanisch-En
+Name[el]=Εφέ-κίνησης-Ιαπωνικό-En
Name[en_GB]=Animation-Japanese-En
Name[es]=Animación-Japonés-En
Name[et]=Animation-Japanese-En
Name[gl]=Animación-xaponesa-en-inglés
Name[it]=Animazione-Giapponese-EN
Name[ja]=日本式アニメ(英語版)
Name[nl]=Animatie-Japans-En
Name[pl]=Animacja-Japońska-En
Name[pt]=Animação-Japonês-EN
Name[pt_BR]=Animation-Japanese-En
Name[ru]=Анимация-японская-англ
Name[sk]=Animation-Japanese-En
Name[sv]=Animering-japanska-en
Name[tr]=Canlandırma-Japonca-İngilizce
Name[uk]=Японська анімація (англійською)
Name[x-test]=xxAnimation-Japanese-Enxx
Name[zh_CN]=日本动画 (英式)
diff --git a/krita/data/templates/animation/Anim-Jp-JP.desktop b/krita/data/templates/animation/Anim-Jp-JP.desktop
index 0ac9ccdc76..31a2d0bdc9 100644
--- a/krita/data/templates/animation/Anim-Jp-JP.desktop
+++ b/krita/data/templates/animation/Anim-Jp-JP.desktop
@@ -1,25 +1,26 @@
[Desktop Entry]
Type=Link
URL=.source/Anim-Jp-JP.kra
Icon=template_animation
Name=Animation-Japanese-JP
Name[ca]=Animació-Japonès-JP
Name[ca@valencia]=Animació-Japonés-JP
Name[de]=Animation-Japanisch-JP
+Name[el]=Εφέ-κίνησης-Ιαπωνικό-JP
Name[en_GB]=Animation-Japanese-JP
Name[es]=Animación-Japonés-JP
Name[et]=Animation-Japanese-JP
Name[gl]=Animación-xaponesa-en-xaponés
Name[it]=Animazione-Giapponese-JP
Name[ja]=日本式アニメ(日本語版)
Name[nl]=Animatie-Japans-JP
Name[pl]=Animacja-Japońska-JP
Name[pt]=Animação-Japonês-JP
Name[pt_BR]=Animation-Japanese-JP
Name[ru]=Анимация-японская-японск
Name[sk]=Animation-Japanese-JP
Name[sv]=Animering-japanska-jp
Name[tr]=Canlandırma-Japonca-JP
Name[uk]=Японська анімація (японською)
Name[x-test]=xxAnimation-Japanese-JPxx
Name[zh_CN]=日本动画 (日式)
diff --git a/krita/data/templates/design/web_design.desktop b/krita/data/templates/design/web_design.desktop
index fc268b05af..659aa3e071 100644
--- a/krita/data/templates/design/web_design.desktop
+++ b/krita/data/templates/design/web_design.desktop
@@ -1,31 +1,32 @@
[Desktop Entry]
Icon=template_web_design
Name=Web Design [ 2160x1440 , 72ppi RGB , 8bit ]
Name[bs]=Web dizajn [ 2160x1440 , 72ppi RGB , 8bit ]
Name[ca]=Disseny web [ 2160x1440 / 72ppi RGB / 8bit ]
Name[ca@valencia]=Disseny web [ 2160x1440 / 72ppi RGB / 8bit ]
Name[cs]=Návrh webu [ 2160x1440 , 72ppi RGB , 8bit ]
Name[da]=Webdesign [ 2160x1440 , 72ppi RGB , 8bit ]
Name[de]=Web-Design [ 2160x1440 , 72ppi RGB , 8bit ]
+Name[el]=Σχεδίαση διαδικτυακών τόπων [ 2160x1440 , 72ppi RGB , 8bit ]
Name[en_GB]=Web Design [ 2160x1440 , 72ppi RGB , 8bit ]
Name[es]=Diseño de web 4:3 [ 2160x1440 , 72ppi RGB , 8bit ]
Name[et]=Veebidisain [ 2160x1440, 72ppi RGB, 8-bitine ]
Name[fr]=Style écran [ 2160x1440, 72ppi RGB , 8bit ]
Name[gl]=Deseño web (2160×1440, 72 ppi RGB, 8 bits)
Name[it]=Progettazione web [ 2160x1440 , 72ppi RGB , 8bit ]
Name[ja]=ウェブデザイン [ 2160x1440、72ppi RGB、8 ビット ]
Name[nb]=Web Design [ 2160x1440 , 72ppi RGB , 8bit ]
Name[nl]=Webontwerp [ 2160x1440 , 72ppi RGB , 8bit ]
Name[pl]=Projekt sieciowy [ 2160x1440 , 72ppi RGB , 8bit ]
Name[pt]=Desenho na Web [ 2160x1440 , 72ppp RGB , 8-bits ]
Name[pt_BR]=Web Design [ 2160x1440 , 72ppi RGB , 8bits ]
Name[ru]=Веб-дизайн [ 2160x1440 , 72ppi RGB , 8 бит ]
Name[sk]=Webový dizajn [ 2160x1440 , 72ppi RGB , 8bit ]
Name[sv]=Webbdesign [ 2160x1440, 72 punkter/tum RGB, 8 bitar ]
Name[tr]=Web Tasarımı [ 2160x1440 , 72ppi RGB , 8bit ]
Name[uk]=Вебдизайн [2160⨯1440, 72 т./д., RGB, 8 бітів]
Name[x-test]=xxWeb Design [ 2160x1440 , 72ppi RGB , 8bit ]xx
Name[zh_CN]=网页设计 [ 2160x1440 像素, 72ppi RGB , 8 位 ]
Type=Link
URL[$e]=.source/web_design.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop b/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop
index 8bc2e328cb..a90b4c916c 100644
--- a/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop
+++ b/krita/data/templates/texture/Texture1024x10248bitsrgb.desktop
@@ -1,31 +1,32 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 1024x1024 8bit srgb
Name[bs]=Tekstura 1024x1024 8bit srgb
Name[ca]=Textura 1024x1024 8bit SRGB
Name[ca@valencia]=Textura 1024x1024 8bit SRGB
Name[cs]=Textura 1024x1024 8bit srgb
Name[da]=Tekstur 1024x1024 8bit srgb
Name[de]=Textur 1024x1024 8bit srgb
+Name[el]=Υφή 1024x1024 8bit srgb
Name[en_GB]=Texture 1024x1024 8bit srgb
Name[es]=Textura 1024x1024 8bits srgb
Name[et]=Tekstuur 1024x1024 8bit srgb
Name[fr]=Texture 1024x1024 8bit srgb
Name[gl]=Textura de 1024×1024 e 8 bits SRGB
Name[it]=Trama 1024x1024 8bit srgb
Name[ja]=テクスチャ 1024x1024 8 ビット sRGB
Name[nb]=Tekstur 1024x1024 8bit srgb
Name[nl]=Textuur 1024x1024 8bit srgb
Name[pl]=Tekstura 1024x1024 8bit srgb
Name[pt]=Textura 1024x1024 8-bits sRGB
Name[pt_BR]=Textura 1024x1024 8-bits sRGB
Name[ru]=Текстура 1024x1024 8 бит srgb
Name[sk]=Textúra 1024x1024 8bit srgb
Name[sv]=Struktur 1024 x 1024 8-bitar SRGB
Name[tr]=Doku 1024x1024 8bit srgb
Name[uk]=Текстура 1024⨯1024, 8-бітова, srgb
Name[x-test]=xxTexture 1024x1024 8bit srgbxx
Name[zh_CN]=纹理 1024x1024 像素 8位 srgb 色彩空间
Type=Link
URL[$e]=.source/Texture1024x10248bitsrgb.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture1k32bitscalar.desktop b/krita/data/templates/texture/Texture1k32bitscalar.desktop
index 3f2566f7a1..5149df5731 100755
--- a/krita/data/templates/texture/Texture1k32bitscalar.desktop
+++ b/krita/data/templates/texture/Texture1k32bitscalar.desktop
@@ -1,35 +1,35 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 1k 32bit scalar
Name[bs]=Tekstura 1k 32bit scalar
Name[ca]=Textura 1k 32bit escalar
Name[ca@valencia]=Textura 1k 32bit escalar
Name[cs]=Textura 1k 32bit skalární
Name[da]=Tekstur 1k 32bit scalar
Name[de]=Textur 1k 32bit scalar
-Name[el]=Texture 1k 32bit βαθμωτό
+Name[el]=Υφή 1k 32bit βαθμωτό
Name[en_GB]=Texture 1k 32bit scalar
Name[es]=Textura 1k 32 bit escalar
Name[et]=Tekstuur 1k 32bit skalaar
Name[eu]=Testura 1k 16bit eskalarra
Name[fr]=Texture 1k 32bit scalaire
Name[gl]=Textura de 1k e 32 bits escalar
Name[hu]=Textúra 1k 32bit skalár
Name[it]=Trama 1k 32bit scalare
Name[ja]=テクスチャ 1k 32 ビットスカラー
Name[kk]=Текстура 1k 32 бит скаляр
Name[nb]=Tekstur 1k 32bit skalar
Name[nl]=Textuur 1k 32bit scalar
Name[pl]=Tekstura 1k 32bit skalar
Name[pt]=Textura 1k 32-bits escalar
Name[pt_BR]=Textura 1k 32bits escalar
Name[ru]=Текстура 1k 32 бит scalar
Name[sk]=Textúra 1k 32bit skalár
Name[sv]=Struktur 1k 32-bitar skalär
Name[tr]=Doku 1k 32bit sayısal
Name[uk]=Текстура 1k, 32-бітова, скалярна
Name[x-test]=xxTexture 1k 32bit scalarxx
Name[zh_CN]=纹理 1K 像素 32 位 scalar 色彩空间
Type=Link
URL[$e]=.source/Texture1k32bitscalar.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture1k8bitsrgb.desktop b/krita/data/templates/texture/Texture1k8bitsrgb.desktop
index 478ae4c74d..91346239e4 100755
--- a/krita/data/templates/texture/Texture1k8bitsrgb.desktop
+++ b/krita/data/templates/texture/Texture1k8bitsrgb.desktop
@@ -1,35 +1,35 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 1k 8bit srgb
Name[bs]=Tekstura 1k 8bit srgb
Name[ca]=Textura 1k 8bit SRGB
Name[ca@valencia]=Textura 1k 8bit SRGB
Name[cs]=Textura 1k 8bit srgb
Name[da]=Tekstur 1k 8bit srgb
Name[de]=Textur 1k 8bit srgb
-Name[el]=Texture 1k 8bit srgb
+Name[el]=Υφή 1k 8bit srgb
Name[en_GB]=Texture 1k 8bit srgb
Name[es]=Textura 1k 8bit srgb
Name[et]=Tekstuur 1k 8bit srgb
Name[eu]=Testura 1k 8bit sGBU
Name[fr]=Texture 1k 8bit srgb
Name[gl]=Textura de 1k e 8 bits SRGB
Name[hu]=Textúra 1k 8bit srgb
Name[it]=Trama 1k 8bit srgb
Name[ja]=テクスチャ 1k 8 ビット sRGB
Name[kk]=Текстура 1k 8 бит srgb
Name[nb]=Tekstur 1k 8bit srgb
Name[nl]=Textuur 1k 8bit srgb
Name[pl]=Tekstura 1k 8bit srgb
Name[pt]=Textura 1k 8-bits sRGB
Name[pt_BR]=Textura 1k 8bits sRGB
Name[ru]=Текстура 1k 8 бит srgb
Name[sk]=Textúra 1k 8bit srgb
Name[sv]=Struktur 1k 8-bitar SRGB
Name[tr]=Doku 1k 8bit srgb
Name[uk]=Текстура 1k, 8-бітова, srgb
Name[x-test]=xxTexture 1k 8bit srgbxx
Name[zh_CN]=纹理 1K 像素 8 位 sRGB 色彩空间
Type=Link
URL[$e]=.source/Texture1k8bitsrgb.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop b/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop
index 34b1482b92..dbb6c9236e 100644
--- a/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop
+++ b/krita/data/templates/texture/Texture2048x20488bitsrgb.desktop
@@ -1,31 +1,32 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 2048x2048 8bit srgb
Name[bs]=Tekstura 2048x2048 8bit srgb
Name[ca]=Textura 2048x2048 8bit SRGB
Name[ca@valencia]=Textura 2048x2048 8bit SRGB
Name[cs]=Textura 2048x2048 8bit srgb
Name[da]=Tekstur 2048x2048 8bit srgb
Name[de]=Textur 2048x2048 8bit srgb
+Name[el]=Υφή 2048x2048 8bit srgb
Name[en_GB]=Texture 2048x2048 8bit srgb
Name[es]=Textura 2048x2048 8bits srgb
Name[et]=Tekstuur 2048x2048 8bit srgb
Name[fr]=Texture 2048x2048 8bit srgb
Name[gl]=Textura de 2048×2048 e 8 bits SRGB
Name[it]=Trama 2048x2048 8bit srgb
Name[ja]=テクスチャ 2048x2048 8 ビット sRGB
Name[nb]=Tekstur 2048x2048 8bit srgb
Name[nl]=Textuur 2048x2048 8bit srgb
Name[pl]=Tekstura 2048x2048 8bit srgb
Name[pt]=Textura 2048x2048 8-bits sRGB
Name[pt_BR]=Textura 2048x2048 8bits sRGB
Name[ru]=Текстура 2048x2048 8 бит srgb
Name[sk]=Textúra 2048x2048 8bit srgb
Name[sv]=Struktur 2048 x 2048 8-bitar SRGB
Name[tr]=Doku 2048x2048 8bit srgb
Name[uk]=Текстура 2048⨯2048, 8-бітова, srgb
Name[x-test]=xxTexture 2048x2048 8bit srgbxx
Name[zh_CN]=纹理 2048x2048 像素 8 位 sRGB 色彩空间
Type=Link
URL[$e]=.source/Texture2048x20488bitsrgb.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture256x2568bitsrgb.desktop b/krita/data/templates/texture/Texture256x2568bitsrgb.desktop
index 9163d3143f..a612b40baa 100644
--- a/krita/data/templates/texture/Texture256x2568bitsrgb.desktop
+++ b/krita/data/templates/texture/Texture256x2568bitsrgb.desktop
@@ -1,31 +1,32 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 256x256 8bit srgb
Name[bs]=Tekstura 256x256 8bit srgb
Name[ca]=Textura 256x256 8bit SRGB
Name[ca@valencia]=Textura 256x256 8bit SRGB
Name[cs]=Textura 256x256 8bit srgb
Name[da]=Tekstur 256x256 8bit srgb
Name[de]=Textur 256x256 8bit srgb
+Name[el]=Υφή 256x256 8bit srgb
Name[en_GB]=Texture 256x256 8bit srgb
Name[es]=Textura 256x256 8bits srgb
Name[et]=Tekstuur 256x256 8bit srgb
Name[fr]=Texture 256x256 8bit srgb
Name[gl]=Textura de 256×256 e 8 bits SRGB
Name[it]=Trama 256x256 8bit srgb
Name[ja]=テクスチャ 256x256 8 ビット sRGB
Name[nb]=Tekstur 256x256 8bit srgb
Name[nl]=Textuur 256x256 8bit srgb
Name[pl]=Tekstura 256x256 8bit srgb
Name[pt]=Textura 256x256 8-bits sRGB
Name[pt_BR]=Textura 256x256 8bits sRGB
Name[ru]=Текстура 256x256 8 бит srgb
Name[sk]=Textúra 256x256 8bit srgb
Name[sv]=Struktur 256 x 256 8-bitar SRGB
Name[tr]=Doku 256x256 8bit srgb
Name[uk]=Текстура 256⨯256, 8-бітова, srgb
Name[x-test]=xxTexture 256x256 8bit srgbxx
Name[zh_CN]=纹理 256x256 像素 8 位 srgb 色彩空间
Type=Link
URL[$e]=.source/Texture256x2568bitsrgb.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture2k32bitscalar.desktop b/krita/data/templates/texture/Texture2k32bitscalar.desktop
index bc96e84458..1fc17a4989 100755
--- a/krita/data/templates/texture/Texture2k32bitscalar.desktop
+++ b/krita/data/templates/texture/Texture2k32bitscalar.desktop
@@ -1,35 +1,35 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 2k 32bit scalar
Name[bs]=Tekstura 2k 32bit scalar
Name[ca]=Textura 2k 32bit escalar
Name[ca@valencia]=Textura 2k 32bit escalar
Name[cs]=Textura 2k 32bit skalární
Name[da]=Tekstur 2k 32bit scalar
Name[de]=Textur 2k 32bit scalar
-Name[el]=Texture 2k 32bit βαθμωτό
+Name[el]=Υφή 2k 32bit βαθμωτό
Name[en_GB]=Texture 2k 32bit scalar
Name[es]=Textura 2k 32bit escalar
Name[et]=Tekstuur 2k 32bit skalaar
Name[eu]=Testura 2k 32bit eskalarra
Name[fr]=Texture 2k 32bit scalaire
Name[gl]=Textura de 2k e 32 bits escalar
Name[hu]=Textúra 2k 32bit skalár
Name[it]=Trama 2k 32bit scalare
Name[ja]=テクスチャ 2k 32 ビットスカラー
Name[kk]=Текстура 2k 32 бит скаляр
Name[nb]=Tekstur 2k 32bit skalar
Name[nl]=Textuur 2k 32bit scalar
Name[pl]=Tekstura 2k 32bit skalar
Name[pt]=Textura 2k 32-bits escalar
Name[pt_BR]=Textura 2k 32bits escalar
Name[ru]=Текстура 2k 32 бит scalar
Name[sk]=Textúra 2k 32bit skalár
Name[sv]=Struktur 2k 32-bitar skalär
Name[tr]=Doku 2k 32bit sayısal
Name[uk]=Текстура 2k, 32-бітова, скалярна
Name[x-test]=xxTexture 2k 32bit scalarxx
Name[zh_CN]=纹理 2K 像素 32 位 scalar 色彩空间
Type=Link
URL[$e]=.source/Texture2k32bitscalar.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture2k8bitsrgb.desktop b/krita/data/templates/texture/Texture2k8bitsrgb.desktop
index 9da38fb090..702de2bd39 100755
--- a/krita/data/templates/texture/Texture2k8bitsrgb.desktop
+++ b/krita/data/templates/texture/Texture2k8bitsrgb.desktop
@@ -1,35 +1,35 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 2k 8bit srgb
Name[bs]=Tekstura 2k 8bit srgb
Name[ca]=Textura 2k 8bit SRGB
Name[ca@valencia]=Textura 2k 8bit SRGB
Name[cs]=Textura 2k 8bit srgb
Name[da]=Tekstur 2k 8bit srgb
Name[de]=Textur 2k 8bit srgb
-Name[el]=Texture 2k 8bit srgb
+Name[el]=Υφή 2k 8bit srgb
Name[en_GB]=Texture 2k 8bit srgb
Name[es]=Textura 2k 8bit srgb
Name[et]=Tekstuur 2k 8bit srgb
Name[eu]=Testura 2k 8bit sGBU
Name[fr]=Texture 2k 8bit srgb
Name[gl]=Textura de 2k e 8 bits SRGB
Name[hu]=Textúra 2k 8bit srgb
Name[it]=Trama 2k 8bit srgb
Name[ja]=テクスチャ 2k 8 ビット sRGB
Name[kk]=Текстура 2k 8 бит srgb
Name[nb]=Tekstur 2k 8bit srgb
Name[nl]=Textuur 2k 8bit srgb
Name[pl]=Tekstura 2k 8bit srgb
Name[pt]=Textura 2k 8-bits sRGB
Name[pt_BR]=Textura 2k 8bits sRGB
Name[ru]=Текстура 2k 8 бит srgb
Name[sk]=Textúra 2k 8bit srgb
Name[sv]=Struktur 2k 8-bitar SRGB
Name[tr]=Doku 2k 8bit srgb
Name[uk]=Текстура 2k, 8-бітова, srgb
Name[x-test]=xxTexture 2k 8bit srgbxx
Name[zh_CN]=纹理 2K 像素 8 位 sRGB 色彩空间
Type=Link
URL[$e]=.source/Texture2k8bitsrgb.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop b/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop
index 8e12bab233..704522b2f6 100644
--- a/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop
+++ b/krita/data/templates/texture/Texture4096x40968bitsrgb.desktop
@@ -1,31 +1,32 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 4096x4096 8bit srgb
Name[bs]=Tekstura 4096x4096 8bit srgb
Name[ca]=Textura 4096x4096 8bit SRGB
Name[ca@valencia]=Textura 4096x4096 8bit SRGB
Name[cs]=Textura 4096x4096 8bit srgb
Name[da]=Tekstur 4096x4096 8bit srgb
Name[de]=Textur 4096x4096 8bit srgb
+Name[el]=Υφή 4096x4096 8bit srgb
Name[en_GB]=Texture 4096x4096 8bit srgb
Name[es]=Textura 4096x4096 8bits srgb
Name[et]=Tekstuur 4096x4096 8bit srgb
Name[fr]=Texture 4096x4096 8bit srgb
Name[gl]=Textura de 4096×4096 e 8 bits SRGB
Name[it]=Trama 4096x4096 8bit srgb
Name[ja]=テクスチャ 4096x4096 8 ビット sRGB
Name[nb]=Tekstur 4096x4096 8bit srgb
Name[nl]=Textuur 4096x4096 8bit srgb
Name[pl]=Tekstura 4096x4096 8bit srgb
Name[pt]=Textura 4096x4096 8-bits sRGB
Name[pt_BR]=Textura 4096x4096 8bits sRGB
Name[ru]=Текстура 4096x4096 8 бит srgb
Name[sk]=Textúra 4096x4096 8bit srgb
Name[sv]=Struktur 4096 x 4096 8-bitar SRGB
Name[tr]=Doku 4096x4096 8bit srgb
Name[uk]=Текстура 4096⨯4096, 8-бітова, srgb
Name[x-test]=xxTexture 4096x4096 8bit srgbxx
Name[zh_CN]=纹理 4096x4096 像素 8 位 sRGB 色彩空间
Type=Link
URL[$e]=.source/Texture4096x40968bitsrgb.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture4k32bitscalar.desktop b/krita/data/templates/texture/Texture4k32bitscalar.desktop
index 093320a216..2519cdc53f 100755
--- a/krita/data/templates/texture/Texture4k32bitscalar.desktop
+++ b/krita/data/templates/texture/Texture4k32bitscalar.desktop
@@ -1,35 +1,35 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 4k 32bit scalar
Name[bs]=Tekstura 4k 32bit scalar
Name[ca]=Textura 4k 32bit escalar
Name[ca@valencia]=Textura 4k 32bit escalar
Name[cs]=Textura 4k 32bit skalární
Name[da]=Tekstur 4k 32bit scalar
Name[de]=Textur 4k 32bit scalar
-Name[el]=Texture 4k 32bit βαθμωτό
+Name[el]=Υφή 4k 32bit βαθμωτό
Name[en_GB]=Texture 4k 32bit scalar
Name[es]=Textura 4k 32bit escalar
Name[et]=Tekstuur 4k 32bit skalaar
Name[eu]=Testura 4k 32bit eskalarra
Name[fr]=Texture 4k 32bit scalaire
Name[gl]=Textura de 4k e 32 bits escalar
Name[hu]=Textúra 4k 32bit skalár
Name[it]=Trama 4k 32bit scalare
Name[ja]=テクスチャ 4k 32 ビットスカラー
Name[kk]=Текстура 4k 32 бит скаляр
Name[nb]=Tekstur 4k 32bit skalar
Name[nl]=Textuur 4k 32bit scalar
Name[pl]=Tekstura 4k 32bit skalar
Name[pt]=Textura 4k 32-bits escalar
Name[pt_BR]=Textura 4k 32bits escalar
Name[ru]=Текстура 4k 32 бит scalar
Name[sk]=Textúra 4k 32bit skalár
Name[sv]=Struktur 4k 32-bitar skalär
Name[tr]=Doku 4k 32bit sayısal
Name[uk]=Текстура 4k, 32-бітова, скалярна
Name[x-test]=xxTexture 4k 32bit scalarxx
Name[zh_CN]=纹理 4K 像素 32 位 scalar 色彩空间
Type=Link
URL[$e]=.source/Texture4k32bitscalar.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture4k8bitsrgb.desktop b/krita/data/templates/texture/Texture4k8bitsrgb.desktop
index e7b84ce75e..a9cdec0211 100755
--- a/krita/data/templates/texture/Texture4k8bitsrgb.desktop
+++ b/krita/data/templates/texture/Texture4k8bitsrgb.desktop
@@ -1,35 +1,35 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 4k 8bit srgb
Name[bs]=Tekstura 4k 8bit srgb
Name[ca]=Textura 4k 8bit SRGB
Name[ca@valencia]=Textura 4k 8bit SRGB
Name[cs]=Textura 4k 8bit srgb
Name[da]=Tekstur 4k 8bit srgb
Name[de]=Textur 4k 8bit srgb
-Name[el]=Texture 4k 8bit srgb
+Name[el]=Υφή 4k 8bit srgb
Name[en_GB]=Texture 4k 8bit srgb
Name[es]=Textura 4k 8bit srgb
Name[et]=Tekstuur 4k 8bit srgb
Name[eu]=Testura 4k 8bit sGBU
Name[fr]=Texture 4k 8bit srgb
Name[gl]=Textura de 4k e 8 bits SRGB
Name[hu]=Textúra 4k 8bit srgb
Name[it]=Trama 4k 8bit srgb
Name[ja]=テクスチャ 4k 8 ビット sRGB
Name[kk]=Текстура 4k 8 бит srgb
Name[nb]=Tekstur 4k 8bit srgb
Name[nl]=Textuur 4k 8bit srgb
Name[pl]=Tekstura 4k 8bit srgb
Name[pt]=Textura 4k 8-bits sRGB
Name[pt_BR]=Textura 4k 8bits sRGB
Name[ru]=Текстура 4k 8 бит srgb
Name[sk]=Textúra 4k 8bit srgb
Name[sv]=Struktur 4k 8-bitar SRGB
Name[tr]=Doku 4k 8bit srgb
Name[uk]=Текстура 4k, 8-бітова, srgb
Name[x-test]=xxTexture 4k 8bit srgbxx
Name[zh_CN]=纹理 4K 像素 8 位 srgb 色彩空间
Type=Link
URL[$e]=.source/Texture4k8bitsrgb.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture512x5128bitsrgb.desktop b/krita/data/templates/texture/Texture512x5128bitsrgb.desktop
index 7d431c6116..7dda03418b 100644
--- a/krita/data/templates/texture/Texture512x5128bitsrgb.desktop
+++ b/krita/data/templates/texture/Texture512x5128bitsrgb.desktop
@@ -1,31 +1,32 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 512x512 8bit srgb
Name[bs]=Tekstura 512x512 8bit srgb
Name[ca]=Textura 512x512 8bit SRGB
Name[ca@valencia]=Textura 512x512 8bit SRGB
Name[cs]=Textura 512x512 8bit srgb
Name[da]=Tekstur 512x512 8bit srgb
Name[de]=Textur 512x512 8bit srgb
+Name[el]=Υφή 512x512 8bit srgb
Name[en_GB]=Texture 512x512 8bit srgb
Name[es]=Textura 512x512 8bits srgb
Name[et]=Tekstuur 512x512 8bit srgb
Name[fr]=Texture 512x512 8bit srgb
Name[gl]=Textura de 512×512 e 8 bits SRGB
Name[it]=Trama 512x512 8bit srgb
Name[ja]=テクスチャ 512x512 8 ビット sRGB
Name[nb]=Tekstur 512x512 8bit srgb
Name[nl]=Textuur 512x512 8bit srgb
Name[pl]=Tekstura 512x512 8bit srgb
Name[pt]=Textura 512x512 8-bits sRGB
Name[pt_BR]=Textura 512x512 8bits sRGB
Name[ru]=Текстура 512x512 8 бит srgb
Name[sk]=Textúra 512x512 8bit srgb
Name[sv]=Struktur 512 x 512 8-bitar SRGB
Name[tr]=Doku 512x512 8bit srgb
Name[uk]=Текстура 512⨯512, 8-бітова, srgb
Name[x-test]=xxTexture 512x512 8bit srgbxx
Name[zh_CN]=纹理 512x512 像素 8 位 sRGB 色彩空间
Type=Link
URL[$e]=.source/Texture512x5128bitsrgb.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture8k32bitscalar.desktop b/krita/data/templates/texture/Texture8k32bitscalar.desktop
index 07f4930507..a9c7eeced6 100755
--- a/krita/data/templates/texture/Texture8k32bitscalar.desktop
+++ b/krita/data/templates/texture/Texture8k32bitscalar.desktop
@@ -1,35 +1,35 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 8k 32bit scalar
Name[bs]=Tekstura 8k 32bit scalar
Name[ca]=Textura 8k 32bit escalar
Name[ca@valencia]=Textura 8k 32bit escalar
Name[cs]=Textura 8k 32bit skalární
Name[da]=Tekstur 8k 32bit scalar
Name[de]=Textur 8k 32bit scalar
-Name[el]=Texture 8k 32bit βαθμωτό
+Name[el]=Υφή 8k 32bit βαθμωτό
Name[en_GB]=Texture 8k 32bit scalar
Name[es]=Textura 8k 32 bit escalar
Name[et]=Tekstuur 8k 32bit skalaar
Name[eu]=Testura 8k 32bit eskalarra
Name[fr]=Texture 8k 32bit scalaire
Name[gl]=Textura de 8k e 32 bits escalar
Name[hu]=Textúra 8k 32bit skalár
Name[it]=Trama 8k 32bit scalare
Name[ja]=テクスチャ 8k 32 ビットスカラー
Name[kk]=Текстура 8k 32 бит скаляр
Name[nb]=Tekstur 8k 32bit skalar
Name[nl]=Textuur 8k 32bit scalar
Name[pl]=Tekstura 8k 32bit skalar
Name[pt]=Textura 8k 32-bits escalar
Name[pt_BR]=Textura 8k 32bits escalar
Name[ru]=Текстура 8k 32 бит scalar
Name[sk]=Textúra 8k 32bit skalár
Name[sv]=Struktur 8k 32-bitar skalär
Name[tr]=Doku 8k 32bit sayısal
Name[uk]=Текстура 8k, 32-бітова, скалярна
Name[x-test]=xxTexture 8k 32bit scalarxx
Name[zh_CN]=纹理 8K 像素 32 位 scalar 色彩空间
Type=Link
URL[$e]=.source/Texture8k32bitscalar.kra
X-KDE-Hidden=false
diff --git a/krita/data/templates/texture/Texture8k8bitsrgb.desktop b/krita/data/templates/texture/Texture8k8bitsrgb.desktop
index 4b78395b8e..0f07543436 100755
--- a/krita/data/templates/texture/Texture8k8bitsrgb.desktop
+++ b/krita/data/templates/texture/Texture8k8bitsrgb.desktop
@@ -1,36 +1,36 @@
[Desktop Entry]
Icon=template_texture
Name=Texture 8k 8bit srgb
Name[bs]=Tekstura 8k 8bit srgb
Name[ca]=Textura 8k 8bit SRGB
Name[ca@valencia]=Textura 8k 8bit SRGB
Name[cs]=Textura 8k 8bit srgb
Name[da]=Tekstur 8k 8bit srgb
Name[de]=Textur 8k 8bit srgb
-Name[el]=Texture 8k 8bit srgb
+Name[el]=Υφή 8k 8bit srgb
Name[en_GB]=Texture 8k 8bit srgb
Name[es]=Textura 8k 8bit srgb
Name[et]=Tekstuur 8k 8bit srgb
Name[eu]=Testura 8k 8bit sGBU
Name[fr]=Texture 8k 8bit srgb
Name[gl]=Textura de 8k e 8 bits SRGB
Name[hu]=Textúra 8k 8bit srgb
Name[it]=Trama 8k 8bit srgb
Name[ja]=テクスチャ 8k 8 ビット sRGB
Name[kk]=Текстура 8k 8 бит srgb
Name[nb]=Tekstur 8k 8bit srgb
Name[nl]=Textuur 8k 8bit srgb
Name[pl]=Tekstura 8k 8bit srgb
Name[pt]=Textura 8k 8-bits sRGB
Name[pt_BR]=Textura 8k 8bits sRGB
Name[ru]=Текстура 8k 8 бит srgb
Name[sk]=Textúra 8k 8bit srgb
Name[sl]=Tekstura 8k 8 bitov srgb
Name[sv]=Struktur 8k 8-bitar SRGB
Name[tr]=Doku 8k 8bit srgb
Name[uk]=Текстура 8k, 8-бітова, srgb
Name[x-test]=xxTexture 8k 8bit srgbxx
Name[zh_CN]=纹理 8K 像素 8 位 srgb 色彩空间
Type=Link
URL[$e]=.source/Texture8k8bitsrgb.kra
X-KDE-Hidden=false
diff --git a/krita/krita.action b/krita/krita.action
index 3c302c1e66..cbc3dfbe8c 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -1,3078 +1,3090 @@
<?xml version="1.0" encoding="UTF-8"?>
<ActionCollection version="2" name="Krita">
<Actions category="General">
<text>General</text>
<Action name="open_resources_directory">
<icon></icon>
<text>Open Resources Folder</text>
<whatsThis>Opens a file browser at the location Krita saves resources such as brushes to.</whatsThis>
<toolTip>Opens a file browser at the location Krita saves resources such as brushes to.</toolTip>
<iconText>Open Resources Folder</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_blacklist_cleanup">
<icon></icon>
<text>Cleanup removed files...</text>
<whatsThis></whatsThis>
<toolTip>Cleanup removed files</toolTip>
<iconText>Cleanup removed files</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="windows_cascade">
<icon></icon>
<text>C&amp;ascade</text>
<whatsThis></whatsThis>
<toolTip>Cascade</toolTip>
<iconText>Cascade</iconText>
<activationFlags>10</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="windows_tile">
<icon></icon>
<text>&amp;Tile</text>
<whatsThis></whatsThis>
<toolTip>Tile</toolTip>
<iconText>Tile</iconText>
<activationFlags>10</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_bundle">
<icon></icon>
<text>Create Resource Bundle...</text>
<whatsThis></whatsThis>
<toolTip>Create Resource Bundle</toolTip>
<iconText>Create Resource Bundle</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mainToolBar">
<icon></icon>
<text>Show File Toolbar</text>
<whatsThis></whatsThis>
<toolTip>Show File Toolbar</toolTip>
<iconText>Show File Toolbar</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_color_selector">
<icon></icon>
<text>Show color selector</text>
<whatsThis></whatsThis>
<toolTip>Show color selector</toolTip>
<iconText>Show color selector</iconText>
<shortcut>Shift+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_mypaint_shade_selector">
<icon></icon>
<text>Show MyPaint shade selector</text>
<whatsThis></whatsThis>
<toolTip>Show MyPaint shade selector</toolTip>
<iconText>Show MyPaint shade selector</iconText>
<shortcut>Shift+M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_minimal_shade_selector">
<icon></icon>
<text>Show minimal shade selector</text>
<whatsThis></whatsThis>
<toolTip>Show minimal shade selector</toolTip>
<iconText>Show minimal shade selector</iconText>
<shortcut>Shift+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_color_history">
<icon></icon>
<text>Show color history</text>
<whatsThis></whatsThis>
<toolTip>Show color history</toolTip>
<iconText>Show color history</iconText>
<shortcut>H</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_common_colors">
<icon></icon>
<text>Show common colors</text>
<whatsThis></whatsThis>
<toolTip>Show common colors</toolTip>
<iconText>Show common colors</iconText>
<shortcut>U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_tool_options">
<icon></icon>
<text>Show Tool Options</text>
<whatsThis></whatsThis>
<toolTip>Show Tool Options</toolTip>
<iconText>Show Tool Options</iconText>
<shortcut>\</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_brush_editor">
<icon></icon>
<text>Show Brush Editor</text>
<whatsThis></whatsThis>
<toolTip>Show Brush Editor</toolTip>
<iconText>Show Brush Editor</iconText>
<shortcut>F5</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_brush_presets">
<icon></icon>
<text>Show Brush Presets</text>
<whatsThis></whatsThis>
<toolTip>Show Brush Presets</toolTip>
<iconText>Show Brush Presets</iconText>
<shortcut>F6</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="tablet_debugger">
<icon></icon>
<text>Toggle Tablet Debugger</text>
<whatsThis></whatsThis>
<toolTip>Toggle Tablet Debugger</toolTip>
<iconText>Toggle Tablet Debugger</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="buginfo">
<icon></icon>
<text>Show system information for bug reports.</text>
<whatsThis></whatsThis>
<toolTip>Show system information for bug reports.</toolTip>
<iconText>Show system information for bug reports.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rename_composition">
<icon></icon>
<text>Rename Composition...</text>
<whatsThis></whatsThis>
<toolTip>Rename Composition</toolTip>
<iconText>Rename Composition</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="update_composition">
<icon></icon>
<text>Update Composition</text>
<whatsThis></whatsThis>
<toolTip>Update Composition</toolTip>
<iconText>Update Composition</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Painting">
<text>Painting</text>
<Action name="make_brush_color_lighter">
<icon>lightness-increase</icon>
<text>Make brush color lighter</text>
<whatsThis></whatsThis>
<toolTip>Make brush color lighter</toolTip>
<iconText>Make brush color lighter</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>L</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_darker">
<icon>lightness-decrease</icon>
<text>Make brush color darker</text>
<whatsThis></whatsThis>
<toolTip>Make brush color darker</toolTip>
<iconText>Make brush color darker</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>K</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_saturated">
<icon></icon>
<text>Make brush color more saturated</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more saturated</toolTip>
<iconText>Make brush color more saturated</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_desaturated">
<icon></icon>
<text>Make brush color more desaturated</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more desaturated</toolTip>
<iconText>Make brush color more desaturated</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shift_brush_color_clockwise">
<icon></icon>
<text>Shift brush color hue clockwise</text>
<whatsThis></whatsThis>
<toolTip>Shift brush color hue clockwise</toolTip>
<iconText>Shift brush color hue clockwise</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shift_brush_color_counter_clockwise">
<icon></icon>
<text>Shift brush color hue counter-clockwise</text>
<whatsThis></whatsThis>
<toolTip>Shift brush color hue counter-clockwise</toolTip>
<iconText>Shift brush color hue counter-clockwise</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_redder">
<icon></icon>
<text>Make brush color more red</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more red</toolTip>
<iconText>Make brush color more red</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_greener">
<icon></icon>
<text>Make brush color more green</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more green</toolTip>
<iconText>Make brush color more green</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_bluer">
<icon></icon>
<text>Make brush color more blue</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more blue</toolTip>
<iconText>Make brush color more blue</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="make_brush_color_yellower">
<icon></icon>
<text>Make brush color more yellow</text>
<whatsThis></whatsThis>
<toolTip>Make brush color more yellow</toolTip>
<iconText>Make brush color more yellow</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="increase_opacity">
<icon>opacity-increase</icon>
<text>Increase opacity</text>
<whatsThis></whatsThis>
<toolTip>Increase opacity</toolTip>
<iconText>Increase opacity</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>O</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="decrease_opacity">
<icon>opacity-decrease</icon>
<text>Decrease opacity</text>
<whatsThis></whatsThis>
<toolTip>Decrease opacity</toolTip>
<iconText>Decrease opacity</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="erase_action">
<icon>draw-eraser</icon>
<text>Set eraser mode</text>
<whatsThis></whatsThis>
<toolTip>Set eraser mode</toolTip>
<iconText>Set eraser mode</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>E</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reload_preset_action">
<icon>view-refresh</icon>
<text>Reload Original Preset</text>
<whatsThis></whatsThis>
<toolTip>Reload Original Preset</toolTip>
<iconText>Reload Original Preset</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="preserve_alpha">
<icon>transparency-unlocked</icon>
<text>Preserve Alpha</text>
<whatsThis></whatsThis>
<toolTip>Preserve Alpha</toolTip>
<iconText>Preserve Alpha</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="disable_pressure">
<icon>transform_icons_penPressure</icon>
<text>Use Pen Pressure</text>
<whatsThis></whatsThis>
<toolTip>Use Pen Pressure</toolTip>
<iconText>Use Pen Pressure</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="hmirror_action">
<icon>symmetry-horizontal</icon>
<text>Horizontal Mirror Tool</text>
<whatsThis></whatsThis>
<toolTip>Horizontal Mirror Tool</toolTip>
<iconText>Horizontal Mirror Tool</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="vmirror_action">
<icon>symmetry-vertical</icon>
<text>Vertical Mirror Tool</text>
<whatsThis></whatsThis>
<toolTip>Vertical Mirror Tool</toolTip>
<iconText>Vertical Mirror Tool</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorX-hideDecorations">
<icon></icon>
<text>Hide Mirror X Line</text>
<whatsThis></whatsThis>
<toolTip>Hide Mirror X Line</toolTip>
<iconText>Hide Mirror X Line</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorY-hideDecorations">
<icon></icon>
<text>Hide Mirror Y Line</text>
<whatsThis></whatsThis>
<toolTip>Hide Mirror Y Line</toolTip>
<iconText>Hide Mirror Y Line</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorX-lock">
<icon></icon>
<text>Lock</text>
<whatsThis></whatsThis>
<toolTip>Lock X Line</toolTip>
<iconText>Lock X Line</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorY-lock">
<icon></icon>
<text>Lock Y Line</text>
<whatsThis></whatsThis>
<toolTip>Lock Y Line</toolTip>
<iconText>Lock Y Line</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorX-moveToCenter">
<icon></icon>
<text>Move to Canvas Center</text>
<whatsThis></whatsThis>
<toolTip>Move to Canvas Center X</toolTip>
<iconText>Move to Canvas Center X</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorY-moveToCenter">
<icon></icon>
<text>Move to Canvas Center Y</text>
<whatsThis></whatsThis>
<toolTip>Move to Canvas Center Y</toolTip>
<iconText>Move to Canvas Center Y</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="invert">
<icon></icon>
<text>&amp;Invert Selection</text>
<whatsThis></whatsThis>
<toolTip>Invert current selection</toolTip>
<iconText>Invert Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut>Ctrl+Shift+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle-selection-overlay-mode">
<icon></icon>
<text>&amp;Toggle Selection Display Mode</text>
<whatsThis></whatsThis>
<toolTip>Toggle Selection Display Mode</toolTip>
<iconText>Toggle Selection Display Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="next_favorite_preset">
<icon></icon>
<text>Next Favourite Preset</text>
<whatsThis></whatsThis>
<toolTip>Next Favourite Preset</toolTip>
<iconText>Next Favourite Preset</iconText>
<shortcut>,</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="previous_favorite_preset">
<icon></icon>
<text>Previous Favourite Preset</text>
<whatsThis></whatsThis>
<toolTip>Previous Favourite Preset</toolTip>
<iconText>Previous Favourite Preset</iconText>
<shortcut>.</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="previous_preset">
<icon>preset-switcher</icon>
<text>Switch to Previous Preset</text>
<whatsThis></whatsThis>
<toolTip>Switch to Previous Preset</toolTip>
<iconText>Switch to Previous Preset</iconText>
<shortcut>/</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="BrushesAndStuff">
<icon></icon>
<text>Hide Brushes and Stuff Toolbar</text>
<whatsThis></whatsThis>
<toolTip>Hide Brushes and Stuff Toolbar</toolTip>
<iconText>Hide Brushes and Stuff Toolbar</iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reset_fg_bg">
<icon></icon>
<text>Reset Foreground and Background Color</text>
<whatsThis></whatsThis>
<toolTip>Reset Foreground and Background Color</toolTip>
<iconText>Reset Foreground and Background Color</iconText>
<shortcut>D</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_fg_bg">
<icon></icon>
<text>Swap Foreground and Background Color</text>
<whatsThis></whatsThis>
<toolTip>Swap Foreground and Background Color</toolTip>
<iconText>Swap Foreground and Background Color</iconText>
<shortcut>X</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_weighted_brush_smoothing">
<icon>smoothing-weighted</icon>
<text>Brush Smoothing: Weighted</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Weighted</toolTip>
<iconText>Brush Smoothing: Weighted</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_no_brush_smoothing">
<icon>smoothing-no</icon>
<text>Brush Smoothing: Disabled</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Disabled</toolTip>
<iconText>Brush Smoothing: Disabled</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_stabilizer_brush_smoothing">
<icon>smoothing-stabilizer</icon>
<text>Brush Smoothing: Stabilizer</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Stabilizer</toolTip>
<iconText>Brush Smoothing: Stabilizer</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="decrease_brush_size">
<icon>brushsize-decrease</icon>
<text>Decrease Brush Size</text>
<whatsThis></whatsThis>
<toolTip>Decrease Brush Size</toolTip>
<iconText>Decrease Brush Size</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>[</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_simple_brush_smoothing">
<icon>smoothing-basic</icon>
<text>Brush Smoothing: Basic</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Basic</toolTip>
<iconText>Brush Smoothing: Basic</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="increase_brush_size">
<icon>brushsize-increase</icon>
<text>Increase Brush Size</text>
<whatsThis></whatsThis>
<toolTip>Increase Brush Size</toolTip>
<iconText>Increase Brush Size</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>]</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_assistant">
<icon></icon>
<text>Toggle Assistant</text>
<whatsThis></whatsThis>
<toolTip>Toggle Assistant</toolTip>
<iconText>ToggleAssistant</iconText>
<shortcut>Ctrl+Shift+L</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="undo_polygon_selection">
<icon></icon>
<text>Undo Polygon Selection Points</text>
<whatsThis></whatsThis>
<toolTip>Undo Polygon Selection Points</toolTip>
<iconText>Undo Polygon Selection Points</iconText>
<shortcut>Shift+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_foreground_color_opacity">
<icon></icon>
<text>Fill with Foreground Color (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Foreground Color (Opacity)</toolTip>
<iconText>Fill with Foreground Color (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Shift+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_background_color_opacity">
<icon></icon>
<text>Fill with Background Color (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Background Color (Opacity)</toolTip>
<iconText>Fill with Background Color (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_pattern_opacity">
<icon></icon>
<text>Fill with Pattern (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Pattern (Opacity)</toolTip>
<iconText>Fill with Pattern (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_selection_to_shape">
<icon></icon>
<text>Convert &amp;to Shape</text>
<whatsThis></whatsThis>
<toolTip>Convert to Shape</toolTip>
<iconText>Convert to Shape</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_opaque">
<icon></icon>
<text>&amp;Select Opaque</text>
<whatsThis></whatsThis>
<toolTip>Select Opaque</toolTip>
<iconText>Select Opaque</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show-global-selection-mask">
<icon></icon>
<text>&amp;Show Global Selection Mask</text>
<whatsThis></whatsThis>
<toolTip>Shows global selection as a usual selection mask in &lt;interface>Layers&lt;/interface> docker</toolTip>
<iconText>Show Global Selection Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Filters">
<text>Filters</text>
<Action name="krita_filter_colortoalpha">
<icon>color-to-alpha</icon>
<text>&amp;Color to Alpha...</text>
<whatsThis></whatsThis>
<toolTip>Color to Alpha</toolTip>
<iconText>Color to Alpha</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_top edge detections">
<icon></icon>
<text>&amp;Top Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Top Edge Detection</toolTip>
<iconText>Top Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_indexcolors">
<icon></icon>
<text>&amp;Index Colors...</text>
<whatsThis></whatsThis>
<toolTip>Index Colors</toolTip>
<iconText>Index Colors</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss horizontal only">
<icon></icon>
<text>Emboss Horizontal &amp;Only</text>
<whatsThis></whatsThis>
<toolTip>Emboss Horizontal Only</toolTip>
<iconText>Emboss Horizontal Only</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_dodge">
<icon></icon>
<text>D&amp;odge</text>
<whatsThis></whatsThis>
<toolTip>Dodge</toolTip>
<iconText>Dodge</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_sharpen">
<icon></icon>
<text>&amp;Sharpen</text>
<whatsThis></whatsThis>
<toolTip>Sharpen</toolTip>
<iconText>Sharpen</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_burn">
<icon></icon>
<text>B&amp;urn</text>
<whatsThis></whatsThis>
<toolTip>Burn</toolTip>
<iconText>Burn</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_mean removal">
<icon></icon>
<text>&amp;Mean Removal</text>
<whatsThis></whatsThis>
<toolTip>Mean Removal</toolTip>
<iconText>Mean Removal</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_gaussian blur">
<icon></icon>
<text>&amp;Gaussian Blur...</text>
<whatsThis></whatsThis>
<toolTip>Gaussian Blur</toolTip>
<iconText>Gaussian Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss all directions">
<icon></icon>
<text>Emboss &amp;in All Directions</text>
<whatsThis></whatsThis>
<toolTip>Emboss in All Directions</toolTip>
<iconText>Emboss in All Directions</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_smalltiles">
<icon></icon>
<text>&amp;Small Tiles...</text>
<whatsThis></whatsThis>
<toolTip>Small Tiles</toolTip>
<iconText>Small Tiles</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_levels">
<icon></icon>
<text>&amp;Levels...</text>
<whatsThis></whatsThis>
<toolTip>Levels</toolTip>
<iconText>Levels</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+L</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_sobel">
<icon></icon>
<text>&amp;Sobel...</text>
<whatsThis></whatsThis>
<toolTip>Sobel</toolTip>
<iconText>Sobel</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_wave">
<icon></icon>
<text>&amp;Wave...</text>
<whatsThis></whatsThis>
<toolTip>Wave</toolTip>
<iconText>Wave</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_motion blur">
<icon></icon>
<text>&amp;Motion Blur...</text>
<whatsThis></whatsThis>
<toolTip>Motion Blur</toolTip>
<iconText>Motion Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_perchannel">
<icon></icon>
<text>&amp;Color Adjustment curves...</text>
<whatsThis></whatsThis>
<toolTip>Color Adjustment curves</toolTip>
<iconText>Color Adjustment curves</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_pixelize">
<icon></icon>
<text>Pi&amp;xelize...</text>
<whatsThis></whatsThis>
<toolTip>Pixelize</toolTip>
<iconText>Pixelize</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss laplascian">
<icon></icon>
<text>Emboss (&amp;Laplacian)</text>
<whatsThis></whatsThis>
<toolTip>Emboss (Laplacian)</toolTip>
<iconText>Emboss (Laplacian)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_left edge detections">
<icon></icon>
<text>&amp;Left Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Left Edge Detection</toolTip>
<iconText>Left Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_blur">
<icon></icon>
<text>&amp;Blur...</text>
<whatsThis></whatsThis>
<toolTip>Blur</toolTip>
<iconText>Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_raindrops">
<icon></icon>
<text>&amp;Raindrops...</text>
<whatsThis></whatsThis>
<toolTip>Raindrops</toolTip>
<iconText>Raindrops</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_bottom edge detections">
<icon></icon>
<text>&amp;Bottom Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Bottom Edge Detection</toolTip>
<iconText>Bottom Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_noise">
<icon></icon>
<text>&amp;Random Noise...</text>
<whatsThis></whatsThis>
<toolTip>Random Noise</toolTip>
<iconText>Random Noise</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_brightnesscontrast">
<icon></icon>
<text>&amp;Brightness/Contrast curve...</text>
<whatsThis></whatsThis>
<toolTip>Brightness/Contrast curve</toolTip>
<iconText>Brightness/Contrast curve</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_colorbalance">
<icon></icon>
<text>Colo&amp;r Balance..</text>
<whatsThis></whatsThis>
<toolTip>Color Balance..</toolTip>
<iconText>Color Balance..</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+B</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_phongbumpmap">
<icon></icon>
<text>&amp;Phong Bumpmap...</text>
<whatsThis></whatsThis>
<toolTip>Phong Bumpmap</toolTip>
<iconText>Phong Bumpmap</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_desaturate">
<icon></icon>
<text>&amp;Desaturate</text>
<whatsThis></whatsThis>
<toolTip>Desaturate</toolTip>
<iconText>Desaturate</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_colortransfer">
<icon></icon>
<text>Color &amp;Transfer...</text>
<whatsThis></whatsThis>
<toolTip>Color Transfer</toolTip>
<iconText>Color Transfer</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss vertical only">
<icon></icon>
<text>Emboss &amp;Vertical Only</text>
<whatsThis></whatsThis>
<toolTip>Emboss Vertical Only</toolTip>
<iconText>Emboss Vertical Only</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_lens blur">
<icon></icon>
<text>&amp;Lens Blur...</text>
<whatsThis></whatsThis>
<toolTip>Lens Blur</toolTip>
<iconText>Lens Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_minimize">
<icon></icon>
<text>M&amp;inimize Channel</text>
<whatsThis></whatsThis>
<toolTip>Minimize Channel</toolTip>
<iconText>Minimize Channel</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_maximize">
<icon></icon>
<text>M&amp;aximize Channel</text>
<whatsThis></whatsThis>
<toolTip>Maximize Channel</toolTip>
<iconText>Maximize Channel</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_oilpaint">
<icon></icon>
<text>&amp;Oilpaint...</text>
<whatsThis></whatsThis>
<toolTip>Oilpaint</toolTip>
<iconText>Oilpaint</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_right edge detections">
<icon></icon>
<text>&amp;Right Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Right Edge Detection</toolTip>
<iconText>Right Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_autocontrast">
<icon></icon>
<text>&amp;Auto Contrast</text>
<whatsThis></whatsThis>
<toolTip>Auto Contrast</toolTip>
<iconText>Auto Contrast</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_roundcorners">
<icon></icon>
<text>&amp;Round Corners...</text>
<whatsThis></whatsThis>
<toolTip>Round Corners</toolTip>
<iconText>Round Corners</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_unsharp">
<icon></icon>
<text>&amp;Unsharp Mask...</text>
<whatsThis></whatsThis>
<toolTip>Unsharp Mask</toolTip>
<iconText>Unsharp Mask</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss">
<icon></icon>
<text>&amp;Emboss with Variable Depth...</text>
<whatsThis></whatsThis>
<toolTip>Emboss with Variable Depth</toolTip>
<iconText>Emboss with Variable Depth</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss horizontal and vertical">
<icon></icon>
<text>Emboss &amp;Horizontal &amp;&amp; Vertical</text>
<whatsThis></whatsThis>
<toolTip>Emboss Horizontal &amp; Vertical</toolTip>
<iconText>Emboss Horizontal &amp; Vertical</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_randompick">
<icon></icon>
<text>Random &amp;Pick...</text>
<whatsThis></whatsThis>
<toolTip>Random Pick</toolTip>
<iconText>Random Pick</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_gaussiannoisereducer">
<icon></icon>
<text>&amp;Gaussian Noise Reduction...</text>
<whatsThis></whatsThis>
<toolTip>Gaussian Noise Reduction</toolTip>
<iconText>Gaussian Noise Reduction</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_posterize">
<icon></icon>
<text>&amp;Posterize...</text>
<whatsThis></whatsThis>
<toolTip>Posterize</toolTip>
<iconText>Posterize</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_waveletnoisereducer">
<icon></icon>
<text>&amp;Wavelet Noise Reducer...</text>
<whatsThis></whatsThis>
<toolTip>Wavelet Noise Reducer</toolTip>
<iconText>Wavelet Noise Reducer</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_hsvadjustment">
<icon></icon>
<text>&amp;HSV Adjustment...</text>
<whatsThis></whatsThis>
<toolTip>HSV Adjustment</toolTip>
<iconText>HSV Adjustment</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="tool-shortcuts">
<text>Tool Shortcuts</text>
<Action name="KritaShape/KisToolDyna">
<icon></icon>
<text>Dynamic Brush Tool</text>
<whatsThis></whatsThis>
<toolTip>Dynamic Brush Tool</toolTip>
<iconText>Dynamic Brush Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolCrop">
<icon></icon>
<text>Crop Tool</text>
<whatsThis></whatsThis>
<toolTip>Crop the image to an area</toolTip>
<iconText>Crop the image to an area</iconText>
<shortcut>C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPolygon">
<icon></icon>
<text>Polygon Tool</text>
<whatsThis></whatsThis>
<toolTip>Polygon Tool. Shift-mouseclick ends the polygon.</toolTip>
<iconText>Polygon Tool. Shift-mouseclick ends the polygon.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ReferencesTool">
<icon></icon>
<text>References</text>
<whatsThis></whatsThis>
<toolTip>References</toolTip>
<iconText>References</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolRectangle">
<icon></icon>
<text>Rectangle Tool</text>
<whatsThis></whatsThis>
<toolTip>Rectangle Tool</toolTip>
<iconText>Rectangle Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolMultiBrush">
<icon></icon>
<text>Multibrush Tool</text>
<whatsThis></whatsThis>
<toolTip>Multibrush Tool</toolTip>
<iconText>Multibrush Tool</iconText>
<shortcut>Q</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolLazyBrush">
<icon></icon>
<text>Lazy Brush Tool</text>
<whatsThis></whatsThis>
<toolTip>Lazy Brush Tool</toolTip>
<iconText>Lazy Brush Tool</iconText>
<shortcut></shortcut>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolSmartPatch">
<icon></icon>
<text>Smart Patch Tool</text>
<whatsThis></whatsThis>
<toolTip>Smart Patch Tool</toolTip>
<iconText>Smart Patch Tool</iconText>
<shortcut></shortcut>
<statusTip></statusTip>
</Action>
<Action name="PanTool">
<icon></icon>
<text>Pan Tool</text>
<whatsThis></whatsThis>
<toolTip>Pan Tool</toolTip>
<iconText>Pan Tool</iconText>
<shortcut></shortcut>
<statusTip></statusTip>
</Action>
<Action name="InteractionTool">
<icon></icon>
<text>Shape Manipulation Tool</text>
<whatsThis></whatsThis>
<toolTip>Shape Manipulation Tool</toolTip>
<iconText>Shape Manipulation Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaSelected/KisToolColorPicker">
<icon></icon>
<text>Color Picker</text>
<whatsThis></whatsThis>
<toolTip>Select a color from the image or current layer</toolTip>
<iconText>Select a color from the image or current layer</iconText>
<shortcut>P</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="TextTool">
<icon></icon>
<text>Text Editing Tool</text>
<whatsThis></whatsThis>
<toolTip>Text editing</toolTip>
<iconText>Text editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectOutline">
<icon></icon>
<text>Outline Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Outline Selection Tool</toolTip>
<iconText>Outline Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ArtisticTextTool">
<icon></icon>
<text>Artistic Text Tool</text>
<whatsThis></whatsThis>
<toolTip>Artistic text editing</toolTip>
<iconText>Artistic text editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectPath">
<icon></icon>
<text>Bezier Curve Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Select a </toolTip>
<iconText>Bezier Curve Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectSimilar">
<icon></icon>
<text>Similar Color Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Select a </toolTip>
<iconText>Similar Color Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaFill/KisToolFill">
<icon></icon>
<text>Fill Tool</text>
<whatsThis></whatsThis>
<toolTip>Fill a contiguous area of color with a color, or fill a selection.</toolTip>
<iconText>Fill a contiguous area of color with a color, or fill a selection.</iconText>
<shortcut>F</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolLine">
<icon></icon>
<text>Line Tool</text>
<whatsThis></whatsThis>
<toolTip>Line Tool</toolTip>
<iconText>Line Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPencil">
<icon></icon>
<text>Freehand Path Tool</text>
<whatsThis></whatsThis>
<toolTip>Freehand Path Tool</toolTip>
<iconText>Freehand Path Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPath">
<icon></icon>
<text>Bezier Curve Tool</text>
<whatsThis></whatsThis>
<toolTip>Bezier Curve Tool. Shift-mouseclick ends the curve.</toolTip>
<iconText>Bezier Curve Tool. Shift-mouseclick ends the curve.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolEllipse">
<icon></icon>
<text>Ellipse Tool</text>
<whatsThis></whatsThis>
<toolTip>Ellipse Tool</toolTip>
<iconText>Ellipse Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolBrush">
<icon></icon>
<text>Freehand Brush Tool</text>
<whatsThis></whatsThis>
<toolTip>Freehand Brush Tool</toolTip>
<iconText>Freehand Brush Tool</iconText>
<shortcut>B</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="CreateShapesTool">
<icon></icon>
<text>Create object</text>
<whatsThis></whatsThis>
<toolTip>Create object</toolTip>
<iconText>Create object</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectElliptical">
<icon></icon>
<text>Elliptical Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Elliptical Selection Tool</toolTip>
<iconText>Elliptical Selection Tool</iconText>
<shortcut>J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectContiguous">
<icon></icon>
<text>Contiguous Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Contiguous Selection Tool</toolTip>
<iconText>Contiguous Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KarbonPatternTool">
<icon></icon>
<text>Pattern editing</text>
<whatsThis></whatsThis>
<toolTip>Pattern editing</toolTip>
<iconText>Pattern editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ReviewTool">
<icon></icon>
<text>Review</text>
<whatsThis></whatsThis>
<toolTip>Review</toolTip>
<iconText>Review</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaFill/KisToolGradient">
<icon></icon>
<text>Draw a gradient.</text>
<whatsThis></whatsThis>
<toolTip>Draw a gradient.</toolTip>
<iconText>Draw a gradient.</iconText>
<shortcut>G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectPolygonal">
<icon></icon>
<text>Polygonal Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Polygonal Selection Tool</toolTip>
<iconText>Polygonal Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolMeasure">
<icon></icon>
<text>Measurement Tool</text>
<whatsThis></whatsThis>
<toolTip>Measure the distance between two points</toolTip>
<iconText>Measure the distance between two points</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectRectangular">
<icon></icon>
<text>Rectangular Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Rectangular Selection Tool</toolTip>
<iconText>Rectangular Selection Tool</iconText>
<shortcut>Ctrl+R</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaTransform/KisToolMove">
<icon></icon>
<text>Move Tool</text>
<whatsThis></whatsThis>
<toolTip>Move a layer</toolTip>
<iconText>Move a layer</iconText>
<shortcut>T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="VectorTool">
<icon></icon>
<text>Vector Image Tool</text>
<whatsThis></whatsThis>
<toolTip>Vector Image (EMF/WMF/SVM/SVG) tool</toolTip>
<iconText>Vector Image (EMF/WMF/SVM/SVG) tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KarbonCalligraphyTool">
<icon></icon>
<text>Calligraphy</text>
<whatsThis></whatsThis>
<toolTip>Calligraphy</toolTip>
<iconText>Calligraphy</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="PathTool">
<icon></icon>
<text>Path editing</text>
<whatsThis></whatsThis>
<toolTip>Path editing</toolTip>
<iconText>Path editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ZoomTool">
<icon></icon>
<text>Zoom Tool</text>
<whatsThis></whatsThis>
<toolTip>Zoom Tool</toolTip>
<iconText>Zoom Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPolyline">
<icon></icon>
<text>Polyline Tool</text>
<whatsThis></whatsThis>
<toolTip>Polyline Tool. Shift-mouseclick ends the polyline.</toolTip>
<iconText>Polyline Tool. Shift-mouseclick ends the polyline.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolTransform">
<icon></icon>
<text>Transform Tool</text>
<whatsThis></whatsThis>
<toolTip>Transform a layer or a selection</toolTip>
<iconText>Transform a layer or a selection</iconText>
<shortcut>Ctrl+T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisRulerAssistantTool">
<icon></icon>
<text>Ruler assistant editor tool</text>
<whatsThis></whatsThis>
<toolTip>Ruler assistant editor tool</toolTip>
<iconText>Ruler assistant editor tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolText">
<icon></icon>
<text>Text tool</text>
<whatsThis></whatsThis>
<toolTip>Text tool</toolTip>
<iconText>Text tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KarbonGradientTool">
<icon></icon>
<text>Gradient Editing Tool</text>
<whatsThis></whatsThis>
<toolTip>Gradient editing</toolTip>
<iconText>Gradient editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Blending Modes">
<text>Blending Modes</text>
<!-- commented out in the code right now
<Action name="Next Blending Mode">
<icon></icon>
<text>Next Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Next Blending Mode</toolTip>
<iconText>Next Blending Mode</iconText>
<shortcut>Alt+Shift++</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Previous Blending Mode">
<icon></icon>
<text>Previous Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Previous Blending Mode</toolTip>
<iconText>Previous Blending Mode</iconText>
<shortcut>Alt+Shift+-</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
-->
<Action name="Select Normal Blending Mode">
<icon></icon>
<text>Select Normal Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Normal Blending Mode</toolTip>
<iconText>Select Normal Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Dissolve Blending Mode">
<icon></icon>
<text>Select Dissolve Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Dissolve Blending Mode</toolTip>
<iconText>Select Dissolve Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Behind Blending Mode">
<icon></icon>
<text>Select Behind Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Behind Blending Mode</toolTip>
<iconText>Select Behind Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+Q</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Clear Blending Mode">
<icon></icon>
<text>Select Clear Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Clear Blending Mode</toolTip>
<iconText>Select Clear Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+R</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Darken Blending Mode">
<icon></icon>
<text>Select Darken Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Darken Blending Mode</toolTip>
<iconText>Select Darken Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+K</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Multiply Blending Mode">
<icon></icon>
<text>Select Multiply Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Multiply Blending Mode</toolTip>
<iconText>Select Multiply Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Color Burn Blending Mode">
<icon></icon>
<text>Select Color Burn Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Color Burn Blending Mode</toolTip>
<iconText>Select Color Burn Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+B</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Linear Burn Blending Mode">
<icon></icon>
<text>Select Linear Burn Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Linear Burn Blending Mode</toolTip>
<iconText>Select Linear Burn Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Lighten Blending Mode">
<icon></icon>
<text>Select Lighten Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Lighten Blending Mode</toolTip>
<iconText>Select Lighten Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Screen Blending Mode">
<icon></icon>
<text>Select Screen Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Screen Blending Mode</toolTip>
<iconText>Select Screen Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Color Dodge Blending Mode">
<icon></icon>
<text>Select Color Dodge Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Color Dodge Blending Mode</toolTip>
<iconText>Select Color Dodge Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+D</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Linear Dodge Blending Mode">
<icon></icon>
<text>Select Linear Dodge Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Linear Dodge Blending Mode</toolTip>
<iconText>Select Linear Dodge Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+W</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Overlay Blending Mode">
<icon></icon>
<text>Select Overlay Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Overlay Blending Mode</toolTip>
<iconText>Select Overlay Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+O</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hard Overlay Blending Mode">
<icon></icon>
<text>Select Hard Overlay Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hard Overlay Blending Mode</toolTip>
<iconText>Select Hard Overlay Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+P</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Soft Light Blending Mode">
<icon></icon>
<text>Select Soft Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Soft Light Blending Mode</toolTip>
<iconText>Select Soft Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+F</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hard Light Blending Mode">
<icon></icon>
<text>Select Hard Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hard Light Blending Mode</toolTip>
<iconText>Select Hard Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+H</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Vivid Light Blending Mode">
<icon></icon>
<text>Select Vivid Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Vivid Light Blending Mode</toolTip>
<iconText>Select Vivid Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+V</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Linear Light Blending Mode">
<icon></icon>
<text>Select Linear Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Linear Light Blending Mode</toolTip>
<iconText>Select Linear Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Pin Light Blending Mode">
<icon></icon>
<text>Select Pin Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Pin Light Blending Mode</toolTip>
<iconText>Select Pin Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hard Mix Blending Mode">
<icon></icon>
<text>Select Hard Mix Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hard Mix Blending Mode</toolTip>
<iconText>Select Hard Mix Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+L</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Difference Blending Mode">
<icon></icon>
<text>Select Difference Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Difference Blending Mode</toolTip>
<iconText>Select Difference Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+E</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Exclusion Blending Mode">
<icon></icon>
<text>Select Exclusion Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Exclusion Blending Mode</toolTip>
<iconText>Select Exclusion Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+X</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hue Blending Mode">
<icon></icon>
<text>Select Hue Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hue Blending Mode</toolTip>
<iconText>Select Hue Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Saturation Blending Mode">
<icon></icon>
<text>Select Saturation Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Saturation Blending Mode</toolTip>
<iconText>Select Saturation Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Color Blending Mode">
<icon></icon>
<text>Select Color Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Color Blending Mode</toolTip>
<iconText>Select Color Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Luminosity Blending Mode">
<icon></icon>
<text>Select Luminosity Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Luminosity Blending Mode</toolTip>
<iconText>Select Luminosity Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+Y</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Animation">
<text>Animation</text>
<Action name="previous_frame">
<icon></icon>
<text>Previous frame</text>
<whatsThis></whatsThis>
<toolTip>Move to previous frame</toolTip>
<iconText>Move to previous frame</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="next_frame">
<icon></icon>
<text>Next frame</text>
<whatsThis></whatsThis>
<toolTip>Move to next frame</toolTip>
<iconText>Move to next frame</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_playback">
<icon></icon>
<text>Play / pause animation</text>
<whatsThis></whatsThis>
<toolTip>Play / pause animation</toolTip>
<iconText>Play / pause animation</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_blank_frame">
<icon></icon>
<text>Add blank frame</text>
<whatsThis></whatsThis>
<toolTip>Add blank frame</toolTip>
<iconText>Add blank frame</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_duplicate_frame">
<icon></icon>
<text>Copy Frame</text>
<whatsThis></whatsThis>
<toolTip>Add duplicate frame</toolTip>
<iconText>Add duplicate frame</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_onion_skin">
<icon></icon>
<text>Toggle onion skin</text>
<whatsThis></whatsThis>
<toolTip>Toggle onion skin</toolTip>
<iconText>Toggle onion skin</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="previous_keyframe">
<icon></icon>
<text>Previous Keyframe</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="next_keyframe">
<icon></icon>
<text>Next Keyframe</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="first_frame">
<icon></icon>
<text>First Frame</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="last_frame">
<icon></icon>
<text>Last Frame</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="lazy_frame">
<icon></icon>
<text>Auto Frame Mode</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="drop_frames">
<icon></icon>
<text></text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_blank_frame">
<icon></icon>
<text>Add blank frame</text>
<whatsThis></whatsThis>
<toolTip>Add blank frame</toolTip>
<iconText>Add blank frame</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_in_timeline">
<icon></icon>
<text>Show in Timeline</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Layers">
<text>Layers</text>
<Action name="activateNextLayer">
<icon></icon>
<text>Activate next layer</text>
<whatsThis></whatsThis>
<toolTip>Activate next layer</toolTip>
<iconText>Activate next layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>PgUp</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="activatePreviousLayer">
<icon></icon>
<text>Activate previous layer</text>
<whatsThis></whatsThis>
<toolTip>Activate previous layer</toolTip>
<iconText>Activate previous layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>PgDown</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="switchToPreviouslyActiveNode">
<icon></icon>
<text>Activate previously selected layer</text>
<whatsThis></whatsThis>
<toolTip>Activate previously selected layer</toolTip>
<iconText>Activate previously selected layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>;</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_group_layer">
<icon>groupLayer</icon>
<text>&amp;Group Layer</text>
<whatsThis></whatsThis>
<toolTip>Group Layer</toolTip>
<iconText>Group Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_clone_layer">
<icon>cloneLayer</icon>
<text>&amp;Clone Layer</text>
<whatsThis></whatsThis>
<toolTip>Clone Layer</toolTip>
<iconText>Clone Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_shape_layer">
<icon>vectorLayer</icon>
<text>&amp;Vector Layer</text>
<whatsThis></whatsThis>
<toolTip>Vector Layer</toolTip>
<iconText>Vector Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_adjustment_layer">
<icon>filterLayer</icon>
<text>&amp;Filter Layer...</text>
<whatsThis></whatsThis>
<toolTip>Filter Layer</toolTip>
<iconText>Filter Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_fill_layer">
<icon>fillLayer</icon>
<text>&amp;Fill Layer...</text>
<whatsThis></whatsThis>
<toolTip>Fill Layer</toolTip>
<iconText>Fill Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_file_layer">
<icon>fileLayer</icon>
<text>&amp;File Layer...</text>
<whatsThis></whatsThis>
<toolTip>File Layer</toolTip>
<iconText>File Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_transparency_mask">
<icon>transparencyMask</icon>
<text>&amp;Transparency Mask</text>
<whatsThis></whatsThis>
<toolTip>Transparency Mask</toolTip>
<iconText>Transparency Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_filter_mask">
<icon>filterMask</icon>
<text>&amp;Filter Mask...</text>
<whatsThis></whatsThis>
<toolTip>Filter Mask</toolTip>
<iconText>Filter Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_colorize_mask">
<icon>filterMask</icon>
<text>&amp;Colorize Mask</text>
<whatsThis></whatsThis>
<toolTip>Colorize Mask</toolTip>
<iconText>Colorize Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_transform_mask">
<icon>transformMask</icon>
<text>&amp;Transform Mask...</text>
<whatsThis></whatsThis>
<toolTip>Transform Mask</toolTip>
<iconText>Transform Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_selection_mask">
<icon>selectionMask</icon>
<text>&amp;Local Selection</text>
<whatsThis></whatsThis>
<toolTip>Local Selection</toolTip>
<iconText>Local Selection</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="isolate_layer">
<icon>view-filter</icon>
<text>&amp;Isolate Layer</text>
<whatsThis></whatsThis>
<toolTip>Isolate Layer</toolTip>
<iconText>Isolate Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_layer_lock">
<icon>layer-locked</icon>
<text>&amp;Toggle layer lock</text>
<whatsThis></whatsThis>
<toolTip>Toggle layer lock</toolTip>
<iconText>Toggle layer lock</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_layer_visibility">
<icon>visible</icon>
<text>Toggle layer &amp;visibility</text>
<whatsThis></whatsThis>
<toolTip>Toggle layer visibility</toolTip>
<iconText>Toggle layer visibility</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_layer_alpha_lock">
<icon>transparency-locked</icon>
<text>Toggle layer &amp;alpha</text>
<whatsThis></whatsThis>
<toolTip>Toggle layer alpha</toolTip>
<iconText>Toggle layer alpha</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_layer_inherit_alpha">
<icon>transparency-enabled</icon>
<text>Toggle layer alpha &amp;inheritance</text>
<whatsThis></whatsThis>
<toolTip>Toggle layer alpha inheritance</toolTip>
<iconText>Toggle layer alpha inheritance</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_paint_layer">
<icon>paintLayer</icon>
<text>&amp;Paint Layer</text>
<whatsThis></whatsThis>
<toolTip>Paint Layer</toolTip>
<iconText>Paint Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Insert</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="new_from_visible">
<icon></icon>
<text>&amp;New Layer From Visible</text>
<whatsThis></whatsThis>
<toolTip>New layer from visible</toolTip>
<iconText>New layer from visible</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="duplicatelayer">
<icon>duplicatelayer</icon>
<text>&amp;Duplicate Layer or Mask</text>
<whatsThis></whatsThis>
<toolTip>Duplicate Layer or Mask</toolTip>
<iconText>Duplicate Layer or Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_selection_to_new_layer">
<icon></icon>
<text>&amp;Cut Selection to New Layer</text>
<whatsThis></whatsThis>
<toolTip>Cut Selection to New Layer</toolTip>
<iconText>Cut Selection to New Layer</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Shift+J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_selection_to_new_layer">
<icon></icon>
<text>Copy &amp;Selection to New Layer</text>
<whatsThis></whatsThis>
<toolTip>Copy Selection to New Layer</toolTip>
<iconText>Copy Selection to New Layer</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_layer_clipboard">
<icon></icon>
<text>Copy Layer</text>
<whatsThis></whatsThis>
<toolTip>Copy layer to clipboard</toolTip>
<iconText>Copy layer to clipboard</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_layer_clipboard">
<icon></icon>
<text>Cut Layer</text>
<whatsThis></whatsThis>
<toolTip>Cut layer to clipboard</toolTip>
<iconText>Cut layer to clipboard</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paste_layer_from_clipboard">
<icon></icon>
<text>Paste Layer</text>
<whatsThis></whatsThis>
<toolTip>Paste layer from clipboard</toolTip>
<iconText>Paste layer from clipboard</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_quick_group">
<icon></icon>
<text>Quick Group</text>
<whatsThis></whatsThis>
<toolTip>Create a group layer containing selected layers</toolTip>
<iconText>Quick Group</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="quick_ungroup">
<icon></icon>
<text>Quick Ungroup</text>
<whatsThis></whatsThis>
<toolTip>Remove grouping of the layers or remove one layer out of the group</toolTip>
<iconText>Quick Ungroup</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_quick_clipping_group">
<icon></icon>
<text>Quick Clipping Group</text>
<whatsThis></whatsThis>
<toolTip>Group selected layers and add a layer with clipped alpha channel</toolTip>
<iconText>Quick Clipping Group</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_all_layers">
<icon></icon>
<text>All Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all layers</toolTip>
<iconText>Select all layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_visible_layers">
<icon></icon>
<text>Visible Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all visible layers</toolTip>
<iconText>Select all visible layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_locked_layers">
<icon></icon>
<text>Locked Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all locked layers</toolTip>
<iconText>Select all locked layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_invisible_layers">
<icon></icon>
<text>Invisible Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all invisible layers</toolTip>
<iconText>Select all invisible layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_unlocked_layers">
<icon></icon>
<text>Unlocked Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all unlocked layers</toolTip>
<iconText>Select all unlocked layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_node_as_image">
<icon>document-save</icon>
<text>&amp;Save Layer/Mask...</text>
<whatsThis></whatsThis>
<toolTip>Save Layer/Mask</toolTip>
<iconText>Save Layer/Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_groups_as_images">
<icon>document-save</icon>
<text>Save &amp;Group Layers...</text>
<whatsThis></whatsThis>
<toolTip>Save Group Layers</toolTip>
<iconText>Save Group Layers</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_group_to_animated">
<icon></icon>
<text>Convert group to &amp;animated layer</text>
<whatsThis></whatsThis>
<toolTip>Convert child layers into animation frames</toolTip>
<iconText>Convert child layers into animation frames</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
+ <Action name="convert_layer_to_file_layer">
+ <icon>fileLayer</icon>
+ <text>to &amp;File Layer</text>
+ <whatsThis></whatsThis>
+ <toolTip>Saves out the layers into a new image and then references that image.</toolTip>
+ <iconText>Convert to File Layer</iconText>
+ <activationFlags>100000</activationFlags>
+ <activationConditions>0</activationConditions>
+ <shortcut></shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
<Action name="import_layer_from_file">
<icon></icon>
<text>I&amp;mport Layer...</text>
<whatsThis></whatsThis>
<toolTip>Import Layer</toolTip>
<iconText>Import Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_paint_layer">
<icon>paintLayer</icon>
<text>&amp;as Paint Layer...</text>
<whatsThis></whatsThis>
<toolTip>as Paint Layer</toolTip>
<iconText>as Paint Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_transparency_mask">
<icon>transparencyMask</icon>
<text>as &amp;Transparency Mask...</text>
<whatsThis></whatsThis>
<toolTip>as Transparency Mask</toolTip>
<iconText>as Transparency Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_filter_mask">
<icon>filterMask</icon>
<text>as &amp;Filter Mask...</text>
<whatsThis></whatsThis>
<toolTip>as Filter Mask</toolTip>
<iconText>as Filter Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_selection_mask">
<icon>selectionMask</icon>
<text>as &amp;Selection Mask...</text>
<whatsThis></whatsThis>
<toolTip>as Selection Mask</toolTip>
<iconText>as Selection Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_paint_layer">
<icon>paintLayer</icon>
<text>to &amp;Paint Layer</text>
<whatsThis></whatsThis>
<toolTip>to Paint Layer</toolTip>
<iconText>to Paint Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_transparency_mask">
<icon>transparencyMask</icon>
<text>to &amp;Transparency Mask</text>
<whatsThis></whatsThis>
<toolTip>to Transparency Mask</toolTip>
<iconText>to Transparency Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_filter_mask">
<icon>filterMask</icon>
<text>to &amp;Filter Mask...</text>
<whatsThis></whatsThis>
<toolTip>to Filter Mask</toolTip>
<iconText>to Filter Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_selection_mask">
<icon>selectionMask</icon>
<text>to &amp;Selection Mask</text>
<whatsThis></whatsThis>
<toolTip>to Selection Mask</toolTip>
<iconText>to Selection Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="split_alpha_into_mask">
<icon>transparencyMask</icon>
<text>&amp;Alpha into Mask</text>
<whatsThis></whatsThis>
<toolTip>Alpha into Mask</toolTip>
<iconText>Alpha into Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>10</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="split_alpha_write">
<icon>transparency-enabled</icon>
<text>&amp;Write as Alpha</text>
<whatsThis></whatsThis>
<toolTip>Write as Alpha</toolTip>
<iconText>Write as Alpha</iconText>
<activationFlags>1000000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="split_alpha_save_merged">
<icon>document-save</icon>
<text>&amp;Save Merged...</text>
<whatsThis></whatsThis>
<toolTip>Save Merged</toolTip>
<iconText>Save Merged</iconText>
<activationFlags>1000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layersplit">
<icon>split-layer</icon>
<text>Split Layer...</text>
<whatsThis></whatsThis>
<toolTip>Split Layer</toolTip>
<iconText>Split Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="waveletdecompose">
<icon></icon>
<text>Wavelet Decompose ...</text>
<whatsThis></whatsThis>
<toolTip>Wavelet Decompose</toolTip>
<iconText>Wavelet Decompose</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorNodeX">
<icon>symmetry-horizontal</icon>
<text>Mirror Layer Hori&amp;zontally</text>
<whatsThis></whatsThis>
<toolTip>Mirror Layer Horizontally</toolTip>
<iconText>Mirror Layer Horizontally</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorNodeY">
<icon>symmetry-vertical</icon>
<text>Mirror Layer &amp;Vertically</text>
<whatsThis></whatsThis>
<toolTip>Mirror Layer Vertically</toolTip>
<iconText>Mirror Layer Vertically</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotatelayer">
<icon></icon>
<text>&amp;Rotate Layer...</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer</toolTip>
<iconText>Rotate Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateLayerCW90">
<icon>object-rotate-right</icon>
<text>Rotate &amp;Layer 90° to the Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer 90° to the Right</toolTip>
<iconText>Rotate Layer 90° to the Right</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateLayerCCW90">
<icon>object-rotate-left</icon>
<text>Rotate Layer &amp;90° to the Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer 90° to the Left</toolTip>
<iconText>Rotate Layer 90° to the Left</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateLayer180">
<icon></icon>
<text>Rotate Layer &amp;180°</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer 180°</toolTip>
<iconText>Rotate Layer 180°</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layersize">
<icon></icon>
<text>Scale &amp;Layer to new Size...</text>
<whatsThis></whatsThis>
<toolTip>Scale Layer to new Size</toolTip>
<iconText>Scale Layer to new Size</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shearlayer">
<icon></icon>
<text>&amp;Shear Layer...</text>
<whatsThis></whatsThis>
<toolTip>Shear Layer</toolTip>
<iconText>Shear Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="offsetlayer">
<icon></icon>
<text>&amp;Offset Layer...</text>
<whatsThis></whatsThis>
<toolTip>Offset Layer</toolTip>
<iconText>Offset Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="clones_array">
<icon></icon>
<text>Clones &amp;Array...</text>
<whatsThis></whatsThis>
<toolTip>Clones Array</toolTip>
<iconText>Clones Array</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="EditLayerMetaData">
<icon></icon>
<text>&amp;Edit metadata...</text>
<whatsThis></whatsThis>
<toolTip>Edit metadata</toolTip>
<iconText>Edit metadata</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="histogram">
<icon></icon>
<text>&amp;Histogram...</text>
<whatsThis></whatsThis>
<toolTip>Histogram</toolTip>
<iconText>Histogram</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layercolorspaceconversion">
<icon></icon>
<text>&amp;Convert Layer Color Space...</text>
<whatsThis></whatsThis>
<toolTip>Convert Layer Color Space</toolTip>
<iconText>Convert Layer Color Space</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="merge_layer">
<icon>merge-layer-below</icon>
<text>&amp;Merge with Layer Below</text>
<whatsThis></whatsThis>
<toolTip>Merge with Layer Below</toolTip>
<iconText>Merge with Layer Below</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+E</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="flatten_layer">
<icon></icon>
<text>&amp;Flatten Layer</text>
<whatsThis></whatsThis>
<toolTip>Flatten Layer</toolTip>
<iconText>Flatten Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rasterize_layer">
<icon></icon>
<text>Ras&amp;terize Layer</text>
<whatsThis></whatsThis>
<toolTip>Rasterize Layer</toolTip>
<iconText>Rasterize Layer</iconText>
<activationFlags>10000000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="flatten_image">
<icon></icon>
<text>Flatten ima&amp;ge</text>
<whatsThis></whatsThis>
<toolTip>Flatten image</toolTip>
<iconText>Flatten image</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+E</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layer_style">
<icon></icon>
<text>La&amp;yer Style...</text>
<whatsThis></whatsThis>
<toolTip>Layer Style</toolTip>
<iconText>Layer Style</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="LayerGroupSwitcher/previous">
<icon></icon>
<text>Move into previous group</text>
<whatsThis></whatsThis>
<toolTip>Move into previous group</toolTip>
<iconText>Move into previous group</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="LayerGroupSwitcher/next">
<icon></icon>
<text>Move into next group</text>
<whatsThis></whatsThis>
<toolTip>Move into next group</toolTip>
<iconText>Move into next group</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="RenameCurrentLayer">
<icon></icon>
<text>Rename current layer</text>
<whatsThis></whatsThis>
<toolTip>Rename current layer</toolTip>
<iconText>Rename current layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>F2</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_layer">
<icon>deletelayer</icon>
<text>&amp;Remove Layer</text>
<whatsThis></whatsThis>
<toolTip>Remove Layer</toolTip>
<iconText>Remove Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Shift+Delete</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="move_layer_up">
<icon>arrowupblr</icon>
<text>Move Layer or Mask Up</text>
<whatsThis></whatsThis>
<toolTip>Move Layer or Mask Up</toolTip>
<iconText></iconText>
<shortcut>Ctrl+PgUp</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="move_layer_down">
<icon>arrowdown</icon>
<text>Move Layer or Mask Down</text>
<whatsThis></whatsThis>
<toolTip>Move Layer or Mask Down</toolTip>
<iconText></iconText>
<shortcut>Ctrl+PgDown</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layer_properties">
<icon>properties</icon>
<text>&amp;Properties...</text>
<whatsThis></whatsThis>
<toolTip>Properties</toolTip>
<iconText>Properties</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>F3</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
</ActionCollection>
diff --git a/krita/krita4.xmlgui b/krita/krita4.xmlgui
index 1fa669cc82..124564df15 100644
--- a/krita/krita4.xmlgui
+++ b/krita/krita4.xmlgui
@@ -1,383 +1,385 @@
<?xml version="1.0"?>
<kpartgui xmlns="http://www.kde.org/standards/kxmlgui/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="Krita"
-version="121"
+version="400"
xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0 http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd">
<MenuBar>
<Menu name="file">
<text>&amp;File</text>
<Action name="file_new"/>
<Action name="file_open"/>
<Action name="file_open_recent"/>
<Separator/>
<Action name="file_save"/>
<Action name="file_save_as"/>
<Action name="file_reload_file"/>
<Separator/>
<Action name="file_import_file"/>
<Action name="file_export_file"/>
<Separator/>
<Action name="file_export_pdf"/>
<Separator/>
<Action name="file_import_animation"/>
<Action name="render_animation"/>
<Separator/>
<Action name="save_incremental_version"/>
<Action name="save_incremental_backup"/>
<Separator/>
<Action name="create_template"/>
<Action name="create_copy"/>
<!--Separator/>
<Action name="file_print"/>
<Action name="file_print_preview"/-->
<Separator/>
<Action name="file_documentinfo"/>
<Separator/>
<Action name="file_close"/>
<Action name="file_close_all"/>
<Action name="file_quit"/>
</Menu>
<Menu name="edit">
<text>&amp;Edit</text>
<Action name="edit_undo"/>
<Action name="edit_redo"/>
<Separator/>
<Action name="edit_cut"/>
<Action name="edit_copy"/>
<Action name="cut_sharp"/>
<Action name="copy_sharp"/>
<Action name="copy_merged"/>
<Action name="edit_paste"/>
<Action name="paste_at"/>
<Action name="paste_new"/>
<Action name="clear"/>
<Action name="fill_selection_foreground_color"/>
<Action name="fill_selection_background_color"/>
<Action name="fill_selection_pattern"/>
<Menu name="fill_special">
<text>Fill Special</text>
<Action name="fill_selection_foreground_color_opacity"/>
<Action name="fill_selection_background_color_opacity"/>
<Action name="fill_selection_pattern_opacity"/>
</Menu>
<Action name="stroke_shapes"/>
<Action name="stroke_selection"/>
<Action name="delete"/>
<Separator/>
<Action name="revert"/>
</Menu>
<Menu name="view">
<text>&amp;View</text>
<Action name="view_show_canvas_only"/>
<Action name="fullscreen"/>
<Action name="wrap_around_mode"/>
<Action name="level_of_detail_mode"/>
<Action name="softProof"/>
<Action name="gamutCheck"/>
<Separator/>
<Menu name="Canvas">
<text>&amp;Canvas</text>
<Action name="mirror_canvas"/>
<Separator/>
<Action name="zoom_to_100pct" />
<Action name="rotate_canvas_right" />
<Action name="rotate_canvas_left" />
<Action name="reset_canvas_rotation" />
<!-- TODO: Something is not right with the way zoom actions are hooked up. These are in the KoZoomController.
It seems they are not being properly placed in the view manager since the MDI changes were implemented
-->
<Action name="view_zoom_in"/>
<Action name="view_zoom_out"/>
</Menu>
<!-- TODO: None of these actions are showing. There names must have been changed to something with the MDI changes?...
<Action name="actual_pixels"/>
<Action name="actual_size"/>
<Action name="fit_to_canvas"/>
-->
<Separator/>
<Action name="view_ruler"/>
<Action name="rulers_track_mouse"/>
<Action name="view_show_guides"/>
<Action name="view_lock_guides"/>
<Action name="showStatusBar" />
<Separator/>
<Action name="view_grid"/>
+ <Action name="view_pixel_grid"/>
<Separator/>
<Menu name="SnapTo">
<text>&amp;Snap To</text>
<Action name="view_snap_to_guides"/>
<Action name="view_snap_to_grid"/>
<Action name="view_snap_orthogonal" />
<Action name="view_snap_node" />
<Action name="view_snap_extension" />
<Action name="view_snap_intersection" />
<Action name="view_snap_bounding_box" />
<Action name="view_snap_image_bounds" />
<Action name="view_snap_image_center" />
</Menu>
<Separator/>
<Action name="view_toggle_painting_assistants"/>
<Action name="view_toggle_assistant_previews"/>
<Separator/>
<Action name="view_palette_action_menu"/>
<Separator/>
<Action name="refresh_canvas"/>
</Menu>
<Menu name="Image">
<text>&amp;Image</text>
<Action name="image_properties"/>
<Action name="image_color"/>
<Action name="imagecolorspaceconversion"/>
<Action name="duplicate_image"/>
<Separator/>
<Action name="trim_to_image"/>
<Action name="resizeimagetolayer"/>
<Action name="resizeimagetoselection"/>
<Separator/>
<Menu name="Rotate">
<text>&amp;Rotate</text>
<Action name="rotateimage"/>
<Separator/>
<Action name="rotateImageCW90"/>
<Action name="rotateImageCCW90"/>
<Action name="rotateImage180"/>
</Menu>
<Action name="shearimage"/>
<Separator/>
<Action name="mirrorImageHorizontal"/>
<Action name="mirrorImageVertical"/>
<Separator/>
<Action name="imagesize"/>
<Action name="offsetimage"/>
<Action name="imageresolution"/>
<Action name="canvassize"/>
<Separator/>
<Action name="imagesplit"/>
<Action name="waveletdecompose"/>
<Action name="separate"/>
</Menu>
<Menu name="Layer">
<text>&amp;Layer</text>
<Action name="cut_layer_clipboard"/>
<Action name="copy_layer_clipboard"/>
<Action name="paste_layer_from_clipboard"/>
<Separator/>
<Menu name="LayerNew">
<text>New</text>
<Action name="add_new_paint_layer"/>
<Action name="new_from_visible"/>
<Action name="duplicatelayer"/>
<Separator/>
<Action name="cut_selection_to_new_layer"/>
<Action name="copy_selection_to_new_layer"/>
</Menu>
<Menu name="LayerImportExport">
<text>&amp;Import/Export</text>
<Action name="save_node_as_image"/>
<Action name="save_groups_as_images"/>
<Separator/>
<Action name="import_layer_from_file"/>
<Menu name="LayerImportAs">
<text>Import</text>
<Action name="import_layer_as_paint_layer"/>
<Action name="import_layer_as_transparency_mask"/>
<Action name="import_layer_as_filter_mask"/>
<Action name="import_layer_as_selection_mask"/>
</Menu>
</Menu>
<Menu name="LayerConvert">
<text>&amp;Convert</text>
<Action name="convert_to_paint_layer"/>
<Action name="convert_to_transparency_mask"/>
<Action name="convert_to_filter_mask"/>
<Action name="convert_to_selection_mask"/>
+ <Action name="convert_layer_to_file_layer"/>
<Action name="convert_group_to_animated"/>
<Action name="layercolorspaceconversion"/>
</Menu>
<Separator/>
<Menu name="LayerSelect">
<text>&amp;Select</text>
<Action name="select_all_layers"/>
<Action name="select_visible_layers"/>
<Action name="select_invisible_layers"/>
<Action name="select_locked_layers"/>
<Action name="select_unlocked_layers"/>
</Menu>
<Menu name="LayerGroup">
<text>&amp;Group</text>
<Action name="create_quick_group"/>
<Action name="create_quick_clipping_group"/>
<Action name="quick_ungroup"/>
</Menu>
<Menu name="LayerTransform">
<text>&amp;Transform</text>
<Action name="mirrorNodeX"/>
<Action name="mirrorNodeY"/>
<Action name="layersize"/>
<Menu name="Rotate">
<text>&amp;Rotate</text>
<Action name="rotatelayer"/>
<Separator/>
<Action name="rotateLayerCW90"/>
<Action name="rotateLayerCCW90"/>
<Action name="rotateLayer180"/>
</Menu>
<Action name="shearlayer"/>
<Action name="offsetlayer"/>
</Menu>
<Menu name="LayerSplitAlpha">
<text>S&amp;plit</text>
<Menu name="LayerSplitAlpha">
<text>S&amp;plit Alpha</text>
<Action name="split_alpha_into_mask"/>
<Action name="split_alpha_write"/>
<Action name="split_alpha_save_merged"/>
</Menu>
<Action name="layersplit"/>
<Action name="clones_array"/>
</Menu>
<Separator/>
<Action name="EditLayerMetaData"/>
<Action name="histogram"/>
<Separator/>
<Action name="merge_layer"/>
<Action name="flatten_layer"/>
<Action name="rasterize_layer"/>
<Action name="merge_all_shape_layers"/>
<Action name="flatten_image"/>
<Action name="merge_selected_layers"/>
<Separator/>
<Action name="layer_style"/>
</Menu>
<Menu name="Select">
<text>&amp;Select</text>
<Action name="select_all"/>
<Action name="deselect"/>
<Action name="reselect"/>
<Action name="invert"/>
<Action name="convert_to_vector_selection"/>
<Action name="convert_shapes_to_vector_selection"/>
<Action name="convert_selection_to_shape"/>
<Separator/>
<Action name="feather"/>
<Action name="similar"/>
<Separator/>
<Action name="toggle_display_selection"/>
<Action name="show-global-selection-mask"/>
<Action name="selectionscale"/>
<Separator/>
<Action name="colorrange"/>
<Action name="selectopaque"/>
<Separator/>
<Action name="featherselection"/>
<Action name="growselection"/>
<Action name="shrinkselection"/>
<Action name="borderselection"/>
<Action name="smoothselection"/>
</Menu>
<Menu name="Filter">
<text>Filte&amp;r</text>
<Action name="filter_apply_again"/>
<Action name="filter_gallery"/>
<Separator/>
<Action name="adjust_filters"/>
<Action name="artistic_filters"/>
<Action name="blur_filters"/>
<Action name="color_filters"/>
<Action name="decor_filters"/>
<Action name="edge_filters"/>
<Action name="enhance_filters"/>
<Action name="emboss_filters"/>
<Action name="map_filters"/>
<Action name="nonphotorealistic_filters"/>
<Action name="other_filters"/>
<Separator/>
<Action name="QMic"/>
<Action name="QMicAgain"/>
</Menu>
<Menu name="tools">
<text>&amp;Tools</text>
<Action name="scripts"/>
<Menu name="Recording">
<text>Recording</text>
<Action name="Recording_Start_Recording_Macro"/>
<Action name="Recording_Stop_Recording_Macro"/>
</Menu>
<Menu name="Macros">
<text>Macros</text>
<Action name="Macro_Open_Play"/>
<Action name="Macro_Open_Edit"/>
</Menu>
</Menu>
<Menu name="settings">
<text>Setti&amp;ngs</text>
<Action name="options_configure"/>
<Action name="manage_bundles"/>
<Separator/>
<Action name="options_configure_toolbars"/>
<Merge name="StandardToolBarMenuHandler" />
<Separator/>
<Action name="view_toggledockers"/>
<Action name="view_toggledockertitlebars"/>
<Action name="settings_dockers_menu"/>
<Separator/>
<Action name="theme_menu"/>
<Separator/>
<!-- `Configure Shortcuts` was moved into main configuration menu -->
<!-- <Action name="options_configure_keybinding"/> -->
<Separator/>
<Action name="switch_application_language"/>
<Action name="settings_active_author"/>
<Separator/>
</Menu>
<Action name="window"/>
<Separator/>
<Menu name="help">
<text>&amp;Help</text>
<Action name="help_contents"/>
<Action name="help_whats_this"/>
<Separator/>
<MergeLocal/>
<Action name="help_show_tip"/>
<Separator/>
<Action name="help_report_bug"/>
<Action name="buginfo"/>
<Separator/>
<Action name="help_about_app"/>
<Action name="help_about_kde"/>
</Menu>
</MenuBar>
<ToolBar name="mainToolBar" fullWidth="false" noMerge="1">
<Text>File</Text>
<Action name="file_new"/>
<Action name="file_open"/>
<Action name="file_save"/>
<!--Separator/>
<Action name="edit_undo"/>
<Action name="edit_redo"/-->
</ToolBar>
<ToolBar name="BrushesAndStuff" position="top">
<Text>Brushes and Stuff</Text>
<Action name="gradients"/>
<Action name="patterns"/>
<Separator/>
<Action name="dual"/>
<Separator/>
<Action name="paintops"/>
<Action name="paintop_options"/>
<Action name="composite_actions"/>
<Action name="brushslider1"/>
<Action name="brushslider2"/>
<Separator/>
<Action name="mirror_actions"/>
<Action name="expanding_spacer_1"/>
<Action name="workspaces"/>
</ToolBar>
</kpartgui>
diff --git a/krita/kritamenu.action b/krita/kritamenu.action
index 1fa8efd13b..651b3d5a0e 100644
--- a/krita/kritamenu.action
+++ b/krita/kritamenu.action
@@ -1,1783 +1,1795 @@
<?xml version="1.0" encoding="UTF-8"?>
<ActionCollection version="2" name="Menu">
<Actions category="File">
<text>File</text>
<Action name="file_new">
<icon>document-new</icon>
<text>&amp;New</text>
<whatsThis></whatsThis>
<toolTip>Create new document</toolTip>
<iconText>New</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_open">
<icon>document-open</icon>
<text>&amp;Open...</text>
<whatsThis></whatsThis>
<toolTip>Open an existing document</toolTip>
<iconText>Open</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+O</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_open_recent">
<icon>document-open-recent</icon>
<text>Open &amp;Recent</text>
<whatsThis></whatsThis>
<toolTip>Open a document which was recently opened</toolTip>
<iconText>Open Recent</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_save">
<icon>document-save</icon>
<text>&amp;Save</text>
<whatsThis></whatsThis>
<toolTip>Save</toolTip>
<iconText>Save</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_save_as">
<icon>document-save-as</icon>
<text>Save &amp;As...</text>
<whatsThis></whatsThis>
<toolTip>Save document under a new name</toolTip>
<iconText>Save As</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<!-- commented out in the code right now
<Action name="file_reload_file">
<icon></icon>
<text>Reload</text>
<whatsThis></whatsThis>
<toolTip>Reload</toolTip>
<iconText>Reload</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
-->
<Action name="file_import_file">
<icon>document-import</icon>
<text>Open ex&amp;isting Document as Untitled Document...</text>
<whatsThis></whatsThis>
<toolTip>Open existing Document as Untitled Document</toolTip>
<iconText>Open existing Document as Untitled Document</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_export_file">
<icon>document-export</icon>
<text>E&amp;xport...</text>
<whatsThis></whatsThis>
<toolTip>Export</toolTip>
<iconText>Export</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_export_pdf">
<icon>application-pdf</icon>
<text>&amp;Export as PDF...</text>
<whatsThis></whatsThis>
<toolTip>Export as PDF</toolTip>
<iconText>Export as PDF</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_import_animation">
<icon></icon>
<text>Import animation frames...</text>
<whatsThis></whatsThis>
<toolTip>Import animation frames</toolTip>
<iconText>Import animation frames</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="render_animation">
<icon></icon>
<text>&amp;Render Animation...</text>
<whatsThis></whatsThis>
<toolTip>Render Animation to GIF, Image Sequence or Video</toolTip>
<iconText>Render Animation</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="render_image_sequence_again">
<icon></icon>
<text>&amp;Render Image Sequence Again</text>
<whatsThis></whatsThis>
<toolTip>Render Animation to Image Sequence Again</toolTip>
<iconText>Render Animation</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_incremental_version">
<icon></icon>
<text>Save Incremental &amp;Version</text>
<whatsThis></whatsThis>
<toolTip>Save Incremental Version</toolTip>
<iconText>Save Incremental Version</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_incremental_backup">
<icon></icon>
<text>Save Incremental &amp;Backup</text>
<whatsThis></whatsThis>
<toolTip>Save Incremental Backup</toolTip>
<iconText>Save Incremental Backup</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>F4</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_template">
<icon></icon>
<text>&amp;Create Template From Image...</text>
<whatsThis></whatsThis>
<toolTip>Create Template From Image</toolTip>
<iconText>Create Template From Image</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_copy">
<icon></icon>
<text>Create Copy &amp;From Current Image</text>
<whatsThis></whatsThis>
<toolTip>Create Copy From Current Image</toolTip>
<iconText>Create Copy From Current Image</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_print">
<icon>document-print</icon>
<text>&amp;Print...</text>
<whatsThis></whatsThis>
<toolTip>Print document</toolTip>
<iconText>Print</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+P</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_print_preview">
<icon>document-print-preview</icon>
<text>Print Previe&amp;w</text>
<whatsThis></whatsThis>
<toolTip>Show a print preview of document</toolTip>
<iconText>Print Preview</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_documentinfo">
<icon>configure</icon>
<text>&amp;Document Information</text>
<whatsThis></whatsThis>
<toolTip>Document Information</toolTip>
<iconText>Document Information</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_close_all">
<icon></icon>
<text>&amp;Close All</text>
<whatsThis></whatsThis>
<toolTip>Close All</toolTip>
<iconText>Close All</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+W</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_close">
<icon></icon>
<text>C&amp;lose</text>
<whatsThis></whatsThis>
<toolTip>Close</toolTip>
<iconText>Close</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_quit">
<icon></icon>
<text>&amp;Quit</text>
<whatsThis></whatsThis>
<toolTip>Quit application</toolTip>
<iconText>Quit</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Q</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Edit">
<text>Edit</text>
<Action name="edit_undo">
<icon>edit-undo</icon>
<text>Undo</text>
<whatsThis></whatsThis>
<toolTip>Undo last action</toolTip>
<iconText>Undo</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_redo">
<icon>edit-redo</icon>
<text>Redo</text>
<whatsThis></whatsThis>
<toolTip>Redo last undone action</toolTip>
<iconText>Redo</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_cut">
<icon>edit-cut</icon>
<text>Cu&amp;t</text>
<whatsThis></whatsThis>
<toolTip>Cut selection to clipboard</toolTip>
<iconText>Cut</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+X</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_copy">
<icon>edit-copy</icon>
<text>&amp;Copy</text>
<whatsThis></whatsThis>
<toolTip>Copy selection to clipboard</toolTip>
<iconText>Copy</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_sharp">
<icon></icon>
<text>C&amp;opy (sharp)</text>
<whatsThis></whatsThis>
<toolTip>Copy (sharp)</toolTip>
<iconText>Copy (sharp)</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_sharp">
<icon></icon>
<text>Cut (&amp;sharp)</text>
<whatsThis></whatsThis>
<toolTip>Cut (sharp)</toolTip>
<iconText>Cut (sharp)</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_merged">
<icon></icon>
<text>Copy &amp;merged</text>
<whatsThis></whatsThis>
<toolTip>Copy merged</toolTip>
<iconText>Copy merged</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_paste">
<icon>edit-paste</icon>
<text>&amp;Paste</text>
<whatsThis></whatsThis>
<toolTip>Paste clipboard content</toolTip>
<iconText>Paste</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+V</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paste_at">
<icon></icon>
<text>Paste at Cursor</text>
<whatsThis></whatsThis>
<toolTip>Paste at cursor</toolTip>
<iconText>Paste at cursor</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+V</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paste_new">
<icon></icon>
<text>Paste into &amp;New Image</text>
<whatsThis></whatsThis>
<toolTip>Paste into New Image</toolTip>
<iconText>Paste into New Image</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="clear">
<icon>edit-clear</icon>
<text>C&amp;lear</text>
<whatsThis></whatsThis>
<toolTip>Clear</toolTip>
<iconText>Clear</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Del</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_foreground_color">
<icon></icon>
<text>&amp;Fill with Foreground Color</text>
<whatsThis></whatsThis>
<toolTip>Fill with Foreground Color</toolTip>
<iconText>Fill with Foreground Color</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Shift+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_background_color">
<icon></icon>
<text>Fill &amp;with Background Color</text>
<whatsThis></whatsThis>
<toolTip>Fill with Background Color</toolTip>
<iconText>Fill with Background Color</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_pattern">
<icon></icon>
<text>F&amp;ill with Pattern</text>
<whatsThis></whatsThis>
<toolTip>Fill with Pattern</toolTip>
<iconText>Fill with Pattern</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Actions category="EditFill">
<text>Fill Special</text>
<Action name="fill_selection_foreground_color_opacity">
<icon></icon>
<text>Fill with Foreground Color (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Foreground Color (Opacity)</toolTip>
<iconText>Fill with Foreground Color (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Shift+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_background_color_opacity">
<icon></icon>
<text>Fill with Background Color (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Background Color (Opacity)</toolTip>
<iconText>Fill with Background Color (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_pattern_opacity">
<icon></icon>
<text>Fill with Pattern (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Pattern (Opacity)</toolTip>
<iconText>Fill with Pattern (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Action name="stroke_shapes">
<icon></icon>
<text>Stro&amp;ke selected shapes</text>
<whatsThis></whatsThis>
<toolTip>Stroke selected shapes</toolTip>
<iconText>Stroke selected shapes</iconText>
<activationFlags>1000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="stroke_selection">
<icon></icon>
<text>Stroke Selec&amp;tion...</text>
<whatsThis></whatsThis>
<toolTip>Stroke selection</toolTip>
<iconText>Stroke selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="delete_keyframe">
<icon></icon>
<text>Delete keyframe</text>
<whatsThis></whatsThis>
<toolTip>Delete keyframe</toolTip>
<iconText>Delete keyframe</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Window">
<text>Window</text>
<Action name="view_newwindow">
<icon>window-new</icon>
<text>&amp;New Window</text>
<whatsThis></whatsThis>
<toolTip>New Window</toolTip>
<iconText>New Window</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="windows_next">
<icon></icon>
<text>N&amp;ext</text>
<whatsThis></whatsThis>
<toolTip>Next</toolTip>
<iconText>Next</iconText>
<activationFlags>10</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="windows_previous">
<icon></icon>
<text>Previous</text>
<whatsThis></whatsThis>
<toolTip>Previous</toolTip>
<iconText>Previous</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="View">
<text>View</text>
<Action name="view_show_canvas_only">
<icon></icon>
<text>&amp;Show Canvas Only</text>
<whatsThis></whatsThis>
<toolTip>Show just the canvas or the whole window</toolTip>
<iconText>Show Canvas Only</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Tab</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fullscreen">
<icon>view-fullscreen</icon>
<text>F&amp;ull Screen Mode</text>
<whatsThis></whatsThis>
<toolTip>Display the window in full screen</toolTip>
<iconText>Full Screen Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+F</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="wrap_around_mode">
<icon></icon>
<text>&amp;Wrap Around Mode</text>
<whatsThis></whatsThis>
<toolTip>Wrap Around Mode</toolTip>
<iconText>Wrap Around Mode</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>W</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="level_of_detail_mode">
<icon></icon>
<text>&amp;Instant Preview Mode</text>
<whatsThis></whatsThis>
<toolTip>Instant Preview Mode</toolTip>
<iconText>Instant Preview Mode</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Shift+L</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="softProof">
<icon></icon>
<text>Soft Proofing</text>
<whatsThis></whatsThis>
<toolTip>Turns on Soft Proofing</toolTip>
<iconText>Turns on Soft Proofing</iconText>
<shortcut>Ctrl+Y</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="gamutCheck">
<icon></icon>
<text>Out of Gamut Warnings</text>
<whatsThis></whatsThis>
<toolTip>Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on.</toolTip>
<iconText>Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on.</iconText>
<shortcut>Ctrl+Shift+Y</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirror_canvas">
<icon>mirror-view</icon>
<text>Mirror View</text>
<whatsThis></whatsThis>
<toolTip>Mirror View</toolTip>
<iconText>Mirror View</iconText>
<shortcut>M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="zoom_to_100pct">
<icon>zoom-original</icon>
<text>&amp;Reset zoom</text>
<whatsThis></whatsThis>
<toolTip>Reset zoom</toolTip>
<iconText>Reset zoom</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+0</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_zoom_in">
<icon>zoom-in</icon>
<text>Zoom &amp;In</text>
<whatsThis></whatsThis>
<toolTip>Zoom In</toolTip>
<iconText></iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl++</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_zoom_out">
<icon>zoom-out</icon>
<text>Zoom &amp;Out</text>
<whatsThis></whatsThis>
<toolTip>Zoom Out</toolTip>
<iconText></iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+-</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotate_canvas_right">
<icon>rotate-canvas-right</icon>
<text>Rotate &amp;Canvas Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate Canvas Right</toolTip>
<iconText>Rotate Canvas Right</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+]</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotate_canvas_left">
<icon>rotate-canvas-left</icon>
<text>Rotate Canvas &amp;Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate Canvas Left</toolTip>
<iconText>Rotate Canvas Left</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+[</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reset_canvas_rotation">
<icon>rotation-reset</icon>
<text>Reset Canvas Rotation</text>
<whatsThis></whatsThis>
<toolTip>Reset Canvas Rotation</toolTip>
<iconText>Reset Canvas Rotation</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_ruler">
<icon></icon>
<text>Show &amp;Rulers</text>
<whatsThis>The rulers show the horizontal and vertical positions of the mouse on the image and can be used to position your mouse at the right place on the canvas. &lt;p>Uncheck this to hide the rulers.&lt;/p></whatsThis>
<toolTip>Show Rulers</toolTip>
<iconText>Show Rulers</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rulers_track_mouse">
<icon></icon>
<text>Rulers Track Pointer</text>
<whatsThis>The rulers will track current mouse position and show it on screen. It can cause suptle performance slowdown</whatsThis>
<toolTip>Rulers Track Pointer</toolTip>
<iconText>Rulers Track Pointer</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_show_guides">
<icon></icon>
<text>Show Guides</text>
<whatsThis></whatsThis>
<toolTip>Show or hide guides</toolTip>
<iconText>Show Guides</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_lock_guides">
<icon></icon>
<text>Lock Guides</text>
<whatsThis></whatsThis>
<toolTip>Lock or unlock guides</toolTip>
<iconText>Lock Guides</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_to_guides">
<icon></icon>
<text>Snap to Guides</text>
<whatsThis></whatsThis>
<toolTip>Snap cursor to guides position</toolTip>
<iconText>Snap to Guides</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="showStatusBar">
<icon></icon>
<text>Show Status &amp;Bar</text>
<whatsThis></whatsThis>
<toolTip>Show or hide the status bar</toolTip>
<iconText>Show Status Bar</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
+ <Action name="view_pixel_grid">
+ <icon></icon>
+ <text>Show Pixel Grid</text>
+ <whatsThis></whatsThis>
+ <toolTip>Show Pixel Grid</toolTip>
+ <iconText>Show Pixel Grid</iconText>
+ <activationFlags>1000</activationFlags>
+ <activationConditions>0</activationConditions>
+ <shortcut></shortcut>
+ <isCheckable>true</isCheckable>
+ <statusTip></statusTip>
+ </Action>
<Action name="view_grid">
<icon>view-grid</icon>
<text>Show &amp;Grid</text>
<whatsThis></whatsThis>
<toolTip>Show Grid</toolTip>
<iconText>Show Grid</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+'</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_to_grid">
<icon></icon>
<text>Snap To Grid</text>
<whatsThis></whatsThis>
<toolTip>Snap To Grid</toolTip>
<iconText>Snap To Grid</iconText>
<activationFlags>1000</activationFlags>
<shortcut>Ctrl+Shift+;</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_snap_options_popup">
<icon></icon>
<text>Show Snap Options Popup</text>
<whatsThis></whatsThis>
<toolTip>Show Snap Options Popup</toolTip>
<iconText>Show Snap Options Popup</iconText>
<activationFlags>1000</activationFlags>
<shortcut>Shift+s</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_orthogonal">
<icon></icon>
<text>Snap Orthogonal</text>
<whatsThis></whatsThis>
<toolTip>Snap Orthogonal</toolTip>
<iconText>Snap Orthogonal</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_node">
<icon></icon>
<text>Snap Node</text>
<whatsThis></whatsThis>
<toolTip>Snap Node</toolTip>
<iconText>Snap Node</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_extension">
<icon></icon>
<text>Snap Extension</text>
<whatsThis></whatsThis>
<toolTip>Snap Extension</toolTip>
<iconText>Snap Extension</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_intersection">
<icon></icon>
<text>Snap Intersection</text>
<whatsThis></whatsThis>
<toolTip>Snap Intersection</toolTip>
<iconText>Snap Intersection</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_bounding_box">
<icon></icon>
<text>Snap Bounding Box</text>
<whatsThis></whatsThis>
<toolTip>Snap Bounding Box</toolTip>
<iconText>Snap Bounding Box</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_image_bounds">
<icon></icon>
<text>Snap Image Bounds</text>
<whatsThis></whatsThis>
<toolTip>Snap Image Bounds</toolTip>
<iconText>Snap Image Bounds</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_image_center">
<icon></icon>
<text>Snap Image Center</text>
<whatsThis></whatsThis>
<toolTip>Snap Image Center</toolTip>
<iconText>Snap Image Center</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggle_painting_assistants">
<icon></icon>
<text>S&amp;how Painting Assistants</text>
<whatsThis></whatsThis>
<toolTip>Show Painting Assistants</toolTip>
<iconText>Show Painting Assistants</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggle_assistant_previews">
<icon></icon>
<text>Show &amp;Assistant Previews</text>
<whatsThis></whatsThis>
<toolTip>Show Assistant Previews</toolTip>
<iconText>Show Assistant Previews</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Image">
<text>Image</text>
<Action name="image_properties">
<icon>document-properties</icon>
<text>&amp;Properties...</text>
<whatsThis></whatsThis>
<toolTip>Properties</toolTip>
<iconText>Properties</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="image_color">
<icon>format-stroke-color</icon>
<text>&amp;Image Background Color and Transparency...</text>
<whatsThis></whatsThis>
<toolTip>Change the background color of the image</toolTip>
<iconText>Image Background Color and Transparency</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="imagecolorspaceconversion">
<icon></icon>
<text>&amp;Convert Image Color Space...</text>
<whatsThis></whatsThis>
<toolTip>Convert Image Color Space</toolTip>
<iconText>Convert Image Color Space</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="trim_to_image">
<icon>trim-to-image</icon>
<text>&amp;Trim to Image Size</text>
<whatsThis></whatsThis>
<toolTip>Trim to Image Size</toolTip>
<iconText>Trim to Image Size</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="resizeimagetolayer">
<icon></icon>
<text>Trim to Current &amp;Layer</text>
<whatsThis></whatsThis>
<toolTip>Trim to Current Layer</toolTip>
<iconText>Trim to Current Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="resizeimagetoselection">
<icon></icon>
<text>Trim to S&amp;election</text>
<whatsThis></whatsThis>
<toolTip>Trim to Selection</toolTip>
<iconText>Trim to Selection</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateimage">
<icon></icon>
<text>&amp;Rotate Image...</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image</toolTip>
<iconText>Rotate Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateImageCW90">
<icon>object-rotate-right</icon>
<text>Rotate &amp;Image 90° to the Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image 90° to the Right</toolTip>
<iconText>Rotate Image 90° to the Right</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateImageCCW90">
<icon>object-rotate-left</icon>
<text>Rotate Image &amp;90° to the Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image 90° to the Left</toolTip>
<iconText>Rotate Image 90° to the Left</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateImage180">
<icon></icon>
<text>Rotate Image &amp;180°</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image 180°</toolTip>
<iconText>Rotate Image 180°</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shearimage">
<icon></icon>
<text>&amp;Shear Image...</text>
<whatsThis></whatsThis>
<toolTip>Shear Image</toolTip>
<iconText>Shear Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorImageHorizontal">
<icon>symmetry-horizontal</icon>
<text>&amp;Mirror Image Horizontally</text>
<whatsThis></whatsThis>
<toolTip>Mirror Image Horizontally</toolTip>
<iconText>Mirror Image Horizontally</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorImageVertical">
<icon>symmetry-vertical</icon>
<text>Mirror Image &amp;Vertically</text>
<whatsThis></whatsThis>
<toolTip>Mirror Image Vertically</toolTip>
<iconText>Mirror Image Vertically</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="imagesize">
<icon></icon>
<text>Scale Image To &amp;New Size...</text>
<whatsThis></whatsThis>
<toolTip>Scale Image To New Size</toolTip>
<iconText>Scale Image To New Size</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="offsetimage">
<icon></icon>
<text>&amp;Offset Image...</text>
<whatsThis></whatsThis>
<toolTip>Offset Image</toolTip>
<iconText>Offset Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="canvassize">
<icon></icon>
<text>R&amp;esize Canvas...</text>
<whatsThis></whatsThis>
<toolTip>Resize Canvas</toolTip>
<iconText>Resize Canvas</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="imagesplit">
<icon></icon>
<text>Im&amp;age Split </text>
<whatsThis></whatsThis>
<toolTip>Image Split</toolTip>
<iconText>Image Split</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="separate">
<icon></icon>
<text>Separate Ima&amp;ge...</text>
<whatsThis></whatsThis>
<toolTip>Separate Image</toolTip>
<iconText>Separate Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Select">
<text>Select</text>
<Action name="select_all">
<icon>edit-select-all</icon>
<text>Select &amp;All</text>
<whatsThis></whatsThis>
<toolTip>Select All</toolTip>
<iconText>Select All</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="deselect">
<icon>edit-select-all</icon>
<text>&amp;Deselect</text>
<whatsThis></whatsThis>
<toolTip>Deselect</toolTip>
<iconText>Deselect</iconText>
<activationFlags>1100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reselect">
<icon></icon>
<text>&amp;Reselect</text>
<whatsThis></whatsThis>
<toolTip>Reselect</toolTip>
<iconText>Reselect</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+D</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_invert">
<icon></icon>
<text>&amp;Invert</text>
<whatsThis></whatsThis>
<toolTip>Invert</toolTip>
<iconText>Invert</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_vector_selection">
<icon></icon>
<text>&amp;Convert to Vector Selection</text>
<whatsThis></whatsThis>
<toolTip>Convert to Vector Selection</toolTip>
<iconText>Convert to Vector Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_shapes_to_vector_selection">
<icon></icon>
<text>Convert Shapes to &amp;Vector Selection</text>
<whatsThis></whatsThis>
<toolTip>Convert Shapes to Vector Selection</toolTip>
<iconText>Convert Shapes to Vector Selection</iconText>
<activationFlags>1000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="featherselection">
<icon></icon>
<text>&amp;Feather Selection...</text>
<whatsThis></whatsThis>
<toolTip>Feather Selection</toolTip>
<iconText>Feather Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut>Shift+F6</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_display_selection">
<icon></icon>
<text>Dis&amp;play Selection</text>
<whatsThis></whatsThis>
<toolTip>Display Selection</toolTip>
<iconText>Display Selection</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+H</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selectionscale">
<icon></icon>
<text>Sca&amp;le...</text>
<whatsThis></whatsThis>
<toolTip>Scale</toolTip>
<iconText>Scale</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="colorrange">
<icon></icon>
<text>S&amp;elect from Color Range...</text>
<whatsThis></whatsThis>
<toolTip>Select from Color Range</toolTip>
<iconText>Select from Color Range</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selectopaque">
<icon></icon>
<text>Select &amp;Opaque</text>
<whatsThis></whatsThis>
<toolTip>Select Opaque</toolTip>
<iconText>Select Opaque</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="growselection">
<icon></icon>
<text>&amp;Grow Selection...</text>
<whatsThis></whatsThis>
<toolTip>Grow Selection</toolTip>
<iconText>Grow Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shrinkselection">
<icon></icon>
<text>S&amp;hrink Selection...</text>
<whatsThis></whatsThis>
<toolTip>Shrink Selection</toolTip>
<iconText>Shrink Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="borderselection">
<icon></icon>
<text>&amp;Border Selection...</text>
<whatsThis></whatsThis>
<toolTip>Border Selection</toolTip>
<iconText>Border Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="smoothselection">
<icon></icon>
<text>S&amp;mooth</text>
<whatsThis></whatsThis>
<toolTip>Smooth</toolTip>
<iconText>Smooth</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Filter">
<text>Filter</text>
<Action name="filter_apply_again">
<icon></icon>
<text>&amp;Apply Filter Again</text>
<whatsThis></whatsThis>
<toolTip>Apply Filter Again</toolTip>
<iconText>Apply Filter Again</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+F</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="adjust_filters">
<icon></icon>
<text>Adjust</text>
<whatsThis></whatsThis>
<toolTip>Adjust</toolTip>
<iconText>Adjust</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="artistic_filters">
<icon></icon>
<text>Artistic</text>
<whatsThis></whatsThis>
<toolTip>Artistic</toolTip>
<iconText>Artistic</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="blur_filters">
<icon></icon>
<text>Blur</text>
<whatsThis></whatsThis>
<toolTip>Blur</toolTip>
<iconText>Blur</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="color_filters">
<icon></icon>
<text>Colors</text>
<whatsThis></whatsThis>
<toolTip>Colors</toolTip>
<iconText>Colors</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edge_filters">
<icon></icon>
<text>Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Edge Detection</toolTip>
<iconText>Edge Detection</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="enhance_filters">
<icon></icon>
<text>Enhance</text>
<whatsThis></whatsThis>
<toolTip>Enhance</toolTip>
<iconText>Enhance</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="emboss_filters">
<icon></icon>
<text>Emboss</text>
<whatsThis></whatsThis>
<toolTip>Emboss</toolTip>
<iconText>Emboss</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="map_filters">
<icon></icon>
<text>Map</text>
<whatsThis></whatsThis>
<toolTip>Map</toolTip>
<iconText>Map</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="other_filters">
<icon></icon>
<text>Other</text>
<whatsThis></whatsThis>
<toolTip>Other</toolTip>
<iconText>Other</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="QMic">
<icon>gmic</icon>
<text>Start G'MIC-Qt</text>
<whatsThis></whatsThis>
<toolTip>Start G'Mic-Qt</toolTip>
<iconText>Start G'Mic-Qt</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="QMicAgain">
<icon>gmic</icon>
<text>Re-apply the last G'MIC filter</text>
<whatsThis></whatsThis>
<toolTip>Apply the last G'Mic-Qt action again</toolTip>
<iconText>Apply the last G'Mic-Qt action again</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Tools">
<text>Tools</text>
<Action name="Recording_Start_Recording_Macro">
<icon>media-record</icon>
<text>&amp;Start recording macro</text>
<whatsThis></whatsThis>
<toolTip>Start recording macro</toolTip>
<iconText>Start recording macro</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Recording_Stop_Recording_Macro">
<icon>media-playback-stop</icon>
<text>Stop &amp;recording actions</text>
<whatsThis></whatsThis>
<toolTip>Stop recording actions</toolTip>
<iconText>Stop recording actions</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Macro_Open_Play">
<icon>media-playback-start</icon>
<text>&amp;Open and play...</text>
<whatsThis></whatsThis>
<toolTip>Open and play</toolTip>
<iconText>Open and play</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Macro_Open_Edit">
<icon>document-edit</icon>
<text>Open &amp;and edit...</text>
<whatsThis></whatsThis>
<toolTip>Open and edit</toolTip>
<iconText>Open and edit</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Settings">
<text>Settings</text>
<Action name="options_configure">
<icon>configure</icon>
<text>&amp;Configure Krita...</text>
<whatsThis></whatsThis>
<toolTip>Configure Krita</toolTip>
<iconText>Configure Krita</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="manage_bundles">
<icon></icon>
<text>&amp;Manage Resources...</text>
<whatsThis></whatsThis>
<toolTip>Manage Resources</toolTip>
<iconText>Manage Resources</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="switch_application_language">
<icon>preferences-desktop-locale</icon>
<text>Switch Application &amp;Language...</text>
<whatsThis></whatsThis>
<toolTip>Switch Application Language</toolTip>
<iconText>Switch Application Language</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggledockers">
<icon></icon>
<text>&amp;Show Dockers</text>
<whatsThis></whatsThis>
<toolTip>Show Dockers</toolTip>
<iconText>Show Dockers</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggledockertitlebars">
<icon></icon>
<text>Sho&amp;w Docker Titlebars</text>
<whatsThis></whatsThis>
<toolTip>Show Docker Titlebars</toolTip>
<iconText>Show Docker Titlebars</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="options_configure_toolbars">
<icon>configure</icon>
<text>Configure Tool&amp;bars...</text>
<whatsThis></whatsThis>
<toolTip>Configure Toolbars</toolTip>
<iconText>Configure Toolbars</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="settings_dockers_menu">
<icon></icon>
<text>Dockers</text>
<whatsThis></whatsThis>
<toolTip>Dockers</toolTip>
<iconText>Dockers</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="theme_menu">
<icon></icon>
<text>&amp;Themes</text>
<whatsThis></whatsThis>
<toolTip>Themes</toolTip>
<iconText>Themes</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="settings_active_author">
<icon>im-user</icon>
<text>Active Author Profile</text>
<whatsThis></whatsThis>
<toolTip>Active Author Profile</toolTip>
<iconText>Active Author Profile</iconText>
<shortcut></shortcut>
<isCheckable></isCheckable>
<statusTip></statusTip>
</Action>
<Action name="options_configure_keybinding">
<icon>configure-shortcuts</icon>
<text>Configure S&amp;hortcuts...</text>
<whatsThis></whatsThis>
<toolTip>Configure Shortcuts</toolTip>
<iconText>Configure Shortcuts</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="window">
<icon></icon>
<text>&amp;Window</text>
<whatsThis></whatsThis>
<toolTip>Window</toolTip>
<iconText>Window</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Help">
<text>Help</text>
<Action name="help_contents">
<icon>help-contents</icon>
<text>Krita &amp;Handbook</text>
<whatsThis></whatsThis>
<toolTip>Krita Handbook</toolTip>
<iconText>Krita Handbook</iconText>
<shortcut>F1</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="help_report_bug">
<icon>tools-report-bug</icon>
<text>&amp;Report Bug...</text>
<whatsThis></whatsThis>
<toolTip>Report Bug</toolTip>
<iconText>Report Bug</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="help_about_app">
<icon>calligrakrita</icon>
<text>&amp;About Krita</text>
<whatsThis></whatsThis>
<toolTip>About Krita</toolTip>
<iconText>About Krita</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="help_about_kde">
<icon>kde</icon>
<text>About &amp;KDE</text>
<whatsThis></whatsThis>
<toolTip>About KDE</toolTip>
<iconText>About KDE</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="BrushesAndStuff">
<text>Brushes and Stuff</text>
<Action name="gradients">
<icon></icon>
<text>&amp;Gradients</text>
<whatsThis></whatsThis>
<toolTip>Gradients</toolTip>
<iconText>Gradients</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="patterns">
<icon></icon>
<text>&amp;Patterns</text>
<whatsThis></whatsThis>
<toolTip>Patterns</toolTip>
<iconText>Patterns</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="dual">
<icon></icon>
<text>&amp;Color</text>
<whatsThis></whatsThis>
<toolTip>Color</toolTip>
<iconText>Color</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paintops">
<icon></icon>
<text>&amp;Painter's Tools</text>
<whatsThis></whatsThis>
<toolTip>Painter's Tools</toolTip>
<iconText>Painter's Tools</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="composite_actions">
<icon></icon>
<text>Brush composite</text>
<whatsThis></whatsThis>
<toolTip>Brush composite</toolTip>
<iconText>Brush composite</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="brushslider1">
<icon></icon>
<text>Brush option slider 1</text>
<whatsThis></whatsThis>
<toolTip>Brush option slider 1</toolTip>
<iconText>Brush option slider 1</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="brushslider2">
<icon></icon>
<text>Brush option slider 2</text>
<whatsThis></whatsThis>
<toolTip>Brush option slider 2</toolTip>
<iconText>Brush option slider 2</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="brushslider3">
<icon></icon>
<text>Brush option slider 3</text>
<whatsThis></whatsThis>
<toolTip>Brush option slider 3</toolTip>
<iconText>Brush option slider 3</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirror_actions">
<icon></icon>
<text>Mirror</text>
<whatsThis></whatsThis>
<toolTip>Mirror</toolTip>
<iconText>Mirror</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="workspaces">
<icon></icon>
<text>Workspaces</text>
<whatsThis></whatsThis>
<toolTip>Workspaces</toolTip>
<iconText>Workspaces</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
</ActionCollection>
diff --git a/krita/main.cc b/krita/main.cc
index 86bfb4d0ae..5864cd875b 100644
--- a/krita/main.cc
+++ b/krita/main.cc
@@ -1,314 +1,314 @@
/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdlib.h>
#include <QString>
#include <QPixmap>
#include <kis_debug.h>
#include <QProcess>
#include <QProcessEnvironment>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QDir>
#include <QDate>
#include <QLocale>
#include <QSettings>
#include <time.h>
#include <KisApplication.h>
#include <KisLoggingManager.h>
#include <KoConfig.h>
#include <KoResourcePaths.h>
#include "data/splash/splash_screen.xpm"
#include "data/splash/splash_holidays.xpm"
#include "KisDocument.h"
#include "kis_splash_screen.h"
#include "KisPart.h"
#include "KisApplicationArguments.h"
#include <opengl/kis_opengl.h>
#include "input/KisQtWidgetsTweaker.h"
#if defined Q_OS_WIN
#include <windows.h>
#include <stdlib.h>
#include <kis_tablet_support_win.h>
#include <kis_tablet_support_win8.h>
#include <kis_config.h>
#elif defined HAVE_X11
#include <kis_tablet_support_x11.h>
#include <kis_xi2_event_filter.h>
#endif
#if defined HAVE_KCRASH
#include <kcrash.h>
#elif defined USE_DRMINGW
namespace
{
void tryInitDrMingw()
{
wchar_t path[MAX_PATH];
QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll");
if (pathStr.size() > MAX_PATH - 1) {
return;
}
int pathLen = pathStr.toWCharArray(path);
path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator
HMODULE hMod = LoadLibraryW(path);
if (!hMod) {
return;
}
// No need to call ExcHndlInit since the crash handler is installed on DllMain
auto myExcHndlSetLogFileNameA = reinterpret_cast<BOOL (APIENTRY *)(const char *)>(GetProcAddress(hMod, "ExcHndlSetLogFileNameA"));
if (!myExcHndlSetLogFileNameA) {
return;
}
// Set the log file path to %LocalAppData%\kritacrash.log
QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log");
myExcHndlSetLogFileNameA(logFile.toLocal8Bit());
}
} // namespace
#endif
extern "C" int main(int argc, char **argv)
{
// The global initialization of the random generator
qsrand(time(0));
bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty();
#if defined HAVE_X11
qputenv("QT_QPA_PLATFORM", "xcb");
#endif
KisLoggingManager::initialize();
// A per-user unique string, without /, because QLocalServer cannot use names with a / in it
- QString key = "Krita3" + QDesktopServices::storageLocation(QDesktopServices::HomeLocation).replace("/", "_");
+ QString key = "Krita3" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_");
key = key.replace(":", "_").replace("\\","_");
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true);
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
bool singleApplication = true;
bool enableOpenGLDebug = false;
bool openGLDebugSynchronous = false;
{
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
singleApplication = kritarc.value("EnableSingleApplication", true).toBool();
#if QT_VERSION >= 0x050600
if (kritarc.value("EnableHiDPI", false).toBool()) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
}
if (!qgetenv("KRITA_HIDPI").isEmpty()) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
}
#endif
if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) {
enableOpenGLDebug = true;
} else {
enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool();
}
if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) {
openGLDebugSynchronous = true;
}
KisOpenGL::setDefaultFormat(enableOpenGLDebug, openGLDebugSynchronous);
#ifdef Q_OS_WIN
QString preferredOpenGLRenderer = kritarc.value("OpenGLRenderer", "auto").toString();
// Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP
// might get weird crashes atm.
qputenv("QT_ANGLE_PLATFORM", "d3d11");
// Probe QPA auto OpenGL detection
KisOpenGL::probeWindowsQpaOpenGL(argc, argv, preferredOpenGLRenderer);
#endif
}
KLocalizedString::setApplicationDomain("krita");
// first create the application so we can create a pixmap
KisApplication app(key, argc, argv);
#ifdef Q_OS_LINUX
qputenv("XDG_DATA_DIRS", QFile::encodeName(KoResourcePaths::getApplicationRoot() + "share") + ":" + qgetenv("XDG_DATA_DIRS"));
#else
qputenv("XDG_DATA_DIRS", QFile::encodeName(KoResourcePaths::getApplicationRoot() + "share"));
#endif
qDebug() << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS");
qDebug() << "Available translations" << KLocalizedString::availableApplicationTranslations();
qDebug() << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita");
// Now that the paths are set, set the language. First check the override from the langage
// selection dialog.
{
QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat);
languageoverride.beginGroup(QStringLiteral("Language"));
QString language = languageoverride.value(qAppName(), "").toString();
qDebug() << "Override language:" << language;
if (!language.isEmpty()) {
KLocalizedString::setLanguages(language.split(":"));
// And override Qt's locale, too
qputenv("LANG", language.split(":").first().toUtf8());
QLocale locale(language.split(":").first());
QLocale::setDefault(locale);
qDebug() << "Qt ui languages" << locale.uiLanguages();
}
else {
// And if there isn't one, check the one set by the system.
// XXX: This doesn't work, for some !@#$% reason.
QLocale locale = QLocale::system();
if (locale.bcp47Name() != QStringLiteral("en")) {
qputenv("LANG", locale.bcp47Name().toLatin1());
KLocalizedString::setLanguages(QStringList() << locale.bcp47Name());
}
}
}
#ifdef Q_OS_WIN
QDir appdir(KoResourcePaths::getApplicationRoot());
QString path = qgetenv("PATH");
qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";"
+ appdir.absolutePath() + "/lib" + ";"
+ appdir.absolutePath() + "/Frameworks" + ";"
+ appdir.absolutePath() + ";"
+ path));
qDebug() << "PATH" << qgetenv("PATH");
#endif
if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) {
qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location.");
}
#if defined HAVE_KCRASH
KCrash::initialize();
#elif defined USE_DRMINGW
tryInitDrMingw();
#endif
// If we should clear the config, it has to be done as soon as possible after
// KisApplication has been created. Otherwise the config file may have been read
// and stored in a KConfig object we have no control over.
app.askClearConfig();
KisApplicationArguments args(app);
if (singleApplication && app.isRunning()) {
// only pass arguments to main instance if they are not for batch processing
// any batch processing would be done in this separate instance
const bool batchRun = (args.print() || args.exportAs() || args.exportAsPdf());
if (!batchRun) {
QByteArray ba = args.serialize();
if (app.sendMessage(ba)) {
return 0;
}
}
}
if (!runningInKDE) {
// Icons in menus are ugly and distracting
app.setAttribute(Qt::AA_DontShowIconsInMenus);
}
#if defined HAVE_X11
app.installNativeEventFilter(KisXi2EventFilter::instance());
#endif
app.installEventFilter(KisQtWidgetsTweaker::instance());
if (!args.noSplash()) {
// then create the pixmap from an xpm: we cannot get the
// location of our datadir before we've started our components,
// so use an xpm.
QDate currentDate = QDate::currentDate();
QWidget *splash = 0;
if (currentDate > QDate(currentDate.year(), 12, 4) ||
currentDate < QDate(currentDate.year(), 1, 9)) {
splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm));
}
else {
splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm));
}
app.setSplashScreen(splash);
}
#if defined Q_OS_WIN
{
KisConfig cfg;
bool isUsingWin8PointerInput = false;
if (cfg.useWin8PointerInput()) {
KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8();
if (penFilter->init()) {
// penFilter.registerPointerDeviceNotifications();
app.installNativeEventFilter(penFilter);
isUsingWin8PointerInput = true;
qDebug() << "Using Win8 Pointer Input for tablet support";
} else {
qDebug() << "No Win8 Pointer Input available";
delete penFilter;
}
}
if (!isUsingWin8PointerInput) {
KisTabletSupportWin::init();
// app.installNativeEventFilter(new KisTabletSupportWin());
}
}
#endif
if (!app.start(args)) {
return 1;
}
#if QT_VERSION >= 0x050700
app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false);
#endif
// Set up remote arguments.
QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)),
&app, SLOT(remoteArguments(QByteArray,QObject*)));
QObject::connect(&app, SIGNAL(fileOpenRequest(QString)),
&app, SLOT(fileOpenRequested(QString)));
int state = app.exec();
{
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("canvasState", "OPENGL_SUCCESS");
}
return state;
}
diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml
index 3361a99637..4fd3677dda 100644
--- a/krita/org.kde.krita.appdata.xml
+++ b/krita/org.kde.krita.appdata.xml
@@ -1,172 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<component type="desktop">
<id>org.kde.krita.desktop</id>
<metadata_license>CC0-1.0</metadata_license>
<name>Krita</name>
<name xml:lang="ast">Krita</name>
<name xml:lang="ca">Krita</name>
<name xml:lang="ca-valencia">Krita</name>
<name xml:lang="cs">Krita</name>
<name xml:lang="de">Krita</name>
+ <name xml:lang="el">Krita</name>
<name xml:lang="en-GB">Krita</name>
<name xml:lang="es">Krita</name>
<name xml:lang="fr">Krita</name>
<name xml:lang="gl">Krita</name>
<name xml:lang="it">Krita</name>
<name xml:lang="nl">Krita</name>
<name xml:lang="pl">Krita</name>
<name xml:lang="pt">Krita</name>
<name xml:lang="pt-BR">Krita</name>
<name xml:lang="sk">Krita</name>
<name xml:lang="sv">Krita</name>
<name xml:lang="uk">Krita</name>
<name xml:lang="x-test">xxKritaxx</name>
<name xml:lang="zh-CN">Krita</name>
<summary>Digital Painting, Creative Freedom</summary>
<summary xml:lang="ast">Pintura dixital, llibertá creativa</summary>
<summary xml:lang="bs">Digitalno crtanje, kreativna sloboda</summary>
<summary xml:lang="ca">Dibuix digital, Llibertat creativa</summary>
<summary xml:lang="ca-valencia">Dibuix digital, Llibertat creativa</summary>
<summary xml:lang="cs">Digitální malování, svoboda tvorby</summary>
<summary xml:lang="da">Digital tegning, kunstnerisk frihed</summary>
<summary xml:lang="de">Digitales Malen, kreative Freiheit</summary>
+ <summary xml:lang="el">Ψηφιακή ζωγραφική, δημιουργική ελευθερία</summary>
<summary xml:lang="en-GB">Digital Painting, Creative Freedom</summary>
<summary xml:lang="es">Pintura digital, libertad creativa</summary>
<summary xml:lang="et">Digitaalne joonistamine, loominguline vabadus</summary>
<summary xml:lang="fi">Digitaalimaalaus, luova vapaus</summary>
<summary xml:lang="fr">Peinture numérique, liberté créatrice</summary>
<summary xml:lang="gl">Debuxo dixital, liberdade creativa</summary>
<summary xml:lang="ia">Pictura digital, Libertate creative</summary>
<summary xml:lang="it">Pittura digitale, libertà creativa</summary>
<summary xml:lang="nl">Digital Painting, Creative Freedom</summary>
<summary xml:lang="pl">Cyfrowe malowanie, Wolność Twórcza</summary>
<summary xml:lang="pt">Pintura Digital, Liberdade Criativa</summary>
<summary xml:lang="pt-BR">Pintura digital, liberdade criativa</summary>
<summary xml:lang="ru">Цифровое рисование. Творческая свобода</summary>
<summary xml:lang="sk">Digitálne maľovanie, kreatívna sloboda</summary>
<summary xml:lang="sv">Digital målning, kreativ frihet</summary>
<summary xml:lang="uk">Цифрове малювання, творча свобода</summary>
<summary xml:lang="x-test">xxDigital Painting, Creative Freedomxx</summary>
<summary xml:lang="zh-CN">数码绘图,自由创作</summary>
<description>
<p>Krita is the full-featured digital art studio.</p>
<p xml:lang="ast">Krita ye l'estudiu completu d'arte dixital.</p>
<p xml:lang="bs">Krita je potpuni digitalni umjetnički studio.</p>
<p xml:lang="ca">Krita és l'estudi d'art digital ple de funcionalitats.</p>
<p xml:lang="ca-valencia">Krita és l'estudi d'art digital ple de funcionalitats.</p>
<p xml:lang="de">Krita ist ein digitales Designstudio mit umfangreichen Funktionen.</p>
+ <p xml:lang="el">Το Krita είναι ένα πλήρες χαρακτηριστικών ψηφιακό ατελιέ.</p>
<p xml:lang="en-GB">Krita is the full-featured digital art studio.</p>
<p xml:lang="es">Krita es un estudio de arte digital completo</p>
<p xml:lang="et">Krita on rohkete võimalustega digitaalkunstistuudio.</p>
<p xml:lang="fi">Krita on täyspiirteinen digitaiteen ateljee.</p>
<p xml:lang="fr">Krita est le studio d'art numérique complet.</p>
<p xml:lang="gl">Krita é un estudio completo de arte dixital.</p>
<p xml:lang="ia">Krita es le studio de arte digital complete.</p>
<p xml:lang="it">Krita è uno studio d'arte digitale completo.</p>
<p xml:lang="ja">Krita は、フル機能を備えたデジタルなアートスタジオです。</p>
<p xml:lang="nl">Krita is de digitale kunststudio vol mogelijkheden.</p>
<p xml:lang="pl">Krita jest pełnowymiarowym, cyfrowym studiem artystycznym</p>
<p xml:lang="pt">O Krita é o estúdio de arte digital completo.</p>
<p xml:lang="pt-BR">O Krita é o estúdio de arte digital completo.</p>
<p xml:lang="ru">Krita полнофункциональный инструмент для создания цифровой графики.</p>
<p xml:lang="sk">Krita je plne vybavené digitálne umelecké štúdio.</p>
<p xml:lang="sv">Krita är den fullfjädrade digitala konststudion.</p>
<p xml:lang="uk">Krita — повноцінний комплекс для створення цифрових художніх творів.</p>
<p xml:lang="x-test">xxKrita is the full-featured digital art studio.xx</p>
- <p xml:lang="zh-CN">Krita 是全功能的数码艺术工作室。</p>
+ <p xml:lang="zh-CN">Krita 是全功能的数字艺术工作室。</p>
<p>It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.</p>
<p xml:lang="bs">On je savršen za skiciranje i slikanje i predstavlja finalno rješenje za kreiranje digitalnih slika od nule s majstorima</p>
<p xml:lang="ca">És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.</p>
<p xml:lang="ca-valencia">És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.</p>
+ <p xml:lang="el">Είναι ιδανικό για σκιτσογραφία και ζωγραφική, και παρουσιάζει μια από άκρη σε άκρη λύση για τη δημιουργία από το μηδέν αρχείων ψηφιακης ζωγραφικής από τους δασκάλους της τέχνης.</p>
<p xml:lang="en-GB">It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.</p>
<p xml:lang="es">Es perfecto para diseñar y pintar, y ofrece una solución completa para crear desde cero archivos de pintura digital apta para profesionales.</p>
<p xml:lang="et">See on suurepärane töövahend visandite ja joonistuste valmistamiseks ning annab andekatele kunstnikele võimaluse luua digitaalpilt algusest lõpuni just oma käe järgi.</p>
<p xml:lang="fi">Se on täydellinen luonnosteluun ja maalaukseen ja tarjoaa kokonaisratkaisun digitaalisten kuvatiedostojen luomiseen alusta alkaen.</p>
<p xml:lang="fr">Il est parfait pour crayonner et peindre, et constitue une solution de bout en bout pour créer des fichier de peinture numérique depuis la feuille blanche jusqu'au épreuves finales.</p>
<p xml:lang="gl">Resulta perfecto para debuxar e pintar, e presenta unha solución completa que permite aos mestres crear ficheiros de debuxo dixital desde cero.</p>
<p xml:lang="ia">Illo es perfecte pro schizzar e pinger, e presenta un solution ab fin al fin pro crear files de pictura digital ab grattamentos per maestros.</p>
<p xml:lang="it">Perfetto per fare schizzi e dipingere, prevede una soluzione completa che consente agli artisti di creare file di dipinti digitali partendo da zero.</p>
<p xml:lang="nl">Het is perfect voor schetsen en schilderen en zet een end–to–end oplossing voor het maken van digitale bestanden voor schilderingen vanuit het niets door meesters.</p>
<p xml:lang="pl">Nadaje się perfekcyjnie do szkicowania i malowania i dostarcza zupełnego rozwiązania dla tworzenia plików malowideł cyfrowych od zalążka.</p>
<p xml:lang="pt">É perfeito para desenhos e pinturas, oferecendo uma solução final para criar ficheiros de pintura digital do zero por mestres.</p>
<p xml:lang="pt-BR">É perfeito para desenhos e pinturas, oferecendo uma solução final para criar arquivos de desenho digital feitos a partir do zero por mestres.</p>
<p xml:lang="ru">Она превосходно подходит для набросков и рисования, предоставляя мастерам самодостаточный инструмент для создания цифровой живописи с нуля.</p>
<p xml:lang="sk">Je ideálna na skicovanie a maľovanie a poskytuje end-to-end riešenie na vytváranie súborov digitálneho maľovania od základu od profesionálov.</p>
<p xml:lang="sv">Den är perfekt för att skissa och måla, samt erbjuder en helomfattande lösning för att skapa digitala målningsfiler från grunden av mästare.</p>
<p xml:lang="uk">Цей комплекс чудово пасує для створення ескізів та художніх зображень і є самодостатнім набором для створення файлів цифрових полотен «з нуля» для справжніх художників.</p>
<p xml:lang="x-test">xxIt is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.xx</p>
<p xml:lang="zh-CN">适合做草图和绘画,为艺术大师提供了从草稿到数码绘画的完整解决方案。</p>
<p>
Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK
at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.
</p>
<p xml:lang="bs">Krita je odličan izbor za kreiranje konceptualne umjetnosti, stripove, teksture za obradu i mat slike. Krita podržava mnoge prostore boja kao RGB i CMIK na 8 i 16 bitnim cjelobrojnim kanalimaa, kao i 16 i 32 bita floating point kanalima.</p>
<p xml:lang="ca">El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.</p>
<p xml:lang="ca-valencia">El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.</p>
+ <p xml:lang="el">Το Krita είναι μια εξαιρετική επιλογή για τη δημιουργία αφηρημένης τέχνης, ιστοριών με εικόνες, υφής για ζωγραφική αποτύπωσης και διάχυσης φωτός. Το Krita υποστηρίζει πολλούς χρωματικούς χώρους όπως τα RGB και CMYK σε 8 και 16 bit κανάλια ακεραίων καθώς επίσης και σε 16 και 32 bit κανάλια κινητής υποδιαστολής,</p>
<p xml:lang="en-GB">Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colourspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.</p>
<p xml:lang="es">Krita es una gran elección para crear arte conceptual, cómics, texturas para renderizar y «matte paintings». Krita permite el uso de muchos espacios de color, como, por ejemplo, RGB y CMYK, tanto en canales de enteros de 8 y 16 bits, así como en canales de coma flotante de 16 y 32 bits.</p>
<p xml:lang="et">Krita on üks paremaid valikuid kontseptuaalkunsti, koomiksite, tekstuuride ja digitaalmaalide loomiseks. Krita toetab paljusid värviruume, näiteks RGB ja CMYK 8 ja 16 täisarvulise bitiga kanali kohta, samuti 16 ja 32 ujukomabitiga kanali kohta.</p>
<p xml:lang="fi">Krita on hyvä valinta konseptikuvituksen, sarjakuvien, pintakuvioiden ja maalausten luomiseen. Krita tukee useita väriavaruuksia kuten RGB:tä ja CMYK:ta 8 ja 16 bitin kokonaisluku- samoin kuin 16 ja 32 bitin liukulukukanavin.</p>
<p xml:lang="fr">Krita est un très bon choix pour créer des concepts arts, des bandes-dessinées, des textures de rendu et des peintures. Krita prend en charge plusieurs espaces de couleurs comme RVB et CMJN avec les canaux de 8 et 16 bits entiers ainsi que les canaux de 16 et 32 bits flottants.</p>
<p xml:lang="gl">Krita é unha gran opción para crear arte conceptual, texturas para renderización e pinturas mate. Krita permite usar moitos espazos de cores como RGB e CMYK con canles de 8 e 16 bits, así como canles de coma flotante de 16 e 32 bits.</p>
<p xml:lang="ia">Krita es un grande selection pro crear arte de concepto, comics, texturas pro rendering e picturas opac. Krita supporta multe spatios de colores como RGB e CMYK con canales de integer a 8 e 16 bits, como anque canales floating point a 16 e 32 bits.</p>
<p xml:lang="it">Krita rappresenta una scelta ottimale per la creazione di arte concettuale, fumetti e texture per il rendering e il matte painting. Krita supporta molti spazi colori come RGB e CMYK a 8 e 16 bit per canali interi e 16 e 32 bit per canali a virgola mobile.</p>
<p xml:lang="ja">コンセプトアート、コミック、3DCG 用テクスチャ、マットペイントを制作する方にとって、Krita は最適な選択です。Krita は、8/16 ビット整数/チャンネル、および 16/32 ビット浮動小数点/チャンネルの RGB や CMYK をはじめ、さまざまな色空間をサポートしています。</p>
<p xml:lang="nl">Krita is een goede keuze voor het maken van kunstconcepten, strips, textuur voor weergeven en matte schilderijen. Krita ondersteunt vele kleurruimten zoals RGB en CMYK in 8 en 16 bits kanalen met gehele getallen, evenals 16 en 32 bits kanalen met drijvende komma.</p>
<p xml:lang="pl">Krita jest świetnym wyborem przy tworzeniu koncepcyjnej sztuki, komiksów, tekstur do wyświetlania i kaszet. Krita obsługuje wiele przestrzeni barw takich jak RGB oraz CMYK dla kanałów 8 oraz 16 bitowych wyrażonych w l. całkowitych, a także 16 oraz 32 bitowych wyrażonych w l. zmiennoprzecinkowych.</p>
<p xml:lang="pt">O Krita é uma óptima escolha para criar arte conceptual, banda desenhada, texturas para desenho e pinturas. O Krita suporta diversos espaços de cores como o RGB e o CMYK com canais de cores inteiros a 8 e 16 bits, assim como canais de vírgula flutuante a 16 e a 32 bits.</p>
<p xml:lang="pt-BR">O Krita é uma ótima escolha para criação de arte conceitual, histórias em quadrinhos, texturas para desenhos e pinturas. O Krita tem suporte a diversos espaços de cores como RGB e CMYK com canais de cores inteiros de 8 e 16 bits, assim como canais de ponto flutuante de 16 e 32 bits.</p>
<p xml:lang="ru">Krita - отличный выбор для создания концепт-артов, комиксов, текстур для рендеринга и рисования. Она поддерживает множество цветовых пространств включая RGB и CMYK с 8 и 16 целыми битами на канал, а также 16 и 32 битами с плавающей запятой на канал.</p>
<p xml:lang="sk">Krita je výborná voľba pre vytváranie konceptového umenia, textúr na renderovanie a matné kresby. Krita podporuje mnoho farebných priestorov ako RGB a CMYK na 8 a 16 bitových celočíselných kanáloch ako aj 16 a 32 bitových reálnych kanáloch.</p>
<p xml:lang="sv">Krita är ett utmärkt val för att skapa concept art, serier, strukturer för återgivning och bakgrundsmålningar. Krita stöder många färgrymder som RGB och CMYK med 8- och 16-bitars heltal, samt 16- och 32-bitars flyttal.</p>
<p xml:lang="uk">Krita — чудовий інструмент для створення концептуального живопису, коміксів, текстур для моделей та декорацій. У Krita передбачено підтримку багатьох просторів кольорів, зокрема RGB та CMYK з 8-бітовими та 16-бітовими цілими значеннями, а також 16-бітовими та 32-бітовими значеннями з рухомою крапкою для каналів кольорів.</p>
<p xml:lang="x-test">xxKrita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.xx</p>
<p xml:lang="zh-CN">Krita 是创建抽象艺术,漫画,渲染纹理和亚光绘画的理想选择。Krita 支持非常多的色彩空间,比如 8 位和 16 位整数通道以及 16 位和 32 位浮点通道的 RGB 和 CMYK。</p>
<p>Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.</p>
<p xml:lang="bs">Zabavite se kreirajući napredne pogone četki, filtere i mnoge praktične osobine koje čine Krita vrlo produktivnim.</p>
<p xml:lang="ca">Gaudiu pintant amb els motors de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.</p>
<p xml:lang="ca-valencia">Gaudiu pintant amb els motors de pinzells avançats, els filtres impressionants i moltes funcionalitats útils que fan el Krita molt productiu.</p>
+ <p xml:lang="el">Διασκεδάστε ζωγραφίζοντας με τις προηγμένες μηχανές πινέλων, με εκπληκτικά φίλτρα και πολλά εύκολης χρήσης χαρακτηριστικά που παρέχουν στο Krita εξαιρετικά αυξημένη παραγωγικότητα.</p>
<p xml:lang="en-GB">Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.</p>
<p xml:lang="es">Diviértase pintando con los avanzados motores de pinceles, los espectaculares filtros y muchas funcionalidades prácticas que hacen que Krita sea enormemente productivo.</p>
<p xml:lang="et">Joonistamise muudavad tunduvalt lõbusamaks võimsad pintslimootorid, imetabased filtrid ja veel paljud käepärased võimalused, mis muudavad Krita kasutaja tohutult tootlikuks.</p>
<p xml:lang="fi">Pidä hauskaa maalatessasi edistyneillä sivellinmoottoreilla, hämmästyttävillä suotimilla ja monilla muilla kätevillä ominaisuuksilla, jotka tekevät Kritasta tavattoman tehokkaan.</p>
<p xml:lang="fr">Amusez-vous à peindre avec les outils de brosse avancés, les filtres incroyables et les nombreuses fonctionnalités pratiques qui rendent Krita extrêmement productif.</p>
<p xml:lang="gl">Goza debuxando con motores de pincel avanzados, filtros fantásticos e moitas outras funcionalidades útiles que fan de Krita un programa extremadamente produtivo.</p>
<p xml:lang="ia">Amusa te a pinger con le motores de pincel avantiate, filtros stupende e multe characteristicas amical que face Krita enormemente productive.</p>
<p xml:lang="it">Divertiti a dipingere con gli avanzati sistemi di pennelli, i sorprendenti filtri e molte altre utili caratteristiche che fanno di Krita un software enormemente produttivo.</p>
<p xml:lang="ja">Krita のソフトウェアとしての生産性を高めている先進的なブラシエンジンや素晴らしいフィルタのほか、便利な機能の数々をお楽しみください。</p>
<p xml:lang="nl">Veel plezier met schilderen met the geavanceerde penseel-engines, filters vol verbazing en vele handige mogelijkheden die maken dat Krita enorm productief is.</p>
<p xml:lang="pl">Baw się przy malowaniu przy użyciu zaawansowanych silników pędzli, zadziwiających filtrów i wielu innych przydatnych cech, które czynią z Krity bardzo produktywną.</p>
<p xml:lang="pt">Divirta-se a pintar com os motores de pincéis avançados, os filtros espantosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.</p>
<p xml:lang="pt-BR">Divirta-se pintando com os mecanismos de pincéis avançados, filtros maravilhosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.</p>
<p xml:lang="ru">Получайте удовольствие от использования особых кистевых движков, впечатляющих фильтров и множества других функций, делающих Krita сверхпродуктивной.</p>
<p xml:lang="sk">Užívajte si maľovanie s pokročilými kresliacimi enginmi, úžasnými filtrami a mnohými užitočnými funkciami, ktoré robia Kritu veľmi produktívnu.</p>
<p xml:lang="sv">Ha det så kul vid målning med de avancerade penselfunktionerna, fantastiska filtren och många praktiska funktioner som gör Krita så enormt produktiv.</p>
<p xml:lang="uk">Отримуйте задоволення від малювання за допомогою пензлів з найширшими можливостями, чудових фільтрів та багатьох зручних можливостей, які роблять Krita надзвичайно продуктивним засобом малювання.</p>
<p xml:lang="x-test">xxHave fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.xx</p>
<p xml:lang="zh-CN">尽情使用高级笔刷引擎,超赞的滤镜和很多手绘特性,发挥 Krita 绝佳的创造力。</p>
</description>
<url type="homepage">https://www.krita.org/</url>
<url type="faq">https://krita.org/about/faq/</url>
<url type="donation">https://krita.org/support-us/donations/</url>
<url type="help">https://docs.krita.org/Category:Tutorials</url>
<screenshots>
<screenshot type="default">
<image>http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_002.png</image>
</screenshot>
<screenshot type="default">
<image>http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_003.png</image>
</screenshot>
<screenshot type="default">
<image>http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_004.png</image>
</screenshot>
<screenshot type="default">
<image>http://files.kde.org/krita/marketing/appdata/2016-05-24_screenshot_005.png</image>
</screenshot>
</screenshots>
<update_contact>foundation@krita.org</update_contact>
<project_group>KDE</project_group>
<provides>
<binary>krita</binary>
</provides>
</component>
diff --git a/libs/brush/kis_auto_brush.cpp b/libs/brush/kis_auto_brush.cpp
index 510f9821e4..a16407c20d 100644
--- a/libs/brush/kis_auto_brush.cpp
+++ b/libs/brush/kis_auto_brush.cpp
@@ -1,413 +1,398 @@
/*
* Copyright (c) 2004,2007-2009 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <compositeops/KoVcMultiArchBuildSupport.h> //MSVC requires that Vc come first
#include "kis_auto_brush.h"
#include <kis_debug.h>
#include <math.h>
#include <QRect>
#include <QDomElement>
#include <QtConcurrentMap>
#include <QByteArray>
#include <QBuffer>
#include <QFile>
#include <QFileInfo>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <kis_datamanager.h>
#include <kis_fixed_paint_device.h>
#include <kis_paint_device.h>
#include <brushengine/kis_paint_information.h>
#include <kis_mask_generator.h>
#include <kis_boundary.h>
#include <brushengine/kis_paintop_lod_limitations.h>
#include <kis_brush_mask_applicator_base.h>
#if defined(_WIN32) || defined(_WIN64)
#include <stdlib.h>
#define srand48 srand
inline double drand48()
{
return double(rand()) / RAND_MAX;
}
#endif
struct KisAutoBrush::Private {
Private()
: randomness(0), density(1.0), idealThreadCountCached(1) {}
Private(const Private &rhs)
: shape(rhs.shape->clone()),
randomness(rhs.randomness),
density(rhs.density),
idealThreadCountCached(rhs.idealThreadCountCached)
{
}
QScopedPointer<KisMaskGenerator> shape;
qreal randomness;
qreal density;
int idealThreadCountCached;
};
KisAutoBrush::KisAutoBrush(KisMaskGenerator* as, qreal angle, qreal randomness, qreal density)
: KisBrush(),
d(new Private)
{
d->shape.reset(as);
d->randomness = randomness;
d->density = density;
d->idealThreadCountCached = QThread::idealThreadCount();
setBrushType(MASK);
setWidth(qMax(qreal(1.0), d->shape->width()));
setHeight(qMax(qreal(1.0), d->shape->height()));
QImage image = createBrushPreview();
setBrushTipImage(image);
// Set angle here so brush tip image is generated unrotated
setAngle(angle);
image = createBrushPreview();
setImage(image);
}
KisAutoBrush::~KisAutoBrush()
{
}
qreal KisAutoBrush::userEffectiveSize() const
{
return d->shape->diameter();
}
void KisAutoBrush::setUserEffectiveSize(qreal value)
{
d->shape->setDiameter(value);
}
KisAutoBrush::KisAutoBrush(const KisAutoBrush& rhs)
: KisBrush(rhs),
d(new Private(*rhs.d))
{
}
KisBrush* KisAutoBrush::clone() const
{
return new KisAutoBrush(*this);
}
/* It's difficult to predict the mask height when exaclty when there are
* more than 2 spikes, so we return an upperbound instead. */
static KisDabShape lieAboutDabShape(KisDabShape const& shape)
{
return KisDabShape(shape.scale(), 1.0, shape.rotation());
}
qint32 KisAutoBrush::maskHeight(KisDabShape const& shape,
qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
{
return KisBrush::maskHeight(
lieAboutDabShape(shape), subPixelX, subPixelY, info);
}
qint32 KisAutoBrush::maskWidth(KisDabShape const& shape,
qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
{
return KisBrush::maskWidth(
lieAboutDabShape(shape), subPixelX, subPixelY, info);
}
QSizeF KisAutoBrush::characteristicSize(KisDabShape const& shape) const
{
return KisBrush::characteristicSize(lieAboutDabShape(shape));
}
inline void fillPixelOptimized_4bytes(quint8 *color, quint8 *buf, int size)
{
/**
* This version of filling uses low granularity of data transfers
* (32-bit chunks) and internal processor's parallelism. It reaches
* 25% better performance in KisStrokeBenchmark in comparison to
* per-pixel memcpy version (tested on Sandy Bridge).
*/
int block1 = size / 8;
int block2 = size % 8;
quint32 *src = reinterpret_cast<quint32*>(color);
quint32 *dst = reinterpret_cast<quint32*>(buf);
// check whether all buffers are 4 bytes aligned
// (uncomment if experience some problems)
// Q_ASSERT(((qint64)src & 3) == 0);
// Q_ASSERT(((qint64)dst & 3) == 0);
for (int i = 0; i < block1; i++) {
*dst = *src;
*(dst + 1) = *src;
*(dst + 2) = *src;
*(dst + 3) = *src;
*(dst + 4) = *src;
*(dst + 5) = *src;
*(dst + 6) = *src;
*(dst + 7) = *src;
dst += 8;
}
for (int i = 0; i < block2; i++) {
*dst = *src;
dst++;
}
}
inline void fillPixelOptimized_general(quint8 *color, quint8 *buf, int size, int pixelSize)
{
/**
* This version uses internal processor's parallelism and gives
* 20% better performance in KisStrokeBenchmark in comparison to
* per-pixel memcpy version (tested on Sandy Bridge (+20%) and
* on Merom (+10%)).
*/
int block1 = size / 8;
int block2 = size % 8;
for (int i = 0; i < block1; i++) {
quint8 *d1 = buf;
quint8 *d2 = buf + pixelSize;
quint8 *d3 = buf + 2 * pixelSize;
quint8 *d4 = buf + 3 * pixelSize;
quint8 *d5 = buf + 4 * pixelSize;
quint8 *d6 = buf + 5 * pixelSize;
quint8 *d7 = buf + 6 * pixelSize;
quint8 *d8 = buf + 7 * pixelSize;
for (int j = 0; j < pixelSize; j++) {
*(d1 + j) = color[j];
*(d2 + j) = color[j];
*(d3 + j) = color[j];
*(d4 + j) = color[j];
*(d5 + j) = color[j];
*(d6 + j) = color[j];
*(d7 + j) = color[j];
*(d8 + j) = color[j];
}
buf += 8 * pixelSize;
}
for (int i = 0; i < block2; i++) {
memcpy(buf, color, pixelSize);
buf += pixelSize;
}
}
void KisAutoBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
KisBrush::ColoringInformation* coloringInformation,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX , double subPixelY, qreal softnessFactor) const
{
Q_UNUSED(info);
// Generate the paint device from the mask
const KoColorSpace* cs = dst->colorSpace();
quint32 pixelSize = cs->pixelSize();
// mask dimension methods already includes KisBrush::angle()
int dstWidth = maskWidth(shape, subPixelX, subPixelY, info);
int dstHeight = maskHeight(shape, subPixelX, subPixelY, info);
QPointF hotSpot = this->hotSpot(shape, info);
// mask size and hotSpot function take the KisBrush rotation into account
qreal angle = shape.rotation() + KisBrush::angle();
// if there's coloring information, we merely change the alpha: in that case,
// the dab should be big enough!
if (coloringInformation) {
-
- // old bounds
- QRect oldBounds = dst->bounds();
-
// new bounds. we don't care if there is some extra memory occcupied.
dst->setRect(QRect(0, 0, dstWidth, dstHeight));
-
- if (dstWidth * dstHeight <= oldBounds.width() * oldBounds.height()) {
- // just clear the data in dst,
- memset(dst->data(), OPACITY_TRANSPARENT_U8, dstWidth * dstHeight * dst->pixelSize());
- }
- else {
- // enlarge the data
- dst->initialize();
- }
+ dst->lazyGrowBufferWithoutInitialization();
}
else {
- if (dst->data() == 0 || dst->bounds().isEmpty()) {
- warnKrita << "Creating a default black dab: no coloring info and no initialized paint device to mask";
- dst->clear(QRect(0, 0, dstWidth, dstHeight));
- }
- Q_ASSERT(dst->bounds().width() >= dstWidth && dst->bounds().height() >= dstHeight);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(dst->bounds().width() >= dstWidth &&
+ dst->bounds().height() >= dstHeight);
}
quint8* dabPointer = dst->data();
quint8* color = 0;
if (coloringInformation) {
if (dynamic_cast<PlainColoringInformation*>(coloringInformation)) {
color = const_cast<quint8*>(coloringInformation->color());
}
}
double centerX = hotSpot.x() - 0.5 + subPixelX;
double centerY = hotSpot.y() - 0.5 + subPixelY;
d->shape->setScale(shape.scaleX(), shape.scaleY());
d->shape->setSoftness(softnessFactor);
if (coloringInformation) {
if (color && pixelSize == 4) {
fillPixelOptimized_4bytes(color, dabPointer, dstWidth * dstHeight);
}
else if (color) {
fillPixelOptimized_general(color, dabPointer, dstWidth * dstHeight, pixelSize);
}
else {
for (int y = 0; y < dstHeight; y++) {
for (int x = 0; x < dstWidth; x++) {
memcpy(dabPointer, coloringInformation->color(), pixelSize);
coloringInformation->nextColumn();
dabPointer += pixelSize;
}
coloringInformation->nextRow();
}
}
}
MaskProcessingData data(dst, cs, d->randomness, d->density,
centerX, centerY,
angle);
KisBrushMaskApplicatorBase *applicator = d->shape->applicator();
applicator->initializeData(&data);
int jobs = d->idealThreadCountCached;
- if (dstHeight > 100 && jobs >= 4) {
+ if (threadingAllowed() && dstHeight > 100 && jobs >= 4) {
int splitter = dstHeight / jobs;
QVector<QRect> rects;
for (int i = 0; i < jobs - 1; i++) {
rects << QRect(0, i * splitter, dstWidth, splitter);
}
rects << QRect(0, (jobs - 1)*splitter, dstWidth, dstHeight - (jobs - 1)*splitter);
OperatorWrapper wrapper(applicator);
QtConcurrent::blockingMap(rects, wrapper);
}
else {
QRect rect(0, 0, dstWidth, dstHeight);
applicator->process(rect);
}
}
void KisAutoBrush::toXML(QDomDocument& doc, QDomElement& e) const
{
QDomElement shapeElt = doc.createElement("MaskGenerator");
d->shape->toXML(doc, shapeElt);
e.appendChild(shapeElt);
e.setAttribute("type", "auto_brush");
e.setAttribute("spacing", QString::number(spacing()));
e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive()));
e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff()));
e.setAttribute("angle", QString::number(KisBrush::angle()));
e.setAttribute("randomness", QString::number(d->randomness));
e.setAttribute("density", QString::number(d->density));
KisBrush::toXML(doc, e);
}
QImage KisAutoBrush::createBrushPreview()
{
int width = maskWidth(KisDabShape(), 0.0, 0.0, KisPaintInformation());
int height = maskHeight(KisDabShape(), 0.0, 0.0, KisPaintInformation());
KisPaintInformation info(QPointF(width * 0.5, height * 0.5), 0.5, 0, 0, angle(), 0, 0, 0, 0);
KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
fdev->setRect(QRect(0, 0, width, height));
fdev->initialize();
mask(fdev, KoColor(Qt::black, fdev->colorSpace()), KisDabShape(), info);
return fdev->convertToQImage(0);
}
const KisMaskGenerator* KisAutoBrush::maskGenerator() const
{
return d->shape.data();
}
qreal KisAutoBrush::density() const
{
return d->density;
}
qreal KisAutoBrush::randomness() const
{
return d->randomness;
}
QPainterPath KisAutoBrush::outline() const
{
bool simpleOutline = (d->density < 1.0);
if (simpleOutline) {
QPainterPath path;
QRectF brushBoundingbox(0, 0, width(), height());
if (maskGenerator()->type() == KisMaskGenerator::CIRCLE) {
path.addEllipse(brushBoundingbox);
}
else { // if (maskGenerator()->type() == KisMaskGenerator::RECTANGLE)
path.addRect(brushBoundingbox);
}
return path;
}
return KisBrush::boundary()->path();
}
void KisAutoBrush::lodLimitations(KisPaintopLodLimitations *l) const
{
KisBrush::lodLimitations(l);
if (!qFuzzyCompare(density(), 1.0)) {
l->limitations << KoID("auto-brush-density", i18nc("PaintOp instant preview limitation", "Brush Density recommended value 100.0"));
}
if (!qFuzzyCompare(randomness(), 0.0)) {
l->limitations << KoID("auto-brush-randomness", i18nc("PaintOp instant preview limitation", "Brush Randomness recommended value 0.0"));
}
}
diff --git a/libs/brush/kis_brush.cpp b/libs/brush/kis_brush.cpp
index 91660b2e72..10ba3b128b 100644
--- a/libs/brush/kis_brush.cpp
+++ b/libs/brush/kis_brush.cpp
@@ -1,638 +1,653 @@
/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* Copyright (c) 2003 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004-2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_brush.h"
#include <QDomElement>
#include <QFile>
#include <QPoint>
#include <QFileInfo>
#include <QBuffer>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoColor.h>
#include <KoColorSpaceMaths.h>
#include <KoColorSpaceRegistry.h>
#include "kis_datamanager.h"
#include "kis_paint_device.h"
#include "kis_global.h"
#include "kis_boundary.h"
#include "kis_image.h"
#include "kis_iterator_ng.h"
#include "kis_brush_registry.h"
#include <brushengine/kis_paint_information.h>
#include <kis_fixed_paint_device.h>
#include <kis_qimage_pyramid.h>
#include <brushengine/kis_paintop_lod_limitations.h>
KisBrush::ColoringInformation::~ColoringInformation()
{
}
KisBrush::PlainColoringInformation::PlainColoringInformation(const quint8* color) : m_color(color)
{
}
KisBrush::PlainColoringInformation::~PlainColoringInformation()
{
}
const quint8* KisBrush::PlainColoringInformation::color() const
{
return m_color;
}
void KisBrush::PlainColoringInformation::nextColumn()
{
}
void KisBrush::PlainColoringInformation::nextRow()
{
}
KisBrush::PaintDeviceColoringInformation::PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width)
: m_source(source)
, m_iterator(m_source->createHLineConstIteratorNG(0, 0, width))
{
}
KisBrush::PaintDeviceColoringInformation::~PaintDeviceColoringInformation()
{
}
const quint8* KisBrush::PaintDeviceColoringInformation::color() const
{
return m_iterator->oldRawData();
}
void KisBrush::PaintDeviceColoringInformation::nextColumn()
{
m_iterator->nextPixel();
}
void KisBrush::PaintDeviceColoringInformation::nextRow()
{
m_iterator->nextRow();
}
struct KisBrush::Private {
Private()
: boundary(0)
, angle(0)
, scale(1.0)
, hasColor(false)
, brushType(INVALID)
, autoSpacingActive(false)
, autoSpacingCoeff(1.0)
+ , threadingAllowed(true)
{}
~Private() {
delete boundary;
}
mutable KisBoundary* boundary;
qreal angle;
qreal scale;
bool hasColor;
enumBrushType brushType;
qint32 width;
qint32 height;
double spacing;
QPointF hotSpot;
mutable QSharedPointer<const KisQImagePyramid> brushPyramid;
QImage brushTipImage;
bool autoSpacingActive;
qreal autoSpacingCoeff;
+
+ bool threadingAllowed;
};
KisBrush::KisBrush()
: KoResource(QString())
, d(new Private)
{
}
KisBrush::KisBrush(const QString& filename)
: KoResource(filename)
, d(new Private)
{
}
KisBrush::KisBrush(const KisBrush& rhs)
: KoResource(QString())
, KisShared()
, d(new Private)
{
setBrushTipImage(rhs.brushTipImage());
d->brushType = rhs.d->brushType;
d->width = rhs.d->width;
d->height = rhs.d->height;
d->spacing = rhs.d->spacing;
d->hotSpot = rhs.d->hotSpot;
d->hasColor = rhs.d->hasColor;
d->angle = rhs.d->angle;
d->scale = rhs.d->scale;
d->autoSpacingActive = rhs.d->autoSpacingActive;
d->autoSpacingCoeff = rhs.d->autoSpacingCoeff;
+ d->threadingAllowed = rhs.d->threadingAllowed;
setFilename(rhs.filename());
/**
* Be careful! The pyramid is shared between two brush objects,
* therefore you cannot change it, only recreate! That i sthe
* reason why it is defined as const!
*/
d->brushPyramid = rhs.d->brushPyramid;
// don't copy the boundary, it will be regenerated -- see bug 291910
}
KisBrush::~KisBrush()
{
clearBrushPyramid();
delete d;
}
QImage KisBrush::brushTipImage() const
{
if (d->brushTipImage.isNull()) {
const_cast<KisBrush*>(this)->load();
}
return d->brushTipImage;
}
qint32 KisBrush::width() const
{
return d->width;
}
void KisBrush::setWidth(qint32 width)
{
d->width = width;
}
qint32 KisBrush::height() const
{
return d->height;
}
void KisBrush::setHeight(qint32 height)
{
d->height = height;
}
void KisBrush::setHotSpot(QPointF pt)
{
double x = pt.x();
double y = pt.y();
if (x < 0)
x = 0;
else if (x >= width())
x = width() - 1;
if (y < 0)
y = 0;
else if (y >= height())
y = height() - 1;
d->hotSpot = QPointF(x, y);
}
QPointF KisBrush::hotSpot(KisDabShape const& shape, const KisPaintInformation& info) const
{
Q_UNUSED(info);
QSizeF metric = characteristicSize(shape);
qreal w = metric.width();
qreal h = metric.height();
// The smallest brush we can produce is a single pixel.
if (w < 1) {
w = 1;
}
if (h < 1) {
h = 1;
}
// XXX: This should take d->hotSpot into account, though it
// isn't specified by gimp brushes so it would default to the center
// anyway.
QPointF p(w / 2, h / 2);
return p;
}
bool KisBrush::hasColor() const
{
return d->hasColor;
}
void KisBrush::setHasColor(bool hasColor)
{
d->hasColor = hasColor;
}
bool KisBrush::isPiercedApprox() const
{
QImage image = brushTipImage();
qreal w = image.width();
qreal h = image.height();
qreal xPortion = qMin(0.1, 5.0 / w);
qreal yPortion = qMin(0.1, 5.0 / h);
int x0 = std::floor((0.5 - xPortion) * w);
int x1 = std::ceil((0.5 + xPortion) * w);
int y0 = std::floor((0.5 - yPortion) * h);
int y1 = std::ceil((0.5 + yPortion) * h);
const int maxNumSamples = (x1 - x0 + 1) * (y1 - y0 + 1);
const int failedPixelsThreshold = 0.1 * maxNumSamples;
const int thresholdValue = 0.95 * 255;
int failedPixels = 0;
for (int y = y0; y <= y1; y++) {
for (int x = x0; x <= x1; x++) {
QRgb pixel = image.pixel(x,y);
if (qRed(pixel) > thresholdValue) {
failedPixels++;
}
}
}
return failedPixels > failedPixelsThreshold;
}
bool KisBrush::canPaintFor(const KisPaintInformation& /*info*/)
{
return true;
}
void KisBrush::setBrushTipImage(const QImage& image)
{
//Q_ASSERT(!image.isNull());
d->brushTipImage = image;
if (!image.isNull()) {
if (image.width() > 128 || image.height() > 128) {
KoResource::setImage(image.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
else {
KoResource::setImage(image);
}
setWidth(image.width());
setHeight(image.height());
}
clearBrushPyramid();
}
void KisBrush::setBrushType(enumBrushType type)
{
d->brushType = type;
}
enumBrushType KisBrush::brushType() const
{
return d->brushType;
}
void KisBrush::predefinedBrushToXML(const QString &type, QDomElement& e) const
{
e.setAttribute("type", type);
e.setAttribute("filename", shortFilename());
e.setAttribute("spacing", QString::number(spacing()));
e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive()));
e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff()));
e.setAttribute("angle", QString::number(angle()));
e.setAttribute("scale", QString::number(scale()));
}
void KisBrush::toXML(QDomDocument& /*document*/ , QDomElement& element) const
{
element.setAttribute("BrushVersion", "2");
}
KisBrushSP KisBrush::fromXML(const QDomElement& element, bool forceCopy)
{
KisBrushSP brush = KisBrushRegistry::instance()->getOrCreateBrush(element, forceCopy);
if (brush && element.attribute("BrushVersion", "1") == "1") {
brush->setScale(brush->scale() * 2.0);
}
return brush;
}
QSizeF KisBrush::characteristicSize(KisDabShape const& shape) const
{
KisDabShape normalizedShape(
shape.scale() * d->scale,
shape.ratio(),
normalizeAngle(shape.rotation() + d->angle));
return KisQImagePyramid::characteristicSize(
QSize(width(), height()), normalizedShape);
}
qint32 KisBrush::maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
{
Q_UNUSED(info);
qreal angle = normalizeAngle(shape.rotation() + d->angle);
qreal scale = shape.scale() * d->scale;
return KisQImagePyramid::imageSize(QSize(width(), height()),
KisDabShape(scale, shape.ratio(), angle),
subPixelX, subPixelY).width();
}
qint32 KisBrush::maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
{
Q_UNUSED(info);
qreal angle = normalizeAngle(shape.rotation() + d->angle);
qreal scale = shape.scale() * d->scale;
return KisQImagePyramid::imageSize(QSize(width(), height()),
KisDabShape(scale, shape.ratio(), angle),
subPixelX, subPixelY).height();
}
double KisBrush::maskAngle(double angle) const
{
return normalizeAngle(angle + d->angle);
}
quint32 KisBrush::brushIndex(const KisPaintInformation& info) const
{
Q_UNUSED(info);
return 0;
}
void KisBrush::setSpacing(double s)
{
if (s < 0.02) s = 0.02;
d->spacing = s;
}
double KisBrush::spacing() const
{
return d->spacing;
}
void KisBrush::setAutoSpacing(bool active, qreal coeff)
{
d->autoSpacingCoeff = coeff;
d->autoSpacingActive = active;
}
bool KisBrush::autoSpacingActive() const
{
return d->autoSpacingActive;
}
qreal KisBrush::autoSpacingCoeff() const
{
return d->autoSpacingCoeff;
}
void KisBrush::notifyStrokeStarted()
{
}
void KisBrush::notifyCachedDabPainted(const KisPaintInformation& info)
{
Q_UNUSED(info);
}
+void KisBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo)
+{
+ Q_UNUSED(info);
+ Q_UNUSED(seqNo);
+}
+
+void KisBrush::setThreadingAllowed(bool value)
+{
+ d->threadingAllowed = value;
+}
+
+bool KisBrush::threadingAllowed() const
+{
+ return d->threadingAllowed;
+}
+
void KisBrush::prepareBrushPyramid() const
{
if (!d->brushPyramid) {
d->brushPyramid = toQShared(new KisQImagePyramid(brushTipImage()));
}
}
void KisBrush::clearBrushPyramid()
{
d->brushPyramid.clear();
}
-void KisBrush::mask(KisFixedPaintDeviceSP dst, KisDabShape const& shape, const KisPaintInformation& info , double subPixelX, double subPixelY, qreal softnessFactor) const
-{
- generateMaskAndApplyMaskOrCreateDab(dst, 0, shape, info, subPixelX, subPixelY, softnessFactor);
-}
-
void KisBrush::mask(KisFixedPaintDeviceSP dst, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const
{
PlainColoringInformation pci(color.data());
generateMaskAndApplyMaskOrCreateDab(dst, &pci, shape, info, subPixelX, subPixelY, softnessFactor);
}
void KisBrush::mask(KisFixedPaintDeviceSP dst, const KisPaintDeviceSP src, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const
{
PaintDeviceColoringInformation pdci(src, maskWidth(shape, subPixelX, subPixelY, info));
generateMaskAndApplyMaskOrCreateDab(dst, &pdci, shape, info, subPixelX, subPixelY, softnessFactor);
}
void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
ColoringInformation* coloringInformation,
KisDabShape const& shape,
const KisPaintInformation& info_,
double subPixelX, double subPixelY, qreal softnessFactor) const
{
Q_ASSERT(valid());
Q_UNUSED(info_);
Q_UNUSED(softnessFactor);
prepareBrushPyramid();
QImage outputImage = d->brushPyramid->createImage(KisDabShape(
shape.scale() * d->scale, shape.ratio(),
-normalizeAngle(shape.rotation() + d->angle)),
subPixelX, subPixelY);
qint32 maskWidth = outputImage.width();
qint32 maskHeight = outputImage.height();
dst->setRect(QRect(0, 0, maskWidth, maskHeight));
- dst->initialize();
+ dst->lazyGrowBufferWithoutInitialization();
quint8* color = 0;
if (coloringInformation) {
if (dynamic_cast<PlainColoringInformation*>(coloringInformation)) {
color = const_cast<quint8*>(coloringInformation->color());
}
}
const KoColorSpace *cs = dst->colorSpace();
qint32 pixelSize = cs->pixelSize();
quint8 *dabPointer = dst->data();
quint8 *rowPointer = dabPointer;
quint8 *alphaArray = new quint8[maskWidth];
bool hasColor = this->hasColor();
for (int y = 0; y < maskHeight; y++) {
const quint8* maskPointer = outputImage.constScanLine(y);
if (coloringInformation) {
for (int x = 0; x < maskWidth; x++) {
if (color) {
memcpy(dabPointer, color, pixelSize);
}
else {
memcpy(dabPointer, coloringInformation->color(), pixelSize);
coloringInformation->nextColumn();
}
dabPointer += pixelSize;
}
}
if (hasColor) {
const quint8 *src = maskPointer;
quint8 *dst = alphaArray;
for (int x = 0; x < maskWidth; x++) {
const QRgb *c = reinterpret_cast<const QRgb*>(src);
*dst = KoColorSpaceMaths<quint8>::multiply(255 - qGray(*c), qAlpha(*c));
src += 4;
dst++;
}
}
else {
const quint8 *src = maskPointer;
quint8 *dst = alphaArray;
for (int x = 0; x < maskWidth; x++) {
const QRgb *c = reinterpret_cast<const QRgb*>(src);
*dst = KoColorSpaceMaths<quint8>::multiply(255 - *src, qAlpha(*c));
src += 4;
dst++;
}
}
cs->applyAlphaU8Mask(rowPointer, alphaArray, maskWidth);
rowPointer += maskWidth * pixelSize;
dabPointer = rowPointer;
if (!color && coloringInformation) {
coloringInformation->nextRow();
}
}
delete[] alphaArray;
}
KisFixedPaintDeviceSP KisBrush::paintDevice(const KoColorSpace * colorSpace,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX, double subPixelY) const
{
Q_ASSERT(valid());
Q_UNUSED(info);
double angle = normalizeAngle(shape.rotation() + d->angle);
double scale = shape.scale() * d->scale;
prepareBrushPyramid();
QImage outputImage = d->brushPyramid->createImage(
KisDabShape(scale, shape.ratio(), -angle), subPixelX, subPixelY);
KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(colorSpace);
Q_CHECK_PTR(dab);
dab->convertFromQImage(outputImage, "");
return dab;
}
void KisBrush::resetBoundary()
{
delete d->boundary;
d->boundary = 0;
}
void KisBrush::generateBoundary() const
{
KisFixedPaintDeviceSP dev;
KisDabShape inverseTransform(1.0 / scale(), 1.0, -angle());
if (brushType() == IMAGE || brushType() == PIPE_IMAGE) {
dev = paintDevice(KoColorSpaceRegistry::instance()->rgb8(),
inverseTransform, KisPaintInformation());
}
else {
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
dev = new KisFixedPaintDevice(cs);
mask(dev, KoColor(Qt::black, cs), inverseTransform, KisPaintInformation());
}
d->boundary = new KisBoundary(dev);
d->boundary->generateBoundary();
}
const KisBoundary* KisBrush::boundary() const
{
if (!d->boundary)
generateBoundary();
return d->boundary;
}
void KisBrush::setScale(qreal _scale)
{
d->scale = _scale;
}
qreal KisBrush::scale() const
{
return d->scale;
}
void KisBrush::setAngle(qreal _rotation)
{
d->angle = _rotation;
}
qreal KisBrush::angle() const
{
return d->angle;
}
QPainterPath KisBrush::outline() const
{
return boundary()->path();
}
void KisBrush::lodLimitations(KisPaintopLodLimitations *l) const
{
if (spacing() > 0.5) {
l->limitations << KoID("huge-spacing", i18nc("PaintOp instant preview limitation", "Spacing > 0.5, consider disabling Instant Preview"));
}
}
diff --git a/libs/brush/kis_brush.h b/libs/brush/kis_brush.h
index 203d2824f0..c336216c59 100644
--- a/libs/brush/kis_brush.h
+++ b/libs/brush/kis_brush.h
@@ -1,379 +1,396 @@
/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_BRUSH_
#define KIS_BRUSH_
#include <QImage>
#include <resources/KoResource.h>
#include <kis_types.h>
#include <kis_shared.h>
#include <kis_dab_shape.h>
#include <kritabrush_export.h>
class KisQImagemask;
typedef KisSharedPtr<KisQImagemask> KisQImagemaskSP;
class QString;
class KoColor;
class KoColorSpace;
class KisPaintInformation;
class KisBoundary;
class KisPaintopLodLimitations;
enum enumBrushType {
INVALID,
MASK,
IMAGE,
PIPE_MASK,
PIPE_IMAGE
};
static const qreal DEFAULT_SOFTNESS_FACTOR = 1.0;
class KisBrush;
typedef KisSharedPtr<KisBrush> KisBrushSP;
/**
* KisBrush is the base class for brush resources. A brush resource
* defines one or more images that are used to potato-stamp along
* the drawn path. The brush type defines how this brush is used --
* the important difference is between masks (which take the current
* painting color) and images (which do not). It is up to the paintop
* to make use of this feature.
*
* Brushes must be serializable to an xml representation and provide
* a factory class that can recreate or retrieve the brush based on
* this representation.
*
* XXX: This api is still a big mess -- it needs a good refactoring.
* And the whole KoResource architecture is way over-designed.
*/
class BRUSH_EXPORT KisBrush : public KoResource, public KisShared
{
public:
class ColoringInformation
{
public:
virtual ~ColoringInformation();
virtual const quint8* color() const = 0;
virtual void nextColumn() = 0;
virtual void nextRow() = 0;
};
protected:
class PlainColoringInformation : public ColoringInformation
{
public:
PlainColoringInformation(const quint8* color);
~PlainColoringInformation() override;
const quint8* color() const override ;
void nextColumn() override;
void nextRow() override;
private:
const quint8* m_color;
};
class PaintDeviceColoringInformation : public ColoringInformation
{
public:
PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width);
~PaintDeviceColoringInformation() override;
const quint8* color() const override ;
void nextColumn() override;
void nextRow() override;
private:
const KisPaintDeviceSP m_source;
KisHLineConstIteratorSP m_iterator;
};
public:
KisBrush();
KisBrush(const QString& filename);
~KisBrush() override;
virtual qreal userEffectiveSize() const = 0;
virtual void setUserEffectiveSize(qreal value) = 0;
bool load() override {
return false;
}
bool loadFromDevice(QIODevice *) override {
return false;
}
bool save() override {
return false;
}
bool saveToDevice(QIODevice* ) const override {
return false;
}
/**
* @brief brushImage the image the brush tip can paint with. Not all brush types have a single
* image.
* @return a valid QImage.
*/
virtual QImage brushTipImage() const;
/**
* Change the spacing of the brush.
* @param spacing a spacing of 1.0 means that strokes will be separated from one time the size
* of the brush.
*/
virtual void setSpacing(double spacing);
/**
* @return the spacing between two strokes for this brush
*/
double spacing() const;
void setAutoSpacing(bool active, qreal coeff);
bool autoSpacingActive() const;
qreal autoSpacingCoeff() const;
/**
* @return the width (for scale == 1.0)
*/
qint32 width() const;
/**
* @return the height (for scale == 1.0)
*/
qint32 height() const;
/**
* @return the width of the mask for the given scale and angle
*/
virtual qint32 maskWidth(KisDabShape const&, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const;
/**
* @return the height of the mask for the given scale and angle
*/
virtual qint32 maskHeight(KisDabShape const&, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const;
/**
* @return the logical size of the brush, that is the size measured
* in floating point value.
*
* This value should not be used for calculating future dab sizes
* because it doesn't take any rounding into account. The only use
* of this metric is calculation of brush-size derivatives like
* hotspots and spacing.
*/
virtual QSizeF characteristicSize(KisDabShape const&) const;
/**
* @return the angle of the mask adding the given angle
*/
double maskAngle(double angle = 0) const;
/**
* @return the index of the brush
* if the brush consists of multiple images
*/
virtual quint32 brushIndex(const KisPaintInformation& info) const;
/**
* The brush type defines how the brush is used.
*/
virtual enumBrushType brushType() const;
QPointF hotSpot(KisDabShape const&, const KisPaintInformation& info) const;
/**
* Returns true if this brush can return something useful for the info. This is used
* by Pipe Brushes that can't paint sometimes
**/
virtual bool canPaintFor(const KisPaintInformation& /*info*/);
/**
* Is called by the paint op when a paintop starts a stroke. The
* point is that we store brushes a server while the paint ops are
* are recreated all the time. Is means that upon a stroke start
* the brushes may need to clear its state.
*/
virtual void notifyStrokeStarted();
/**
* Is called by the cache, when cache hit has happened.
* Having got this notification the brush can update the counters
* of dabs, generate some new random values if needed.
*
+ * * NOTE: one should use **either** notifyCachedDabPainted() or prepareForSeqNo()
+ *
* Currently, this is used by pipe'd brushes to implement
* incremental and random parasites
*/
virtual void notifyCachedDabPainted(const KisPaintInformation& info);
+ /**
+ * Is called by the multithreaded queue to prepare a specific brush
+ * tip for the particular seqNo.
+ *
+ * NOTE: one should use **either** notifyCachedDabPainted() or prepareForSeqNo()
+ *
+ * Currently, this is used by pipe'd brushes to implement
+ * incremental and random parasites
+ */
+ virtual void prepareForSeqNo(const KisPaintInformation& info, int seqNo);
+
+ /**
+ * Notify the brush if it can use QtConcurrent's threading capabilities in its
+ * internal routines. By default it is allowed, but some paintops (who do their
+ * own multithreading) may ask the brush to avoid internal threading.
+ */
+ void setThreadingAllowed(bool value);
+
+ /**
+ * \see setThreadingAllowed() for details
+ */
+ bool threadingAllowed() const;
+
/**
* Return a fixed paint device that contains a correctly scaled image dab.
*/
virtual KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace,
KisDabShape const&,
const KisPaintInformation& info,
double subPixelX = 0, double subPixelY = 0) const;
- /**
- * Apply the brush mask to the pixels in dst. Dst should be big enough!
- */
- void mask(KisFixedPaintDeviceSP dst,
- KisDabShape const& shape,
- const KisPaintInformation& info,
- double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const;
-
/**
* clear dst fill it with a mask colored with KoColor
*/
void mask(KisFixedPaintDeviceSP dst,
const KoColor& color,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const;
/**
* clear dst and fill it with a mask colored with the corresponding colors of src
*/
void mask(KisFixedPaintDeviceSP dst,
const KisPaintDeviceSP src,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const;
virtual bool hasColor() const;
/**
* Create a mask and either mask dst (that is, change all alpha values of the
* existing pixels to those of the mask) or, if coloringInfo is present, clear
* dst and fill dst with pixels according to coloringInfo, masked according to the
* generated mask.
*
* @param dst the destination that will be draw on the image, and this function
* will edit its alpha channel
* @param coloringInfo coloring information that will be copied on the dab, it can be null
* @param scale a scale applied on the alpha mask
* @param angle a rotation applied on the alpha mask
* @param info the painting information (this is only and should only be used by
* KisImagePipeBrush and only to be backward compatible with the Gimp,
* KisImagePipeBrush is ignoring scale and angle information)
* @param subPixelX sub position of the brush (contained between 0.0 and 1.0)
* @param subPixelY sub position of the brush (contained between 0.0 and 1.0)
*
* @return a mask computed from the grey-level values of the
* pixels in the brush.
*/
virtual void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
ColoringInformation* coloringInfo,
KisDabShape const&,
const KisPaintInformation& info,
double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const;
/**
* Serialize this brush to XML.
*/
virtual void toXML(QDomDocument& , QDomElement&) const;
static KisBrushSP fromXML(const QDomElement& element, bool forceCopy = false);
virtual const KisBoundary* boundary() const;
virtual QPainterPath outline() const;
virtual void setScale(qreal _scale);
qreal scale() const;
virtual void setAngle(qreal _angle);
qreal angle() const;
void prepareBrushPyramid() const;
void clearBrushPyramid();
virtual void lodLimitations(KisPaintopLodLimitations *l) const;
virtual KisBrush* clone() const = 0;
//protected:
KisBrush(const KisBrush& rhs);
void setWidth(qint32 width);
void setHeight(qint32 height);
void setHotSpot(QPointF);
/**
* The image is used to represent the brush in the gui, and may also, depending on the brush type
* be used to define the actual brush instance.
*/
virtual void setBrushTipImage(const QImage& image);
/**
* XXX
*/
virtual void setBrushType(enumBrushType type);
virtual void setHasColor(bool hasColor);
/**
* Returns true if the brush has a bunch of pixels almost
* fully transparent in the very center. If the brush is pierced,
* then dulling mode may not work correctly due to empty samples.
*
* WARNING: this method is relatively expensive since it iterates
* up to 100 pixels of the brush.
*/
bool isPiercedApprox() const;
protected:
void resetBoundary();
void predefinedBrushToXML(const QString &type, QDomElement& e) const;
private:
// Initialize our boundary
void generateBoundary() const;
struct Private;
Private* const d;
};
#endif // KIS_BRUSH_
diff --git a/libs/brush/kis_brushes_pipe.h b/libs/brush/kis_brushes_pipe.h
index 458770b586..5855282ad5 100644
--- a/libs/brush/kis_brushes_pipe.h
+++ b/libs/brush/kis_brushes_pipe.h
@@ -1,175 +1,182 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_BRUSHES_PIPE_H
#define __KIS_BRUSHES_PIPE_H
#include <kis_fixed_paint_device.h>
template<class BrushType>
class KisBrushesPipe
{
public:
KisBrushesPipe() {
}
KisBrushesPipe(const KisBrushesPipe &rhs) {
qDeleteAll(m_brushes);
m_brushes.clear();
Q_FOREACH (BrushType * brush, rhs.m_brushes) {
BrushType *clonedBrush = dynamic_cast<BrushType*>(brush->clone());
KIS_ASSERT_RECOVER(clonedBrush) {continue;}
m_brushes.append(clonedBrush);
}
}
virtual ~KisBrushesPipe() {
qDeleteAll(m_brushes);
}
virtual void clear() {
qDeleteAll(m_brushes);
m_brushes.clear();
}
BrushType* firstBrush() const {
return m_brushes.first();
}
BrushType* lastBrush() const {
return m_brushes.last();
}
BrushType* currentBrush(const KisPaintInformation& info) {
return !m_brushes.isEmpty() ? m_brushes.at(chooseNextBrush(info)) : 0;
}
int brushIndex(const KisPaintInformation& info) {
return chooseNextBrush(info);
}
qint32 maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) {
BrushType *brush = currentBrush(info);
return brush ? brush->maskWidth(shape, subPixelX, subPixelY, info) : 0;
}
qint32 maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) {
BrushType *brush = currentBrush(info);
return brush ? brush->maskHeight(shape, subPixelX, subPixelY, info) : 0;
}
void setAngle(qreal angle) {
Q_FOREACH (BrushType * brush, m_brushes) {
brush->setAngle(angle);
}
}
void setScale(qreal scale) {
Q_FOREACH (BrushType * brush, m_brushes) {
brush->setScale(scale);
}
}
void setSpacing(double spacing) {
Q_FOREACH (BrushType * brush, m_brushes) {
brush->setSpacing(spacing);
}
}
bool hasColor() const {
Q_FOREACH (BrushType * brush, m_brushes) {
if (brush->hasColor()) return true;
}
return false;
}
void notifyCachedDabPainted(const KisPaintInformation& info) {
- updateBrushIndexes(info);
+ updateBrushIndexes(info, -1);
+ }
+
+ void prepareForSeqNo(const KisPaintInformation& info, int seqNo) {
+ updateBrushIndexes(info, seqNo);
}
void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX , double subPixelY,
qreal softnessFactor) {
BrushType *brush = currentBrush(info);
if (!brush) return;
brush->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor);
- updateBrushIndexes(info);
+ notifyCachedDabPainted(info);
}
KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX, double subPixelY) {
BrushType *brush = currentBrush(info);
if (!brush) return 0;
KisFixedPaintDeviceSP device = brush->paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
- updateBrushIndexes(info);
+ notifyCachedDabPainted(info);
return device;
}
QVector<BrushType*> brushes() {
return m_brushes;
}
void testingSelectNextBrush(const KisPaintInformation& info) {
(void) chooseNextBrush(info);
- updateBrushIndexes(info);
+ notifyCachedDabPainted(info);
}
/**
* Is called by the paint op when a paintop starts a stroke. The
* brushes are shared among different strokes, so sometimes the
* brush should be reset.
*/
virtual void notifyStrokeStarted() = 0;
protected:
void addBrush(BrushType *brush) {
m_brushes.append(brush);
}
/**
* Returns the index of the brush that corresponds to the current
* values of \p info. This method is called *before* the dab is
* actually painted.
*
* The method is const, so no internal counters of the brush should
* change during its execution
*/
virtual int chooseNextBrush(const KisPaintInformation& info) = 0;
/**
* Updates internal counters of the brush *after* a dab has been
* painted on the canvas. Some incremental switching of the brushes
* may me implemented in this method.
+ *
+ * If \p seqNo is equal or greater than zero, then incremental iteration is
+ * overriden by this seqNo value
*/
- virtual void updateBrushIndexes(const KisPaintInformation& info) = 0;
+ virtual void updateBrushIndexes(const KisPaintInformation& info, int seqNo) = 0;
protected:
QVector<BrushType*> m_brushes;
};
#endif /* __KIS_BRUSHES_PIPE_H */
diff --git a/libs/brush/kis_dab_shape.h b/libs/brush/kis_dab_shape.h
index 04dbedb861..6bd548bf32 100644
--- a/libs/brush/kis_dab_shape.h
+++ b/libs/brush/kis_dab_shape.h
@@ -1,44 +1,52 @@
/*
* Copyright (c) 2016 Nishant Rodrigues <nishantjr@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
#include <QtGlobal>
class KisDabShape {
qreal m_scale;
qreal m_ratio;
qreal m_rotation;
public:
KisDabShape()
: m_scale(1.0)
, m_ratio(1.0)
, m_rotation(0.0)
{}
KisDabShape(qreal scale, qreal ratio, qreal rotation)
: m_scale(scale)
, m_ratio(ratio)
, m_rotation(rotation)
{}
+
+ bool operator==(const KisDabShape &rhs) const {
+ return
+ qFuzzyCompare(m_scale, rhs.m_scale) &&
+ qFuzzyCompare(m_ratio, rhs.m_ratio) &&
+ qFuzzyCompare(m_rotation, rhs.m_rotation);
+ }
+
qreal scale() const { return m_scale; }
qreal scaleX() const { return scale(); }
qreal scaleY() const { return m_scale * m_ratio; }
qreal ratio() const { return m_ratio; }
qreal rotation() const { return m_rotation; }
};
diff --git a/libs/brush/kis_gbr_brush.cpp b/libs/brush/kis_gbr_brush.cpp
index d25aa1d341..77a9beeddd 100644
--- a/libs/brush/kis_gbr_brush.cpp
+++ b/libs/brush/kis_gbr_brush.cpp
@@ -1,508 +1,509 @@
/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* Copyright (c) 2003 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <sys/types.h>
#include <QtEndian>
#include "kis_gbr_brush.h"
#include <QDomElement>
#include <QFile>
#include <QImage>
#include <QPoint>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoColor.h>
#include <KoColorSpaceRegistry.h>
#include "kis_datamanager.h"
#include "kis_paint_device.h"
#include "kis_global.h"
#include "kis_image.h"
struct GimpBrushV1Header {
quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */
quint32 version; /* brush file version # */
quint32 width; /* width of brush */
quint32 height; /* height of brush */
quint32 bytes; /* depth of brush in bytes */
};
/// All fields are in MSB on disk!
struct GimpBrushHeader {
quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */
quint32 version; /* brush file version # */
quint32 width; /* width of brush */
quint32 height; /* height of brush */
quint32 bytes; /* depth of brush in bytes */
/* The following are only defined in version 2 */
quint32 magic_number; /* GIMP brush magic number */
quint32 spacing; /* brush spacing as % of width & height, 0 - 1000 */
};
// Needed, or the GIMP won't open it!
quint32 const GimpV2BrushMagic = ('G' << 24) + ('I' << 16) + ('M' << 8) + ('P' << 0);
struct KisGbrBrush::Private {
QByteArray data;
bool ownData; /* seems to indicate that @ref data is owned by the brush, but in Qt4.x this is already guaranteed... so in reality it seems more to indicate whether the data is loaded from file (ownData = true) or memory (ownData = false) */
bool useColorAsMask;
quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */
quint32 version; /* brush file version # */
quint32 bytes; /* depth of brush in bytes */
quint32 magic_number; /* GIMP brush magic number */
};
#define DEFAULT_SPACING 0.25
KisGbrBrush::KisGbrBrush(const QString& filename)
: KisScalingSizeBrush(filename)
, d(new Private)
{
d->ownData = true;
d->useColorAsMask = false;
setHasColor(false);
setSpacing(DEFAULT_SPACING);
}
KisGbrBrush::KisGbrBrush(const QString& filename,
const QByteArray& data,
qint32 & dataPos)
: KisScalingSizeBrush(filename)
, d(new Private)
{
d->ownData = false;
d->useColorAsMask = false;
setHasColor(false);
setSpacing(DEFAULT_SPACING);
d->data = QByteArray::fromRawData(data.data() + dataPos, data.size() - dataPos);
init();
d->data.clear();
dataPos += d->header_size + (width() * height() * d->bytes);
}
KisGbrBrush::KisGbrBrush(KisPaintDeviceSP image, int x, int y, int w, int h)
: KisScalingSizeBrush()
, d(new Private)
{
d->ownData = true;
d->useColorAsMask = false;
setHasColor(false);
setSpacing(DEFAULT_SPACING);
initFromPaintDev(image, x, y, w, h);
}
KisGbrBrush::KisGbrBrush(const QImage& image, const QString& name)
: KisScalingSizeBrush()
, d(new Private)
{
d->ownData = false;
d->useColorAsMask = false;
setHasColor(false);
setSpacing(DEFAULT_SPACING);
setBrushTipImage(image);
setName(name);
}
KisGbrBrush::KisGbrBrush(const KisGbrBrush& rhs)
: KisScalingSizeBrush(rhs)
, d(new Private(*rhs.d))
{
setName(rhs.name());
+ setBrushTipImage(rhs.brushTipImage());
d->data = QByteArray();
setValid(rhs.valid());
}
KisGbrBrush::~KisGbrBrush()
{
delete d;
}
bool KisGbrBrush::load()
{
QFile file(filename());
if (file.size() == 0) return false;
file.open(QIODevice::ReadOnly);
bool res = loadFromDevice(&file);
file.close();
return res;
}
bool KisGbrBrush::loadFromDevice(QIODevice *dev)
{
if (d->ownData) {
d->data = dev->readAll();
}
return init();
}
bool KisGbrBrush::init()
{
GimpBrushHeader bh;
if (sizeof(GimpBrushHeader) > (uint)d->data.size()) {
return false;
}
memcpy(&bh, d->data, sizeof(GimpBrushHeader));
bh.header_size = qFromBigEndian(bh.header_size);
d->header_size = bh.header_size;
bh.version = qFromBigEndian(bh.version);
d->version = bh.version;
bh.width = qFromBigEndian(bh.width);
bh.height = qFromBigEndian(bh.height);
bh.bytes = qFromBigEndian(bh.bytes);
d->bytes = bh.bytes;
bh.magic_number = qFromBigEndian(bh.magic_number);
d->magic_number = bh.magic_number;
if (bh.version == 1) {
// No spacing in version 1 files so use Gimp default
bh.spacing = static_cast<int>(DEFAULT_SPACING * 100);
}
else {
bh.spacing = qFromBigEndian(bh.spacing);
if (bh.spacing > 1000) {
return false;
}
}
setSpacing(bh.spacing / 100.0);
if (bh.header_size > (uint)d->data.size() || bh.header_size == 0) {
return false;
}
QString name;
if (bh.version == 1) {
// Version 1 has no magic number or spacing, so the name
// is at a different offset. Character encoding is undefined.
const char *text = d->data.constData() + sizeof(GimpBrushV1Header);
name = QString::fromLatin1(text, bh.header_size - sizeof(GimpBrushV1Header) - 1);
}
else {
// ### Version = 3->cinepaint; may be float16 data!
// Version >=2: UTF-8 encoding is used
name = QString::fromUtf8(d->data.constData() + sizeof(GimpBrushHeader),
bh.header_size - sizeof(GimpBrushHeader) - 1);
}
setName(name);
if (bh.width == 0 || bh.height == 0) {
return false;
}
QImage::Format imageFormat;
if (bh.bytes == 1) {
imageFormat = QImage::Format_Indexed8;
} else {
imageFormat = QImage::Format_ARGB32;
}
QImage image(QImage(bh.width, bh.height, imageFormat));
if (image.isNull()) {
return false;
}
qint32 k = bh.header_size;
if (bh.bytes == 1) {
QVector<QRgb> table;
for (int i = 0; i < 256; ++i) table.append(qRgb(i, i, i));
image.setColorTable(table);
// Grayscale
if (static_cast<qint32>(k + bh.width * bh.height) > d->data.size()) {
return false;
}
setHasColor(false);
for (quint32 y = 0; y < bh.height; y++) {
uchar *pixel = reinterpret_cast<uchar *>(image.scanLine(y));
for (quint32 x = 0; x < bh.width; x++, k++) {
qint32 val = 255 - static_cast<uchar>(d->data[k]);
*pixel = val;
++pixel;
}
}
} else if (bh.bytes == 4) {
// RGBA
if (static_cast<qint32>(k + (bh.width * bh.height * 4)) > d->data.size()) {
return false;
}
setHasColor(true);
for (quint32 y = 0; y < bh.height; y++) {
QRgb *pixel = reinterpret_cast<QRgb *>(image.scanLine(y));
for (quint32 x = 0; x < bh.width; x++, k += 4) {
*pixel = qRgba(d->data[k], d->data[k + 1], d->data[k + 2], d->data[k + 3]);
++pixel;
}
}
}
else {
warnKrita << "WARNING: loading of GBR brushes with" << bh.bytes << "bytes per pixel is not supported";
return false;
}
setWidth(image.width());
setHeight(image.height());
if (d->ownData) {
d->data.resize(0); // Save some memory, we're using enough of it as it is.
}
setValid(image.width() != 0 && image.height() != 0);
setBrushTipImage(image);
return true;
}
bool KisGbrBrush::initFromPaintDev(KisPaintDeviceSP image, int x, int y, int w, int h)
{
// Forcefully convert to RGBA8
// XXX profile and exposure?
setBrushTipImage(image->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()));
setName(image->objectName());
setHasColor(true);
return true;
}
bool KisGbrBrush::save()
{
QFile file(filename());
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
bool ok = saveToDevice(&file);
file.close();
return ok;
}
bool KisGbrBrush::saveToDevice(QIODevice* dev) const
{
GimpBrushHeader bh;
QByteArray utf8Name = name().toUtf8(); // Names in v2 brushes are in UTF-8
char const* name = utf8Name.data();
int nameLength = qstrlen(name);
int wrote;
bh.header_size = qToBigEndian((quint32)sizeof(GimpBrushHeader) + nameLength + 1);
bh.version = qToBigEndian((quint32)2); // Only RGBA8 data needed atm, no cinepaint stuff
bh.width = qToBigEndian((quint32)width());
bh.height = qToBigEndian((quint32)height());
// Hardcoded, 4 bytes RGBA or 1 byte GREY
if (!hasColor()) {
bh.bytes = qToBigEndian((quint32)1);
}
else {
bh.bytes = qToBigEndian((quint32)4);
}
bh.magic_number = qToBigEndian((quint32)GimpV2BrushMagic);
bh.spacing = qToBigEndian(static_cast<quint32>(spacing() * 100.0));
// Write header: first bh, then the name
QByteArray bytes = QByteArray::fromRawData(reinterpret_cast<char*>(&bh), sizeof(GimpBrushHeader));
wrote = dev->write(bytes);
bytes.clear();
if (wrote == -1) {
return false;
}
wrote = dev->write(name, nameLength + 1);
if (wrote == -1) {
return false;
}
int k = 0;
QImage image = brushTipImage();
if (!hasColor()) {
bytes.resize(width() * height());
for (qint32 y = 0; y < height(); y++) {
for (qint32 x = 0; x < width(); x++) {
QRgb c = image.pixel(x, y);
bytes[k++] = static_cast<char>(255 - qRed(c)); // red == blue == green
}
}
} else {
bytes.resize(width() * height() * 4);
for (qint32 y = 0; y < height(); y++) {
for (qint32 x = 0; x < width(); x++) {
// order for gimp brushes, v2 is: RGBA
QRgb pixel = image.pixel(x, y);
bytes[k++] = static_cast<char>(qRed(pixel));
bytes[k++] = static_cast<char>(qGreen(pixel));
bytes[k++] = static_cast<char>(qBlue(pixel));
bytes[k++] = static_cast<char>(qAlpha(pixel));
}
}
}
wrote = dev->write(bytes);
if (wrote == -1) {
return false;
}
KoResource::saveToDevice(dev);
return true;
}
QImage KisGbrBrush::brushTipImage() const
{
QImage image = KisBrush::brushTipImage();
if (hasColor() && useColorAsMask()) {
for (int y = 0; y < image.height(); y++) {
QRgb *pixel = reinterpret_cast<QRgb *>(image.scanLine(y));
for (int x = 0; x < image.width(); x++) {
QRgb c = pixel[x];
int a = qGray(c);
pixel[x] = qRgba(a, a, a, qAlpha(c));
}
}
}
return image;
}
enumBrushType KisGbrBrush::brushType() const
{
return !hasColor() || useColorAsMask() ? MASK : IMAGE;
}
void KisGbrBrush::setBrushType(enumBrushType type)
{
Q_UNUSED(type);
qFatal("FATAL: protected member setBrushType has no meaning for KisGbrBrush");
}
void KisGbrBrush::setBrushTipImage(const QImage& image)
{
KisBrush::setBrushTipImage(image);
setValid(true);
}
void KisGbrBrush::makeMaskImage()
{
if (!hasColor()) {
return;
}
QImage brushTip = brushTipImage();
if (brushTip.width() == width() && brushTip.height() == height()) {
int imageWidth = width();
int imageHeight = height();
QImage image(imageWidth, imageHeight, QImage::Format_Indexed8);
QVector<QRgb> table;
for (int i = 0; i < 256; ++i) {
table.append(qRgb(i, i, i));
}
image.setColorTable(table);
for (int y = 0; y < imageHeight; y++) {
QRgb *pixel = reinterpret_cast<QRgb *>(brushTip.scanLine(y));
uchar * dstPixel = image.scanLine(y);
for (int x = 0; x < imageWidth; x++) {
QRgb c = pixel[x];
float alpha = qAlpha(c) / 255.0f;
// linear interpolation with maximum gray value which is transparent in the mask
//int a = (qGray(c) * alpha) + ((1.0 - alpha) * 255);
// single multiplication version
int a = 255 + alpha * (qGray(c) - 255);
dstPixel[x] = (uchar)a;
}
}
setBrushTipImage(image);
}
setHasColor(false);
setUseColorAsMask(false);
resetBoundary();
clearBrushPyramid();
}
KisBrush* KisGbrBrush::clone() const
{
return new KisGbrBrush(*this);
}
void KisGbrBrush::toXML(QDomDocument& d, QDomElement& e) const
{
predefinedBrushToXML("gbr_brush", e);
e.setAttribute("ColorAsMask", QString::number((int)useColorAsMask()));
KisBrush::toXML(d, e);
}
void KisGbrBrush::setUseColorAsMask(bool useColorAsMask)
{
/**
* WARNING: There is a problem in the brush server, since it
* returns not copies of brushes, but direct pointers to them. It
* means that the brushes are shared among all the currently
* present paintops, which might be a problem for e.g. Multihand
* Brush Tool.
*
* Right now, all the instances of Multihand Brush Tool share the
* same brush, so there is no problem in this sharing, unless we
* reset the internal state of the brush on our way.
*/
if (useColorAsMask != d->useColorAsMask) {
d->useColorAsMask = useColorAsMask;
resetBoundary();
clearBrushPyramid();
}
}
bool KisGbrBrush::useColorAsMask() const
{
return d->useColorAsMask;
}
QString KisGbrBrush::defaultFileExtension() const
{
return QString(".gbr");
}
diff --git a/libs/brush/kis_gbr_brush.h b/libs/brush/kis_gbr_brush.h
index 6d578e9e96..1f2257847f 100644
--- a/libs/brush/kis_gbr_brush.h
+++ b/libs/brush/kis_gbr_brush.h
@@ -1,122 +1,121 @@
/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_GBR_BRUSH_
#define KIS_GBR_BRUSH_
#include <QImage>
#include <QVector>
#include "kis_scaling_size_brush.h"
#include <kis_types.h>
#include <kis_shared.h>
#include <brushengine/kis_paint_information.h>
#include "kritabrush_export.h"
class KisQImagemask;
typedef KisSharedPtr<KisQImagemask> KisQImagemaskSP;
class QString;
class QIODevice;
class BRUSH_EXPORT KisGbrBrush : public KisScalingSizeBrush
{
protected:
public:
/// Construct brush to load filename later as brush
KisGbrBrush(const QString& filename);
/// Load brush from the specified data, at position dataPos, and set the filename
KisGbrBrush(const QString& filename,
const QByteArray & data,
qint32 & dataPos);
/// Load brush from the specified paint device, in the specified region
KisGbrBrush(KisPaintDeviceSP image, int x, int y, int w, int h);
/// Load brush as a copy from the specified QImage (handy when you need to copy a brush!)
KisGbrBrush(const QImage& image, const QString& name = QString());
+ KisGbrBrush(const KisGbrBrush& rhs);
+
~KisGbrBrush() override;
bool load() override;
bool loadFromDevice(QIODevice *dev) override;
bool save() override;
bool saveToDevice(QIODevice* dev) const override;
/**
* @return a preview of the brush
*/
QImage brushTipImage() const override;
/**
* If the brush image data are colorful (e.g. you created the brush from the canvas with custom brush)
* and you want to paint with it as with masks, set to true.
*/
virtual void setUseColorAsMask(bool useColorAsMask);
virtual bool useColorAsMask() const;
/**
* Convert the mask to inverted gray scale, so it is alpha mask.
* It can be used as MASK brush type. This operates on the date of the brush,
* so it destruct the original brush data
*/
virtual void makeMaskImage();
enumBrushType brushType() const override;
/**
* Makes a copy of this brush.
*/
KisBrush* clone() const override;
/**
* @return default file extension for saving the brush
*/
QString defaultFileExtension() const override;
protected:
/**
* save the content of this brush to an IO device
*/
friend class KisImageBrushesPipe;
-
- KisGbrBrush(const KisGbrBrush& rhs);
-
void setBrushType(enumBrushType type) override;
void setBrushTipImage(const QImage& image) override;
void toXML(QDomDocument& d, QDomElement& e) const override;
private:
bool init();
bool initFromPaintDev(KisPaintDeviceSP image, int x, int y, int w, int h);
struct Private;
Private* const d;
};
#endif // KIS_GBR_BRUSH_
diff --git a/libs/brush/kis_imagepipe_brush.cpp b/libs/brush/kis_imagepipe_brush.cpp
index 8a11cd2d57..1189170c55 100644
--- a/libs/brush/kis_imagepipe_brush.cpp
+++ b/libs/brush/kis_imagepipe_brush.cpp
@@ -1,502 +1,509 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
*
* 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_imagepipe_brush.h"
#include "kis_pipebrush_parasite.h"
#include "kis_brushes_pipe.h"
class KisImageBrushesPipe : public KisBrushesPipe<KisGbrBrush>
{
public:
KisImageBrushesPipe()
: m_isInitialized(false)
{
}
/*
pre and post are split because:
21:12:20 < dmitryK> boud: i guess it was somehow related to the fact that the maskWidth/maskHeight should
correspond to the size of the mask returned by paintDevice()
21:13:33 < dmitryK> boud: the random stuff is called once per brush->paintDevice() call, after the device is
returned to the paint op, that is "preparing the randomness for the next call"
21:14:16 < dmitryK> boud: and brushesPipe->currentBrush() always returning the same brush for any particular
paintInfo.
*/
protected:
static int selectPre(KisParasite::SelectionMode mode,
int index, int rank,
const KisPaintInformation& info) {
qreal angle;
switch (mode) {
case KisParasite::Constant:
case KisParasite::Incremental:
case KisParasite::Random:
break;
case KisParasite::Pressure:
index = static_cast<int>(info.pressure() * (rank - 1) + 0.5);
break;
case KisParasite::Angular:
// + m_d->PI_2 to be compatible with the gimp
angle = info.drawingAngle() + M_PI_2;
angle = normalizeAngle(angle);
index = static_cast<int>(angle / (2.0 * M_PI) * rank);
break;
case KisParasite::TiltX:
index = qRound(info.xTilt() / 2.0 * rank) + rank / 2;
break;
case KisParasite::TiltY:
index = qRound(info.yTilt() / 2.0 * rank) + rank / 2;
break;
default:
warnImage << "Parasite" << mode << "is not implemented";
index = 0;
}
return index;
}
static int selectPost(KisParasite::SelectionMode mode,
int index, int rank,
- const KisPaintInformation& info) {
+ const KisPaintInformation& info,
+ int seqNo) {
switch (mode) {
case KisParasite::Constant: break;
case KisParasite::Incremental:
- index = (index + 1) % rank;
+ index = (seqNo >= 0 ? seqNo : (index + 1)) % rank;
break;
case KisParasite::Random:
index = info.randomSource()->generate(0, rank);
break;
case KisParasite::Pressure:
case KisParasite::Angular:
break;
case KisParasite::TiltX:
case KisParasite::TiltY:
break;
default:
warnImage << "Parasite" << mode << "is not implemented";
index = 0;
}
return index;
}
int chooseNextBrush(const KisPaintInformation& info) override {
quint32 brushIndex = 0;
if (!m_isInitialized) {
/**
* Reset all the indexes to the initial values and do the
* generation based on parameters.
*/
for (int i = 0; i < m_parasite.dim; i++) {
m_parasite.index[i] = 0;
}
- updateBrushIndexes(info);
+ updateBrushIndexes(info, 0);
m_isInitialized = true;
}
for (int i = 0; i < m_parasite.dim; i++) {
int index = selectPre(m_parasite.selection[i],
m_parasite.index[i],
m_parasite.rank[i], info);
brushIndex += m_parasite.brushesCount[i] * index;
}
brushIndex %= m_brushes.size();
return brushIndex;
}
- void updateBrushIndexes(const KisPaintInformation& info) override {
+ void updateBrushIndexes(const KisPaintInformation& info, int seqNo) override {
for (int i = 0; i < m_parasite.dim; i++) {
m_parasite.index[i] = selectPost(m_parasite.selection[i],
m_parasite.index[i],
m_parasite.rank[i],
- info);
+ info,
+ seqNo);
}
}
public:
using KisBrushesPipe<KisGbrBrush>::addBrush;
void setParasite(const KisPipeBrushParasite& parasite) {
m_parasite = parasite;
}
const KisPipeBrushParasite& parasite() const {
return m_parasite;
}
void setUseColorAsMask(bool useColorAsMask) {
Q_FOREACH (KisGbrBrush * brush, m_brushes) {
brush->setUseColorAsMask(useColorAsMask);
}
}
void makeMaskImage() {
Q_FOREACH (KisGbrBrush * brush, m_brushes) {
brush->makeMaskImage();
}
}
bool saveToDevice(QIODevice* dev) const {
Q_FOREACH (KisGbrBrush * brush, m_brushes) {
if (!brush->saveToDevice(dev)) {
return false;
}
}
return true;
}
void notifyStrokeStarted() override {
m_isInitialized = false;
}
private:
KisPipeBrushParasite m_parasite;
bool m_isInitialized;
};
struct KisImagePipeBrush::Private {
public:
KisImageBrushesPipe brushesPipe;
};
KisImagePipeBrush::KisImagePipeBrush(const QString& filename)
: KisGbrBrush(filename)
, m_d(new Private())
{
}
KisImagePipeBrush::KisImagePipeBrush(const QString& name, int w, int h,
QVector< QVector<KisPaintDevice*> > devices,
QVector<KisParasite::SelectionMode > modes)
: KisGbrBrush(QString())
, m_d(new Private())
{
Q_ASSERT(devices.count() == modes.count());
Q_ASSERT(devices.count() > 0);
Q_ASSERT(devices.count() < 2); // XXX Multidimensionals not supported yet, change to MaxDim!
setName(name);
KisPipeBrushParasite parasite;
parasite.dim = devices.count();
// XXX Change for multidim! :
parasite.ncells = devices.at(0).count();
parasite.rank[0] = parasite.ncells; // ### This can masquerade some bugs, be careful here in the future
parasite.selection[0] = modes.at(0);
// XXX needsmovement!
parasite.setBrushesCount();
setParasite(parasite);
setDevices(devices, w, h);
setBrushTipImage(m_d->brushesPipe.firstBrush()->brushTipImage());
}
KisImagePipeBrush::KisImagePipeBrush(const KisImagePipeBrush& rhs)
: KisGbrBrush(rhs),
m_d(new Private(*rhs.m_d))
{
}
KisImagePipeBrush::~KisImagePipeBrush()
{
delete m_d;
}
bool KisImagePipeBrush::load()
{
QFile file(filename());
file.open(QIODevice::ReadOnly);
bool res = loadFromDevice(&file);
file.close();
return res;
}
bool KisImagePipeBrush::loadFromDevice(QIODevice *dev)
{
QByteArray data = dev->readAll();
return initFromData(data);
}
bool KisImagePipeBrush::initFromData(const QByteArray &data)
{
if (data.size() == 0) return false;
// XXX: this doesn't correctly load the image pipe brushes yet.
// XXX: This stuff is in utf-8, too.
// The first line contains the name -- this means we look until we arrive at the first newline
QByteArray line1;
qint32 i = 0;
while (data[i] != '\n' && i < data.size()) {
line1.append(data[i]);
i++;
}
setName(QString::fromUtf8(line1, line1.size()));
i++; // Skip past the first newline
// The second line contains the number of brushes, separated by a space from the parasite
// XXX: This stuff is in utf-8, too.
QByteArray line2;
while (data[i] != '\n' && i < data.size()) {
line2.append(data[i]);
i++;
}
QString paramline = QString::fromUtf8(line2, line2.size());
qint32 numOfBrushes = paramline.left(paramline.indexOf(' ')).toUInt();
QString parasiteString = paramline.mid(paramline.indexOf(' ') + 1);
KisPipeBrushParasite parasite = KisPipeBrushParasite(parasiteString);
parasite.sanitize();
m_d->brushesPipe.setParasite(parasite);
i++; // Skip past the second newline
for (int brushIndex = 0;
brushIndex < numOfBrushes && i < data.size(); brushIndex++) {
KisGbrBrush* brush = new KisGbrBrush(name() + '_' + QString().setNum(brushIndex),
data,
i);
m_d->brushesPipe.addBrush(brush);
}
if (numOfBrushes > 0) {
setValid(true);
setSpacing(m_d->brushesPipe.lastBrush()->spacing());
setWidth(m_d->brushesPipe.firstBrush()->width());
setHeight(m_d->brushesPipe.firstBrush()->height());
setBrushTipImage(m_d->brushesPipe.firstBrush()->brushTipImage());
}
return true;
}
bool KisImagePipeBrush::save()
{
QFile file(filename());
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
bool ok = saveToDevice(&file);
file.close();
return ok;
}
bool KisImagePipeBrush::saveToDevice(QIODevice* dev) const
{
QByteArray utf8Name = name().toUtf8(); // Names in v2 brushes are in UTF-8
char const* name = utf8Name.data();
int len = qstrlen(name);
if (m_d->brushesPipe.parasite().dim != 1) {
warnImage << "Save to file for pipe brushes with dim != not yet supported!";
return false;
}
// Save this pipe brush: first the header, and then all individual brushes consecutively
// XXX: this needs some care for when we have > 1 dimension)
// Gimp Pipe Brush header format: Name\n<number of brushes> <parasite>\n
// The name\n
if (dev->write(name, len) == -1)
return false;
if (!dev->putChar('\n'))
return false;
// Write the parasite (also writes number of brushes)
if (!m_d->brushesPipe.parasite().saveToDevice(dev))
return false;
if (!dev->putChar('\n'))
return false;
KoResource::saveToDevice(dev);
// <gbr brushes>
return m_d->brushesPipe.saveToDevice(dev);
}
void KisImagePipeBrush::notifyStrokeStarted()
{
m_d->brushesPipe.notifyStrokeStarted();
}
void KisImagePipeBrush::notifyCachedDabPainted(const KisPaintInformation& info)
{
m_d->brushesPipe.notifyCachedDabPainted(info);
}
+void KisImagePipeBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo)
+{
+ m_d->brushesPipe.prepareForSeqNo(info, seqNo);
+}
+
void KisImagePipeBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
KisDabShape const& shape,
const KisPaintInformation& info,
double subPixelX , double subPixelY,
qreal softnessFactor) const
{
m_d->brushesPipe.generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor);
}
QVector<KisGbrBrush *> KisImagePipeBrush::brushes() const
{
return m_d->brushesPipe.brushes();
}
KisFixedPaintDeviceSP KisImagePipeBrush::paintDevice(
const KoColorSpace * colorSpace,
KisDabShape const& shape,
const KisPaintInformation& info, double subPixelX, double subPixelY) const
{
return m_d->brushesPipe.paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
}
enumBrushType KisImagePipeBrush::brushType() const
{
return !hasColor() || useColorAsMask() ? PIPE_MASK : PIPE_IMAGE;
}
bool KisImagePipeBrush::hasColor() const
{
return m_d->brushesPipe.hasColor();
}
void KisImagePipeBrush::makeMaskImage()
{
m_d->brushesPipe.makeMaskImage();
setUseColorAsMask(false);
}
void KisImagePipeBrush::setUseColorAsMask(bool useColorAsMask)
{
KisGbrBrush::setUseColorAsMask(useColorAsMask);
m_d->brushesPipe.setUseColorAsMask(useColorAsMask);
}
const KisBoundary* KisImagePipeBrush::boundary() const
{
KisGbrBrush *brush = m_d->brushesPipe.firstBrush();
Q_ASSERT(brush);
return brush->boundary();
}
bool KisImagePipeBrush::canPaintFor(const KisPaintInformation& info)
{
return (!m_d->brushesPipe.parasite().needsMovement || info.drawingDistance() >= 0.5);
}
KisBrush* KisImagePipeBrush::clone() const
{
return new KisImagePipeBrush(*this);
}
QString KisImagePipeBrush::defaultFileExtension() const
{
return QString(".gih");
}
quint32 KisImagePipeBrush::brushIndex(const KisPaintInformation& info) const
{
return m_d->brushesPipe.brushIndex(info);
}
qint32 KisImagePipeBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
{
return m_d->brushesPipe.maskWidth(shape, subPixelX, subPixelY, info);
}
qint32 KisImagePipeBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
{
return m_d->brushesPipe.maskHeight(shape, subPixelX, subPixelY, info);
}
void KisImagePipeBrush::setAngle(qreal _angle)
{
KisGbrBrush::setAngle(_angle);
m_d->brushesPipe.setAngle(_angle);
}
void KisImagePipeBrush::setScale(qreal _scale)
{
KisGbrBrush::setScale(_scale);
m_d->brushesPipe.setScale(_scale);
}
void KisImagePipeBrush::setSpacing(double _spacing)
{
KisGbrBrush::setSpacing(_spacing);
m_d->brushesPipe.setSpacing(_spacing);
}
void KisImagePipeBrush::setBrushType(enumBrushType type)
{
Q_UNUSED(type);
qFatal("FATAL: protected member setBrushType has no meaning for KisImagePipeBrush");
// brushType() is a function of hasColor() and useColorAsMask()
}
void KisImagePipeBrush::setHasColor(bool hasColor)
{
Q_UNUSED(hasColor);
qFatal("FATAL: protected member setHasColor has no meaning for KisImagePipeBrush");
// hasColor() is a function of the underlying brushes
}
KisGbrBrush* KisImagePipeBrush::testingGetCurrentBrush(const KisPaintInformation& info) const
{
return m_d->brushesPipe.currentBrush(info);
}
void KisImagePipeBrush::testingSelectNextBrush(const KisPaintInformation& info) const
{
return m_d->brushesPipe.testingSelectNextBrush(info);
}
const KisPipeBrushParasite& KisImagePipeBrush::parasite() const {
return m_d->brushesPipe.parasite();
}
void KisImagePipeBrush::setParasite(const KisPipeBrushParasite &parasite)
{
m_d->brushesPipe.setParasite(parasite);
}
void KisImagePipeBrush::setDevices(QVector<QVector<KisPaintDevice *> > devices, int w, int h)
{
for (int i = 0; i < devices.at(0).count(); i++) {
m_d->brushesPipe.addBrush(new KisGbrBrush(devices.at(0).at(i), 0, 0, w, h));
}
}
diff --git a/libs/brush/kis_imagepipe_brush.h b/libs/brush/kis_imagepipe_brush.h
index 9679fdf4f6..59141d8094 100644
--- a/libs/brush/kis_imagepipe_brush.h
+++ b/libs/brush/kis_imagepipe_brush.h
@@ -1,142 +1,143 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
*
* 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_IMAGEPIPE_BRUSH_
#define KIS_IMAGEPIPE_BRUSH_
#include <QList>
#include <QMap>
#include <QString>
#include <resources/KoResource.h>
#include "kis_gbr_brush.h"
#include "kis_global.h"
class KisPipeBrushParasite;
/**
* Velocity won't be supported, atm Tilt isn't either,
* but have chances of implementation
*/
namespace KisParasite
{
enum SelectionMode {
Constant,
Incremental,
Angular,
Velocity,
Random,
Pressure,
TiltX,
TiltY
};
}
class BRUSH_EXPORT KisImagePipeBrush : public KisGbrBrush
{
public:
KisImagePipeBrush(const QString& filename);
/**
* Specialized constructor that makes a new pipe brush from a sequence of samesize
* devices. The fact that it's a vector of a vector, is to support multidimensional
* brushes (not yet supported!) */
KisImagePipeBrush(const QString& name, int w, int h,
QVector< QVector<KisPaintDevice*> > devices,
QVector<KisParasite::SelectionMode> modes);
~KisImagePipeBrush() override;
bool load() override;
bool loadFromDevice(QIODevice *dev) override;
bool save() override;
bool saveToDevice(QIODevice* dev) const override;
/**
* @return the next image in the pipe.
*/
KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace,
KisDabShape const&,
const KisPaintInformation& info,
double subPixelX = 0, double subPixelY = 0) const override;
void setUseColorAsMask(bool useColorAsMask) override;
bool hasColor() const override;
enumBrushType brushType() const override;
const KisBoundary* boundary() const override;
bool canPaintFor(const KisPaintInformation& info) override;
void makeMaskImage() override;
KisBrush* clone() const override;
QString defaultFileExtension() const override;
void setAngle(qreal _angle) override;
void setScale(qreal _scale) override;
void setSpacing(double _spacing) override;
quint32 brushIndex(const KisPaintInformation& info) const override;
qint32 maskWidth(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const override;
qint32 maskHeight(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const override;
void notifyStrokeStarted() override;
void notifyCachedDabPainted(const KisPaintInformation& info) override;
+ void prepareForSeqNo(const KisPaintInformation& info, int seqNo) override;
void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
KisDabShape const&,
const KisPaintInformation& info,
double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const override;
QVector<KisGbrBrush*> brushes() const;
const KisPipeBrushParasite &parasite() const;
void setParasite(const KisPipeBrushParasite& parasite);
void setDevices(QVector< QVector<KisPaintDevice*> > devices, int w, int h);
protected:
void setBrushType(enumBrushType type) override;
void setHasColor(bool hasColor) override;
/// Will call KisBrush's saveToDevice as well
KisImagePipeBrush(const KisImagePipeBrush& rhs);
private:
friend class KisImagePipeBrushTest;
KisGbrBrush* testingGetCurrentBrush(const KisPaintInformation& info) const;
void testingSelectNextBrush(const KisPaintInformation& info) const;
bool initFromData(const QByteArray &data);
private:
struct Private;
Private * const m_d;
};
#endif // KIS_IMAGEPIPE_BRUSH_
diff --git a/libs/brush/kis_text_brush.cpp b/libs/brush/kis_text_brush.cpp
index f39e5c3e24..6a73c97f01 100644
--- a/libs/brush/kis_text_brush.cpp
+++ b/libs/brush/kis_text_brush.cpp
@@ -1,310 +1,334 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_text_brush.h"
#include <QDomDocument>
#include <QDomElement>
#include <QFontMetrics>
#include <QPainter>
#include "kis_gbr_brush.h"
#include "kis_brushes_pipe.h"
#include <kis_dom_utils.h>
#include <kis_threaded_text_rendering_workaround.h>
#ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
#include <QApplication>
#include <QWidget>
#include <QThread>
#endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
class KisTextBrushesPipe : public KisBrushesPipe<KisGbrBrush>
{
public:
KisTextBrushesPipe() {
m_charIndex = 0;
m_currentBrushIndex = 0;
}
KisTextBrushesPipe(const KisTextBrushesPipe &rhs)
- : KisBrushesPipe<KisGbrBrush>(rhs) {
+ : KisBrushesPipe<KisGbrBrush>(), // no copy here!
+ m_text(rhs.m_text),
+ m_charIndex(rhs.m_charIndex),
+ m_currentBrushIndex(rhs.m_currentBrushIndex)
+ {
m_brushesMap.clear();
QMapIterator<QChar, KisGbrBrush*> iter(rhs.m_brushesMap);
while (iter.hasNext()) {
iter.next();
- m_brushesMap.insert(iter.key(), iter.value());
+ KisGbrBrush *brush = new KisGbrBrush(*iter.value());
+ m_brushesMap.insert(iter.key(), brush);
+ KisBrushesPipe<KisGbrBrush>::addBrush(brush);
}
}
void setText(const QString &text, const QFont &font) {
m_text = text;
+
m_charIndex = 0;
clear();
for (int i = 0; i < m_text.length(); i++) {
- QChar letter = m_text.at(i);
+
+ const QChar letter = m_text.at(i);
+
+ // skip letters that are already present in the brushes pipe
+ if (m_brushesMap.contains(letter)) continue;
+
QImage image = renderChar(letter, font);
KisGbrBrush *brush = new KisGbrBrush(image, letter);
brush->setSpacing(0.1); // support for letter spacing?
brush->makeMaskImage();
m_brushesMap.insert(letter, brush);
KisBrushesPipe<KisGbrBrush>::addBrush(brush);
}
}
static QImage renderChar(const QString& text, const QFont &font) {
#ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
QWidget *focusWidget = qApp->focusWidget();
if (focusWidget) {
QThread *guiThread = focusWidget->thread();
if (guiThread != QThread::currentThread()) {
warnKrita << "WARNING: Rendering text in non-GUI thread!"
<< "That may lead to hangups and crashes on some"
<< "versions of X11/Qt!";
}
}
#endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
QFontMetrics metric(font);
QRect rect = metric.boundingRect(text);
if (rect.isEmpty()) {
rect = QRect(0, 0, 1, 1); // paint at least something
}
QRect paintingRect = rect.translated(-rect.x(), -rect.y());
QImage renderedChar(paintingRect.size(), QImage::Format_ARGB32);
QPainter p;
p.begin(&renderedChar);
p.setFont(font);
p.fillRect(paintingRect, Qt::white);
p.setPen(Qt::black);
p.drawText(-rect.x(), -rect.y(), text);
p.end();
return renderedChar;
}
void clear() override {
m_brushesMap.clear();
KisBrushesPipe<KisGbrBrush>::clear();
}
KisGbrBrush* firstBrush() const {
Q_ASSERT(m_text.size() > 0);
Q_ASSERT(m_brushesMap.size() > 0);
return m_brushesMap.value(m_text.at(0));
}
void notifyStrokeStarted() override {
m_charIndex = 0;
updateBrushIndexesImpl();
}
protected:
int chooseNextBrush(const KisPaintInformation& info) override {
Q_UNUSED(info);
return m_currentBrushIndex;
}
- void updateBrushIndexes(const KisPaintInformation& info) override {
+ void updateBrushIndexes(const KisPaintInformation& info, int seqNo) override {
Q_UNUSED(info);
- m_charIndex++;
+ if (m_text.size()) {
+ m_charIndex = (seqNo >= 0 ? seqNo : (m_charIndex + 1)) % m_text.size();
+ } else {
+ m_charIndex = 0;
+ }
+
updateBrushIndexesImpl();
}
private:
void updateBrushIndexesImpl() {
if (m_text.isEmpty()) return;
if (m_charIndex >= m_text.size()) {
m_charIndex = 0;
}
QChar letter = m_text.at(m_charIndex);
Q_ASSERT(m_brushesMap.contains(letter));
m_currentBrushIndex = m_brushes.indexOf(m_brushesMap.value(letter));
}
private:
QMap<QChar, KisGbrBrush*> m_brushesMap;
QString m_text;
int m_charIndex;
int m_currentBrushIndex;
};
KisTextBrush::KisTextBrush()
: m_brushesPipe(new KisTextBrushesPipe())
{
setPipeMode(false);
}
KisTextBrush::KisTextBrush(const KisTextBrush &rhs)
: KisScalingSizeBrush(rhs),
+ m_font(rhs.m_font),
+ m_text(rhs.m_text),
m_brushesPipe(new KisTextBrushesPipe(*rhs.m_brushesPipe))
{
}
KisTextBrush::~KisTextBrush()
{
delete m_brushesPipe;
}
void KisTextBrush::setPipeMode(bool pipe)
{
setBrushType(pipe ? PIPE_MASK : MASK);
}
bool KisTextBrush::pipeMode() const
{
return brushType() == PIPE_MASK;
}
void KisTextBrush::setText(const QString& txt)
{
m_text = txt;
}
QString KisTextBrush::text(void) const
{
return m_text;
}
void KisTextBrush::setFont(const QFont& font)
{
m_font = font;
}
QFont KisTextBrush::font()
{
return m_font;
}
void KisTextBrush::notifyStrokeStarted()
{
m_brushesPipe->notifyStrokeStarted();
}
void KisTextBrush::notifyCachedDabPainted(const KisPaintInformation& info)
{
m_brushesPipe->notifyCachedDabPainted(info);
}
+void KisTextBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo)
+{
+ m_brushesPipe->prepareForSeqNo(info, seqNo);
+}
+
void KisTextBrush::generateMaskAndApplyMaskOrCreateDab(
KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
KisDabShape const& shape,
const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const
{
if (brushType() == MASK) {
KisBrush::generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor);
}
else { /* if (brushType() == PIPE_MASK)*/
m_brushesPipe->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor);
}
}
KisFixedPaintDeviceSP KisTextBrush::paintDevice(const KoColorSpace * colorSpace,
KisDabShape const& shape,
const KisPaintInformation& info, double subPixelX, double subPixelY) const
{
if (brushType() == MASK) {
return KisBrush::paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
}
else { /* if (brushType() == PIPE_MASK)*/
return m_brushesPipe->paintDevice(colorSpace, shape, info, subPixelX, subPixelY);
}
}
void KisTextBrush::toXML(QDomDocument& doc, QDomElement& e) const
{
Q_UNUSED(doc);
e.setAttribute("type", "kis_text_brush");
e.setAttribute("spacing", KisDomUtils::toString(spacing()));
e.setAttribute("text", m_text);
e.setAttribute("font", m_font.toString());
e.setAttribute("pipe", (brushType() == PIPE_MASK) ? "true" : "false");
KisBrush::toXML(doc, e);
}
void KisTextBrush::updateBrush()
{
Q_ASSERT((brushType() == PIPE_MASK) || (brushType() == MASK));
if (brushType() == PIPE_MASK) {
m_brushesPipe->setText(m_text, m_font);
setBrushTipImage(m_brushesPipe->firstBrush()->brushTipImage());
}
else { /* if (brushType() == MASK)*/
setBrushTipImage(KisTextBrushesPipe::renderChar(m_text, m_font));
}
resetBoundary();
setValid(true);
}
quint32 KisTextBrush::brushIndex(const KisPaintInformation& info) const
{
return brushType() == MASK ? 0 : 1 + m_brushesPipe->brushIndex(info);
}
qint32 KisTextBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
{
return brushType() == MASK ?
KisBrush::maskWidth(shape, subPixelX, subPixelY, info) :
m_brushesPipe->maskWidth(shape, subPixelX, subPixelY, info);
}
qint32 KisTextBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const
{
return brushType() == MASK ?
KisBrush::maskHeight(shape, subPixelX, subPixelY, info) :
m_brushesPipe->maskHeight(shape, subPixelX, subPixelY, info);
}
void KisTextBrush::setAngle(qreal _angle)
{
KisBrush::setAngle(_angle);
m_brushesPipe->setAngle(_angle);
}
void KisTextBrush::setScale(qreal _scale)
{
KisBrush::setScale(_scale);
m_brushesPipe->setScale(_scale);
}
void KisTextBrush::setSpacing(double _spacing)
{
KisBrush::setSpacing(_spacing);
m_brushesPipe->setSpacing(_spacing);
}
KisBrush* KisTextBrush::clone() const
{
return new KisTextBrush(*this);
}
diff --git a/libs/brush/kis_text_brush.h b/libs/brush/kis_text_brush.h
index 4c13b17b89..01dfed2854 100644
--- a/libs/brush/kis_text_brush.h
+++ b/libs/brush/kis_text_brush.h
@@ -1,95 +1,96 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_TEXT_BRUSH_H_
#define _KIS_TEXT_BRUSH_H_
#include <QFont>
#include "kis_scaling_size_brush.h"
#include "kritabrush_export.h"
class KisTextBrushesPipe;
class BRUSH_EXPORT KisTextBrush : public KisScalingSizeBrush
{
public:
KisTextBrush();
KisTextBrush(const KisTextBrush &rhs);
~KisTextBrush() override;
void notifyStrokeStarted() override;
void notifyCachedDabPainted(const KisPaintInformation& info) override;
+ void prepareForSeqNo(const KisPaintInformation& info, int seqNo) override;
void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
KisDabShape const&,
const KisPaintInformation& info,
double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const override;
KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace,
KisDabShape const&, const KisPaintInformation& info, double subPixelX, double subPixelY) const override;
bool load() override {
return false;
}
bool loadFromDevice(QIODevice *) override {
return false;
}
bool save() override {
return false;
}
bool saveToDevice(QIODevice* ) const override {
return false;
}
void setText(const QString& txt);
QString text(void) const;
QFont font();
void setFont(const QFont& font);
void setPipeMode(bool pipe);
bool pipeMode() const;
void updateBrush();
void toXML(QDomDocument& , QDomElement&) const override;
quint32 brushIndex(const KisPaintInformation& info) const override;
qint32 maskWidth(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const override;
qint32 maskHeight(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const override;
void setAngle(qreal _angle) override;
void setScale(qreal _scale) override;
void setSpacing(double _spacing) override;
KisBrush* clone() const override;
private:
QFont m_font;
QString m_text;
private:
KisTextBrushesPipe *m_brushesPipe;
};
#endif
diff --git a/libs/brush/tests/kis_gbr_brush_test.cpp b/libs/brush/tests/kis_gbr_brush_test.cpp
index c027e9dc88..e5994faef5 100644
--- a/libs/brush/tests/kis_gbr_brush_test.cpp
+++ b/libs/brush/tests/kis_gbr_brush_test.cpp
@@ -1,361 +1,297 @@
/*
* Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_gbr_brush_test.h"
#include <QTest>
#include <QString>
#include <QDir>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include "testutil.h"
#include "../kis_gbr_brush.h"
#include "kis_types.h"
#include "kis_paint_device.h"
#include "brushengine/kis_paint_information.h"
#include <kis_fixed_paint_device.h>
#include "kis_qimage_pyramid.h"
-void KisGbrBrushTest::testMaskGenerationNoColor()
-{
- KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr");
- brush->load();
- Q_ASSERT(brush->valid());
- const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
-
- KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
-
- // check masking an existing paint device
- KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs);
- fdev->setRect(QRect(0, 0, 100, 100));
- fdev->initialize();
- cs->setOpacity(fdev->data(), OPACITY_OPAQUE_U8, 100 * 100);
-
- QPoint errpoint;
- QImage result(QString(FILES_DATA_DIR) + QDir::separator() + "result_brush_1.png");
- QImage image = fdev->convertToQImage(0);
-
- if (!TestUtil::compareQImages(errpoint, image, result)) {
- image.save("kis_gbr_brush_test_1.png");
- QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
- }
-
- brush->mask(fdev, KisDabShape(), info);
-
- result = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "result_brush_2.png");
- image = fdev->convertToQImage(0);
- if (!TestUtil::compareQImages(errpoint, image, result)) {
- image.save("kis_gbr_brush_test_2.png");
- QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
- }
-}
void KisGbrBrushTest::testMaskGenerationSingleColor()
{
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr");
brush->load();
Q_ASSERT(brush->valid());
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
// check masking an existing paint device
KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs);
fdev->setRect(QRect(0, 0, 100, 100));
fdev->initialize();
cs->setOpacity(fdev->data(), OPACITY_OPAQUE_U8, 100 * 100);
// Check creating a mask dab with a single color
fdev = new KisFixedPaintDevice(cs);
brush->mask(fdev, KoColor(Qt::black, cs), KisDabShape(), info);
QPoint errpoint;
QImage result = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "result_brush_3.png");
QImage image = fdev->convertToQImage(0);
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("kis_gbr_brush_test_3.png");
QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisGbrBrushTest::testMaskGenerationDevColor()
{
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr");
brush->load();
Q_ASSERT(brush->valid());
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
// check masking an existing paint device
KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs);
fdev->setRect(QRect(0, 0, 100, 100));
fdev->initialize();
cs->setOpacity(fdev->data(), OPACITY_OPAQUE_U8, 100 * 100);
// Check creating a mask dab with a color taken from a paint device
KoColor red(Qt::red, cs);
cs->setOpacity(red.data(), quint8(128), 1);
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->fill(0, 0, 100, 100, red.data());
fdev = new KisFixedPaintDevice(cs);
brush->mask(fdev, dev, KisDabShape(), info);
QPoint errpoint;
QImage result = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "result_brush_4.png");
QImage image = fdev->convertToQImage(0);
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("kis_gbr_brush_test_4.png");
QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
-void KisGbrBrushTest::testMaskGenerationDefaultColor()
-{
- KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr");
- brush->load();
- Q_ASSERT(brush->valid());
- const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
-
- KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
-
- // check masking an existing paint device
- KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs);
- fdev->setRect(QRect(0, 0, 100, 100));
- fdev->initialize();
- cs->setOpacity(fdev->data(), OPACITY_OPAQUE_U8, 100 * 100);
-
- // check creating a mask dab with a default color
- fdev = new KisFixedPaintDevice(cs);
- brush->mask(fdev, KisDabShape(), info);
-
- QPoint errpoint;
- QImage result = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "result_brush_3.png");
- QImage image = fdev->convertToQImage(0);
- if (!TestUtil::compareQImages(errpoint, image, result)) {
- image.save("kis_gbr_brush_test_5.png");
- QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
- }
-
- delete brush;
-}
-
-
void KisGbrBrushTest::testImageGeneration()
{
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
bool res = brush->load();
Q_UNUSED(res);
Q_ASSERT(res);
QVERIFY(!brush->brushTipImage().isNull());
brush->prepareBrushPyramid();
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
KisFixedPaintDeviceSP dab;
for (int i = 0; i < 200; i++) {
qreal scale = qreal(qrand()) / RAND_MAX * 2.0;
qreal rotation = qreal(qrand()) / RAND_MAX * 2 * M_PI;
qreal subPixelX = qreal(qrand()) / RAND_MAX * 0.5;
QString testName =
QString("brush_%1_sc_%2_rot_%3_sub_%4")
.arg(i).arg(scale).arg(rotation).arg(subPixelX);
dab = brush->paintDevice(cs, KisDabShape(scale, 1.0, rotation), info, subPixelX);
/**
* Compare first 10 images. Others are tested for asserts only
*/
if (i < 10) {
QImage result = dab->convertToQImage(0);
TestUtil::checkQImage(result, "brush_masks", "", testName);
}
}
}
void KisGbrBrushTest::benchmarkPyramidCreation()
{
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
brush->load();
QVERIFY(!brush->brushTipImage().isNull());
QBENCHMARK {
brush->prepareBrushPyramid();
brush->clearBrushPyramid();
}
}
void KisGbrBrushTest::benchmarkScaling()
{
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
brush->load();
QVERIFY(!brush->brushTipImage().isNull());
brush->prepareBrushPyramid();
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
KisFixedPaintDeviceSP dab;
QBENCHMARK {
dab = brush->paintDevice(cs, KisDabShape(qreal(qrand()) / RAND_MAX * 2.0, 1.0, 0.0), info);
//dab->convertToQImage(0).save(QString("dab_%1_new_smooth.png").arg(i++));
}
}
void KisGbrBrushTest::benchmarkRotation()
{
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
brush->load();
QVERIFY(!brush->brushTipImage().isNull());
brush->prepareBrushPyramid();
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
KisFixedPaintDeviceSP dab;
QBENCHMARK {
dab = brush->paintDevice(cs, KisDabShape(1.0, 1.0, qreal(qrand()) / RAND_MAX * 2 * M_PI), info);
}
}
void KisGbrBrushTest::benchmarkMaskScaling()
{
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
brush->load();
QVERIFY(!brush->brushTipImage().isNull());
brush->prepareBrushPyramid();
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(cs);
QBENCHMARK {
KoColor c(Qt::black, cs);
qreal scale = qreal(qrand()) / RAND_MAX * 2.0;
brush->mask(dab, c, KisDabShape(scale, 1.0, 0.0), info, 0.0, 0.0, 1.0);
}
}
void KisGbrBrushTest::testPyramidLevelRounding()
{
QSize imageSize(41, 41);
QImage image(imageSize, QImage::Format_ARGB32);
image.fill(0);
KisQImagePyramid pyramid(image);
qreal baseScale;
int baseLevel;
baseLevel = pyramid.findNearestLevel(1.0, &baseScale);
QCOMPARE(baseScale, 1.0);
QCOMPARE(baseLevel, 3);
baseLevel = pyramid.findNearestLevel(2.0, &baseScale);
QCOMPARE(baseScale, 2.0);
QCOMPARE(baseLevel, 2);
baseLevel = pyramid.findNearestLevel(4.0, &baseScale);
QCOMPARE(baseScale, 4.0);
QCOMPARE(baseLevel, 1);
baseLevel = pyramid.findNearestLevel(0.5, &baseScale);
QCOMPARE(baseScale, 0.5);
QCOMPARE(baseLevel, 4);
baseLevel = pyramid.findNearestLevel(0.25, &baseScale);
QCOMPARE(baseScale, 0.25);
QCOMPARE(baseLevel, 5);
baseLevel = pyramid.findNearestLevel(0.25 + 1e-7, &baseScale);
QCOMPARE(baseScale, 0.25);
QCOMPARE(baseLevel, 5);
}
static QSize dabTransformHelper(KisDabShape const& shape)
{
QSize const testSize(150, 150);
qreal const subPixelX = 0.0,
subPixelY = 0.0;
return KisQImagePyramid::imageSize(testSize, shape, subPixelX, subPixelY);
}
void KisGbrBrushTest::testPyramidDabTransform()
{
QCOMPARE(dabTransformHelper(KisDabShape(1.0, 1.0, 0.0)), QSize(150, 150));
QCOMPARE(dabTransformHelper(KisDabShape(1.0, 0.5, 0.0)), QSize(150, 75));
QCOMPARE(dabTransformHelper(KisDabShape(1.0, 1.0, M_PI / 4)), QSize(213, 213));
QCOMPARE(dabTransformHelper(KisDabShape(1.0, 0.5, M_PI / 4)), QSize(160, 160));
}
// see comment in KisQImagePyramid::appendPyramidLevel
void KisGbrBrushTest::testQPainterTransformationBorder()
{
QImage image1(10, 10, QImage::Format_ARGB32);
QImage image2(12, 12, QImage::Format_ARGB32);
image1.fill(0);
image2.fill(0);
{
QPainter gc(&image1);
gc.fillRect(QRect(0, 0, 10, 10), Qt::black);
}
{
QPainter gc(&image2);
gc.fillRect(QRect(1, 1, 10, 10), Qt::black);
}
image1.save("src1.png");
image2.save("src2.png");
{
QImage canvas(100, 100, QImage::Format_ARGB32);
canvas.fill(0);
QPainter gc(&canvas);
QTransform transform;
transform.rotate(15);
gc.setTransform(transform);
gc.setRenderHints(QPainter::SmoothPixmapTransform);
gc.drawImage(QPointF(50, 50), image1);
gc.end();
canvas.save("canvas1.png");
}
{
QImage canvas(100, 100, QImage::Format_ARGB32);
canvas.fill(0);
QPainter gc(&canvas);
QTransform transform;
transform.rotate(15);
gc.setTransform(transform);
gc.setRenderHints(QPainter::SmoothPixmapTransform);
gc.drawImage(QPointF(50, 50), image2);
gc.end();
canvas.save("canvas2.png");
}
}
QTEST_MAIN(KisGbrBrushTest)
diff --git a/libs/brush/tests/kis_gbr_brush_test.h b/libs/brush/tests/kis_gbr_brush_test.h
index 070a03b3b7..fa630e9a16 100644
--- a/libs/brush/tests/kis_gbr_brush_test.h
+++ b/libs/brush/tests/kis_gbr_brush_test.h
@@ -1,49 +1,47 @@
/*
* Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_BRUSH_TEST_H
#define KIS_BRUSH_TEST_H
#include <QtTest>
class KisGbrBrushTest : public QObject
{
Q_OBJECT
// XXX disabled until I figure out why they don't work from here, while the brushes do work from Krita
- void testMaskGenerationNoColor();
void testMaskGenerationSingleColor();
void testMaskGenerationDevColor();
- void testMaskGenerationDefaultColor();
private Q_SLOTS:
void testImageGeneration();
void benchmarkPyramidCreation();
void benchmarkScaling();
void benchmarkRotation();
void benchmarkMaskScaling();
void testPyramidLevelRounding();
void testPyramidDabTransform();
void testQPainterTransformationBorder();
};
#endif
diff --git a/libs/brush/tests/kis_imagepipe_brush_test.cpp b/libs/brush/tests/kis_imagepipe_brush_test.cpp
index ea47af1816..5a54e81fb4 100644
--- a/libs/brush/tests/kis_imagepipe_brush_test.cpp
+++ b/libs/brush/tests/kis_imagepipe_brush_test.cpp
@@ -1,278 +1,277 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_imagepipe_brush_test.h"
#include <QTest>
#include <QPainter>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoCompositeOpRegistry.h>
#include <kis_fixed_paint_device.h>
#include <brushengine/kis_paint_information.h>
#include "kis_imagepipe_brush.h"
#include <kis_paint_device.h>
#include <kis_painter.h>
#define COMPARE_ALL(brush, method) \
Q_FOREACH (KisGbrBrush *child, brush->brushes()) { \
if(brush->method() != child->method()) { \
dbgKrita << "Failing method:" << #method \
<< "brush index:" \
<< brush->brushes().indexOf(child); \
QCOMPARE(brush->method(), child->method()); \
} \
}
inline void KisImagePipeBrushTest::checkConsistency(KisImagePipeBrush *brush)
{
qreal scale = 0.5; Q_UNUSED(scale);
KisGbrBrush *firstBrush = brush->brushes().first();
/**
* This set of values is supposed to be constant, so
* it is just set to the corresponding values of the
* first brush
*/
QCOMPARE(brush->width(), firstBrush->width());
QCOMPARE(brush->height(), firstBrush->height());
QCOMPARE(brush->boundary(), firstBrush->boundary());
/**
* These values should be spread over the children brushes
*/
COMPARE_ALL(brush, maskAngle);
COMPARE_ALL(brush, scale);
COMPARE_ALL(brush, angle);
COMPARE_ALL(brush, spacing);
/**
* Check mask size values, they depend on current brush
*/
KisPaintInformation info;
KisBrush *oldBrush = brush->testingGetCurrentBrush(info);
QVERIFY(oldBrush);
qreal realScale = 1;
qreal realAngle = 0;
qreal subPixelX = 0;
qreal subPixelY = 0;
int maskWidth = brush->maskWidth(KisDabShape(realScale, 1.0, realAngle), subPixelX, subPixelY, info);
int maskHeight = brush->maskHeight(KisDabShape(realScale, 1.0, realAngle), subPixelX, subPixelY, info);
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisFixedPaintDeviceSP dev = brush->testingGetCurrentBrush(info)->paintDevice(
cs, KisDabShape(realScale, 1.0, realAngle), info, subPixelX, subPixelY);
QCOMPARE(maskWidth, dev->bounds().width());
QCOMPARE(maskHeight, dev->bounds().height());
KisBrush *newBrush = brush->testingGetCurrentBrush(info);
QCOMPARE(oldBrush, newBrush);
}
void KisImagePipeBrushTest::testLoading()
{
KisImagePipeBrush *brush = new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "C_Dirty_Spot.gih");
brush->load();
QVERIFY(brush->valid());
checkConsistency(brush);
delete brush;
}
void KisImagePipeBrushTest::testChangingBrushes()
{
KisImagePipeBrush *brush = new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "C_Dirty_Spot.gih");
brush->load();
QVERIFY(brush->valid());
qreal rotation = 0;
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation);
for (int i = 0; i < 100; i++) {
checkConsistency(brush);
brush->testingSelectNextBrush(info);
}
delete brush;
}
void checkIncrementalPainting(KisBrush *brush, const QString &prefix)
{
qreal realScale = 1;
qreal realAngle = 0;
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KoColor fillColor(Qt::red, cs);
KisFixedPaintDeviceSP fixedDab = new KisFixedPaintDevice(cs);
qreal rotation = 0;
qreal subPixelX = 0.0;
qreal subPixelY = 0.0;
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation);
for (int i = 0; i < 20; i++) {
int maskWidth = brush->maskWidth(KisDabShape(realScale, 1.0, realAngle), subPixelX, subPixelY, info);
int maskHeight = brush->maskHeight(KisDabShape(realScale, 1.0, realAngle), subPixelX, subPixelY, info);
QRect fillRect(0, 0, maskWidth, maskHeight);
fixedDab->setRect(fillRect);
- fixedDab->initialize();
- fixedDab->fill(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), fillColor.data());
+ fixedDab->lazyGrowBufferWithoutInitialization();
- brush->mask(fixedDab, KisDabShape(realScale, 1.0, realAngle), info);
+ brush->mask(fixedDab, fillColor, KisDabShape(realScale, 1.0, realAngle), info);
QCOMPARE(fixedDab->bounds(), fillRect);
QImage result = fixedDab->convertToQImage(0);
result.save(QString("fixed_dab_%1_%2.png").arg(prefix).arg(i));
}
}
void KisImagePipeBrushTest::testSimpleDabApplication()
{
KisImagePipeBrush *brush = new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "C_Dirty_Spot.gih");
brush->load();
QVERIFY(brush->valid());
checkConsistency(brush);
checkIncrementalPainting(brush, "simple");
delete brush;
}
void KisImagePipeBrushTest::testColoredDab()
{
KisImagePipeBrush *brush = new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "G_Sparks.gih");
brush->load();
QVERIFY(brush->valid());
checkConsistency(brush);
QCOMPARE(brush->useColorAsMask(), false);
QCOMPARE(brush->hasColor(), true);
QCOMPARE(brush->brushType(), PIPE_IMAGE);
// let it be the mask (should be revertible)
brush->setUseColorAsMask(true);
QCOMPARE(brush->useColorAsMask(), true);
QCOMPARE(brush->hasColor(), true);
QCOMPARE(brush->brushType(), PIPE_MASK);
// revert back
brush->setUseColorAsMask(false);
QCOMPARE(brush->useColorAsMask(), false);
QCOMPARE(brush->hasColor(), true);
QCOMPARE(brush->brushType(), PIPE_IMAGE);
// convert to the mask (irreversible)
brush->makeMaskImage();
QCOMPARE(brush->useColorAsMask(), false);
QCOMPARE(brush->hasColor(), false);
QCOMPARE(brush->brushType(), PIPE_MASK);
checkConsistency(brush);
delete brush;
}
void KisImagePipeBrushTest::testColoredDabWash()
{
KisImagePipeBrush *brush = new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "G_Sparks.gih");
brush->load();
QVERIFY(brush->valid());
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
qreal rotation = 0;
KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation);
KisPaintDeviceSP layer = new KisPaintDevice(cs);
KisPainter painter(layer);
painter.setCompositeOp(COMPOSITE_ALPHA_DARKEN);
const QVector<KisGbrBrush*> gbrs = brush->brushes();
KisFixedPaintDeviceSP dab = gbrs.at(0)->paintDevice(cs, KisDabShape(2.0, 1.0, 0.0), info);
painter.bltFixed(0, 0, dab, 0, 0, dab->bounds().width(), dab->bounds().height());
painter.bltFixed(80, 60, dab, 0, 0, dab->bounds().width(), dab->bounds().height());
painter.end();
QRect rc = layer->exactBounds();
QImage result = layer->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
#if 0
// if you want to see the result on white background, set #if 1
QImage bg(result.size(), result.format());
bg.fill(Qt::white);
QPainter qPainter(&bg);
qPainter.drawImage(0, 0, result);
result = bg;
#endif
result.save("z_spark_alpha_darken.png");
delete brush;
}
#include "kis_text_brush.h"
void KisImagePipeBrushTest::testTextBrushNoPipes()
{
KisTextBrush *brush = new KisTextBrush();
brush->setPipeMode(false);
brush->setFont(QApplication::font());
brush->setText("The_Quick_Brown_Fox_Jumps_Over_The_Lazy_Dog");
brush->updateBrush();
checkIncrementalPainting(brush, "text_no_incremental");
delete brush;
}
void KisImagePipeBrushTest::testTextBrushPiped()
{
KisTextBrush *brush = new KisTextBrush();
brush->setPipeMode(true);
brush->setFont(QApplication::font());
brush->setText("The_Quick_Brown_Fox_Jumps_Over_The_Lazy_Dog");
brush->updateBrush();
checkIncrementalPainting(brush, "text_incremental");
delete brush;
}
QTEST_MAIN(KisImagePipeBrushTest)
diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp
index aad020dbeb..1ccea69799 100644
--- a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp
+++ b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp
@@ -1,253 +1,255 @@
/* This file is part of the KDE project
Copyright (c) 2017 L. E. Segovia <leo.segovia@siggraph.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <resources/KoSvgSymbolCollectionResource.h>
#include <QDebug>
#include <QVector>
#include <QFile>
#include <QFileInfo>
#include <QBuffer>
#include <QByteArray>
#include <QImage>
#include <QPainter>
#include <klocalizedstring.h>
#include <KoStore.h>
#include <KoDocumentResourceManager.h>
#include "kis_debug.h"
#include <KoShape.h>
#include <KoShapeGroup.h>
#include <KoShapeManager.h>
#include <KoViewConverter.h>
#include <KoShapePaintingContext.h>
#include <SvgParser.h>
+
+void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
+{
+ QList<KoShape*> shapes = group->shapes();
+ std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
+ Q_FOREACH (KoShape *child, shapes) {
+ // we paint recursively here, so we do not have to check recursively for visibility
+ if (!child->isVisible())
+ continue;
+ KoShapeGroup *childGroup = dynamic_cast<KoShapeGroup*>(child);
+ if (childGroup) {
+ paintGroup(childGroup, painter, converter, paintContext);
+ } else {
+ painter.save();
+ KoShapeManager::renderSingleShape(child, painter, converter, paintContext);
+ painter.restore();
+ }
+ }
+
+}
+
+QImage KoSvgSymbol::icon()
+{
+ KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
+ Q_ASSERT(group);
+
+ QRectF rc = group->boundingRect().normalized();
+
+ QImage image(rc.width(), rc.height(), QImage::Format_ARGB32_Premultiplied);
+ QPainter gc(&image);
+ image.fill(Qt::gray);
+
+ KoViewConverter vc;
+ KoShapePaintingContext ctx;
+
+// qDebug() << "Going to render. Original bounding rect:" << group->boundingRect()
+// << "Normalized: " << rc
+// << "Scale W" << 256 / rc.width() << "Scale H" << 256 / rc.height();
+
+ gc.translate(-rc.x(), -rc.y());
+ paintGroup(group, gc, vc, ctx);
+ gc.end();
+ image = image.scaled(128, 128, Qt::KeepAspectRatio);
+ return image;
+}
+
+
+
struct KoSvgSymbolCollectionResource::Private {
QVector<KoSvgSymbol*> symbols;
QString title;
QString description;
};
KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const QString& filename)
: KoResource(filename)
, d(new Private())
{
}
KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource()
: KoResource(QString())
, d(new Private())
{
}
KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs)
: QObject(0)
, KoResource(QString())
, d(new Private())
{
setFilename(rhs.filename());
d->symbols = rhs.d->symbols;
setValid(true);
}
KoSvgSymbolCollectionResource::~KoSvgSymbolCollectionResource()
{
}
bool KoSvgSymbolCollectionResource::load()
{
QFile file(filename());
if (file.size() == 0) return false;
if (!file.open(QIODevice::ReadOnly)) {
return false;
}
bool res = loadFromDevice(&file);
file.close();
return res;
}
-void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
-{
- QList<KoShape*> shapes = group->shapes();
- std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
- Q_FOREACH (KoShape *child, shapes) {
- // we paint recursively here, so we do not have to check recursively for visibility
- if (!child->isVisible())
- continue;
- KoShapeGroup *childGroup = dynamic_cast<KoShapeGroup*>(child);
- if (childGroup) {
- paintGroup(childGroup, painter, converter, paintContext);
- } else {
- painter.save();
- KoShapeManager::renderSingleShape(child, painter, converter, paintContext);
- painter.restore();
- }
- }
-
-}
bool KoSvgSymbolCollectionResource::loadFromDevice(QIODevice *dev)
{
if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
KoXmlDocument doc;
QString errorMsg;
int errorLine = 0;
int errorColumn;
bool ok = doc.setContent(dev->readAll(), false, &errorMsg, &errorLine, &errorColumn);
if (!ok) {
errKrita << "Parsing error in " << filename() << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3"
, errorLine , errorColumn , errorMsg);
return false;
}
KoDocumentResourceManager manager;
SvgParser parser(&manager);
parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values
QSizeF fragmentSize;
// We're not interested in the shapes themselves
qDeleteAll(parser.parseSvg(doc.documentElement(), &fragmentSize));
d->symbols = parser.takeSymbols();
// qDebug() << "Loaded" << filename() << "\n\t"
// << "Title" << parser.documentTitle() << "\n\t"
// << "Description" << parser.documentDescription()
// << "\n\tgot" << d->symbols.size() << "symbols"
// << d->symbols[0]->shape->outlineRect()
// << d->symbols[0]->shape->size();
d->title = parser.documentTitle();
setName(d->title);
d->description = parser.documentDescription();
if (d->symbols.size() < 1) {
setValid(false);
return false;
}
setValid(true);;
-
- for(int i = 0; i < d->symbols.size(); ++i) {
-
- KoSvgSymbol *symbol = d->symbols[i];
- KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(symbol->shape);
- Q_ASSERT(group);
-
- QRectF rc = group->boundingRect().normalized();
-
- QImage image(rc.width(), rc.height(), QImage::Format_ARGB32_Premultiplied);
- QPainter gc(&image);
- image.fill(Qt::gray);
-
- KoViewConverter vc;
- KoShapePaintingContext ctx;
-
-// qDebug() << "Going to render. Original bounding rect:" << group->boundingRect()
-// << "Normalized: " << rc
-// << "Scale W" << 256 / rc.width() << "Scale H" << 256 / rc.height();
-
- gc.translate(-rc.x(), -rc.y());
- paintGroup(group, gc, vc, ctx);
- gc.end();
- d->symbols[i]->icon = image.scaled(500, 500, Qt::KeepAspectRatio);
-
- }
-
- setImage(d->symbols[0]->icon);
+ setImage(d->symbols[0]->icon());
return true;
}
bool KoSvgSymbolCollectionResource::save()
{
QFile file(filename());
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
return false;
}
saveToDevice(&file);
file.close();
return true;
}
bool KoSvgSymbolCollectionResource::saveToDevice(QIODevice *dev) const
{
bool res = false;
// XXX
if (res) {
KoResource::saveToDevice(dev);
}
return res;
}
QString KoSvgSymbolCollectionResource::defaultFileExtension() const
{
return QString(".svg");
}
QString KoSvgSymbolCollectionResource::title() const
{
return d->title;
}
QString KoSvgSymbolCollectionResource::description() const
{
return d->description;
}
QString KoSvgSymbolCollectionResource::creator() const
{
return "";
}
QString KoSvgSymbolCollectionResource::rights() const
{
return "";
}
QString KoSvgSymbolCollectionResource::language() const
{
return "";
}
QStringList KoSvgSymbolCollectionResource::subjects() const
{
return QStringList();
}
QString KoSvgSymbolCollectionResource::license() const
{
return "";
}
QStringList KoSvgSymbolCollectionResource::permits() const
{
return QStringList();
}
QVector<KoSvgSymbol *> KoSvgSymbolCollectionResource::symbols() const
{
return d->symbols;
}
diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.h b/libs/flake/resources/KoSvgSymbolCollectionResource.h
index 58fcc3de6e..9fff3c05d7 100644
--- a/libs/flake/resources/KoSvgSymbolCollectionResource.h
+++ b/libs/flake/resources/KoSvgSymbolCollectionResource.h
@@ -1,101 +1,106 @@
/* This file is part of the KDE project
Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef KOSVGSYMBOLCOLLECTIONRESOURCE
#define KOSVGSYMBOLCOLLECTIONRESOURCE
#include <QObject>
#include <QColor>
#include <QVector>
#include <QScopedPointer>
#include <QImage>
+#include <QImage>
+#include <QPainter>
#include <resources/KoResource.h>
#include <KoShape.h>
-
+#include <KoShapeGroup.h>
+#include <KoShapeManager.h>
+#include <KoViewConverter.h>
+#include <KoShapePaintingContext.h>
#include "kritaflake_export.h"
-struct KoSvgSymbol {
+struct KRITAFLAKE_EXPORT KoSvgSymbol {
KoSvgSymbol() {}
KoSvgSymbol(const QString &_title)
: title(_title) {}
~KoSvgSymbol()
{
delete shape;
}
QString id;
QString title;
KoShape *shape;
- QImage icon;
+ QImage icon();
bool operator==(const KoSvgSymbol& rhs) const {
return title == rhs.title;
}
};
/**
* Loads an svg file that contains "symbol" objects and creates a collection of those objects.
*/
class KRITAFLAKE_EXPORT KoSvgSymbolCollectionResource : public QObject, public KoResource
{
Q_OBJECT
public:
/**
*/
explicit KoSvgSymbolCollectionResource(const QString &filename);
/// Create an empty color set
KoSvgSymbolCollectionResource();
/// Explicit copy constructor (KoResource copy constructor is private)
KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs);
~KoSvgSymbolCollectionResource() override;
bool load() override;
bool loadFromDevice(QIODevice *dev) override;
bool save() override;
bool saveToDevice(QIODevice* dev) const override;
QString defaultFileExtension() const override;
QString title() const;
QString description() const;
QString creator() const;
QString rights() const;
QString language() const;
QStringList subjects() const;
QString license() const;
QStringList permits() const;
QVector<KoSvgSymbol *> symbols() const;
private:
struct Private;
const QScopedPointer<Private> d;
};
#endif // KoSvgSymbolCollectionResource
diff --git a/libs/global/CMakeLists.txt b/libs/global/CMakeLists.txt
index f9d2d7ba97..219c5148f4 100644
--- a/libs/global/CMakeLists.txt
+++ b/libs/global/CMakeLists.txt
@@ -1,44 +1,51 @@
+add_subdirectory( tests )
+
include(CheckFunctionExists)
check_function_exists(backtrace HAVE_BACKTRACE)
configure_file(config-debug.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-debug.h)
option(HAVE_MEMORY_LEAK_TRACKER "Enable memory leak tracker (always disabled in release build)" OFF)
option(HAVE_BACKTRACE_SUPPORT "Enable recording of backtrace in memory leak tracker" OFF)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-memory-leak-tracker.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-memory-leak-tracker.h) ### WRONG PLACE???
set(kritaglobal_LIB_SRCS
kis_assert.cpp
kis_debug.cpp
kis_algebra_2d.cpp
kis_memory_leak_tracker.cpp
kis_shared.cpp
kis_dom_utils.cpp
kis_painting_tweaks.cpp
KisHandlePainterHelper.cpp
KisHandleStyle.cpp
+ kis_relaxed_timer.cpp
kis_signal_compressor.cpp
kis_signal_compressor_with_param.cpp
kis_acyclic_signal_connector.cpp
+ kis_latency_tracker.cpp
KisQPainterStateSaver.cpp
+ KisSharedThreadPoolAdapter.cpp
+ KisSharedRunnable.cpp
+ KisRollingMeanAccumulatorWrapper.cpp
KisLoggingManager.cpp
)
add_library(kritaglobal SHARED ${kritaglobal_LIB_SRCS} )
generate_export_header(kritaglobal BASE_NAME kritaglobal)
target_link_libraries(kritaglobal
PUBLIC
Qt5::Concurrent
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::Xml
KF5::I18n
)
set_target_properties(kritaglobal PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaglobal ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/global/KisRollingMeanAccumulatorWrapper.cpp b/libs/global/KisRollingMeanAccumulatorWrapper.cpp
new file mode 100644
index 0000000000..0ae502a78b
--- /dev/null
+++ b/libs/global/KisRollingMeanAccumulatorWrapper.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisRollingMeanAccumulatorWrapper.h"
+
+#include <boost/accumulators/accumulators.hpp>
+#include <boost/accumulators/statistics/stats.hpp>
+#include <boost/accumulators/statistics/rolling_mean.hpp>
+
+using namespace boost::accumulators;
+
+struct KisRollingMeanAccumulatorWrapper::Private {
+ Private(int windowSize)
+ : accumulator(tag::rolling_window::window_size = windowSize)
+ {
+ }
+
+ accumulator_set<qreal, stats<tag::lazy_rolling_mean> > accumulator;
+};
+
+
+KisRollingMeanAccumulatorWrapper::KisRollingMeanAccumulatorWrapper(int windowSize)
+ : m_d(new Private(windowSize))
+{
+}
+
+KisRollingMeanAccumulatorWrapper::~KisRollingMeanAccumulatorWrapper()
+{
+}
+
+void KisRollingMeanAccumulatorWrapper::operator()(qreal value)
+{
+ m_d->accumulator(value);
+}
+
+qreal KisRollingMeanAccumulatorWrapper::rollingMean() const
+{
+ return boost::accumulators::rolling_mean(m_d->accumulator);
+}
+
+void KisRollingMeanAccumulatorWrapper::reset(int windowSize)
+{
+ m_d->accumulator =
+ accumulator_set<qreal, stats<tag::lazy_rolling_mean>>(
+ tag::rolling_window::window_size = windowSize);
+}
diff --git a/libs/global/KisRollingMeanAccumulatorWrapper.h b/libs/global/KisRollingMeanAccumulatorWrapper.h
new file mode 100644
index 0000000000..0d696b565e
--- /dev/null
+++ b/libs/global/KisRollingMeanAccumulatorWrapper.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISROLLINGMEANACCUMULATORWRAPPER_H
+#define KISROLLINGMEANACCUMULATORWRAPPER_H
+
+#include <QtGlobal>
+#include <QScopedPointer>
+#include "kritaglobal_export.h"
+
+/**
+ * @brief A simple wrapper class that hides boost includes from QtCreator preventing it
+ * from crashing when one adds boost's accumulator into a file
+ */
+
+class KRITAGLOBAL_EXPORT KisRollingMeanAccumulatorWrapper
+{
+public:
+ /**
+ * Create a rolling mean accumulator with window \p windowSize
+ */
+ KisRollingMeanAccumulatorWrapper(int windowSize);
+ ~KisRollingMeanAccumulatorWrapper();
+
+ /**
+ * Add \p value to a set of numbers
+ */
+ void operator()(qreal value);
+
+ /**
+ * Get rolling mean of the numbers passed to the operator
+ */
+ qreal rollingMean() const;
+
+ /**
+ * Reset accumulator and any stored value
+ */
+ void reset(int windowSize);
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KISROLLINGMEANACCUMULATORWRAPPER_H
diff --git a/libs/image/kis_projection_updates_filter.cpp b/libs/global/KisSharedRunnable.cpp
similarity index 63%
copy from libs/image/kis_projection_updates_filter.cpp
copy to libs/global/KisSharedRunnable.cpp
index e1493ec367..f1874dfa72 100644
--- a/libs/image/kis_projection_updates_filter.cpp
+++ b/libs/global/KisSharedRunnable.cpp
@@ -1,36 +1,37 @@
/*
- * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "kis_projection_updates_filter.h"
+#include "KisSharedRunnable.h"
+#include "KisSharedThreadPoolAdapter.h"
+#include "kis_assert.h"
-#include <QtGlobal>
-#include <QRect>
-
-KisProjectionUpdatesFilter::~KisProjectionUpdatesFilter()
+void KisSharedRunnable::run()
{
+ runShared();
+
+ if (m_adapter) {
+ m_adapter->notifyJobCompleted();
+ }
}
-bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache)
+void KisSharedRunnable::setSharedThreadPoolAdapter(KisSharedThreadPoolAdapter *adapter)
{
- Q_UNUSED(image);
- Q_UNUSED(node);
- Q_UNUSED(rect);
- Q_UNUSED(resetAnimationCache);
- return true;
+ m_adapter = adapter;
}
+
diff --git a/libs/ui/opengl/kis_opengl_canvas_debugger.h b/libs/global/KisSharedRunnable.h
similarity index 58%
copy from libs/ui/opengl/kis_opengl_canvas_debugger.h
copy to libs/global/KisSharedRunnable.h
index 8d7e605e96..50dbf7d9c5 100644
--- a/libs/ui/opengl/kis_opengl_canvas_debugger.h
+++ b/libs/global/KisSharedRunnable.h
@@ -1,45 +1,42 @@
/*
- * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef __KIS_OPENGL_CANVAS_DEBUGGER_H
-#define __KIS_OPENGL_CANVAS_DEBUGGER_H
+#ifndef KISSHAREDRUNNABLE_H
+#define KISSHAREDRUNNABLE_H
-#include <QScopedPointer>
+#include <kritaglobal_export.h>
+#include <QRunnable>
+class KisSharedThreadPoolAdapter;
-class KisOpenglCanvasDebugger
+class KRITAGLOBAL_EXPORT KisSharedRunnable : public QRunnable
{
public:
- KisOpenglCanvasDebugger();
- ~KisOpenglCanvasDebugger();
+ virtual void runShared() = 0;
+ void run() final;
- static KisOpenglCanvasDebugger* instance();
-
- bool showFpsOnCanvas() const;
-
- void nofityPaintRequested();
- void nofitySyncStatus(bool value);
- qreal accumulatedFps();
+private:
+ friend class KisSharedThreadPoolAdapter;
+ void setSharedThreadPoolAdapter(KisSharedThreadPoolAdapter *adapter);
private:
- struct Private;
- const QScopedPointer<Private> m_d;
+ KisSharedThreadPoolAdapter *m_adapter = 0;
};
-#endif /* __KIS_OPENGL_CANVAS_DEBUGGER_H */
+#endif // KISSHAREDRUNNABLE_H
diff --git a/libs/global/KisSharedThreadPoolAdapter.cpp b/libs/global/KisSharedThreadPoolAdapter.cpp
new file mode 100644
index 0000000000..888274f106
--- /dev/null
+++ b/libs/global/KisSharedThreadPoolAdapter.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisSharedThreadPoolAdapter.h"
+
+#include "kis_assert.h"
+#include <QThreadPool>
+#include <QElapsedTimer>
+
+
+KisSharedThreadPoolAdapter::KisSharedThreadPoolAdapter(QThreadPool *parentPool)
+ : m_parentPool(parentPool),
+ m_numRunningJobs(0)
+{
+}
+
+KisSharedThreadPoolAdapter::~KisSharedThreadPoolAdapter()
+{
+ waitForDone();
+ KIS_SAFE_ASSERT_RECOVER_NOOP(!m_numRunningJobs);
+}
+
+void KisSharedThreadPoolAdapter::start(KisSharedRunnable *runnable, int priority)
+{
+ QMutexLocker l(&m_mutex);
+
+ runnable->setSharedThreadPoolAdapter(this);
+ m_parentPool->start(runnable, priority);
+
+ m_numRunningJobs++;
+}
+
+bool KisSharedThreadPoolAdapter::tryStart(KisSharedRunnable *runnable)
+{
+ QMutexLocker l(&m_mutex);
+
+ runnable->setSharedThreadPoolAdapter(this);
+ const bool result = m_parentPool->tryStart(runnable);
+
+ if (result) {
+ m_numRunningJobs++;
+ }
+
+ return result;
+}
+
+bool KisSharedThreadPoolAdapter::waitForDone(int msecs)
+{
+ QElapsedTimer t;
+ t.start();
+
+ while (1) {
+ QMutexLocker l(&m_mutex);
+
+ if (!m_numRunningJobs) break;
+
+ const qint64 elapsed = t.elapsed();
+ if (msecs >= 0 && msecs < elapsed) return false;
+
+ const unsigned long timeout = msecs < 0 ? ULONG_MAX : msecs - elapsed;
+
+ m_waitCondition.wait(&m_mutex, timeout);
+ }
+
+ return true;
+}
+
+void KisSharedThreadPoolAdapter::notifyJobCompleted()
+{
+ QMutexLocker l(&m_mutex);
+
+ KIS_SAFE_ASSERT_RECOVER (m_numRunningJobs > 0) {
+ m_waitCondition.wakeAll();
+ return;
+ }
+
+ m_numRunningJobs--;
+ if (!m_numRunningJobs) {
+ m_waitCondition.wakeAll();
+ }
+}
+
+
diff --git a/libs/global/KisSharedThreadPoolAdapter.h b/libs/global/KisSharedThreadPoolAdapter.h
new file mode 100644
index 0000000000..eadf83fab1
--- /dev/null
+++ b/libs/global/KisSharedThreadPoolAdapter.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISSHAREDTHREADPOOLADAPTER_H
+#define KISSHAREDTHREADPOOLADAPTER_H
+
+#include <QMutex>
+#include <QWaitCondition>
+
+#include <KisSharedRunnable.h>
+
+class QThreadPool;
+
+class KRITAGLOBAL_EXPORT KisSharedThreadPoolAdapter
+{
+public:
+ KisSharedThreadPoolAdapter(QThreadPool *parentPool);
+ ~KisSharedThreadPoolAdapter();
+
+ void start(KisSharedRunnable *runnable, int priority = 0);
+ bool tryStart(KisSharedRunnable *runnable);
+
+ bool waitForDone(int msecs = -1);
+
+private:
+ friend class KisSharedRunnable;
+ void notifyJobCompleted();
+
+ KisSharedThreadPoolAdapter(KisSharedThreadPoolAdapter &rhs) = delete;
+
+private:
+ QThreadPool *m_parentPool;
+ QMutex m_mutex;
+ QWaitCondition m_waitCondition;
+ int m_numRunningJobs;
+};
+
+#endif // KISSHAREDTHREADPOOLADAPTER_H
diff --git a/libs/image/kis_projection_updates_filter.cpp b/libs/global/kis_latency_tracker.cpp
similarity index 63%
copy from libs/image/kis_projection_updates_filter.cpp
copy to libs/global/kis_latency_tracker.cpp
index e1493ec367..c66b79b603 100644
--- a/libs/image/kis_projection_updates_filter.cpp
+++ b/libs/global/kis_latency_tracker.cpp
@@ -1,36 +1,30 @@
/*
- * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (C) 2017 Bernhard Liebl <poke1024@gmx.de>
*
* 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_projection_updates_filter.h"
+#include "kis_latency_tracker.h"
-
-#include <QtGlobal>
-#include <QRect>
-
-KisProjectionUpdatesFilter::~KisProjectionUpdatesFilter()
+KisLatencyTracker::KisLatencyTracker(int windowSize) :
+ KisScalarTracker<qint64>("event latency", windowSize)
{
}
-bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache)
+void KisLatencyTracker::push(qint64 timestamp)
{
- Q_UNUSED(image);
- Q_UNUSED(node);
- Q_UNUSED(rect);
- Q_UNUSED(resetAnimationCache);
- return true;
+ const qint64 latency = currentTimestamp() - timestamp;
+ KisScalarTracker<qint64>::push(latency);
}
diff --git a/libs/global/kis_latency_tracker.h b/libs/global/kis_latency_tracker.h
new file mode 100644
index 0000000000..ffe7fe6fde
--- /dev/null
+++ b/libs/global/kis_latency_tracker.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 Bernhard Liebl <poke1024@gmx.de>
+ *
+ * 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 KRITA_KIS_SCALAR_TRACKER_H
+#define KRITA_KIS_SCALAR_TRACKER_H
+
+#include "kis_shared.h"
+#include <kritaglobal_export.h>
+
+#include <QQueue>
+#include <QElapsedTimer>
+#include <QDebug>
+
+#include <boost/heap/fibonacci_heap.hpp>
+
+#include <boost/accumulators/accumulators.hpp>
+#include <boost/accumulators/statistics/stats.hpp>
+#include <boost/accumulators/statistics/rolling_mean.hpp>
+#include <boost/accumulators/statistics/rolling_variance.hpp>
+
+template<typename T>
+class KisRollingMax {
+public:
+ KisRollingMax(int windowSize) : m_windowSize(windowSize) {
+ }
+
+ void push(T value) {
+ while (m_samples.size() > m_windowSize) {
+ m_values.erase(m_samples.dequeue());
+ }
+
+ m_samples.enqueue(m_values.push(value));
+ }
+
+ T max() const {
+ if (m_values.empty()) {
+ throw std::runtime_error("no values to get max of");
+ } else {
+ return m_values.top();
+ }
+ }
+
+private:
+ const int m_windowSize;
+
+ typedef boost::heap::fibonacci_heap<T> heap_type;
+
+ QQueue<typename heap_type::handle_type> m_samples;
+ heap_type m_values;
+};
+
+template<typename T>
+class KisScalarTracker : public KisShared {
+public:
+ /**
+ * Create a tracker with the given window size.
+ * @param window The maximum number of elements to take into account for calculation
+ * of max, mean and variance values.
+ */
+ KisScalarTracker(const QString &name, int windowSize = 500) :
+ m_name(name),
+ m_windowSize(windowSize),
+ m_addCount(0),
+ m_max(windowSize),
+ m_acc(boost::accumulators::tag::rolling_window::window_size = windowSize)
+ {
+ m_printTimer.start();
+ }
+
+ virtual ~KisScalarTracker() {
+ }
+
+ /**
+ * Add a scalar.
+ * @param value the scalar to be added.
+ */
+ virtual void push(T value) {
+ m_max.push(value);
+ m_acc(value);
+ m_addCount++;
+
+ if (m_addCount >= m_windowSize || m_printTimer.elapsed() >= 1000) {
+ m_printTimer.restart();
+ QString s = format(boost::accumulators::rolling_mean(m_acc),
+ boost::accumulators::rolling_variance(m_acc),
+ m_max.max());
+ print(s);
+ m_addCount = 0;
+ }
+
+ }
+
+protected:
+ /**
+ * Print out a message.
+ * @param message the message to print
+ */
+ virtual void print(const QString &message) {
+ qInfo() << qUtf8Printable(message);
+ }
+
+ /**
+ * Formats a message for printing.
+ * @param mean the mean scalar in the window
+ * @param variance the variance of the scalar in the window
+ * @param max the max scalar in the window
+ */
+ virtual QString format(qint64 mean, qint64 variance, qint64 max) {
+ return QString("%1: mean %2 ms, var %3, max %4 ms").arg(m_name).arg(mean).arg(variance).arg(max);
+ }
+
+private:
+ const QString m_name;
+ const int m_windowSize;
+ int m_addCount;
+
+ QElapsedTimer m_printTimer;
+
+ KisRollingMax<T> m_max;
+
+ // see https://svn.boost.org/trac10/ticket/11437
+ typedef boost::accumulators::stats<
+ boost::accumulators::tag::lazy_rolling_mean,
+ boost::accumulators::tag::rolling_variance> stats;
+
+ boost::accumulators::accumulator_set<T, stats> m_acc;
+};
+
+/**
+ * KisLatencyTracker tracks the time it takes events to reach a certain point in the program.
+ */
+
+class KRITAGLOBAL_EXPORT KisLatencyTracker : public KisScalarTracker<qint64> {
+public:
+ /**
+ * Create a tracker with the given window size.
+ * @param window The maximum number of elements to take into account for calculation
+ * of max, mean and variance values.
+ */
+ KisLatencyTracker(int windowSize = 500);
+
+ /**
+ * Register that an event with the given timestamp has arrived just now.
+ * @param timestamp Timestamp of the event that just arrived (the difference to the
+ * current time is the latency).
+ */
+ virtual void push(qint64 timestamp);
+
+protected:
+ /**
+ * @return The timestamp of "right now" in a frame that is comparable to those
+ * timestamps given to push().
+ */
+ virtual qint64 currentTimestamp() const = 0;
+};
+
+#endif //KRITA_KIS_SCALAR_TRACKER_H
diff --git a/libs/global/kis_relaxed_timer.cpp b/libs/global/kis_relaxed_timer.cpp
new file mode 100644
index 0000000000..195006b834
--- /dev/null
+++ b/libs/global/kis_relaxed_timer.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2017 Bernhard Liebl <poke1024@gmx.de>
+ *
+ * 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_relaxed_timer.h"
+
+KisRelaxedTimer::KisRelaxedTimer(QObject *parent)
+ : QObject(parent)
+ , m_interval(0)
+ , m_singleShot(false)
+ , m_nextTimerTickSeqNo(1)
+ , m_emitOnTimeTick(0)
+ , m_isEmitting(false)
+{
+}
+
+void KisRelaxedTimer::setInterval(int interval)
+{
+ Q_ASSERT(!isActive());
+ m_interval = interval;
+}
+
+void KisRelaxedTimer::setSingleShot(bool singleShot)
+{
+ m_singleShot = singleShot;
+}
+
+int KisRelaxedTimer::remainingTime() const
+{
+ // in contrast to normal QTimers, the remaining time is calculated in
+ // terms of 2 * m_interval as this is the worst case interval.
+
+ if (!isActive()) {
+ return -1;
+ } else {
+ return qMax(qint64(0), 2 * m_interval - qint64(m_elapsed.elapsed()));
+ }
+}
+
+void KisRelaxedTimer::start()
+{
+ m_elapsed.start();
+
+ // cancels any previously scheduled timer and schedules a new timer to be
+ // triggered as soon as possible, but never sooner than \p m_interval ms.
+
+ if (!m_timer.isActive()) {
+ // no internal timer is running. start one, and configure it to send
+ // us a timeout event on the next possible tick which will be exactly
+ // \p m_interval ms in the future.
+
+ m_emitOnTimeTick = m_nextTimerTickSeqNo;
+ m_timer.start(m_interval, this);
+ } else if (m_isEmitting) {
+ // an internal timer is running and we are actually called from a
+ // timeout event. so we know the next tick will happen in exactly
+ // \p m_interval ms.
+
+ m_emitOnTimeTick = m_nextTimerTickSeqNo;
+ } else {
+ // an internal timer is already running, but we do not know when
+ // the next tick will happen. we need to skip next tick as it
+ // will be sooner than m_delay. the one after that will be good as
+ // it will be m_interval * (1 + err) in the future.
+
+ m_emitOnTimeTick = m_nextTimerTickSeqNo + 1;
+ }
+}
+
+void KisRelaxedTimer::timerEvent(QTimerEvent *event)
+{
+ Q_UNUSED(event);
+
+ const int ticksStopThreshold = 5;
+
+ const qint64 timerTickSeqNo = m_nextTimerTickSeqNo;
+
+ // from this point on, if this is an emit tick, we are no longer active.
+ m_nextTimerTickSeqNo++;
+
+ if (timerTickSeqNo == m_emitOnTimeTick) {
+ if (m_singleShot) {
+ stop();
+ }
+ const IsEmitting emitting(*this);
+ emit timeout();
+ } else if (timerTickSeqNo - m_emitOnTimeTick > ticksStopThreshold) {
+ m_timer.stop();
+ }
+}
diff --git a/libs/global/kis_relaxed_timer.h b/libs/global/kis_relaxed_timer.h
new file mode 100644
index 0000000000..01a9356ce6
--- /dev/null
+++ b/libs/global/kis_relaxed_timer.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2017 Bernhard Liebl <poke1024@gmx.de>
+ *
+ * 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_RELAXED_TIMER_H
+#define __KIS_RELAXED_TIMER_H
+
+#include <QObject>
+#include <QBasicTimer>
+#include <QElapsedTimer>
+
+#include "kritaglobal_export.h"
+
+/**
+ * A timer using an interface like QTimer that relaxes the given interval callback
+ * time guarantees and minimizes internal timer restarts by keeping one long-running
+ * repeating timer.
+ *
+ * Users can use this just like a QTimer. The difference is that KisRelaxedTimer will
+ * relax the callback guarantee time as follows: timeouts will never happen earlier
+ * than \p interval ms, but may well happen only after 2 * \p interval ms (whereas
+ * QTimer guarantees a fixed interval of \p interval ms).
+ *
+ * The rationale for using this is that stopping and starting timers can produce a
+ * measurable performance overhead. KisRelaxedTimer removes that overhead.
+ */
+class KRITAGLOBAL_EXPORT KisRelaxedTimer : public QObject
+{
+ Q_OBJECT
+
+public:
+ KisRelaxedTimer(QObject *parent = nullptr);
+
+ void start();
+
+ inline void stop() {
+ m_emitOnTimeTick = 0;
+ }
+
+ void setInterval(int interval);
+ void setSingleShot(bool singleShot);
+
+ inline bool isActive() const {
+ return m_emitOnTimeTick >= m_nextTimerTickSeqNo;
+ }
+
+ int remainingTime() const;
+
+Q_SIGNALS:
+ void timeout();
+
+protected:
+ void timerEvent(QTimerEvent *event) override;
+
+private:
+ int m_interval;
+ bool m_singleShot;
+
+ QBasicTimer m_timer;
+ qint64 m_nextTimerTickSeqNo;
+ qint64 m_emitOnTimeTick;
+
+ QElapsedTimer m_elapsed;
+
+protected:
+ class IsEmitting {
+ public:
+ IsEmitting(KisRelaxedTimer &timer) : m_timer(timer) {
+ timer.m_isEmitting = true;
+ }
+
+ ~IsEmitting() {
+ m_timer.m_isEmitting = false;
+ }
+
+ private:
+ KisRelaxedTimer &m_timer;
+ };
+
+ friend class IsEmitting;
+
+ bool m_isEmitting;
+};
+
+#endif /* __KIS_RELAXED_TIMER_H */
diff --git a/libs/global/kis_signal_compressor.cpp b/libs/global/kis_signal_compressor.cpp
index 39e2bc1f4a..5dd7dc7f40 100644
--- a/libs/global/kis_signal_compressor.cpp
+++ b/libs/global/kis_signal_compressor.cpp
@@ -1,110 +1,132 @@
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "kis_signal_compressor.h"
-
-#include <QTimer>
+/**
+ * KisSignalCompressor will never trigger timeout more often than every \p delay ms,
+ * i.e. \p delay ms is a given lower limit defining the highest frequency.
+ *
+ * The current implementation uses a long-running monitor timer to eliminate the
+ * overhead incurred by restarting and stopping timers with each signal. The
+ * consequence of this is that the given \p delay ms is not always exactly followed.
+ *
+ * KisSignalCompressor makes the following callback guarantees (0 < err <= 1, with
+ * err == 0 if this is the first signal after a while):
+ *
+ * POSTPONE:
+ * - timeout after <= (1 + err) * \p delay ms.
+ * FIRST_ACTIVE_POSTPONE_NEXT:
+ * - first timeout immediately
+ * - postponed timeout after (1 + err) * \p delay ms
+ * FIRST_ACTIVE:
+ * - first timeout immediately
+ * - second timeout after (1 + err) * \p delay ms
+ * - after that: \p delay ms
+ * FIRST_INACTIVE:
+ * - timeout after (1 + err) * \p delay ms
+ */
+#include "kis_signal_compressor.h"
+#include "kis_relaxed_timer.h"
KisSignalCompressor::KisSignalCompressor()
: QObject(0)
- , m_timer(new QTimer(this))
+ , m_timer(new KisRelaxedTimer(this))
, m_mode(UNDEFINED)
, m_gotSignals(false)
{
m_timer->setSingleShot(true);
connect(m_timer, SIGNAL(timeout()), SLOT(slotTimerExpired()));
}
KisSignalCompressor::KisSignalCompressor(int delay, Mode mode, QObject *parent)
: QObject(parent),
- m_timer(new QTimer(this)),
+ m_timer(new KisRelaxedTimer(this)),
m_mode(mode),
m_gotSignals(false)
{
m_timer->setSingleShot(true);
m_timer->setInterval(delay);
connect(m_timer, SIGNAL(timeout()), SLOT(slotTimerExpired()));
}
void KisSignalCompressor::setDelay(int delay)
{
m_timer->setInterval(delay);
}
void KisSignalCompressor::start()
{
Q_ASSERT(m_mode != UNDEFINED);
switch (m_mode) {
case POSTPONE:
m_timer->start();
break;
case FIRST_ACTIVE_POSTPONE_NEXT:
case FIRST_ACTIVE:
if (!m_timer->isActive()) {
m_gotSignals = false;
m_timer->start();
emit timeout();
} else {
m_gotSignals = true;
if (m_mode == FIRST_ACTIVE_POSTPONE_NEXT) {
m_timer->start();
} else if (m_mode == FIRST_ACTIVE && m_timer->remainingTime() == 0) {
// overdue, swamped by other events
m_timer->stop();
slotTimerExpired();
}
}
break;
case FIRST_INACTIVE:
if (!m_timer->isActive()) {
m_timer->start();
}
case UNDEFINED:
; // Should never happen, but do nothing
};
if (m_mode == POSTPONE || !m_timer->isActive()) {
m_timer->start();
}
}
void KisSignalCompressor::slotTimerExpired()
{
Q_ASSERT(m_mode != UNDEFINED);
if (m_mode != FIRST_ACTIVE || m_gotSignals) {
m_gotSignals = false;
emit timeout();
}
}
void KisSignalCompressor::stop()
{
m_timer->stop();
}
bool KisSignalCompressor::isActive() const
{
return m_timer->isActive() && (m_mode != FIRST_ACTIVE || m_gotSignals);
}
void KisSignalCompressor::setMode(KisSignalCompressor::Mode mode)
{
m_mode = mode;
}
diff --git a/libs/global/kis_signal_compressor.h b/libs/global/kis_signal_compressor.h
index ab6d6945e6..ff2ff7af18 100644
--- a/libs/global/kis_signal_compressor.h
+++ b/libs/global/kis_signal_compressor.h
@@ -1,91 +1,93 @@
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_SIGNAL_COMPRESSOR_H
#define __KIS_SIGNAL_COMPRESSOR_H
-#include <QTimer>
+#include <QObject>
#include "kritaglobal_export.h"
-class QTimer;
+class KisRelaxedTimer;
/**
* Sets a timer to delay or throttle activation of a Qt slot. One example of
* where this is used is to limit the amount of expensive redraw activity on the
* canvas.
*
* There are three behaviors to choose from.
*
* POSTPONE resets the timer after each call. Therefore if the calls are made
* quickly enough, the timer will never be activated.
*
* FIRST_ACTIVE_POSTPONE_NEXT emits the first signal and postpones all
* the other actions the other action like in POSTPONE. This mode is
* used e.g. in move/remove layer functionality. If you remove a
* single layer, you'll see the result immediately. But if you want to
* remove multiple layers, you should wait until all the actions are
* finished.
*
* FIRST_ACTIVE emits the timeout() event immediately and sets a timer of
* duration \p delay. If the compressor is triggered during this time, it will
* wait until the end of the delay period to fire the signal. Further events are
* ignored until the timer elapses. Think of it as a queue with size 1, and
* where the leading element is popped every \p delay ms.
*
* FIRST_INACTIVE emits the timeout() event at the end of a timer of duration \p
* delay ms. The compressor becomes inactive and all events are ignored until
* the timer has elapsed.
*
+ * The current implementation allows the timeout() to be delayed by up to 2 times
+ * \p delay in certain situations (for details see cpp file).
*/
class KRITAGLOBAL_EXPORT KisSignalCompressor : public QObject
{
Q_OBJECT
public:
enum Mode {
POSTPONE, /* Calling start() resets the timer to \p delay ms */
FIRST_ACTIVE_POSTPONE_NEXT, /* emits the first signal and postpones all the next ones */
FIRST_ACTIVE, /* Emit timeout() signal immediately. Throttle further timeout() to rate of one per \p delay ms */
FIRST_INACTIVE, /* Set a timer \p delay ms, emit timeout() when it elapses. Ignore all events meanwhile. */
UNDEFINED /* KisSignalCompressor is created without an explicit mode */
};
public:
KisSignalCompressor();
KisSignalCompressor(int delay, Mode mode, QObject *parent = 0);
bool isActive() const;
void setMode(Mode mode);
void setDelay(int delay);
public Q_SLOTS:
void start();
void stop();
private Q_SLOTS:
void slotTimerExpired();
Q_SIGNALS:
void timeout();
private:
- QTimer *m_timer;
+ KisRelaxedTimer *m_timer;
Mode m_mode;
bool m_gotSignals;
};
#endif /* __KIS_SIGNAL_COMPRESSOR_H */
diff --git a/libs/global/tests/CMakeLists.txt b/libs/global/tests/CMakeLists.txt
new file mode 100644
index 0000000000..3cc4776465
--- /dev/null
+++ b/libs/global/tests/CMakeLists.txt
@@ -0,0 +1,8 @@
+include(ECMAddTests)
+include(KritaAddBrokenUnitTest)
+
+macro_add_unittest_definitions()
+
+ecm_add_test(KisSharedThreadPoolAdapterTest.cpp
+ TEST_NAME KisSharedThreadPoolAdapter
+ LINK_LIBRARIES kritaglobal Qt5::Test)
diff --git a/libs/global/tests/KisSharedThreadPoolAdapterTest.cpp b/libs/global/tests/KisSharedThreadPoolAdapterTest.cpp
new file mode 100644
index 0000000000..436ee4c01a
--- /dev/null
+++ b/libs/global/tests/KisSharedThreadPoolAdapterTest.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisSharedThreadPoolAdapterTest.h"
+
+#include <QTest>
+
+#include <QThreadPool>
+#include <QAtomicInt>
+#include <KisSharedThreadPoolAdapter.h>
+
+#include "kis_debug.h"
+
+struct NastyCounter : public KisSharedRunnable
+{
+ NastyCounter(QAtomicInt *value)
+ : m_value(value)
+ {
+ }
+
+ void runShared() override {
+ for (int i = 0; i < numCycles(); i++) {
+ QThread::msleep(qrand() % 10);
+ m_value->ref();
+ }
+ }
+
+ static int numCycles() {
+ return 100;
+ }
+
+private:
+ QAtomicInt *m_value;
+};
+
+
+void KisSharedThreadPoolAdapterTest::test()
+{
+ QAtomicInt value;
+
+ KisSharedThreadPoolAdapter adapter(QThreadPool::globalInstance());
+
+ const int numThreads = 30;
+
+ for (int i = 0; i < numThreads; i++) {
+ adapter.start(new NastyCounter(&value));
+ }
+
+ adapter.waitForDone();
+
+ QCOMPARE(int(value), numThreads * NastyCounter::numCycles());
+}
+
+// TODO: test waitForDone on empty queue!!!!
+
+
+QTEST_MAIN(KisSharedThreadPoolAdapterTest)
diff --git a/plugins/paintops/chalk/kis_chalk_paintop_settings_widget.h b/libs/global/tests/KisSharedThreadPoolAdapterTest.h
similarity index 58%
copy from plugins/paintops/chalk/kis_chalk_paintop_settings_widget.h
copy to libs/global/tests/KisSharedThreadPoolAdapterTest.h
index a0663ab2f0..8daba54d5e 100644
--- a/plugins/paintops/chalk/kis_chalk_paintop_settings_widget.h
+++ b/libs/global/tests/KisSharedThreadPoolAdapterTest.h
@@ -1,42 +1,34 @@
/*
- * Copyright (c) 2008 Lukas Tvrdy <lukast.dev@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef KIS_CHALKPAINTOP_SETTINGS_WIDGET_H_
-#define KIS_CHALKPAINTOP_SETTINGS_WIDGET_H_
+#ifndef KISSHAREDTHREADPOOLADAPTERTEST_H
+#define KISSHAREDTHREADPOOLADAPTERTEST_H
-#include <kis_paintop_settings_widget.h>
+#include <QtTest>
-#include "ui_wdgchalkoptions.h"
-
-class KisChalkOpOption;
-
-class KisChalkPaintOpSettingsWidget : public KisPaintOpSettingsWidget
+class KisSharedThreadPoolAdapterTest : public QObject
{
Q_OBJECT
-public:
- KisChalkPaintOpSettingsWidget(QWidget* parent = 0);
- ~KisChalkPaintOpSettingsWidget() override;
+private Q_SLOTS:
- KisPropertiesConfigurationSP configuration() const override;
+ void test();
-public:
- KisChalkOpOption* m_chalkOption;
};
-#endif
+#endif // KISSHAREDTHREADPOOLADAPTERTEST_H
diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt
index 4423928930..9556869232 100644
--- a/libs/image/CMakeLists.txt
+++ b/libs/image/CMakeLists.txt
@@ -1,391 +1,400 @@
add_subdirectory( tests )
add_subdirectory( tiles3 )
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/metadata
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty
${CMAKE_CURRENT_SOURCE_DIR}/brushengine
${CMAKE_CURRENT_SOURCE_DIR}/commands
${CMAKE_CURRENT_SOURCE_DIR}/commands_new
${CMAKE_CURRENT_SOURCE_DIR}/filter
${CMAKE_CURRENT_SOURCE_DIR}/floodfill
${CMAKE_CURRENT_SOURCE_DIR}/generator
${CMAKE_CURRENT_SOURCE_DIR}/layerstyles
${CMAKE_CURRENT_SOURCE_DIR}/processing
${CMAKE_CURRENT_SOURCE_DIR}/recorder
${CMAKE_SOURCE_DIR}/sdk/tests
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${Boost_INCLUDE_DIRS}
)
if(FFTW3_FOUND)
include_directories(${FFTW3_INCLUDE_DIR})
endif()
if(HAVE_VC)
include_directories(SYSTEM ${Vc_INCLUDE_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS})
ko_compile_for_all_implementations(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp)
else()
set(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp)
endif()
set(kritaimage_LIB_SRCS
tiles3/kis_tile.cc
tiles3/kis_tile_data.cc
tiles3/kis_tile_data_store.cc
tiles3/kis_tile_data_pooler.cc
tiles3/kis_tiled_data_manager.cc
tiles3/kis_memento_manager.cc
tiles3/kis_hline_iterator.cpp
tiles3/kis_vline_iterator.cpp
tiles3/kis_random_accessor.cc
tiles3/swap/kis_abstract_compression.cpp
tiles3/swap/kis_lzf_compression.cpp
tiles3/swap/kis_abstract_tile_compressor.cpp
tiles3/swap/kis_legacy_tile_compressor.cpp
tiles3/swap/kis_tile_compressor_2.cpp
tiles3/swap/kis_chunk_allocator.cpp
tiles3/swap/kis_memory_window.cpp
tiles3/swap/kis_swapped_data_store.cpp
tiles3/swap/kis_tile_data_swapper.cpp
kis_distance_information.cpp
kis_painter.cc
+ kis_painter_blt_multi_fixed.cpp
kis_marker_painter.cpp
kis_progress_updater.cpp
brushengine/kis_paint_information.cc
brushengine/kis_random_source.cpp
+ brushengine/KisPerStrokeRandomSource.cpp
brushengine/kis_stroke_random_source.cpp
brushengine/kis_paintop.cc
brushengine/kis_paintop_factory.cpp
brushengine/kis_paintop_preset.cpp
brushengine/kis_paintop_registry.cc
brushengine/kis_paintop_settings.cpp
brushengine/kis_paintop_settings_update_proxy.cpp
+ brushengine/kis_paintop_utils.cpp
brushengine/kis_no_size_paintop_settings.cpp
brushengine/kis_locked_properties.cc
brushengine/kis_locked_properties_proxy.cpp
brushengine/kis_locked_properties_server.cpp
brushengine/kis_paintop_config_widget.cpp
brushengine/kis_uniform_paintop_property.cpp
brushengine/kis_combo_based_paintop_property.cpp
brushengine/kis_slider_based_paintop_property.cpp
brushengine/kis_standard_uniform_properties_factory.cpp
+ brushengine/KisStrokeSpeedMeasurer.cpp
commands/kis_deselect_global_selection_command.cpp
commands/kis_image_change_layers_command.cpp
commands/kis_image_change_visibility_command.cpp
commands/kis_image_command.cpp
commands/kis_image_set_projection_color_space_command.cpp
commands/kis_image_layer_add_command.cpp
commands/kis_image_layer_move_command.cpp
commands/kis_image_layer_remove_command.cpp
commands/kis_image_layer_remove_command_impl.cpp
commands/kis_image_lock_command.cpp
commands/kis_layer_command.cpp
commands/kis_node_command.cpp
commands/kis_node_compositeop_command.cpp
commands/kis_node_opacity_command.cpp
commands/kis_node_property_list_command.cpp
commands/kis_reselect_global_selection_command.cpp
commands/kis_set_global_selection_command.cpp
commands_new/kis_saved_commands.cpp
commands_new/kis_processing_command.cpp
commands_new/kis_image_resize_command.cpp
commands_new/kis_image_set_resolution_command.cpp
commands_new/kis_node_move_command2.cpp
commands_new/kis_set_layer_style_command.cpp
commands_new/kis_selection_move_command2.cpp
commands_new/kis_update_command.cpp
commands_new/kis_switch_current_time_command.cpp
commands_new/kis_change_projection_color_command.cpp
commands_new/kis_activate_selection_mask_command.cpp
processing/kis_do_nothing_processing_visitor.cpp
processing/kis_simple_processing_visitor.cpp
processing/kis_crop_processing_visitor.cpp
processing/kis_crop_selections_processing_visitor.cpp
processing/kis_transform_processing_visitor.cpp
processing/kis_mirror_processing_visitor.cpp
filter/kis_filter.cc
filter/kis_filter_configuration.cc
filter/kis_color_transformation_configuration.cc
filter/kis_filter_registry.cc
filter/kis_color_transformation_filter.cc
generator/kis_generator.cpp
generator/kis_generator_layer.cpp
generator/kis_generator_registry.cpp
floodfill/kis_fill_interval_map.cpp
floodfill/kis_scanline_fill.cpp
lazybrush/kis_min_cut_worker.cpp
lazybrush/kis_lazy_fill_tools.cpp
lazybrush/kis_multiway_cut.cpp
lazybrush/kis_colorize_mask.cpp
lazybrush/kis_colorize_stroke_strategy.cpp
KisDelayedUpdateNodeInterface.cpp
kis_adjustment_layer.cc
kis_selection_based_layer.cpp
kis_node_filter_interface.cpp
kis_base_accessor.cpp
kis_base_node.cpp
kis_base_processor.cpp
kis_bookmarked_configuration_manager.cc
kis_clone_info.cpp
kis_clone_layer.cpp
kis_colorspace_convert_visitor.cpp
kis_config_widget.cpp
kis_convolution_kernel.cc
kis_convolution_painter.cc
kis_gaussian_kernel.cpp
kis_edge_detection_kernel.cpp
kis_cubic_curve.cpp
kis_default_bounds.cpp
kis_default_bounds_base.cpp
kis_effect_mask.cc
kis_fast_math.cpp
kis_fill_painter.cc
kis_filter_mask.cpp
kis_filter_strategy.cc
kis_transform_mask.cpp
kis_transform_mask_params_interface.cpp
kis_recalculate_transform_mask_job.cpp
kis_recalculate_generator_layer_job.cpp
kis_transform_mask_params_factory_registry.cpp
kis_safe_transform.cpp
kis_gradient_painter.cc
kis_gradient_shape_strategy.cpp
kis_cached_gradient_shape_strategy.cpp
kis_polygonal_gradient_shape_strategy.cpp
kis_iterator_ng.cpp
kis_async_merger.cpp
kis_merge_walker.cc
kis_updater_context.cpp
kis_update_job_item.cpp
kis_stroke_strategy_undo_command_based.cpp
kis_simple_stroke_strategy.cpp
+ KisRunnableBasedStrokeStrategy.cpp
+ KisRunnableStrokeJobData.cpp
+ KisRunnableStrokeJobsInterface.cpp
+ KisFakeRunnableStrokeJobsExecutor.cpp
kis_stroke_job_strategy.cpp
kis_stroke_strategy.cpp
kis_stroke.cpp
kis_strokes_queue.cpp
+ KisStrokesQueueMutatedJobInterface.cpp
kis_simple_update_queue.cpp
kis_update_scheduler.cpp
kis_queues_progress_updater.cpp
kis_composite_progress_proxy.cpp
kis_sync_lod_cache_stroke_strategy.cpp
kis_lod_capable_layer_offset.cpp
kis_update_time_monitor.cpp
KisUpdateSchedulerConfigNotifier.cpp
kis_group_layer.cc
kis_count_visitor.cpp
kis_histogram.cc
kis_image_interfaces.cpp
kis_image_animation_interface.cpp
kis_time_range.cpp
kis_node_graph_listener.cpp
kis_image.cc
kis_image_signal_router.cpp
kis_image_config.cpp
kis_projection_updates_filter.cpp
kis_suspend_projection_updates_stroke_strategy.cpp
kis_regenerate_frame_stroke_strategy.cpp
kis_switch_time_stroke_strategy.cpp
kis_crop_saved_extra_data.cpp
kis_thread_safe_signal_compressor.cpp
kis_timed_signal_threshold.cpp
kis_layer.cc
kis_indirect_painting_support.cpp
kis_abstract_projection_plane.cpp
kis_layer_projection_plane.cpp
kis_layer_utils.cpp
kis_mask_projection_plane.cpp
kis_projection_leaf.cpp
kis_mask.cc
kis_base_mask_generator.cpp
kis_rect_mask_generator.cpp
kis_circle_mask_generator.cpp
kis_gauss_circle_mask_generator.cpp
kis_gauss_rect_mask_generator.cpp
${__per_arch_circle_mask_generator_objs}
kis_curve_circle_mask_generator.cpp
kis_curve_rect_mask_generator.cpp
kis_math_toolbox.cpp
kis_memory_statistics_server.cpp
kis_name_server.cpp
kis_node.cpp
kis_node_facade.cpp
kis_node_progress_proxy.cpp
kis_busy_progress_indicator.cpp
kis_node_visitor.cpp
kis_paint_device.cc
kis_paint_device_debug_utils.cpp
kis_fixed_paint_device.cpp
kis_paint_layer.cc
kis_perspective_math.cpp
kis_pixel_selection.cpp
kis_processing_information.cpp
kis_properties_configuration.cc
kis_random_accessor_ng.cpp
kis_random_generator.cc
kis_random_sub_accessor.cpp
kis_wrapped_random_accessor.cpp
kis_selection.cc
kis_selection_mask.cpp
kis_update_outline_job.cpp
kis_update_selection_job.cpp
kis_serializable_configuration.cc
kis_transaction_data.cpp
kis_transform_worker.cc
kis_perspectivetransform_worker.cpp
bsplines/kis_bspline_1d.cpp
bsplines/kis_bspline_2d.cpp
bsplines/kis_nu_bspline_2d.cpp
kis_warptransform_worker.cc
kis_cage_transform_worker.cpp
kis_liquify_transform_worker.cpp
kis_green_coordinates_math.cpp
kis_transparency_mask.cc
kis_undo_adapter.cpp
kis_macro_based_undo_store.cpp
kis_surrogate_undo_adapter.cpp
kis_legacy_undo_adapter.cpp
kis_post_execution_undo_adapter.cpp
kis_processing_visitor.cpp
kis_processing_applicator.cpp
krita_utils.cpp
kis_outline_generator.cpp
kis_layer_composition.cpp
kis_selection_filters.cpp
KisProofingConfiguration.h
metadata/kis_meta_data_entry.cc
metadata/kis_meta_data_filter.cc
metadata/kis_meta_data_filter_p.cc
metadata/kis_meta_data_filter_registry.cc
metadata/kis_meta_data_filter_registry_model.cc
metadata/kis_meta_data_io_backend.cc
metadata/kis_meta_data_merge_strategy.cc
metadata/kis_meta_data_merge_strategy_p.cc
metadata/kis_meta_data_merge_strategy_registry.cc
metadata/kis_meta_data_parser.cc
metadata/kis_meta_data_schema.cc
metadata/kis_meta_data_schema_registry.cc
metadata/kis_meta_data_store.cc
metadata/kis_meta_data_type_info.cc
metadata/kis_meta_data_validator.cc
metadata/kis_meta_data_value.cc
recorder/kis_action_recorder.cc
recorder/kis_macro.cc
recorder/kis_macro_player.cc
recorder/kis_node_query_path.cc
recorder/kis_play_info.cc
recorder/kis_recorded_action.cc
recorder/kis_recorded_action_factory_registry.cc
recorder/kis_recorded_action_load_context.cpp
recorder/kis_recorded_action_save_context.cpp
recorder/kis_recorded_filter_action.cpp
recorder/kis_recorded_fill_paint_action.cpp
recorder/kis_recorded_node_action.cc
recorder/kis_recorded_paint_action.cpp
recorder/kis_recorded_path_paint_action.cpp
recorder/kis_recorded_shape_paint_action.cpp
kis_keyframe.cpp
kis_keyframe_channel.cpp
kis_keyframe_commands.cpp
kis_scalar_keyframe_channel.cpp
kis_raster_keyframe_channel.cpp
kis_onion_skin_compositor.cpp
kis_onion_skin_cache.cpp
kis_idle_watcher.cpp
kis_psd_layer_style.cpp
kis_layer_properties_icons.cpp
layerstyles/kis_multiple_projection.cpp
layerstyles/kis_layer_style_filter.cpp
layerstyles/kis_layer_style_filter_environment.cpp
layerstyles/kis_layer_style_filter_projection_plane.cpp
layerstyles/kis_layer_style_projection_plane.cpp
layerstyles/kis_ls_drop_shadow_filter.cpp
layerstyles/kis_ls_satin_filter.cpp
layerstyles/kis_ls_stroke_filter.cpp
layerstyles/kis_ls_bevel_emboss_filter.cpp
layerstyles/kis_ls_overlay_filter.cpp
layerstyles/kis_ls_utils.cpp
layerstyles/gimp_bump_map.cpp
KisProofingConfiguration.cpp
)
set(einspline_SRCS
3rdparty/einspline/bspline_create.cpp
3rdparty/einspline/bspline_data.cpp
3rdparty/einspline/multi_bspline_create.cpp
3rdparty/einspline/nubasis.cpp
3rdparty/einspline/nubspline_create.cpp
3rdparty/einspline/nugrid.cpp
)
add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS})
generate_export_header(kritaimage BASE_NAME kritaimage)
target_link_libraries(kritaimage
PUBLIC
kritaversion
kritawidgets
kritaglobal kritapsd
kritaodf kritapigment
kritacommand
kritawidgetutils
Qt5::Concurrent
)
target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY})
if(OPENEXR_FOUND)
target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES})
endif()
if(FFTW3_FOUND)
target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES})
endif()
if(HAVE_VC)
target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES})
endif()
if (NOT GSL_FOUND)
message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.")
else ()
target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES})
endif ()
target_include_directories(kritaimage
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/brushengine>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/filter>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/generator>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/layerstyles>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/metadata>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/processing>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/recorder>
)
set_target_properties(kritaimage PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS})
########### install schemas #############
install( FILES
metadata/schemas/dc.schema
metadata/schemas/exif.schema
metadata/schemas/tiff.schema
metadata/schemas/mkn.schema
metadata/schemas/xmp.schema
metadata/schemas/xmpmm.schema
metadata/schemas/xmprights.schema
DESTINATION ${DATA_INSTALL_DIR}/krita/metadata/schemas)
diff --git a/plugins/paintops/chalk/chalk_brush.h b/libs/image/KisFakeRunnableStrokeJobsExecutor.cpp
similarity index 53%
rename from plugins/paintops/chalk/chalk_brush.h
rename to libs/image/KisFakeRunnableStrokeJobsExecutor.cpp
index 4c5e3eec20..38ef1c4b59 100644
--- a/plugins/paintops/chalk/chalk_brush.h
+++ b/libs/image/KisFakeRunnableStrokeJobsExecutor.cpp
@@ -1,48 +1,36 @@
/*
- * Copyright (c) 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef CHALK_BRUSH_H_
-#define CHALK_BRUSH_H_
+#include "KisFakeRunnableStrokeJobsExecutor.h"
-#include <QVector>
-
-#include <KoColor.h>
-
-#include "kis_chalkop_option.h"
-#include "kis_paint_device.h"
-#include <brushengine/kis_random_source.h>
+#include <KisRunnableStrokeJobData.h>
+#include <kis_assert.h>
+#include <QVector>
-class ChalkBrush
+void KisFakeRunnableStrokeJobsExecutor::addRunnableJobs(const QVector<KisRunnableStrokeJobData *> &list)
{
+ Q_FOREACH (KisRunnableStrokeJobData *data, list) {
+ KIS_SAFE_ASSERT_RECOVER_NOOP(data->sequentiality() != KisStrokeJobData::BARRIER && "barrier jobs are not supported on the fake executor");
+ KIS_SAFE_ASSERT_RECOVER_NOOP(data->exclusivity() != KisStrokeJobData::EXCLUSIVE && "exclusive jobs are not supported on the fake executor");
-public:
- ChalkBrush(const ChalkProperties * properties, KoColorTransformation* transformation);
- ~ChalkBrush();
- void paint(KisPaintDeviceSP dev, qreal x, qreal y, const KoColor &color, qreal additionalScale);
-
-private:
- KoColor m_inkColor;
- int m_counter;
- const ChalkProperties * m_properties;
- KoColorTransformation* m_transfo;
- int m_saturationId;
- KisRandomSource m_randomSource;
-};
+ data->run();
+ }
-#endif
+ qDeleteAll(list);
+}
diff --git a/plugins/paintops/chalk/chalk_paintop_plugin.h b/libs/image/KisFakeRunnableStrokeJobsExecutor.h
similarity index 63%
copy from plugins/paintops/chalk/chalk_paintop_plugin.h
copy to libs/image/KisFakeRunnableStrokeJobsExecutor.h
index 0d78033afd..5a7fd07af9 100644
--- a/plugins/paintops/chalk/chalk_paintop_plugin.h
+++ b/libs/image/KisFakeRunnableStrokeJobsExecutor.h
@@ -1,36 +1,31 @@
/*
- * Copyright (c) 2008 Lukáš Tvrdý (lukast.dev@gmail.com)
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef CHALK_PAINTOP_PLUGIN_H_
-#define CHALK_PAINTOP_PLUGIN_H_
+#ifndef KISFAKERUNNABLESTROKEJOBSEXECUTOR_H
+#define KISFAKERUNNABLESTROKEJOBSEXECUTOR_H
-#include <QObject>
-#include <QVariant>
+#include "KisRunnableStrokeJobsInterface.h"
-/**
- * A plugin wrapper that adds the paintop factories to the paintop registry.
- */
-class ChalkPaintOpPlugin : public QObject
+
+class KRITAIMAGE_EXPORT KisFakeRunnableStrokeJobsExecutor : public KisRunnableStrokeJobsInterface
{
- Q_OBJECT
public:
- ChalkPaintOpPlugin(QObject *parent, const QVariantList &);
- ~ChalkPaintOpPlugin() override;
+ void addRunnableJobs(const QVector<KisRunnableStrokeJobData*> &list);
};
-#endif // CHALK_PAINTOP_PLUGIN_H_
+#endif // KISFAKERUNNABLESTROKEJOBSEXECUTOR_H
diff --git a/plugins/paintops/chalk/kis_chalk_paintop_settings_widget.h b/libs/image/KisRenderedDab.h
similarity index 53%
rename from plugins/paintops/chalk/kis_chalk_paintop_settings_widget.h
rename to libs/image/KisRenderedDab.h
index a0663ab2f0..92f7f9a7f2 100644
--- a/plugins/paintops/chalk/kis_chalk_paintop_settings_widget.h
+++ b/libs/image/KisRenderedDab.h
@@ -1,42 +1,46 @@
/*
- * Copyright (c) 2008 Lukas Tvrdy <lukast.dev@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef KIS_CHALKPAINTOP_SETTINGS_WIDGET_H_
-#define KIS_CHALKPAINTOP_SETTINGS_WIDGET_H_
+#ifndef KISRENDEREDDAB_H
+#define KISRENDEREDDAB_H
-#include <kis_paintop_settings_widget.h>
+#include "kis_types.h"
+#include "kis_fixed_paint_device.h"
-#include "ui_wdgchalkoptions.h"
-
-class KisChalkOpOption;
-
-class KisChalkPaintOpSettingsWidget : public KisPaintOpSettingsWidget
+struct KisRenderedDab
{
- Q_OBJECT
-
-public:
- KisChalkPaintOpSettingsWidget(QWidget* parent = 0);
- ~KisChalkPaintOpSettingsWidget() override;
-
- KisPropertiesConfigurationSP configuration() const override;
-
-public:
- KisChalkOpOption* m_chalkOption;
+ KisRenderedDab() {}
+ KisRenderedDab(KisFixedPaintDeviceSP _device)
+ : device(_device),
+ offset(_device->bounds().topLeft())
+ {
+ }
+
+ KisFixedPaintDeviceSP device;
+ QPoint offset;
+
+ qreal opacity = OPACITY_OPAQUE_F;
+ qreal flow = OPACITY_OPAQUE_F;
+ qreal averageOpacity = OPACITY_TRANSPARENT_F;
+
+ inline QRect realBounds() const {
+ return QRect(offset, device->bounds().size());
+ }
};
-#endif
+#endif // KISRENDEREDDAB_H
diff --git a/libs/image/KisRunnableBasedStrokeStrategy.cpp b/libs/image/KisRunnableBasedStrokeStrategy.cpp
new file mode 100644
index 0000000000..45829d73c6
--- /dev/null
+++ b/libs/image/KisRunnableBasedStrokeStrategy.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisRunnableBasedStrokeStrategy.h"
+
+#include <QRunnable>
+#include <functional>
+
+#include "KisRunnableStrokeJobData.h"
+#include "KisRunnableStrokeJobsInterface.h"
+
+struct KisRunnableBasedStrokeStrategy::JobsInterface : public KisRunnableStrokeJobsInterface
+{
+ JobsInterface(KisRunnableBasedStrokeStrategy *q)
+ : m_q(q)
+ {
+ }
+
+
+ void addRunnableJobs(const QVector<KisRunnableStrokeJobData*> &list) {
+ QVector<KisStrokeJobData*> newList;
+
+ Q_FOREACH (KisRunnableStrokeJobData *item, list) {
+ newList.append(item);
+ }
+
+ m_q->addMutatedJobs(newList);
+ }
+
+private:
+ KisRunnableBasedStrokeStrategy *m_q;
+};
+
+
+KisRunnableBasedStrokeStrategy::KisRunnableBasedStrokeStrategy(QString id, const KUndo2MagicString &name)
+ : KisSimpleStrokeStrategy(id, name),
+ m_jobsInterface(new JobsInterface(this))
+{
+}
+
+KisRunnableBasedStrokeStrategy::KisRunnableBasedStrokeStrategy(const KisRunnableBasedStrokeStrategy &rhs)
+ : KisSimpleStrokeStrategy(rhs),
+ m_jobsInterface(new JobsInterface(this))
+{
+}
+
+KisRunnableBasedStrokeStrategy::~KisRunnableBasedStrokeStrategy()
+{
+}
+
+void KisRunnableBasedStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
+{
+ if (!data) return;
+
+ KisRunnableStrokeJobData *runnable = dynamic_cast<KisRunnableStrokeJobData*>(data);
+ if (!runnable) return;
+
+ runnable->run();
+}
+
+KisRunnableStrokeJobsInterface *KisRunnableBasedStrokeStrategy::runnableJobsInterface() const
+{
+ return m_jobsInterface.data();
+}
diff --git a/libs/image/KisRunnableBasedStrokeStrategy.h b/libs/image/KisRunnableBasedStrokeStrategy.h
new file mode 100644
index 0000000000..6346ecafe0
--- /dev/null
+++ b/libs/image/KisRunnableBasedStrokeStrategy.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISRUNNABLEBASEDSTROKESTRATEGY_H
+#define KISRUNNABLEBASEDSTROKESTRATEGY_H
+
+#include "kis_simple_stroke_strategy.h"
+
+class KisRunnableStrokeJobsInterface;
+
+class KRITAIMAGE_EXPORT KisRunnableBasedStrokeStrategy : public KisSimpleStrokeStrategy
+{
+private:
+ struct JobsInterface;
+
+public:
+ KisRunnableBasedStrokeStrategy(QString id, const KUndo2MagicString &name);
+ KisRunnableBasedStrokeStrategy(const KisRunnableBasedStrokeStrategy &rhs);
+ ~KisRunnableBasedStrokeStrategy();
+
+ void doStrokeCallback(KisStrokeJobData *data) override;
+
+ KisRunnableStrokeJobsInterface *runnableJobsInterface() const;
+
+private:
+ const QScopedPointer<KisRunnableStrokeJobsInterface> m_jobsInterface;
+};
+
+#endif // KISRUNNABLEBASEDSTROKESTRATEGY_H
diff --git a/libs/image/KisRunnableStrokeJobData.cpp b/libs/image/KisRunnableStrokeJobData.cpp
new file mode 100644
index 0000000000..a79fd504a0
--- /dev/null
+++ b/libs/image/KisRunnableStrokeJobData.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisRunnableStrokeJobData.h"
+
+#include <QRunnable>
+#include <kis_assert.h>
+
+KisRunnableStrokeJobData::KisRunnableStrokeJobData(QRunnable *runnable, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity)
+ : KisStrokeJobData(sequentiality, exclusivity),
+ m_runnable(runnable)
+{
+}
+
+KisRunnableStrokeJobData::KisRunnableStrokeJobData(std::function<void ()> func, KisStrokeJobData::Sequentiality sequentiality, KisStrokeJobData::Exclusivity exclusivity)
+ : KisStrokeJobData(sequentiality, exclusivity),
+ m_func(func)
+{
+}
+
+KisRunnableStrokeJobData::~KisRunnableStrokeJobData() {
+ if (m_runnable && m_runnable->autoDelete()) {
+ delete m_runnable;
+ }
+}
+
+void KisRunnableStrokeJobData::run() {
+ if (m_runnable) {
+ m_runnable->run();
+ } else if (m_func) {
+ m_func();
+ }
+}
diff --git a/libs/image/KisRunnableStrokeJobData.h b/libs/image/KisRunnableStrokeJobData.h
new file mode 100644
index 0000000000..1afbffed40
--- /dev/null
+++ b/libs/image/KisRunnableStrokeJobData.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISRUNNABLESTROKEJOBDATA_H
+#define KISRUNNABLESTROKEJOBDATA_H
+
+#include "kritaimage_export.h"
+#include "kis_stroke_job_strategy.h"
+#include <functional>
+
+class QRunnable;
+
+class KRITAIMAGE_EXPORT KisRunnableStrokeJobData : public KisStrokeJobData {
+public:
+ KisRunnableStrokeJobData(QRunnable *runnable, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL,
+ KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL);
+
+ KisRunnableStrokeJobData(std::function<void()> func, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL,
+ KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL);
+
+ ~KisRunnableStrokeJobData();
+
+ void run();
+
+private:
+ QRunnable *m_runnable = 0;
+ std::function<void()> m_func;
+};
+
+#endif // KISRUNNABLESTROKEJOBDATA_H
diff --git a/libs/image/kis_projection_updates_filter.cpp b/libs/image/KisRunnableStrokeJobsInterface.cpp
similarity index 63%
copy from libs/image/kis_projection_updates_filter.cpp
copy to libs/image/KisRunnableStrokeJobsInterface.cpp
index e1493ec367..76c667c89d 100644
--- a/libs/image/kis_projection_updates_filter.cpp
+++ b/libs/image/KisRunnableStrokeJobsInterface.cpp
@@ -1,36 +1,31 @@
/*
- * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "kis_projection_updates_filter.h"
+#include "KisRunnableStrokeJobsInterface.h"
+#include <QVector>
-#include <QtGlobal>
-#include <QRect>
-
-KisProjectionUpdatesFilter::~KisProjectionUpdatesFilter()
+KisRunnableStrokeJobsInterface::~KisRunnableStrokeJobsInterface()
{
+
}
-bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache)
+void KisRunnableStrokeJobsInterface::addRunnableJob(KisRunnableStrokeJobData *data)
{
- Q_UNUSED(image);
- Q_UNUSED(node);
- Q_UNUSED(rect);
- Q_UNUSED(resetAnimationCache);
- return true;
+ addRunnableJobs({data});
}
diff --git a/libs/image/kis_projection_updates_filter.cpp b/libs/image/KisRunnableStrokeJobsInterface.h
similarity index 60%
copy from libs/image/kis_projection_updates_filter.cpp
copy to libs/image/KisRunnableStrokeJobsInterface.h
index e1493ec367..6f522f3690 100644
--- a/libs/image/kis_projection_updates_filter.cpp
+++ b/libs/image/KisRunnableStrokeJobsInterface.h
@@ -1,36 +1,37 @@
/*
- * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "kis_projection_updates_filter.h"
-
+#ifndef KISRUNNABLESTROKEJOBSINTERFACE_H
+#define KISRUNNABLESTROKEJOBSINTERFACE_H
+#include "kritaimage_export.h"
#include <QtGlobal>
-#include <QRect>
-KisProjectionUpdatesFilter::~KisProjectionUpdatesFilter()
-{
-}
+class KisRunnableStrokeJobData;
+
-bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache)
+class KRITAIMAGE_EXPORT KisRunnableStrokeJobsInterface
{
- Q_UNUSED(image);
- Q_UNUSED(node);
- Q_UNUSED(rect);
- Q_UNUSED(resetAnimationCache);
- return true;
-}
+public:
+ virtual ~KisRunnableStrokeJobsInterface();
+
+ void addRunnableJob(KisRunnableStrokeJobData *data);
+ virtual void addRunnableJobs(const QVector<KisRunnableStrokeJobData*> &list) = 0;
+};
+
+#endif // KISRUNNABLESTROKEJOBSINTERFACE_H
diff --git a/libs/image/kis_projection_updates_filter.cpp b/libs/image/KisStrokesQueueMutatedJobInterface.cpp
similarity index 62%
copy from libs/image/kis_projection_updates_filter.cpp
copy to libs/image/KisStrokesQueueMutatedJobInterface.cpp
index e1493ec367..7caad5a7a8 100644
--- a/libs/image/kis_projection_updates_filter.cpp
+++ b/libs/image/KisStrokesQueueMutatedJobInterface.cpp
@@ -1,36 +1,24 @@
/*
- * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "kis_projection_updates_filter.h"
+#include "KisStrokesQueueMutatedJobInterface.h"
-
-#include <QtGlobal>
-#include <QRect>
-
-KisProjectionUpdatesFilter::~KisProjectionUpdatesFilter()
+KisStrokesQueueMutatedJobInterface::~KisStrokesQueueMutatedJobInterface()
{
}
-bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache)
-{
- Q_UNUSED(image);
- Q_UNUSED(node);
- Q_UNUSED(rect);
- Q_UNUSED(resetAnimationCache);
- return true;
-}
diff --git a/plugins/paintops/chalk/chalk_paintop_plugin.h b/libs/image/KisStrokesQueueMutatedJobInterface.h
similarity index 63%
copy from plugins/paintops/chalk/chalk_paintop_plugin.h
copy to libs/image/KisStrokesQueueMutatedJobInterface.h
index 0d78033afd..b479c72fcf 100644
--- a/plugins/paintops/chalk/chalk_paintop_plugin.h
+++ b/libs/image/KisStrokesQueueMutatedJobInterface.h
@@ -1,36 +1,34 @@
/*
- * Copyright (c) 2008 Lukáš Tvrdý (lukast.dev@gmail.com)
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef CHALK_PAINTOP_PLUGIN_H_
-#define CHALK_PAINTOP_PLUGIN_H_
+#ifndef KISSTROKESQUEUEMUTATEDJOBINTERFACE_H
+#define KISSTROKESQUEUEMUTATEDJOBINTERFACE_H
-#include <QObject>
-#include <QVariant>
+#include "kis_types.h"
-/**
- * A plugin wrapper that adds the paintop factories to the paintop registry.
- */
-class ChalkPaintOpPlugin : public QObject
+class KisStrokeJobData;
+
+class KisStrokesQueueMutatedJobInterface
{
- Q_OBJECT
public:
- ChalkPaintOpPlugin(QObject *parent, const QVariantList &);
- ~ChalkPaintOpPlugin() override;
+ virtual ~KisStrokesQueueMutatedJobInterface();
+
+ virtual void addMutatedJobs(KisStrokeId strokeId, const QVector<KisStrokeJobData*> list) = 0;
};
-#endif // CHALK_PAINTOP_PLUGIN_H_
+#endif // KISSTROKESQUEUEMUTATEDJOBINTERFACE_H
diff --git a/plugins/paintops/chalk/chalk_paintop_plugin.h b/libs/image/KisUpdaterContextSnapshotEx.h
similarity index 58%
copy from plugins/paintops/chalk/chalk_paintop_plugin.h
copy to libs/image/KisUpdaterContextSnapshotEx.h
index 0d78033afd..dd6608af9e 100644
--- a/plugins/paintops/chalk/chalk_paintop_plugin.h
+++ b/libs/image/KisUpdaterContextSnapshotEx.h
@@ -1,36 +1,34 @@
/*
- * Copyright (c) 2008 Lukáš Tvrdý (lukast.dev@gmail.com)
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef CHALK_PAINTOP_PLUGIN_H_
-#define CHALK_PAINTOP_PLUGIN_H_
+#ifndef KISUPDATERCONTEXTSNAPSHOTEX_H
+#define KISUPDATERCONTEXTSNAPSHOTEX_H
-#include <QObject>
-#include <QVariant>
-
-/**
- * A plugin wrapper that adds the paintop factories to the paintop registry.
- */
-class ChalkPaintOpPlugin : public QObject
-{
- Q_OBJECT
-public:
- ChalkPaintOpPlugin(QObject *parent, const QVariantList &);
- ~ChalkPaintOpPlugin() override;
+enum KisUpdaterContextSnapshotExTag {
+ ContextEmpty = 0x00,
+ HasSequentialJob = 0x01,
+ HasUniquelyConcurrentJob = 0x02,
+ HasConcurrentJob = 0x04,
+ HasBarrierJob = 0x08,
+ HasMergeJob = 0x10
};
-#endif // CHALK_PAINTOP_PLUGIN_H_
+Q_DECLARE_FLAGS(KisUpdaterContextSnapshotEx, KisUpdaterContextSnapshotExTag);
+Q_DECLARE_OPERATORS_FOR_FLAGS(KisUpdaterContextSnapshotEx);
+
+#endif // KISUPDATERCONTEXTSNAPSHOTEX_H
diff --git a/libs/image/brushengine/KisPerStrokeRandomSource.cpp b/libs/image/brushengine/KisPerStrokeRandomSource.cpp
new file mode 100644
index 0000000000..2b552d22ac
--- /dev/null
+++ b/libs/image/brushengine/KisPerStrokeRandomSource.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisPerStrokeRandomSource.h"
+
+#include <QHash>
+#include <QMutex>
+#include <QMutexLocker>
+
+#include <boost/random/taus88.hpp>
+#include <boost/random/uniform_smallint.hpp>
+#include <boost/random/normal_distribution.hpp>
+
+struct KisPerStrokeRandomSource::Private
+{
+ Private(int _seed)
+ : seed(_seed)
+ {
+ boost::taus88 tempGenerator(seed);
+ generatorMax = tempGenerator.max();
+ }
+
+ Private(const Private &rhs)
+ : seed(rhs.seed),
+ generatorMax(rhs.generatorMax),
+ valuesCache(rhs.valuesCache)
+ {
+ }
+
+ qint64 fetchInt(const QString &key);
+
+ int seed = 0;
+ qint64 generatorMax = 0;
+ QHash<QString, qint64> valuesCache;
+ QMutex mutex;
+};
+
+KisPerStrokeRandomSource::KisPerStrokeRandomSource()
+ : m_d(new Private(qrand()))
+{
+
+}
+
+KisPerStrokeRandomSource::KisPerStrokeRandomSource(const KisPerStrokeRandomSource &rhs)
+ : KisShared(),
+ m_d(new Private(*rhs.m_d))
+{
+}
+
+KisPerStrokeRandomSource::~KisPerStrokeRandomSource()
+{
+}
+
+
+qint64 KisPerStrokeRandomSource::Private::fetchInt(const QString &key)
+{
+ QMutexLocker l(&mutex);
+
+ auto it = valuesCache.find(key);
+ if (it != valuesCache.end()) {
+ return it.value();
+ }
+
+ boost::taus88 oneTimeGenerator(seed + qHash(key));
+ const qint64 newValue = oneTimeGenerator();
+
+ valuesCache.insert(key, newValue);
+
+ return newValue;
+}
+
+int KisPerStrokeRandomSource::generate(const QString &key, int min, int max) const
+{
+ return min + m_d->fetchInt(key) % (max - min);
+}
+
+qreal KisPerStrokeRandomSource::generateNormalized(const QString &key) const
+{
+ return qreal(m_d->fetchInt(key)) / m_d->generatorMax;
+}
+
diff --git a/libs/image/brushengine/kis_stroke_random_source.h b/libs/image/brushengine/KisPerStrokeRandomSource.h
similarity index 50%
copy from libs/image/brushengine/kis_stroke_random_source.h
copy to libs/image/brushengine/KisPerStrokeRandomSource.h
index bbc4bd1fd2..9721eb51e8 100644
--- a/libs/image/brushengine/kis_stroke_random_source.h
+++ b/libs/image/brushengine/KisPerStrokeRandomSource.h
@@ -1,54 +1,56 @@
/*
- * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef __KIS_STROKE_RANDOM_SOURCE_H
-#define __KIS_STROKE_RANDOM_SOURCE_H
+#ifndef KISPERSTROKERANDOMSOURCE_H
+#define KISPERSTROKERANDOMSOURCE_H
#include <QScopedPointer>
+#include "kis_shared.h"
+#include "kis_shared_ptr.h"
#include "kritaimage_export.h"
-#include "kis_random_source.h"
-/**
- * A helper class to handle multiple KisRandomSource objects in a
- * stroke strategies. It creates two identical random sources in the
- * beginning of the stroke, so, when copied through copy-ctor and set
- * another level of detail starts returning the same sequence of
- * numbers as was returned for the first stroke.
- */
-class KRITAIMAGE_EXPORT KisStrokeRandomSource
+class KRITAIMAGE_EXPORT KisPerStrokeRandomSource : public KisShared
{
public:
- KisStrokeRandomSource();
- KisStrokeRandomSource(const KisStrokeRandomSource &rhs);
- KisStrokeRandomSource& operator=(const KisStrokeRandomSource &rhs);
+ KisPerStrokeRandomSource();
+ KisPerStrokeRandomSource(const KisPerStrokeRandomSource &rhs);
- ~KisStrokeRandomSource();
+ ~KisPerStrokeRandomSource();
- KisRandomSourceSP source() const;
+ /**
+ * Generates a random number in a range from \p min to \p max
+ */
+ int generate(const QString &key, int min, int max) const;
- int levelOfDetail() const;
- void setLevelOfDetail(int value);
+ /**
+ * Generates a random number in a closed range [0; 1.0]
+ */
+ qreal generateNormalized(const QString &key) const;
private:
struct Private;
const QScopedPointer<Private> m_d;
};
-#endif /* __KIS_STROKE_RANDOM_SOURCE_H */
+class KisPerStrokeRandomSource;
+typedef KisSharedPtr<KisPerStrokeRandomSource> KisPerStrokeRandomSourceSP;
+typedef KisWeakSharedPtr<KisPerStrokeRandomSource> KisPerStrokeRandomSourceWSP;
+
+#endif // KISPERSTROKERANDOMSOURCE_H
diff --git a/libs/image/brushengine/KisStrokeSpeedMeasurer.cpp b/libs/image/brushengine/KisStrokeSpeedMeasurer.cpp
new file mode 100644
index 0000000000..6edd13841c
--- /dev/null
+++ b/libs/image/brushengine/KisStrokeSpeedMeasurer.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisStrokeSpeedMeasurer.h"
+
+#include <QQueue>
+#include <QVector>
+
+#include "kis_global.h"
+
+struct KisStrokeSpeedMeasurer::Private
+{
+ struct StrokeSample {
+ StrokeSample() {}
+ StrokeSample(int _time, qreal _distance) : time(_time), distance(_distance) {}
+
+ int time = 0; /* ms */
+ qreal distance = 0;
+ };
+
+ int timeSmoothWindow = 0;
+
+ QList<StrokeSample> samples;
+ QPointF lastSamplePos;
+ int startTime = 0;
+
+ qreal maxSpeed = 0;
+
+ void purgeOldSamples();
+ void addSampleImpl(const QPointF &pt, int time);
+};
+
+KisStrokeSpeedMeasurer::KisStrokeSpeedMeasurer(int timeSmoothWindow)
+ : m_d(new Private())
+{
+ m_d->timeSmoothWindow = timeSmoothWindow;
+}
+
+KisStrokeSpeedMeasurer::~KisStrokeSpeedMeasurer()
+{
+}
+
+void KisStrokeSpeedMeasurer::Private::addSampleImpl(const QPointF &pt, int time)
+{
+ if (samples.isEmpty()) {
+ lastSamplePos = pt;
+ startTime = time;
+ samples.append(Private::StrokeSample(time, 0));
+ } else {
+ Private::StrokeSample &lastSample = samples.last();
+
+ const qreal newStrokeDistance = lastSample.distance + kisDistance(lastSamplePos, pt);
+ lastSamplePos = pt;
+
+ if (lastSample.time >= time) {
+ lastSample.distance = newStrokeDistance;
+ } else {
+ samples.append(Private::StrokeSample(time, newStrokeDistance));
+ }
+ }
+}
+
+void KisStrokeSpeedMeasurer::addSample(const QPointF &pt, int time)
+{
+ m_d->addSampleImpl(pt, time);
+ m_d->purgeOldSamples();
+ sampleMaxSpeed();
+}
+
+void KisStrokeSpeedMeasurer::addSamples(const QVector<QPointF> &points, int time)
+{
+ const int lastSampleTime = !m_d->samples.isEmpty() ? m_d->samples.last().time : 0;
+
+ const int timeSmoothBase = qMin(lastSampleTime, time);
+ const qreal timeSmoothStep = qreal(time - timeSmoothBase) / points.size();
+
+ for (int i = 0; i < points.size(); i++) {
+ const int sampleTime = timeSmoothBase + timeSmoothStep * (i + 1);
+ m_d->addSampleImpl(points[i], sampleTime);
+ }
+
+ m_d->purgeOldSamples();
+ sampleMaxSpeed();
+}
+
+qreal KisStrokeSpeedMeasurer::averageSpeed() const
+{
+ if (m_d->samples.isEmpty()) return 0;
+
+ const Private::StrokeSample &lastSample = m_d->samples.last();
+
+ const int timeDiff = lastSample.time - m_d->startTime;
+ if (!timeDiff) return 0;
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(timeDiff > 0, 0);
+
+ return lastSample.distance / timeDiff;
+}
+
+void KisStrokeSpeedMeasurer::Private::purgeOldSamples()
+{
+ if (samples.size() <= 1) return;
+
+ const Private::StrokeSample lastSample = samples.last();
+
+ auto lastValueToKeep = samples.end();
+
+ for (auto it = samples.begin(); it != samples.end(); ++it) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(lastSample.time - it->time >= 0);
+
+ if (lastSample.time - it->time < timeSmoothWindow) break;
+ lastValueToKeep = it;
+ }
+
+ if (lastValueToKeep != samples.begin() &&
+ lastValueToKeep != samples.end()) {
+
+ samples.erase(samples.begin(), lastValueToKeep - 1);
+ }
+}
+
+qreal KisStrokeSpeedMeasurer::currentSpeed() const
+{
+ if (m_d->samples.size() <= 1) return 0;
+
+ const Private::StrokeSample firstSample = m_d->samples.first();
+ const Private::StrokeSample lastSample = m_d->samples.last();
+
+ const int timeDiff = lastSample.time - firstSample.time;
+ if (!timeDiff) return 0;
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(timeDiff > 0, 0);
+
+ return (lastSample.distance - firstSample.distance) / timeDiff;
+}
+
+qreal KisStrokeSpeedMeasurer::maxSpeed() const
+{
+ return m_d->maxSpeed;
+}
+
+void KisStrokeSpeedMeasurer::reset()
+{
+ m_d->samples.clear();
+ m_d->lastSamplePos = QPointF();
+ m_d->startTime = 0;
+ m_d->maxSpeed = 0;
+}
+
+void KisStrokeSpeedMeasurer::sampleMaxSpeed()
+{
+ if (m_d->samples.size() <= 1) return;
+
+ const Private::StrokeSample firstSample = m_d->samples.first();
+ const Private::StrokeSample lastSample = m_d->samples.last();
+
+ const int timeDiff = lastSample.time - firstSample.time;
+ if (timeDiff < m_d->timeSmoothWindow) return;
+
+ const qreal speed = currentSpeed();
+ if (speed > m_d->maxSpeed) {
+ m_d->maxSpeed = speed;
+ }
+}
diff --git a/libs/ui/opengl/kis_opengl_canvas_debugger.h b/libs/image/brushengine/KisStrokeSpeedMeasurer.h
similarity index 58%
copy from libs/ui/opengl/kis_opengl_canvas_debugger.h
copy to libs/image/brushengine/KisStrokeSpeedMeasurer.h
index 8d7e605e96..3dacd68cd8 100644
--- a/libs/ui/opengl/kis_opengl_canvas_debugger.h
+++ b/libs/image/brushengine/KisStrokeSpeedMeasurer.h
@@ -1,45 +1,53 @@
/*
- * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef __KIS_OPENGL_CANVAS_DEBUGGER_H
-#define __KIS_OPENGL_CANVAS_DEBUGGER_H
+#ifndef KISSTROKESPEEDMEASURER_H
+#define KISSTROKESPEEDMEASURER_H
+#include "kritaimage_export.h"
#include <QScopedPointer>
+#include <QtGlobal>
+class QPointF;
-class KisOpenglCanvasDebugger
+
+class KRITAIMAGE_EXPORT KisStrokeSpeedMeasurer
{
public:
- KisOpenglCanvasDebugger();
- ~KisOpenglCanvasDebugger();
+ KisStrokeSpeedMeasurer(int timeSmoothWindow);
+ ~KisStrokeSpeedMeasurer();
+
+ void addSample(const QPointF &pt, int time);
+ void addSamples(const QVector<QPointF> &points, int time);
- static KisOpenglCanvasDebugger* instance();
+ qreal averageSpeed() const;
+ qreal currentSpeed() const;
+ qreal maxSpeed() const;
- bool showFpsOnCanvas() const;
+ void reset();
- void nofityPaintRequested();
- void nofitySyncStatus(bool value);
- qreal accumulatedFps();
+private:
+ void sampleMaxSpeed();
private:
struct Private;
const QScopedPointer<Private> m_d;
};
-#endif /* __KIS_OPENGL_CANVAS_DEBUGGER_H */
+#endif // KISSTROKESPEEDMEASURER_H
diff --git a/libs/image/brushengine/kis_paint_information.cc b/libs/image/brushengine/kis_paint_information.cc
index 25583e910c..bf238ba88e 100644
--- a/libs/image/brushengine/kis_paint_information.cc
+++ b/libs/image/brushengine/kis_paint_information.cc
@@ -1,595 +1,635 @@
/*
* Copyright (c) 2007,2010 Cyrille Berger <cberger@cberger.net>
*
* 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 <brushengine/kis_paint_information.h>
#include <QDomElement>
#include <boost/optional.hpp>
#include "kis_paintop.h"
#include "kis_algebra_2d.h"
#include "kis_lod_transform.h"
#include "kis_spacing_information.h"
#include <kis_dom_utils.h>
struct KisPaintInformation::Private {
Private(const QPointF & pos_,
qreal pressure_,
qreal xTilt_, qreal yTilt_,
qreal rotation_,
qreal tangentialPressure_,
qreal perspective_,
qreal time_,
qreal speed_,
bool isHoveringMode_)
:
pos(pos_),
pressure(pressure_),
xTilt(xTilt_),
yTilt(yTilt_),
rotation(rotation_),
tangentialPressure(tangentialPressure_),
perspective(perspective_),
time(time_),
speed(speed_),
isHoveringMode(isHoveringMode_),
randomSource(0),
- currentDistanceInfo(0),
+ perStrokeRandomSource(0),
levelOfDetail(0)
{
}
~Private() {
- KIS_ASSERT_RECOVER_NOOP(!currentDistanceInfo);
+ KIS_ASSERT_RECOVER_NOOP(!sanityIsRegistered);
}
Private(const Private &rhs) {
copy(rhs);
}
Private& operator=(const Private &rhs) {
copy(rhs);
return *this;
}
void copy(const Private &rhs) {
pos = rhs.pos;
pressure = rhs.pressure;
xTilt = rhs.xTilt;
yTilt = rhs.yTilt;
rotation = rhs.rotation;
tangentialPressure = rhs.tangentialPressure;
perspective = rhs.perspective;
time = rhs.time;
speed = rhs.speed;
isHoveringMode = rhs.isHoveringMode;
randomSource = rhs.randomSource;
- currentDistanceInfo = rhs.currentDistanceInfo;
+ perStrokeRandomSource = rhs.perStrokeRandomSource;
+ sanityIsRegistered = false; // HINT: we do not copy registration mark!
+ directionHistoryInfo = rhs.directionHistoryInfo;
canvasRotation = rhs.canvasRotation;
canvasMirroredH = rhs.canvasMirroredH;
if (rhs.drawingAngleOverride) {
drawingAngleOverride = *rhs.drawingAngleOverride;
}
levelOfDetail = rhs.levelOfDetail;
}
QPointF pos;
qreal pressure;
qreal xTilt;
qreal yTilt;
qreal rotation;
qreal tangentialPressure;
qreal perspective;
qreal time;
qreal speed;
bool isHoveringMode;
KisRandomSourceSP randomSource;
+ KisPerStrokeRandomSourceSP perStrokeRandomSource;
int canvasRotation;
bool canvasMirroredH;
boost::optional<qreal> drawingAngleOverride;
- KisDistanceInformation *currentDistanceInfo;
+ bool sanityIsRegistered = false;
+
+ struct DirectionHistoryInfo {
+ DirectionHistoryInfo() {}
+ DirectionHistoryInfo(qreal _totalDistance,
+ int _currentDabSeqNo,
+ qreal _lastAngle,
+ QPointF _lastPosition,
+ boost::optional<qreal> _lockedDrawingAngle)
+ : totalStrokeLength(_totalDistance),
+ currentDabSeqNo(_currentDabSeqNo),
+ lastAngle(_lastAngle),
+ lastPosition(_lastPosition),
+ lockedDrawingAngle(_lockedDrawingAngle)
+ {
+ }
+
+ qreal totalStrokeLength = 0.0;
+ int currentDabSeqNo = 0;
+ qreal lastAngle = 0.0;
+ QPointF lastPosition;
+ boost::optional<qreal> lockedDrawingAngle;
+ };
+ boost::optional<DirectionHistoryInfo> directionHistoryInfo;
int levelOfDetail;
void registerDistanceInfo(KisDistanceInformation *di) {
- currentDistanceInfo = di;
+ directionHistoryInfo = DirectionHistoryInfo(di->scalarDistanceApprox(),
+ di->currentDabSeqNo(),
+ di->lastDrawingAngle(),
+ di->lastPosition(),
+ di->lockedDrawingAngleOptional());
+
+
+ KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityIsRegistered);
+ sanityIsRegistered = true;
}
void unregisterDistanceInfo() {
- currentDistanceInfo = 0;
+ sanityIsRegistered = false;
}
};
KisPaintInformation::DistanceInformationRegistrar::
DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo)
: p(_p)
{
p->d->registerDistanceInfo(distanceInfo);
}
KisPaintInformation::DistanceInformationRegistrar::
~DistanceInformationRegistrar()
{
p->d->unregisterDistanceInfo();
}
KisPaintInformation::KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt, qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal time,
qreal speed)
: d(new Private(pos,
pressure,
xTilt, yTilt,
rotation,
tangentialPressure,
perspective,
time,
speed,
false))
{
}
KisPaintInformation::KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation)
: d(new Private(pos,
pressure,
xTilt, yTilt,
rotation,
0.0,
1.0,
0.0,
0.0,
false))
{
}
KisPaintInformation::KisPaintInformation(const QPointF &pos,
qreal pressure)
: d(new Private(pos,
pressure,
0.0, 0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
false))
{
}
KisPaintInformation::KisPaintInformation(const KisPaintInformation& rhs)
: d(new Private(*rhs.d))
{
}
void KisPaintInformation::operator=(const KisPaintInformation & rhs)
{
*d = *rhs.d;
}
KisPaintInformation::~KisPaintInformation()
{
delete d;
}
bool KisPaintInformation::isHoveringMode() const
{
return d->isHoveringMode;
}
KisPaintInformation
KisPaintInformation::createHoveringModeInfo(const QPointF &pos,
qreal pressure,
qreal xTilt, qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal speed,
int canvasrotation,
bool canvasMirroredH)
{
KisPaintInformation info(pos,
pressure,
xTilt, yTilt,
rotation,
tangentialPressure,
perspective, 0, speed);
info.d->isHoveringMode = true;
info.d->canvasRotation = canvasrotation;
info.d->canvasMirroredH = canvasMirroredH;
return info;
}
int KisPaintInformation::canvasRotation() const
{
return d->canvasRotation;
}
void KisPaintInformation::setCanvasRotation(int rotation)
{
if (rotation < 0) {
d->canvasRotation= 360- abs(rotation % 360);
} else {
d->canvasRotation= rotation % 360;
}
}
bool KisPaintInformation::canvasMirroredH() const
{
return d->canvasMirroredH;
}
void KisPaintInformation::setCanvasHorizontalMirrorState(bool mir)
{
d->canvasMirroredH = mir;
}
void KisPaintInformation::toXML(QDomDocument&, QDomElement& e) const
{
// hovering mode infos are not supposed to be saved
KIS_ASSERT_RECOVER_NOOP(!d->isHoveringMode);
e.setAttribute("pointX", QString::number(pos().x(), 'g', 15));
e.setAttribute("pointY", QString::number(pos().y(), 'g', 15));
e.setAttribute("pressure", QString::number(pressure(), 'g', 15));
e.setAttribute("xTilt", QString::number(xTilt(), 'g', 15));
e.setAttribute("yTilt", QString::number(yTilt(), 'g', 15));
e.setAttribute("rotation", QString::number(rotation(), 'g', 15));
e.setAttribute("tangentialPressure", QString::number(tangentialPressure(), 'g', 15));
e.setAttribute("perspective", QString::number(perspective(), 'g', 15));
e.setAttribute("time", QString::number(d->time, 'g', 15));
e.setAttribute("speed", QString::number(d->speed, 'g', 15));
}
KisPaintInformation KisPaintInformation::fromXML(const QDomElement& e)
{
qreal pointX = qreal(KisDomUtils::toDouble(e.attribute("pointX", "0.0")));
qreal pointY = qreal(KisDomUtils::toDouble(e.attribute("pointY", "0.0")));
qreal pressure = qreal(KisDomUtils::toDouble(e.attribute("pressure", "0.0")));
qreal rotation = qreal(KisDomUtils::toDouble(e.attribute("rotation", "0.0")));
qreal tangentialPressure = qreal(KisDomUtils::toDouble(e.attribute("tangentialPressure", "0.0")));
qreal perspective = qreal(KisDomUtils::toDouble(e.attribute("perspective", "0.0")));
qreal xTilt = qreal(KisDomUtils::toDouble(e.attribute("xTilt", "0.0")));
qreal yTilt = qreal(KisDomUtils::toDouble(e.attribute("yTilt", "0.0")));
qreal time = KisDomUtils::toDouble(e.attribute("time", "0"));
qreal speed = KisDomUtils::toDouble(e.attribute("speed", "0"));
return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt,
rotation, tangentialPressure, perspective, time, speed);
}
const QPointF& KisPaintInformation::pos() const
{
return d->pos;
}
void KisPaintInformation::setPos(const QPointF& p)
{
d->pos = p;
}
qreal KisPaintInformation::pressure() const
{
return d->pressure;
}
void KisPaintInformation::setPressure(qreal p)
{
d->pressure = p;
}
qreal KisPaintInformation::xTilt() const
{
return d->xTilt;
}
qreal KisPaintInformation::yTilt() const
{
return d->yTilt;
}
void KisPaintInformation::overrideDrawingAngle(qreal angle)
{
d->drawingAngleOverride = angle;
}
qreal KisPaintInformation::drawingAngleSafe(const KisDistanceInformation &distance) const
{
- if (d->drawingAngleOverride) return *d->drawingAngleOverride;
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!d->directionHistoryInfo, 0.0);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(distance.hasLastDabInformation(), 0.0);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!d->drawingAngleOverride, 0.0);
- if (!distance.hasLastDabInformation()) {
- warnKrita << "KisPaintInformation::drawingAngleSafe()" << "Cannot access Distance Info last dab data";
- return 0.0;
- }
+ return KisAlgebra2D::directionBetweenPoints(distance.lastPosition(),
+ pos(),
+ distance.lastDrawingAngle());
- return distance.nextDrawingAngle(pos());
}
KisPaintInformation::DistanceInformationRegistrar
KisPaintInformation::registerDistanceInformation(KisDistanceInformation *distance)
{
return DistanceInformationRegistrar(this, distance);
}
-qreal KisPaintInformation::drawingAngle() const
+qreal KisPaintInformation::drawingAngle(bool considerLockedAngle) const
{
if (d->drawingAngleOverride) return *d->drawingAngleOverride;
- if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
- warnKrita << "KisPaintInformation::drawingAngle()" << "Cannot access Distance Info last dab data";
+ if (!d->directionHistoryInfo) {
+ warnKrita << "KisPaintInformation::drawingAngleSafe()" << "DirectionHistoryInfo object is not available";
return 0.0;
}
- return d->currentDistanceInfo->nextDrawingAngle(pos());
-}
-
-void KisPaintInformation::lockCurrentDrawingAngle(qreal alpha_unused) const
-{
- Q_UNUSED(alpha_unused);
-
- if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
- warnKrita << "KisPaintInformation::lockCurrentDrawingAngle()" << "Cannot access Distance Info last dab data";
- return;
- }
-
- qreal angle = d->currentDistanceInfo->nextDrawingAngle(pos(), false);
-
- qreal newAngle = angle;
+ if (considerLockedAngle &&
+ d->directionHistoryInfo->lockedDrawingAngle) {
- if (d->currentDistanceInfo->hasLockedDrawingAngle()) {
- const qreal stabilizingCoeff = 20.0;
- const qreal dist = stabilizingCoeff * d->currentDistanceInfo->currentSpacing().scalarApprox();
- const qreal alpha = qMax(0.0, dist - d->currentDistanceInfo->scalarDistanceApprox()) / dist;
-
- const qreal oldAngle = d->currentDistanceInfo->lockedDrawingAngle();
-
- if (shortestAngularDistance(oldAngle, newAngle) < M_PI / 6) {
- newAngle = (1.0 - alpha) * oldAngle + alpha * newAngle;
- } else {
- newAngle = oldAngle;
- }
+ return *d->directionHistoryInfo->lockedDrawingAngle;
}
- d->currentDistanceInfo->setLockedDrawingAngle(newAngle);
+ // If the start and end positions are the same, we can't compute an angle. In that case, use the
+ // provided default.
+ return KisAlgebra2D::directionBetweenPoints(d->directionHistoryInfo->lastPosition,
+ pos(),
+ d->directionHistoryInfo->lastAngle);
}
QPointF KisPaintInformation::drawingDirectionVector() const
{
- if (d->drawingAngleOverride) {
- qreal angle = *d->drawingAngleOverride;
- return QPointF(cos(angle), sin(angle));
- }
-
- if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
- warnKrita << "KisPaintInformation::drawingDirectionVector()" << "Cannot access Distance Info last dab data";
- return QPointF(1.0, 0.0);
- }
-
- return d->currentDistanceInfo->nextDrawingDirectionVector(pos());
+ const qreal angle = drawingAngle(false);
+ return QPointF(cos(angle), sin(angle));
}
qreal KisPaintInformation::drawingDistance() const
{
- if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
- warnKrita << "KisPaintInformation::drawingDistance()" << "Cannot access Distance Info last dab data";
+ if (!d->directionHistoryInfo) {
+ warnKrita << "KisPaintInformation::drawingDistance()" << "DirectionHistoryInfo object is not available";
return 1.0;
}
- QVector2D diff(pos() - d->currentDistanceInfo->lastPosition());
+ QVector2D diff(pos() - d->directionHistoryInfo->lastPosition);
qreal length = diff.length();
if (d->levelOfDetail) {
length *= KisLodTransform::lodToInvScale(d->levelOfDetail);
}
return length;
}
qreal KisPaintInformation::drawingSpeed() const
{
return d->speed;
}
qreal KisPaintInformation::rotation() const
{
return d->rotation;
}
qreal KisPaintInformation::tangentialPressure() const
{
return d->tangentialPressure;
}
qreal KisPaintInformation::perspective() const
{
return d->perspective;
}
qreal KisPaintInformation::currentTime() const
{
return d->time;
}
+int KisPaintInformation::currentDabSeqNo() const
+{
+ if (!d->directionHistoryInfo) {
+ warnKrita << "KisPaintInformation::currentDabSeqNo()" << "DirectionHistoryInfo object is not available";
+ return 0;
+ }
+
+ return d->directionHistoryInfo->currentDabSeqNo;
+}
+
+qreal KisPaintInformation::totalStrokeLength() const
+{
+ if (!d->directionHistoryInfo) {
+ warnKrita << "KisPaintInformation::totalStrokeLength()" << "DirectionHistoryInfo object is not available";
+ return 0;
+ }
+
+ return d->directionHistoryInfo->totalStrokeLength;
+}
+
KisRandomSourceSP KisPaintInformation::randomSource() const
{
if (!d->randomSource) {
+ qWarning() << "Accessing uninitialized random source!";
d->randomSource = new KisRandomSource();
}
return d->randomSource;
}
void KisPaintInformation::setRandomSource(KisRandomSourceSP value)
{
d->randomSource = value;
}
+KisPerStrokeRandomSourceSP KisPaintInformation::perStrokeRandomSource() const
+{
+ if (!d->perStrokeRandomSource) {
+ qWarning() << "Accessing uninitialized per stroke random source!";
+ d->perStrokeRandomSource = new KisPerStrokeRandomSource();
+ }
+
+ return d->perStrokeRandomSource;
+}
+
+void KisPaintInformation::setPerStrokeRandomSource(KisPerStrokeRandomSourceSP value)
+{
+ d->perStrokeRandomSource = value;
+}
+
void KisPaintInformation::setLevelOfDetail(int levelOfDetail)
{
d->levelOfDetail = levelOfDetail;
}
QDebug operator<<(QDebug dbg, const KisPaintInformation &info)
{
#ifdef NDEBUG
Q_UNUSED(info);
#else
dbg.nospace() << "Position: " << info.pos();
dbg.nospace() << ", Pressure: " << info.pressure();
dbg.nospace() << ", X Tilt: " << info.xTilt();
dbg.nospace() << ", Y Tilt: " << info.yTilt();
dbg.nospace() << ", Rotation: " << info.rotation();
dbg.nospace() << ", Tangential Pressure: " << info.tangentialPressure();
dbg.nospace() << ", Perspective: " << info.perspective();
dbg.nospace() << ", Drawing Angle: " << info.drawingAngle();
dbg.nospace() << ", Drawing Speed: " << info.drawingSpeed();
dbg.nospace() << ", Drawing Distance: " << info.drawingDistance();
dbg.nospace() << ", Time: " << info.currentTime();
#endif
return dbg.space();
}
KisPaintInformation KisPaintInformation::mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi)
{
QPointF pt = (1 - t) * mixedPi.pos() + t * basePi.pos();
return mixImpl(pt, t, mixedPi, basePi, true, false);
}
KisPaintInformation KisPaintInformation::mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos();
return mix(pt, t, pi1, pi2);
}
KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
return mixImpl(p, t, pi1, pi2, false, true);
}
KisPaintInformation KisPaintInformation::mixWithoutTime(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos();
return mixWithoutTime(pt, t, pi1, pi2);
}
KisPaintInformation KisPaintInformation::mixWithoutTime(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
return mixImpl(p, t, pi1, pi2, false, false);
}
void KisPaintInformation::mixOtherOnlyPosition(qreal t, const KisPaintInformation& other)
{
QPointF pt = (1 - t) * other.pos() + t * this->pos();
this->mixOtherImpl(pt, t, other, true, false);
}
void KisPaintInformation::mixOtherWithoutTime(qreal t, const KisPaintInformation& other)
{
QPointF pt = (1 - t) * other.pos() + t * this->pos();
this->mixOtherImpl(pt, t, other, false, false);
}
KisPaintInformation KisPaintInformation::mixImpl(const QPointF &p, qreal t, const KisPaintInformation &pi1, const KisPaintInformation &pi2, bool posOnly, bool mixTime)
{
KisPaintInformation result(pi2);
result.mixOtherImpl(p, t, pi1, posOnly, mixTime);
return result;
}
void KisPaintInformation::mixOtherImpl(const QPointF &p, qreal t, const KisPaintInformation &other, bool posOnly, bool mixTime)
{
if (posOnly) {
this->d->pos = p;
this->d->isHoveringMode = false;
- this->d->currentDistanceInfo = 0;
this->d->levelOfDetail = 0;
return;
}
else {
qreal pressure = (1 - t) * other.pressure() + t * this->pressure();
qreal xTilt = (1 - t) * other.xTilt() + t * this->xTilt();
qreal yTilt = (1 - t) * other.yTilt() + t * this->yTilt();
qreal rotation = other.rotation();
if (other.rotation() != this->rotation()) {
qreal a1 = kisDegreesToRadians(other.rotation());
qreal a2 = kisDegreesToRadians(this->rotation());
qreal distance = shortestAngularDistance(a2, a1);
rotation = kisRadiansToDegrees(incrementInDirection(a1, t * distance, a2));
}
qreal tangentialPressure = (1 - t) * other.tangentialPressure() + t * this->tangentialPressure();
qreal perspective = (1 - t) * other.perspective() + t * this->perspective();
qreal time = mixTime ? ((1 - t) * other.currentTime() + t * this->currentTime()) : this->currentTime();
qreal speed = (1 - t) * other.drawingSpeed() + t * this->drawingSpeed();
KIS_ASSERT_RECOVER_NOOP(other.isHoveringMode() == this->isHoveringMode());
*(this->d) = Private(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed, other.isHoveringMode());
this->d->randomSource = other.d->randomSource;
+ this->d->perStrokeRandomSource = other.d->perStrokeRandomSource;
// this->d->isHoveringMode = other.isHoveringMode();
- this->d->currentDistanceInfo = 0;
this->d->levelOfDetail = other.d->levelOfDetail;
}
}
qreal KisPaintInformation::tiltDirection(const KisPaintInformation& info, bool normalize)
{
qreal xTilt = info.xTilt();
qreal yTilt = info.yTilt();
// radians -PI, PI
qreal tiltDirection = atan2(-xTilt, yTilt);
// if normalize is true map to 0.0..1.0
return normalize ? (tiltDirection / (2 * M_PI) + 0.5) : tiltDirection;
}
qreal KisPaintInformation::tiltElevation(const KisPaintInformation& info, qreal maxTiltX, qreal maxTiltY, bool normalize)
{
qreal xTilt = qBound(qreal(-1.0), info.xTilt() / maxTiltX , qreal(1.0));
qreal yTilt = qBound(qreal(-1.0), info.yTilt() / maxTiltY , qreal(1.0));
qreal e;
if (fabs(xTilt) > fabs(yTilt)) {
e = sqrt(qreal(1.0) + yTilt * yTilt);
} else {
e = sqrt(qreal(1.0) + xTilt * xTilt);
}
qreal cosAlpha = sqrt(xTilt * xTilt + yTilt * yTilt) / e;
qreal tiltElevation = acos(cosAlpha); // in radians in [0, 0.5 * PI]
// mapping to 0.0..1.0 if normalize is true
return normalize ? (tiltElevation / (M_PI * qreal(0.5))) : tiltElevation;
}
diff --git a/libs/image/brushengine/kis_paint_information.h b/libs/image/brushengine/kis_paint_information.h
index 7a6ab7a274..2ff0a1548a 100644
--- a/libs/image/brushengine/kis_paint_information.h
+++ b/libs/image/brushengine/kis_paint_information.h
@@ -1,295 +1,309 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_PAINT_INFORMATION_
#define _KIS_PAINT_INFORMATION_
#include <kis_debug.h>
#include <QTime>
#include "kis_global.h"
#include "kritaimage_export.h"
#include <kis_distance_information.h>
#include "kis_random_source.h"
+#include "KisPerStrokeRandomSource.h"
#include "kis_spacing_information.h"
#include "kis_timing_information.h"
class QDomDocument;
class QDomElement;
class KisDistanceInformation;
/**
* KisPaintInformation contains information about the input event that
* causes the brush action to happen to the brush engine's paint
* methods.
*
* XXX: we directly pass the KoPointerEvent x and y tilt to
* KisPaintInformation, and their range is -60 to +60!
*
* @param pos: the position of the paint event in subpixel accuracy
* @param pressure: the pressure of the stylus
* @param xTilt: the angle between the device (a pen, for example) and
* the perpendicular in the direction of the x axis. Positive values
* are towards the bottom of the tablet. The angle is within the range
* 0 to 1
* @param yTilt: the angle between the device (a pen, for example) and
* the perpendicular in the direction of the y axis. Positive values
* are towards the bottom of the tablet. The angle is within the range
* 0 to .
* @param movement: current position minus the last position of the call to paintAt
* @param rotation
* @param tangentialPressure
* @param perspective
**/
class KRITAIMAGE_EXPORT KisPaintInformation
{
public:
/**
* Note, that this class is relied on the compiler optimization
* of the return value. So if it doesn't work for some reason,
* please implement a proper copy c-tor
*/
class KRITAIMAGE_EXPORT DistanceInformationRegistrar
{
public:
DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo);
~DistanceInformationRegistrar();
private:
KisPaintInformation *p;
};
public:
/**
* Create a new KisPaintInformation object.
*/
KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal time,
qreal speed);
KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation);
KisPaintInformation(const QPointF & pos = QPointF(),
qreal pressure = PRESSURE_DEFAULT);
KisPaintInformation(const KisPaintInformation& rhs);
void operator=(const KisPaintInformation& rhs);
~KisPaintInformation();
template <class PaintOp>
void paintAt(PaintOp &op, KisDistanceInformation *distanceInfo) {
KisSpacingInformation spacingInfo;
KisTimingInformation timingInfo;
{
DistanceInformationRegistrar r = registerDistanceInformation(distanceInfo);
spacingInfo = op.paintAt(*this);
timingInfo = op.updateTimingImpl(*this);
+
+ // Initiate the process of locking the drawing angle. The locked value will
+ // always be present in the internals, but it will be requested but the users
+ // with a special parameter of drawingAngle() only.
+ if (!this->isHoveringMode()) {
+ distanceInfo->lockCurrentDrawingAngle(*this);
+ }
}
distanceInfo->registerPaintedDab(*this, spacingInfo, timingInfo);
}
const QPointF& pos() const;
void setPos(const QPointF& p);
/// The pressure of the value (from 0.0 to 1.0)
qreal pressure() const;
/// Set the pressure
void setPressure(qreal p);
/// The tilt of the pen on the horizontal axis (from 0.0 to 1.0)
qreal xTilt() const;
/// The tilt of the pen on the vertical axis (from 0.0 to 1.0)
qreal yTilt() const;
/// XXX !!! :-| Please add dox!
void overrideDrawingAngle(qreal angle);
/// XXX !!! :-| Please add dox!
qreal drawingAngleSafe(const KisDistanceInformation &distance) const;
/**
* Causes the specified distance information to be temporarily registered with this
* KisPaintInformation object, so that the KisPaintInformation can compute certain values that
* may be needed at painting time, such as the drawing direction. When the returned object is
* destroyed, the KisDistanceInformation will be unregistered. At most one
* KisDistanceInformation can be registered with a given KisPaintInformation at a time.
*/
DistanceInformationRegistrar registerDistanceInformation(KisDistanceInformation *distance);
/**
* Current brush direction computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
- qreal drawingAngle() const;
-
- /**
- * Lock current drawing angle for the rest of the stroke. If some
- * value has already been locked, \p alpha shown the coefficient
- * with which the new velue should be blended in.
- */
- void lockCurrentDrawingAngle(qreal alpha) const;
+ qreal drawingAngle(bool considerLockedAngle = false) const;
/**
* Current brush direction vector computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
QPointF drawingDirectionVector() const;
/**
* Current brush speed computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingSpeed() const;
/**
* Current distance from the previous dab
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingDistance() const;
/// rotation as given by the tablet event
qreal rotation() const;
/// tangential pressure (i.e., rate for an airbrush device)
qreal tangentialPressure() const;
/// reciprocal of distance on the perspective grid
qreal perspective() const;
/// Number of ms since the beginning of the stroke
qreal currentTime() const;
+ /// Number of dabs painted since the beginning of the stroke
+ int currentDabSeqNo() const;
+
+ /// The length of the stroke **before** painting the curent dab
+ qreal totalStrokeLength() const;
+
// random source for generating in-stroke effects
KisRandomSourceSP randomSource() const;
// the stroke should initialize random source of all the used
// paint info objects, otherwise it shows a warning
void setRandomSource(KisRandomSourceSP value);
+ // random source for generating in-stroke effects, generates one(!) value per stroke
+ KisPerStrokeRandomSourceSP perStrokeRandomSource() const;
+
+ // the stroke should initialize per stroke random source of all the used
+ // paint info objects, otherwise it shows a warning
+ void setPerStrokeRandomSource(KisPerStrokeRandomSourceSP value);
+
// set level of detail which info object has been generated for
void setLevelOfDetail(int levelOfDetail);
/**
* The paint information may be generated not only during real
* stroke when the actual painting is happening, but also when the
* cursor is hovering the canvas. In this mode some of the sensors
* work a bit differently. The most outstanding example is Fuzzy
* sensor, which returns unit value in this mode, otherwise it is
* too irritating for a user.
*
* This value is true only for paint information objects created with
* createHoveringModeInfo() constructor.
*
* \see createHoveringModeInfo()
*/
bool isHoveringMode() const;
/**
* Create a fake info object with isHoveringMode() property set to
* true.
*
* \see isHoveringMode()
*/
static KisPaintInformation createHoveringModeInfo(const QPointF &pos,
qreal pressure = PRESSURE_DEFAULT,
qreal xTilt = 0.0, qreal yTilt = 0.0,
qreal rotation = 0.0,
qreal tangentialPressure = 0.0,
qreal perspective = 1.0,
qreal speed = 0.0,
int canvasrotation = 0,
bool canvasMirroredH = false);
/**
*Returns the canvas rotation if that has been given to the kispaintinformation.
*/
int canvasRotation() const;
/**
*set the canvas rotation.
*/
void setCanvasRotation(int rotation);
/*
*Whether the canvas is mirrored for the paint-operation.
*/
bool canvasMirroredH() const;
/*
*Set whether the canvas is mirrored for the paint-operation.
*/
void setCanvasHorizontalMirrorState(bool mir);
void toXML(QDomDocument&, QDomElement&) const;
static KisPaintInformation fromXML(const QDomElement&);
// TODO: Refactor the static mix functions to non-static in-place mutation
// versions like mixOtherOnlyPosition and mixOtherWithoutTime.
// Heap allocation on Windows is awfully slow and will fragment the memory
// badly. Since KisPaintInformation allocates on the heap, we should re-use
// existing instance whenever possible, especially in loops.
// Ref: https://phabricator.kde.org/D6578
/// (1-t) * p1 + t * p2
static KisPaintInformation mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi);
static KisPaintInformation mix(const QPointF& p, qreal t, const KisPaintInformation& p1, const KisPaintInformation& p2);
static KisPaintInformation mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2);
static KisPaintInformation mixWithoutTime(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2);
static KisPaintInformation mixWithoutTime(qreal t, const KisPaintInformation &pi1, const KisPaintInformation &pi2);
void mixOtherOnlyPosition(qreal t, const KisPaintInformation& other);
void mixOtherWithoutTime(qreal t, const KisPaintInformation& other);
static qreal tiltDirection(const KisPaintInformation& info, bool normalize = true);
static qreal tiltElevation(const KisPaintInformation& info, qreal maxTiltX = 60.0, qreal maxTiltY = 60.0, bool normalize = true);
private:
static KisPaintInformation mixImpl(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2, bool posOnly, bool mixTime);
void mixOtherImpl(const QPointF &p, qreal t, const KisPaintInformation &other, bool posOnly, bool mixTime);
private:
struct Private;
Private* const d;
};
KRITAIMAGE_EXPORT QDebug operator<<(QDebug debug, const KisPaintInformation& info);
#endif
diff --git a/libs/image/brushengine/kis_paintop.cc b/libs/image/brushengine/kis_paintop.cc
index 24b1df7d35..6c6571c618 100644
--- a/libs/image/brushengine/kis_paintop.cc
+++ b/libs/image/brushengine/kis_paintop.cc
@@ -1,205 +1,211 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Clarence Dang <dang@kde.org>
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2004,2007,2010 Cyrille Berger <cberger@cberger.net>
*
* 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.h"
#include <math.h>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoPointerEvent.h>
#include "kis_painter.h"
#include "kis_layer.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "kis_global.h"
#include "kis_datamanager.h"
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paint_information.h>
#include "kis_vec.h"
#include "kis_perspective_math.h"
#include "kis_fixed_paint_device.h"
#include "kis_paintop_utils.h"
#define BEZIER_FLATNESS_THRESHOLD 0.5
#include <kis_distance_information.h>
#include <qnumeric.h>
struct Q_DECL_HIDDEN KisPaintOp::Private {
Private(KisPaintOp *_q)
: q(_q), dab(0),
fanCornersEnabled(false),
fanCornersStep(1.0) {}
KisPaintOp *q;
KisFixedPaintDeviceSP dab;
KisPainter* painter;
bool fanCornersEnabled;
qreal fanCornersStep;
};
KisPaintOp::KisPaintOp(KisPainter * painter) : d(new Private(this))
{
d->painter = painter;
}
KisPaintOp::~KisPaintOp()
{
d->dab.clear();
delete d;
}
KisFixedPaintDeviceSP KisPaintOp::cachedDab()
{
return cachedDab(d->painter->device()->colorSpace());
}
KisFixedPaintDeviceSP KisPaintOp::cachedDab(const KoColorSpace *cs)
{
if (!d->dab || *d->dab->colorSpace() != *cs) {
d->dab = new KisFixedPaintDevice(cs);
}
return d->dab;
}
void KisPaintOp::setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep)
{
d->fanCornersEnabled = fanCornersEnabled;
d->fanCornersStep = fanCornersStep;
}
void KisPaintOp::splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction)
{
const qint32 i = std::floor(coordinate);
const qreal f = coordinate - i;
*whole = i;
*fraction = f;
}
+int KisPaintOp::doAsyncronousUpdate(QVector<KisRunnableStrokeJobData *> &jobs)
+{
+ Q_UNUSED(jobs);
+ return 40;
+}
+
static void paintBezierCurve(KisPaintOp *paintOp,
const KisPaintInformation &pi1,
const KisVector2D &control1,
const KisVector2D &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
LineEquation line = LineEquation::Through(toKisVector2D(pi1.pos()), toKisVector2D(pi2.pos()));
qreal d1 = line.absDistance(control1);
qreal d2 = line.absDistance(control2);
if ((d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD)
|| qIsNaN(d1) || qIsNaN(d2)) {
paintOp->paintLine(pi1, pi2, currentDistance);
} else {
// Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508
KisVector2D l2 = (toKisVector2D(pi1.pos()) + control1) / 2;
KisVector2D h = (control1 + control2) / 2;
KisVector2D l3 = (l2 + h) / 2;
KisVector2D r3 = (control2 + toKisVector2D(pi2.pos())) / 2;
KisVector2D r2 = (h + r3) / 2;
KisVector2D l4 = (l3 + r2) / 2;
KisPaintInformation middlePI = KisPaintInformation::mix(toQPointF(l4), 0.5, pi1, pi2);
paintBezierCurve(paintOp, pi1, l2, l3, middlePI, currentDistance);
paintBezierCurve(paintOp, middlePI, r2, r3, pi2, currentDistance);
}
}
void KisPaintOp::paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
return ::paintBezierCurve(this, pi1, toKisVector2D(control1), toKisVector2D(control2), pi2, currentDistance);
}
void KisPaintOp::paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
KisPaintOpUtils::paintLine(*this, pi1, pi2, currentDistance,
d->fanCornersEnabled,
d->fanCornersStep);
}
void KisPaintOp::paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance)
{
Q_ASSERT(currentDistance);
KisPaintInformation pi(info);
pi.paintAt(*this, currentDistance);
}
void KisPaintOp::updateSpacing(const KisPaintInformation &info,
KisDistanceInformation &currentDistance) const
{
KisPaintInformation pi(info);
KisSpacingInformation spacingInfo;
{
KisPaintInformation::DistanceInformationRegistrar r
= pi.registerDistanceInformation(&currentDistance);
spacingInfo = updateSpacingImpl(pi);
}
currentDistance.updateSpacing(spacingInfo);
}
void KisPaintOp::updateTiming(const KisPaintInformation &info,
KisDistanceInformation &currentDistance) const
{
KisPaintInformation pi(info);
KisTimingInformation timingInfo;
{
KisPaintInformation::DistanceInformationRegistrar r
= pi.registerDistanceInformation(&currentDistance);
timingInfo = updateTimingImpl(pi);
}
currentDistance.updateTiming(timingInfo);
}
KisTimingInformation KisPaintOp::updateTimingImpl(const KisPaintInformation &info) const
{
Q_UNUSED(info);
return KisTimingInformation();
}
KisPainter* KisPaintOp::painter() const
{
return d->painter;
}
KisPaintDeviceSP KisPaintOp::source() const
{
return d->painter->device();
}
diff --git a/libs/image/brushengine/kis_paintop.h b/libs/image/brushengine/kis_paintop.h
index e97639249b..f1365270a9 100644
--- a/libs/image/brushengine/kis_paintop.h
+++ b/libs/image/brushengine/kis_paintop.h
@@ -1,155 +1,165 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Clarence Dang <dang@kde.org>
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2004,2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PAINTOP_H_
#define KIS_PAINTOP_H_
#include <kis_distance_information.h>
#include "kis_shared.h"
#include "kis_types.h"
#include <kritaimage_export.h>
class QPointF;
class KoColorSpace;
class KisPainter;
class KisPaintInformation;
+class KisRunnableStrokeJobData;
/**
* KisPaintOp are use by tools to draw on a paint device. A paintop takes settings
* and input information, like pressure, tilt or motion and uses that to draw pixels
*/
class KRITAIMAGE_EXPORT KisPaintOp : public KisShared
{
struct Private;
public:
KisPaintOp(KisPainter * painter);
virtual ~KisPaintOp();
/**
* Paint at the subpixel point pos using the specified paint information..
*
* The distance/time between two calls of the paintAt is always specified by spacing and timing,
* which are automatically saved into the current distance information object.
*/
void paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance);
/**
* Updates the spacing settings in currentDistance based on the provided information. Note that
* the spacing is updated automatically in the paintAt method, so there is no need to call this
* method if paintAt has just been called.
*/
void updateSpacing(const KisPaintInformation &info, KisDistanceInformation &currentDistance)
const;
/**
* Updates the timing settings in currentDistance based on the provided information. Note that
* the timing is updated automatically in the paintAt method, so there is no need to call this
* method if paintAt has just been called.
*/
void updateTiming(const KisPaintInformation &info, KisDistanceInformation &currentDistance)
const;
/**
* Draw a line between pos1 and pos2 using the currently set brush and color.
* If savedDist is less than zero, the brush is painted at pos1 before being
* painted along the line using the spacing setting.
*
* @return the drag distance, that is the remains of the distance
* between p1 and p2 not covered because the currenlty set brush
* has a spacing greater than that distance.
*/
virtual void paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance);
/**
* Draw a Bezier curve between pos1 and pos2 using control points 1 and 2.
* If savedDist is less than zero, the brush is painted at pos1 before being
* painted along the curve using the spacing setting.
* @return the drag distance, that is the remains of the distance between p1 and p2 not covered
* because the currenlty set brush has a spacing greater than that distance.
*/
virtual void paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance);
/**
* Whether this paintop can paint. Can be false in case that some setting isn't read correctly.
* @return if paintop is ready for painting, default is true
*/
virtual bool canPaint() const {
return true;
}
/**
* Split the coordinate into whole + fraction, where fraction is always >= 0.
*/
static void splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction);
+ /**
+ * If the preset supports asynchronous updates, then the stroke execution core will
+ * call this method with a desured frame rate. The jobs that should be run to prepare the update
+ * are returned via \p jobs
+ *
+ * @return the desired FPS rate (period of updates)
+ */
+ virtual int doAsyncronousUpdate(QVector<KisRunnableStrokeJobData*> &jobs);
+
protected:
friend class KisPaintInformation;
/**
* The implementation of painting of a dab and updating spacing. This does NOT need to update
* the timing information.
*/
virtual KisSpacingInformation paintAt(const KisPaintInformation& info) = 0;
/**
* Implementation of a spacing update
*/
virtual KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const = 0;
/**
* Implementation of a timing update. The default implementation always disables timing. This is
* suitable for paintops that do not support airbrushing.
*/
virtual KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const;
KisFixedPaintDeviceSP cachedDab();
KisFixedPaintDeviceSP cachedDab(const KoColorSpace *cs);
/**
* Return the painter this paintop is owned by
*/
KisPainter* painter() const;
/**
* Return the paintdevice the painter this paintop is owned by
*/
KisPaintDeviceSP source() const;
private:
friend class KisPressureRotationOption;
void setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep);
private:
Private* const d;
};
#endif // KIS_PAINTOP_H_
diff --git a/libs/image/brushengine/kis_paintop_config_widget.cpp b/libs/image/brushengine/kis_paintop_config_widget.cpp
index e956181ff3..c220372b70 100644
--- a/libs/image/brushengine/kis_paintop_config_widget.cpp
+++ b/libs/image/brushengine/kis_paintop_config_widget.cpp
@@ -1,66 +1,66 @@
/*
* Copyright (c) 2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_paintop_config_widget.h"
#include <brushengine/kis_paintop_settings.h>
-KisPaintOpConfigWidget::KisPaintOpConfigWidget(QWidget * parent, Qt::WFlags f)
+KisPaintOpConfigWidget::KisPaintOpConfigWidget(QWidget * parent, Qt::WindowFlags f)
: KisConfigWidget(parent, f, 10),
m_isInsideUpdateCall(0)
{
}
KisPaintOpConfigWidget::~KisPaintOpConfigWidget() {
}
void KisPaintOpConfigWidget::writeConfigurationSafe(KisPropertiesConfigurationSP config) const
{
if (m_isInsideUpdateCall) return;
m_isInsideUpdateCall++;
writeConfiguration(config);
m_isInsideUpdateCall--;
}
void KisPaintOpConfigWidget::setConfigurationSafe(const KisPropertiesConfigurationSP config)
{
if (m_isInsideUpdateCall) return;
m_isInsideUpdateCall++;
setConfiguration(config);
m_isInsideUpdateCall--;
}
void KisPaintOpConfigWidget::setImage(KisImageWSP image) {
m_image = image;
}
void KisPaintOpConfigWidget::setNode(KisNodeWSP node) {
m_node = node;
}
bool KisPaintOpConfigWidget::presetIsValid() {
return true;
}
bool KisPaintOpConfigWidget::supportScratchBox() {
return true;
}
diff --git a/libs/image/brushengine/kis_paintop_config_widget.h b/libs/image/brushengine/kis_paintop_config_widget.h
index b4f209297b..6f9dd7c9cb 100644
--- a/libs/image/brushengine/kis_paintop_config_widget.h
+++ b/libs/image/brushengine/kis_paintop_config_widget.h
@@ -1,79 +1,79 @@
/*
* Copyright (c) 2008 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PAINTOP_CONFIG_WIDGET_H_
#define KIS_PAINTOP_CONFIG_WIDGET_H_
#include "kritaimage_export.h"
#include "kis_config_widget.h"
#include "kis_image.h"
#include <kis_debug.h>
#include <kis_properties_configuration.h>
class KisPaintopLodLimitations;
/**
* Base class for widgets that are used to edit and display paintop settings.
*/
class KRITAIMAGE_EXPORT KisPaintOpConfigWidget : public KisConfigWidget
{
Q_OBJECT
public:
- KisPaintOpConfigWidget(QWidget * parent = 0, Qt::WFlags f = 0);
+ KisPaintOpConfigWidget(QWidget * parent = 0, Qt::WindowFlags f = 0);
~KisPaintOpConfigWidget() override;
void writeConfigurationSafe(KisPropertiesConfigurationSP config) const;
void setConfigurationSafe(const KisPropertiesConfigurationSP config);
protected:
friend class CompositeOpModel;
void setConfiguration(const KisPropertiesConfigurationSP config) override = 0;
virtual void writeConfiguration(KisPropertiesConfigurationSP config) const = 0;
public:
virtual KisPaintopLodLimitations lodLimitations() const = 0;
virtual void setImage(KisImageWSP image);
virtual void setNode(KisNodeWSP node);
/**
* This is true for all of the paintop widget except for the Custom brush tab in the Brush tip dialog
*/
virtual bool presetIsValid();
/**
* Some paintops are more complicated and require full canvas with layers, projections and KisImage etc.
* Example is duplicate paintop. In this case simple canvas like scratchbox does not work.
* Every paintop supports the scratchbox by default, override and return false if paintop does not.
*/
virtual bool supportScratchBox();
protected:
KisImageWSP m_image;
KisNodeWSP m_node;
mutable int m_isInsideUpdateCall;
};
#endif
diff --git a/libs/image/brushengine/kis_paintop_settings.cpp b/libs/image/brushengine/kis_paintop_settings.cpp
index c6680e195a..6702f91497 100644
--- a/libs/image/brushengine/kis_paintop_settings.cpp
+++ b/libs/image/brushengine/kis_paintop_settings.cpp
@@ -1,456 +1,461 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2008 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2014 Mohit Goyal <mohit.bits2011@gmail.com>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <brushengine/kis_paintop_settings.h>
#include <QImage>
#include <QColor>
#include <QPointer>
#include <KoPointerEvent.h>
#include <KoColor.h>
#include <KoCompositeOpRegistry.h>
#include <KoViewConverter.h>
#include "kis_paint_layer.h"
#include "kis_image.h"
#include "kis_painter.h"
#include "kis_paint_device.h"
#include "kis_paintop_registry.h"
#include "kis_timing_information.h"
#include <brushengine/kis_paint_information.h>
#include "kis_paintop_config_widget.h"
#include <brushengine/kis_paintop_preset.h>
#include "kis_paintop_settings_update_proxy.h"
#include <time.h>
#include <kis_types.h>
#include <kis_signals_blocker.h>
#include <brushengine/kis_locked_properties_server.h>
#include <brushengine/kis_locked_properties_proxy.h>
struct Q_DECL_HIDDEN KisPaintOpSettings::Private {
Private()
: disableDirtyNotifications(false)
{}
QPointer<KisPaintOpConfigWidget> settingsWidget;
QString modelName;
KisPaintOpPresetWSP preset;
QList<KisUniformPaintOpPropertyWSP> uniformProperties;
bool disableDirtyNotifications;
class DirtyNotificationsLocker {
public:
DirtyNotificationsLocker(KisPaintOpSettings::Private *d)
: m_d(d),
m_oldNotificationsState(d->disableDirtyNotifications)
{
m_d->disableDirtyNotifications = true;
}
~DirtyNotificationsLocker() {
m_d->disableDirtyNotifications = m_oldNotificationsState;
}
private:
KisPaintOpSettings::Private *m_d;
bool m_oldNotificationsState;
Q_DISABLE_COPY(DirtyNotificationsLocker)
};
KisPaintopSettingsUpdateProxy* updateProxyNoCreate() const {
auto presetSP = preset.toStrongRef();
return presetSP ? presetSP->updateProxyNoCreate() : 0;
}
KisPaintopSettingsUpdateProxy* updateProxyCreate() const {
auto presetSP = preset.toStrongRef();
return presetSP ? presetSP->updateProxy() : 0;
}
};
KisPaintOpSettings::KisPaintOpSettings()
: d(new Private)
{
d->preset = 0;
}
KisPaintOpSettings::~KisPaintOpSettings()
{
}
KisPaintOpSettings::KisPaintOpSettings(const KisPaintOpSettings &rhs)
: KisPropertiesConfiguration(rhs)
, d(new Private)
{
d->settingsWidget = 0;
d->preset = rhs.preset();
d->modelName = rhs.modelName();
}
void KisPaintOpSettings::setOptionsWidget(KisPaintOpConfigWidget* widget)
{
d->settingsWidget = widget;
}
void KisPaintOpSettings::setPreset(KisPaintOpPresetWSP preset)
{
d->preset = preset;
}
KisPaintOpPresetWSP KisPaintOpSettings::preset() const
{
return d->preset;
}
bool KisPaintOpSettings::mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode)
{
Q_UNUSED(modifiers);
Q_UNUSED(currentNode);
setRandomOffset(paintInformation);
return true; // ignore the event by default
}
void KisPaintOpSettings::setRandomOffset(const KisPaintInformation &paintInformation)
{
if (getBool("Texture/Pattern/Enabled")) {
if (getBool("Texture/Pattern/isRandomOffsetX")) {
setProperty("Texture/Pattern/OffsetX",
paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetX")));
}
if (getBool("Texture/Pattern/isRandomOffsetY")) {
setProperty("Texture/Pattern/OffsetY",
paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetY")));
}
}
}
KisPaintOpSettingsSP KisPaintOpSettings::clone() const
{
QString paintopID = getString("paintop");
if (paintopID.isEmpty())
return 0;
KisPaintOpSettingsSP settings = KisPaintOpRegistry::instance()->settings(KoID(paintopID, ""));
QMapIterator<QString, QVariant> i(getProperties());
while (i.hasNext()) {
i.next();
settings->setProperty(i.key(), QVariant(i.value()));
}
settings->setPreset(this->preset());
return settings;
}
void KisPaintOpSettings::activate()
{
}
void KisPaintOpSettings::setPaintOpOpacity(qreal value)
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
proxy->setProperty("OpacityValue", value);
}
void KisPaintOpSettings::setPaintOpFlow(qreal value)
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
proxy->setProperty("FlowValue", value);
}
void KisPaintOpSettings::setPaintOpCompositeOp(const QString &value)
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
proxy->setProperty("CompositeOp", value);
}
qreal KisPaintOpSettings::paintOpOpacity()
{
KisLockedPropertiesProxySP proxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this);
return proxy->getDouble("OpacityValue", 1.0);
}
qreal KisPaintOpSettings::paintOpFlow()
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
return proxy->getDouble("FlowValue", 1.0);
}
QString KisPaintOpSettings::paintOpCompositeOp()
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
return proxy->getString("CompositeOp", COMPOSITE_OVER);
}
void KisPaintOpSettings::setEraserMode(bool value)
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
proxy->setProperty("EraserMode", value);
}
bool KisPaintOpSettings::eraserMode()
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
return proxy->getBool("EraserMode", false);
}
QString KisPaintOpSettings::effectivePaintOpCompositeOp()
{
return !eraserMode() ? paintOpCompositeOp() : COMPOSITE_ERASE;
}
qreal KisPaintOpSettings::savedEraserSize() const
{
return getDouble("SavedEraserSize", 0.0);
}
void KisPaintOpSettings::setSavedEraserSize(qreal value)
{
setProperty("SavedEraserSize", value);
setPropertyNotSaved("SavedEraserSize");
}
qreal KisPaintOpSettings::savedBrushSize() const
{
return getDouble("SavedBrushSize", 0.0);
}
void KisPaintOpSettings::setSavedBrushSize(qreal value)
{
setProperty("SavedBrushSize", value);
setPropertyNotSaved("SavedBrushSize");
}
qreal KisPaintOpSettings::savedEraserOpacity() const
{
return getDouble("SavedEraserOpacity", 0.0);
}
void KisPaintOpSettings::setSavedEraserOpacity(qreal value)
{
setProperty("SavedEraserOpacity", value);
setPropertyNotSaved("SavedEraserOpacity");
}
qreal KisPaintOpSettings::savedBrushOpacity() const
{
return getDouble("SavedBrushOpacity", 0.0);
}
void KisPaintOpSettings::setSavedBrushOpacity(qreal value)
{
setProperty("SavedBrushOpacity", value);
setPropertyNotSaved("SavedBrushOpacity");
}
QString KisPaintOpSettings::modelName() const
{
return d->modelName;
}
void KisPaintOpSettings::setModelName(const QString & modelName)
{
d->modelName = modelName;
}
KisPaintOpConfigWidget* KisPaintOpSettings::optionsWidget() const
{
if (d->settingsWidget.isNull())
return 0;
return d->settingsWidget.data();
}
bool KisPaintOpSettings::isValid() const
{
return true;
}
bool KisPaintOpSettings::isLoadable()
{
return isValid();
}
QString KisPaintOpSettings::indirectPaintingCompositeOp() const
{
return COMPOSITE_ALPHA_DARKEN;
}
bool KisPaintOpSettings::isAirbrushing() const
{
return getBool(AIRBRUSH_ENABLED, false);
}
qreal KisPaintOpSettings::airbrushInterval() const
{
qreal rate = getDouble(AIRBRUSH_RATE, 1.0);
if (rate == 0.0) {
return LONG_TIME;
}
else {
return 1000.0 / rate;
}
}
bool KisPaintOpSettings::useSpacingUpdates() const
{
return getBool(SPACING_USE_UPDATES, false);
}
+bool KisPaintOpSettings::needsAsynchronousUpdates() const
+{
+ return false;
+}
+
QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode)
{
QPainterPath path;
if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) {
path = ellipseOutline(10, 10, 1.0, 0);
if (mode == CursorTiltOutline) {
path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), 0.0, 2.0));
}
path.translate(info.pos());
}
return path;
}
QPainterPath KisPaintOpSettings::ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation)
{
QPainterPath path;
QRectF ellipse(0, 0, width * scale, height * scale);
ellipse.translate(-ellipse.center());
path.addEllipse(ellipse);
QTransform m;
m.reset();
m.rotate(rotation);
path = m.map(path);
return path;
}
QPainterPath KisPaintOpSettings::makeTiltIndicator(KisPaintInformation const& info,
QPointF const& start, qreal maxLength, qreal angle)
{
if (maxLength == 0.0) maxLength = 50.0;
maxLength = qMax(maxLength, 50.0);
qreal const length = maxLength * (1 - info.tiltElevation(info, 60.0, 60.0, true));
qreal const baseAngle = 360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0);
QLineF guideLine = QLineF::fromPolar(length, baseAngle + angle);
guideLine.translate(start);
QPainterPath ret;
ret.moveTo(guideLine.p1());
ret.lineTo(guideLine.p2());
guideLine.setAngle(baseAngle - angle);
ret.lineTo(guideLine.p2());
ret.lineTo(guideLine.p1());
return ret;
}
void KisPaintOpSettings::setCanvasRotation(qreal angle)
{
Private::DirtyNotificationsLocker locker(d.data());
setProperty("runtimeCanvasRotation", angle);
setPropertyNotSaved("runtimeCanvasRotation");
}
void KisPaintOpSettings::setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored)
{
Private::DirtyNotificationsLocker locker(d.data());
setProperty("runtimeCanvasMirroredX", xAxisMirrored);
setPropertyNotSaved("runtimeCanvasMirroredX");
setProperty("runtimeCanvasMirroredY", yAxisMirrored);
setPropertyNotSaved("runtimeCanvasMirroredY");
}
void KisPaintOpSettings::setProperty(const QString & name, const QVariant & value)
{
if (value != KisPropertiesConfiguration::getProperty(name) &&
!d->disableDirtyNotifications) {
KisPaintOpPresetSP presetSP = preset().toStrongRef();
if (presetSP) {
presetSP->setPresetDirty(true);
}
}
KisPropertiesConfiguration::setProperty(name, value);
onPropertyChanged();
}
void KisPaintOpSettings::onPropertyChanged()
{
KisPaintopSettingsUpdateProxy *proxy = d->updateProxyNoCreate();
if (proxy) {
proxy->notifySettingsChanged();
}
}
bool KisPaintOpSettings::isLodUserAllowed(const KisPropertiesConfigurationSP config)
{
return config->getBool("lodUserAllowed", true);
}
void KisPaintOpSettings::setLodUserAllowed(KisPropertiesConfigurationSP config, bool value)
{
config->setProperty("lodUserAllowed", value);
}
#include "kis_standard_uniform_properties_factory.h"
QList<KisUniformPaintOpPropertySP> KisPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings)
{
QList<KisUniformPaintOpPropertySP> props =
listWeakToStrong(d->uniformProperties);
if (props.isEmpty()) {
using namespace KisStandardUniformPropertiesFactory;
props.append(createProperty(opacity, settings, d->updateProxyCreate()));
props.append(createProperty(size, settings, d->updateProxyCreate()));
props.append(createProperty(flow, settings, d->updateProxyCreate()));
d->uniformProperties = listStrongToWeak(props);
}
return props;
}
diff --git a/libs/image/brushengine/kis_paintop_settings.h b/libs/image/brushengine/kis_paintop_settings.h
index 7ed5fb2e5c..bef12bea17 100644
--- a/libs/image/brushengine/kis_paintop_settings.h
+++ b/libs/image/brushengine/kis_paintop_settings.h
@@ -1,315 +1,321 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PAINTOP_SETTINGS_H_
#define KIS_PAINTOP_SETTINGS_H_
#include "kis_types.h"
#include "kritaimage_export.h"
#include <QImage>
#include <QScopedPointer>
#include "kis_properties_configuration.h"
#include <brushengine/kis_paint_information.h>
#include <brushengine/kis_uniform_paintop_property.h>
class KisPaintOpConfigWidget;
class KisPaintopSettingsUpdateProxy;
/**
* Configuration property used to control whether airbrushing is enabled.
*/
const QString AIRBRUSH_ENABLED = "PaintOpSettings/isAirbrushing";
/**
* Configuration property used to control airbrushing rate. The value should be in dabs per second.
*/
const QString AIRBRUSH_RATE = "PaintOpSettings/rate";
/**
* Configuration property used to control whether airbrushing is configured to ignore distance-based
* spacing.
*/
const QString AIRBRUSH_IGNORE_SPACING = "PaintOpSettings/ignoreSpacing";
/**
* Configuration property used to control whether the spacing settings can be updated between
* painted dabs.
*/
const QString SPACING_USE_UPDATES = "PaintOpSettings/updateSpacingBetweenDabs";
/**
* This class is used to cache the settings for a paintop
* between two creations. There is one KisPaintOpSettings per input device (mouse, tablet,
* etc...).
*
* The settings may be stored in a preset or a recorded brush stroke. Note that if your
* paintop's settings subclass has data that is not stored as a property, that data is not
* saved and restored.
*
* The object also contains a pointer to its parent KisPaintOpPreset object.This is to control the DirtyPreset
* property of KisPaintOpPreset. Whenever the settings are changed/modified from the original -- the preset is
* set to dirty.
*/
class KRITAIMAGE_EXPORT KisPaintOpSettings : public KisPropertiesConfiguration
{
public:
KisPaintOpSettings();
~KisPaintOpSettings() override;
KisPaintOpSettings(const KisPaintOpSettings &rhs);
/**
*
*/
virtual void setOptionsWidget(KisPaintOpConfigWidget* widget);
/**
* This function is called by a tool when the mouse is pressed. It's useful if
* the paintop needs mouse interaction for instance in the case of the clone op.
* If the tool is supposed to ignore the event, the paint op should return false
* and if the tool is supposed to use the event, return true.
*/
virtual bool mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode);
/**
* Clone the current settings object. Override this if your settings instance doesn't
* store everything as properties.
*/
virtual KisPaintOpSettingsSP clone() const;
/**
* @return the node the paintop is working on.
*/
KisNodeSP node() const;
/**
* Call this function when the paint op is selected or the tool is activated
*/
virtual void activate();
/**
* XXX: Remove this after 2.0, when the paint operation (incremental/non incremental) will
* be completely handled in the paintop, not in the tool. This is a filthy hack to move
* the option to the right place, at least.
* @return true if we paint incrementally, false if we paint like Photoshop. By default, paintops
* do not support non-incremental.
*/
virtual bool paintIncremental() {
return true;
}
/**
* @return the composite op it to which the indirect painting device
* should be initialized to. This is used by clone op to reset
* the composite op to COMPOSITE_COPY
*/
virtual QString indirectPaintingCompositeOp() const;
/**
* Whether this paintop wants to deposit paint even when not moving, i.e. the tool needs to
* activate its timer. If this is true, painting updates need to be generated at regular
* intervals even in the absence of input device events, e.g. when the cursor is not moving.
*
* The default implementation checks the property AIRBRUSH_ENABLED, defaulting to false if the
* property is not found. This should be suitable for most paintops.
*/
virtual bool isAirbrushing() const;
/**
* Indicates the minimum time interval that might be needed between airbrush dabs, in
* milliseconds. A lower value means painting updates need to happen more frequently. This value
* should be ignored if isAirbrushing() is false.
*
* The default implementation uses the property AIRBRUSH_RATE, defaulting to an interval of
* one second if the property is not found. This should be suitable for most paintops.
*/
virtual qreal airbrushInterval() const;
/**
* Indicates whether this configuration allows spacing information to be updated between painted
* dabs during a stroke.
*/
virtual bool useSpacingUpdates() const;
+ /**
+ * Indicates if the tool should call paintOp->doAsynchronousUpdate() inbetween
+ * paintAt() calls to do the asynchronous rendering
+ */
+ virtual bool needsAsynchronousUpdates() const;
+
/**
* This enum defines the current mode for painting an outline.
*/
enum OutlineMode {
CursorIsOutline = 1, ///< When this mode is set, an outline is painted around the cursor
CursorIsCircleOutline,
CursorNoOutline,
CursorTiltOutline,
CursorColorOutline
};
/**
* Returns the brush outline in pixel coordinates. Tool is responsible for conversion into view coordinates.
* Outline mode has to be passed to the paintop which builds the outline as some paintops have to paint outline
* always like clone paintop indicating the duplicate position
*/
virtual QPainterPath brushOutline(const KisPaintInformation &info, OutlineMode mode);
/**
* Helpers for drawing the brush outline
*/
static QPainterPath ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation);
/**
* Helper for drawing a triangle representing the tilt of the stylus.
*
* @param start is the offset from the brush's outline's bounding box
* @param lengthScale is used for deciding the size of the triangle.
* Brush diameter or width are common choices for this.
* @param angle is the angle between the two sides of the triangle.
*/
static QPainterPath makeTiltIndicator(KisPaintInformation const& info,
QPointF const& start, qreal lengthScale, qreal angle);
/**
* Set paintop opacity directly in the properties
*/
void setPaintOpOpacity(qreal value);
/**
* Set paintop flow directly in the properties
*/
void setPaintOpFlow(qreal value);
/**
* Set paintop composite mode directly in the properties
*/
void setPaintOpCompositeOp(const QString &value);
/**
* @return opacity saved in the properties
*/
qreal paintOpOpacity();
/**
* @return flow saved in the properties
*/
qreal paintOpFlow();
/**
* @return composite mode saved in the properties
*/
QString paintOpCompositeOp();
/**
* Set paintop size directly in the properties
*/
virtual void setPaintOpSize(qreal value) = 0;
/**
* @return size saved in the properties
*/
virtual qreal paintOpSize() const = 0;
void setEraserMode(bool value);
bool eraserMode();
qreal savedEraserSize() const;
void setSavedEraserSize(qreal value);
qreal savedBrushSize() const;
void setSavedBrushSize(qreal value);
qreal savedEraserOpacity() const;
void setSavedEraserOpacity(qreal value);
qreal savedBrushOpacity() const;
void setSavedBrushOpacity(qreal value);
QString effectivePaintOpCompositeOp();
void setPreset(KisPaintOpPresetWSP preset);
KisPaintOpPresetWSP preset() const;
/**
* @return filename of the 3D brush model, empty if no brush is set
*/
virtual QString modelName() const;
/**
* Set filename of 3D brush model. By default no brush is set
*/
void setModelName(const QString & modelName);
/// Check if the settings are valid, setting might be invalid through missing brushes etc
/// Overwrite if the settings of a paintop can be invalid
/// @return state of the settings, default implementation is true
virtual bool isValid() const;
/// Check if the settings are loadable, that might the case if we can fallback to something
/// Overwrite if the settings can do some kind of fallback
/// @return loadable state of the settings, by default implementation return the same as isValid()
virtual bool isLoadable();
/**
* These methods are populating properties with runtime
* information about canvas rotation/mirroring. This information
* is set directly by KisToolFreehand. Later the data is accessed
* by the pressure options to make a final decision.
*/
void setCanvasRotation(qreal angle);
void setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored);
/**
* Overrides the method in KisPropertiesCofiguration to allow
* onPropertyChanged() callback
*/
void setProperty(const QString & name, const QVariant & value) override;
virtual QList<KisUniformPaintOpPropertySP> uniformProperties(KisPaintOpSettingsSP settings);
static bool isLodUserAllowed(const KisPropertiesConfigurationSP config);
static void setLodUserAllowed(KisPropertiesConfigurationSP config, bool value);
/**
* @return the option widget of the paintop (can be 0 is no option widgets is set)
*/
KisPaintOpConfigWidget* optionsWidget() const;
/**
* This function is called to set random offsets to the brush whenever the mouse is clicked. It is
* specific to when the pattern option is set.
*
*/
virtual void setRandomOffset(const KisPaintInformation &paintInformation);
protected:
/**
* The callback is called every time when a property changes
*/
virtual void onPropertyChanged();
private:
struct Private;
const QScopedPointer<Private> d;
};
#endif
diff --git a/libs/image/brushengine/kis_paintop_utils.cpp b/libs/image/brushengine/kis_paintop_utils.cpp
new file mode 100644
index 0000000000..b78833b9ab
--- /dev/null
+++ b/libs/image/brushengine/kis_paintop_utils.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "kis_paintop_utils.h"
+
+#include "krita_utils.h"
+#include "krita_container_utils.h"
+#include <KisRenderedDab.h>
+
+namespace KisPaintOpUtils {
+
+
+KisSpacingInformation effectiveSpacing(qreal dabWidth, qreal dabHeight, qreal extraScale, bool distanceSpacingEnabled, bool isotropicSpacing, qreal rotation, bool axesFlipped, qreal spacingVal, bool autoSpacingActive, qreal autoSpacingCoeff, qreal lodScale)
+{
+ QPointF spacing;
+
+ if (!isotropicSpacing) {
+ if (autoSpacingActive) {
+ spacing = calcAutoSpacing(QPointF(dabWidth, dabHeight), autoSpacingCoeff, lodScale);
+ } else {
+ spacing = QPointF(dabWidth, dabHeight);
+ spacing *= spacingVal;
+ }
+ }
+ else {
+ qreal significantDimension = qMax(dabWidth, dabHeight);
+ if (autoSpacingActive) {
+ significantDimension = calcAutoSpacing(significantDimension, autoSpacingCoeff);
+ } else {
+ significantDimension *= spacingVal;
+ }
+ spacing = QPointF(significantDimension, significantDimension);
+ rotation = 0.0;
+ axesFlipped = false;
+ }
+
+ spacing *= extraScale;
+
+ return KisSpacingInformation(distanceSpacingEnabled, spacing, rotation, axesFlipped);
+}
+
+KisTimingInformation effectiveTiming(bool timingEnabled, qreal timingInterval, qreal rateExtraScale)
+{
+
+ if (!timingEnabled) {
+ return KisTimingInformation();
+ }
+ else {
+ qreal scaledInterval = rateExtraScale <= 0.0 ? LONG_TIME : timingInterval / rateExtraScale;
+ return KisTimingInformation(scaledInterval);
+ }
+}
+
+QVector<QRect> splitAndFilterDabRect(const QRect &totalRect, const QList<KisRenderedDab> &dabs, int idealPatchSize)
+{
+ QVector<QRect> rects = KritaUtils::splitRectIntoPatches(totalRect, QSize(idealPatchSize,idealPatchSize));
+
+ KritaUtils::filterContainer(rects,
+ [dabs] (const QRect &rc) {
+ Q_FOREACH (const KisRenderedDab &dab, dabs) {
+ if (dab.realBounds().intersects(rc)) {
+ return true;
+ }
+ }
+ return false;
+ });
+ return rects;
+}
+
+QVector<QRect> splitDabsIntoRects(const QList<KisRenderedDab> &dabs, int idealNumRects, int diameter, qreal spacing)
+{
+ QRect totalRect;
+
+ Q_FOREACH (const KisRenderedDab &dab, dabs) {
+ const QRect rc = dab.realBounds();
+ totalRect |= rc;
+ }
+
+ constexpr int minPatchSize = 128;
+ constexpr int maxPatchSize = 512;
+ constexpr int patchStep = 64;
+ constexpr int halfPatchStep= patchStep >> 1;
+
+
+ int idealPatchSize = qBound(minPatchSize,
+ (int(diameter * (2.0 - spacing)) + halfPatchStep) & ~(patchStep - 1),
+ maxPatchSize);
+
+
+ QVector<QRect> rects = splitAndFilterDabRect(totalRect, dabs, idealPatchSize);
+
+ while (rects.size() < idealNumRects && idealPatchSize >minPatchSize) {
+ idealPatchSize = qMax(minPatchSize, idealPatchSize - patchStep);
+ rects = splitAndFilterDabRect(totalRect, dabs, idealPatchSize);
+ }
+
+ return rects;
+}
+
+
+
+}
diff --git a/libs/image/brushengine/kis_paintop_utils.h b/libs/image/brushengine/kis_paintop_utils.h
index ca53683483..2e1acc84ea 100644
--- a/libs/image/brushengine/kis_paintop_utils.h
+++ b/libs/image/brushengine/kis_paintop_utils.h
@@ -1,228 +1,203 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_PAINTOP_UTILS_H
#define __KIS_PAINTOP_UTILS_H
#include "kis_global.h"
#include "kis_paint_information.h"
#include "kis_distance_information.h"
#include "kis_spacing_information.h"
#include "kis_timing_information.h"
+#include "kritaimage_export.h"
+
+class KisRenderedDab;
+
namespace KisPaintOpUtils {
template <class PaintOp>
bool paintFan(PaintOp &op,
const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance,
qreal fanCornersStep)
{
const qreal angleStep = fanCornersStep;
const qreal initialAngle = currentDistance->lastDrawingAngle();
const qreal finalAngle = pi2.drawingAngleSafe(*currentDistance);
const qreal fullDistance = shortestAngularDistance(initialAngle,
finalAngle);
qreal lastAngle = initialAngle;
int i = 0;
while (shortestAngularDistance(lastAngle, finalAngle) > angleStep) {
lastAngle = incrementInDirection(lastAngle, angleStep, finalAngle);
qreal t = angleStep * i++ / fullDistance;
QPointF pt = pi1.pos() + t * (pi2.pos() - pi1.pos());
KisPaintInformation pi = KisPaintInformation::mix(pt, t, pi1, pi2);
pi.overrideDrawingAngle(lastAngle);
pi.paintAt(op, currentDistance);
}
return i;
}
template <class PaintOp>
void paintLine(PaintOp &op,
const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance,
bool fanCornersEnabled,
qreal fanCornersStep)
{
QPointF end = pi2.pos();
qreal endTime = pi2.currentTime();
KisPaintInformation pi = pi1;
qreal t = 0.0;
while ((t = currentDistance->getNextPointPosition(pi.pos(), end, pi.currentTime(), endTime)) >= 0.0) {
pi = KisPaintInformation::mix(t, pi, pi2);
if (fanCornersEnabled &&
currentDistance->hasLastPaintInformation()) {
paintFan(op,
currentDistance->lastPaintInformation(),
pi,
currentDistance,
fanCornersStep);
}
/**
* A bit complicated part to ensure the registration
* of the distance information is done in right order
*/
pi.paintAt(op, currentDistance);
}
/*
* Perform spacing and/or timing updates between dabs if appropriate. Typically, this will not
* happen if the above loop actually painted anything. This is because the
* getNextPointPosition() call before the paint operation will reset the accumulators in
* currentDistance and therefore make needsSpacingUpdate() and needsTimingUpdate() false. The
* temporal distance between pi1 and pi2 is typically too small for the accumulators to build
* back up enough to require a spacing or timing update after that. (The accumulated time values
* are updated not during the paint operation, but during the call to getNextPointPosition(),
* that is, updated during every paintLine() call.)
*/
if (currentDistance->needsSpacingUpdate()) {
op.updateSpacing(pi2, *currentDistance);
}
if (currentDistance->needsTimingUpdate()) {
op.updateTiming(pi2, *currentDistance);
}
}
/**
* A special class containing the previous position of the cursor for
* the sake of painting the outline of the paint op. The main purpose
* of this class is to ensure that the saved point does not equal to
* the current one, which would cause a outline flicker. To echieve
* this the class stores two previosly requested points instead of the
* last one.
*/
-class PositionHistory
+class KRITAIMAGE_EXPORT PositionHistory
{
public:
/**
* \return the previously used point, which is guaranteed not to
* be equal to \p pt and updates the history if needed
*/
QPointF pushThroughHistory(const QPointF &pt) {
QPointF result;
const qreal pointSwapThreshold = 7.0;
/**
* We check x *and* y separately, because events generated by
* a mouse device tend to come separately for x and y offsets.
* Efficienty generating the 'stairs' pattern.
*/
if (qAbs(pt.x() - m_second.x()) > pointSwapThreshold &&
qAbs(pt.y() - m_second.y()) > pointSwapThreshold) {
result = m_second;
m_first = m_second;
m_second = pt;
} else {
result = m_first;
}
return result;
}
private:
QPointF m_first;
QPointF m_second;
};
-bool checkSizeTooSmall(qreal scale, qreal width, qreal height)
+inline bool checkSizeTooSmall(qreal scale, qreal width, qreal height)
{
return scale * width < 0.01 || scale * height < 0.01;
}
inline qreal calcAutoSpacing(qreal value, qreal coeff)
{
return coeff * (value < 1.0 ? value : sqrt(value));
}
-QPointF calcAutoSpacing(const QPointF &pt, qreal coeff, qreal lodScale)
+inline QPointF calcAutoSpacing(const QPointF &pt, qreal coeff, qreal lodScale)
{
const qreal invLodScale = 1.0 / lodScale;
const QPointF lod0Point = invLodScale * pt;
return lodScale * QPointF(calcAutoSpacing(lod0Point.x(), coeff), calcAutoSpacing(lod0Point.y(), coeff));
}
+KRITAIMAGE_EXPORT
KisSpacingInformation effectiveSpacing(qreal dabWidth,
qreal dabHeight,
qreal extraScale,
bool distanceSpacingEnabled,
bool isotropicSpacing,
qreal rotation,
bool axesFlipped,
qreal spacingVal,
bool autoSpacingActive,
qreal autoSpacingCoeff,
- qreal lodScale)
-{
- QPointF spacing;
-
- if (!isotropicSpacing) {
- if (autoSpacingActive) {
- spacing = calcAutoSpacing(QPointF(dabWidth, dabHeight), autoSpacingCoeff, lodScale);
- } else {
- spacing = QPointF(dabWidth, dabHeight);
- spacing *= spacingVal;
- }
- }
- else {
- qreal significantDimension = qMax(dabWidth, dabHeight);
- if (autoSpacingActive) {
- significantDimension = calcAutoSpacing(significantDimension, autoSpacingCoeff);
- } else {
- significantDimension *= spacingVal;
- }
- spacing = QPointF(significantDimension, significantDimension);
- rotation = 0.0;
- axesFlipped = false;
- }
-
- spacing *= extraScale;
-
- return KisSpacingInformation(distanceSpacingEnabled, spacing, rotation, axesFlipped);
-}
+ qreal lodScale);
+KRITAIMAGE_EXPORT
KisTimingInformation effectiveTiming(bool timingEnabled,
qreal timingInterval,
- qreal rateExtraScale)
-{
+ qreal rateExtraScale);
- if (!timingEnabled) {
- return KisTimingInformation();
- }
- else {
- qreal scaledInterval = rateExtraScale <= 0.0 ? LONG_TIME : timingInterval / rateExtraScale;
- return KisTimingInformation(scaledInterval);
- }
-}
+KRITAIMAGE_EXPORT
+QVector<QRect> splitAndFilterDabRect(const QRect &totalRect, const QList<KisRenderedDab> &dabs, int idealPatchSize);
+
+KRITAIMAGE_EXPORT
+QVector<QRect> splitDabsIntoRects(const QList<KisRenderedDab> &dabs, int idealNumRects, int diameter, qreal spacing);
}
#endif /* __KIS_PAINTOP_UTILS_H */
diff --git a/libs/image/brushengine/kis_random_source.cpp b/libs/image/brushengine/kis_random_source.cpp
index 8a17fecc46..9b28ed3512 100644
--- a/libs/image/brushengine/kis_random_source.cpp
+++ b/libs/image/brushengine/kis_random_source.cpp
@@ -1,96 +1,97 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_random_source.h"
#include <boost/random/taus88.hpp>
#include <boost/random/uniform_smallint.hpp>
#include <boost/random/normal_distribution.hpp>
struct KisRandomSource::Private
{
Private()
: uniformSource(qrand()) {}
Private(int seed)
: uniformSource(seed) {}
/**
* Taus88's numbers are not too random, but it works fast and it
* can be copied very quickly (three 32-bit integers only).
*
* Average cycle: 2^88 steps
*/
boost::taus88 uniformSource;
};
KisRandomSource::KisRandomSource()
: m_d(new Private)
{
}
KisRandomSource::KisRandomSource(int seed)
: m_d(new Private(seed))
{
}
KisRandomSource::KisRandomSource(const KisRandomSource &rhs)
: KisShared(),
m_d(new Private(*rhs.m_d))
{
}
KisRandomSource& KisRandomSource::operator=(const KisRandomSource &rhs)
{
if (this != &rhs) {
*m_d = *rhs.m_d;
}
return *this;
}
KisRandomSource::~KisRandomSource()
{
}
qint64 KisRandomSource::generate() const
{
return m_d->uniformSource();
}
int KisRandomSource::generate(int min, int max) const
{
boost::uniform_smallint<int> smallint(min, max);
return smallint(m_d->uniformSource);
}
qreal KisRandomSource::generateNormalized() const
{
const qint64 v = m_d->uniformSource();
const qint64 max = m_d->uniformSource.max();
+ // we don't have min, because taus88 is always positive
return qreal(v) / max;
}
qreal KisRandomSource::generateGaussian(qreal mean, qreal sigma) const
{
boost::normal_distribution<qreal> normal(mean, sigma);
return normal(m_d->uniformSource);
}
diff --git a/libs/image/brushengine/kis_stroke_random_source.cpp b/libs/image/brushengine/kis_stroke_random_source.cpp
index ce88e3018d..4bdeef23c1 100644
--- a/libs/image/brushengine/kis_stroke_random_source.cpp
+++ b/libs/image/brushengine/kis_stroke_random_source.cpp
@@ -1,73 +1,83 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_stroke_random_source.h"
struct KisStrokeRandomSource::Private
{
Private()
: levelOfDetail(0),
lod0RandomSource(new KisRandomSource()),
- lodNRandomSource(new KisRandomSource(*lod0RandomSource))
+ lodNRandomSource(new KisRandomSource(*lod0RandomSource)),
+ lod0PerStrokeRandomSource(new KisPerStrokeRandomSource()),
+ lodNPerStrokeRandomSource(new KisPerStrokeRandomSource(*lod0PerStrokeRandomSource))
{
}
int levelOfDetail;
KisRandomSourceSP lod0RandomSource;
KisRandomSourceSP lodNRandomSource;
+
+ KisPerStrokeRandomSourceSP lod0PerStrokeRandomSource;
+ KisPerStrokeRandomSourceSP lodNPerStrokeRandomSource;
};
KisStrokeRandomSource::KisStrokeRandomSource()
: m_d(new Private)
{
}
KisStrokeRandomSource::KisStrokeRandomSource(const KisStrokeRandomSource &rhs)
: m_d(new Private(*rhs.m_d))
{
}
KisStrokeRandomSource& KisStrokeRandomSource::operator=(const KisStrokeRandomSource &rhs)
{
if (&rhs != this) {
*m_d = *rhs.m_d;
}
return *this;
}
KisStrokeRandomSource::~KisStrokeRandomSource()
{
}
KisRandomSourceSP KisStrokeRandomSource::source() const
{
return m_d->levelOfDetail ? m_d->lodNRandomSource : m_d->lod0RandomSource;
}
+KisPerStrokeRandomSourceSP KisStrokeRandomSource::perStrokeSource() const
+{
+ return m_d->levelOfDetail ? m_d->lodNPerStrokeRandomSource : m_d->lod0PerStrokeRandomSource;
+}
+
int KisStrokeRandomSource::levelOfDetail() const
{
return m_d->levelOfDetail;
}
void KisStrokeRandomSource::setLevelOfDetail(int value)
{
m_d->levelOfDetail = value;
}
diff --git a/libs/image/brushengine/kis_stroke_random_source.h b/libs/image/brushengine/kis_stroke_random_source.h
index bbc4bd1fd2..c6484351b6 100644
--- a/libs/image/brushengine/kis_stroke_random_source.h
+++ b/libs/image/brushengine/kis_stroke_random_source.h
@@ -1,54 +1,56 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_STROKE_RANDOM_SOURCE_H
#define __KIS_STROKE_RANDOM_SOURCE_H
#include <QScopedPointer>
#include "kritaimage_export.h"
#include "kis_random_source.h"
+#include "KisPerStrokeRandomSource.h"
/**
* A helper class to handle multiple KisRandomSource objects in a
* stroke strategies. It creates two identical random sources in the
* beginning of the stroke, so, when copied through copy-ctor and set
* another level of detail starts returning the same sequence of
* numbers as was returned for the first stroke.
*/
class KRITAIMAGE_EXPORT KisStrokeRandomSource
{
public:
KisStrokeRandomSource();
KisStrokeRandomSource(const KisStrokeRandomSource &rhs);
KisStrokeRandomSource& operator=(const KisStrokeRandomSource &rhs);
~KisStrokeRandomSource();
KisRandomSourceSP source() const;
+ KisPerStrokeRandomSourceSP perStrokeSource() const;
int levelOfDetail() const;
void setLevelOfDetail(int value);
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_STROKE_RANDOM_SOURCE_H */
diff --git a/libs/image/generator/kis_generator_layer.cpp b/libs/image/generator/kis_generator_layer.cpp
index 66a68f52ed..6faf9a7272 100644
--- a/libs/image/generator/kis_generator_layer.cpp
+++ b/libs/image/generator/kis_generator_layer.cpp
@@ -1,167 +1,167 @@
/*
* Copyright (c) 2008 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_generator_layer.h"
#include <klocalizedstring.h>
#include "kis_debug.h"
#include <KoIcon.h>
#include <kis_icon.h>
#include "kis_selection.h"
#include "filter/kis_filter_configuration.h"
#include "kis_processing_information.h"
#include "generator/kis_generator_registry.h"
#include "generator/kis_generator.h"
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_thread_safe_signal_compressor.h"
#include "kis_recalculate_generator_layer_job.h"
#define UPDATE_DELAY 100 /*ms */
struct Q_DECL_HIDDEN KisGeneratorLayer::Private
{
Private()
: updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::FIRST_INACTIVE)
{
}
KisThreadSafeSignalCompressor updateSignalCompressor;
};
KisGeneratorLayer::KisGeneratorLayer(KisImageWSP image,
const QString &name,
KisFilterConfigurationSP kfc,
KisSelectionSP selection)
: KisSelectionBasedLayer(image, name, selection, kfc, true),
m_d(new Private)
{
connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate()));
update();
}
KisGeneratorLayer::KisGeneratorLayer(const KisGeneratorLayer& rhs)
: KisSelectionBasedLayer(rhs),
m_d(new Private)
{
connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate()));
}
KisGeneratorLayer::~KisGeneratorLayer()
{
}
void KisGeneratorLayer::setFilter(KisFilterConfigurationSP filterConfig)
{
KisSelectionBasedLayer::setFilter(filterConfig);
update();
}
void KisGeneratorLayer::slotDelayedStaticUpdate()
{
/**
* The mask might have been deleted from the layers stack in the
* meanwhile. Just ignore the updates in the case.
*/
KisLayerSP parentLayer(qobject_cast<KisLayer*>(parent().data()));
if (!parentLayer) return;
KisImageSP image = parentLayer->image();
if (image) {
image->addSpontaneousJob(new KisRecalculateGeneratorLayerJob(KisGeneratorLayerSP(this)));
}
}
void KisGeneratorLayer::update()
{
KisFilterConfigurationSP filterConfig = filter();
if (!filterConfig) {
warnImage << "BUG: No Filter configuration in KisGeneratorLayer";
return;
}
KisGeneratorSP f = KisGeneratorRegistry::instance()->value(filterConfig->name());
if (!f) return;
QRect processRect = exactBounds();
resetCache();
KisPaintDeviceSP originalDevice = original();
KisProcessingInformation dstCfg(originalDevice,
processRect.topLeft(),
KisSelectionSP());
f->generate(dstCfg, processRect.size(), filterConfig.data());
// hack alert!
// this avoids cyclic loop with KisRecalculateGeneratorLayerJob::run()
KisSelectionBasedLayer::setDirty(extent());
}
bool KisGeneratorLayer::accept(KisNodeVisitor & v)
{
return v.visit(this);
}
void KisGeneratorLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
QIcon KisGeneratorLayer::icon() const
{
return KisIconUtils::loadIcon("fillLayer");
}
KisBaseNode::PropertyList KisGeneratorLayer::sectionModelProperties() const
{
KisFilterConfigurationSP filterConfig = filter();
KisBaseNode::PropertyList l = KisLayer::sectionModelProperties();
l << KisBaseNode::Property(KoID("generator", i18n("Generator")),
KisGeneratorRegistry::instance()->value(filterConfig->name())->name());
return l;
}
void KisGeneratorLayer::setX(qint32 x)
{
KisSelectionBasedLayer::setX(x);
m_d->updateSignalCompressor.start();
}
void KisGeneratorLayer::setY(qint32 y)
{
KisSelectionBasedLayer::setY(y);
m_d->updateSignalCompressor.start();
}
-void KisGeneratorLayer::setDirty(const QRect & rect)
+void KisGeneratorLayer::setDirty(const QVector<QRect> &rects)
{
- KisSelectionBasedLayer::setDirty(rect);
+ KisSelectionBasedLayer::setDirty(rects);
m_d->updateSignalCompressor.start();
}
diff --git a/libs/image/generator/kis_generator_layer.h b/libs/image/generator/kis_generator_layer.h
index eab61dabf4..757b464d52 100644
--- a/libs/image/generator/kis_generator_layer.h
+++ b/libs/image/generator/kis_generator_layer.h
@@ -1,90 +1,90 @@
/*
* Copyright (c) 2008 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_GENERATOR_LAYER_H_
#define KIS_GENERATOR_LAYER_H_
#include "kis_selection_based_layer.h"
#include <kritaimage_export.h>
#include <QScopedPointer>
class KisFilterConfiguration;
/**
* A generator layer is a special kind of layer that can be prefilled
* with some pixel pattern generated by a KisGenerator plugin.
* A KisGenerator is similar to a filter, but doesn't take
* input pixel data and creates new pixel data.
*
* It is not possible to destructively paint on a generator layer.
*
* XXX: what about threadedness?
*/
class KRITAIMAGE_EXPORT KisGeneratorLayer : public KisSelectionBasedLayer
{
Q_OBJECT
public:
/**
* Create a new Generator layer with the given configuration
* and selection. Note that the selection will be _copied_
* (using COW, though).
*/
KisGeneratorLayer(KisImageWSP image, const QString &name, KisFilterConfigurationSP kfc, KisSelectionSP selection);
KisGeneratorLayer(const KisGeneratorLayer& rhs);
~KisGeneratorLayer() override;
KisNodeSP clone() const override {
return KisNodeSP(new KisGeneratorLayer(*this));
}
void setFilter(KisFilterConfigurationSP filterConfig) override;
bool accept(KisNodeVisitor &) override;
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override;
QIcon icon() const override;
KisBaseNode::PropertyList sectionModelProperties() const override;
/**
* re-run the generator. This happens over the bounds
* of the associated selection.
*/
void update();
using KisSelectionBasedLayer::setDirty;
- void setDirty(const QRect & rect) override;
+ void setDirty(const QVector<QRect> &rects) override;
void setX(qint32 x) override;
void setY(qint32 y) override;
private Q_SLOTS:
void slotDelayedStaticUpdate();
public:
// KisIndirectPaintingSupport
KisLayer* layer() {
return this;
}
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif
diff --git a/libs/image/kis_brush_mask_applicators.h b/libs/image/kis_brush_mask_applicators.h
index 4c1a15494e..4ecd348da9 100644
--- a/libs/image/kis_brush_mask_applicators.h
+++ b/libs/image/kis_brush_mask_applicators.h
@@ -1,214 +1,202 @@
/*
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_BRUSH_MASK_APPLICATORS_H
#define __KIS_BRUSH_MASK_APPLICATORS_H
#include "kis_brush_mask_applicator_base.h"
#include "kis_global.h"
+#include "kis_random_source.h"
// 3x3 supersampling
#define SUPERSAMPLING 3
-#if defined(_WIN32) || defined(_WIN64)
-#include <stdlib.h>
-#define srand48 srand
-inline double drand48() {
- return double(rand()) / RAND_MAX;
-}
-#endif
-
-#include <random>
-
template<class MaskGenerator, Vc::Implementation _impl>
struct KisBrushMaskScalarApplicator : public KisBrushMaskApplicatorBase
{
KisBrushMaskScalarApplicator(MaskGenerator *maskGenerator)
: m_maskGenerator(maskGenerator)
{
}
void process(const QRect &rect) override {
processScalar(rect);
}
protected:
void processScalar(const QRect &rect);
protected:
MaskGenerator *m_maskGenerator;
- std::random_device m_rand_dev;
+ KisRandomSource m_randomSource; // TODO: make it more deterministic for LoD
};
#if defined HAVE_VC
template<class MaskGenerator, Vc::Implementation _impl>
struct KisBrushMaskVectorApplicator : public KisBrushMaskScalarApplicator<MaskGenerator, _impl>
{
KisBrushMaskVectorApplicator(MaskGenerator *maskGenerator)
: KisBrushMaskScalarApplicator<MaskGenerator, _impl>(maskGenerator)
{
}
void process(const QRect &rect) {
startProcessing(rect, TypeHelper<MaskGenerator, _impl>());
}
protected:
void processVector(const QRect &rect);
private:
template<class U, Vc::Implementation V> struct TypeHelper {};
private:
template<class U>
inline void startProcessing(const QRect &rect, TypeHelper<U, Vc::ScalarImpl>) {
KisBrushMaskScalarApplicator<MaskGenerator, _impl>::processScalar(rect);
}
template<class U, Vc::Implementation V>
inline void startProcessing(const QRect &rect, TypeHelper<U, V>) {
MaskGenerator *m_maskGenerator = KisBrushMaskScalarApplicator<MaskGenerator, _impl>::m_maskGenerator;
if (m_maskGenerator->shouldVectorize()) {
processVector(rect);
} else {
KisBrushMaskScalarApplicator<MaskGenerator, _impl>::processScalar(rect);
}
}
};
template<class MaskGenerator, Vc::Implementation _impl>
void KisBrushMaskVectorApplicator<MaskGenerator, _impl>::processVector(const QRect &rect)
{
const MaskProcessingData *m_d = KisBrushMaskApplicatorBase::m_d;
MaskGenerator *m_maskGenerator = KisBrushMaskScalarApplicator<MaskGenerator, _impl>::m_maskGenerator;
qreal random = 1.0;
quint8* dabPointer = m_d->device->data() + rect.y() * rect.width() * m_d->pixelSize;
quint8 alphaValue = OPACITY_TRANSPARENT_U8;
// this offset is needed when brush size is smaller then fixed device size
int offset = (m_d->device->bounds().width() - rect.width()) * m_d->pixelSize;
int width = rect.width();
// We need to calculate with a multiple of the width of the simd register
int alignOffset = 0;
if (width % Vc::float_v::size() != 0) {
alignOffset = Vc::float_v::size() - (width % Vc::float_v::size());
}
int simdWidth = width + alignOffset;
float *buffer = Vc::malloc<float, Vc::AlignOnCacheline>(simdWidth);
typename MaskGenerator::FastRowProcessor processor(m_maskGenerator);
for (int y = rect.y(); y < rect.y() + rect.height(); y++) {
processor.template process<_impl>(buffer, simdWidth, y, m_d->cosa, m_d->sina, m_d->centerX, m_d->centerY);
if (m_d->randomness != 0.0 || m_d->density != 1.0) {
for (int x = 0; x < width; x++) {
if (m_d->randomness!= 0.0){
- random = (1.0 - m_d->randomness) + m_d->randomness * float(rand()) / RAND_MAX;
+ random = (1.0 - m_d->randomness) + m_d->randomness * KisBrushMaskScalarApplicator<MaskGenerator, _impl>::m_randomSource.generateNormalized();
}
alphaValue = quint8( (OPACITY_OPAQUE_U8 - buffer[x]*255) * random);
// avoid computation of random numbers if density is full
if (m_d->density != 1.0){
// compute density only for visible pixels of the mask
if (alphaValue != OPACITY_TRANSPARENT_U8){
- if ( !(m_d->density >= drand48()) ){
+ if ( !(m_d->density >= KisBrushMaskScalarApplicator<MaskGenerator, _impl>::m_randomSource.generateNormalized()) ){
alphaValue = OPACITY_TRANSPARENT_U8;
}
}
}
m_d->colorSpace->applyAlphaU8Mask(dabPointer, &alphaValue, 1);
dabPointer += m_d->pixelSize;
}
} else {
m_d->colorSpace->applyInverseNormedFloatMask(dabPointer, buffer, width);
dabPointer += width * m_d->pixelSize;
}//endfor x
dabPointer += offset;
}//endfor y
Vc::free(buffer);
}
#endif /* defined HAVE_VC */
template<class MaskGenerator, Vc::Implementation _impl>
void KisBrushMaskScalarApplicator<MaskGenerator, _impl>::processScalar(const QRect &rect)
{
const MaskProcessingData *m_d = KisBrushMaskApplicatorBase::m_d;
MaskGenerator *m_maskGenerator = KisBrushMaskScalarApplicator<MaskGenerator, _impl>::m_maskGenerator;
- std::default_random_engine rand_engine{m_rand_dev()};
- std::uniform_real_distribution<> rand_distr(0.0f, 1.0f);
-
qreal random = 1.0;
quint8* dabPointer = m_d->device->data() + rect.y() * rect.width() * m_d->pixelSize;
quint8 alphaValue = OPACITY_TRANSPARENT_U8;
// this offset is needed when brush size is smaller then fixed device size
int offset = (m_d->device->bounds().width() - rect.width()) * m_d->pixelSize;
int supersample = (m_maskGenerator->shouldSupersample() ? SUPERSAMPLING : 1);
double invss = 1.0 / supersample;
int samplearea = pow2(supersample);
for (int y = rect.y(); y < rect.y() + rect.height(); y++) {
for (int x = rect.x(); x < rect.x() + rect.width(); x++) {
int value = 0;
for (int sy = 0; sy < supersample; sy++) {
for (int sx = 0; sx < supersample; sx++) {
double x_ = x + sx * invss - m_d->centerX;
double y_ = y + sy * invss - m_d->centerY;
double maskX = m_d->cosa * x_ - m_d->sina * y_;
double maskY = m_d->sina * x_ + m_d->cosa * y_;
value += m_maskGenerator->valueAt(maskX, maskY);
}
}
if (supersample != 1) value /= samplearea;
if (m_d->randomness!= 0.0){
- random = (1.0 - m_d->randomness) + m_d->randomness * rand_distr(rand_engine);
+ random = (1.0 - m_d->randomness) + m_d->randomness * m_randomSource.generateNormalized();
}
alphaValue = quint8( (OPACITY_OPAQUE_U8 - value) * random);
// avoid computation of random numbers if density is full
if (m_d->density != 1.0){
// compute density only for visible pixels of the mask
if (alphaValue != OPACITY_TRANSPARENT_U8){
- if ( !(m_d->density >= rand_distr(rand_engine)) ){
+ if ( !(m_d->density >= m_randomSource.generateNormalized()) ){
alphaValue = OPACITY_TRANSPARENT_U8;
}
}
}
m_d->colorSpace->applyAlphaU8Mask(dabPointer, &alphaValue, 1);
dabPointer += m_d->pixelSize;
}//endfor x
dabPointer += offset;
}//endfor y
}
#endif /* __KIS_BRUSH_MASK_APPLICATORS_H */
diff --git a/libs/image/kis_config_widget.cpp b/libs/image/kis_config_widget.cpp
index 336672acdc..88e47a4163 100644
--- a/libs/image/kis_config_widget.cpp
+++ b/libs/image/kis_config_widget.cpp
@@ -1,47 +1,47 @@
/*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_config_widget.h"
#include "kis_debug.h"
#include <QTimer>
-KisConfigWidget::KisConfigWidget(QWidget * parent, Qt::WFlags f, int delay)
+KisConfigWidget::KisConfigWidget(QWidget * parent, Qt::WindowFlags f, int delay)
: QWidget(parent, f)
, m_compressor(delay, KisSignalCompressor::FIRST_ACTIVE)
{
connect(this, SIGNAL(sigConfigurationItemChanged()), SLOT(slotConfigChanged()));
connect(&m_compressor, SIGNAL(timeout()), SIGNAL(sigConfigurationUpdated()));
}
KisConfigWidget::~KisConfigWidget()
{
}
void KisConfigWidget::slotConfigChanged()
{
if (!signalsBlocked()) {
m_compressor.start();
}
}
void KisConfigWidget::setView(KisViewManager *view)
{
if (!view) {
warnKrita << "KisConfigWidget::setView has got view == 0. That's a bug! Please report it!";
}
}
diff --git a/libs/image/kis_config_widget.h b/libs/image/kis_config_widget.h
index 8f4c2186e8..98dd7150f1 100644
--- a/libs/image/kis_config_widget.h
+++ b/libs/image/kis_config_widget.h
@@ -1,92 +1,92 @@
/*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
* Copyright (c) 2004-2006 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_CONFIG_WIDGET_H_
#define _KIS_CONFIG_WIDGET_H_
#include <QWidget>
#include <kritaimage_export.h>
#include "kis_signal_compressor.h"
#include <kis_properties_configuration.h>
class KisViewManager;
/**
* Empty base class. Configurable resources like filters, paintops etc.
* can build their own configuration widgets that inherit this class.
* The configuration widget should emit sigConfigurationItemChanged
* when it wants a preview updated; there is a timer that
* waits a little time to see if there are more changes coming
* and then emits sigConfigurationUpdated.
*/
class KRITAIMAGE_EXPORT KisConfigWidget : public QWidget
{
Q_OBJECT
protected:
- KisConfigWidget(QWidget * parent = 0, Qt::WFlags f = 0, int delay = 200);
+ KisConfigWidget(QWidget * parent = 0, Qt::WindowFlags f = 0, int delay = 200);
public:
~KisConfigWidget() override;
/**
* @param config the configuration for this configuration widget.
*/
virtual void setConfiguration(const KisPropertiesConfigurationSP config) = 0;
/**
* @return the configuration
*/
virtual KisPropertiesConfigurationSP configuration() const = 0;
/**
* Sets the view object that can be used by the configuration
* widget for richer functionality
*/
virtual void setView(KisViewManager *view);
Q_SIGNALS:
/**
* emitted whenever it makes sense to update the preview
*/
void sigConfigurationUpdated();
/**
* Subclasses should emit this signal whenever the preview should be
* be recalculated. This kicks of a timer, so it's perfectly fine
* to connect this to the changed signals of the widgets in your configuration
* widget.
*/
void sigConfigurationItemChanged();
void sigSaveLockedConfig(KisPropertiesConfigurationSP p);
void sigDropLockedConfig(KisPropertiesConfigurationSP p);
private Q_SLOTS:
void slotConfigChanged();
private:
KisSignalCompressor m_compressor;
};
#endif
diff --git a/libs/image/kis_convolution_worker_fft.h b/libs/image/kis_convolution_worker_fft.h
index 32bd6aca9c..503f8f13fd 100644
--- a/libs/image/kis_convolution_worker_fft.h
+++ b/libs/image/kis_convolution_worker_fft.h
@@ -1,541 +1,541 @@
/*
* Copyright (c) 2010 Edward Apap <schumifer@hotmail.com>
* Copyright (c) 2011 José Luis Vergara Toloza <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CONVOLUTION_WORKER_FFT_H
#define KIS_CONVOLUTION_WORKER_FFT_H
#include <iostream>
#include <KoChannelInfo.h>
#include "kis_convolution_worker.h"
#include "kis_math_toolbox.h"
#include <QMutex>
#include <QVector>
#include <QTextStream>
#include <QFile>
#include <QDir>
#include <fftw3.h>
template<class _IteratorFactory_> class KisConvolutionWorkerFFT;
class KisConvolutionWorkerFFTLock
{
private:
static QMutex fftwMutex;
template<class _IteratorFactory_> friend class KisConvolutionWorkerFFT;
};
QMutex KisConvolutionWorkerFFTLock::fftwMutex;
template<class _IteratorFactory_>
class KisConvolutionWorkerFFT : public KisConvolutionWorker<_IteratorFactory_>
{
public:
KisConvolutionWorkerFFT(KisPainter *painter, KoUpdater *progress)
: KisConvolutionWorker<_IteratorFactory_>(painter, progress),
m_currentProgress(0),
m_kernelFFT(0)
{
}
~KisConvolutionWorkerFFT()
{
}
virtual void execute(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, const QRect& dataRect)
{
// Make the area we cover as small as possible
if (this->m_painter->selection())
{
- QRect r = this->m_painter->selection()->selectedRect().intersect(QRect(srcPos, areaSize));
+ QRect r = this->m_painter->selection()->selectedRect().intersected(QRect(srcPos, areaSize));
dstPos += r.topLeft() - srcPos;
srcPos = r.topLeft();
areaSize = r.size();
}
if (areaSize.width() == 0 || areaSize.height() == 0)
return;
addToProgress(0);
if (isInterrupted()) return;
const quint32 halfKernelWidth = (kernel->width() - 1) / 2;
const quint32 halfKernelHeight = (kernel->height() - 1) / 2;
m_fftWidth = areaSize.width() + 4 * halfKernelWidth;
m_fftHeight = areaSize.height() + 2 * halfKernelHeight;
/**
* FIXME: check whether this "optimization" is needed to
* be uncommented. My tests showed about 30% better performance
* when the line is commented out (DK).
*/
//optimumDimensions(m_fftWidth, m_fftHeight);
m_fftLength = m_fftHeight * (m_fftWidth / 2 + 1);
m_extraMem = (m_fftWidth % 2) ? 1 : 2;
// create and fill kernel
m_kernelFFT = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * m_fftLength);
memset(m_kernelFFT, 0, sizeof(fftw_complex) * m_fftLength);
fftFillKernelMatrix(kernel, m_kernelFFT);
// find out which channels need convolving
QList<KoChannelInfo*> convChannelList = this->convolvableChannelList(src);
m_channelFFT.resize(convChannelList.count());
for (auto i = m_channelFFT.begin(); i != m_channelFFT.end(); ++i) {
*i = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * m_fftLength);
}
const double kernelFactor = kernel->factor() ? kernel->factor() : 1;
const double fftScale = 1.0 / (m_fftHeight * m_fftWidth) / kernelFactor;
FFTInfo info (fftScale, convChannelList, kernel, this->m_painter->device()->colorSpace());
int cacheRowStride = m_fftWidth + m_extraMem;
fillCacheFromDevice(src,
QRect(srcPos.x() - halfKernelWidth,
srcPos.y() - halfKernelHeight,
m_fftWidth,
m_fftHeight),
cacheRowStride,
info, dataRect);
addToProgress(10);
if (isInterrupted()) return;
// calculate number off fft operations required for progress reporting
const float progressPerFFT = (100 - 30) / (double)(convChannelList.count() * 2 + 1);
// perform FFT
fftw_plan fftwPlanForward, fftwPlanBackward;
KisConvolutionWorkerFFTLock::fftwMutex.lock();
fftwPlanForward = fftw_plan_dft_r2c_2d(m_fftHeight, m_fftWidth, (double*)m_kernelFFT, m_kernelFFT, FFTW_ESTIMATE);
fftwPlanBackward = fftw_plan_dft_c2r_2d(m_fftHeight, m_fftWidth, m_kernelFFT, (double*)m_kernelFFT, FFTW_ESTIMATE);
KisConvolutionWorkerFFTLock::fftwMutex.unlock();
fftw_execute(fftwPlanForward);
addToProgress(progressPerFFT);
if (isInterrupted()) return;
for (auto k = m_channelFFT.begin(); k != m_channelFFT.end(); ++k)
{
fftw_execute_dft_r2c(fftwPlanForward, (double*)(*k), *k);
addToProgress(progressPerFFT);
if (isInterrupted()) return;
fftMultiply(*k, m_kernelFFT);
fftw_execute_dft_c2r(fftwPlanBackward, *k, (double*)*k);
addToProgress(progressPerFFT);
if (isInterrupted()) return;
}
KisConvolutionWorkerFFTLock::fftwMutex.lock();
fftw_destroy_plan(fftwPlanForward);
fftw_destroy_plan(fftwPlanBackward);
KisConvolutionWorkerFFTLock::fftwMutex.unlock();
writeResultToDevice(QRect(dstPos.x(), dstPos.y(), areaSize.width(), areaSize.height()),
cacheRowStride, halfKernelWidth, halfKernelHeight,
info, dataRect);
addToProgress(20);
cleanUp();
}
struct FFTInfo {
FFTInfo(qreal _fftScale,
const QList<KoChannelInfo*> &_convChannelList,
const KisConvolutionKernelSP kernel,
const KoColorSpace */*colorSpace*/)
: fftScale(_fftScale),
convChannelList(_convChannelList),
alphaCachePos(-1),
alphaRealPos(-1)
{
KisMathToolbox mathToolbox;
for (int i = 0; i < convChannelList.count(); ++i) {
minClamp.append(mathToolbox.minChannelValue(convChannelList[i]));
maxClamp.append(mathToolbox.maxChannelValue(convChannelList[i]));
absoluteOffset.append((maxClamp[i] - minClamp[i]) * kernel->offset());
if (convChannelList[i]->channelType() == KoChannelInfo::ALPHA) {
alphaCachePos = i;
alphaRealPos = convChannelList[i]->pos();
}
}
toDoubleFuncPtr.resize(convChannelList.count());
fromDoubleFuncPtr.resize(convChannelList.count());
bool result = mathToolbox.getToDoubleChannelPtr(convChannelList, toDoubleFuncPtr);
result &= mathToolbox.getFromDoubleChannelPtr(convChannelList, fromDoubleFuncPtr);
KIS_ASSERT(result);
}
inline int numChannels() const {
return convChannelList.size();
}
QVector<qreal> minClamp;
QVector<qreal> maxClamp;
QVector<qreal> absoluteOffset;
qreal fftScale;
QList<KoChannelInfo*> convChannelList;
QVector<PtrToDouble> toDoubleFuncPtr;
QVector<PtrFromDouble> fromDoubleFuncPtr;
int alphaCachePos;
int alphaRealPos;
};
void fillCacheFromDevice(KisPaintDeviceSP src,
const QRect &rect,
const int cacheRowStride,
const FFTInfo &info,
const QRect &dataRect) {
typename _IteratorFactory_::HLineConstIterator hitSrc =
_IteratorFactory_::createHLineConstIterator(src,
rect.x(), rect.y(), rect.width(),
dataRect);
const int channelCount = info.numChannels();
QVector<double*> channelPtr(channelCount);
const auto channelPtrBegin = channelPtr.begin();
const auto channelPtrEnd = channelPtr.end();
auto iFFt = m_channelFFT.constBegin();
for (auto i = channelPtrBegin; i != channelPtrEnd; ++i, ++iFFt) {
*i = (double*)*iFFt;
}
// prepare cache, reused in all loops
QVector<double*> cacheRowStart(channelCount);
const auto cacheRowStartBegin = cacheRowStart.begin();
for (int y = 0; y < rect.height(); ++y) {
// cache current channelPtr in cacheRowStart
memcpy(cacheRowStart.data(), channelPtr.data(), channelCount * sizeof(double*));
for (int x = 0; x < rect.width(); ++x) {
const quint8 *data = hitSrc->oldRawData();
// no alpha is a rare case, so just multiply by 1.0 in that case
double alphaValue = info.alphaRealPos >= 0 ?
info.toDoubleFuncPtr[info.alphaCachePos](data, info.alphaRealPos) : 1.0;
int k = 0;
for (auto i = channelPtrBegin; i != channelPtrEnd; ++i, ++k) {
if (k != info.alphaCachePos) {
const quint32 channelPos = info.convChannelList[k]->pos();
**i = info.toDoubleFuncPtr[k](data, channelPos) * alphaValue;
} else {
**i = alphaValue;
}
++(*i);
}
hitSrc->nextPixel();
}
auto iRowStart = cacheRowStartBegin;
for (auto i = channelPtrBegin; i != channelPtrEnd; ++i, ++iRowStart) {
*i = *iRowStart + cacheRowStride;
}
hitSrc->nextRow();
}
}
inline void limitValue(qreal *value, qreal lowBound, qreal highBound) {
if (*value > highBound) {
*value = highBound;
} else if (!(*value >= lowBound)) { // value < lowBound or value == NaN
// IEEE compliant comparisons with NaN are always false
*value = lowBound;
}
}
template <bool additionalMultiplierActive>
inline qreal writeOneChannelFromCache(quint8* dstPtr,
const quint32 channel,
const FFTInfo &info,
double* channelValuePtr,
const qreal additionalMultiplier = 0.0) {
qreal channelPixelValue;
if (additionalMultiplierActive) {
channelPixelValue = (*channelValuePtr * info.fftScale + info.absoluteOffset[channel]) * additionalMultiplier;
} else {
channelPixelValue = *channelValuePtr * info.fftScale + info.absoluteOffset[channel];
}
limitValue(&channelPixelValue, info.minClamp[channel], info.maxClamp[channel]);
info.fromDoubleFuncPtr[channel](dstPtr, info.convChannelList[channel]->pos(), channelPixelValue);
return channelPixelValue;
}
void writeResultToDevice(const QRect &rect,
const int cacheRowStride,
const int halfKernelWidth,
const int halfKernelHeight,
const FFTInfo &info,
const QRect &dataRect) {
typename _IteratorFactory_::HLineIterator hitDst =
_IteratorFactory_::createHLineIterator(this->m_painter->device(),
rect.x(), rect.y(), rect.width(),
dataRect);
int initialOffset = cacheRowStride * halfKernelHeight + halfKernelWidth;
const int channelCount = info.numChannels();
QVector<double*> channelPtr(channelCount);
const auto channelPtrBegin = channelPtr.begin();
const auto channelPtrEnd = channelPtr.end();
auto iFFt = m_channelFFT.constBegin();
for (auto i = channelPtrBegin; i != channelPtrEnd; ++i, ++iFFt) {
*i = (double*)*iFFt + initialOffset;
}
// prepare cache, reused in all loops
QVector<double*> cacheRowStart(channelCount);
const auto cacheRowStartBegin = cacheRowStart.begin();
for (int y = 0; y < rect.height(); ++y) {
// cache current channelPtr in cacheRowStart
memcpy(cacheRowStart.data(), channelPtr.data(), channelCount * sizeof(double*));
for (int x = 0; x < rect.width(); ++x) {
quint8 *dstPtr = hitDst->rawData();
if (info.alphaCachePos >= 0) {
qreal alphaValue =
writeOneChannelFromCache<false>(dstPtr,
info.alphaCachePos,
info,
channelPtr.at(info.alphaCachePos));
if (alphaValue > std::numeric_limits<qreal>::epsilon()) {
qreal alphaValueInv = 1.0 / alphaValue;
int k = 0;
for (auto i = channelPtrBegin; i != channelPtrEnd; ++i, ++k) {
if (k != info.alphaCachePos) {
writeOneChannelFromCache<true>(dstPtr,
k,
info,
*i,
alphaValueInv);
}
++(*i);
}
} else {
int k = 0;
for (auto i = channelPtrBegin; i != channelPtrEnd; ++i, ++k) {
if (k != info.alphaCachePos) {
info.fromDoubleFuncPtr[k](dstPtr,
info.convChannelList[k]->pos(),
0.0);
}
++(*i);
}
}
} else {
int k = 0;
for (auto i = channelPtrBegin; i != channelPtrEnd; ++i, ++k) {
writeOneChannelFromCache<false>(dstPtr,
k,
info,
*i);
++(*i);
}
}
hitDst->nextPixel();
}
auto iRowStart = cacheRowStartBegin;
for (auto i = channelPtrBegin; i != channelPtrEnd; ++i, ++iRowStart) {
*i = *iRowStart + cacheRowStride;
}
hitDst->nextRow();
}
}
private:
void fftFillKernelMatrix(const KisConvolutionKernelSP kernel, fftw_complex *m_kernelFFT)
{
// find central item
QPoint offset((kernel->width() - 1) / 2, (kernel->height() - 1) / 2);
qint32 xShift = m_fftWidth - offset.x();
qint32 yShift = m_fftHeight - offset.y();
quint32 absXpos, absYpos;
for (quint32 y = 0; y < kernel->height(); y++)
{
absYpos = y + yShift;
if (absYpos >= m_fftHeight)
absYpos -= m_fftHeight;
for (quint32 x = 0; x < kernel->width(); x++)
{
absXpos = x + xShift;
if (absXpos >= m_fftWidth)
absXpos -= m_fftWidth;
((double*)m_kernelFFT)[(m_fftWidth + m_extraMem) * absYpos + absXpos] = kernel->data()->coeff(y, x);
}
}
}
void fftMultiply(fftw_complex* channel, fftw_complex* kernel)
{
// perform complex multiplication
fftw_complex *channelPtr = channel;
fftw_complex *kernelPtr = kernel;
fftw_complex tmp;
for (quint32 pixelPos = 0; pixelPos < m_fftLength; ++pixelPos)
{
tmp[0] = ((*channelPtr)[0] * (*kernelPtr)[0]) - ((*channelPtr)[1] * (*kernelPtr)[1]);
tmp[1] = ((*channelPtr)[0] * (*kernelPtr)[1]) + ((*channelPtr)[1] * (*kernelPtr)[0]);
(*channelPtr)[0] = tmp[0];
(*channelPtr)[1] = tmp[1];
++channelPtr;
++kernelPtr;
}
}
void optimumDimensions(quint32& w, quint32& h)
{
// FFTW is most efficient when array size is a factor of 2, 3, 5 or 7
quint32 optW = w, optH = h;
while ((optW % 2 != 0) || (optW % 3 != 0) || (optW % 5 != 0) || (optW % 7 != 0))
++optW;
while ((optH % 2 != 0) || (optH % 3 != 0) || (optH % 5 != 0) || (optH % 7 != 0))
++optH;
quint32 optAreaW = optW * h;
quint32 optAreaH = optH * w;
if (optAreaW < optAreaH) {
w = optW;
}
else {
h = optH;
}
}
void fftLogMatrix(double* channel, const QString &f)
{
KisConvolutionWorkerFFTLock::fftwMutex.lock();
QString filename(QDir::homePath() + "/log_" + f + ".txt");
dbgKrita << "Log File Name: " << filename;
QFile file (filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
{
dbgKrita << "Failed";
KisConvolutionWorkerFFTLock::fftwMutex.unlock();
return;
}
QTextStream in(&file);
for (quint32 y = 0; y < m_fftHeight; y++)
{
for (quint32 x = 0; x < m_fftWidth; x++)
{
QString num = QString::number(channel[y * m_fftWidth + x]);
while (num.length() < 15)
num += " ";
in << num << " ";
}
in << "\n";
}
KisConvolutionWorkerFFTLock::fftwMutex.unlock();
}
void addToProgress(float amount)
{
m_currentProgress += amount;
if (this->m_progress) {
this->m_progress->setProgress((int)m_currentProgress);
}
}
bool isInterrupted()
{
if (this->m_progress && this->m_progress->interrupted()) {
cleanUp();
return true;
}
return false;
}
void cleanUp()
{
// free kernel fft data
if (m_kernelFFT) {
fftw_free(m_kernelFFT);
}
Q_FOREACH (fftw_complex *channel, m_channelFFT) {
fftw_free(channel);
}
m_channelFFT.clear();
}
private:
quint32 m_fftWidth, m_fftHeight, m_fftLength, m_extraMem;
float m_currentProgress;
fftw_complex* m_kernelFFT;
QVector<fftw_complex*> m_channelFFT;
};
#endif
diff --git a/libs/image/kis_convolution_worker_spatial.h b/libs/image/kis_convolution_worker_spatial.h
index 35946dc86e..5bb9fba760 100644
--- a/libs/image/kis_convolution_worker_spatial.h
+++ b/libs/image/kis_convolution_worker_spatial.h
@@ -1,386 +1,386 @@
/*
* Copyright (c) 2005, 2008, 2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2009, 2010 Edward Apap <schumifer@hotmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CONVOLUTION_WORKER_SPATIAL_H
#define KIS_CONVOLUTION_WORKER_SPATIAL_H
#include "kis_convolution_worker.h"
#include "kis_math_toolbox.h"
template <class _IteratorFactory_>
class KisConvolutionWorkerSpatial : public KisConvolutionWorker<_IteratorFactory_>
{
public:
KisConvolutionWorkerSpatial(KisPainter *painter, KoUpdater *progress)
: KisConvolutionWorker<_IteratorFactory_>(painter, progress)
, m_alphaCachePos(-1)
, m_alphaRealPos(-1)
, m_pixelPtrCache(0)
, m_pixelPtrCacheCopy(0)
, m_minClamp(0)
, m_maxClamp(0)
, m_absoluteOffset(0)
{
}
~KisConvolutionWorkerSpatial() override {
}
inline void loadPixelToCache(qreal **cache, const quint8 *data, int index) {
// no alpha is rare case, so just multiply by 1.0 in that case
qreal alphaValue = m_alphaRealPos >= 0 ?
m_toDoubleFuncPtr[m_alphaCachePos](data, m_alphaRealPos) : 1.0;
for (quint32 k = 0; k < m_convolveChannelsNo; ++k) {
if (k != (quint32)m_alphaCachePos) {
const quint32 channelPos = m_convChannelList[k]->pos();
cache[index][k] = m_toDoubleFuncPtr[k](data, channelPos) * alphaValue;
} else {
cache[index][k] = alphaValue;
}
}
}
void execute(const KisConvolutionKernelSP kernel, const KisPaintDeviceSP src, QPoint srcPos, QPoint dstPos, QSize areaSize, const QRect& dataRect) override {
// store some kernel characteristics
m_kw = kernel->width();
m_kh = kernel->height();
m_khalfWidth = (m_kw - 1) / 2;
m_khalfHeight = (m_kh - 1) / 2;
m_cacheSize = m_kw * m_kh;
m_pixelSize = src->colorSpace()->pixelSize();
quint32 channelCount = src->colorSpace()->channelCount();
m_kernelData = new qreal[m_cacheSize];
qreal *kernelDataPtr = m_kernelData;
// fill in data
for (quint32 r = 0; r < kernel->height(); r++) {
for (quint32 c = 0; c < kernel->width(); c++) {
*kernelDataPtr = (*(kernel->data()))(r, c);
kernelDataPtr++;
}
}
// Make the area we cover as small as possible
if (this->m_painter->selection()) {
- QRect r = this->m_painter->selection()->selectedRect().intersect(QRect(srcPos, areaSize));
+ QRect r = this->m_painter->selection()->selectedRect().intersected(QRect(srcPos, areaSize));
dstPos += r.topLeft() - srcPos;
srcPos = r.topLeft();
areaSize = r.size();
}
if (areaSize.width() == 0 || areaSize.height() == 0)
return;
// Don't convolve with an even sized kernel
Q_ASSERT((m_kw & 0x01) == 1 || (m_kh & 0x01) == 1 || kernel->factor() != 0);
// find out which channels need be convolved
m_convChannelList = this->convolvableChannelList(src);
m_convolveChannelsNo = m_convChannelList.count();
for (int i = 0; i < m_convChannelList.size(); i++) {
if (m_convChannelList[i]->channelType() == KoChannelInfo::ALPHA) {
m_alphaCachePos = i;
m_alphaRealPos = m_convChannelList[i]->pos();
}
}
bool hasProgressUpdater = this->m_progress;
if (hasProgressUpdater)
this->m_progress->setProgress(0);
// Iterate over all pixels in our rect, create a cache of pixels around the current pixel and convolve them.
m_pixelPtrCache = new qreal*[m_cacheSize];
m_pixelPtrCacheCopy = new qreal*[m_cacheSize];
for (quint32 c = 0; c < m_cacheSize; ++c) {
m_pixelPtrCache[c] = new qreal[channelCount];
m_pixelPtrCacheCopy[c] = new qreal[channelCount];
}
// decide caching strategy
enum TraversingDirection { Horizontal, Vertical };
TraversingDirection traversingDirection = Vertical;
if (m_kw > m_kh) {
traversingDirection = Horizontal;
}
KisMathToolbox mathToolbox;
m_toDoubleFuncPtr = QVector<PtrToDouble>(m_convolveChannelsNo);
if (!mathToolbox.getToDoubleChannelPtr(m_convChannelList, m_toDoubleFuncPtr))
return;
m_fromDoubleFuncPtr = QVector<PtrFromDouble>(m_convolveChannelsNo);
if (!mathToolbox.getFromDoubleChannelPtr(m_convChannelList, m_fromDoubleFuncPtr))
return;
m_kernelFactor = kernel->factor() ? 1.0 / kernel->factor() : 1;
m_maxClamp = new qreal[m_convChannelList.count()];
m_minClamp = new qreal[m_convChannelList.count()];
m_absoluteOffset = new qreal[m_convChannelList.count()];
for (quint16 i = 0; i < m_convChannelList.count(); ++i) {
m_minClamp[i] = mathToolbox.minChannelValue(m_convChannelList[i]);
m_maxClamp[i] = mathToolbox.maxChannelValue(m_convChannelList[i]);
m_absoluteOffset[i] = (m_maxClamp[i] - m_minClamp[i]) * kernel->offset();
}
qint32 row = srcPos.y();
qint32 col = srcPos.x();
// populate pixelPtrCacheCopy for starting position (0, 0)
qint32 i = 0;
typename _IteratorFactory_::HLineConstIterator hitInitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row - m_khalfHeight, m_kw, dataRect);
for (quint32 krow = 0; krow < m_kh; ++krow) {
do {
const quint8* data = hitInitSrc->oldRawData();
loadPixelToCache(m_pixelPtrCacheCopy, data, i);
++i;
} while (hitInitSrc->nextPixel());
hitInitSrc->nextRow();
}
if (traversingDirection == Horizontal) {
if(hasProgressUpdater) {
this->m_progress->setRange(0, areaSize.height());
}
typename _IteratorFactory_::HLineIterator hitDst = _IteratorFactory_::createHLineIterator(this->m_painter->device(), dstPos.x(), dstPos.y(), areaSize.width(), dataRect);
typename _IteratorFactory_::HLineConstIterator hitSrc = _IteratorFactory_::createHLineConstIterator(src, srcPos.x(), srcPos.y(), areaSize.width(), dataRect);
typename _IteratorFactory_::HLineConstIterator khitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row + m_khalfHeight, m_kw, dataRect);
for (int prow = 0; prow < areaSize.height(); ++prow) {
// reload cache from copy
for (quint32 i = 0; i < m_cacheSize; ++i)
memcpy(m_pixelPtrCache[i], m_pixelPtrCacheCopy[i], channelCount * sizeof(qreal));
typename _IteratorFactory_::VLineConstIterator kitSrc = _IteratorFactory_::createVLineConstIterator(src, col + m_khalfWidth, row - m_khalfHeight, m_kh, dataRect);
for (int pcol = 0; pcol < areaSize.width(); ++pcol) {
// write original channel values
memcpy(hitDst->rawData(), hitSrc->oldRawData(), m_pixelSize);
convolveCache(hitDst->rawData());
++col;
kitSrc->nextColumn();
hitDst->nextPixel();
hitSrc->nextPixel();
moveKernelRight(kitSrc, m_pixelPtrCache);
}
row++;
khitSrc->nextRow();
hitDst->nextRow();
hitSrc->nextRow();
col = srcPos.x();
moveKernelDown(khitSrc, m_pixelPtrCacheCopy);
if (hasProgressUpdater) {
this->m_progress->setValue(prow);
if (this->m_progress->interrupted()) {
cleanUp();
return;
}
}
}
} else if (traversingDirection == Vertical) {
if(hasProgressUpdater) {
this->m_progress->setRange(0, areaSize.width());
}
typename _IteratorFactory_::VLineIterator vitDst = _IteratorFactory_::createVLineIterator(this->m_painter->device(), dstPos.x(), dstPos.y(), areaSize.height(), dataRect);
typename _IteratorFactory_::VLineConstIterator vitSrc = _IteratorFactory_::createVLineConstIterator(src, srcPos.x(), srcPos.y(), areaSize.height(), dataRect);
typename _IteratorFactory_::VLineConstIterator kitSrc = _IteratorFactory_::createVLineConstIterator(src, col + m_khalfWidth, row - m_khalfHeight, m_kh, dataRect);
for (int pcol = 0; pcol < areaSize.width(); pcol++) {
// reload cache from copy
for (quint32 i = 0; i < m_cacheSize; ++i)
memcpy(m_pixelPtrCache[i], m_pixelPtrCacheCopy[i], channelCount * sizeof(qreal));
typename _IteratorFactory_::HLineConstIterator khitSrc = _IteratorFactory_::createHLineConstIterator(src, col - m_khalfWidth, row + m_khalfHeight, m_kw, dataRect);
for (int prow = 0; prow < areaSize.height(); prow++) {
// write original channel values
memcpy(vitDst->rawData(), vitSrc->oldRawData(), m_pixelSize);
convolveCache(vitDst->rawData());
++row;
khitSrc->nextRow();
vitDst->nextPixel();
vitSrc->nextPixel();
moveKernelDown(khitSrc, m_pixelPtrCache);
}
++col;
kitSrc->nextColumn();
vitDst->nextColumn();
vitSrc->nextColumn();
row = srcPos.y();
moveKernelRight(kitSrc, m_pixelPtrCacheCopy);
if (hasProgressUpdater) {
this->m_progress->setValue(pcol);
if (this->m_progress->interrupted()) {
cleanUp();
return;
}
}
}
}
cleanUp();
}
inline void limitValue(qreal *value, qreal lowBound, qreal highBound) {
if (*value > highBound) {
*value = highBound;
} else if (!(*value >= lowBound)) { // value < lowBound or value == NaN
// IEEE compliant comparisons with NaN are always false
*value = lowBound;
}
}
template <bool additionalMultiplierActive>
inline qreal convolveOneChannelFromCache(quint8* dstPtr, quint32 channel, qreal additionalMultiplier = 0.0) {
qreal interimConvoResult = 0;
for (quint32 pIndex = 0; pIndex < m_cacheSize; ++pIndex) {
qreal cacheValue = m_pixelPtrCache[pIndex][channel];
interimConvoResult += m_kernelData[m_cacheSize - pIndex - 1] * cacheValue;
}
qreal channelPixelValue;
if (additionalMultiplierActive) {
channelPixelValue = (interimConvoResult * m_kernelFactor) * additionalMultiplier + m_absoluteOffset[channel];
} else {
channelPixelValue = interimConvoResult * m_kernelFactor + m_absoluteOffset[channel];
}
limitValue(&channelPixelValue, m_minClamp[channel], m_maxClamp[channel]);
const quint32 channelPos = m_convChannelList[channel]->pos();
m_fromDoubleFuncPtr[channel](dstPtr, channelPos, channelPixelValue);
return channelPixelValue;
}
inline void convolveCache(quint8* dstPtr) {
if (m_alphaCachePos >= 0) {
qreal alphaValue = convolveOneChannelFromCache<false>(dstPtr, m_alphaCachePos);
// TODO: we need a special case for applying LoG filter,
// when the alpha i suniform and therefore should not be
// filtered!
//alphaValue = 255.0;
if (alphaValue != 0.0) {
qreal alphaValueInv = 1.0 / alphaValue;
for (quint32 k = 0; k < m_convolveChannelsNo; ++k) {
if (k == (quint32)m_alphaCachePos) continue;
convolveOneChannelFromCache<true>(dstPtr, k, alphaValueInv);
}
} else {
for (quint32 k = 0; k < m_convolveChannelsNo; ++k) {
if (k == (quint32)m_alphaCachePos) continue;
const qreal zeroValue = 0.0;
const quint32 channelPos = m_convChannelList[k]->pos();
m_fromDoubleFuncPtr[k](dstPtr, channelPos, zeroValue);
}
}
} else {
for (quint32 k = 0; k < m_convolveChannelsNo; ++k) {
convolveOneChannelFromCache<false>(dstPtr, k);
}
}
}
inline void moveKernelRight(typename _IteratorFactory_::VLineConstIterator& kitSrc, qreal **pixelPtrCache) {
qreal** d = pixelPtrCache;
for (quint32 krow = 0; krow < m_kh; ++krow) {
qreal* first = *d;
memmove(d, d + 1, (m_kw - 1) * sizeof(qreal *));
*(d + m_kw - 1) = first;
d += m_kw;
}
qint32 i = m_kw - 1;
do {
const quint8* data = kitSrc->oldRawData();
loadPixelToCache(pixelPtrCache, data, i);
i += m_kw;
} while (kitSrc->nextPixel());
}
inline void moveKernelDown(typename _IteratorFactory_::HLineConstIterator& kitSrc, qreal **pixelPtrCache) {
quint8 **tmp = new quint8*[m_kw];
memcpy(tmp, pixelPtrCache, m_kw * sizeof(qreal *));
memmove(pixelPtrCache, pixelPtrCache + m_kw, (m_kw * m_kh - m_kw) * sizeof(quint8 *));
memcpy(pixelPtrCache + m_kw *(m_kh - 1), tmp, m_kw * sizeof(quint8 *));
delete[] tmp;
qint32 i = m_kw * (m_kh - 1);
do {
const quint8* data = kitSrc->oldRawData();
loadPixelToCache(pixelPtrCache, data, i);
i++;
} while (kitSrc->nextPixel());
}
void cleanUp() {
for (quint32 c = 0; c < m_cacheSize; ++c) {
delete[] m_pixelPtrCache[c];
delete[] m_pixelPtrCacheCopy[c];
}
delete[] m_kernelData;
delete[] m_pixelPtrCache;
delete[] m_pixelPtrCacheCopy;
delete[] m_minClamp;
delete[] m_maxClamp;
delete[] m_absoluteOffset;
}
private:
quint32 m_kw, m_kh;
quint32 m_khalfWidth, m_khalfHeight;
quint32 m_convolveChannelsNo;
quint32 m_cacheSize, m_pixelSize;
int m_alphaCachePos;
int m_alphaRealPos;
qreal *m_kernelData;
qreal** m_pixelPtrCache, ** m_pixelPtrCacheCopy;
qreal* m_minClamp, *m_maxClamp, *m_absoluteOffset;
qreal m_kernelFactor;
QList<KoChannelInfo *> m_convChannelList;
QVector<PtrToDouble> m_toDoubleFuncPtr;
QVector<PtrFromDouble> m_fromDoubleFuncPtr;
};
#endif
diff --git a/libs/image/kis_distance_information.cpp b/libs/image/kis_distance_information.cpp
index 76643df404..b7060c1257 100644
--- a/libs/image/kis_distance_information.cpp
+++ b/libs/image/kis_distance_information.cpp
@@ -1,649 +1,625 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <kis_distance_information.h>
#include <brushengine/kis_paint_information.h>
#include "kis_spacing_information.h"
#include "kis_timing_information.h"
#include "kis_debug.h"
#include <QtCore/qmath.h>
#include <QVector2D>
#include <QTransform>
#include "kis_algebra_2d.h"
#include "kis_dom_utils.h"
#include "kis_lod_transform.h"
const qreal MIN_DISTANCE_SPACING = 0.5;
// Smallest allowed interval when timed spacing is enabled, in milliseconds.
const qreal MIN_TIMED_INTERVAL = 0.5;
// Largest allowed interval when timed spacing is enabled, in milliseconds.
const qreal MAX_TIMED_INTERVAL = LONG_TIME;
struct Q_DECL_HIDDEN KisDistanceInformation::Private {
Private() :
accumDistance(),
accumTime(0.0),
spacingUpdateInterval(LONG_TIME),
timeSinceSpacingUpdate(0.0),
timingUpdateInterval(LONG_TIME),
timeSinceTimingUpdate(0.0),
lastDabInfoValid(false),
lastPaintInfoValid(false),
- lockedDrawingAngle(0.0),
- hasLockedDrawingAngle(false),
- totalDistance(0.0) {}
+ totalDistance(0.0),
+ currentDabSeqNo(0),
+ levelOfDetail(0)
+ {
+ }
// Accumulators of time/distance passed since the last painted dab
QPointF accumDistance;
qreal accumTime;
KisSpacingInformation spacing;
qreal spacingUpdateInterval;
// Accumulator of time passed since the last spacing update
qreal timeSinceSpacingUpdate;
KisTimingInformation timing;
qreal timingUpdateInterval;
// Accumulator of time passed since the last timing update
qreal timeSinceTimingUpdate;
// Information about the last position considered (not necessarily a painted dab)
QPointF lastPosition;
- qreal lastTime;
qreal lastAngle;
bool lastDabInfoValid;
// Information about the last painted dab
KisPaintInformation lastPaintInformation;
bool lastPaintInfoValid;
- qreal lockedDrawingAngle;
- bool hasLockedDrawingAngle;
qreal totalDistance;
+ boost::optional<qreal> lockedDrawingAngleOptional;
+
+ int currentDabSeqNo;
+ int levelOfDetail;
};
struct Q_DECL_HIDDEN KisDistanceInitInfo::Private {
Private() :
hasLastInfo(false),
lastPosition(),
- lastTime(0.0),
lastAngle(0.0),
spacingUpdateInterval(LONG_TIME),
- timingUpdateInterval(LONG_TIME) {}
+ timingUpdateInterval(LONG_TIME),
+ currentDabSeqNo(0)
+ {
+ }
- // Indicates whether lastPosition, lastTime, and lastAngle are valid or not.
+ // Indicates whether lastPosition, and lastAngle are valid or not.
bool hasLastInfo;
QPointF lastPosition;
- qreal lastTime;
qreal lastAngle;
qreal spacingUpdateInterval;
qreal timingUpdateInterval;
+
+ int currentDabSeqNo;
};
KisDistanceInitInfo::KisDistanceInitInfo()
: m_d(new Private)
{
}
-KisDistanceInitInfo::KisDistanceInitInfo(qreal spacingUpdateInterval, qreal timingUpdateInterval)
+KisDistanceInitInfo::KisDistanceInitInfo(qreal spacingUpdateInterval, qreal timingUpdateInterval, int currentDabSeqNo)
: m_d(new Private)
{
m_d->spacingUpdateInterval = spacingUpdateInterval;
m_d->timingUpdateInterval = timingUpdateInterval;
+ m_d->currentDabSeqNo = currentDabSeqNo;
}
-KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime,
- qreal lastAngle)
+KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition,
+ qreal lastAngle, int currentDabSeqNo)
: m_d(new Private)
{
m_d->hasLastInfo = true;
m_d->lastPosition = lastPosition;
- m_d->lastTime = lastTime;
m_d->lastAngle = lastAngle;
+ m_d->currentDabSeqNo = currentDabSeqNo;
}
-KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime,
+KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition,
qreal lastAngle, qreal spacingUpdateInterval,
- qreal timingUpdateInterval)
+ qreal timingUpdateInterval,
+ int currentDabSeqNo)
: m_d(new Private)
{
m_d->hasLastInfo = true;
m_d->lastPosition = lastPosition;
- m_d->lastTime = lastTime;
m_d->lastAngle = lastAngle;
m_d->spacingUpdateInterval = spacingUpdateInterval;
m_d->timingUpdateInterval = timingUpdateInterval;
+ m_d->currentDabSeqNo = currentDabSeqNo;
}
KisDistanceInitInfo::KisDistanceInitInfo(const KisDistanceInitInfo &rhs)
: m_d(new Private(*rhs.m_d))
{
}
KisDistanceInitInfo::~KisDistanceInitInfo()
{
delete m_d;
}
bool KisDistanceInitInfo::operator==(const KisDistanceInitInfo &other) const
{
if (m_d->spacingUpdateInterval != other.m_d->spacingUpdateInterval
|| m_d->timingUpdateInterval != other.m_d->timingUpdateInterval
|| m_d->hasLastInfo != other.m_d->hasLastInfo)
{
return false;
}
if (m_d->hasLastInfo) {
- if (m_d->lastPosition != other.m_d->lastPosition || m_d->lastTime != other.m_d->lastTime
+ if (m_d->lastPosition != other.m_d->lastPosition
|| m_d->lastAngle != other.m_d->lastAngle)
{
return false;
}
}
+ if (m_d->currentDabSeqNo != other.m_d->currentDabSeqNo) {
+ return false;
+ }
+
return true;
}
bool KisDistanceInitInfo::operator!=(const KisDistanceInitInfo &other) const
{
return !(*this == other);
}
KisDistanceInitInfo &KisDistanceInitInfo::operator=(const KisDistanceInitInfo &rhs)
{
*m_d = *rhs.m_d;
return *this;
}
KisDistanceInformation KisDistanceInitInfo::makeDistInfo()
{
if (m_d->hasLastInfo) {
- return KisDistanceInformation(m_d->lastPosition, m_d->lastTime, m_d->lastAngle,
- m_d->spacingUpdateInterval, m_d->timingUpdateInterval);
+ return KisDistanceInformation(m_d->lastPosition, m_d->lastAngle,
+ m_d->spacingUpdateInterval, m_d->timingUpdateInterval,
+ m_d->currentDabSeqNo);
}
else {
- return KisDistanceInformation(m_d->spacingUpdateInterval, m_d->timingUpdateInterval);
+ return KisDistanceInformation(m_d->spacingUpdateInterval, m_d->timingUpdateInterval, m_d->currentDabSeqNo);
}
}
void KisDistanceInitInfo::toXML(QDomDocument &doc, QDomElement &elt) const
{
elt.setAttribute("spacingUpdateInterval", QString::number(m_d->spacingUpdateInterval, 'g', 15));
elt.setAttribute("timingUpdateInterval", QString::number(m_d->timingUpdateInterval, 'g', 15));
+ elt.setAttribute("currentDabSeqNo", QString::number(m_d->currentDabSeqNo));
if (m_d->hasLastInfo) {
QDomElement lastInfoElt = doc.createElement("LastInfo");
lastInfoElt.setAttribute("lastPosX", QString::number(m_d->lastPosition.x(), 'g', 15));
lastInfoElt.setAttribute("lastPosY", QString::number(m_d->lastPosition.y(), 'g', 15));
- lastInfoElt.setAttribute("lastTime", QString::number(m_d->lastTime, 'g', 15));
lastInfoElt.setAttribute("lastAngle", QString::number(m_d->lastAngle, 'g', 15));
elt.appendChild(lastInfoElt);
}
}
KisDistanceInitInfo KisDistanceInitInfo::fromXML(const QDomElement &elt)
{
const qreal spacingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("spacingUpdateInterval",
QString::number(LONG_TIME, 'g', 15))));
const qreal timingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("timingUpdateInterval",
QString::number(LONG_TIME, 'g', 15))));
+ const qreal currentDabSeqNo = KisDomUtils::toInt(elt.attribute("currentDabSeqNo", "0"));
+
const QDomElement lastInfoElt = elt.firstChildElement("LastInfo");
const bool hasLastInfo = !lastInfoElt.isNull();
if (hasLastInfo) {
const qreal lastPosX = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosX",
"0.0")));
const qreal lastPosY = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosY",
"0.0")));
- const qreal lastTime = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastTime",
- "0.0")));
const qreal lastAngle = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastAngle",
"0.0")));
- return KisDistanceInitInfo(QPointF(lastPosX, lastPosY), lastTime, lastAngle,
- spacingUpdateInterval, timingUpdateInterval);
+ return KisDistanceInitInfo(QPointF(lastPosX, lastPosY), lastAngle,
+ spacingUpdateInterval, timingUpdateInterval,
+ currentDabSeqNo);
}
else {
- return KisDistanceInitInfo(spacingUpdateInterval, timingUpdateInterval);
+ return KisDistanceInitInfo(spacingUpdateInterval, timingUpdateInterval,
+ currentDabSeqNo);
}
}
KisDistanceInformation::KisDistanceInformation()
: m_d(new Private)
{
}
KisDistanceInformation::KisDistanceInformation(qreal spacingUpdateInterval,
- qreal timingUpdateInterval)
+ qreal timingUpdateInterval,
+ int currentDabSeqNo)
: m_d(new Private)
{
m_d->spacingUpdateInterval = spacingUpdateInterval;
m_d->timingUpdateInterval = timingUpdateInterval;
+ m_d->currentDabSeqNo = currentDabSeqNo;
}
KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
- qreal lastTime,
qreal lastAngle)
: m_d(new Private)
{
m_d->lastPosition = lastPosition;
- m_d->lastTime = lastTime;
m_d->lastAngle = lastAngle;
m_d->lastDabInfoValid = true;
}
KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
- qreal lastTime,
qreal lastAngle,
qreal spacingUpdateInterval,
- qreal timingUpdateInterval)
- : KisDistanceInformation(lastPosition, lastTime, lastAngle)
+ qreal timingUpdateInterval,
+ int currentDabSeqNo)
+ : KisDistanceInformation(lastPosition, lastAngle)
{
m_d->spacingUpdateInterval = spacingUpdateInterval;
m_d->timingUpdateInterval = timingUpdateInterval;
+ m_d->currentDabSeqNo = currentDabSeqNo;
}
KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs)
: m_d(new Private(*rhs.m_d))
{
}
KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail)
: m_d(new Private(*rhs.m_d))
{
KIS_ASSERT_RECOVER_NOOP(!m_d->lastPaintInfoValid &&
"The distance information "
"should be cloned before the "
"actual painting is started");
+ m_d->levelOfDetail = levelOfDetail;
+
KisLodTransform t(levelOfDetail);
m_d->lastPosition = t.map(m_d->lastPosition);
}
KisDistanceInformation& KisDistanceInformation::operator=(const KisDistanceInformation &rhs)
{
*m_d = *rhs.m_d;
return *this;
}
-void KisDistanceInformation::overrideLastValues(const QPointF &lastPosition, qreal lastTime,
+void KisDistanceInformation::overrideLastValues(const QPointF &lastPosition,
qreal lastAngle)
{
m_d->lastPosition = lastPosition;
- m_d->lastTime = lastTime;
m_d->lastAngle = lastAngle;
m_d->lastDabInfoValid = true;
}
KisDistanceInformation::~KisDistanceInformation()
{
delete m_d;
}
const KisSpacingInformation& KisDistanceInformation::currentSpacing() const
{
return m_d->spacing;
}
void KisDistanceInformation::updateSpacing(const KisSpacingInformation &spacing)
{
m_d->spacing = spacing;
m_d->timeSinceSpacingUpdate = 0.0;
}
bool KisDistanceInformation::needsSpacingUpdate() const
{
return m_d->timeSinceSpacingUpdate >= m_d->spacingUpdateInterval;
}
const KisTimingInformation &KisDistanceInformation::currentTiming() const
{
return m_d->timing;
}
void KisDistanceInformation::updateTiming(const KisTimingInformation &timing)
{
m_d->timing = timing;
m_d->timeSinceTimingUpdate = 0.0;
}
bool KisDistanceInformation::needsTimingUpdate() const
{
return m_d->timeSinceTimingUpdate >= m_d->timingUpdateInterval;
}
bool KisDistanceInformation::hasLastDabInformation() const
{
return m_d->lastDabInfoValid;
}
QPointF KisDistanceInformation::lastPosition() const
{
return m_d->lastPosition;
}
-qreal KisDistanceInformation::lastTime() const
-{
- return m_d->lastTime;
-}
-
qreal KisDistanceInformation::lastDrawingAngle() const
{
return m_d->lastAngle;
}
bool KisDistanceInformation::hasLastPaintInformation() const
{
return m_d->lastPaintInfoValid;
}
const KisPaintInformation& KisDistanceInformation::lastPaintInformation() const
{
return m_d->lastPaintInformation;
}
+int KisDistanceInformation::currentDabSeqNo() const
+{
+ return m_d->currentDabSeqNo;
+}
+
bool KisDistanceInformation::isStarted() const
{
return m_d->lastPaintInfoValid;
}
void KisDistanceInformation::registerPaintedDab(const KisPaintInformation &info,
const KisSpacingInformation &spacing,
const KisTimingInformation &timing)
{
- m_d->totalDistance += KisAlgebra2D::norm(info.pos() - m_d->lastPosition);
+ m_d->totalDistance +=
+ KisAlgebra2D::norm(info.pos() - m_d->lastPosition) *
+ KisLodTransform::lodToInvScale(m_d->levelOfDetail);
m_d->lastPaintInformation = info;
m_d->lastPaintInfoValid = true;
- m_d->lastAngle = nextDrawingAngle(info.pos());
+ m_d->lastAngle = info.drawingAngle(false);
m_d->lastPosition = info.pos();
- m_d->lastTime = info.currentTime();
m_d->lastDabInfoValid = true;
m_d->spacing = spacing;
m_d->timing = timing;
+
+ m_d->currentDabSeqNo++;
}
qreal KisDistanceInformation::getNextPointPosition(const QPointF &start,
const QPointF &end,
qreal startTime,
qreal endTime)
{
// Compute interpolation factor based on distance.
qreal distanceFactor = -1.0;
if (m_d->spacing.isDistanceSpacingEnabled()) {
distanceFactor = m_d->spacing.isIsotropic() ?
getNextPointPositionIsotropic(start, end) :
getNextPointPositionAnisotropic(start, end);
}
// Compute interpolation factor based on time.
qreal timeFactor = -1.0;
if (m_d->timing.isTimedSpacingEnabled()) {
timeFactor = getNextPointPositionTimed(startTime, endTime);
}
// Return the distance-based or time-based factor, whichever is smallest.
qreal t = -1.0;
if (distanceFactor < 0.0) {
t = timeFactor;
} else if (timeFactor < 0.0) {
t = distanceFactor;
} else {
t = qMin(distanceFactor, timeFactor);
}
// If we aren't ready to paint a dab, accumulate time for the spacing/timing updates that might
// be needed between dabs.
if (t < 0.0) {
m_d->timeSinceSpacingUpdate += endTime - startTime;
m_d->timeSinceTimingUpdate += endTime - startTime;
}
// If we are ready to paint a dab, reset the accumulated time for spacing/timing updates.
else {
m_d->timeSinceSpacingUpdate = 0.0;
m_d->timeSinceTimingUpdate = 0.0;
}
return t;
}
qreal KisDistanceInformation::getNextPointPositionIsotropic(const QPointF &start,
const QPointF &end)
{
qreal distance = m_d->accumDistance.x();
qreal spacing = qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x());
if (start == end) {
return -1;
}
qreal dragVecLength = QVector2D(end - start).length();
qreal nextPointDistance = spacing - distance;
qreal t;
// nextPointDistance can sometimes be negative if the spacing info has been modified since the
// last interpolation attempt. In that case, have a point painted immediately.
if (nextPointDistance <= 0.0) {
resetAccumulators();
t = 0.0;
}
else if (nextPointDistance <= dragVecLength) {
t = nextPointDistance / dragVecLength;
resetAccumulators();
} else {
t = -1;
m_d->accumDistance.rx() += dragVecLength;
}
return t;
}
qreal KisDistanceInformation::getNextPointPositionAnisotropic(const QPointF &start,
const QPointF &end)
{
if (start == end) {
return -1;
}
qreal a_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x());
qreal b_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().y());
qreal x = m_d->accumDistance.x();
qreal y = m_d->accumDistance.y();
qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1;
// If the distance accumulator is already past the spacing ellipse, have a point painted
// immediately. This can happen if the spacing info has been modified since the last
// interpolation attempt.
if (gamma >= 0.0) {
resetAccumulators();
return 0.0;
}
static const qreal eps = 2e-3; // < 0.2 deg
qreal currentRotation = m_d->spacing.rotation();
if (m_d->spacing.coordinateSystemFlipped()) {
currentRotation = 2 * M_PI - currentRotation;
}
QPointF diff = end - start;
if (currentRotation > eps) {
QTransform rot;
// since the ellipse is symmetrical, the sign
// of rotation doesn't matter
rot.rotateRadians(currentRotation);
diff = rot.map(diff);
}
qreal dx = qAbs(diff.x());
qreal dy = qAbs(diff.y());
qreal alpha = pow2(dx * a_rev) + pow2(dy * b_rev);
qreal beta = x * dx * a_rev * a_rev + y * dy * b_rev * b_rev;
qreal D_4 = pow2(beta) - alpha * gamma;
qreal t = -1.0;
if (D_4 >= 0) {
qreal k = (-beta + qSqrt(D_4)) / alpha;
if (k >= 0.0 && k <= 1.0) {
t = k;
resetAccumulators();
} else {
m_d->accumDistance += KisAlgebra2D::abs(diff);
}
} else {
warnKrita << "BUG: No solution for elliptical spacing equation has been found. This shouldn't have happened.";
}
return t;
}
qreal KisDistanceInformation::getNextPointPositionTimed(qreal startTime,
qreal endTime)
{
// If start time is not before end time, do not interpolate.
if (!(startTime < endTime)) {
return -1.0;
}
qreal timedSpacingInterval = qBound(MIN_TIMED_INTERVAL, m_d->timing.timedSpacingInterval(),
MAX_TIMED_INTERVAL);
qreal nextPointInterval = timedSpacingInterval - m_d->accumTime;
qreal t = -1.0;
// nextPointInterval can sometimes be negative if the spacing info has been modified since the
// last interpolation attempt. In that case, have a point painted immediately.
if (nextPointInterval <= 0.0) {
resetAccumulators();
t = 0.0;
}
else if (nextPointInterval <= endTime - startTime) {
resetAccumulators();
t = nextPointInterval / (endTime - startTime);
}
else {
m_d->accumTime += endTime - startTime;
t = -1.0;
}
return t;
}
void KisDistanceInformation::resetAccumulators()
{
m_d->accumDistance = QPointF();
m_d->accumTime = 0.0;
}
-bool KisDistanceInformation::hasLockedDrawingAngle() const
+boost::optional<qreal> KisDistanceInformation::lockedDrawingAngleOptional() const
{
- return m_d->hasLockedDrawingAngle;
+ return m_d->lockedDrawingAngleOptional;
}
-qreal KisDistanceInformation::lockedDrawingAngle() const
+void KisDistanceInformation::lockCurrentDrawingAngle(const KisPaintInformation &info) const
{
- return m_d->lockedDrawingAngle;
-}
+ const qreal angle = info.drawingAngle(false);
-void KisDistanceInformation::setLockedDrawingAngle(qreal angle)
-{
- m_d->hasLockedDrawingAngle = true;
- m_d->lockedDrawingAngle = angle;
-}
+ qreal newAngle = angle;
-qreal KisDistanceInformation::nextDrawingAngle(const QPointF &nextPos,
- bool considerLockedAngle) const
-{
- if (!m_d->lastDabInfoValid) {
- warnKrita << "KisDistanceInformation::nextDrawingAngle()" << "No last dab data";
- return 0.0;
- }
+ if (m_d->lockedDrawingAngleOptional) {
+ const qreal stabilizingCoeff = 20.0;
+ const qreal dist = stabilizingCoeff * m_d->spacing.scalarApprox();
+ const qreal alpha = qMax(0.0, dist - scalarDistanceApprox()) / dist;
- // Compute the drawing angle. If the new position is the same as the previous position, an angle
- // can't be computed. In that case, act as if the angle is the same as in the previous dab.
- return drawingAngleImpl(m_d->lastPosition, nextPos, considerLockedAngle, m_d->lastAngle);
-}
+ const qreal oldAngle = *m_d->lockedDrawingAngleOptional;
-QPointF KisDistanceInformation::nextDrawingDirectionVector(const QPointF &nextPos,
- bool considerLockedAngle) const
-{
- if (!m_d->lastDabInfoValid) {
- warnKrita << "KisDistanceInformation::nextDrawingDirectionVector()" << "No last dab data";
- return QPointF(1.0, 0.0);
+ if (shortestAngularDistance(oldAngle, newAngle) < M_PI / 6) {
+ newAngle = (1.0 - alpha) * oldAngle + alpha * newAngle;
+ } else {
+ newAngle = oldAngle;
+ }
}
- // Compute the direction vector. If the new position is the same as the previous position, a
- // direction can't be computed. In that case, act as if the direction is the same as in the
- // previous dab.
- return drawingDirectionVectorImpl(m_d->lastPosition, nextPos, considerLockedAngle,
- m_d->lastAngle);
+ m_d->lockedDrawingAngleOptional = newAngle;
}
+
qreal KisDistanceInformation::scalarDistanceApprox() const
{
return m_d->totalDistance;
}
-qreal KisDistanceInformation::drawingAngleImpl(const QPointF &start, const QPointF &end,
- bool considerLockedAngle, qreal defaultAngle) const
-{
- if (m_d->hasLockedDrawingAngle && considerLockedAngle) {
- return m_d->lockedDrawingAngle;
- }
-
- // If the start and end positions are the same, we can't compute an angle. In that case, use the
- // provided default.
- return KisAlgebra2D::directionBetweenPoints(start, end, defaultAngle);
-}
-
-QPointF KisDistanceInformation::drawingDirectionVectorImpl(const QPointF &start, const QPointF &end,
- bool considerLockedAngle,
- qreal defaultAngle) const
-{
- if (m_d->hasLockedDrawingAngle && considerLockedAngle) {
- return QPointF(cos(m_d->lockedDrawingAngle), sin(m_d->lockedDrawingAngle));
- }
-
- // If the start and end positions are the same, we can't compute a drawing direction. In that
- // case, use the provided default.
- if (KisAlgebra2D::fuzzyPointCompare(start, end)) {
- return QPointF(cos(defaultAngle), sin(defaultAngle));
- }
-
- const QPointF diff(end - start);
- return KisAlgebra2D::normalize(diff);
-}
diff --git a/libs/image/kis_distance_information.h b/libs/image/kis_distance_information.h
index 5c8462e2a4..2082aa59d6 100644
--- a/libs/image/kis_distance_information.h
+++ b/libs/image/kis_distance_information.h
@@ -1,203 +1,190 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DISTANCE_INFORMATION_H_
#define _KIS_DISTANCE_INFORMATION_H_
#include <QPointF>
#include <QVector2D>
#include <QDomDocument>
#include <QDomElement>
#include "kritaimage_export.h"
+#include <boost/optional.hpp>
class KisPaintInformation;
class KisSpacingInformation;
class KisTimingInformation;
class KisDistanceInformation;
/**
* Represents some information that can be used to initialize a KisDistanceInformation object. The
* main purpose of this class is to allow serialization of KisDistanceInformation initial settings
* to XML.
*/
class KRITAIMAGE_EXPORT KisDistanceInitInfo {
public:
/**
* Creates a KisDistanceInitInfo with no initial last dab information, and spacing and timing
* update intervals set to LONG_TIME.
*/
explicit KisDistanceInitInfo();
/**
* Creates a KisDistanceInitInfo with no initial last dab information, and the specified spacing
* and timing update intervals.
*/
- explicit KisDistanceInitInfo(qreal spacingUpdateInterval, qreal timingUpdateInterval);
+ explicit KisDistanceInitInfo(qreal spacingUpdateInterval, qreal timingUpdateInterval, int currentDabSeqNo);
/**
* Creates a KisDistanceInitInfo with the specified last dab information, and spacing and timing
* update intervals set to LONG_TIME.
*/
- explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle);
+ explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastAngle, int currentDabSeqNo);
/**
* Creates a KisDistanceInitInfo with the specified last dab information and spacing and timing
* update intervals.
*/
- explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle,
- qreal spacingUpdateInterval, qreal timingUpdateInterval);
+ explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastAngle,
+ qreal spacingUpdateInterval, qreal timingUpdateInterval, int currentDabSeqNo);
KisDistanceInitInfo(const KisDistanceInitInfo &rhs);
~KisDistanceInitInfo();
bool operator==(const KisDistanceInitInfo &other) const;
bool operator!=(const KisDistanceInitInfo &other) const;
KisDistanceInitInfo &operator=(const KisDistanceInitInfo &rhs);
/**
* Constructs a KisDistanceInformation with initial settings based on this object.
*/
KisDistanceInformation makeDistInfo();
void toXML(QDomDocument &doc, QDomElement &elt) const;
static KisDistanceInitInfo fromXML(const QDomElement &elt);
private:
struct Private;
Private * const m_d;
};
/**
* This structure keeps track of distance and timing information during a stroke, e.g. the time or
* distance moved since the last dab.
*/
class KRITAIMAGE_EXPORT KisDistanceInformation {
public:
KisDistanceInformation();
- KisDistanceInformation(qreal spacingUpdateInterval, qreal timingUpdateInterval);
- KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle);
+ KisDistanceInformation(qreal spacingUpdateInterval, qreal timingUpdateInterval, int currentDabSeqNo = 0);
+ KisDistanceInformation(const QPointF &lastPosition, qreal lastAngle);
/**
* @param spacingUpdateInterval The amount of time allowed between spacing updates, in
* milliseconds. Use LONG_TIME to only allow spacing updates when a
* dab is painted.
* @param timingUpdateInterval The amount of time allowed between time-based spacing updates, in
* milliseconds. Use LONG_TIME to only allow timing updates when a
* dab is painted.
*/
- KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle,
- qreal spacingUpdateInterval, qreal timingUpdateInterval);
+ KisDistanceInformation(const QPointF &lastPosition, qreal lastAngle,
+ qreal spacingUpdateInterval, qreal timingUpdateInterval, int currentDabSeqNo);
KisDistanceInformation(const KisDistanceInformation &rhs);
KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail);
KisDistanceInformation& operator=(const KisDistanceInformation &rhs);
~KisDistanceInformation();
const KisSpacingInformation& currentSpacing() const;
void updateSpacing(const KisSpacingInformation &spacing);
/**
* Returns true if this KisDistanceInformation should have its spacing information updated
* immediately (regardless of whether a dab is ready to be painted).
*/
bool needsSpacingUpdate() const;
const KisTimingInformation &currentTiming() const;
void updateTiming(const KisTimingInformation &timing);
/**
* Returns true if this KisDistanceInformation should have its timing information updated
* immediately (regardless of whether a dab is ready to be painted).
*/
bool needsTimingUpdate() const;
bool hasLastDabInformation() const;
QPointF lastPosition() const;
- qreal lastTime() const;
qreal lastDrawingAngle() const;
bool hasLastPaintInformation() const;
const KisPaintInformation& lastPaintInformation() const;
+ int currentDabSeqNo() const;
+
/**
* @param spacing The new effective spacing after the dab. (Painting a dab is always supposed to
* cause a spacing update.)
* @param timing The new effective timing after the dab. (Painting a dab is always supposed to
* cause a timing update.)
*/
void registerPaintedDab(const KisPaintInformation &info,
const KisSpacingInformation &spacing,
const KisTimingInformation &timing);
qreal getNextPointPosition(const QPointF &start,
const QPointF &end,
qreal startTime,
qreal endTime);
/**
* \return true if at least one dab has been painted with this
* distance information
*/
bool isStarted() const;
- bool hasLockedDrawingAngle() const;
- qreal lockedDrawingAngle() const;
- void setLockedDrawingAngle(qreal angle);
+ boost::optional<qreal> lockedDrawingAngleOptional() const;
/**
- * Computes the next drawing angle assuming that the next painting position will be nextPos.
- * This method should not be called when hasLastDabInformation() is false.
+ * Lock current drawing angle for the rest of the stroke. The new value is blended
+ * into the result proportional to the length of the stroke.
*/
- qreal nextDrawingAngle(const QPointF &nextPos, bool considerLockedAngle = true) const;
-
- /**
- * Returns a unit vector pointing in the direction that would have been indicated by a call to
- * nextDrawingAngle. This method should not be called when hasLastDabInformation() is false.
- */
- QPointF nextDrawingDirectionVector(const QPointF &nextPos,
- bool considerLockedAngle = true) const;
+ void lockCurrentDrawingAngle(const KisPaintInformation &info) const;
qreal scalarDistanceApprox() const;
- void overrideLastValues(const QPointF &lastPosition, qreal lastTime, qreal lastAngle);
+ void overrideLastValues(const QPointF &lastPosition, qreal lastAngle);
private:
qreal getNextPointPositionIsotropic(const QPointF &start,
const QPointF &end);
qreal getNextPointPositionAnisotropic(const QPointF &start,
const QPointF &end);
qreal getNextPointPositionTimed(qreal startTime,
qreal endTime);
void resetAccumulators();
- qreal drawingAngleImpl(const QPointF &start, const QPointF &end,
- bool considerLockedAngle = true, qreal defaultAngle = 0.0) const;
- QPointF drawingDirectionVectorImpl(const QPointF &start, const QPointF &end,
- bool considerLockedAngle = true,
- qreal defaultAngle = 0.0) const;
-
private:
struct Private;
Private * const m_d;
};
#endif
diff --git a/libs/image/kis_fixed_paint_device.cpp b/libs/image/kis_fixed_paint_device.cpp
index 2f8b44638e..514c3fa76d 100644
--- a/libs/image/kis_fixed_paint_device.cpp
+++ b/libs/image/kis_fixed_paint_device.cpp
@@ -1,317 +1,351 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_fixed_paint_device.h"
#include <KoColorSpaceRegistry.h>
#include <KoColor.h>
#include <KoColorModelStandardIds.h>
#include "kis_debug.h"
KisFixedPaintDevice::KisFixedPaintDevice(const KoColorSpace* colorSpace)
: m_colorSpace(colorSpace)
{
}
KisFixedPaintDevice::~KisFixedPaintDevice()
{
}
KisFixedPaintDevice::KisFixedPaintDevice(const KisFixedPaintDevice& rhs)
: KisShared()
{
m_bounds = rhs.m_bounds;
m_colorSpace = rhs.m_colorSpace;
m_data = rhs.m_data;
}
KisFixedPaintDevice& KisFixedPaintDevice::operator=(const KisFixedPaintDevice& rhs)
{
m_bounds = rhs.m_bounds;
m_colorSpace = rhs.m_colorSpace;
- m_data = rhs.m_data;
+
+
+ const int referenceSize = m_bounds.height() * m_bounds.width() * pixelSize();
+
+ if (m_data.size() >= referenceSize) {
+ memcpy(m_data.data(), rhs.m_data.data(), referenceSize);
+ } else {
+ m_data = rhs.m_data;
+ }
+
return *this;
}
void KisFixedPaintDevice::setRect(const QRect& rc)
{
m_bounds = rc;
}
QRect KisFixedPaintDevice::bounds() const
{
return m_bounds;
}
int KisFixedPaintDevice::allocatedPixels() const
{
return m_data.size() / m_colorSpace->pixelSize();
}
quint32 KisFixedPaintDevice::pixelSize() const
{
return m_colorSpace->pixelSize();
}
bool KisFixedPaintDevice::initialize(quint8 defaultValue)
{
m_data.fill(defaultValue, m_bounds.height() * m_bounds.width() * pixelSize());
return true;
}
+void KisFixedPaintDevice::reallocateBufferWithoutInitialization()
+{
+ const int referenceSize = m_bounds.height() * m_bounds.width() * pixelSize();
+
+ if (referenceSize != m_data.size()) {
+ m_data.resize(m_bounds.height() * m_bounds.width() * pixelSize());
+ }
+}
+
+void KisFixedPaintDevice::lazyGrowBufferWithoutInitialization()
+{
+ const int referenceSize = m_bounds.height() * m_bounds.width() * pixelSize();
+
+ if (m_data.size() < referenceSize) {
+ m_data.resize(referenceSize);
+ }
+}
+
quint8* KisFixedPaintDevice::data()
{
- return m_data.data();
+ return (quint8*) m_data.data();
+}
+
+const quint8 *KisFixedPaintDevice::constData() const
+{
+ return (const quint8*) m_data.constData();
}
quint8* KisFixedPaintDevice::data() const
{
- return const_cast<quint8*>(m_data.data());
+ return const_cast<quint8*>((quint8*)m_data.data());
}
void KisFixedPaintDevice::convertTo(const KoColorSpace* dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags)
{
if (*m_colorSpace == *dstColorSpace) {
return;
}
quint32 size = m_bounds.width() * m_bounds.height();
- QVector<quint8> dstData(size * dstColorSpace->pixelSize());
+ QByteArray dstData;
+
+ // make sure that we are not initializing the destination pixels!
+ dstData.resize(size * dstColorSpace->pixelSize());
- m_colorSpace->convertPixelsTo(data(), dstData.data(),
+ m_colorSpace->convertPixelsTo(constData(), (quint8*)dstData.data(),
dstColorSpace,
size,
renderingIntent,
conversionFlags);
m_colorSpace = dstColorSpace;
m_data = dstData;
-
}
void KisFixedPaintDevice::convertFromQImage(const QImage& _image, const QString &srcProfileName)
{
QImage image = _image;
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
setRect(image.rect());
- initialize();
+ lazyGrowBufferWithoutInitialization();
// Don't convert if not no profile is given and both paint dev and qimage are rgba.
if (srcProfileName.isEmpty() && colorSpace()->id() == "RGBA") {
memcpy(data(), image.constBits(), image.byteCount());
} else {
KoColorSpaceRegistry::instance()
->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), srcProfileName)
->convertPixelsTo(image.constBits(), data(), colorSpace(), image.width() * image.height(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
}
}
QImage KisFixedPaintDevice::convertToQImage(const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent intent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
qint32 x1;
qint32 y1;
qint32 w;
qint32 h;
x1 = m_bounds.x();
y1 = m_bounds.y();
w = m_bounds.width();
h = m_bounds.height();
return convertToQImage(dstProfile, x1, y1, w, h, intent, conversionFlags);
}
QImage KisFixedPaintDevice::convertToQImage(const KoColorProfile * dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent intent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
Q_ASSERT( m_bounds.contains(QRect(x1,y1,w,h)) );
if (w < 0)
return QImage();
if (h < 0)
return QImage();
if (QRect(x1, y1, w, h) == m_bounds) {
- return colorSpace()->convertToQImage(data(), w, h, dstProfile,
+ return colorSpace()->convertToQImage(constData(), w, h, dstProfile,
intent, conversionFlags);
} else {
try {
// XXX: fill the image row by row!
- int pSize = pixelSize();
- int deviceWidth = m_bounds.width();
+ const int pSize = pixelSize();
+ const int deviceWidth = m_bounds.width();
quint8* newData = new quint8[w * h * pSize];
- quint8* srcPtr = data() + x1 * pSize + y1 * deviceWidth * pSize;
+ const quint8* srcPtr = constData() + x1 * pSize + y1 * deviceWidth * pSize;
quint8* dstPtr = newData;
// copy the right area out of the paint device into data
for (int row = 0; row < h; row++) {
memcpy(dstPtr, srcPtr, w * pSize);
srcPtr += deviceWidth * pSize;
dstPtr += w * pSize;
}
QImage image = colorSpace()->convertToQImage(newData, w, h, dstProfile, intent, conversionFlags);
return image;
}
catch(std::bad_alloc) {
return QImage();
}
}
}
void KisFixedPaintDevice::clear(const QRect & rc)
{
KoColor c(Qt::black, m_colorSpace);
quint8* black = new quint8[pixelSize()];
memcpy(black, c.data(), m_colorSpace->pixelSize());
m_colorSpace->setOpacity(black, OPACITY_TRANSPARENT_U8, 1);
fill(rc.x(), rc.y(), rc.width(), rc.height(), black);
delete[] black;
}
void KisFixedPaintDevice::fill(const QRect &rc, const KoColor &color)
{
KoColor realColor(color);
realColor.convertTo(colorSpace());
fill(rc.x(), rc.y(), rc.width(), rc.height(), realColor.data());
}
void KisFixedPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel)
{
if (m_data.isEmpty() || m_bounds.isEmpty()) {
setRect(QRect(x, y, w, h));
- initialize();
+ reallocateBufferWithoutInitialization();
}
QRect rc(x, y, w, h);
if (!m_bounds.contains(rc)) {
rc = m_bounds;
}
quint8 pixelSize = m_colorSpace->pixelSize();
quint8* dabPointer = data();
if (rc.contains(m_bounds)) {
for (int i = 0; i < w * h ; ++i) {
memcpy(dabPointer, fillPixel, pixelSize);
dabPointer += pixelSize;
}
} else {
int deviceWidth = bounds().width();
quint8* rowPointer = dabPointer + ((y - bounds().y()) * deviceWidth + (x - bounds().x())) * pixelSize;
for (int row = 0; row < h; row++) {
for (int col = 0; col < w; col++) {
memcpy(rowPointer + col * pixelSize , fillPixel, pixelSize);
}
rowPointer += deviceWidth * pixelSize;
}
}
}
void KisFixedPaintDevice::readBytes(quint8* dstData, qint32 x, qint32 y, qint32 w, qint32 h) const
{
if (m_data.isEmpty() || m_bounds.isEmpty()) {
return;
}
QRect rc(x, y, w, h);
if (!m_bounds.contains(rc)){
return;
}
- quint8 pixelSize = m_colorSpace->pixelSize();
- quint8* dabPointer = data();
+ const int pixelSize = m_colorSpace->pixelSize();
+ const quint8* dabPointer = constData();
if (rc == m_bounds) {
- memcpy(dstData, dabPointer, pixelSize * w * h);
- }
- else
- {
- int deviceWidth = bounds().width();
- quint8* rowPointer = dabPointer + ((y - bounds().y()) * deviceWidth + (x - bounds().x())) * pixelSize;
+ memcpy(dstData, dabPointer, pixelSize * w * h);
+ } else {
+ int deviceWidth = m_bounds.width();
+ const quint8* rowPointer = dabPointer + ((y - bounds().y()) * deviceWidth + (x - bounds().x())) * pixelSize;
for (int row = 0; row < h; row++) {
- memcpy(dstData,rowPointer, w * pixelSize);
+ memcpy(dstData, rowPointer, w * pixelSize);
rowPointer += deviceWidth * pixelSize;
dstData += w * pixelSize;
}
}
}
void KisFixedPaintDevice::mirror(bool horizontal, bool vertical)
{
if (!horizontal && !vertical){
return;
}
int pixelSize = m_colorSpace->pixelSize();
int w = m_bounds.width();
int h = m_bounds.height();
if (horizontal){
int rowSize = pixelSize * w;
quint8 * dabPointer = data();
quint8 * row = new quint8[ rowSize ];
quint8 * mirror = 0;
for (int y = 0; y < h ; y++){
+ // TODO: implement better flipping of the data
+
memcpy(row, dabPointer, rowSize);
mirror = row;
mirror += (w-1) * pixelSize;
for (int x = 0; x < w; x++){
memcpy(dabPointer,mirror,pixelSize);
dabPointer += pixelSize;
mirror -= pixelSize;
}
}
delete [] row;
}
if (vertical){
int rowsToMove = h / 2;
int rowSize = pixelSize * w;
quint8 * startRow = data();
quint8 * endRow = data() + (h-1) * w * pixelSize;
quint8 * row = new quint8[ rowSize ];
for (int y = 0; y < rowsToMove; y++){
memcpy(row, startRow, rowSize);
memcpy(startRow, endRow, rowSize);
memcpy(endRow, row, rowSize);
startRow += rowSize;
endRow -= rowSize;
}
delete [] row;
}
}
diff --git a/libs/image/kis_fixed_paint_device.h b/libs/image/kis_fixed_paint_device.h
index f09337bccc..d46838bac2 100644
--- a/libs/image/kis_fixed_paint_device.h
+++ b/libs/image/kis_fixed_paint_device.h
@@ -1,187 +1,202 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_FIXED_PAINT_DEVICE_H
#define KIS_FIXED_PAINT_DEVICE_H
#include <kritaimage_export.h>
#include <KoColorSpace.h>
#include "kis_shared.h"
#include <kis_shared_ptr.h>
#include <QRect>
#include <QImage>
#include <QVector>
class KoColor;
/**
* A fixed paint device is a simple paint device that consists of an array
* of bytes and a rectangle. It cannot grow, it cannot shrink, all you can
* do is fill the paint device with the right bytes and use it as an argument
* to KisPainter or use the bytes as an argument to KoColorSpace functions.
*/
class KRITAIMAGE_EXPORT KisFixedPaintDevice : public KisShared
{
public:
KisFixedPaintDevice(const KoColorSpace* colorSpace);
virtual ~KisFixedPaintDevice();
/**
* Deep copy the fixed paint device, including the data.
*/
KisFixedPaintDevice(const KisFixedPaintDevice& rhs);
/**
* Deep copy the fixed paint device, including the data.
*/
KisFixedPaintDevice& operator=(const KisFixedPaintDevice& rhs);
/**
* setRect sets the rect of the fixed paint device to rect.
* This will _not_ create the associated data area.
*
* @rect the bounds in pixels. The x,y of the rect represent the origin
* of the fixed paint device.
*/
void setRect(const QRect& rc);
/**
* @return the rect that the data represents
*/
QRect bounds() const;
/**
* @return the amount of allocated pixels (you can fake the size with setRect/bounds)
* It is useful to know the accumulated memory size in pixels (not in bytes) for optimizations to avoid re-allocation.
*/
int allocatedPixels() const;
/**
* @return the pixelSize associated with this fixed paint device.
*/
quint32 pixelSize() const;
const KoColorSpace* colorSpace() const {
return m_colorSpace;
}
/**
* initializes the paint device.
*
* @param defaultValue the default byte with which all pixels will be filled.
* @return false if the allocation failed.
*/
bool initialize(quint8 defaultValue = 0);
+ /**
+ * Changed the size of the internal buffer to accomodate the exact number of bytes
+ * needed to store area bounds(). The allocated data is *not* initialized!
+ */
+ void reallocateBufferWithoutInitialization();
+
+ /**
+ * If the size of the internal buffer is smller than the one needed to accomodate
+ * bounds(), resize the buffer. Otherwise, do nothing. The allocated data is neither
+ * copying or initialized!
+ */
+ void lazyGrowBufferWithoutInitialization();
+
/**
* @return a pointer to the beginning of the data associated with this fixed paint device.
*/
quint8* data();
+ const quint8* constData() const;
+
quint8* data() const;
/**
* Read the bytes representing the rectangle described by x, y, w, h into
* data. If data is not big enough, Krita will gladly overwrite the rest
* of your precious memory.
*
* Since this is a copy, you need to make sure you have enough memory.
*
* The reading is done only if the rectangular area x,y,w,h is inside the bounds of the device
* and the device is not empty
*/
void readBytes(quint8 * dstData, qint32 x, qint32 y, qint32 w, qint32 h) const;
/**
* Converts the paint device to a different colorspace
*/
void convertTo(const KoColorSpace * dstColorSpace = 0,
KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags());
/**
* Fill this paint device with the data from image
*
* @param srcProfileName name of the RGB profile to interpret the image as. 0 is interpreted as sRGB
*/
virtual void convertFromQImage(const QImage& image, const QString &srcProfileName);
/**
* Create an RGBA QImage from a rectangle in the paint device.
*
* @param x Left coordinate of the rectangle
* @param y Top coordinate of the rectangle
* @param w Width of the rectangle in pixels
* @param h Height of the rectangle in pixels
* @param dstProfile RGB profile to use in conversion. May be 0, in which
* case it's up to the color strategy to choose a profile (most
* like sRGB).
*/
virtual QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h,
KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const;
/**
* Create an RGBA QImage from a rectangle in the paint device. The
* rectangle is defined by the parent image's bounds.
*
* @param dstProfile RGB profile to use in conversion. May be 0, in which
* case it's up to the color strategy to choose a profile (most
* like sRGB).
*/
virtual QImage convertToQImage(const KoColorProfile *dstProfile,
KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const;
/**
* Clear the given rectangle to transparent black.
*
* XXX: this will not (yet) expand the paint device to contain the specified rect
* but if the paintdevice has not been initialized, it will be.
*/
void clear(const QRect & rc);
/**
* Fill the given rectangle with the given pixel. This does not take the
* selection into account.
*
* XXX: this will not (yet) expand the paint device to contain the specified rect
* but if the paintdevice has not been initialized, it will be.
*/
void fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel);
void fill(const QRect &rc, const KoColor &color);
/**
* Mirrors the device.
*/
void mirror(bool horizontal, bool vertical);
private:
const KoColorSpace* m_colorSpace;
QRect m_bounds;
- QVector<quint8> m_data;
+ QByteArray m_data;
};
#endif
diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc
index 2ea1b7a400..cf0c812bb7 100644
--- a/libs/image/kis_image.cc
+++ b/libs/image/kis_image.cc
@@ -1,1726 +1,1729 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_image.h"
#include <KoConfig.h> // WORDS_BIGENDIAN
#include <stdlib.h>
#include <math.h>
#include <QImage>
#include <QPainter>
#include <QSize>
#include <QDateTime>
#include <QRect>
#include <QRegion>
#include <QtConcurrent>
#include <klocalizedstring.h>
#include "KoColorSpaceRegistry.h"
#include "KoColor.h"
#include "KoColorProfile.h"
#include <KoCompositeOpRegistry.h>
#include "KisProofingConfiguration.h"
#include "recorder/kis_action_recorder.h"
#include "kis_adjustment_layer.h"
#include "kis_annotation.h"
#include "kis_change_profile_visitor.h"
#include "kis_colorspace_convert_visitor.h"
#include "kis_count_visitor.h"
#include "kis_filter_strategy.h"
#include "kis_group_layer.h"
#include "commands/kis_image_commands.h"
#include "kis_layer.h"
#include "kis_meta_data_merge_strategy_registry.h"
#include "kis_name_server.h"
#include "kis_paint_layer.h"
#include "kis_painter.h"
#include "kis_selection.h"
#include "kis_transaction.h"
#include "kis_meta_data_merge_strategy.h"
#include "kis_memory_statistics_server.h"
#include "kis_image_config.h"
#include "kis_update_scheduler.h"
#include "kis_image_signal_router.h"
#include "kis_image_animation_interface.h"
#include "kis_stroke_strategy.h"
#include "kis_image_barrier_locker.h"
#include "kis_undo_stores.h"
#include "kis_legacy_undo_adapter.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_transform_worker.h"
#include "kis_processing_applicator.h"
#include "processing/kis_crop_processing_visitor.h"
#include "processing/kis_crop_selections_processing_visitor.h"
#include "processing/kis_transform_processing_visitor.h"
#include "commands_new/kis_image_resize_command.h"
#include "commands_new/kis_image_set_resolution_command.h"
#include "commands_new/kis_activate_selection_mask_command.h"
#include "kis_composite_progress_proxy.h"
#include "kis_layer_composition.h"
#include "kis_wrapped_rect.h"
#include "kis_crop_saved_extra_data.h"
#include "kis_layer_utils.h"
#include "kis_lod_transform.h"
#include "kis_suspend_projection_updates_stroke_strategy.h"
#include "kis_sync_lod_cache_stroke_strategy.h"
#include "kis_projection_updates_filter.h"
#include "kis_layer_projection_plane.h"
#include "kis_update_time_monitor.h"
#include "kis_image_barrier_locker.h"
#include <QtCore>
#include <functional>
#include "kis_time_range.h"
// #define SANITY_CHECKS
#ifdef SANITY_CHECKS
#define SANITY_CHECK_LOCKED(name) \
if (!locked()) warnKrita() << "Locking policy failed:" << name \
<< "has been called without the image" \
"being locked";
#else
#define SANITY_CHECK_LOCKED(name)
#endif
struct KisImageSPStaticRegistrar {
KisImageSPStaticRegistrar() {
qRegisterMetaType<KisImageSP>("KisImageSP");
}
};
static KisImageSPStaticRegistrar __registrar;
class KisImage::KisImagePrivate
{
public:
KisImagePrivate(KisImage *_q, qint32 w, qint32 h,
const KoColorSpace *c,
KisUndoStore *undo,
KisImageAnimationInterface *_animationInterface)
: q(_q)
, lockedForReadOnly(false)
, width(w)
, height(h)
, colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8())
, nserver(1)
, undoStore(undo ? undo : new KisDumbUndoStore())
, legacyUndoAdapter(undoStore.data(), _q)
, postExecutionUndoAdapter(undoStore.data(), _q)
, recorder(_q)
, signalRouter(_q)
, animationInterface(_animationInterface)
, scheduler(_q, _q)
, axesCenter(QPointF(0.5, 0.5))
{
{
KisImageConfig cfg;
if (cfg.enableProgressReporting()) {
scheduler.setProgressProxy(&compositeProgressProxy);
}
// Each of these lambdas defines a new factory function.
scheduler.setLod0ToNStrokeStrategyFactory(
[=](bool forgettable) {
return KisLodSyncPair(
new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable),
KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q)));
});
scheduler.setSuspendUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true),
KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q)));
});
scheduler.setResumeUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false),
KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q)));
});
}
connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged()));
}
~KisImagePrivate() {
/**
* Stop animation interface. It may use the rootLayer.
*/
delete animationInterface;
/**
* First delete the nodes, while strokes
* and undo are still alive
*/
rootLayer.clear();
}
KisImage *q;
quint32 lockCount = 0;
bool lockedForReadOnly;
qint32 width;
qint32 height;
double xres = 1.0;
double yres = 1.0;
const KoColorSpace * colorSpace;
KisProofingConfigurationSP proofingConfig;
KisSelectionSP deselectedGlobalSelection;
KisGroupLayerSP rootLayer; // The layers are contained in here
QList<KisLayerCompositionSP> compositions;
KisNodeSP isolatedRootNode;
bool wrapAroundModePermitted = false;
KisNameServer nserver;
QScopedPointer<KisUndoStore> undoStore;
KisLegacyUndoAdapter legacyUndoAdapter;
KisPostExecutionUndoAdapter postExecutionUndoAdapter;
KisActionRecorder recorder;
vKisAnnotationSP annotations;
QAtomicInt disableUIUpdateSignals;
KisProjectionUpdatesFilterSP projectionUpdatesFilter;
KisImageSignalRouter signalRouter;
KisImageAnimationInterface *animationInterface;
KisUpdateScheduler scheduler;
QAtomicInt disableDirtyRequests;
KisCompositeProgressProxy compositeProgressProxy;
bool blockLevelOfDetail = false;
QPointF axesCenter;
bool tryCancelCurrentStrokeAsync();
void notifyProjectionUpdatedInPatches(const QRect &rc);
};
KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name)
: QObject(0)
, KisShared()
, m_d(new KisImagePrivate(this, width, height,
colorSpace, undoStore,
new KisImageAnimationInterface(this)))
{
// make sure KisImage belongs to the GUI thread
moveToThread(qApp->thread());
setObjectName(name);
setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8));
}
KisImage::~KisImage()
{
dbgImage << "deleting kisimage" << objectName();
/**
* Request the tools to end currently running strokes
*/
waitForDone();
delete m_d;
disconnect(); // in case Qt gets confused
}
KisImage *KisImage::clone(bool exactCopy)
{
return new KisImage(*this, 0, exactCopy);
}
KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy)
: KisNodeFacade(),
KisNodeGraphListener(),
KisShared(),
m_d(new KisImagePrivate(this,
rhs.width(), rhs.height(),
rhs.colorSpace(),
undoStore ? undoStore : new KisDumbUndoStore(),
new KisImageAnimationInterface(*rhs.animationInterface(), this)))
{
// make sure KisImage belongs to the GUI thread
moveToThread(qApp->thread());
setObjectName(rhs.objectName());
m_d->xres = rhs.m_d->xres;
m_d->yres = rhs.m_d->yres;
if (rhs.m_d->proofingConfig) {
m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig));
}
KisNodeSP newRoot = rhs.root()->clone();
newRoot->setGraphListener(this);
newRoot->setImage(this);
m_d->rootLayer = dynamic_cast<KisGroupLayer*>(newRoot.data());
setRoot(newRoot);
if (exactCopy) {
QQueue<KisNodeSP> linearizedNodes;
KisLayerUtils::recursiveApplyNodes(rhs.root(),
[&linearizedNodes](KisNodeSP node) {
linearizedNodes.enqueue(node);
});
KisLayerUtils::recursiveApplyNodes(newRoot,
[&linearizedNodes](KisNodeSP node) {
KisNodeSP refNode = linearizedNodes.dequeue();
node->setUuid(refNode->uuid());
});
}
Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) {
m_d->compositions << toQShared(new KisLayerComposition(*comp, this));
}
rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver);
vKisAnnotationSP newAnnotations;
Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) {
newAnnotations << annotation->clone();
}
m_d->annotations = newAnnotations;
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests);
m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail;
}
void KisImage::aboutToAddANode(KisNode *parent, int index)
{
KisNodeGraphListener::aboutToAddANode(parent, index);
SANITY_CHECK_LOCKED("aboutToAddANode");
}
void KisImage::nodeHasBeenAdded(KisNode *parent, int index)
{
KisNodeGraphListener::nodeHasBeenAdded(parent, index);
SANITY_CHECK_LOCKED("nodeHasBeenAdded");
m_d->signalRouter.emitNodeHasBeenAdded(parent, index);
KisNodeSP newNode = parent->at(index);
if (!dynamic_cast<KisSelectionMask*>(newNode.data())) {
stopIsolatedMode();
}
}
void KisImage::aboutToRemoveANode(KisNode *parent, int index)
{
KisNodeSP deletedNode = parent->at(index);
if (!dynamic_cast<KisSelectionMask*>(deletedNode.data())) {
stopIsolatedMode();
}
KisNodeGraphListener::aboutToRemoveANode(parent, index);
SANITY_CHECK_LOCKED("aboutToRemoveANode");
m_d->signalRouter.emitAboutToRemoveANode(parent, index);
}
void KisImage::nodeChanged(KisNode* node)
{
KisNodeGraphListener::nodeChanged(node);
requestStrokeEnd();
m_d->signalRouter.emitNodeChanged(node);
}
void KisImage::invalidateAllFrames()
{
invalidateFrames(KisTimeRange::infinite(0), QRect());
}
KisSelectionSP KisImage::globalSelection() const
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (selectionMask) {
return selectionMask->selection();
} else {
return 0;
}
}
void KisImage::setGlobalSelection(KisSelectionSP globalSelection)
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (!globalSelection) {
if (selectionMask) {
removeNode(selectionMask);
}
}
else {
if (!selectionMask) {
selectionMask = new KisSelectionMask(this);
selectionMask->initSelection(m_d->rootLayer);
addNode(selectionMask);
// If we do not set the selection now, the setActive call coming next
// can be very, very expensive, depending on the size of the image.
selectionMask->setSelection(globalSelection);
selectionMask->setActive(true);
}
else {
selectionMask->setSelection(globalSelection);
}
Q_ASSERT(m_d->rootLayer->childCount() > 0);
Q_ASSERT(m_d->rootLayer->selectionMask());
}
m_d->deselectedGlobalSelection = 0;
m_d->legacyUndoAdapter.emitSelectionChanged();
}
void KisImage::deselectGlobalSelection()
{
KisSelectionSP savedSelection = globalSelection();
setGlobalSelection(0);
m_d->deselectedGlobalSelection = savedSelection;
}
bool KisImage::canReselectGlobalSelection()
{
return m_d->deselectedGlobalSelection;
}
void KisImage::reselectGlobalSelection()
{
if(m_d->deselectedGlobalSelection) {
setGlobalSelection(m_d->deselectedGlobalSelection);
}
}
QString KisImage::nextLayerName(const QString &_baseName) const
{
QString baseName = _baseName;
if (m_d->nserver.currentSeed() == 0) {
m_d->nserver.number();
return i18n("background");
}
if (baseName.isEmpty()) {
baseName = i18n("Layer");
}
return QString("%1 %2").arg(baseName).arg(m_d->nserver.number());
}
void KisImage::rollBackLayerName()
{
m_d->nserver.rollback();
}
KisCompositeProgressProxy* KisImage::compositeProgressProxy()
{
return &m_d->compositeProgressProxy;
}
bool KisImage::locked() const
{
return m_d->lockCount != 0;
}
void KisImage::barrierLock(bool readOnly)
{
if (!locked()) {
requestStrokeEnd();
m_d->scheduler.barrierLock();
m_d->lockedForReadOnly = readOnly;
} else {
m_d->lockedForReadOnly &= readOnly;
}
m_d->lockCount++;
}
bool KisImage::tryBarrierLock(bool readOnly)
{
bool result = true;
if (!locked()) {
result = m_d->scheduler.tryBarrierLock();
m_d->lockedForReadOnly = readOnly;
}
if (result) {
m_d->lockCount++;
m_d->lockedForReadOnly &= readOnly;
}
return result;
}
bool KisImage::isIdle(bool allowLocked)
{
return (allowLocked || !locked()) && m_d->scheduler.isIdle();
}
void KisImage::lock()
{
if (!locked()) {
requestStrokeEnd();
m_d->scheduler.lock();
}
m_d->lockCount++;
m_d->lockedForReadOnly = false;
}
void KisImage::unlock()
{
Q_ASSERT(locked());
if (locked()) {
m_d->lockCount--;
if (m_d->lockCount == 0) {
m_d->scheduler.unlock(!m_d->lockedForReadOnly);
}
}
}
void KisImage::blockUpdates()
{
m_d->scheduler.blockUpdates();
}
void KisImage::unblockUpdates()
{
m_d->scheduler.unblockUpdates();
}
void KisImage::setSize(const QSize& size)
{
m_d->width = size.width();
m_d->height = size.height();
}
void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
{
if (newRect == bounds() && !cropLayers) return;
KUndo2MagicString actionName = cropLayers ?
kundo2_i18n("Crop Image") :
kundo2_i18n("Resize Image");
KisImageSignalVector emitSignals;
emitSignals << ComplexSizeChangedSignal(newRect, newRect.size());
emitSignals << ModifiedSignal;
KisCropSavedExtraData *extraData =
new KisCropSavedExtraData(cropLayers ?
KisCropSavedExtraData::CROP_IMAGE :
KisCropSavedExtraData::RESIZE_IMAGE,
newRect);
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE |
KisProcessingApplicator::NO_UI_UPDATES,
emitSignals, actionName, extraData);
if (cropLayers || !newRect.topLeft().isNull()) {
KisProcessingVisitorSP visitor =
new KisCropProcessingVisitor(newRect, cropLayers, true);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
applicator.applyCommand(new KisImageResizeCommand(this, newRect.size()));
applicator.end();
}
void KisImage::resizeImage(const QRect& newRect)
{
resizeImageImpl(newRect, false);
}
void KisImage::cropImage(const QRect& newRect)
{
resizeImageImpl(newRect, true);
}
void KisImage::cropNode(KisNodeSP node, const QRect& newRect)
{
bool isLayer = qobject_cast<KisLayer*>(node.data());
KUndo2MagicString actionName = isLayer ?
kundo2_i18n("Crop Layer") :
kundo2_i18n("Crop Mask");
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisCropSavedExtraData *extraData =
new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER,
newRect, node);
KisProcessingApplicator applicator(this, node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName, extraData);
KisProcessingVisitorSP visitor =
new KisCropProcessingVisitor(newRect, true, false);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
{
bool resolutionChanged = xres != xRes() && yres != yRes();
bool sizeChanged = size != this->size();
if (!resolutionChanged && !sizeChanged) return;
KisImageSignalVector emitSignals;
if (resolutionChanged) emitSignals << ResolutionChangedSignal;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size);
emitSignals << ModifiedSignal;
KUndo2MagicString actionName = sizeChanged ?
kundo2_i18n("Scale Image") :
kundo2_i18n("Change Image Resolution");
KisProcessingApplicator::ProcessingFlags signalFlags =
(resolutionChanged || sizeChanged) ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NONE;
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE | signalFlags,
emitSignals, actionName);
qreal sx = qreal(size.width()) / this->size().width();
qreal sy = qreal(size.height()) / this->size().height();
QTransform shapesCorrection;
if (resolutionChanged) {
shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres);
}
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(sx, sy,
0, 0,
QPointF(),
0,
0, 0,
filterStrategy,
shapesCorrection);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
if (resolutionChanged) {
KUndo2Command *parent =
new KisResetShapesCommand(m_d->rootLayer);
new KisImageSetResolutionCommand(this, xres, yres, parent);
applicator.applyCommand(parent);
}
if (sizeChanged) {
applicator.applyCommand(new KisImageResizeCommand(this, size));
}
applicator.end();
}
void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy)
{
KUndo2MagicString actionName(kundo2_i18n("Scale Layer"));
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(this, node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(scaleX, scaleY,
0, 0,
QPointF(),
0,
0, 0,
filterStrategy);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
void KisImage::rotateImpl(const KUndo2MagicString &actionName,
KisNodeSP rootNode,
bool resizeImage,
double radians)
{
QPointF offset;
QSize newSize;
{
KisTransformWorker worker(0,
1.0, 1.0,
0, 0, 0, 0,
radians,
0, 0, 0, 0);
QTransform transform = worker.transform();
if (resizeImage) {
QRect newRect = transform.mapRect(bounds());
newSize = newRect.size();
offset = -newRect.topLeft();
}
else {
QPointF origin = QRectF(rootNode->exactBounds()).center();
newSize = size();
offset = -(transform.map(origin) - origin);
}
}
bool sizeChanged = resizeImage &&
(newSize.width() != width() || newSize.height() != height());
// These signals will be emitted after processing is done
KisImageSignalVector emitSignals;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize);
emitSignals << ModifiedSignal;
// These flags determine whether updates are transferred to the UI during processing
KisProcessingApplicator::ProcessingFlags signalFlags =
sizeChanged ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NONE;
KisProcessingApplicator applicator(this, rootNode,
KisProcessingApplicator::RECURSIVE | signalFlags,
emitSignals, actionName);
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic");
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0,
QPointF(),
radians,
offset.x(), offset.y(),
filter);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
if (sizeChanged) {
applicator.applyCommand(new KisImageResizeCommand(this, newSize));
}
applicator.end();
}
void KisImage::rotateImage(double radians)
{
rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians);
}
void KisImage::rotateNode(KisNodeSP node, double radians)
{
if (node->inherits("KisMask")) {
rotateImpl(kundo2_i18n("Rotate Mask"), node, false, radians);
} else {
rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians);
}
}
void KisImage::shearImpl(const KUndo2MagicString &actionName,
KisNodeSP rootNode,
bool resizeImage,
double angleX, double angleY,
const QPointF &origin)
{
//angleX, angleY are in degrees
const qreal pi = 3.1415926535897932385;
const qreal deg2rad = pi / 180.0;
qreal tanX = tan(angleX * deg2rad);
qreal tanY = tan(angleY * deg2rad);
QPointF offset;
QSize newSize;
{
KisTransformWorker worker(0,
1.0, 1.0,
tanX, tanY, origin.x(), origin.y(),
0,
0, 0, 0, 0);
QRect newRect = worker.transform().mapRect(bounds());
newSize = newRect.size();
if (resizeImage) offset = -newRect.topLeft();
}
if (newSize == size()) return;
KisImageSignalVector emitSignals;
if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize);
emitSignals << ModifiedSignal;
KisProcessingApplicator::ProcessingFlags signalFlags =
KisProcessingApplicator::RECURSIVE;
if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES;
KisProcessingApplicator applicator(this, rootNode,
signalFlags,
emitSignals, actionName);
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear");
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(1.0, 1.0,
tanX, tanY, origin,
0,
offset.x(), offset.y(),
filter);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
if (resizeImage) {
applicator.applyCommand(new KisImageResizeCommand(this, newSize));
}
applicator.end();
}
void KisImage::shearNode(KisNodeSP node, double angleX, double angleY)
{
QPointF shearOrigin = QRectF(bounds()).center();
if (node->inherits("KisMask")) {
shearImpl(kundo2_i18n("Shear Mask"), node, false,
angleX, angleY, shearOrigin);
} else {
shearImpl(kundo2_i18n("Shear Layer"), node, false,
angleX, angleY, shearOrigin);
}
}
void KisImage::shear(double angleX, double angleY)
{
shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true,
angleX, angleY, QPointF());
}
void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags)
{
if (!dstColorSpace) return;
const KoColorSpace *srcColorSpace = m_d->colorSpace;
undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space"));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags);
m_d->rootLayer->accept(visitor);
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
undoAdapter()->endMacro();
setModified();
}
bool KisImage::assignImageProfile(const KoColorProfile *profile)
{
if (!profile) return false;
const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
const KoColorSpace *srcCs = colorSpace();
if (!dstCs) return false;
m_d->colorSpace = dstCs;
KisChangeProfileVisitor visitor(srcCs, dstCs);
bool retval = m_d->rootLayer->accept(visitor);
m_d->signalRouter.emitNotification(ProfileChangedSignal);
return retval;
}
void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace)
{
if (*m_d->colorSpace == *dstColorSpace) return;
undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space"));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
undoAdapter()->endMacro();
setModified();
}
void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace)
{
m_d->colorSpace = colorSpace;
m_d->rootLayer->resetCache();
m_d->signalRouter.emitNotification(ColorSpaceChangedSignal);
}
const KoColorSpace * KisImage::colorSpace() const
{
return m_d->colorSpace;
}
const KoColorProfile * KisImage::profile() const
{
return colorSpace()->profile();
}
double KisImage::xRes() const
{
return m_d->xres;
}
double KisImage::yRes() const
{
return m_d->yres;
}
void KisImage::setResolution(double xres, double yres)
{
m_d->xres = xres;
m_d->yres = yres;
m_d->signalRouter.emitNotification(ResolutionChangedSignal);
}
QPointF KisImage::documentToPixel(const QPointF &documentCoord) const
{
return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes());
}
QPoint KisImage::documentToIntPixel(const QPointF &documentCoord) const
{
QPointF pixelCoord = documentToPixel(documentCoord);
return QPoint((int)pixelCoord.x(), (int)pixelCoord.y());
}
QRectF KisImage::documentToPixel(const QRectF &documentRect) const
{
return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight()));
}
QRect KisImage::documentToIntPixel(const QRectF &documentRect) const
{
return documentToPixel(documentRect).toAlignedRect();
}
QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const
{
return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes());
}
QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const
{
return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes());
}
QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const
{
return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight()));
}
qint32 KisImage::width() const
{
return m_d->width;
}
qint32 KisImage::height() const
{
return m_d->height;
}
KisGroupLayerSP KisImage::rootLayer() const
{
Q_ASSERT(m_d->rootLayer);
return m_d->rootLayer;
}
KisPaintDeviceSP KisImage::projection() const
{
if (m_d->isolatedRootNode) {
return m_d->isolatedRootNode->projection();
}
Q_ASSERT(m_d->rootLayer);
KisPaintDeviceSP projection = m_d->rootLayer->projection();
Q_ASSERT(projection);
return projection;
}
qint32 KisImage::nlayers() const
{
QStringList list;
list << "KisLayer";
KisCountVisitor visitor(list, KoProperties());
m_d->rootLayer->accept(visitor);
return visitor.count();
}
qint32 KisImage::nHiddenLayers() const
{
QStringList list;
list << "KisLayer";
KoProperties properties;
properties.setProperty("visible", false);
KisCountVisitor visitor(list, properties);
m_d->rootLayer->accept(visitor);
return visitor.count();
}
void KisImage::flatten()
{
KisLayerUtils::flattenImage(this);
}
void KisImage::mergeMultipleLayers(QList<KisNodeSP> mergedNodes, KisNodeSP putAfter)
{
if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) {
KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter);
}
}
void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy)
{
KisLayerUtils::mergeDown(this, layer, strategy);
}
void KisImage::flattenLayer(KisLayerSP layer)
{
KisLayerUtils::flattenLayer(this, layer);
}
void KisImage::setModified()
{
m_d->signalRouter.emitNotification(ModifiedSignal);
}
QImage KisImage::convertToQImage(QRect imageRect,
const KoColorProfile * profile)
{
qint32 x;
qint32 y;
qint32 w;
qint32 h;
imageRect.getRect(&x, &y, &w, &h);
return convertToQImage(x, y, w, h, profile);
}
QImage KisImage::convertToQImage(qint32 x,
qint32 y,
qint32 w,
qint32 h,
const KoColorProfile * profile)
{
KisPaintDeviceSP dev = projection();
if (!dev) return QImage();
QImage image = dev->convertToQImage(const_cast<KoColorProfile*>(profile), x, y, w, h,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
return image;
}
QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile)
{
if (scaledImageSize.isEmpty()) {
return QImage();
}
KisPaintDeviceSP dev = new KisPaintDevice(colorSpace());
KisPainter gc;
gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds());
gc.end();
double scaleX = qreal(scaledImageSize.width()) / width();
double scaleY = qreal(scaledImageSize.height()) / height();
QPointer<KoUpdater> updater = new KoDummyUpdater();
KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic"));
worker.run();
delete updater;
return dev->convertToQImage(profile);
}
void KisImage::notifyLayersChanged()
{
m_d->signalRouter.emitNotification(LayersChangedSignal);
}
QRect KisImage::bounds() const
{
return QRect(0, 0, width(), height());
}
QRect KisImage::effectiveLodBounds() const
{
QRect boundRect = bounds();
const int lod = currentLevelOfDetail();
if (lod > 0) {
KisLodTransform t(lod);
boundRect = t.map(boundRect);
}
return boundRect;
}
KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const
{
const int lod = currentLevelOfDetail();
return lod > 0 ?
m_d->scheduler.lodNPostExecutionUndoAdapter() :
&m_d->postExecutionUndoAdapter;
}
void KisImage::setUndoStore(KisUndoStore *undoStore)
{
m_d->legacyUndoAdapter.setUndoStore(undoStore);
m_d->postExecutionUndoAdapter.setUndoStore(undoStore);
m_d->undoStore.reset(undoStore);
}
KisUndoStore* KisImage::undoStore()
{
return m_d->undoStore.data();
}
KisUndoAdapter* KisImage::undoAdapter() const
{
return &m_d->legacyUndoAdapter;
}
KisActionRecorder* KisImage::actionRecorder() const
{
return &m_d->recorder;
}
void KisImage::setDefaultProjectionColor(const KoColor &color)
{
KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer);
m_d->rootLayer->setDefaultProjectionColor(color);
}
KoColor KisImage::defaultProjectionColor() const
{
KIS_ASSERT_RECOVER(m_d->rootLayer) {
return KoColor(Qt::transparent, m_d->colorSpace);
}
return m_d->rootLayer->defaultProjectionColor();
}
void KisImage::setRootLayer(KisGroupLayerSP rootLayer)
{
stopIsolatedMode();
KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace);
if (m_d->rootLayer) {
m_d->rootLayer->setGraphListener(0);
m_d->rootLayer->disconnect();
KisPaintDeviceSP original = m_d->rootLayer->original();
defaultProjectionColor = original->defaultPixel();
}
m_d->rootLayer = rootLayer;
m_d->rootLayer->disconnect();
m_d->rootLayer->setGraphListener(this);
m_d->rootLayer->setImage(this);
KisPaintDeviceSP newOriginal = m_d->rootLayer->original();
newOriginal->setDefaultPixel(defaultProjectionColor);
setRoot(m_d->rootLayer.data());
}
void KisImage::addAnnotation(KisAnnotationSP annotation)
{
// Find the icc annotation, if there is one
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == annotation->type()) {
*it = annotation;
return;
}
++it;
}
m_d->annotations.push_back(annotation);
}
KisAnnotationSP KisImage::annotation(const QString& type)
{
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == type) {
return *it;
}
++it;
}
return KisAnnotationSP(0);
}
void KisImage::removeAnnotation(const QString& type)
{
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == type) {
m_d->annotations.erase(it);
return;
}
++it;
}
}
vKisAnnotationSP_it KisImage::beginAnnotations()
{
return m_d->annotations.begin();
}
vKisAnnotationSP_it KisImage::endAnnotations()
{
return m_d->annotations.end();
}
void KisImage::notifyAboutToBeDeleted()
{
emit sigAboutToBeDeleted();
}
KisImageSignalRouter* KisImage::signalRouter()
{
return &m_d->signalRouter;
}
void KisImage::waitForDone()
{
requestStrokeEnd();
m_d->scheduler.waitForDone();
}
KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy)
{
/**
* Ask open strokes to end gracefully. All the strokes clients
* (including the one calling this method right now) will get
* a notification that they should probably end their strokes.
* However this is purely their choice whether to end a stroke
* or not.
*/
if (strokeStrategy->requestsOtherStrokesToEnd()) {
requestStrokeEnd();
}
/**
* Some of the strokes can cancel their work with undoing all the
* changes they did to the paint devices. The problem is that undo
* stack will know nothing about it. Therefore, just notify it
* explicitly
*/
if (strokeStrategy->clearsRedoOnStart()) {
m_d->undoStore->purgeRedoState();
}
return m_d->scheduler.startStroke(strokeStrategy);
}
void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc)
{
KisImageConfig imageConfig;
int patchWidth = imageConfig.updatePatchWidth();
int patchHeight = imageConfig.updatePatchHeight();
for (int y = 0; y < rc.height(); y += patchHeight) {
for (int x = 0; x < rc.width(); x += patchWidth) {
QRect patchRect(x, y, patchWidth, patchHeight);
patchRect &= rc;
QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect));
}
}
}
bool KisImage::startIsolatedMode(KisNodeSP node)
{
if (!tryBarrierLock()) return false;
unlock();
m_d->isolatedRootNode = node;
emit sigIsolatedModeChanged();
// the GUI uses our thread to do the color space conversion so we
// need to emit this signal in multiple threads
m_d->notifyProjectionUpdatedInPatches(bounds());
invalidateAllFrames();
return true;
}
void KisImage::stopIsolatedMode()
{
if (!m_d->isolatedRootNode) return;
KisNodeSP oldRootNode = m_d->isolatedRootNode;
m_d->isolatedRootNode = 0;
emit sigIsolatedModeChanged();
// the GUI uses our thread to do the color space conversion so we
// need to emit this signal in multiple threads
m_d->notifyProjectionUpdatedInPatches(bounds());
invalidateAllFrames();
// TODO: Substitute notifyProjectionUpdated() with this code
// when update optimization is implemented
//
// QRect updateRect = bounds() | oldRootNode->extent();
// oldRootNode->setDirty(updateRect);
}
KisNodeSP KisImage::isolatedModeRoot() const
{
return m_d->isolatedRootNode;
}
void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data)
{
KisUpdateTimeMonitor::instance()->reportJobStarted(data);
m_d->scheduler.addJob(id, data);
}
void KisImage::endStroke(KisStrokeId id)
{
m_d->scheduler.endStroke(id);
}
bool KisImage::cancelStroke(KisStrokeId id)
{
return m_d->scheduler.cancelStroke(id);
}
bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync()
{
return scheduler.tryCancelCurrentStrokeAsync();
}
void KisImage::requestUndoDuringStroke()
{
emit sigUndoDuringStrokeRequested();
}
void KisImage::requestStrokeCancellation()
{
if (!m_d->tryCancelCurrentStrokeAsync()) {
emit sigStrokeCancellationRequested();
}
}
UndoResult KisImage::tryUndoUnfinishedLod0Stroke()
{
return m_d->scheduler.tryUndoLastStrokeAsync();
}
void KisImage::requestStrokeEnd()
{
emit sigStrokeEndRequested();
emit sigStrokeEndRequestedActiveNodeFiltered();
}
void KisImage::requestStrokeEndActiveNode()
{
emit sigStrokeEndRequested();
}
void KisImage::refreshGraph(KisNodeSP root)
{
refreshGraph(root, bounds(), bounds());
}
void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect)
{
if (!root) root = m_d->rootLayer;
m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
m_d->scheduler.fullRefresh(root, rc, cropRect);
}
void KisImage::initialRefreshGraph()
{
/**
* NOTE: Tricky part. We set crop rect to null, so the clones
* will not rely on precalculated projections of their sources
*/
refreshGraphAsync(0, bounds(), QRect());
waitForDone();
}
void KisImage::refreshGraphAsync(KisNodeSP root)
{
refreshGraphAsync(root, bounds(), bounds());
}
void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc)
{
refreshGraphAsync(root, rc, bounds());
}
void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect)
{
if (!root) root = m_d->rootLayer;
m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
m_d->scheduler.fullRefreshAsync(root, rc, cropRect);
}
void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect)
{
KIS_ASSERT_RECOVER_RETURN(pseudoFilthy);
m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false);
m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect);
}
void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
m_d->scheduler.addSpontaneousJob(spontaneousJob);
}
void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter)
{
// udpate filters are *not* recursive!
KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter);
m_d->projectionUpdatesFilter = filter;
}
KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const
{
return m_d->projectionUpdatesFilter;
}
void KisImage::disableDirtyRequests()
{
setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter()));
}
void KisImage::enableDirtyRequests()
{
setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP());
}
void KisImage::disableUIUpdates()
{
m_d->disableUIUpdateSignals.ref();
}
void KisImage::enableUIUpdates()
{
m_d->disableUIUpdateSignals.deref();
}
void KisImage::notifyProjectionUpdated(const QRect &rc)
{
KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc);
if (!m_d->disableUIUpdateSignals) {
int lod = currentLevelOfDetail();
QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod);
if (dirtyRect.isEmpty()) return;
emit sigImageUpdated(dirtyRect);
}
}
void KisImage::setWorkingThreadsLimit(int value)
{
m_d->scheduler.setThreadsLimit(value);
}
int KisImage::workingThreadsLimit() const
{
return m_d->scheduler.threadsLimit();
}
void KisImage::notifySelectionChanged()
{
/**
* The selection is calculated asynchromously, so it is not
* handled by disableUIUpdates() and other special signals of
* KisImageSignalRouter
*/
m_d->legacyUndoAdapter.emitSelectionChanged();
/**
* Editing of selection masks doesn't necessary produce a
* setDirty() call, so in the end of the stroke we need to request
* direct update of the UI's cache.
*/
if (m_d->isolatedRootNode &&
dynamic_cast<KisSelectionMask*>(m_d->isolatedRootNode.data())) {
notifyProjectionUpdated(bounds());
}
}
void KisImage::requestProjectionUpdateImpl(KisNode *node,
- const QRect &rect,
+ const QVector<QRect> &rects,
const QRect &cropRect)
{
- if (rect.isEmpty()) return;
+ if (rects.isEmpty()) return;
- m_d->scheduler.updateProjection(node, rect, cropRect);
+ m_d->scheduler.updateProjection(node, rects, cropRect);
}
-void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect, bool resetAnimationCache)
+void KisImage::requestProjectionUpdate(KisNode *node, const QVector<QRect> &rects, bool resetAnimationCache)
{
if (m_d->projectionUpdatesFilter
- && m_d->projectionUpdatesFilter->filter(this, node, rect, resetAnimationCache)) {
+ && m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) {
return;
}
if (resetAnimationCache) {
- m_d->animationInterface->notifyNodeChanged(node, rect, false);
+ m_d->animationInterface->notifyNodeChanged(node, rects, false);
}
/**
* Here we use 'permitted' instead of 'active' intentively,
* because the updates may come after the actual stroke has been
* finished. And having some more updates for the stroke not
* supporting the wrap-around mode will not make much harm.
*/
if (m_d->wrapAroundModePermitted) {
- const QRect boundRect = effectiveLodBounds();
- KisWrappedRect splitRect(rect, boundRect);
+ QVector<QRect> allSplitRects;
- Q_FOREACH (const QRect &rc, splitRect) {
- requestProjectionUpdateImpl(node, rc, boundRect);
+ const QRect boundRect = effectiveLodBounds();
+ Q_FOREACH (const QRect &rc, rects) {
+ KisWrappedRect splitRect(rc, boundRect);
+ allSplitRects.append(splitRect);
}
+
+ requestProjectionUpdateImpl(node, allSplitRects, boundRect);
+
} else {
- requestProjectionUpdateImpl(node, rect, bounds());
+ requestProjectionUpdateImpl(node, rects, bounds());
}
- KisNodeGraphListener::requestProjectionUpdate(node, rect, resetAnimationCache);
+ KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache);
}
void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect)
{
m_d->animationInterface->invalidateFrames(range, rect);
}
void KisImage::requestTimeSwitch(int time)
{
m_d->animationInterface->requestTimeSwitchNonGUI(time);
}
QList<KisLayerCompositionSP> KisImage::compositions()
{
return m_d->compositions;
}
void KisImage::addComposition(KisLayerCompositionSP composition)
{
m_d->compositions.append(composition);
}
void KisImage::removeComposition(KisLayerCompositionSP composition)
{
m_d->compositions.removeAll(composition);
}
bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds)
{
KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(root.data());
if (mask &&
(!bounds.contains(mask->paintDevice()->exactBounds()) ||
mask->selection()->hasShapeSelection())) {
return true;
}
KisNodeSP node = root->firstChild();
while (node) {
if (checkMasksNeedConversion(node, bounds)) {
return true;
}
node = node->nextSibling();
}
return false;
}
void KisImage::setWrapAroundModePermitted(bool value)
{
if (m_d->wrapAroundModePermitted != value) {
requestStrokeEnd();
}
m_d->wrapAroundModePermitted = value;
if (m_d->wrapAroundModePermitted &&
checkMasksNeedConversion(root(), bounds())) {
KisProcessingApplicator applicator(this, root(),
KisProcessingApplicator::RECURSIVE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Crop Selections"));
KisProcessingVisitorSP visitor =
new KisCropSelectionsProcessingVisitor(bounds());
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
}
bool KisImage::wrapAroundModePermitted() const
{
return m_d->wrapAroundModePermitted;
}
bool KisImage::wrapAroundModeActive() const
{
return m_d->wrapAroundModePermitted &&
m_d->scheduler.wrapAroundModeSupported();
}
void KisImage::setDesiredLevelOfDetail(int lod)
{
if (m_d->blockLevelOfDetail) {
qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()"
<< "was called while LoD functionality was being blocked!";
return;
}
m_d->scheduler.setDesiredLevelOfDetail(lod);
}
int KisImage::currentLevelOfDetail() const
{
if (m_d->blockLevelOfDetail) {
return 0;
}
return m_d->scheduler.currentLevelOfDetail();
}
void KisImage::setLevelOfDetailBlocked(bool value)
{
KisImageBarrierLockerRaw l(this);
if (value && !m_d->blockLevelOfDetail) {
m_d->scheduler.setDesiredLevelOfDetail(0);
}
m_d->blockLevelOfDetail = value;
}
void KisImage::explicitRegenerateLevelOfDetail()
{
if (!m_d->blockLevelOfDetail) {
m_d->scheduler.explicitRegenerateLevelOfDetail();
}
}
bool KisImage::levelOfDetailBlocked() const
{
return m_d->blockLevelOfDetail;
}
void KisImage::notifyNodeCollpasedChanged()
{
emit sigNodeCollapsedChanged();
}
KisImageAnimationInterface* KisImage::animationInterface() const
{
return m_d->animationInterface;
}
void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig)
{
m_d->proofingConfig = proofingConfig;
emit sigProofingConfigChanged();
}
KisProofingConfigurationSP KisImage::proofingConfiguration() const
{
- if (!m_d->proofingConfig) {
- KisImageConfig cfg;
- m_d->proofingConfig = cfg.defaultProofingconfiguration();
+ if (m_d->proofingConfig) {
+ return m_d->proofingConfig;
}
- return m_d->proofingConfig;
+ return 0;
}
QPointF KisImage::mirrorAxesCenter() const
{
return m_d->axesCenter;
}
void KisImage::setMirrorAxesCenter(const QPointF &value) const
{
m_d->axesCenter = value;
}
diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h
index bfeb5b504c..2f1afab7a8 100644
--- a/libs/image/kis_image.h
+++ b/libs/image/kis_image.h
@@ -1,995 +1,995 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_IMAGE_H_
#define KIS_IMAGE_H_
#include <QObject>
#include <QString>
#include <QPainter>
#include <QRect>
#include <QRegion>
#include <QBitArray>
#include <KoColorConversionTransformation.h>
#include "kis_paint_device.h" // msvc cannot handle forward declarations, so include kis_paint_device here
#include "kis_types.h"
#include "kis_shared.h"
#include "kis_node_graph_listener.h"
#include "kis_node_facade.h"
#include "kis_image_interfaces.h"
#include "kis_strokes_queue_undo_result.h"
#include <kritaimage_export.h>
class KisDocument;
class KoColorSpace;
class KoColor;
class KisCompositeProgressProxy;
class KisActionRecorder;
class KisUndoStore;
class KisUndoAdapter;
class KisImageSignalRouter;
class KisPostExecutionUndoAdapter;
class KisFilterStrategy;
class KoColorProfile;
class KisLayerComposition;
class KisSpontaneousJob;
class KisImageAnimationInterface;
class KUndo2MagicString;
class KisProofingConfiguration;
namespace KisMetaData
{
class MergeStrategy;
}
/**
* This is the image class, it contains a tree of KisLayer stack and
* meta information about the image. And it also provides some
* functions to manipulate the whole image.
*/
class KRITAIMAGE_EXPORT KisImage : public QObject,
public KisStrokesFacade,
public KisStrokeUndoFacade,
public KisUpdatesFacade,
public KisProjectionUpdateListener,
public KisNodeFacade,
public KisNodeGraphListener,
public KisShared
{
Q_OBJECT
public:
/// @param colorSpace can be null. in that case it will be initialised to a default color space.
KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name);
~KisImage() override;
public: // KisNodeGraphListener implementation
void aboutToAddANode(KisNode *parent, int index) override;
void nodeHasBeenAdded(KisNode *parent, int index) override;
void aboutToRemoveANode(KisNode *parent, int index) override;
void nodeChanged(KisNode * node) override;
void invalidateAllFrames() override;
void notifySelectionChanged() override;
- void requestProjectionUpdate(KisNode *node, const QRect& rect, bool resetAnimationCache) override;
+ void requestProjectionUpdate(KisNode *node, const QVector<QRect> &rects, bool resetAnimationCache) override;
void invalidateFrames(const KisTimeRange &range, const QRect &rect) override;
void requestTimeSwitch(int time) override;
public: // KisProjectionUpdateListener implementation
void notifyProjectionUpdated(const QRect &rc) override;
public:
/**
* Set the number of threads used by the image's working threads
*/
void setWorkingThreadsLimit(int value);
/**
* Return the number of threads available to the image's working threads
*/
int workingThreadsLimit() const;
/**
* Makes a copy of the image with all the layers. If possible, shallow
* copies of the layers are made.
*
* \p exactCopy shows if the copied image should look *exactly* the same as
* the other one (according to it's .kra xml representation). It means that
* the layers will have the same UUID keys and, therefore, you are not
* expected to use the copied image anywhere except for saving. Don't use
* this option if you plan to work with the copied image later.
*/
KisImage *clone(bool exactCopy = false);
/**
* Render the projection onto a QImage.
*/
QImage convertToQImage(qint32 x1,
qint32 y1,
qint32 width,
qint32 height,
const KoColorProfile * profile);
/**
* Render the projection onto a QImage.
* (this is an overloaded function)
*/
QImage convertToQImage(QRect imageRect,
const KoColorProfile * profile);
/**
* XXX: docs!
*/
QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile);
/**
* Calls KisUpdateScheduler::lock (XXX: APIDOX -- what does that mean?)
*/
void lock();
/**
* Calls KisUpdateScheduler::unlock (XXX: APIDOX -- what does that mean?)
*/
void unlock();
/**
* Returns true if lock() has been called more often than unlock().
*/
bool locked() const;
/**
* @return the global selection object or 0 if there is none. The
* global selection is always read-write.
*/
KisSelectionSP globalSelection() const;
/**
* Retrieve the next automatic layername (XXX: fix to add option to return Mask X)
*/
QString nextLayerName(const QString &baseName = "") const;
/**
* Set the automatic layer name counter one back.
*/
void rollBackLayerName();
/**
* Resize the image to the specified rect. The resize
* method handles the creating on an undo step itself.
*
* @param newRect the rect describing the new width, height and offset
* of the image
*/
void resizeImage(const QRect& newRect);
/**
* Crop the image to the specified rect. The crop
* method handles the creating on an undo step itself.
*
* @param newRect the rect describing the new width, height and offset
* of the image
*/
void cropImage(const QRect& newRect);
/**
* Crop a node to @newRect. The node will *not* be moved anywhere,
* it just drops some content
*/
void cropNode(KisNodeSP node, const QRect& newRect);
/// XXX: ApiDox
void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy);
/// XXX: ApiDox
void scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy);
/**
* Execute a rotate transform on all layers in this image.
* Image is resized to fit rotated image.
*/
void rotateImage(double radians);
/**
* Execute a rotate transform on on a subtree of this image.
* Image is not resized.
*/
void rotateNode(KisNodeSP node, double radians);
/**
* Execute a shear transform on all layers in this image.
*/
void shear(double angleX, double angleY);
/**
* Shear a node and all its children.
* @param angleX, @param angleY are given in degrees.
*/
void shearNode(KisNodeSP node, double angleX, double angleY);
/**
* Convert the image and all its layers to the dstColorSpace
*/
void convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags);
/**
* Set the color space of the projection (and the root layer)
* to dstColorSpace. No conversion is done for other layers,
* their colorspace can differ.
* NOTE: Note conversion is done, only regeneration, so no rendering
* intent needed
*/
void convertProjectionColorSpace(const KoColorSpace *dstColorSpace);
// Get the profile associated with this image
const KoColorProfile * profile() const;
/**
* Set the profile of the image to the new profile and do the same for
* all layers that have the same colorspace and profile of the image.
* It doesn't do any pixel conversion.
*
* This is essential if you have loaded an image that didn't
* have an embedded profile to which you want to attach the right profile.
*
* This does not create an undo action; only call it when creating or
* loading an image.
*
* @returns false if the profile could not be assigned
*/
bool assignImageProfile(const KoColorProfile *profile);
/**
* Returns the current undo adapter. You can add new commands to the
* undo stack using the adapter. This adapter is used for a backward
* compatibility for old commands created before strokes. It blocks
* all the porcessing at the scheduler, waits until it's finished
* adn executes commands exclusively.
*/
KisUndoAdapter* undoAdapter() const;
/**
* This adapter is used by the strokes system. The commands are added
* to it *after* redo() is done (in the scheduler context). They are
* wrapped into a special command and added to the undo stack. redo()
* in not called.
*/
KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override;
/**
* Replace current undo store with the new one. The old store
* will be deleted.
* This method is used by KisDocument for dropping all the commands
* during file loading.
*/
void setUndoStore(KisUndoStore *undoStore);
/**
* Return current undo store of the image
*/
KisUndoStore* undoStore();
/**
* @return the action recorder associated with this image
*/
KisActionRecorder* actionRecorder() const;
/**
* Tell the image it's modified; this emits the sigImageModified
* signal. This happens when the image needs to be saved
*/
void setModified();
/**
* The default colorspace of this image: new layers will have this
* colorspace and the projection will have this colorspace.
*/
const KoColorSpace * colorSpace() const;
/**
* X resolution in pixels per pt
*/
double xRes() const;
/**
* Y resolution in pixels per pt
*/
double yRes() const;
/**
* Set the resolution in pixels per pt.
*/
void setResolution(double xres, double yres);
/**
* Convert a document coordinate to a pixel coordinate.
*
* @param documentCoord PostScript Pt coordinate to convert.
*/
QPointF documentToPixel(const QPointF &documentCoord) const;
/**
* Convert a document coordinate to an integer pixel coordinate.
*
* @param documentCoord PostScript Pt coordinate to convert.
*/
QPoint documentToIntPixel(const QPointF &documentCoord) const;
/**
* Convert a document rectangle to a pixel rectangle.
*
* @param documentRect PostScript Pt rectangle to convert.
*/
QRectF documentToPixel(const QRectF &documentRect) const;
/**
* Convert a document rectangle to an integer pixel rectangle.
*
* @param documentRect PostScript Pt rectangle to convert.
*/
QRect documentToIntPixel(const QRectF &documentRect) const;
/**
* Convert a pixel coordinate to a document coordinate.
*
* @param pixelCoord pixel coordinate to convert.
*/
QPointF pixelToDocument(const QPointF &pixelCoord) const;
/**
* Convert an integer pixel coordinate to a document coordinate.
* The document coordinate is at the centre of the pixel.
*
* @param pixelCoord pixel coordinate to convert.
*/
QPointF pixelToDocument(const QPoint &pixelCoord) const;
/**
* Convert a document rectangle to an integer pixel rectangle.
*
* @param pixelCoord pixel coordinate to convert.
*/
QRectF pixelToDocument(const QRectF &pixelCoord) const;
/**
* Return the width of the image
*/
qint32 width() const;
/**
* Return the height of the image
*/
qint32 height() const;
/**
* Return the size of the image
*/
QSize size() const {
return QSize(width(), height());
}
/**
* @return the root node of the image node graph
*/
KisGroupLayerSP rootLayer() const;
/**
* Return the projection; that is, the complete, composited
* representation of this image.
*/
KisPaintDeviceSP projection() const;
/**
* Return the number of layers (not other nodes) that are in this
* image.
*/
qint32 nlayers() const;
/**
* Return the number of layers (not other node types) that are in
* this image and that are hidden.
*/
qint32 nHiddenLayers() const;
/**
* Merge all visible layers and discard hidden ones.
*/
void flatten();
/**
* Merge the specified layer with the layer
* below this layer, remove the specified layer.
*/
void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy);
/**
* flatten the layer: that is, the projection becomes the layer
* and all subnodes are removed. If this is not a paint layer, it will morph
* into a paint layer.
*/
void flattenLayer(KisLayerSP layer);
/**
* Merges layers in \p mergedLayers and creates a new layer above
* \p putAfter
*/
void mergeMultipleLayers(QList<KisNodeSP> mergedLayers, KisNodeSP putAfter);
/// @return the exact bounds of the image in pixel coordinates.
QRect bounds() const;
/**
* Returns the actual bounds of the image, taking LevelOfDetail
* into account. This value is used as a bounds() value of
* KisDefaultBounds object.
*/
QRect effectiveLodBounds() const;
/// use if the layers have changed _completely_ (eg. when flattening)
void notifyLayersChanged();
/**
* Sets the default color of the root layer projection. All the layers
* will be merged on top of this very color
*/
void setDefaultProjectionColor(const KoColor &color);
/**
* \see setDefaultProjectionColor()
*/
KoColor defaultProjectionColor() const;
void setRootLayer(KisGroupLayerSP rootLayer);
/**
* Add an annotation for this image. This can be anything: Gamma, EXIF, etc.
* Note that the "icc" annotation is reserved for the color strategies.
* If the annotation already exists, overwrite it with this one.
*/
void addAnnotation(KisAnnotationSP annotation);
/** get the annotation with the given type, can return 0 */
KisAnnotationSP annotation(const QString& type);
/** delete the annotation, if the image contains it */
void removeAnnotation(const QString& type);
/**
* Start of an iteration over the annotations of this image (including the ICC Profile)
*/
vKisAnnotationSP_it beginAnnotations();
/** end of an iteration over the annotations of this image */
vKisAnnotationSP_it endAnnotations();
/**
* Called before the image is delted and sends the sigAboutToBeDeleted signal
*/
void notifyAboutToBeDeleted();
KisImageSignalRouter* signalRouter();
/**
* Returns whether we can reselect current global selection
*
* \see reselectGlobalSelection()
*/
bool canReselectGlobalSelection();
/**
* Returns the layer compositions for the image
*/
QList<KisLayerCompositionSP> compositions();
/**
* Adds a new layer composition, will be saved with the image
*/
void addComposition(KisLayerCompositionSP composition);
/**
* Remove the layer compostion
*/
void removeComposition(KisLayerCompositionSP composition);
/**
* Permit or deny the wrap-around mode for all the paint devices
* of the image. Note that permitting the wraparound mode will not
* necessarily activate it right now. To be activated the wrap
* around mode should be 1) permitted; 2) supported by the
* currently running stroke.
*/
void setWrapAroundModePermitted(bool value);
/**
* \return whether the wrap-around mode is permitted for this
* image. If the wrap around mode is permitted and the
* currently running stroke supports it, the mode will be
* activated for all paint devices of the image.
*
* \see setWrapAroundMode
*/
bool wrapAroundModePermitted() const;
/**
* \return whether the wraparound mode is activated for all the
* devices of the image. The mode is activated when both
* factors are true: the user permitted it and the stroke
* supports it
*/
bool wrapAroundModeActive() const;
/**
* \return curent level of detail which is used when processing the image.
* Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is
* null, which means we work on the original image.
*/
int currentLevelOfDetail() const;
/**
* Notify KisImage which level of detail should be used in the
* lod-mode. Setting the mode does not guarantee the LOD to be
* used. It will be activated only when the stokes supports it.
*/
void setDesiredLevelOfDetail(int lod);
/**
* Relative position of the mirror axis center
* 0,0 - topleft corner of the image
* 1,1 - bottomright corner of the image
*/
QPointF mirrorAxesCenter() const;
/**
* Sets the relative position of the axes center
* \see mirrorAxesCenter() for details
*/
void setMirrorAxesCenter(const QPointF &value) const;
public Q_SLOTS:
/**
* Explicitly start regeneration of LoD planes of all the devices
* in the image. This call should be performed when the user is idle,
* just to make the quality of image updates better.
*/
void explicitRegenerateLevelOfDetail();
public:
/**
* Blocks usage of level of detail functionality. After this method
* has been called, no new strokes will use LoD.
*/
void setLevelOfDetailBlocked(bool value);
/**
* \see setLevelOfDetailBlocked()
*/
bool levelOfDetailBlocked() const;
/**
* Notifies that the node collapsed state has changed
*/
void notifyNodeCollpasedChanged();
KisImageAnimationInterface *animationInterface() const;
/**
* @brief setProofingConfiguration, this sets the image's proofing configuration, and signals
* the proofingConfiguration has changed.
* @param proofingConfig - the kis proofing config that will be used instead.
*/
void setProofingConfiguration(KisProofingConfigurationSP proofingConfig);
/**
* @brief proofingConfiguration
* @return the proofing configuration of the image.
*/
KisProofingConfigurationSP proofingConfiguration() const;
public:
bool startIsolatedMode(KisNodeSP node);
void stopIsolatedMode();
KisNodeSP isolatedModeRoot() const;
Q_SIGNALS:
/**
* Emitted whenever an action has caused the image to be
* recomposited.
*
* @param rc The rect that has been recomposited.
*/
void sigImageUpdated(const QRect &);
/**
Emitted whenever the image has been modified, so that it
doesn't match with the version saved on disk.
*/
void sigImageModified();
/**
* The signal is emitted when the size of the image is changed.
* \p oldStillPoint and \p newStillPoint give the receiver the
* hint about how the new and old rect of the image correspond to
* each other. They specify the point of the image around which
* the conversion was done. This point will stay still on the
* user's screen. That is the \p newStillPoint of the new image
* will be painted at the same screen position, where \p
* oldStillPoint of the old image was painted.
*
* \param oldStillPoint is a still point represented in *old*
* image coordinates
*
* \param newStillPoint is a still point represented in *new*
* image coordinates
*/
void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
void sigProfileChanged(const KoColorProfile * profile);
void sigColorSpaceChanged(const KoColorSpace* cs);
void sigResolutionChanged(double xRes, double yRes);
void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes);
/**
* Inform the model that a node was changed
*/
void sigNodeChanged(KisNodeSP node);
/**
* Inform that the image is going to be deleted
*/
void sigAboutToBeDeleted();
/**
* The signal is emitted right after a node has been connected
* to the graph of the nodes.
*
* WARNING: you must not request any graph-related information
* about the node being run in a not-scheduler thread. If you need
* information about the parent/siblings of the node connect
* with Qt::DirectConnection, get needed information and then
* emit another Qt::AutoConnection signal to pass this information
* to your thread. See details of the implementation
* in KisDummiesfacadeBase.
*/
void sigNodeAddedAsync(KisNodeSP node);
/**
* This signal is emitted right before a node is going to removed
* from the graph of the nodes.
*
* WARNING: you must not request any graph-related information
* about the node being run in a not-scheduler thread.
*
* \see comment in sigNodeAddedAsync()
*/
void sigRemoveNodeAsync(KisNodeSP node);
/**
* Emitted when the root node of the image has changed.
* It happens, e.g. when we flatten the image. When
* this happens the receiver should reload information
* about the image
*/
void sigLayersChangedAsync();
/**
* Emitted when the UI has requested the undo of the last stroke's
* operation. The point is, we cannot deal with the internals of
* the stroke without its creator knowing about it (which most
* probably cause a crash), so we just forward this request from
* the UI to the creator of the stroke.
*
* If your tool supports undoing part of its work, just listen to
* this signal and undo when it comes
*/
void sigUndoDuringStrokeRequested();
/**
* Emitted when the UI has requested the cancellation of
* the stroke. The point is, we cannot cancel the stroke
* without its creator knowing about it (which most probably
* cause a crash), so we just forward this request from the UI
* to the creator of the stroke.
*
* If your tool supports cancelling of its work in the middle
* of operation, just listen to this signal and cancel
* the stroke when it comes
*/
void sigStrokeCancellationRequested();
/**
* Emitted when the image decides that the stroke should better
* be ended. The point is, we cannot just end the stroke
* without its creator knowing about it (which most probably
* cause a crash), so we just forward this request from the UI
* to the creator of the stroke.
*
* If your tool supports long strokes that may involve multiple
* mouse actions in one stroke, just listen to this signal and
* end the stroke when it comes.
*/
void sigStrokeEndRequested();
/**
* Same as sigStrokeEndRequested() but is not emitted when the active node
* is changed.
*/
void sigStrokeEndRequestedActiveNodeFiltered();
/**
* Emitted when the isolated mode status has changed.
*
* Can be used by the receivers to catch a fact of forcefully
* stopping the isolated mode by the image when some complex
* action was requested
*/
void sigIsolatedModeChanged();
/**
* Emitted when one or more nodes changed the collapsed state
*
*/
void sigNodeCollapsedChanged();
/**
* Emitted when the proofing configuration of the image is being changed.
*
*/
void sigProofingConfigChanged();
public Q_SLOTS:
KisCompositeProgressProxy* compositeProgressProxy();
bool isIdle(bool allowLocked = false);
/**
* @brief barrierLock APIDOX
* @param readOnly
*/
void barrierLock(bool readOnly = false);
/**
* @brief barrierLock APIDOX
* @param readOnly
*/
bool tryBarrierLock(bool readOnly = false);
/**
* @brief barrierLock APIDOX
* @param readOnly
*/
void waitForDone();
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override;
void addJob(KisStrokeId id, KisStrokeJobData *data) override;
void endStroke(KisStrokeId id) override;
bool cancelStroke(KisStrokeId id) override;
/**
* @brief blockUpdates block updating the image projection
*/
void blockUpdates() override;
/**
* @brief unblockUpdates unblock updating the image project. This
* only restarts the scheduler and does not schedule a full refresh.
*/
void unblockUpdates() override;
/**
* Disables notification of the UI about the changes in the image.
* This feature is used by KisProcessingApplicator. It is needed
* when we change the size of the image. In this case, the whole
* image will be reloaded into UI by sigSizeChanged(), so there is
* no need to inform the UI about individual dirty rects.
*/
void disableUIUpdates() override;
/**
* \see disableUIUpdates
*/
void enableUIUpdates() override;
/**
* Disables the processing of all the setDirty() requests that
* come to the image. The incoming requests are effectively
* *dropped*.
*
* This feature is used by KisProcessingApplicator. For many cases
* it provides its own updates interface, which recalculates the
* whole subtree of nodes. But while we change any particular
* node, it can ask for an update itself. This method is a way of
* blocking such intermediate (and excessive) requests.
*
* NOTE: this is a convenience function for setProjectionUpdatesFilter()
* that installs a predefined filter that eats everything. Please
* note that these calls are *not* recursive
*/
void disableDirtyRequests() override;
/**
* \see disableDirtyRequests()
*/
void enableDirtyRequests() override;
/**
* Installs a filter object that will filter all the incoming projection update
* requests. If the filter return true, the incoming update is dropped.
*
* NOTE: you cannot set filters recursively!
*/
void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override;
/**
* \see setProjectionUpdatesFilter()
*/
KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override;
void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override;
void refreshGraphAsync(KisNodeSP root, const QRect &rc) override;
void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override;
/**
* Triggers synchronous recomposition of the projection
*/
void refreshGraph(KisNodeSP root = KisNodeSP());
void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect);
void initialRefreshGraph();
/**
* Initiate a stack regeneration skipping the recalculation of the
* filthy node's projection.
*
* Works exactly as pseudoFilthy->setDirty() with the only
* exception that pseudoFilthy::updateProjection() will not be
* called. That is used by KisRecalculateTransformMaskJob to avoid
* cyclic dependencies.
*/
void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect);
/**
* Adds a spontaneous job to the updates queue.
*
* A spontaneous job may do some trivial tasks in the background,
* like updating the outline of selection or purging unused tiles
* from the existing paint devices.
*/
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob);
/**
* This method is called by the UI (*not* by the creator of the
* stroke) when it thinks the current stroke should undo its last
* action, for example, when the user presses Ctrl+Z while some
* stroke is active.
*
* If the creator of the stroke supports undoing of intermediate
* actions, it will be notified about this request and can undo
* its last action.
*/
void requestUndoDuringStroke();
/**
* This method is called by the UI (*not* by the creator of the
* stroke) when it thinks current stroke should be cancelled. If
* there is a running stroke that has already been detached from
* its creator (ended or cancelled), it will be forcefully
* cancelled and reverted. If there is an open stroke present, and
* if its creator supports cancelling, it will be notified about
* the request and the stroke will be cancelled
*/
void requestStrokeCancellation();
/**
* This method requests the last stroke executed on the image to become undone.
* If the stroke is not ended, or if all the Lod0 strokes are completed, the method
* returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT
* is returned and the caller should just wait for its completion and call global undo
* instead. UNDO_OK means one unfinished stroke has been undone.
*/
UndoResult tryUndoUnfinishedLod0Stroke();
/**
* This method is called when image or some other part of Krita
* (*not* the creator of the stroke) decides that the stroke
* should be ended. If the creator of the stroke supports it, it
* will be notified and the stroke will be cancelled
*/
void requestStrokeEnd();
/**
* Same as requestStrokeEnd() but is called by view manager when
* the current node is changed. Use to dintinguish
* sigStrokeEndRequested() and
* sigStrokeEndRequestedActiveNodeFiltered() which are used by
* KisNodeJugglerCompressed
*/
void requestStrokeEndActiveNode();
private:
KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy);
KisImage& operator=(const KisImage& rhs);
void emitSizeChanged();
void resizeImageImpl(const QRect& newRect, bool cropLayers);
void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode,
bool resizeImage, double radians);
void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode,
bool resizeImage, double angleX, double angleY,
const QPointF &origin);
void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2);
void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea);
void requestProjectionUpdateImpl(KisNode *node,
- const QRect& rect,
+ const QVector<QRect> &rects,
const QRect &cropRect);
friend class KisImageResizeCommand;
void setSize(const QSize& size);
friend class KisImageSetProjectionColorSpaceCommand;
void setProjectionColorSpace(const KoColorSpace * colorSpace);
friend class KisDeselectGlobalSelectionCommand;
friend class KisReselectGlobalSelectionCommand;
friend class KisSetGlobalSelectionCommand;
friend class KisImageTest;
friend class Document; // For libkis
/**
* Replaces the current global selection with globalSelection. If
* \p globalSelection is empty, removes the selection object, so that
* \ref globalSelection() will return 0 after that.
*/
void setGlobalSelection(KisSelectionSP globalSelection);
/**
* Deselects current global selection.
* \ref globalSelection() will return 0 after that.
*/
void deselectGlobalSelection();
/**
* Reselects current deselected selection
*
* \see deselectGlobalSelection()
*/
void reselectGlobalSelection();
private:
class KisImagePrivate;
KisImagePrivate * m_d;
};
#endif // KIS_IMAGE_H_
diff --git a/libs/image/kis_image_animation_interface.cpp b/libs/image/kis_image_animation_interface.cpp
index 8636811e55..ceff81c754 100644
--- a/libs/image/kis_image_animation_interface.cpp
+++ b/libs/image/kis_image_animation_interface.cpp
@@ -1,402 +1,416 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_image_animation_interface.h"
#include <QFileInfo>
#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) {
m_d->fullClipRange = range;
emit sigFullClipRangeChanged();
}
const KisTimeRange& KisImageAnimationInterface::playbackRange() const
{
return m_d->playbackRange.isValid() ? m_d->playbackRange : m_d->fullClipRange;
}
void KisImageAnimationInterface::setPlaybackRange(const KisTimeRange range)
{
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)
{
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;
KisTimeRange range = KisTimeRange::infinite(0);
KisTimeRange::calculateTimeRangeRecursive(m_d->image->root(), currentUITime(), range, true);
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 QRegion &dirtyRegion)
{
KisStrokeStrategy *strategy =
new KisRegenerateFrameStrokeStrategy(frameId,
dirtyRegion,
this);
QList<KisStrokeJobData*> 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<QRect>({rect}), recursive);
+}
+
+void KisImageAnimationInterface::notifyNodeChanged(const KisNode *node,
+ const QVector<QRect> &rects,
+ bool recursive)
{
if (externalFrameActive() || m_d->frameInvalidationBlocked) return;
if (node->inherits("KisSelectionMask")) return;
KisKeyframeChannel *channel =
node->getKeyframeChannel(KisKeyframeChannel::Content.id());
- if (recursive) {
- KisTimeRange affectedRange;
- KisTimeRange::calculateTimeRangeRecursive(node, currentTime(), affectedRange, false);
+ KisTimeRange invalidateRange;
- invalidateFrames(affectedRange, rect);
+ if (recursive) {
+ KisTimeRange::calculateTimeRangeRecursive(node, currentTime(), invalidateRange, false);
} else if (channel) {
const int currentTime = m_d->currentTime();
-
- invalidateFrames(channel->affectedFrames(currentTime), rect);
+ invalidateRange = channel->affectedFrames(currentTime);
} else {
- invalidateFrames(KisTimeRange::infinite(0), rect);
+ invalidateRange = KisTimeRange::infinite(0);
+ }
+
+
+ // we compress the updated rect (atm, noone 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_animation_interface.h b/libs/image/kis_image_animation_interface.h
index 04a400d151..e38f53ba0f 100644
--- a/libs/image/kis_image_animation_interface.h
+++ b/libs/image/kis_image_animation_interface.h
@@ -1,208 +1,210 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_IMAGE_ANIMATION_INTERFACE_H
#define __KIS_IMAGE_ANIMATION_INTERFACE_H
#include <QObject>
#include <QScopedPointer>
#include "kis_types.h"
#include "kritaimage_export.h"
class KisUpdatesFacade;
class KisTimeRange;
class KoColor;
namespace KisLayerUtils {
struct SwitchFrameCommand;
}
class KRITAIMAGE_EXPORT KisImageAnimationInterface : public QObject
{
Q_OBJECT
public:
KisImageAnimationInterface(KisImage *image);
KisImageAnimationInterface(const KisImageAnimationInterface &rhs, KisImage *newImage);
~KisImageAnimationInterface() override;
/**
* Returns true of the image has at least one animated layer
*/
bool hasAnimation() const;
/**
* Returns currently active frame of the underlying image. Some strokes
* can override this value and it will report a different value.
*/
int currentTime() const;
/**
* Same as currentTime, except it isn't changed when background strokes
* are running.
*/
int currentUITime() const;
/**
* While any non-current frame is being regenerated by the
* strategy, the image is kept in a special state, named
* 'externalFrameActive'. Is this state the following applies:
*
* 1) All the animated paint devices switch its state into
* frameId() defined by global time.
*
* 2) All animation-not-capable devices switch to a temporary
* content device, which *is in undefined state*. The stroke
* should regenerate the image projection manually.
*/
bool externalFrameActive() const;
void requestTimeSwitchWithUndo(int time);
void requestTimeSwitchNonGUI(int time, bool useUndo = false);
public Q_SLOTS:
/**
* Switches current frame (synchronously) and starts an
* asynchronous regeneration of the entire image.
*/
void switchCurrentTimeAsync(int frameId, bool useUndo = false);
public:
/**
* Start a backgroud thread that will recalculate some extra frame.
* The result will be reported using two types of signals:
*
* 1) KisImage::sigImageUpdated() will be emitted for every chunk
* of updated area.
*
* 2) sigFrameReady() will be emitted in the end of the operation.
* IMPORTANT: to get the result you must connect to this signal
* with Qt::DirectConnection and fetch the result from
* frameProjection(). After the signal handler is exited, the
* data will no longer be available.
*/
void requestFrameRegeneration(int frameId, const QRegion &dirtyRegion);
+
void notifyNodeChanged(const KisNode *node, const QRect &rect, bool recursive);
+ void notifyNodeChanged(const KisNode *node, const QVector<QRect> &rects, bool recursive);
void invalidateFrames(const KisTimeRange &range, const QRect &rect);
/**
* Changes the default color of the "external frame" projection of
* the image's root layer. Please note that this command should be
* executed from a context of an exclusive job!
*/
void setDefaultProjectionColor(const KoColor &color);
/**
* The current time range selected by user.
* @return current time range
*/
const KisTimeRange& fullClipRange() const;
void setFullClipRange(const KisTimeRange range);
const KisTimeRange &playbackRange() const;
void setPlaybackRange(const KisTimeRange range);
int framerate() const;
/**
* @return **absolute** file name of the audio channel file
*/
QString audioChannelFileName() const;
/**
* Sets **absolute** file name of the audio channel file. Dont' try to pass
* a relative path, it'll assert!
*/
void setAudioChannelFileName(const QString &fileName);
/**
* @return is the audio channel is currently muted
*/
bool isAudioMuted() const;
/**
* Mutes the audio channel
*/
void setAudioMuted(bool value);
/**
* Returns the preferred audio value in rangle [0, 1]
*/
qreal audioVolume() const;
/**
* Set the preferred volume for the audio channel in range [0, 1]
*/
void setAudioVolume(qreal value);
public Q_SLOTS:
void setFramerate(int fps);
public:
KisImageWSP image() const;
int totalLength();
private:
// interface for:
friend class KisRegenerateFrameStrokeStrategy;
friend class KisAnimationFrameCacheTest;
friend struct KisLayerUtils::SwitchFrameCommand;
friend class KisImageTest;
void saveAndResetCurrentTime(int frameId, int *savedValue);
void restoreCurrentTime(int *savedValue);
void notifyFrameReady();
void notifyFrameCancelled();
KisUpdatesFacade* updatesFacade() const;
void blockFrameInvalidation(bool value);
friend class KisSwitchTimeStrokeStrategy;
void explicitlySetCurrentTime(int frameId);
Q_SIGNALS:
void sigFrameReady(int time);
void sigFrameCancelled();
void sigUiTimeChanged(int newTime);
void sigFramesChanged(const KisTimeRange &range, const QRect &rect);
void sigInternalRequestTimeSwitch(int frameId, bool useUndo);
void sigFramerateChanged();
void sigFullClipRangeChanged();
void sigPlaybackRangeChanged();
/**
* Emitted when the audio channel of the document is changed
*/
void sigAudioChannelChanged();
/**
* Emitted when audion volume changes. Please note that it doesn't change
* when you mute the channel! When muting, sigAudioChannelChanged() is used instead!
*/
void sigAudioVolumeChanged();
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_IMAGE_ANIMATION_INTERFACE_H */
diff --git a/libs/image/kis_image_config.cpp b/libs/image/kis_image_config.cpp
index 3d70145f00..e6c5562865 100644
--- a/libs/image/kis_image_config.cpp
+++ b/libs/image/kis_image_config.cpp
@@ -1,505 +1,519 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_image_config.h"
#include <ksharedconfig.h>
#include <KoConfig.h>
#include <KoColorProfile.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorConversionTransformation.h>
#include "kis_debug.h"
#include <QThread>
#include <QApplication>
#include <QColor>
#include <QDir>
#include "kis_global.h"
#include <cmath>
#ifdef Q_OS_OSX
#include <errno.h>
#endif
KisImageConfig::KisImageConfig(bool readOnly)
: m_config( KSharedConfig::openConfig()->group(QString())),
m_readOnly(readOnly)
{
#ifdef Q_OS_OSX
// 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::swapDir(bool requestDefault)
{
#ifdef Q_OS_OSX
// 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 obsure /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 = QDir::homePath();
#else
QString swap = QDir::tempPath();
#endif
- return !requestDefault ?
+ QString configuredSwap = !requestDefault ?
m_config.readEntry("swaplocation", swap) : swap;
+ if (configuredSwap.isEmpty()) {
+ configuredSwap = swap;
+ }
+ return swap;
}
void KisImageConfig::setSwapDir(const QString &swapDir)
{
m_config.writeEntry("swaplocation", swapDir);
}
int KisImageConfig::numberOfOnionSkins() const
{
return m_config.readEntry("numberOfOnionSkins", 10);
}
void KisImageConfig::setNumberOfOnionSkins(int value)
{
m_config.writeEntry("numberOfOnionSkins", value);
}
int KisImageConfig::onionSkinTintFactor() const
{
return m_config.readEntry("onionSkinTintFactor", 192);
}
void KisImageConfig::setOnionSkinTintFactor(int value)
{
m_config.writeEntry("onionSkinTintFactor", value);
}
int KisImageConfig::onionSkinOpacity(int offset) const
{
int value = m_config.readEntry("onionSkinOpacity_" + QString::number(offset), -1);
if (value < 0) {
const int num = numberOfOnionSkins();
const qreal dx = qreal(qAbs(offset)) / num;
value = 0.7 * exp(-pow2(dx) / 0.5) * 255;
}
return value;
}
void KisImageConfig::setOnionSkinOpacity(int offset, int value)
{
m_config.writeEntry("onionSkinOpacity_" + QString::number(offset), value);
}
bool KisImageConfig::onionSkinState(int offset) const
{
bool enableByDefault = (qAbs(offset) <= 2);
return m_config.readEntry("onionSkinState_" + QString::number(offset), enableByDefault);
}
void KisImageConfig::setOnionSkinState(int offset, bool value)
{
m_config.writeEntry("onionSkinState_" + QString::number(offset), value);
}
QColor KisImageConfig::onionSkinTintColorBackward() const
{
return m_config.readEntry("onionSkinTintColorBackward", QColor(Qt::red));
}
void KisImageConfig::setOnionSkinTintColorBackward(const QColor &value)
{
m_config.writeEntry("onionSkinTintColorBackward", value);
}
QColor KisImageConfig::onionSkinTintColorForward() const
{
return m_config.readEntry("oninSkinTintColorForward", QColor(Qt::green));
}
void KisImageConfig::setOnionSkinTintColorForward(const QColor &value)
{
m_config.writeEntry("oninSkinTintColorForward", value);
}
bool KisImageConfig::lazyFrameCreationEnabled(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("lazyFrameCreationEnabled", true) : true;
}
void KisImageConfig::setLazyFrameCreationEnabled(bool value)
{
m_config.writeEntry("lazyFrameCreationEnabled", value);
}
#if defined Q_OS_LINUX
#include <sys/sysinfo.h>
#elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD
#include <sys/sysctl.h>
#elif defined Q_OS_WIN
#include <windows.h>
#elif defined Q_OS_OSX
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
#include <kis_debug.h>
int KisImageConfig::totalRAM()
{
// let's think that default memory size is 1000MiB
int totalMemory = 1000; // MiB
int error = 1;
#if defined Q_OS_LINUX
struct sysinfo info;
error = sysinfo(&info);
if(!error) {
totalMemory = info.totalram * info.mem_unit / (1UL << 20);
}
#elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD
u_long physmem;
# if defined HW_PHYSMEM64 // NetBSD only
int mib[] = {CTL_HW, HW_PHYSMEM64};
# else
int mib[] = {CTL_HW, HW_PHYSMEM};
# endif
size_t len = sizeof(physmem);
error = sysctl(mib, 2, &physmem, &len, 0, 0);
if(!error) {
totalMemory = physmem >> 20;
}
#elif defined Q_OS_WIN
MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
error = !GlobalMemoryStatusEx(&status);
if (!error) {
totalMemory = status.ullTotalPhys >> 20;
}
// For 32 bit windows, the total memory available is at max the 2GB per process memory limit.
# if defined ENV32BIT
totalMemory = qMin(totalMemory, 2000);
# endif
#elif defined Q_OS_OSX
int mib[2] = { CTL_HW, HW_MEMSIZE };
u_int namelen = sizeof(mib) / sizeof(mib[0]);
uint64_t size;
size_t len = sizeof(size);
errno = 0;
if (sysctl(mib, namelen, &size, &len, 0, 0) >= 0) {
totalMemory = size >> 20;
error = 0;
}
else {
dbgKrita << "sysctl(\"hw.memsize\") raised error" << strerror(errno);
}
#endif
if (error) {
warnKrita << "Cannot get the size of your RAM. Using 1 GiB by default.";
}
return totalMemory;
}
bool KisImageConfig::showAdditionalOnionSkinsSettings(bool requestDefault) const
{
return !requestDefault ?
m_config.readEntry("showAdditionalOnionSkinsSettings", true) : true;
}
void KisImageConfig::setShowAdditionalOnionSkinsSettings(bool value)
{
m_config.writeEntry("showAdditionalOnionSkinsSettings", value);
}
int KisImageConfig::defaultFrameColorLabel() const
{
return m_config.readEntry("defaultFrameColorLabel", 0);
}
void KisImageConfig::setDefaultFrameColorLabel(int label)
{
m_config.writeEntry("defaultFrameColorLabel", label);
}
KisProofingConfigurationSP KisImageConfig::defaultProofingconfiguration()
{
KisProofingConfiguration *proofingConfig= new KisProofingConfiguration();
proofingConfig->proofingProfile = m_config.readEntry("defaultProofingProfileName", "Chemical proof");
proofingConfig->proofingModel = m_config.readEntry("defaultProofingProfileModel", "CMYKA");
proofingConfig->proofingDepth = m_config.readEntry("defaultProofingProfileDepth", "U8");
proofingConfig->intent = (KoColorConversionTransformation::Intent)m_config.readEntry("defaultProofingProfileIntent", 3);
if (m_config.readEntry("defaultProofingBlackpointCompensation", true)) {
proofingConfig->conversionFlags |= KoColorConversionTransformation::ConversionFlag::BlackpointCompensation;
} else {
- proofingConfig->conversionFlags = proofingConfig->conversionFlags & ~KoColorConversionTransformation::ConversionFlag::BlackpointCompensation;
+ 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
+{
+ return defaultValue ? 100 : m_config.readEntry("fpsLimit", 100);
+}
+
+void KisImageConfig::setFpsLimit(int value)
+{
+ m_config.writeEntry("fpsLimit", value);
+}
diff --git a/libs/image/kis_image_config.h b/libs/image/kis_image_config.h
index 7aeaaacd3f..b701c1d2ab 100644
--- a/libs/image/kis_image_config.h
+++ b/libs/image/kis_image_config.h
@@ -1,130 +1,133 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_IMAGE_CONFIG_H_
#define KIS_IMAGE_CONFIG_H_
#include <kconfiggroup.h>
#include "kritaimage_export.h"
#include "KisProofingConfiguration.h"
#include "kis_types.h"
class KRITAIMAGE_EXPORT KisImageConfig
{
public:
KisImageConfig(bool readOnly = false);
~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 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);
+
private:
Q_DISABLE_COPY(KisImageConfig)
private:
KConfigGroup m_config;
bool m_readOnly;
};
#endif /* KIS_IMAGE_CONFIG_H_ */
diff --git a/libs/image/kis_node.cpp b/libs/image/kis_node.cpp
index b289fe26f4..1b9e22e6f2 100644
--- a/libs/image/kis_node.cpp
+++ b/libs/image/kis_node.cpp
@@ -1,650 +1,648 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node.h"
#include <QList>
#include <QReadWriteLock>
#include <QReadLocker>
#include <QWriteLocker>
#include <QPainterPath>
#include <QRect>
#include <QCoreApplication>
#include <KoProperties.h>
#include "kis_global.h"
#include "kis_node_graph_listener.h"
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_node_progress_proxy.h"
#include "kis_busy_progress_indicator.h"
#include "kis_clone_layer.h"
#include "kis_safe_read_list.h"
typedef KisSafeReadList<KisNodeSP> KisSafeReadNodeList;
#include "kis_abstract_projection_plane.h"
#include "kis_projection_leaf.h"
#include "kis_undo_adapter.h"
#include "kis_keyframe_channel.h"
/**
*The link between KisProjection ans KisImageUpdater
*uses queued signals with an argument of KisNodeSP type,
*so we should register it beforehand
*/
struct KisNodeSPStaticRegistrar {
KisNodeSPStaticRegistrar() {
qRegisterMetaType<KisNodeSP>("KisNodeSP");
}
};
static KisNodeSPStaticRegistrar __registrar1;
struct KisNodeListStaticRegistrar {
KisNodeListStaticRegistrar() {
qRegisterMetaType<KisNodeList>("KisNodeList");
}
};
static KisNodeListStaticRegistrar __registrar2;
/**
* Note about "thread safety" of KisNode
*
* 1) One can *read* any information about node and node graph in any
* number of threads concurrently. This operation is safe because
* of the usage of KisSafeReadNodeList and will run concurrently
* (lock-free).
*
* 2) One can *write* any information into the node or node graph in a
* single thread only! Changing the graph concurrently is *not*
* sane and therefore not supported.
*
* 3) One can *read and write* information about the node graph
* concurrently, given that there is only *one* writer thread and
* any number of reader threads. Please note that in this case the
* node's code is just guaranteed *not to crash*, which is ensured
* by nodeSubgraphLock. You need to ensure the sanity of the data
* read by the reader threads yourself!
*/
struct Q_DECL_HIDDEN KisNode::Private
{
public:
Private(KisNode *node)
: graphListener(0)
, nodeProgressProxy(0)
, busyProgressIndicator(0)
, projectionLeaf(new KisProjectionLeaf(node))
{
}
KisNodeWSP parent;
KisNodeGraphListener *graphListener;
KisSafeReadNodeList nodes;
KisNodeProgressProxy *nodeProgressProxy;
KisBusyProgressIndicator *busyProgressIndicator;
QReadWriteLock nodeSubgraphLock;
KisProjectionLeafSP projectionLeaf;
const KisNode* findSymmetricClone(const KisNode *srcRoot,
const KisNode *dstRoot,
const KisNode *srcTarget);
void processDuplicatedClones(const KisNode *srcDuplicationRoot,
const KisNode *dstDuplicationRoot,
KisNode *node);
};
/**
* Finds the layer in \p dstRoot subtree, which has the same path as
* \p srcTarget has in \p srcRoot
*/
const KisNode* KisNode::Private::findSymmetricClone(const KisNode *srcRoot,
const KisNode *dstRoot,
const KisNode *srcTarget)
{
if (srcRoot == srcTarget) return dstRoot;
KisSafeReadNodeList::const_iterator srcIter = srcRoot->m_d->nodes.constBegin();
KisSafeReadNodeList::const_iterator dstIter = dstRoot->m_d->nodes.constBegin();
for (; srcIter != srcRoot->m_d->nodes.constEnd(); srcIter++, dstIter++) {
KIS_ASSERT_RECOVER_RETURN_VALUE((srcIter != srcRoot->m_d->nodes.constEnd()) ==
(dstIter != dstRoot->m_d->nodes.constEnd()), 0);
const KisNode *node = findSymmetricClone(srcIter->data(), dstIter->data(), srcTarget);
if (node) return node;
}
return 0;
}
/**
* This function walks through a subtrees of old and new layers and
* searches for clone layers. For each clone layer it checks whether
* its copyFrom() lays inside the old subtree, and if it is so resets
* it to the corresponding layer in the new subtree.
*
* That is needed when the user duplicates a group layer with all its
* layer subtree. In such a case all the "internal" clones must stay
* "internal" and not point to the layers of the older group.
*/
void KisNode::Private::processDuplicatedClones(const KisNode *srcDuplicationRoot,
const KisNode *dstDuplicationRoot,
KisNode *node)
{
if (KisCloneLayer *clone = dynamic_cast<KisCloneLayer*>(node)) {
KIS_ASSERT_RECOVER_RETURN(clone->copyFrom());
const KisNode *newCopyFrom = findSymmetricClone(srcDuplicationRoot,
dstDuplicationRoot,
clone->copyFrom());
if (newCopyFrom) {
KisLayer *newCopyFromLayer = qobject_cast<KisLayer*>(const_cast<KisNode*>(newCopyFrom));
KIS_ASSERT_RECOVER_RETURN(newCopyFromLayer);
clone->setCopyFrom(newCopyFromLayer);
}
}
KisSafeReadNodeList::const_iterator iter;
FOREACH_SAFE(iter, node->m_d->nodes) {
KisNode *child = const_cast<KisNode*>((*iter).data());
processDuplicatedClones(srcDuplicationRoot, dstDuplicationRoot, child);
}
}
KisNode::KisNode()
: m_d(new Private(this))
{
m_d->parent = 0;
m_d->graphListener = 0;
moveToThread(qApp->thread());
}
KisNode::KisNode(const KisNode & rhs)
: KisBaseNode(rhs)
, m_d(new Private(this))
{
m_d->parent = 0;
m_d->graphListener = 0;
moveToThread(qApp->thread());
// HACK ALERT: we create opacity channel in KisBaseNode, but we cannot
// initialize its node from there! So workaround it here!
QMap<QString, KisKeyframeChannel*> channels = rhs.keyframeChannels();
for (auto it = channels.begin(); it != channels.end(); ++it) {
it.value()->setNode(this);
}
// NOTE: the nodes are not supposed to be added/removed while
// creation of another node, so we do *no* locking here!
KisSafeReadNodeList::const_iterator iter;
FOREACH_SAFE(iter, rhs.m_d->nodes) {
KisNodeSP child = (*iter)->clone();
child->createNodeProgressProxy();
m_d->nodes.append(child);
child->setParent(this);
}
m_d->processDuplicatedClones(&rhs, this, this);
}
KisNode::~KisNode()
{
if (m_d->busyProgressIndicator) {
m_d->busyProgressIndicator->prepareDestroying();
m_d->busyProgressIndicator->deleteLater();
}
if (m_d->nodeProgressProxy) {
m_d->nodeProgressProxy->prepareDestroying();
m_d->nodeProgressProxy->deleteLater();
}
{
QWriteLocker l(&m_d->nodeSubgraphLock);
m_d->nodes.clear();
}
delete m_d;
}
QRect KisNode::needRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
QRect KisNode::changeRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
QRect KisNode::accessRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
void KisNode::childNodeChanged(KisNodeSP changedChildNode)
{
}
KisAbstractProjectionPlaneSP KisNode::projectionPlane() const
{
KIS_ASSERT_RECOVER_NOOP(0 && "KisNode::projectionPlane() is not defined!");
static KisAbstractProjectionPlaneSP plane =
toQShared(new KisDumbProjectionPlane());
return plane;
}
KisProjectionLeafSP KisNode::projectionLeaf() const
{
return m_d->projectionLeaf;
}
bool KisNode::accept(KisNodeVisitor &v)
{
return v.visit(this);
}
void KisNode::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
visitor.visit(this, undoAdapter);
}
int KisNode::graphSequenceNumber() const
{
return m_d->graphListener ? m_d->graphListener->graphSequenceNumber() : -1;
}
KisNodeGraphListener *KisNode::graphListener() const
{
return m_d->graphListener;
}
void KisNode::setGraphListener(KisNodeGraphListener *graphListener)
{
m_d->graphListener = graphListener;
QReadLocker l(&m_d->nodeSubgraphLock);
KisSafeReadNodeList::const_iterator iter;
FOREACH_SAFE(iter, m_d->nodes) {
KisNodeSP child = (*iter);
child->setGraphListener(graphListener);
}
}
void KisNode::setParent(KisNodeWSP parent)
{
QWriteLocker l(&m_d->nodeSubgraphLock);
m_d->parent = parent;
}
KisNodeSP KisNode::parent() const
{
QReadLocker l(&m_d->nodeSubgraphLock);
return m_d->parent.isValid() ? KisNodeSP(m_d->parent) : KisNodeSP();
}
KisBaseNodeSP KisNode::parentCallback() const
{
return parent();
}
void KisNode::notifyParentVisibilityChanged(bool value)
{
QReadLocker l(&m_d->nodeSubgraphLock);
KisSafeReadNodeList::const_iterator iter;
FOREACH_SAFE(iter, m_d->nodes) {
KisNodeSP child = (*iter);
child->notifyParentVisibilityChanged(value);
}
}
void KisNode::baseNodeChangedCallback()
{
if(m_d->graphListener) {
m_d->graphListener->nodeChanged(this);
}
}
void KisNode::baseNodeInvalidateAllFramesCallback()
{
if(m_d->graphListener) {
m_d->graphListener->invalidateAllFrames();
}
}
void KisNode::addKeyframeChannel(KisKeyframeChannel *channel)
{
channel->setNode(this);
KisBaseNode::addKeyframeChannel(channel);
}
KisNodeSP KisNode::firstChild() const
{
QReadLocker l(&m_d->nodeSubgraphLock);
return !m_d->nodes.isEmpty() ? m_d->nodes.first() : 0;
}
KisNodeSP KisNode::lastChild() const
{
QReadLocker l(&m_d->nodeSubgraphLock);
return !m_d->nodes.isEmpty() ? m_d->nodes.last() : 0;
}
KisNodeSP KisNode::prevChildImpl(KisNodeSP child)
{
/**
* Warning: mind locking policy!
*
* The graph locks must be *always* taken in descending
* order. That is if you want to (or it implicitly happens that
* you) take a lock of a parent and a chil, you must first take
* the lock of a parent, and only after that ask a child to do the
* same. Otherwise you'll get a deadlock.
*/
QReadLocker l(&m_d->nodeSubgraphLock);
int i = m_d->nodes.indexOf(child) - 1;
return i >= 0 ? m_d->nodes.at(i) : 0;
}
KisNodeSP KisNode::nextChildImpl(KisNodeSP child)
{
/**
* See a comment in KisNode::prevChildImpl()
*/
QReadLocker l(&m_d->nodeSubgraphLock);
int i = m_d->nodes.indexOf(child) + 1;
return i > 0 && i < m_d->nodes.size() ? m_d->nodes.at(i) : 0;
}
KisNodeSP KisNode::prevSibling() const
{
KisNodeSP parentNode = parent();
return parentNode ? parentNode->prevChildImpl(const_cast<KisNode*>(this)) : 0;
}
KisNodeSP KisNode::nextSibling() const
{
KisNodeSP parentNode = parent();
return parentNode ? parentNode->nextChildImpl(const_cast<KisNode*>(this)) : 0;
}
quint32 KisNode::childCount() const
{
QReadLocker l(&m_d->nodeSubgraphLock);
return m_d->nodes.size();
}
KisNodeSP KisNode::at(quint32 index) const
{
QReadLocker l(&m_d->nodeSubgraphLock);
if (!m_d->nodes.isEmpty() && index < (quint32)m_d->nodes.size()) {
return m_d->nodes.at(index);
}
return 0;
}
int KisNode::index(const KisNodeSP node) const
{
QReadLocker l(&m_d->nodeSubgraphLock);
return m_d->nodes.indexOf(node);
}
QList<KisNodeSP> KisNode::childNodes(const QStringList & nodeTypes, const KoProperties & properties) const
{
QReadLocker l(&m_d->nodeSubgraphLock);
QList<KisNodeSP> nodes;
KisSafeReadNodeList::const_iterator iter;
FOREACH_SAFE(iter, m_d->nodes) {
if (*iter) {
if (properties.isEmpty() || (*iter)->check(properties)) {
bool rightType = true;
if(!nodeTypes.isEmpty()) {
rightType = false;
Q_FOREACH (const QString &nodeType, nodeTypes) {
if ((*iter)->inherits(nodeType.toLatin1())) {
rightType = true;
break;
}
}
}
if (rightType) {
nodes.append(*iter);
}
}
}
}
return nodes;
}
KisNodeSP KisNode::findChildByName(const QString &name)
{
KisNodeSP child = firstChild();
while (child) {
if (child->name() == name) {
return child;
}
if (child->childCount() > 0) {
KisNodeSP grandChild = child->findChildByName(name);
if (grandChild) {
return grandChild;
}
}
child = child->nextSibling();
}
return 0;
}
bool KisNode::add(KisNodeSP newNode, KisNodeSP aboveThis)
{
Q_ASSERT(newNode);
if (!newNode) return false;
if (aboveThis && aboveThis->parent().data() != this) return false;
if (!allowAsChild(newNode)) return false;
if (newNode->parent()) return false;
if (index(newNode) >= 0) return false;
int idx = aboveThis ? this->index(aboveThis) + 1 : 0;
// threoretical race condition may happen here ('idx' may become
// deprecated until the write lock will be held). But we ignore
// it, because it is not supported to add/remove nodes from two
// concurrent threads simultaneously
if (m_d->graphListener) {
m_d->graphListener->aboutToAddANode(this, idx);
}
{
QWriteLocker l(&m_d->nodeSubgraphLock);
newNode->createNodeProgressProxy();
m_d->nodes.insert(idx, newNode);
newNode->setParent(this);
newNode->setGraphListener(m_d->graphListener);
}
childNodeChanged(newNode);
if (m_d->graphListener) {
m_d->graphListener->nodeHasBeenAdded(this, idx);
}
return true;
}
bool KisNode::remove(quint32 index)
{
if (index < childCount()) {
KisNodeSP removedNode = at(index);
if (m_d->graphListener) {
m_d->graphListener->aboutToRemoveANode(this, index);
}
{
QWriteLocker l(&m_d->nodeSubgraphLock);
removedNode->setGraphListener(0);
removedNode->setParent(0); // after calling aboutToRemoveANode or then the model get broken according to TT's modeltest
m_d->nodes.removeAt(index);
}
childNodeChanged(removedNode);
if (m_d->graphListener) {
m_d->graphListener->nodeHasBeenRemoved(this, index);
}
return true;
}
return false;
}
bool KisNode::remove(KisNodeSP node)
{
return node->parent().data() == this ? remove(index(node)) : false;
}
KisNodeProgressProxy* KisNode::nodeProgressProxy() const
{
if (m_d->nodeProgressProxy) {
return m_d->nodeProgressProxy;
} else if (parent()) {
return parent()->nodeProgressProxy();
}
return 0;
}
KisBusyProgressIndicator* KisNode::busyProgressIndicator() const
{
if (m_d->busyProgressIndicator) {
return m_d->busyProgressIndicator;
} else if (parent()) {
return parent()->busyProgressIndicator();
}
return 0;
}
void KisNode::createNodeProgressProxy()
{
if (!m_d->nodeProgressProxy) {
m_d->nodeProgressProxy = new KisNodeProgressProxy(this);
m_d->busyProgressIndicator = new KisBusyProgressIndicator(m_d->nodeProgressProxy);
m_d->nodeProgressProxy->moveToThread(this->thread());
m_d->busyProgressIndicator->moveToThread(this->thread());
}
}
void KisNode::setDirty()
{
setDirty(extent());
}
void KisNode::setDirty(const QVector<QRect> &rects)
{
- Q_FOREACH (const QRect &rc, rects) {
- setDirty(rc);
+ if(m_d->graphListener) {
+ m_d->graphListener->requestProjectionUpdate(this, rects, true);
}
}
void KisNode::setDirty(const QRegion &region)
{
setDirty(region.rects());
}
void KisNode::setDirtyDontResetAnimationCache()
{
if(m_d->graphListener) {
- m_d->graphListener->requestProjectionUpdate(this, extent(), false);
+ m_d->graphListener->requestProjectionUpdate(this, {extent()}, false);
}
}
void KisNode::setDirty(const QRect & rect)
{
- if(m_d->graphListener) {
- m_d->graphListener->requestProjectionUpdate(this, rect, true);
- }
+ setDirty(QVector<QRect>({rect}));
}
void KisNode::invalidateFrames(const KisTimeRange &range, const QRect &rect)
{
if(m_d->graphListener) {
m_d->graphListener->invalidateFrames(range, rect);
}
}
void KisNode::requestTimeSwitch(int time)
{
if(m_d->graphListener) {
m_d->graphListener->requestTimeSwitch(time);
}
}
void KisNode::syncLodCache()
{
// noop. everything is done by getLodCapableDevices()
}
KisPaintDeviceList KisNode::getLodCapableDevices() const
{
KisPaintDeviceList list;
KisPaintDeviceSP device = paintDevice();
if (device) {
list << device;
}
KisPaintDeviceSP originalDevice = original();
if (originalDevice && originalDevice != device) {
list << originalDevice;
}
list << projectionPlane()->getLodCapableDevices();
return list;
}
diff --git a/libs/image/kis_node.h b/libs/image/kis_node.h
index 86c3f76a1d..aaa8be8ce6 100644
--- a/libs/image/kis_node.h
+++ b/libs/image/kis_node.h
@@ -1,410 +1,410 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_NODE_H
#define _KIS_NODE_H
#include "kis_types.h"
#include "kis_base_node.h"
#include "kritaimage_export.h"
#include <QVector>
class QRect;
class QStringList;
class KoProperties;
class KisNodeVisitor;
class KisNodeGraphListener;
class KisNodeProgressProxy;
class KisBusyProgressIndicator;
class KisAbstractProjectionPlane;
class KisProjectionLeaf;
class KisKeyframeChannel;
class KisTimeRange;
class KisUndoAdapter;
/**
* A KisNode is a KisBaseNode that knows about its direct peers, parent
* and children and whether it can have children.
*
* THREAD-SAFETY: All const methods of this class and setDirty calls
* are considered to be thread-safe(!). All the others
* especially add(), remove() and setParent() must be
* protected externally.
*
* NOTE: your subclasses must have the Q_OBJECT declaration, even if
* you do not define new signals or slots.
*/
class KRITAIMAGE_EXPORT KisNode : public KisBaseNode
{
friend class KisFilterMaskTest;
Q_OBJECT
public:
/**
* The struct describing the position of the node
* against the filthy node.
* NOTE: please change KisBaseRectsWalker::getPositionToFilthy
* when changing this struct
*/
enum PositionToFilthy {
N_ABOVE_FILTHY = 0x08,
N_FILTHY_PROJECTION = 0x20,
N_FILTHY = 0x40,
N_BELOW_FILTHY = 0x80
};
/**
* Create an empty node without a parent.
*/
KisNode();
/**
* Create a copy of this node. The copy will not have a parent
* node.
*/
KisNode(const KisNode & rhs);
/**
* Delete this node
*/
~KisNode() override;
virtual KisNodeSP clone() const = 0;
bool accept(KisNodeVisitor &v) override;
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override;
/**
* Re-implement this method to add constraints for the
* subclasses that can be added as children to this node
*
* @return false if the given node is not allowed as a child to this node
*/
virtual bool allowAsChild(KisNodeSP) const = 0;
/**
* Set the entire node extent dirty; this percolates up to parent
* nodes all the way to the root node. By default this is the
* empty rect (through KisBaseNode::extent())
*/
virtual void setDirty();
/**
* Add the given rect to the set of dirty rects for this node;
* this percolates up to parent nodes all the way to the root
* node.
*/
- virtual void setDirty(const QRect & rect);
+ void setDirty(const QRect & rect);
/**
* Add the given rects to the set of dirty rects for this node;
* this percolates up to parent nodes all the way to the root
* node.
*/
virtual void setDirty(const QVector<QRect> &rects);
/**
* Add the given region to the set of dirty rects for this node;
* this percolates up to parent nodes all the way to the root
* node, if propagate is true;
*/
- virtual void setDirty(const QRegion &region);
+ void setDirty(const QRegion &region);
/**
* @brief setDirtyDontResetAnimationCache does almost the same thing as usual
* setDirty() call, but doesn't reset the animation cache (since onlion skins are
* not used when rendering animation.
*/
void setDirtyDontResetAnimationCache();
/**
* Informs that the frames in the given range are no longer valid
* and need to be recached.
* @param range frames to invalidate
*/
void invalidateFrames(const KisTimeRange &range, const QRect &rect);
/**
* Informs that the current world time should be changed.
* Might be caused by e.g. undo operation
*/
void requestTimeSwitch(int time);
/**
* \return a pointer to a KisAbstractProjectionPlane interface of
* the node. This interface is used by the image merging
* framework to get information and to blending for the
* layer.
*
* Please note the difference between need/change/accessRect and
* the projectionPlane() interface. The former one gives
* information about internal composition of the layer, and the
* latter one about the total composition, including layer styles,
* pass-through blending and etc.
*/
virtual KisAbstractProjectionPlaneSP projectionPlane() const;
/**
* Synchronizes LoD caches of the node with the current state of it.
* The current level of detail is fetched from the image pointed by
* default bounds object
*/
virtual void syncLodCache();
virtual KisPaintDeviceList getLodCapableDevices() const;
/**
* The rendering of the image may not always happen in the order
* of the main graph. Pass-through nodes ake some subgraphs
* linear, so it the order of rendering change. projectionLeaf()
* is a special interface of KisNode that represents "a graph for
* projection rendering". Therefore the nodes in projectionLeaf()
* graph may have a different order the main one.
*/
virtual KisProjectionLeafSP projectionLeaf() const;
protected:
/**
* \return internal changeRect() of the node. Do not mix with \see
* projectionPlane()
*
* Some filters will cause a change of pixels those are outside
* a requested rect. E.g. we change a rect of 2x2, then we want to
* apply a convolution filter with kernel 4x4 (changeRect is
* (2+2*3)x(2+2*3)=8x8) to that area. The rect that should be updated
* on the layer will be exaclty 8x8. More than that the needRect for
* that update will be 14x14. See \ref needeRect.
*/
virtual QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
/**
* \return internal needRect() of the node. Do not mix with \see
* projectionPlane()
*
* Some filters need pixels outside the current processing rect to
* compute the new value (for instance, convolution filters)
* See \ref changeRect
* See \ref accessRect
*/
virtual QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
/**
* \return internal accessRect() of the node. Do not mix with \see
* projectionPlane()
*
* Shows the area of image, that may be accessed during accessing
* the node.
*
* Example. You have a layer that needs to prepare some rect on a
* projection, say expectedRect. To perform this, the projection
* of all the layers below of the size needRect(expectedRect)
* should be calculeated by the merger beforehand and the layer
* will access some other area of image inside the rect
* accessRect(expectedRect) during updateProjection call.
*
* This knowledge about real access rect of a node is used by the
* scheduler to avoid collisions between two multithreaded updaters
* and so avoid flickering of the image.
*
* Currently, this method has nondefault value for shifted clone
* layers only.
*/
virtual QRect accessRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
/**
* Called each time direct child nodes are added or removed under this
* node as parent. This does not track changes inside the child nodes
* or the child nodes' properties.
*/
virtual void childNodeChanged(KisNodeSP changedChildNode);
public: // Graph methods
/**
* @return the graph sequence number calculated by the associated
* graph listener. You can use it for checking for changes in the
* graph.
*/
int graphSequenceNumber() const;
/**
* @return the graph listener this node belongs to. 0 if the node
* does not belong to a grap listener.
*/
KisNodeGraphListener * graphListener() const;
/**
* Set the graph listener for this node. The graphlistener will be
* informed before and after the list of child nodes has changed.
*/
void setGraphListener(KisNodeGraphListener * graphListener);
/**
* Returns the parent node of this node. This is 0 only for a root
* node; otherwise this will be an actual Node
*/
KisNodeSP parent() const;
/**
* Returns the first child node of this node, or 0 if there are no
* child nodes.
*/
KisNodeSP firstChild() const;
/**
* Returns the last child node of this node, or 0 if there are no
* child nodes.
*/
KisNodeSP lastChild() const;
/**
* Returns the previous sibling of this node in the parent's list.
* This is the node *above* this node in the composition stack. 0
* is returned if this child has no more previous siblings (==
* firstChild())
*/
KisNodeSP prevSibling() const;
/**
* Returns the next sibling of this node in the parent's list.
* This is the node *below* this node in the composition stack. 0
* is returned if this child has no more next siblings (==
* lastChild())
*/
KisNodeSP nextSibling() const;
/**
* Returns how many direct child nodes this node has (not
* recursive).
*/
quint32 childCount() const;
/**
* Retrieve the child node at the specified index.
*
* @return 0 if there is no node at this index.
*/
KisNodeSP at(quint32 index) const;
/**
* Retrieve the index of the specified child node.
*
* @return -1 if the specified node is not a child node of this
* node.
*/
int index(const KisNodeSP node) const;
/**
* Return a list of child nodes of the current node that conform
* to the specified constraints. There are no guarantees about the
* order of the nodes in the list. The function is not recursive.
*
* @param nodeTypes. if not empty, only nodes that inherit the
* classnames in this stringlist will be returned.
* @param properties. if not empty, only nodes for which
* KisNodeBase::check(properties) returns true will be returned.
*/
QList<KisNodeSP> childNodes(const QStringList & nodeTypes, const KoProperties & properties) const;
/**
* @brief findChildByName finds the first child that has the given name
* @param name the name to look for
* @return the first child with the given name
*/
KisNodeSP findChildByName(const QString &name);
public:
/**
* @return the node progress proxy used by this node, if this node has no progress
* proxy, it will return the proxy of its parent, if the parent has no progress proxy
* it will return 0
*/
KisNodeProgressProxy* nodeProgressProxy() const;
KisBusyProgressIndicator* busyProgressIndicator() const;
private:
/**
* Create a node progress proxy for this node. You need to create a progress proxy only
* if the node is going to appear in the layerbox, and it needs to be created before
* the layer box is made aware of the proxy.
*/
void createNodeProgressProxy();
protected:
KisBaseNodeSP parentCallback() const override;
void notifyParentVisibilityChanged(bool value) override;
void baseNodeChangedCallback() override;
void baseNodeInvalidateAllFramesCallback() override;
protected:
void addKeyframeChannel(KisKeyframeChannel* channel) override;
private:
friend class KisNodeFacade;
friend class KisNodeTest;
friend class KisLayer; // Note: only for setting the preview mask!
/**
* Set the parent of this node.
*/
void setParent(KisNodeWSP parent);
/**
* Add the specified node above the specified node. If aboveThis
* is 0, the node is added at the bottom.
*/
bool add(KisNodeSP newNode, KisNodeSP aboveThis);
/**
* Removes the node at the specified index from the child nodes.
*
* @return false if there is no node at this index
*/
bool remove(quint32 index);
/**
* Removes the node from the child nodes.
*
* @return false if there's no such node in this node.
*/
bool remove(KisNodeSP node);
KisNodeSP prevChildImpl(KisNodeSP child);
KisNodeSP nextChildImpl(KisNodeSP child);
private:
struct Private;
Private * const m_d;
};
Q_DECLARE_METATYPE(KisNodeSP)
Q_DECLARE_METATYPE(KisNodeWSP)
#endif
diff --git a/libs/image/kis_node_graph_listener.cpp b/libs/image/kis_node_graph_listener.cpp
index 0d04e22df3..6e9bcf2029 100644
--- a/libs/image/kis_node_graph_listener.cpp
+++ b/libs/image/kis_node_graph_listener.cpp
@@ -1,102 +1,102 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_graph_listener.h"
#include "kis_time_range.h"
#include <QRect>
#include <QtGlobal>
struct Q_DECL_HIDDEN KisNodeGraphListener::Private
{
Private() : sequenceNumber(0) {}
int sequenceNumber;
};
KisNodeGraphListener::KisNodeGraphListener()
: m_d(new Private())
{
}
KisNodeGraphListener::~KisNodeGraphListener()
{
}
void KisNodeGraphListener::aboutToAddANode(KisNode */*parent*/, int /*index*/)
{
m_d->sequenceNumber++;
}
void KisNodeGraphListener::nodeHasBeenAdded(KisNode */*parent*/, int /*index*/)
{
m_d->sequenceNumber++;
}
void KisNodeGraphListener::aboutToRemoveANode(KisNode */*parent*/, int /*index*/)
{
m_d->sequenceNumber++;
}
void KisNodeGraphListener::nodeHasBeenRemoved(KisNode */*parent*/, int /*index*/)
{
m_d->sequenceNumber++;
}
void KisNodeGraphListener::aboutToMoveNode(KisNode * /*node*/, int /*oldIndex*/, int /*newIndex*/)
{
m_d->sequenceNumber++;
}
void KisNodeGraphListener::nodeHasBeenMoved(KisNode * /*node*/, int /*oldIndex*/, int /*newIndex*/)
{
m_d->sequenceNumber++;
}
int KisNodeGraphListener::graphSequenceNumber() const
{
return m_d->sequenceNumber;
}
void KisNodeGraphListener::nodeChanged(KisNode * /*node*/)
{
}
void KisNodeGraphListener::invalidateAllFrames()
{
}
void KisNodeGraphListener::notifySelectionChanged()
{
}
-void KisNodeGraphListener::requestProjectionUpdate(KisNode * /*node*/, const QRect& /*rect*/, bool /*resetAnimationCache*/)
+void KisNodeGraphListener::requestProjectionUpdate(KisNode * /*node*/, const QVector<QRect> &/*rects*/, bool /*resetAnimationCache*/)
{
}
void KisNodeGraphListener::invalidateFrames(const KisTimeRange &range, const QRect &rect)
{
Q_UNUSED(range);
Q_UNUSED(rect);
}
void KisNodeGraphListener::requestTimeSwitch(int time)
{
Q_UNUSED(time);
}
diff --git a/libs/image/kis_node_graph_listener.h b/libs/image/kis_node_graph_listener.h
index 692359afcb..f0290f5856 100644
--- a/libs/image/kis_node_graph_listener.h
+++ b/libs/image/kis_node_graph_listener.h
@@ -1,123 +1,123 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_NODE_GRAPH_LISTENER_H_
#define KIS_NODE_GRAPH_LISTENER_H_
#include "kritaimage_export.h"
#include <QScopedPointer>
class KisTimeRange;
class KisNode;
class QRect;
/**
* Implementations of this class are called by nodes whenever the node
* graph changes. These implementations can then emit the right
* signals so Qt interview models can be updated before and after
* changes.
*
* The reason for this go-between is that we don't want our nodes to
* be QObjects, nor to have sig-slot connections between every node
* and every mode.
*
* It also manages the sequence number of the graph. This is a number
* which can be used as a checksum for whether the graph has chenged
* from some period of time or not. \see graphSequenceNumber()
*/
class KRITAIMAGE_EXPORT KisNodeGraphListener
{
public:
KisNodeGraphListener();
virtual ~KisNodeGraphListener();
/**
* Inform the model that we're going to add a node.
*/
virtual void aboutToAddANode(KisNode *parent, int index);
/**
* Inform the model we're done adding a node.
*/
virtual void nodeHasBeenAdded(KisNode *parent, int index);
/**
* Inform the model we're going to remove a node.
*/
virtual void aboutToRemoveANode(KisNode *parent, int index);
/**
* Inform the model we're done removing a node.
*/
virtual void nodeHasBeenRemoved(KisNode *parent, int index);
/**
* Inform the model we're about to start moving a node (which
* includes removing and adding the same node)
*/
virtual void aboutToMoveNode(KisNode * node, int oldIndex, int newIndex);
/**
* Inform the model we're done moving the node: it has been
* removed and added successfully
*/
virtual void nodeHasBeenMoved(KisNode * node, int oldIndex, int newIndex);
virtual void nodeChanged(KisNode * node);
virtual void invalidateAllFrames();
/**
* Inform the model that one of the selections in the graph is
* changed. The sender is not passed to the function (at least for
* now) because the UI should decide itself whether it needs to
* fetch new selection of not.
*/
virtual void notifySelectionChanged();
/**
* Inform the model that a node has been changed (setDirty)
*/
- virtual void requestProjectionUpdate(KisNode * node, const QRect& rect, bool resetAnimationCache);
+ virtual void requestProjectionUpdate(KisNode * node, const QVector<QRect> &rects, bool resetAnimationCache);
virtual void invalidateFrames(const KisTimeRange &range, const QRect &rect);
virtual void requestTimeSwitch(int time);
/**
* Returns the sequence of the graph.
*
* Every time some operation performed, which might change the
* hierarchy of the nodes, the sequence number grows by one. So
* if you have any information about the graph which was acquired
* when the sequence number was X and now it has become Y, it
* means your information is outdated.
*
* It is used in the scheduler for checking whether queued walkers
* should be regenerated.
*/
int graphSequenceNumber() const;
private:
struct Private;
QScopedPointer<Private> m_d;
};
#endif
diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc
index 5ade111ff6..9cadbc02ae 100644
--- a/libs/image/kis_painter.cc
+++ b/libs/image/kis_painter.cc
@@ -1,2915 +1,2906 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Clarence Dang <dang@kde.org>
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 José Luis Vergara Toloza <pentalis@gmail.com>
* Copyright (c) 2011 Silvio Heinrich <plassy@web.de>
*
* 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_painter.h"
#include <stdlib.h>
#include <string.h>
#include <cfloat>
#include <cmath>
#include <climits>
#ifndef Q_OS_WIN
#include <strings.h>
#endif
#include <QImage>
#include <QRect>
#include <QString>
#include <QStringList>
#include <kundo2command.h>
#include <kis_debug.h>
#include <klocalizedstring.h>
-#include <KoColorSpace.h>
-#include <KoColor.h>
-#include <KoCompositeOpRegistry.h>
-
#include "kis_image.h"
#include "filter/kis_filter.h"
#include "kis_layer.h"
#include "kis_paint_device.h"
#include "kis_fixed_paint_device.h"
#include "kis_transaction.h"
#include "kis_vec.h"
#include "kis_iterator_ng.h"
#include "kis_random_accessor_ng.h"
-#include "kis_paintop.h"
-#include "kis_selection.h"
-#include "kis_fill_painter.h"
+
#include "filter/kis_filter_configuration.h"
#include "kis_pixel_selection.h"
#include <brushengine/kis_paint_information.h>
#include "kis_paintop_registry.h"
#include "kis_perspective_math.h"
#include "tiles3/kis_random_accessor.h"
#include <kis_distance_information.h>
#include <KoColorSpaceMaths.h>
#include "kis_lod_transform.h"
#include "kis_algebra_2d.h"
-
+#include "krita_utils.h"
// Maximum distance from a Bezier control point to the line through the start
// and end points for the curve to be considered flat.
#define BEZIER_FLATNESS_THRESHOLD 0.5
#define trunc(x) ((int)(x))
#ifndef Q_OS_WIN
#endif
-struct Q_DECL_HIDDEN KisPainter::Private {
- Private(KisPainter *_q) : q(_q) {}
- Private(KisPainter *_q, const KoColorSpace *cs)
- : q(_q), paintColor(cs), backgroundColor(cs) {}
-
- KisPainter *q;
-
- KisPaintDeviceSP device;
- KisSelectionSP selection;
- KisTransaction* transaction;
- KoUpdater* progressUpdater;
-
- QVector<QRect> dirtyRects;
- KisPaintOp* paintOp;
- KoColor paintColor;
- KoColor backgroundColor;
- KoColor customColor;
- KisFilterConfigurationSP generator;
- KisPaintLayer* sourceLayer;
- FillStyle fillStyle;
- StrokeStyle strokeStyle;
- bool antiAliasPolygonFill;
- const KoPattern* pattern;
- QPointF duplicateOffset;
- quint32 pixelSize;
- const KoColorSpace* colorSpace;
- KoColorProfile* profile;
- const KoCompositeOp* compositeOp;
- const KoAbstractGradient* gradient;
- KisPaintOpPresetSP paintOpPreset;
- QImage polygonMaskImage;
- QPainter* maskPainter;
- KisFillPainter* fillPainter;
- KisPaintDeviceSP polygon;
- qint32 maskImageWidth;
- qint32 maskImageHeight;
- QPointF axesCenter;
- bool mirrorHorizontally;
- bool mirrorVertically;
- bool isOpacityUnit; // TODO: move into ParameterInfo
- KoCompositeOp::ParameterInfo paramInfo;
- KoColorConversionTransformation::Intent renderingIntent;
- KoColorConversionTransformation::ConversionFlags conversionFlags;
-
- bool tryReduceSourceRect(const KisPaintDevice *srcDev,
- QRect *srcRect,
- qint32 *srcX,
- qint32 *srcY,
- qint32 *srcWidth,
- qint32 *srcHeight,
- qint32 *dstX,
- qint32 *dstY);
-
- void fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect);
-};
+#include "kis_painter_p.h"
KisPainter::KisPainter()
: d(new Private(this))
{
init();
}
KisPainter::KisPainter(KisPaintDeviceSP device)
: d(new Private(this, device->colorSpace()))
{
init();
Q_ASSERT(device);
begin(device);
}
KisPainter::KisPainter(KisPaintDeviceSP device, KisSelectionSP selection)
: d(new Private(this, device->colorSpace()))
{
init();
Q_ASSERT(device);
begin(device);
d->selection = selection;
}
void KisPainter::init()
{
d->selection = 0 ;
d->transaction = 0;
d->paintOp = 0;
d->pattern = 0;
d->sourceLayer = 0;
d->fillStyle = FillStyleNone;
d->strokeStyle = StrokeStyleBrush;
d->antiAliasPolygonFill = true;
d->progressUpdater = 0;
d->gradient = 0;
d->maskPainter = 0;
d->fillPainter = 0;
d->maskImageWidth = 255;
d->maskImageHeight = 255;
d->mirrorHorizontally = false;
d->mirrorVertically = false;
d->isOpacityUnit = true;
d->paramInfo = KoCompositeOp::ParameterInfo();
d->renderingIntent = KoColorConversionTransformation::internalRenderingIntent();
d->conversionFlags = KoColorConversionTransformation::internalConversionFlags();
}
KisPainter::~KisPainter()
{
// TODO: Maybe, don't be that strict?
// deleteTransaction();
end();
delete d->paintOp;
delete d->maskPainter;
delete d->fillPainter;
delete d;
}
template <bool useOldData>
void copyAreaOptimizedImpl(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &srcRect)
{
const QRect dstRect(dstPt, srcRect.size());
const bool srcEmpty = (src->extent() & srcRect).isEmpty();
const bool dstEmpty = (dst->extent() & dstRect).isEmpty();
if (!srcEmpty || !dstEmpty) {
if (srcEmpty) {
dst->clear(dstRect);
} else {
KisPainter gc(dst);
gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY));
if (useOldData) {
gc.bitBltOldData(dstRect.topLeft(), src, srcRect);
} else {
gc.bitBlt(dstRect.topLeft(), src, srcRect);
}
}
}
}
void KisPainter::copyAreaOptimized(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &srcRect)
{
copyAreaOptimizedImpl<false>(dstPt, src, dst, srcRect);
}
void KisPainter::copyAreaOptimizedOldData(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &srcRect)
{
copyAreaOptimizedImpl<true>(dstPt, src, dst, srcRect);
}
void KisPainter::copyAreaOptimized(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &originalSrcRect,
KisSelectionSP selection)
{
if (!selection) {
copyAreaOptimized(dstPt, src, dst, originalSrcRect);
return;
}
const QRect selectionRect = selection->selectedRect();
const QRect srcRect = originalSrcRect & selectionRect;
const QPoint dstOffset = srcRect.topLeft() - originalSrcRect.topLeft();
const QRect dstRect = QRect(dstPt + dstOffset, srcRect.size());
const bool srcEmpty = (src->extent() & srcRect).isEmpty();
const bool dstEmpty = (dst->extent() & dstRect).isEmpty();
if (!srcEmpty || !dstEmpty) {
//if (srcEmpty) {
// doesn't support dstRect
// dst->clearSelection(selection);
// } else */
{
KisPainter gc(dst);
gc.setSelection(selection);
gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY));
gc.bitBlt(dstRect.topLeft(), src, srcRect);
}
}
}
KisPaintDeviceSP KisPainter::convertToAlphaAsAlpha(KisPaintDeviceSP src)
{
const KoColorSpace *srcCS = src->colorSpace();
const QRect processRect = src->extent();
KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()));
KisSequentialConstIterator srcIt(src, processRect);
KisSequentialIterator dstIt(dst, processRect);
do {
const quint8 *srcPtr = srcIt.rawDataConst();
quint8 *alpha8Ptr = dstIt.rawData();
const quint8 white = srcCS->intensity8(srcPtr);
const quint8 alpha = srcCS->opacityU8(srcPtr);
*alpha8Ptr = KoColorSpaceMaths<quint8>::multiply(alpha, KoColorSpaceMathsTraits<quint8>::unitValue - white);
} while (srcIt.nextPixel() && dstIt.nextPixel());
return dst;
}
KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src)
{
const KoColorSpace *srcCS = src->colorSpace();
const QRect processRect = src->extent();
KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()));
KisSequentialConstIterator srcIt(src, processRect);
KisSequentialIterator dstIt(dst, processRect);
do {
const quint8 *srcPtr = srcIt.rawDataConst();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->intensity8(srcPtr);
} while (srcIt.nextPixel() && dstIt.nextPixel());
return dst;
}
bool KisPainter::checkDeviceHasTransparency(KisPaintDeviceSP dev)
{
const QRect deviceBounds = dev->exactBounds();
const QRect imageBounds = dev->defaultBounds()->bounds();
if (deviceBounds.isEmpty() ||
(deviceBounds & imageBounds) != imageBounds) {
return true;
}
const KoColorSpace *cs = dev->colorSpace();
KisSequentialConstIterator it(dev, deviceBounds);
do {
if (cs->opacityU8(it.rawDataConst()) != OPACITY_OPAQUE_U8) {
return true;
}
} while(it.nextPixel());
return false;
}
void KisPainter::begin(KisPaintDeviceSP device)
{
begin(device, d->selection);
}
void KisPainter::begin(KisPaintDeviceSP device, KisSelectionSP selection)
{
if (!device) return;
d->selection = selection;
Q_ASSERT(device->colorSpace());
end();
d->device = device;
d->colorSpace = device->colorSpace();
d->compositeOp = d->colorSpace->compositeOp(COMPOSITE_OVER);
d->pixelSize = device->pixelSize();
}
void KisPainter::end()
{
Q_ASSERT_X(!d->transaction, "KisPainter::end()",
"end() was called for the painter having a transaction. "
"Please use end/deleteTransaction() instead");
}
void KisPainter::beginTransaction(const KUndo2MagicString& transactionName,int timedID)
{
Q_ASSERT_X(!d->transaction, "KisPainter::beginTransaction()",
"You asked for a new transaction while still having "
"another one. Please finish the first one with "
"end/deleteTransaction() first");
d->transaction = new KisTransaction(transactionName, d->device);
Q_CHECK_PTR(d->transaction);
d->transaction->undoCommand()->setTimedID(timedID);
}
void KisPainter::revertTransaction()
{
Q_ASSERT_X(d->transaction, "KisPainter::revertTransaction()",
"No transaction is in progress");
d->transaction->revert();
delete d->transaction;
d->transaction = 0;
}
void KisPainter::endTransaction(KisUndoAdapter *undoAdapter)
{
Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()",
"No transaction is in progress");
d->transaction->commit(undoAdapter);
delete d->transaction;
d->transaction = 0;
}
void KisPainter::endTransaction(KisPostExecutionUndoAdapter *undoAdapter)
{
Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()",
"No transaction is in progress");
d->transaction->commit(undoAdapter);
delete d->transaction;
d->transaction = 0;
}
KUndo2Command* KisPainter::endAndTakeTransaction()
{
Q_ASSERT_X(d->transaction, "KisPainter::endTransaction()",
"No transaction is in progress");
KUndo2Command *transactionData = d->transaction->endAndTake();
delete d->transaction;
d->transaction = 0;
return transactionData;
}
void KisPainter::deleteTransaction()
{
if (!d->transaction) return;
delete d->transaction;
d->transaction = 0;
}
void KisPainter::putTransaction(KisTransaction* transaction)
{
Q_ASSERT_X(!d->transaction, "KisPainter::putTransaction()",
"You asked for a new transaction while still having "
"another one. Please finish the first one with "
"end/deleteTransaction() first");
d->transaction = transaction;
}
KisTransaction* KisPainter::takeTransaction()
{
Q_ASSERT_X(d->transaction, "KisPainter::takeTransaction()",
"No transaction is in progress");
KisTransaction *temp = d->transaction;
d->transaction = 0;
return temp;
}
QVector<QRect> KisPainter::takeDirtyRegion()
{
QVector<QRect> vrect = d->dirtyRects;
d->dirtyRects.clear();
return vrect;
}
void KisPainter::addDirtyRect(const QRect & rc)
{
QRect r = rc.normalized();
if (r.isValid()) {
d->dirtyRects.append(rc);
}
}
inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev,
QRect *srcRect,
qint32 *srcX,
qint32 *srcY,
qint32 *srcWidth,
qint32 *srcHeight,
qint32 *dstX,
qint32 *dstY)
{
/**
* In case of COMPOSITE_COPY and Wrap Around Mode even the pixels
* outside the device extent matter, because they will be either
* directly copied (former case) or cloned from another area of
* the image.
*/
if (compositeOp->id() != COMPOSITE_COPY &&
compositeOp->id() != COMPOSITE_DESTINATION_IN &&
compositeOp->id() != COMPOSITE_DESTINATION_ATOP &&
!srcDev->defaultBounds()->wrapAroundMode()) {
/**
* If srcDev->extent() (the area of the tiles containing
* srcDev) is smaller than srcRect, then shrink srcRect to
* that size. This is done as a speed optimization, useful for
* stack recomposition in KisImage. srcRect won't grow if
* srcDev->extent() is larger.
*/
*srcRect &= srcDev->extent();
if (srcRect->isEmpty()) return true;
// Readjust the function paramenters to the new dimensions.
*dstX += srcRect->x() - *srcX; // This will only add, not subtract
*dstY += srcRect->y() - *srcY; // Idem
srcRect->getRect(srcX, srcY, srcWidth, srcHeight);
}
return false;
}
void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
qint32 selX, qint32 selY,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight)
{
// TODO: get selX and selY working as intended
/* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just
initializing they perform some dummy passes with those parameters, and it must not crash */
if (srcWidth == 0 || srcHeight == 0) return;
if (srcDev.isNull()) return;
if (d->device.isNull()) return;
// Check that selection has an alpha colorspace, crash if false
Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8());
QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight);
QRect selRect = QRect(selX, selY, srcWidth, srcHeight);
/* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done,
so crash if someone attempts to do this. Don't resize YET as it would obfuscate the mistake. */
Q_ASSERT(selection->bounds().contains(selRect));
Q_UNUSED(selRect); // only used by the above Q_ASSERT
/**
* An optimization, which crops the source rect by the bounds of
* the source device when it is possible
*/
if (d->tryReduceSourceRect(srcDev, &srcRect,
&srcX, &srcY,
&srcWidth, &srcHeight,
&dstX, &dstY)) return;
/* Create an intermediate byte array to hold information before it is written
to the current paint device (d->device) */
quint8* dstBytes = 0;
try {
dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()];
} catch (std::bad_alloc) {
warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "dst bytes";
return;
}
d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
// Copy the relevant bytes of raw data from srcDev
quint8* srcBytes = 0;
try {
srcBytes = new quint8[srcWidth * srcHeight * srcDev->pixelSize()];
} catch (std::bad_alloc) {
warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "src bytes";
return;
}
srcDev->readBytes(srcBytes, srcX, srcY, srcWidth, srcHeight);
QRect selBounds = selection->bounds();
const quint8 *selRowStart = selection->data() +
(selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize();
/*
* This checks whether there is nothing selected.
*/
if (!d->selection) {
/* As there's nothing selected, blit to dstBytes (intermediary bit array),
ignoring d->selection (the user selection)*/
d->paramInfo.dstRowStart = dstBytes;
d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize();
d->paramInfo.srcRowStart = srcBytes;
d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize();
d->paramInfo.maskRowStart = selRowStart;
d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize();
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
}
else {
/* Read the user selection (d->selection) bytes into an array, ready
to merge in the next block*/
quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize();
quint8* mergedSelectionBytes = 0;
try {
mergedSelectionBytes = new quint8[ totalBytes ];
} catch (std::bad_alloc) {
warnKrita << "KisPainter::bitBltWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes";
return;
}
d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight);
// Merge selections here by multiplying them - compositeOP(COMPOSITE_MULT)
d->paramInfo.dstRowStart = mergedSelectionBytes;
d->paramInfo.dstRowStride = srcWidth * selection->pixelSize();
d->paramInfo.srcRowStart = selRowStart;
d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize();
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo);
// Blit to dstBytes (intermediary bit array)
d->paramInfo.dstRowStart = dstBytes;
d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize();
d->paramInfo.srcRowStart = srcBytes;
d->paramInfo.srcRowStride = srcWidth * srcDev->pixelSize();
d->paramInfo.maskRowStart = mergedSelectionBytes;
d->paramInfo.maskRowStride = srcWidth * selection->pixelSize();
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
delete[] mergedSelectionBytes;
}
d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
delete[] dstBytes;
delete[] srcBytes;
addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight));
}
void KisPainter::bitBltWithFixedSelection(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
qint32 srcWidth, qint32 srcHeight)
{
bitBltWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight);
}
template <bool useOldSrcData>
void KisPainter::bitBltImpl(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight)
{
/* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just
initializing they perform some dummy passes with those parameters, and it must not crash */
if (srcWidth == 0 || srcHeight == 0) return;
if (srcDev.isNull()) return;
if (d->device.isNull()) return;
QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight);
if (d->compositeOp->id() == COMPOSITE_COPY) {
if(!d->selection && d->isOpacityUnit &&
srcX == dstX && srcY == dstY &&
d->device->fastBitBltPossible(srcDev)) {
if(useOldSrcData) {
d->device->fastBitBltOldData(srcDev, srcRect);
} else {
d->device->fastBitBlt(srcDev, srcRect);
}
addDirtyRect(srcRect);
return;
}
}
else {
/**
* An optimization, which crops the source rect by the bounds of
* the source device when it is possible
*/
if (d->tryReduceSourceRect(srcDev, &srcRect,
&srcX, &srcY,
&srcWidth, &srcHeight,
&dstX, &dstY)) return;
}
qint32 dstY_ = dstY;
qint32 srcY_ = srcY;
qint32 rowsRemaining = srcHeight;
// Read below
KisRandomConstAccessorSP srcIt = srcDev->createRandomConstAccessorNG(srcX, srcY);
KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(dstX, dstY);
/* Here be a huge block of verbose code that does roughly the same than
the other bit blit operations. This one is longer than the rest in an effort to
optimize speed and memory use */
if (d->selection) {
KisPaintDeviceSP selectionProjection(d->selection->projection());
KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(dstX, dstY);
while (rowsRemaining > 0) {
qint32 dstX_ = dstX;
qint32 srcX_ = srcX;
qint32 columnsRemaining = srcWidth;
qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_);
qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_);
qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY_);
qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows);
rows = qMin(rows, numContiguousSelRows);
rows = qMin(rows, rowsRemaining);
while (columnsRemaining > 0) {
qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_);
qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_);
qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX_);
qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns);
columns = qMin(columns, numContiguousSelColumns);
columns = qMin(columns, columnsRemaining);
qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_);
srcIt->moveTo(srcX_, srcY_);
qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_);
dstIt->moveTo(dstX_, dstY_);
qint32 maskRowStride = maskIt->rowStride(dstX_, dstY_);
maskIt->moveTo(dstX_, dstY_);
d->paramInfo.dstRowStart = dstIt->rawData();
d->paramInfo.dstRowStride = dstRowStride;
// if we don't use the oldRawData, we need to access the rawData of the source device.
d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast<KisRandomAccessor2*>(srcIt.data())->rawData();
d->paramInfo.srcRowStride = srcRowStride;
d->paramInfo.maskRowStart = static_cast<KisRandomAccessor2*>(maskIt.data())->rawData();
d->paramInfo.maskRowStride = maskRowStride;
d->paramInfo.rows = rows;
d->paramInfo.cols = columns;
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
srcX_ += columns;
dstX_ += columns;
columnsRemaining -= columns;
}
srcY_ += rows;
dstY_ += rows;
rowsRemaining -= rows;
}
}
else {
while (rowsRemaining > 0) {
qint32 dstX_ = dstX;
qint32 srcX_ = srcX;
qint32 columnsRemaining = srcWidth;
qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY_);
qint32 numContiguousSrcRows = srcIt->numContiguousRows(srcY_);
qint32 rows = qMin(numContiguousDstRows, numContiguousSrcRows);
rows = qMin(rows, rowsRemaining);
while (columnsRemaining > 0) {
qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX_);
qint32 numContiguousSrcColumns = srcIt->numContiguousColumns(srcX_);
qint32 columns = qMin(numContiguousDstColumns, numContiguousSrcColumns);
columns = qMin(columns, columnsRemaining);
qint32 srcRowStride = srcIt->rowStride(srcX_, srcY_);
srcIt->moveTo(srcX_, srcY_);
qint32 dstRowStride = dstIt->rowStride(dstX_, dstY_);
dstIt->moveTo(dstX_, dstY_);
d->paramInfo.dstRowStart = dstIt->rawData();
d->paramInfo.dstRowStride = dstRowStride;
// if we don't use the oldRawData, we need to access the rawData of the source device.
d->paramInfo.srcRowStart = useOldSrcData ? srcIt->oldRawData() : static_cast<KisRandomAccessor2*>(srcIt.data())->rawData();
d->paramInfo.srcRowStride = srcRowStride;
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = rows;
d->paramInfo.cols = columns;
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
srcX_ += columns;
dstX_ += columns;
columnsRemaining -= columns;
}
srcY_ += rows;
dstY_ += rows;
rowsRemaining -= rows;
}
}
addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight));
}
void KisPainter::bitBlt(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight)
{
bitBltImpl<false>(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight);
}
void KisPainter::bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect)
{
bitBlt(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height());
}
void KisPainter::bitBltOldData(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight)
{
bitBltImpl<true>(dstX, dstY, srcDev, srcX, srcY, srcWidth, srcHeight);
}
void KisPainter::bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect)
{
bitBltOldData(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height());
}
void KisPainter::fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color)
{
/* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just
* initializing they perform some dummy passes with those parameters, and it must not crash */
if(width == 0 || height == 0 || d->device.isNull())
return;
KoColor srcColor(color, d->device->compositionSourceColorSpace());
qint32 dstY = y;
qint32 rowsRemaining = height;
KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(x, y);
if(d->selection) {
KisPaintDeviceSP selectionProjection(d->selection->projection());
KisRandomConstAccessorSP maskIt = selectionProjection->createRandomConstAccessorNG(x, y);
while(rowsRemaining > 0) {
qint32 dstX = x;
qint32 columnsRemaining = width;
qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY);
qint32 numContiguousSelRows = maskIt->numContiguousRows(dstY);
qint32 rows = qMin(numContiguousDstRows, numContiguousSelRows);
rows = qMin(rows, rowsRemaining);
while (columnsRemaining > 0) {
qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX);
qint32 numContiguousSelColumns = maskIt->numContiguousColumns(dstX);
qint32 columns = qMin(numContiguousDstColumns, numContiguousSelColumns);
columns = qMin(columns, columnsRemaining);
qint32 dstRowStride = dstIt->rowStride(dstX, dstY);
dstIt->moveTo(dstX, dstY);
qint32 maskRowStride = maskIt->rowStride(dstX, dstY);
maskIt->moveTo(dstX, dstY);
d->paramInfo.dstRowStart = dstIt->rawData();
d->paramInfo.dstRowStride = dstRowStride;
d->paramInfo.srcRowStart = srcColor.data();
d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel
d->paramInfo.maskRowStart = maskIt->oldRawData();
d->paramInfo.maskRowStride = maskRowStride;
d->paramInfo.rows = rows;
d->paramInfo.cols = columns;
d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
dstX += columns;
columnsRemaining -= columns;
}
dstY += rows;
rowsRemaining -= rows;
}
}
else {
while(rowsRemaining > 0) {
qint32 dstX = x;
qint32 columnsRemaining = width;
qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY);
qint32 rows = qMin(numContiguousDstRows, rowsRemaining);
while(columnsRemaining > 0) {
qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX);
qint32 columns = qMin(numContiguousDstColumns, columnsRemaining);
qint32 dstRowStride = dstIt->rowStride(dstX, dstY);
dstIt->moveTo(dstX, dstY);
d->paramInfo.dstRowStart = dstIt->rawData();
d->paramInfo.dstRowStride = dstRowStride;
d->paramInfo.srcRowStart = srcColor.data();
d->paramInfo.srcRowStride = 0; // srcRowStride is set to zero to use the compositeOp with only a single color pixel
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = rows;
d->paramInfo.cols = columns;
d->colorSpace->bitBlt(srcColor.colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
dstX += columns;
columnsRemaining -= columns;
}
dstY += rows;
rowsRemaining -= rows;
}
}
addDirtyRect(QRect(x, y, width, height));
}
void KisPainter::bltFixed(qint32 dstX, qint32 dstY,
const KisFixedPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight)
{
/* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just
initializing they perform some dummy passes with those parameters, and it must not crash */
if (srcWidth == 0 || srcHeight == 0) return;
if (srcDev.isNull()) return;
if (d->device.isNull()) return;
QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight);
QRect srcBounds = srcDev->bounds();
/* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done,
so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */
Q_ASSERT(srcBounds.contains(srcRect));
Q_UNUSED(srcRect); // only used in above assertion
/* Create an intermediate byte array to hold information before it is written
to the current paint device (aka: d->device) */
quint8* dstBytes = 0;
try {
dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()];
} catch (std::bad_alloc) {
warnKrita << "KisPainter::bltFixed std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes";
return;
}
d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
const quint8 *srcRowStart = srcDev->data() +
(srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize();
d->paramInfo.dstRowStart = dstBytes;
d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize();
d->paramInfo.srcRowStart = srcRowStart;
d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize();
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
if (d->selection) {
/* d->selection is a KisPaintDevice, so first a readBytes is performed to
get the area of interest... */
KisPaintDeviceSP selectionProjection(d->selection->projection());
quint8* selBytes = 0;
try {
selBytes = new quint8[srcWidth * srcHeight * selectionProjection->pixelSize()];
}
catch (std::bad_alloc) {
delete[] dstBytes;
return;
}
selectionProjection->readBytes(selBytes, dstX, dstY, srcWidth, srcHeight);
d->paramInfo.maskRowStart = selBytes;
d->paramInfo.maskRowStride = srcWidth * selectionProjection->pixelSize();
}
// ...and then blit.
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
delete[] d->paramInfo.maskRowStart;
delete[] dstBytes;
addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight));
}
void KisPainter::bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect)
{
bltFixed(pos.x(), pos.y(), srcDev, srcRect.x(), srcRect.y(), srcRect.width(), srcRect.height());
}
void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY,
const KisFixedPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
qint32 selX, qint32 selY,
qint32 srcX, qint32 srcY,
quint32 srcWidth, quint32 srcHeight)
{
// TODO: get selX and selY working as intended
/* This check for nonsense ought to be a Q_ASSERT. However, when paintops are just
initializing they perform some dummy passes with those parameters, and it must not crash */
if (srcWidth == 0 || srcHeight == 0) return;
if (srcDev.isNull()) return;
if (d->device.isNull()) return;
// Check that selection has an alpha colorspace, crash if false
Q_ASSERT(selection->colorSpace() == KoColorSpaceRegistry::instance()->alpha8());
QRect srcRect = QRect(srcX, srcY, srcWidth, srcHeight);
QRect selRect = QRect(selX, selY, srcWidth, srcHeight);
QRect srcBounds = srcDev->bounds();
QRect selBounds = selection->bounds();
/* Trying to read outside a KisFixedPaintDevice is inherently wrong and shouldn't be done,
so crash if someone attempts to do this. Don't resize as it would obfuscate the mistake. */
Q_ASSERT(srcBounds.contains(srcRect));
Q_UNUSED(srcRect); // only used in above assertion
Q_ASSERT(selBounds.contains(selRect));
Q_UNUSED(selRect); // only used in above assertion
/* Create an intermediate byte array to hold information before it is written
to the current paint device (aka: d->device) */
quint8* dstBytes = 0;
try {
dstBytes = new quint8[srcWidth * srcHeight * d->device->pixelSize()];
} catch (std::bad_alloc) {
warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << srcWidth << " * " << srcHeight << " * " << d->device->pixelSize() << "total bytes";
return;
}
d->device->readBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
const quint8 *srcRowStart = srcDev->data() +
(srcBounds.width() * (srcY - srcBounds.top()) + (srcX - srcBounds.left())) * srcDev->pixelSize();
const quint8 *selRowStart = selection->data() +
(selBounds.width() * (selY - selBounds.top()) + (selX - selBounds.left())) * selection->pixelSize();
if (!d->selection) {
/* As there's nothing selected, blit to dstBytes (intermediary bit array),
ignoring d->selection (the user selection)*/
d->paramInfo.dstRowStart = dstBytes;
d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize();
d->paramInfo.srcRowStart = srcRowStart;
d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize();
d->paramInfo.maskRowStart = selRowStart;
d->paramInfo.maskRowStride = selBounds.width() * selection->pixelSize();
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
}
else {
/* Read the user selection (d->selection) bytes into an array, ready
to merge in the next block*/
quint32 totalBytes = srcWidth * srcHeight * selection->pixelSize();
quint8 * mergedSelectionBytes = 0;
try {
mergedSelectionBytes = new quint8[ totalBytes ];
} catch (std::bad_alloc) {
warnKrita << "KisPainter::bltFixedWithFixedSelection std::bad_alloc for " << totalBytes << "total bytes";
delete[] dstBytes;
return;
}
d->selection->projection()->readBytes(mergedSelectionBytes, dstX, dstY, srcWidth, srcHeight);
// Merge selections here by multiplying them - compositeOp(COMPOSITE_MULT)
d->paramInfo.dstRowStart = mergedSelectionBytes;
d->paramInfo.dstRowStride = srcWidth * selection->pixelSize();
d->paramInfo.srcRowStart = selRowStart;
d->paramInfo.srcRowStride = selBounds.width() * selection->pixelSize();
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = srcHeight;
d->paramInfo.cols = srcWidth;
KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_MULT)->composite(d->paramInfo);
// Blit to dstBytes (intermediary bit array)
d->paramInfo.dstRowStart = dstBytes;
d->paramInfo.dstRowStride = srcWidth * d->device->pixelSize();
d->paramInfo.srcRowStart = srcRowStart;
d->paramInfo.srcRowStride = srcBounds.width() * srcDev->pixelSize();
d->paramInfo.maskRowStart = mergedSelectionBytes;
d->paramInfo.maskRowStride = srcWidth * selection->pixelSize();
d->colorSpace->bitBlt(srcDev->colorSpace(), d->paramInfo, d->compositeOp, d->renderingIntent, d->conversionFlags);
delete[] mergedSelectionBytes;
}
d->device->writeBytes(dstBytes, dstX, dstY, srcWidth, srcHeight);
delete[] dstBytes;
addDirtyRect(QRect(dstX, dstY, srcWidth, srcHeight));
}
void KisPainter::bltFixedWithFixedSelection(qint32 dstX, qint32 dstY,
const KisFixedPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
quint32 srcWidth, quint32 srcHeight)
{
bltFixedWithFixedSelection(dstX, dstY, srcDev, selection, 0, 0, 0, 0, srcWidth, srcHeight);
}
void KisPainter::paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
if (d->device && d->paintOp && d->paintOp->canPaint()) {
d->paintOp->paintLine(pi1, pi2, currentDistance);
}
}
void KisPainter::paintPolyline(const vQPointF &points,
int index, int numPoints)
{
if (index >= (int) points.count())
return;
if (numPoints < 0)
numPoints = points.count();
if (index + numPoints > (int) points.count())
numPoints = points.count() - index;
if (numPoints > 1) {
- KisDistanceInformation saveDist(points[0], 0.0,
+ KisDistanceInformation saveDist(points[0],
KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0));
for (int i = index; i < index + numPoints - 1; i++) {
paintLine(points [i], points [i + 1], &saveDist);
}
}
}
static void getBezierCurvePoints(const KisVector2D &pos1,
const KisVector2D &control1,
const KisVector2D &control2,
const KisVector2D &pos2,
vQPointF& points)
{
LineEquation line = LineEquation::Through(pos1, pos2);
qreal d1 = line.absDistance(control1);
qreal d2 = line.absDistance(control2);
if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) {
points.push_back(toQPointF(pos1));
} else {
// Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508
KisVector2D l2 = (pos1 + control1) / 2;
KisVector2D h = (control1 + control2) / 2;
KisVector2D l3 = (l2 + h) / 2;
KisVector2D r3 = (control2 + pos2) / 2;
KisVector2D r2 = (h + r3) / 2;
KisVector2D l4 = (l3 + r2) / 2;
getBezierCurvePoints(pos1, l2, l3, l4, points);
getBezierCurvePoints(l4, r2, r3, pos2, points);
}
}
void KisPainter::getBezierCurvePoints(const QPointF &pos1,
const QPointF &control1,
const QPointF &control2,
const QPointF &pos2,
vQPointF& points) const
{
::getBezierCurvePoints(toKisVector2D(pos1), toKisVector2D(control1), toKisVector2D(control2), toKisVector2D(pos2), points);
}
void KisPainter::paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
if (d->paintOp && d->paintOp->canPaint()) {
d->paintOp->paintBezierCurve(pi1, control1, control2, pi2, currentDistance);
}
}
void KisPainter::paintRect(const QRectF &rect)
{
QRectF normalizedRect = rect.normalized();
vQPointF points;
points.push_back(normalizedRect.topLeft());
points.push_back(normalizedRect.bottomLeft());
points.push_back(normalizedRect.bottomRight());
points.push_back(normalizedRect.topRight());
paintPolygon(points);
}
void KisPainter::paintRect(const qreal x,
const qreal y,
const qreal w,
const qreal h)
{
paintRect(QRectF(x, y, w, h));
}
void KisPainter::paintEllipse(const QRectF &rect)
{
QRectF r = rect.normalized(); // normalize before checking as negative width and height are empty too
if (r.isEmpty()) return;
// See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation.
// kappa = (4/3*(sqrt(2)-1))
const qreal kappa = 0.5522847498;
const qreal lx = (r.width() / 2) * kappa;
const qreal ly = (r.height() / 2) * kappa;
QPointF center = r.center();
QPointF p0(r.left(), center.y());
QPointF p1(r.left(), center.y() - ly);
QPointF p2(center.x() - lx, r.top());
QPointF p3(center.x(), r.top());
vQPointF points;
getBezierCurvePoints(p0, p1, p2, p3, points);
QPointF p4(center.x() + lx, r.top());
QPointF p5(r.right(), center.y() - ly);
QPointF p6(r.right(), center.y());
getBezierCurvePoints(p3, p4, p5, p6, points);
QPointF p7(r.right(), center.y() + ly);
QPointF p8(center.x() + lx, r.bottom());
QPointF p9(center.x(), r.bottom());
getBezierCurvePoints(p6, p7, p8, p9, points);
QPointF p10(center.x() - lx, r.bottom());
QPointF p11(r.left(), center.y() + ly);
getBezierCurvePoints(p9, p10, p11, p0, points);
paintPolygon(points);
}
void KisPainter::paintEllipse(const qreal x,
const qreal y,
const qreal w,
const qreal h)
{
paintEllipse(QRectF(x, y, w, h));
}
void KisPainter::paintAt(const KisPaintInformation& pi,
KisDistanceInformation *savedDist)
{
if (d->paintOp && d->paintOp->canPaint()) {
d->paintOp->paintAt(pi, savedDist);
}
}
void KisPainter::fillPolygon(const vQPointF& points, FillStyle fillStyle)
{
if (points.count() < 3) {
return;
}
if (fillStyle == FillStyleNone) {
return;
}
QPainterPath polygonPath;
polygonPath.moveTo(points.at(0));
for (int pointIndex = 1; pointIndex < points.count(); pointIndex++) {
polygonPath.lineTo(points.at(pointIndex));
}
polygonPath.closeSubpath();
d->fillStyle = fillStyle;
fillPainterPath(polygonPath);
}
void KisPainter::paintPolygon(const vQPointF& points)
{
if (d->fillStyle != FillStyleNone) {
fillPolygon(points, d->fillStyle);
}
if (d->strokeStyle != StrokeStyleNone) {
if (points.count() > 1) {
- KisDistanceInformation distance(points[0], 0.0,
+ KisDistanceInformation distance(points[0],
KisAlgebra2D::directionBetweenPoints(points[0], points[1], 0.0));
for (int i = 0; i < points.count() - 1; i++) {
paintLine(KisPaintInformation(points[i]), KisPaintInformation(points[i + 1]), &distance);
}
paintLine(points[points.count() - 1], points[0], &distance);
}
}
}
void KisPainter::paintPainterPath(const QPainterPath& path)
{
if (d->fillStyle != FillStyleNone) {
fillPainterPath(path);
}
if (d->strokeStyle == StrokeStyleNone) return;
QPointF lastPoint, nextPoint;
int elementCount = path.elementCount();
KisDistanceInformation saveDist;
for (int i = 0; i < elementCount; i++) {
QPainterPath::Element element = path.elementAt(i);
switch (element.type) {
case QPainterPath::MoveToElement:
lastPoint = QPointF(element.x, element.y);
break;
case QPainterPath::LineToElement:
nextPoint = QPointF(element.x, element.y);
paintLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint), &saveDist);
lastPoint = nextPoint;
break;
case QPainterPath::CurveToElement:
nextPoint = QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y);
paintBezierCurve(KisPaintInformation(lastPoint),
QPointF(path.elementAt(i).x, path.elementAt(i).y),
QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y),
KisPaintInformation(nextPoint), &saveDist);
lastPoint = nextPoint;
break;
default:
continue;
}
}
}
void KisPainter::fillPainterPath(const QPainterPath& path)
{
fillPainterPath(path, QRect());
}
void KisPainter::fillPainterPath(const QPainterPath& path, const QRect &requestedRect)
{
if (d->mirrorHorizontally || d->mirrorVertically) {
KisLodTransform lod(d->device);
QPointF effectiveAxesCenter = lod.map(d->axesCenter);
QTransform C1 = QTransform::fromTranslate(-effectiveAxesCenter.x(), -effectiveAxesCenter.y());
QTransform C2 = QTransform::fromTranslate(effectiveAxesCenter.x(), effectiveAxesCenter.y());
QTransform t;
QPainterPath newPath;
QRect newRect;
if (d->mirrorHorizontally) {
t = C1 * QTransform::fromScale(-1,1) * C2;
newPath = t.map(path);
newRect = t.mapRect(requestedRect);
d->fillPainterPathImpl(newPath, newRect);
}
if (d->mirrorVertically) {
t = C1 * QTransform::fromScale(1,-1) * C2;
newPath = t.map(path);
newRect = t.mapRect(requestedRect);
d->fillPainterPathImpl(newPath, newRect);
}
if (d->mirrorHorizontally && d->mirrorVertically) {
t = C1 * QTransform::fromScale(-1,-1) * C2;
newPath = t.map(path);
newRect = t.mapRect(requestedRect);
d->fillPainterPathImpl(newPath, newRect);
}
}
d->fillPainterPathImpl(path, requestedRect);
}
void KisPainter::Private::fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect)
{
if (fillStyle == FillStyleNone) {
return;
}
// Fill the polygon bounding rectangle with the required contents then we'll
// create a mask for the actual polygon coverage.
if (!fillPainter) {
polygon = device->createCompositionSourceDevice();
fillPainter = new KisFillPainter(polygon);
} else {
polygon->clear();
}
Q_CHECK_PTR(polygon);
QRectF boundingRect = path.boundingRect();
QRect fillRect = boundingRect.toAlignedRect();
// Expand the rectangle to allow for anti-aliasing.
fillRect.adjust(-1, -1, 1, 1);
if (requestedRect.isValid()) {
fillRect &= requestedRect;
}
switch (fillStyle) {
default:
// Fall through
case FillStyleGradient:
// Currently unsupported, fall through
case FillStyleStrokes:
// Currently unsupported, fall through
warnImage << "Unknown or unsupported fill style in fillPolygon\n";
case FillStyleForegroundColor:
fillPainter->fillRect(fillRect, q->paintColor(), OPACITY_OPAQUE_U8);
break;
case FillStyleBackgroundColor:
fillPainter->fillRect(fillRect, q->backgroundColor(), OPACITY_OPAQUE_U8);
break;
case FillStylePattern:
if (pattern) { // if the user hasn't got any patterns installed, we shouldn't crash...
fillPainter->fillRect(fillRect, pattern);
}
break;
case FillStyleGenerator:
if (generator) { // if the user hasn't got any generators, we shouldn't crash...
fillPainter->fillRect(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), q->generator());
}
break;
}
if (polygonMaskImage.isNull() || (maskPainter == 0)) {
polygonMaskImage = QImage(maskImageWidth, maskImageHeight, QImage::Format_ARGB32_Premultiplied);
maskPainter = new QPainter(&polygonMaskImage);
maskPainter->setRenderHint(QPainter::Antialiasing, q->antiAliasPolygonFill());
}
// Break the mask up into chunks so we don't have to allocate a potentially very large QImage.
const QColor black(Qt::black);
const QBrush brush(Qt::white);
for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += maskImageWidth) {
for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += maskImageHeight) {
polygonMaskImage.fill(black.rgb());
maskPainter->translate(-x, -y);
maskPainter->fillPath(path, brush);
maskPainter->translate(x, y);
qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, maskImageWidth);
qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, maskImageHeight);
KisHLineIteratorSP lineIt = polygon->createHLineIteratorNG(x, y, rectWidth);
quint8 tmp;
for (int row = y; row < y + rectHeight; row++) {
QRgb* line = reinterpret_cast<QRgb*>(polygonMaskImage.scanLine(row - y));
do {
tmp = qRed(line[lineIt->x() - x]);
polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1);
} while (lineIt->nextPixel());
lineIt->nextRow();
}
}
}
QRect bltRect = !requestedRect.isEmpty() ? requestedRect : fillRect;
q->bitBlt(bltRect.x(), bltRect.y(), polygon, bltRect.x(), bltRect.y(), bltRect.width(), bltRect.height());
}
void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen)
{
drawPainterPath(path, pen, QRect());
}
void KisPainter::drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect)
{
// we are drawing mask, it has to be white
// color of the path is given by paintColor()
Q_ASSERT(pen.color() == Qt::white);
if (!d->fillPainter) {
d->polygon = d->device->createCompositionSourceDevice();
d->fillPainter = new KisFillPainter(d->polygon);
} else {
d->polygon->clear();
}
Q_CHECK_PTR(d->polygon);
QRectF boundingRect = path.boundingRect();
QRect fillRect = boundingRect.toAlignedRect();
// take width of the pen into account
int penWidth = qRound(pen.widthF());
fillRect.adjust(-penWidth, -penWidth, penWidth, penWidth);
// Expand the rectangle to allow for anti-aliasing.
fillRect.adjust(-1, -1, 1, 1);
if (!requestedRect.isNull()) {
fillRect &= requestedRect;
}
d->fillPainter->fillRect(fillRect, paintColor(), OPACITY_OPAQUE_U8);
if (d->polygonMaskImage.isNull() || (d->maskPainter == 0)) {
d->polygonMaskImage = QImage(d->maskImageWidth, d->maskImageHeight, QImage::Format_ARGB32_Premultiplied);
d->maskPainter = new QPainter(&d->polygonMaskImage);
d->maskPainter->setRenderHint(QPainter::Antialiasing, antiAliasPolygonFill());
}
// Break the mask up into chunks so we don't have to allocate a potentially very large QImage.
const QColor black(Qt::black);
QPen oldPen = d->maskPainter->pen();
d->maskPainter->setPen(pen);
for (qint32 x = fillRect.x(); x < fillRect.x() + fillRect.width(); x += d->maskImageWidth) {
for (qint32 y = fillRect.y(); y < fillRect.y() + fillRect.height(); y += d->maskImageHeight) {
d->polygonMaskImage.fill(black.rgb());
d->maskPainter->translate(-x, -y);
d->maskPainter->drawPath(path);
d->maskPainter->translate(x, y);
qint32 rectWidth = qMin(fillRect.x() + fillRect.width() - x, d->maskImageWidth);
qint32 rectHeight = qMin(fillRect.y() + fillRect.height() - y, d->maskImageHeight);
KisHLineIteratorSP lineIt = d->polygon->createHLineIteratorNG(x, y, rectWidth);
quint8 tmp;
for (int row = y; row < y + rectHeight; row++) {
QRgb* line = reinterpret_cast<QRgb*>(d->polygonMaskImage.scanLine(row - y));
do {
tmp = qRed(line[lineIt->x() - x]);
d->polygon->colorSpace()->applyAlphaU8Mask(lineIt->rawData(), &tmp, 1);
} while (lineIt->nextPixel());
lineIt->nextRow();
}
}
}
d->maskPainter->setPen(oldPen);
QRect r = d->polygon->extent();
bitBlt(r.x(), r.y(), d->polygon, r.x(), r.y(), r.width(), r.height());
}
inline void KisPainter::compositeOnePixel(quint8 *dst, const KoColor &color)
{
d->paramInfo.dstRowStart = dst;
d->paramInfo.dstRowStride = 0;
d->paramInfo.srcRowStart = color.data();
d->paramInfo.srcRowStride = 0;
d->paramInfo.maskRowStart = 0;
d->paramInfo.maskRowStride = 0;
d->paramInfo.rows = 1;
d->paramInfo.cols = 1;
d->colorSpace->bitBlt(color.colorSpace(), d->paramInfo, d->compositeOp,
d->renderingIntent,
d->conversionFlags);
}
/**/
void KisPainter::drawLine(const QPointF& start, const QPointF& end, qreal width, bool antialias){
int x1 = start.x();
int y1 = start.y();
int x2 = end.x();
int y2 = end.y();
if ((x2 == x1 ) && (y2 == y1)) return;
int dstX = x2-x1;
int dstY = y2-y1;
qreal uniC = dstX*y1 - dstY*x1;
qreal projectionDenominator = 1.0 / (pow((double)dstX, 2) + pow((double)dstY, 2));
qreal subPixel;
if (qAbs(dstX) > qAbs(dstY)){
subPixel = start.x() - x1;
}else{
subPixel = start.y() - y1;
}
qreal halfWidth = width * 0.5 + subPixel;
int W_ = qRound(halfWidth) + 1;
// save the state
int X1_ = x1;
int Y1_ = y1;
int X2_ = x2;
int Y2_ = y2;
if (x2<x1) std::swap(x1,x2);
if (y2<y1) std::swap(y1,y2);
qreal denominator = sqrt(pow((double)dstY,2) + pow((double)dstX,2));
if (denominator == 0.0) {
denominator = 1.0;
}
denominator = 1.0/denominator;
qreal projection,scanX,scanY,AA_;
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1);
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1);
}
for (int y = y1-W_; y < y2+W_ ; y++){
for (int x = x1-W_; x < x2+W_; x++){
projection = ( (x-X1_)* dstX + (y-Y1_)*dstY ) * projectionDenominator;
scanX = X1_ + projection * dstX;
scanY = Y1_ + projection * dstY;
if (((scanX < x1) || (scanX > x2)) || ((scanY < y1) || (scanY > y2))) {
AA_ = qMin( sqrt( pow((double)x - X1_, 2) + pow((double)y - Y1_, 2) ),
sqrt( pow((double)x - X2_, 2) + pow((double)y - Y2_, 2) ));
}else{
AA_ = qAbs(dstY*x - dstX*y + uniC) * denominator;
}
if (AA_>halfWidth) {
continue;
}
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x,y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
KoColor mycolor = d->paintColor;
if (antialias && AA_ > halfWidth-1.0) {
mycolor.colorSpace()->multiplyAlpha(mycolor.data(), 1.0 - (AA_-(halfWidth-1.0)), 1);
}
compositeOnePixel(accessor->rawData(), mycolor);
}
}
}
}
/**/
void KisPainter::drawLine(const QPointF & start, const QPointF & end)
{
drawThickLine(start, end, 1, 1);
}
void KisPainter::drawDDALine(const QPointF & start, const QPointF & end)
{
int x = int(start.x());
int y = int(start.y());
int x2 = int(end.x());
int y2 = int(end.y());
// Width and height of the line
int xd = x2 - x;
int yd = y2 - y;
float m = (float)yd / (float)xd;
float fx = x;
float fy = y;
int inc;
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x, y);
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x, y);
}
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x,y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), d->paintColor);
}
if (fabs(m) > 1.0f) {
inc = (yd > 0) ? 1 : -1;
m = 1.0f / m;
m *= inc;
while (y != y2) {
y = y + inc;
fx = fx + m;
x = qRound(fx);
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), d->paintColor);
}
}
} else {
inc = (xd > 0) ? 1 : -1;
m *= inc;
while (x != x2) {
x = x + inc;
fy = fy + m;
y = qRound(fy);
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), d->paintColor);
}
}
}
}
void KisPainter::drawWobblyLine(const QPointF & start, const QPointF & end)
{
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y());
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y());
}
KoColor mycolor(d->paintColor);
int x1 = start.x();
int y1 = start.y();
int x2 = end.x();
int y2 = end.y();
// Width and height of the line
int xd = (x2 - x1);
int yd = (y2 - y1);
int x;
int y;
float fx = (x = x1);
float fy = (y = y1);
float m = (float)yd / (float)xd;
int inc;
if (fabs(m) > 1) {
inc = (yd > 0) ? 1 : -1;
m = 1.0f / m;
m *= inc;
while (y != y2) {
fx = fx + m;
y = y + inc;
x = qRound(fx);
float br1 = int(fx + 1) - fx;
float br2 = fx - (int)fx;
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
mycolor.setOpacity((quint8)(255*br1));
compositeOnePixel(accessor->rawData(), mycolor);
}
accessor->moveTo(x + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(x + 1, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
mycolor.setOpacity((quint8)(255*br2));
compositeOnePixel(accessor->rawData(), mycolor);
}
}
} else {
inc = (xd > 0) ? 1 : -1;
m *= inc;
while (x != x2) {
fy = fy + m;
x = x + inc;
y = qRound(fy);
float br1 = int(fy + 1) - fy;
float br2 = fy - (int)fy;
accessor->moveTo(x, y);
if (selectionAccessor) selectionAccessor->moveTo(x, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
mycolor.setOpacity((quint8)(255*br1));
compositeOnePixel(accessor->rawData(), mycolor);
}
accessor->moveTo(x, y + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, y + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
mycolor.setOpacity((quint8)(255*br2));
compositeOnePixel(accessor->rawData(), mycolor);
}
}
}
}
void KisPainter::drawWuLine(const QPointF & start, const QPointF & end)
{
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y());
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y());
}
KoColor lineColor(d->paintColor);
int x1 = start.x();
int y1 = start.y();
int x2 = end.x();
int y2 = end.y();
float grad, xd, yd;
float xgap, ygap, xend, yend, yf, xf;
float brightness1, brightness2;
int ix1, ix2, iy1, iy2;
quint8 c1, c2;
// gradient of line
xd = (x2 - x1);
yd = (y2 - y1);
if (yd == 0) {
/* Horizontal line */
int incr = (x1 < x2) ? 1 : -1;
ix1 = (int)x1;
ix2 = (int)x2;
iy1 = (int)y1;
while (ix1 != ix2) {
ix1 = ix1 + incr;
accessor->moveTo(ix1, iy1);
if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), lineColor);
}
}
return;
}
if (xd == 0) {
/* Vertical line */
int incr = (y1 < y2) ? 1 : -1;
iy1 = (int)y1;
iy2 = (int)y2;
ix1 = (int)x1;
while (iy1 != iy2) {
iy1 = iy1 + incr;
accessor->moveTo(ix1, iy1);
if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), lineColor);
}
}
return;
}
if (fabs(xd) > fabs(yd)) {
// horizontal line
// line have to be paint from left to right
if (x1 > x2) {
float tmp;
tmp = x1; x1 = x2; x2 = tmp;
tmp = y1; y1 = y2; y2 = tmp;
xd = (x2 - x1);
yd = (y2 - y1);
}
grad = yd / xd;
// nearest X,Y interger coordinates
xend = static_cast<int>(x1 + 0.5f);
yend = y1 + grad * (xend - x1);
xgap = invertFrac(x1 + 0.5f);
ix1 = static_cast<int>(xend);
iy1 = static_cast<int>(yend);
// calc the intensity of the other end point pixel pair.
brightness1 = invertFrac(yend) * xgap;
brightness2 = frac(yend) * xgap;
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(ix1, iy1);
if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(ix1, iy1 + 1);
if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1 + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
// calc first Y-intersection for main loop
yf = yend + grad;
xend = trunc(x2 + 0.5f);
yend = y2 + grad * (xend - x2);
xgap = invertFrac(x2 - 0.5f);
ix2 = static_cast<int>(xend);
iy2 = static_cast<int>(yend);
brightness1 = invertFrac(yend) * xgap;
brightness2 = frac(yend) * xgap;
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(ix2, iy2);
if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(ix2, iy2 + 1);
if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2 + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
// main loop
for (int x = ix1 + 1; x <= ix2 - 1; x++) {
brightness1 = invertFrac(yf);
brightness2 = frac(yf);
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(x, int (yf));
if (selectionAccessor) selectionAccessor->moveTo(x, int (yf));
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(x, int (yf) + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, int (yf) + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
yf = yf + grad;
}
} else {
//vertical
// line have to be painted from left to right
if (y1 > y2) {
float tmp;
tmp = x1; x1 = x2; x2 = tmp;
tmp = y1; y1 = y2; y2 = tmp;
xd = (x2 - x1);
yd = (y2 - y1);
}
grad = xd / yd;
// nearest X,Y interger coordinates
yend = static_cast<int>(y1 + 0.5f);
xend = x1 + grad * (yend - y1);
ygap = invertFrac(y1 + 0.5f);
ix1 = static_cast<int>(xend);
iy1 = static_cast<int>(yend);
// calc the intensity of the other end point pixel pair.
brightness1 = invertFrac(xend) * ygap;
brightness2 = frac(xend) * ygap;
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(ix1, iy1);
if (selectionAccessor) selectionAccessor->moveTo(ix1, iy1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(x1 + 1, y1);
if (selectionAccessor) selectionAccessor->moveTo(x1 + 1, y1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
// calc first Y-intersection for main loop
xf = xend + grad;
yend = trunc(y2 + 0.5f);
xend = x2 + grad * (yend - y2);
ygap = invertFrac(y2 - 0.5f);
ix2 = static_cast<int>(xend);
iy2 = static_cast<int>(yend);
brightness1 = invertFrac(xend) * ygap;
brightness2 = frac(xend) * ygap;
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(ix2, iy2);
if (selectionAccessor) selectionAccessor->moveTo(ix2, iy2);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(ix2 + 1, iy2);
if (selectionAccessor) selectionAccessor->moveTo(ix2 + 1, iy2);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
// main loop
for (int y = iy1 + 1; y <= iy2 - 1; y++) {
brightness1 = invertFrac(xf);
brightness2 = frac(xf);
c1 = (int)(brightness1 * OPACITY_OPAQUE_U8);
c2 = (int)(brightness2 * OPACITY_OPAQUE_U8);
accessor->moveTo(int (xf), y);
if (selectionAccessor) selectionAccessor->moveTo(int (xf), y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(int (xf) + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(int (xf) + 1, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c2);
compositeOnePixel(accessor->rawData(), lineColor);
}
xf = xf + grad;
}
}//end-of-else
}
void KisPainter::drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth)
{
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(start.x(), start.y());
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(start.x(), start.y());
}
const KoColorSpace *cs = d->device->colorSpace();
KoColor c1(d->paintColor);
KoColor c2(d->paintColor);
KoColor c3(d->paintColor);
KoColor col1(c1);
KoColor col2(c1);
float grada, gradb, dxa, dxb, dya, dyb, fraca, fracb,
xfa, yfa, xfb, yfb, b1a, b2a, b1b, b2b, dstX, dstY;
int x, y, ix1, ix2, iy1, iy2;
int x0a, y0a, x1a, y1a, x0b, y0b, x1b, y1b;
int tp0, tn0, tp1, tn1;
int horizontal = 0;
float opacity = 1.0;
tp0 = startWidth / 2;
tn0 = startWidth / 2;
if (startWidth % 2 == 0) // even width startWidth
tn0--;
tp1 = endWidth / 2;
tn1 = endWidth / 2;
if (endWidth % 2 == 0) // even width endWidth
tn1--;
int x0 = qRound(start.x());
int y0 = qRound(start.y());
int x1 = qRound(end.x());
int y1 = qRound(end.y());
dstX = x1 - x0; // run of general line
dstY = y1 - y0; // rise of general line
if (dstY < 0) dstY = -dstY;
if (dstX < 0) dstX = -dstX;
if (dstX > dstY) { // horizontalish
horizontal = 1;
x0a = x0; y0a = y0 - tn0;
x0b = x0; y0b = y0 + tp0;
x1a = x1; y1a = y1 - tn1;
x1b = x1; y1b = y1 + tp1;
} else {
x0a = x0 - tn0; y0a = y0;
x0b = x0 + tp0; y0b = y0;
x1a = x1 - tn1; y1a = y1;
x1b = x1 + tp1; y1b = y1;
}
if (horizontal) { // draw endpoints
for (int i = y0a; i <= y0b; i++) {
accessor->moveTo(x0, i);
if (selectionAccessor) selectionAccessor->moveTo(x0, i);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c1);
}
}
for (int i = y1a; i <= y1b; i++) {
accessor->moveTo(x1, i);
if (selectionAccessor) selectionAccessor->moveTo(x1, i);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c1);
}
}
} else {
for (int i = x0a; i <= x0b; i++) {
accessor->moveTo(i, y0);
if (selectionAccessor) selectionAccessor->moveTo(i, y0);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c1);
}
}
for (int i = x1a; i <= x1b; i++) {
accessor->moveTo(i, y1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c1);
}
}
}
//antialias endpoints
if (x1 != x0 && y1 != y0) {
if (horizontal) {
accessor->moveTo(x0a, y0a - 1);
if (selectionAccessor) selectionAccessor->moveTo(x0a, y0a - 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = .25 * c1.opacityF() + (1 - .25) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
accessor->moveTo(x1b, y1b + 1);
if (selectionAccessor) selectionAccessor->moveTo(x1b, y1b + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = .25 * c2.opacityF() + (1 - .25) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
} else {
accessor->moveTo(x0a - 1, y0a);
if (selectionAccessor) selectionAccessor->moveTo(x0a - 1, y0a);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = .25 * c1.opacityF() + (1 - .25) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
accessor->moveTo(x1b + 1, y1b);
if (selectionAccessor) selectionAccessor->moveTo(x1b + 1, y1b);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = .25 * c2.opacityF() + (1 - .25) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
}
}
dxa = x1a - x0a; // run of a
dya = y1a - y0a; // rise of a
dxb = x1b - x0b; // run of b
dyb = y1b - y0b; // rise of b
if (horizontal) { // horizontal-ish lines
if (x1 < x0) {
int xt, yt, wt;
KoColor tmp;
xt = x1a; x1a = x0a; x0a = xt;
yt = y1a; y1a = y0a; y0a = yt;
xt = x1b; x1b = x0b; x0b = xt;
yt = y1b; y1b = y0b; y0b = yt;
xt = x1; x1 = x0; x0 = xt;
yt = y1; y1 = y0; y0 = yt;
tmp = c1; c1 = c2; c2 = tmp;
wt = startWidth; startWidth = endWidth; endWidth = wt;
}
grada = dya / dxa;
gradb = dyb / dxb;
ix1 = x0; iy1 = y0;
ix2 = x1; iy2 = y1;
yfa = y0a + grada;
yfb = y0b + gradb;
for (x = ix1 + 1; x <= ix2 - 1; x++) {
fraca = yfa - int (yfa);
b1a = 1 - fraca;
b2a = fraca;
fracb = yfb - int (yfb);
b1b = 1 - fracb;
b2b = fracb;
// color first pixel of bottom line
opacity = ((x - ix1) / dstX) * c2.opacityF() + (1 - (x - ix1) / dstX) * c1.opacityF();
c3.setOpacity(opacity);
accessor->moveTo(x, (int)yfa);
if (selectionAccessor) selectionAccessor->moveTo(x, (int)yfa);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b1a * c3.opacityF() + (1 - b1a) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
// color first pixel of top line
if (!(startWidth == 1 && endWidth == 1)) {
accessor->moveTo(x, (int)yfb);
if (selectionAccessor) selectionAccessor->moveTo(x, (int)yfb);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b1b * c3.opacityF() + (1 - b1b) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
}
// color second pixel of bottom line
if (grada != 0 && grada != 1) { // if not flat or exact diagonal
accessor->moveTo(x, int (yfa) + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, int (yfa) + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b2a * c3.opacityF() + (1 - b2a) * alpha;
col2.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col2);
}
}
// color second pixel of top line
if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) {
accessor->moveTo(x, int (yfb) + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, int (yfb) + 1);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b2b * c3.opacityF() + (1 - b2b) * alpha;
col2.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col2);
}
}
// fill remaining pixels
if (!(startWidth == 1 && endWidth == 1)) {
if (yfa < yfb)
for (int i = yfa + 1; i <= yfb; i++) {
accessor->moveTo(x, i);
if (selectionAccessor) selectionAccessor->moveTo(x, i);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c3);
}
}
else
for (int i = yfa + 1; i >= yfb; i--) {
accessor->moveTo(x, i);
if (selectionAccessor) selectionAccessor->moveTo(x, i);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c3);
}
}
}
yfa += grada;
yfb += gradb;
}
} else { // vertical-ish lines
if (y1 < y0) {
int xt, yt, wt;
xt = x1a; x1a = x0a; x0a = xt;
yt = y1a; y1a = y0a; y0a = yt;
xt = x1b; x1b = x0b; x0b = xt;
yt = y1b; y1b = y0b; y0b = yt;
xt = x1; x1 = x0; x0 = xt;
yt = y1; y1 = y0; y0 = yt;
KoColor tmp;
tmp = c1; c1 = c2; c2 = tmp;
wt = startWidth; startWidth = endWidth; endWidth = wt;
}
grada = dxa / dya;
gradb = dxb / dyb;
ix1 = x0; iy1 = y0;
ix2 = x1; iy2 = y1;
xfa = x0a + grada;
xfb = x0b + gradb;
for (y = iy1 + 1; y <= iy2 - 1; y++) {
fraca = xfa - int (xfa);
b1a = 1 - fraca;
b2a = fraca;
fracb = xfb - int (xfb);
b1b = 1 - fracb;
b2b = fracb;
// color first pixel of left line
opacity = ((y - iy1) / dstY) * c2.opacityF() + (1 - (y - iy1) / dstY) * c1.opacityF();
c3.setOpacity(opacity);
accessor->moveTo(int (xfa), y);
if (selectionAccessor) selectionAccessor->moveTo(int (xfa), y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b1a * c3.opacityF() + (1 - b1a) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
// color first pixel of right line
if (!(startWidth == 1 && endWidth == 1)) {
accessor->moveTo(int(xfb), y);
if (selectionAccessor) selectionAccessor->moveTo(int(xfb), y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b1b * c3.opacityF() + (1 - b1b) * alpha;
col1.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col1);
}
}
// color second pixel of left line
if (grada != 0 && grada != 1) { // if not flat or exact diagonal
accessor->moveTo(int(xfa) + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(int(xfa) + 1, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b2a * c3.opacityF() + (1 - b2a) * alpha;
col2.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col2);
}
}
// color second pixel of right line
if (gradb != 0 && gradb != 1 && !(startWidth == 1 && endWidth == 1)) {
accessor->moveTo(int(xfb) + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(int(xfb) + 1, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
qreal alpha = cs->opacityF(accessor->rawData());
opacity = b2b * c3.opacityF() + (1 - b2b) * alpha;
col2.setOpacity(opacity);
compositeOnePixel(accessor->rawData(), col2);
}
}
// fill remaining pixels between current xfa,xfb
if (!(startWidth == 1 && endWidth == 1)) {
if (xfa < xfb)
for (int i = (int) xfa + 1; i <= (int) xfb; i++) {
accessor->moveTo(i, y);
if (selectionAccessor) selectionAccessor->moveTo(i, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c3);
}
}
else
for (int i = (int) xfb; i <= (int) xfa + 1; i++) {
accessor->moveTo(i, y);
if (selectionAccessor) selectionAccessor->moveTo(i, y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
compositeOnePixel(accessor->rawData(), c3);
}
}
}
xfa += grada;
xfb += gradb;
}
}
}
void KisPainter::setProgress(KoUpdater * progressUpdater)
{
d->progressUpdater = progressUpdater;
}
const KisPaintDeviceSP KisPainter::device() const
{
return d->device;
}
KisPaintDeviceSP KisPainter::device()
{
return d->device;
}
void KisPainter::setChannelFlags(QBitArray channelFlags)
{
Q_ASSERT(channelFlags.isEmpty() || quint32(channelFlags.size()) == d->colorSpace->channelCount());
// Now, if all bits in the channelflags are true, pass an empty channel flags bitarray
// because otherwise the compositeops cannot optimize.
d->paramInfo.channelFlags = channelFlags;
if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) {
d->paramInfo.channelFlags = QBitArray();
}
}
QBitArray KisPainter::channelFlags()
{
return d->paramInfo.channelFlags;
}
void KisPainter::setPattern(const KoPattern * pattern)
{
d->pattern = pattern;
}
const KoPattern * KisPainter::pattern() const
{
return d->pattern;
}
void KisPainter::setPaintColor(const KoColor& color)
{
d->paintColor = color;
if (d->device) {
d->paintColor.convertTo(d->device->compositionSourceColorSpace());
}
}
const KoColor &KisPainter::paintColor() const
{
return d->paintColor;
}
void KisPainter::setBackgroundColor(const KoColor& color)
{
d->backgroundColor = color;
if (d->device) {
d->backgroundColor.convertTo(d->device->compositionSourceColorSpace());
}
}
const KoColor &KisPainter::backgroundColor() const
{
return d->backgroundColor;
}
void KisPainter::setGenerator(KisFilterConfigurationSP generator)
{
d->generator = generator;
}
const KisFilterConfigurationSP KisPainter::generator() const
{
return d->generator;
}
void KisPainter::setFillStyle(FillStyle fillStyle)
{
d->fillStyle = fillStyle;
}
KisPainter::FillStyle KisPainter::fillStyle() const
{
return d->fillStyle;
}
void KisPainter::setAntiAliasPolygonFill(bool antiAliasPolygonFill)
{
d->antiAliasPolygonFill = antiAliasPolygonFill;
}
bool KisPainter::antiAliasPolygonFill()
{
return d->antiAliasPolygonFill;
}
void KisPainter::setStrokeStyle(KisPainter::StrokeStyle strokeStyle)
{
d->strokeStyle = strokeStyle;
}
KisPainter::StrokeStyle KisPainter::strokeStyle() const
{
return d->strokeStyle;
}
void KisPainter::setFlow(quint8 flow)
{
d->paramInfo.flow = float(flow) / 255.0f;
}
quint8 KisPainter::flow() const
{
return quint8(d->paramInfo.flow * 255.0f);
}
void KisPainter::setOpacityUpdateAverage(quint8 opacity)
{
d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8;
d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f);
}
+void KisPainter::setAverageOpacity(qreal averageOpacity)
+{
+ d->paramInfo.setOpacityAndAverage(d->paramInfo.opacity, averageOpacity);
+}
+
+qreal KisPainter::blendAverageOpacity(qreal opacity, qreal averageOpacity)
+{
+ const float exponent = 0.1;
+
+ return averageOpacity < opacity ?
+ opacity :
+ exponent * opacity + (1.0 - exponent) * (averageOpacity);
+}
+
void KisPainter::setOpacity(quint8 opacity)
{
d->isOpacityUnit = opacity == OPACITY_OPAQUE_U8;
d->paramInfo.opacity = float(opacity) / 255.0f;
}
quint8 KisPainter::opacity() const
{
return quint8(d->paramInfo.opacity * 255.0f);
}
void KisPainter::setCompositeOp(const KoCompositeOp * op)
{
d->compositeOp = op;
}
const KoCompositeOp * KisPainter::compositeOp()
{
return d->compositeOp;
}
/**
* TODO: Rename this setCompositeOpId(). See KoCompositeOpRegistry.h
*/
void KisPainter::setCompositeOp(const QString& op)
{
d->compositeOp = d->colorSpace->compositeOp(op);
}
void KisPainter::setSelection(KisSelectionSP selection)
{
d->selection = selection;
}
KisSelectionSP KisPainter::selection()
{
return d->selection;
}
KoUpdater * KisPainter::progressUpdater()
{
return d->progressUpdater;
}
void KisPainter::setGradient(const KoAbstractGradient* gradient)
{
d->gradient = gradient;
}
const KoAbstractGradient* KisPainter::gradient() const
{
return d->gradient;
}
void KisPainter::setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image)
{
d->paintOpPreset = preset;
KisPaintOp *paintop = KisPaintOpRegistry::instance()->paintOp(preset, this, node, image);
Q_ASSERT(paintop);
if (paintop) {
delete d->paintOp;
d->paintOp = paintop;
}
else {
warnKrita << "Could not create paintop for preset " << preset->name();
}
}
KisPaintOpPresetSP KisPainter::preset() const
{
return d->paintOpPreset;
}
KisPaintOp* KisPainter::paintOp() const
{
return d->paintOp;
}
void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHorizontally, bool mirrorVertically)
{
d->axesCenter = axesCenter;
d->mirrorHorizontally = mirrorHorizontally;
d->mirrorVertically = mirrorVertically;
}
-void KisPainter::copyMirrorInformation(KisPainter* painter)
+bool KisPainter::hasMirroring() const
{
- painter->setMirrorInformation(d->axesCenter, d->mirrorHorizontally, d->mirrorVertically);
+ return d->mirrorHorizontally || d->mirrorVertically;
}
-bool KisPainter::hasMirroring() const
+bool KisPainter::hasHorizontalMirroring() const
{
- return d->mirrorHorizontally || d->mirrorVertically;
+ return d->mirrorHorizontally;
+}
+
+bool KisPainter::hasVerticalMirroring() const
+{
+ return d->mirrorVertically;
}
void KisPainter::setMaskImageSize(qint32 width, qint32 height)
{
d->maskImageWidth = qBound(1, width, 256);
d->maskImageHeight = qBound(1, height, 256);
d->fillPainter = 0;
d->polygonMaskImage = QImage();
}
//void KisPainter::setLockAlpha(bool protect)
//{
// if(d->paramInfo.channelFlags.isEmpty()) {
// d->paramInfo.channelFlags = d->colorSpace->channelFlags(true, true);
// }
// QBitArray switcher =
// d->colorSpace->channelFlags(protect, !protect);
// if(protect) {
// d->paramInfo.channelFlags &= switcher;
// }
// else {
// d->paramInfo.channelFlags |= switcher;
// }
// Q_ASSERT(quint32(d->paramInfo.channelFlags.size()) == d->colorSpace->channelCount());
//}
//bool KisPainter::alphaLocked() const
//{
// QBitArray switcher = d->colorSpace->channelFlags(false, true);
// return !(d->paramInfo.channelFlags & switcher).count(true);
//}
void KisPainter::setRenderingIntent(KoColorConversionTransformation::Intent intent)
{
d->renderingIntent = intent;
}
void KisPainter::setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags)
{
d->conversionFlags = conversionFlags;
}
+void KisPainter::setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface)
+{
+ d->runnableStrokeJobsInterface = interface;
+}
+
+KisRunnableStrokeJobsInterface *KisPainter::runnableStrokeJobsInterface() const
+{
+ if (!d->runnableStrokeJobsInterface) {
+ if (!d->fakeRunnableStrokeJobsInterface) {
+ d->fakeRunnableStrokeJobsInterface.reset(new KisFakeRunnableStrokeJobsExecutor());
+ }
+ return d->fakeRunnableStrokeJobsInterface.data();
+ }
+
+ return d->runnableStrokeJobsInterface;
+}
+
void KisPainter::renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab)
{
if (!d->mirrorHorizontally && !d->mirrorVertically) return;
KisFixedPaintDeviceSP dabToProcess = dab;
if (preserveDab) {
dabToProcess = new KisFixedPaintDevice(*dab);
}
renderMirrorMask(rc, dabToProcess);
}
void KisPainter::renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask)
{
if (!d->mirrorHorizontally && !d->mirrorVertically) return;
KisFixedPaintDeviceSP maskToProcess = mask;
if (preserveMask) {
maskToProcess = new KisFixedPaintDevice(*mask);
}
renderMirrorMask(rc, dab, sx, sy, maskToProcess);
}
void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab)
{
int x = rc.topLeft().x();
int y = rc.topLeft().y();
KisLodTransform t(d->device);
- QPointF effectiveAxesCenter = t.map(d->axesCenter);
+ QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x();
int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y();
if (d->mirrorHorizontally && d->mirrorVertically){
dab->mirror(true, false);
bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height());
dab->mirror(false,true);
bltFixed(mirrorX, mirrorY, dab, 0,0,rc.width(),rc.height());
dab->mirror(true, false);
bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height());
}
else if (d->mirrorHorizontally){
dab->mirror(true, false);
bltFixed(mirrorX, y, dab, 0,0,rc.width(),rc.height());
}
else if (d->mirrorVertically){
dab->mirror(false, true);
bltFixed(x, mirrorY, dab, 0,0,rc.width(),rc.height());
}
}
void KisPainter::renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask)
{
int x = rc.topLeft().x();
int y = rc.topLeft().y();
KisLodTransform t(d->device);
- QPointF effectiveAxesCenter = t.map(d->axesCenter);
+ QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x();
int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y();
if (d->mirrorHorizontally && d->mirrorVertically){
dab->mirror(true, false);
mask->mirror(true, false);
bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() );
dab->mirror(false,true);
mask->mirror(false, true);
bltFixedWithFixedSelection(mirrorX,mirrorY, dab, mask, rc.width() ,rc.height() );
dab->mirror(true, false);
mask->mirror(true, false);
bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() );
}else if (d->mirrorHorizontally){
dab->mirror(true, false);
mask->mirror(true, false);
bltFixedWithFixedSelection(mirrorX,y, dab, mask, rc.width() ,rc.height() );
}else if (d->mirrorVertically){
dab->mirror(false, true);
mask->mirror(false, true);
bltFixedWithFixedSelection(x,mirrorY, dab, mask, rc.width() ,rc.height() );
}
}
void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab){
if (d->mirrorHorizontally || d->mirrorVertically){
KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace()));
QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) );
mirrorDab->setRect(dabRc);
- mirrorDab->initialize();
+ mirrorDab->lazyGrowBufferWithoutInitialization();
dab->readBytes(mirrorDab->data(),rc);
renderMirrorMask( QRect(rc.topLeft(),dabRc.size()), mirrorDab);
}
}
void KisPainter::renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask)
{
if (d->mirrorHorizontally || d->mirrorVertically){
KisFixedPaintDeviceSP mirrorDab(new KisFixedPaintDevice(dab->colorSpace()));
QRect dabRc( QPoint(0,0), QSize(rc.width(),rc.height()) );
mirrorDab->setRect(dabRc);
- mirrorDab->initialize();
+ mirrorDab->lazyGrowBufferWithoutInitialization();
dab->readBytes(mirrorDab->data(),QRect(QPoint(sx,sy),rc.size()));
renderMirrorMask(rc, mirrorDab, mask);
}
}
void KisPainter::renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab)
{
QVector<QRect> rects;
int x = rc.topLeft().x();
int y = rc.topLeft().y();
KisLodTransform t(d->device);
- QPointF effectiveAxesCenter = t.map(d->axesCenter);
+ QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
int mirrorX = -((x+rc.width()) - effectiveAxesCenter.x()) + effectiveAxesCenter.x();
int mirrorY = -((y+rc.height()) - effectiveAxesCenter.y()) + effectiveAxesCenter.y();
rects << rc;
if (d->mirrorHorizontally && d->mirrorVertically){
rects << QRect(mirrorX, y, rc.width(), rc.height());
rects << QRect(mirrorX, mirrorY, rc.width(), rc.height());
rects << QRect(x, mirrorY, rc.width(), rc.height());
} else if (d->mirrorHorizontally) {
rects << QRect(mirrorX, y, rc.width(), rc.height());
} else if (d->mirrorVertically) {
rects << QRect(x, mirrorY, rc.width(), rc.height());
}
Q_FOREACH (const QRect &rc, rects) {
d->device->clear(rc);
}
QRect resultRect = dab->extent() | rc;
bool intersects = false;
for (int i = 1; i < rects.size(); i++) {
if (rects[i].intersects(resultRect)) {
intersects = true;
break;
}
}
/**
* If there are no cross-intersections, we can use a fast path
* and do no cycling recompositioning
*/
if (!intersects) {
rects.resize(1);
}
Q_FOREACH (const QRect &rc, rects) {
bitBlt(rc.topLeft(), dab, rc);
}
Q_FOREACH (const QRect &rc, rects) {
renderMirrorMask(rc, dab);
}
}
+void KisPainter::mirrorRect(Qt::Orientation direction, QRect *rc) const
+{
+ KisLodTransform t(d->device);
+ QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
+
+ KritaUtils::mirrorRect(direction, effectiveAxesCenter, rc);
+}
+
+void KisPainter::mirrorDab(Qt::Orientation direction, KisRenderedDab *dab) const
+{
+ KisLodTransform t(d->device);
+ QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
+
+ KritaUtils::mirrorDab(direction, effectiveAxesCenter, dab);
+}
diff --git a/libs/image/kis_painter.h b/libs/image/kis_painter.h
index 353afb1a28..ef1c5e2a7e 100644
--- a/libs/image/kis_painter.h
+++ b/libs/image/kis_painter.h
@@ -1,809 +1,860 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Clarence Dang <dang@kde.org>
* Copyright (c) 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 José Luis Vergara Toloza <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PAINTER_H_
#define KIS_PAINTER_H_
#include <math.h>
#include <QVector>
#include <KoColorSpaceConstants.h>
#include <KoColorConversionTransformation.h>
#include "kundo2magicstring.h"
#include "kis_types.h"
#include <kis_filter_configuration.h>
#include <kritaimage_export.h>
class QPen;
class KUndo2Command;
class QRect;
class QRectF;
class QBitArray;
class QPainterPath;
class KoAbstractGradient;
class KoUpdater;
class KoColor;
class KoCompositeOp;
class KisUndoAdapter;
class KisPostExecutionUndoAdapter;
class KisTransaction;
class KoPattern;
class KisPaintInformation;
class KisPaintOp;
class KisDistanceInformation;
+class KisRenderedDab;
+class KisRunnableStrokeJobsInterface;
/**
* KisPainter contains the graphics primitives necessary to draw on a
* KisPaintDevice. This is the same kind of abstraction as used in Qt
* itself, where you have QPainter and QPaintDevice.
*
* However, KisPainter works on a tiled image and supports different
* color models, and that's a lot more complicated.
*
* KisPainter supports transactions that can group various paint operations
* in one undoable step.
*
* For more complex operations, you might want to have a look at the subclasses
* of KisPainter: KisConvolutionPainter, KisFillPainter and KisGradientPainter
*
* KisPainter sets a number of default values, like COMPOSITE_OVER for compositeop,
* OPACITY_OPAQUE for opacity and no selection for selection.
*/
class KRITAIMAGE_EXPORT KisPainter
{
public:
/// Construct painter without a device
KisPainter();
/// Construct a painter, and begin painting on the device
KisPainter(KisPaintDeviceSP device);
/// Construct a painter, and begin painting on the device. All actions will be masked by the given selection.
KisPainter(KisPaintDeviceSP device, KisSelectionSP selection);
virtual ~KisPainter();
public:
static void copyAreaOptimized(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &originalSrcRect);
static void copyAreaOptimizedOldData(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &originalSrcRect);
static void copyAreaOptimized(const QPoint &dstPt,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
const QRect &originalSrcRect,
KisSelectionSP selection);
static KisPaintDeviceSP convertToAlphaAsAlpha(KisPaintDeviceSP src);
static KisPaintDeviceSP convertToAlphaAsGray(KisPaintDeviceSP src);
static bool checkDeviceHasTransparency(KisPaintDeviceSP dev);
/**
* Start painting on the specified device. Not undoable.
*/
void begin(KisPaintDeviceSP device);
/**
* Start painting on the specified paint device. All actions will be masked by the given selection.
*/
void begin(KisPaintDeviceSP device, KisSelectionSP selection);
/**
* Finish painting on the current device
*/
void end();
/**
* If set, the painter action is cancelable, if the action supports that.
*/
void setProgress(KoUpdater * progressUpdater);
/// Begin an undoable paint operation
void beginTransaction(const KUndo2MagicString& transactionName = KUndo2MagicString(),int timedID = -1);
/// Cancel all the changes made by the painter
void revertTransaction();
/// Finish the undoable paint operation
void endTransaction(KisUndoAdapter *undoAdapter);
/**
* Finish transaction and load it to a special adapter for strokes
*/
void endTransaction(KisPostExecutionUndoAdapter *undoAdapter);
/**
* Finishes a transaction and returns a pointer to its undo command
*/
KUndo2Command* endAndTakeTransaction();
/**
* Finish the transaction and delete it's undo information.
* NOTE: Be careful, because all the previous transactions
* will become non-undoable after execution of this method.
*/
void deleteTransaction();
/// continue a transaction started somewhere else
void putTransaction(KisTransaction* transaction);
/// take transaction out of the reach of KisPainter
KisTransaction* takeTransaction();
/// Returns the current paint device.
const KisPaintDeviceSP device() const;
KisPaintDeviceSP device();
/**
* Blast a region of srcWidth @param srcWidth and srcHeight @param srcHeight from @param
* srcDev onto the current paint device. @param srcX and @param srcY set the x and y
* positions of the origin top-left corner, @param dstX and @param dstY those of
* the destination.
* Any pixel read outside the limits of @param srcDev will return the
* default pixel, this is a property of \ref KisPaintDevice.
*
* @param dstX the destination x-coordinate
* @param dstY the destination y-coordinate
* @param srcDev the source device
* @param srcX the source x-coordinate
* @param srcY the source y-coordinate
* @param srcWidth the width of the region to be manipulated
* @param srcHeight the height of the region to be manipulated
*/
void bitBlt(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight);
/**
* Convenience method that uses QPoint and QRect.
*
* @param pos the destination coordinate, it replaces @param dstX and @param dstY.
* @param srcDev the source device.
* @param srcRect the rectangle describing the area to blast from @param srcDev into the current paint device.
* @param srcRect replaces @param srcX, @param srcY, @param srcWidth and @param srcHeight.
*
*/
void bitBlt(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect);
/**
* The same as @ref bitBlt() but reads data from oldData() part of the device
*
* @param dstX the destination x-coordinate
* @param dstY the destination y-coordinate
* @param srcDev the source device
* @param srcX the source x-coordinate
* @param srcY the source y-coordinate
* @param srcWidth the width of the region to be manipulated
* @param srcHeight the height of the region to be manipulated
*/
void bitBltOldData(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight);
/**
* Convenience method that uses QPoint and QRect.
*
* @param pos the destination coordinate, it replaces @param dstX and @param dstY.
* @param srcDev the source device.
* @param srcRect the rectangle describing the area to blast from @param srcDev into the current paint device.
* @param srcRect replaces @param srcX, @param srcY, @param srcWidth and @param srcHeight.
*
*/
void bitBltOldData(const QPoint & pos, const KisPaintDeviceSP srcDev, const QRect & srcRect);
/**
* Blasts a @param selection of srcWidth @param srcWidth and srcHeight @param srcHeight
* of @param srcDev on the current paint device. There is parameters
* to control where the area begins in each distinct device, explained below.
* @param selection can be used as a mask to shape @param srcDev to
* something interesting in the same step it is rendered to the current
* paint device. @param selection 's colorspace must be alpha8 (the
* colorspace for selections/transparency), the rectangle formed by
* @param selX, @param selY, @param srcWidth and @param srcHeight must not go
* beyond its limits, and they must be different from zero.
* @param selection and KisPainter's selection (the user selection) are
* fused together through the composite operation COMPOSITE_MULT.
* Any pixel read outside the limits of @param srcDev will return the
* default pixel, this is a property of \ref KisPaintDevice.
*
* @param dstX the destination x-coordinate
* @param dstY the destination y-coordinate
* @param srcDev the source device
* @param selection the custom selection to apply on the source device
* @param selX the selection x-coordinate
* @param selY the selection y-coordinate
* @param srcX the source x-coordinate
* @param srcY the source y-coordinate
* @param srcWidth the width of the region to be manipulated
* @param srcHeight the height of the region to be manipulated
*
*/
void bitBltWithFixedSelection(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
qint32 selX, qint32 selY,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight);
/**
* Convenience method that assumes @param selX, @param selY, @param srcX and @param srcY are
* equal to 0. Best used when @param selection and the desired area of @param srcDev have exactly
* the same dimensions and are specially made for each other.
*
* @param dstX the destination x-coordinate
* @param dstY the destination y-coordinate
* @param srcDev the source device
* @param selection the custom selection to apply on the source device
* @param srcWidth the width of the region to be manipulated
* @param srcHeight the height of the region to be manipulated
*/
void bitBltWithFixedSelection(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
qint32 srcWidth, qint32 srcHeight);
/**
* Blast a region of srcWidth @param srcWidth and srcHeight @param srcHeight from @param srcDev onto the current
* paint device. @param srcX and @param srcY set the x and y positions of the
* origin top-left corner, @param dstX and @param dstY those of the destination.
* @param srcDev is a \ref KisFixedPaintDevice: this means that @param srcDev must have the same
* colorspace as the destination device.
*
* @param dstX the destination x-coordinate
* @param dstY the destination y-coordinate
* @param srcDev the source device
* @param srcX the source x-coordinate
* @param srcY the source y-coordinate
* @param srcWidth the width of the region to be manipulated
* @param srcHeight the height of the region to be manipulated
*/
void bltFixed(qint32 dstX, qint32 dstY,
const KisFixedPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight);
+
+ /**
+ * Render the area \p rc from \p srcDevices on the destination device.
+ * If \p rc doesn't cross the device's rect, then the device is not
+ * rendered at all.
+ */
+ void bltFixed(const QRect &rc, const QList<KisRenderedDab> allSrcDevices);
+
/**
* Convenience method that uses QPoint and QRect.
*
* @param pos the destination coordinate, it replaces @param dstX and @param dstY.
* @param srcDev the source device.
* @param srcRect the rectangle describing the area to blast from @param srcDev into the current paint device.
* @param srcRect replaces @param srcX, @param srcY, @param srcWidth and @param srcHeight.
*
*/
void bltFixed(const QPoint & pos, const KisFixedPaintDeviceSP srcDev, const QRect & srcRect);
/**
* Blasts a @param selection of srcWidth @param srcWidth and srcHeight @param srcHeight
* of @param srcDev on the current paint device. There is parameters to control
* the top-left corner of the area in each respective paint device (@param dstX,
* @param dstY, @param srcX, @param srcY).
* @param selection can be used as a mask to shape @param srcDev to something
* interesting in the same step it is rendered to the current paint device.
* @param srcDev is a \ref KisFixedPaintDevice: this means that @param srcDev
* must have the same colorspace as the destination device.
* @param selection 's colorspace must be alpha8 (the colorspace for
* selections/transparency).
* The rectangle formed by the respective top-left coordinates of each device
* and @param srcWidth and @param srcHeight must not go beyond their limits, and
* they must be different from zero.
* @param selection and KisPainter's selection (the user selection) are
* fused together through the composite operation COMPOSITE_MULT.
*
* @param dstX the destination x-coordinate
* @param dstY the destination y-coordinate
* @param srcDev the source device
* @param selection the selection stored in fixed device
* @param selX the selection x-coordinate
* @param selY the selection y-coordinate
* @param srcX the source x-coordinate
* @param srcY the source y-coordinate
* @param srcWidth the width of the region to be manipulated
* @param srcHeight the height of the region to be manipulated
*/
void bltFixedWithFixedSelection(qint32 dstX, qint32 dstY,
const KisFixedPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
qint32 selX, qint32 selY,
qint32 srcX, qint32 srcY,
quint32 srcWidth, quint32 srcHeight);
/**
* Convenience method that assumes @param selX, @param selY, @param srcX and @param srcY are
* equal to 0. Best used when @param selection and @param srcDev have exactly the same
* dimensions and are specially made for each other.
*
* @param dstX the destination x-coordinate
* @param dstY the destination y-coordinate
* @param srcDev the source device
* @param selection the custom selection to apply on the source device
* @param srcWidth the width of the region to be manipulated
* @param srcHeight the height of the region to be manipulated
*/
void bltFixedWithFixedSelection(qint32 dstX, qint32 dstY,
const KisFixedPaintDeviceSP srcDev,
const KisFixedPaintDeviceSP selection,
quint32 srcWidth, quint32 srcHeight);
/**
* fills a region of width @param width and height @param height of the current
* paint device with the color @param color. @param x and @param y set the x and y positions of the
* origin top-left corner.
*
* @param x the destination x-coordinate
* @param y the destination y-coordinate
* @param width the width of the region to be manipulated
* @param height the height of the region to be manipulated
* @param color the color the area is filled with
*/
void fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor& color);
/**
* First you need to setup the painter with setMirrorInformation,
* then these set of methods provide way to render the devices mirrored
* according the axesCenter vertically or horizontally or both.
*
* @param rc rectangle area covered by dab
* @param dab this device will be mirrored in-place, it means that it will be changed
*/
void renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab);
void renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab, KisFixedPaintDeviceSP mask);
void renderMirrorMask(QRect rc, KisPaintDeviceSP dab);
void renderMirrorMask(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask);
/**
* Convenience method for renderMirrorMask(), allows to choose whether
* we need to preserve out dab or do the transformations in-place.
*
* @param rc rectangle area covered by dab
* @param dab the device to render
* @param preserveDab states whether a temporary device should be
* created to do the transformations
*/
void renderMirrorMaskSafe(QRect rc, KisFixedPaintDeviceSP dab, bool preserveDab);
/**
* Convenience method for renderMirrorMask(), allows to choose whether
* we need to preserve our fixed mask or do the transformations in-place.
*
* @param rc rectangle area covered by dab
* @param dab the device to render
* @param mask mask to use for rendering
* @param preserveMask states whether a temporary device should be
* created to do the transformations
*/
void renderMirrorMaskSafe(QRect rc, KisPaintDeviceSP dab, int sx, int sy, KisFixedPaintDeviceSP mask, bool preserveMask);
/**
* A complex method that re-renders a dab on an \p rc area.
* The \p rc area and all the dedicated mirroring areas are cleared
* before the painting, so this method should be used by paintops
* which do not update the canvas incrementally, but instead
* regenerate some internal cache \p dab with the COMPOSITE_COPY op.
*
* \see KisExperimentPaintOp
*/
void renderDabWithMirroringNonIncremental(QRect rc, KisPaintDeviceSP dab);
/**
* The methods in this class do not tell the paintdevice to update, but they calculate the
* dirty area. This method returns this dirty area and resets it.
*/
QVector<QRect> takeDirtyRegion();
/**
* Paint a line that connects the dots in points
*/
void paintPolyline(const QVector <QPointF> &points,
int index = 0, int numPoints = -1);
/**
* Draw a line between pos1 and pos2 using the currently set brush and color.
* If savedDist is less than zero, the brush is painted at pos1 before being
* painted along the line using the spacing setting.
* @return the drag distance, that is the remains of the distance between p1 and p2 not covered
* because the currenlty set brush has a spacing greater than that distance.
*/
void paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *curentDistance);
/**
* Draw a Bezier curve between pos1 and pos2 using control points 1 and 2.
* If savedDist is less than zero, the brush is painted at pos1 before being
* painted along the curve using the spacing setting.
* @return the drag distance, that is the remains of the distance between p1 and p2 not covered
* because the currenlty set brush has a spacing greater than that distance.
*/
void paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance);
/**
* Fill the given vector points with the points needed to draw the Bezier curve between
* pos1 and pos2 using control points 1 and 2, excluding the final pos2.
*/
void getBezierCurvePoints(const QPointF &pos1,
const QPointF &control1,
const QPointF &control2,
const QPointF &pos2,
vQPointF& points) const;
/**
* Paint a rectangle.
* @param rect the rectangle to paint.
*/
void paintRect(const QRectF &rect);
/**
* Paint a rectangle.
*
* @param x x coordinate of the top-left corner
* @param y y coordinate of the top-left corner
* @param w the rectangle width
* @param h the rectangle height
*/
void paintRect(const qreal x,
const qreal y,
const qreal w,
const qreal h);
/**
* Paint the ellipse that fills the given rectangle.
*
* @param rect the rectangle containing the ellipse to paint.
*/
void paintEllipse(const QRectF &rect);
/**
* Paint the ellipse that fills the given rectangle.
*
* @param x x coordinate of the top-left corner
* @param y y coordinate of the top-left corner
* @param w the rectangle width
* @param h the rectangle height
*/
void paintEllipse(const qreal x,
const qreal y,
const qreal w,
const qreal h);
/**
* Paint the polygon with the points given in points. It automatically closes the polygon
* by drawing the line from the last point to the first.
*/
void paintPolygon(const vQPointF& points);
/** Draw a spot at pos using the currently set paint op, brush and color */
void paintAt(const KisPaintInformation &pos,
KisDistanceInformation *savedDist);
/**
* Stroke the given QPainterPath.
*/
void paintPainterPath(const QPainterPath& path);
/**
* Fills the area enclosed by the given QPainterPath
* Convenience method for fillPainterPath(path, rect)
*/
void fillPainterPath(const QPainterPath& path);
/**
* Fills the portion of an area enclosed by the given QPainterPath
*
* \param rect the portion of the path to fill
*/
void fillPainterPath(const QPainterPath& path, const QRect &requestedRect);
/**
* Draw the path using the Pen
*
* if \p requestedRect is null, the entire path is painted
*/
void drawPainterPath(const QPainterPath& path, const QPen& pen, const QRect &requestedRect);
// convenience overload
void drawPainterPath(const QPainterPath& path, const QPen& pen);
/**
* paint an unstroked one-pixel wide line from specified start position to the
* specified end position.
*
*/
void drawLine(const QPointF & start, const QPointF & end);
/**
* paint an unstroked line with thickness from specified start position to the
* specified end position. Scanline algorithm is used.
*/
void drawLine(const QPointF &start, const QPointF &end, qreal width, bool antialias);
/**
* paints an unstroked, aliased one-pixel line using the DDA algorithm from specified start position to the
* specified end position.
*
*/
void drawDDALine(const QPointF & start, const QPointF & end);
/**
* Paint an unstroked, wobbly one-pixel wide line from the specified start to the specified
* end position.
*
*/
void drawWobblyLine(const QPointF & start, const QPointF & end);
/**
* Paint an unstroked, anti-aliased one-pixel wide line from the specified start to the specified
* end position using the Wu algorithm
*/
void drawWuLine(const QPointF & start, const QPointF & end);
/**
* Paint an unstroked wide line from the specified start to the specified
* end position with width varying from @param w1 at the start to @param w2 at
* the end.
*
* XXX: the width should be set in doubles, not integers.
*/
void drawThickLine(const QPointF & start, const QPointF & end, int startWidth, int endWidth);
/**
* Set the channelflags: a bit array where true means that the
* channel corresponding in position with the bit will be read
* by the operation, and false means that it will not be affected.
*
* An empty channelFlags parameter means that all channels are
* affected.
*
* @param the bit array that masks the source channels; only
* the channels where the corresponding bit is true will will be
* composited onto the destination device.
*/
void setChannelFlags(QBitArray channelFlags);
/// @return the channel flags
QBitArray channelFlags();
/**
* Set the paintop preset to use. If @param image is given,
* the paintop will be created using this image as parameter.
* Some paintops really want to know about the image they work
* for, e.g. the clone paintop.
*/
void setPaintOpPreset(KisPaintOpPresetSP preset, KisNodeSP node, KisImageSP image);
/// Return the paintop preset
KisPaintOpPresetSP preset() const;
/**
* Return the active paintop (which is created based on the specified preset and
* will be deleted as soon as the KisPainter instance dies).
*/
KisPaintOp* paintOp() const;
void setMirrorInformation(const QPointF &axesCenter, bool mirrorHorizontally, bool mirrorVertically);
- /**
- * copy the mirror information to other painter
- */
- void copyMirrorInformation(KisPainter * painter);
-
/**
* Returns whether the mirroring methods will do any
* work when called
*/
bool hasMirroring() const;
+ /**
+ * Indicates if horizontal mirroring mode is activated
+ */
+ bool hasHorizontalMirroring() const;
+
+ /**
+ * Indicates if vertical mirroring mode is activated
+ */
+ bool hasVerticalMirroring() const;
+
+ /**
+ * Mirror \p rc in the requested \p direction around the center point defined
+ * in the painter.
+ */
+ void mirrorRect(Qt::Orientation direction, QRect *rc) const;
+
+ /**
+ * Mirror \p dab in the requested direction around the center point defined
+ * in the painter. The dab's offset is adjusted automatically.
+ */
+ void mirrorDab(Qt::Orientation direction, KisRenderedDab *dab) const;
+
/// Set the current pattern
void setPattern(const KoPattern * pattern);
/// Returns the currently set pattern
const KoPattern * pattern() const;
/**
* Set the color that will be used to paint with, and convert it
* to the color space of the current paint device.
*/
void setPaintColor(const KoColor& color);
/// Returns the color that will be used to paint with
const KoColor &paintColor() const;
/**
* Set the current background color, and convert it
* to the color space of the current paint device.
*/
void setBackgroundColor(const KoColor& color);
/// Returns the current background color
const KoColor &backgroundColor() const;
/// Set the current generator (a generator can be used to fill an area
void setGenerator(KisFilterConfigurationSP generator);
/// @return the current generator configuration
const KisFilterConfigurationSP generator() const;
/// This enum contains the styles with which we can fill things like polygons and ellipses
enum FillStyle {
FillStyleNone,
FillStyleForegroundColor,
FillStyleBackgroundColor,
FillStylePattern,
FillStyleGradient,
FillStyleStrokes,
FillStyleGenerator,
};
/// Set the current style with which to fill
void setFillStyle(FillStyle fillStyle);
/// Returns the current fill style
FillStyle fillStyle() const;
/// Set whether a polygon's filled area should be anti-aliased or not. The default is true.
void setAntiAliasPolygonFill(bool antiAliasPolygonFill);
/// Return whether a polygon's filled area should be anti-aliased or not
bool antiAliasPolygonFill();
/// The style of the brush stroke around polygons and so
enum StrokeStyle {
StrokeStyleNone,
StrokeStyleBrush
};
/// Set the current brush stroke style
void setStrokeStyle(StrokeStyle strokeStyle);
/// Returns the current brush stroke style
StrokeStyle strokeStyle() const;
void setFlow(quint8 flow);
quint8 flow() const;
/**
* Sets the opacity of the painting and recalculates the
* mean opacity of the stroke. This mean value is used to
* make ALPHA_DARKEN painting look correct
*/
void setOpacityUpdateAverage(quint8 opacity);
+ /**
+ * Sets average opacity, that is used to make ALPHA_DARKEN painting look correct
+ */
+ void setAverageOpacity(qreal averageOpacity);
+
+ /**
+ * Calculate average opacity value after painting a single dab with \p opacity
+ */
+ static qreal blendAverageOpacity(qreal opacity, qreal averageOpacity);
+
/// Set the opacity which is used in painting (like filling polygons)
void setOpacity(quint8 opacity);
/// Returns the opacity that is used in painting
quint8 opacity() const;
/// Set the composite op for this painter
void setCompositeOp(const KoCompositeOp * op);
const KoCompositeOp * compositeOp();
/// Set the composite op for this painter by string.
/// Note: the colorspace must be set previously!
void setCompositeOp(const QString& op);
/**
* Add the r to the current dirty rect.
*/
void addDirtyRect(const QRect & r);
/**
* Reset the selection to the given selection. All painter actions will be
* masked by the specified selection.
*/
void setSelection(KisSelectionSP selection);
/**
* @return the selection set on this painter.
*/
KisSelectionSP selection();
void setGradient(const KoAbstractGradient* gradient);
const KoAbstractGradient* gradient() const;
/**
* Set the size of the tile in fillPainterPath, useful when optimizing the use of fillPainterPath
* e.g. Spray paintop uses more small tiles, although selections uses bigger tiles. QImage::fill
* is quite expensive so with smaller images you can save instructions
* Default and maximum size is 256x256 image
*/
void setMaskImageSize(qint32 width, qint32 height);
// /**
// * If the alpha channel is locked, the alpha values of the paint device we are painting on
// * will not change.
// */
// void setLockAlpha(bool protect);
// bool alphaLocked() const;
/**
* set the rendering intent in case pixels need to be converted before painting
*/
void setRenderingIntent(KoColorConversionTransformation::Intent intent);
/**
* set the conversion flags in case pixels need to be converted before painting
*/
void setColorConversionFlags(KoColorConversionTransformation::ConversionFlags conversionFlags);
+ /**
+ * Set interface for running asynchronous jobs by paintops.
+ *
+ * NOTE: the painter does *not* own the interface device. It is the responsibility
+ * of the caller to ensure that the interface object is alive during the lifetime
+ * of the painter.
+ */
+ void setRunnableStrokeJobsInterface(KisRunnableStrokeJobsInterface *interface);
+
+ /**
+ * Get the interface for running asynchronous jobs. It is used by paintops mostly.
+ */
+ KisRunnableStrokeJobsInterface* runnableStrokeJobsInterface() const;
+
protected:
/// Initialize, set everything to '0' or defaults
void init();
/// Fill the polygon defined by points with the fillStyle
void fillPolygon(const vQPointF& points, FillStyle fillStyle);
private:
KisPainter(const KisPainter&);
KisPainter& operator=(const KisPainter&);
float frac(float value) {
float tmp = 0;
return modff(value , &tmp);
}
float invertFrac(float value) {
float tmp = 0;
return 1.0f - modff(value , &tmp);
}
protected:
KoUpdater * progressUpdater();
private:
template <bool useOldSrcData>
void bitBltImpl(qint32 dstX, qint32 dstY,
const KisPaintDeviceSP srcDev,
qint32 srcX, qint32 srcY,
qint32 srcWidth, qint32 srcHeight);
inline void compositeOnePixel(quint8 *dst, const KoColor &color);
private:
struct Private;
Private* const d;
};
#endif // KIS_PAINTER_H_
diff --git a/libs/image/kis_painter_blt_multi_fixed.cpp b/libs/image/kis_painter_blt_multi_fixed.cpp
new file mode 100644
index 0000000000..5db6f4d43a
--- /dev/null
+++ b/libs/image/kis_painter_blt_multi_fixed.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "kis_painter.h"
+#include "kis_painter_p.h"
+
+#include "kis_paint_device.h"
+#include "kis_fixed_paint_device.h"
+#include "kis_random_accessor_ng.h"
+#include "KisRenderedDab.h"
+
+void KisPainter::Private::applyDevice(const QRect &applyRect,
+ const KisRenderedDab &dab,
+ KisRandomAccessorSP dstIt,
+ const KoColorSpace *srcColorSpace,
+ KoCompositeOp::ParameterInfo &localParamInfo)
+{
+ const QRect dabRect = dab.realBounds();
+ const QRect rc = applyRect & dabRect;
+
+ const int srcPixelSize = srcColorSpace->pixelSize();
+ const int dabRowStride = srcPixelSize * dabRect.width();
+
+
+ qint32 dstY = rc.y();
+ qint32 rowsRemaining = rc.height();
+
+ while (rowsRemaining > 0) {
+ qint32 dstX = rc.x();
+
+ qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY);
+ qint32 rows = qMin(rowsRemaining, numContiguousDstRows);
+
+ qint32 columnsRemaining = rc.width();
+
+ while (columnsRemaining > 0) {
+
+ qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX);
+ qint32 columns = qMin(numContiguousDstColumns, columnsRemaining);
+
+ qint32 dstRowStride = dstIt->rowStride(dstX, dstY);
+ dstIt->moveTo(dstX, dstY);
+
+ localParamInfo.dstRowStart = dstIt->rawData();
+ localParamInfo.dstRowStride = dstRowStride;
+ localParamInfo.maskRowStart = 0;
+ localParamInfo.maskRowStride = 0;
+ localParamInfo.rows = rows;
+ localParamInfo.cols = columns;
+
+
+ const int dabX = dstX - dabRect.x();
+ const int dabY = dstY - dabRect.y();
+
+ localParamInfo.srcRowStart = dab.device->constData() + dabX * pixelSize + dabY * dabRowStride;
+ localParamInfo.srcRowStride = dabRowStride;
+ localParamInfo.setOpacityAndAverage(dab.opacity, dab.averageOpacity);
+ localParamInfo.flow = dab.flow;
+ colorSpace->bitBlt(srcColorSpace, localParamInfo, compositeOp, renderingIntent, conversionFlags);
+
+ dstX += columns;
+ columnsRemaining -= columns;
+ }
+
+ dstY += rows;
+ rowsRemaining -= rows;
+ }
+
+}
+
+void KisPainter::Private::applyDeviceWithSelection(const QRect &applyRect,
+ const KisRenderedDab &dab,
+ KisRandomAccessorSP dstIt,
+ KisRandomConstAccessorSP maskIt,
+ const KoColorSpace *srcColorSpace,
+ KoCompositeOp::ParameterInfo &localParamInfo)
+{
+ const QRect dabRect = dab.realBounds();
+ const QRect rc = applyRect & dabRect;
+
+ const int srcPixelSize = srcColorSpace->pixelSize();
+ const int dabRowStride = srcPixelSize * dabRect.width();
+
+
+ qint32 dstY = rc.y();
+ qint32 rowsRemaining = rc.height();
+
+ while (rowsRemaining > 0) {
+ qint32 dstX = rc.x();
+
+ qint32 numContiguousDstRows = dstIt->numContiguousRows(dstY);
+ qint32 numContiguousMaskRows = maskIt->numContiguousRows(dstY);
+ qint32 rows = qMin(rowsRemaining, qMin(numContiguousDstRows, numContiguousMaskRows));
+
+ qint32 columnsRemaining = rc.width();
+
+ while (columnsRemaining > 0) {
+
+ qint32 numContiguousDstColumns = dstIt->numContiguousColumns(dstX);
+ qint32 numContiguousMaskColumns = maskIt->numContiguousColumns(dstX);
+ qint32 columns = qMin(columnsRemaining, qMin(numContiguousDstColumns, numContiguousMaskColumns));
+
+ qint32 dstRowStride = dstIt->rowStride(dstX, dstY);
+ qint32 maskRowStride = maskIt->rowStride(dstX, dstY);
+ dstIt->moveTo(dstX, dstY);
+ maskIt->moveTo(dstX, dstY);
+
+ localParamInfo.dstRowStart = dstIt->rawData();
+ localParamInfo.dstRowStride = dstRowStride;
+ localParamInfo.maskRowStart = maskIt->rawDataConst();
+ localParamInfo.maskRowStride = maskRowStride;
+ localParamInfo.rows = rows;
+ localParamInfo.cols = columns;
+
+
+ const int dabX = dstX - dabRect.x();
+ const int dabY = dstY - dabRect.y();
+
+ localParamInfo.srcRowStart = dab.device->constData() + dabX * pixelSize + dabY * dabRowStride;
+ localParamInfo.srcRowStride = dabRowStride;
+ localParamInfo.setOpacityAndAverage(dab.opacity, dab.averageOpacity);
+ localParamInfo.flow = dab.flow;
+ colorSpace->bitBlt(srcColorSpace, localParamInfo, compositeOp, renderingIntent, conversionFlags);
+
+ dstX += columns;
+ columnsRemaining -= columns;
+ }
+
+ dstY += rows;
+ rowsRemaining -= rows;
+ }
+
+}
+
+void KisPainter::bltFixed(const QRect &applyRect, const QList<KisRenderedDab> allSrcDevices)
+{
+ const KoColorSpace *srcColorSpace = 0;
+ QList<KisRenderedDab> devices;
+ QRect rc = applyRect;
+
+ if (d->selection) {
+ rc &= d->selection->selectedRect();
+ }
+
+ QRect totalDevicesRect;
+
+ Q_FOREACH (const KisRenderedDab &dab, allSrcDevices) {
+ if (rc.intersects(dab.realBounds())) {
+ devices.append(dab);
+ totalDevicesRect |= dab.realBounds();
+ }
+
+ if (!srcColorSpace) {
+ srcColorSpace = dab.device->colorSpace();
+ } else {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(*srcColorSpace == *dab.device->colorSpace());
+ }
+ }
+
+ rc &= totalDevicesRect;
+
+ if (devices.isEmpty() || rc.isEmpty()) return;
+
+ KoCompositeOp::ParameterInfo localParamInfo = d->paramInfo;
+ KisRandomAccessorSP dstIt = d->device->createRandomAccessorNG(rc.left(), rc.top());
+ KisRandomConstAccessorSP maskIt = d->selection ? d->selection->projection()->createRandomConstAccessorNG(rc.left(), rc.top()) : 0;
+
+ if (maskIt) {
+ Q_FOREACH (const KisRenderedDab &dab, devices) {
+ d->applyDeviceWithSelection(rc, dab, dstIt, maskIt, srcColorSpace, localParamInfo);
+ }
+ } else {
+ Q_FOREACH (const KisRenderedDab &dab, devices) {
+ d->applyDevice(rc, dab, dstIt, srcColorSpace, localParamInfo);
+ }
+ }
+
+
+#if 0
+ // the code above does basically the same thing as this one,
+ // but more efficiently :)
+
+ Q_FOREACH (KisFixedPaintDeviceSP dev, devices) {
+ const QRect copyRect = dev->bounds() & rc;
+ if (copyRect.isEmpty()) continue;
+
+ bltFixed(copyRect.topLeft(), dev, copyRect);
+ }
+#endif
+}
+
diff --git a/libs/image/kis_painter_p.h b/libs/image/kis_painter_p.h
new file mode 100644
index 0000000000..fd150fdb5e
--- /dev/null
+++ b/libs/image/kis_painter_p.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISPAINTERPRIVATE_H
+#define KISPAINTERPRIVATE_H
+
+#include <KoColorSpace.h>
+#include <KoColor.h>
+#include <KoCompositeOpRegistry.h>
+#include <KoUpdater.h>
+#include "kis_paintop.h"
+#include "kis_selection.h"
+#include "kis_fill_painter.h"
+#include "kis_painter.h"
+#include "kis_paintop_preset.h"
+#include <KisFakeRunnableStrokeJobsExecutor.h>
+
+struct Q_DECL_HIDDEN KisPainter::Private {
+ Private(KisPainter *_q) : q(_q) {}
+ Private(KisPainter *_q, const KoColorSpace *cs)
+ : q(_q), paintColor(cs), backgroundColor(cs) {}
+
+ KisPainter *q;
+
+ KisPaintDeviceSP device;
+ KisSelectionSP selection;
+ KisTransaction* transaction;
+ KoUpdater* progressUpdater;
+
+ QVector<QRect> dirtyRects;
+ KisPaintOp* paintOp;
+ KoColor paintColor;
+ KoColor backgroundColor;
+ KoColor customColor;
+ KisFilterConfigurationSP generator;
+ KisPaintLayer* sourceLayer;
+ FillStyle fillStyle;
+ StrokeStyle strokeStyle;
+ bool antiAliasPolygonFill;
+ const KoPattern* pattern;
+ QPointF duplicateOffset;
+ quint32 pixelSize;
+ const KoColorSpace* colorSpace;
+ KoColorProfile* profile;
+ const KoCompositeOp* compositeOp;
+ const KoAbstractGradient* gradient;
+ KisPaintOpPresetSP paintOpPreset;
+ QImage polygonMaskImage;
+ QPainter* maskPainter;
+ KisFillPainter* fillPainter;
+ KisPaintDeviceSP polygon;
+ qint32 maskImageWidth;
+ qint32 maskImageHeight;
+ QPointF axesCenter;
+ bool mirrorHorizontally;
+ bool mirrorVertically;
+ bool isOpacityUnit; // TODO: move into ParameterInfo
+ KoCompositeOp::ParameterInfo paramInfo;
+ KoColorConversionTransformation::Intent renderingIntent;
+ KoColorConversionTransformation::ConversionFlags conversionFlags;
+ KisRunnableStrokeJobsInterface *runnableStrokeJobsInterface = 0;
+ QScopedPointer<KisRunnableStrokeJobsInterface> fakeRunnableStrokeJobsInterface;
+
+ bool tryReduceSourceRect(const KisPaintDevice *srcDev,
+ QRect *srcRect,
+ qint32 *srcX,
+ qint32 *srcY,
+ qint32 *srcWidth,
+ qint32 *srcHeight,
+ qint32 *dstX,
+ qint32 *dstY);
+
+ void fillPainterPathImpl(const QPainterPath& path, const QRect &requestedRect);
+
+ void applyDevice(const QRect &applyRect,
+ const KisRenderedDab &dab,
+ KisRandomAccessorSP dstIt,
+ const KoColorSpace *srcColorSpace,
+ KoCompositeOp::ParameterInfo &localParamInfo);
+
+ void applyDeviceWithSelection(const QRect &applyRect,
+ const KisRenderedDab &dab,
+ KisRandomAccessorSP dstIt,
+ KisRandomConstAccessorSP maskIt,
+ const KoColorSpace *srcColorSpace,
+ KoCompositeOp::ParameterInfo &localParamInfo);
+
+};
+
+#endif // KISPAINTERPRIVATE_H
diff --git a/libs/image/kis_projection_updates_filter.cpp b/libs/image/kis_projection_updates_filter.cpp
index e1493ec367..3e3fc6be59 100644
--- a/libs/image/kis_projection_updates_filter.cpp
+++ b/libs/image/kis_projection_updates_filter.cpp
@@ -1,36 +1,36 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_projection_updates_filter.h"
#include <QtGlobal>
#include <QRect>
KisProjectionUpdatesFilter::~KisProjectionUpdatesFilter()
{
}
-bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache)
+bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QVector<QRect> &rects, bool resetAnimationCache)
{
Q_UNUSED(image);
Q_UNUSED(node);
- Q_UNUSED(rect);
+ Q_UNUSED(rects);
Q_UNUSED(resetAnimationCache);
return true;
}
diff --git a/libs/image/kis_projection_updates_filter.h b/libs/image/kis_projection_updates_filter.h
index 9910cc0355..13054670b9 100644
--- a/libs/image/kis_projection_updates_filter.h
+++ b/libs/image/kis_projection_updates_filter.h
@@ -1,50 +1,50 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_PROJECTION_UPDATES_FILTER_H
#define __KIS_PROJECTION_UPDATES_FILTER_H
#include <QSharedPointer>
class KisImage;
class KisNode;
class QRect;
class KisProjectionUpdatesFilter
{
public:
virtual ~KisProjectionUpdatesFilter();
/**
* \return true if an update should be dropped by the image
*/
- virtual bool filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache) = 0;
+ virtual bool filter(KisImage *image, KisNode *node, const QVector<QRect> &rects, bool resetAnimationCache) = 0;
};
/**
* A dummy filter implementation that eats all the updates
*/
class KisDropAllProjectionUpdatesFilter : public KisProjectionUpdatesFilter
{
public:
- bool filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache) override;
+ bool filter(KisImage *image, KisNode *node, const QVector<QRect> &rects, bool resetAnimationCache) override;
};
#endif /* __KIS_PROJECTION_UPDATES_FILTER_H */
diff --git a/libs/image/kis_simple_update_queue.cpp b/libs/image/kis_simple_update_queue.cpp
index 7316a8d48d..81c5509814 100644
--- a/libs/image/kis_simple_update_queue.cpp
+++ b/libs/image/kis_simple_update_queue.cpp
@@ -1,375 +1,397 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_simple_update_queue.h"
#include <QMutexLocker>
+#include <QVector>
#include "kis_image_config.h"
#include "kis_full_refresh_walker.h"
#include "kis_spontaneous_job.h"
//#define ENABLE_DEBUG_JOIN
//#define ENABLE_ACCUMULATOR
#ifdef ENABLE_DEBUG_JOIN
#define DEBUG_JOIN(baseRect, newRect, alpha) \
dbgKrita << "Two rects were joined:\t" \
<< (baseRect) << "+" << (newRect) << "->" \
<< ((baseRect) | (newRect)) << "(" << alpha << ")"
#else
#define DEBUG_JOIN(baseRect, newRect, alpha)
#endif /* ENABLE_DEBUG_JOIN */
#ifdef ENABLE_ACCUMULATOR
#define DECLARE_ACCUMULATOR() static qreal _baseAmount=0, _newAmount=0
#define ACCUMULATOR_ADD(baseAmount, newAmount) \
do {_baseAmount += baseAmount; _newAmount += newAmount;} while (0)
#define ACCUMULATOR_DEBUG() \
dbgKrita << "Accumulated alpha:" << _newAmount / _baseAmount
#else
#define DECLARE_ACCUMULATOR()
#define ACCUMULATOR_ADD(baseAmount, newAmount)
#define ACCUMULATOR_DEBUG()
#endif /* ENABLE_ACCUMULATOR */
KisSimpleUpdateQueue::KisSimpleUpdateQueue()
: m_overrideLevelOfDetail(-1)
{
updateSettings();
}
KisSimpleUpdateQueue::~KisSimpleUpdateQueue()
{
QMutexLocker locker(&m_lock);
while (!m_spontaneousJobsList.isEmpty()) {
delete m_spontaneousJobsList.takeLast();
}
}
void KisSimpleUpdateQueue::updateSettings()
{
QMutexLocker locker(&m_lock);
KisImageConfig config;
m_patchWidth = config.updatePatchWidth();
m_patchHeight = config.updatePatchHeight();
m_maxCollectAlpha = config.maxCollectAlpha();
m_maxMergeAlpha = config.maxMergeAlpha();
m_maxMergeCollectAlpha = config.maxMergeCollectAlpha();
}
int KisSimpleUpdateQueue::overrideLevelOfDetail() const
{
return m_overrideLevelOfDetail;
}
void KisSimpleUpdateQueue::processQueue(KisUpdaterContext &updaterContext)
{
updaterContext.lock();
while(updaterContext.hasSpareThread() &&
processOneJob(updaterContext));
updaterContext.unlock();
}
bool KisSimpleUpdateQueue::processOneJob(KisUpdaterContext &updaterContext)
{
QMutexLocker locker(&m_lock);
KisBaseRectsWalkerSP item;
KisMutableWalkersListIterator iter(m_updatesList);
bool jobAdded = false;
int currentLevelOfDetail = updaterContext.currentLevelOfDetail();
while(iter.hasNext()) {
item = iter.next();
if ((currentLevelOfDetail < 0 || currentLevelOfDetail == item->levelOfDetail()) &&
!item->checksumValid()) {
m_overrideLevelOfDetail = item->levelOfDetail();
item->recalculate(item->requestedRect());
m_overrideLevelOfDetail = -1;
}
if ((currentLevelOfDetail < 0 || currentLevelOfDetail == item->levelOfDetail()) &&
updaterContext.isJobAllowed(item)) {
updaterContext.addMergeJob(item);
iter.remove();
jobAdded = true;
break;
}
}
if (jobAdded) return true;
if (!m_spontaneousJobsList.isEmpty()) {
/**
* WARNING: Please note that this still doesn't guarantee that
* the spontaneous jobs are exclusive, since updates and/or
* strokes can be added after them. The only thing it
* guarantees that two spontaneous jobs will not be executed
* in parallel.
*
* Right now it works as it is. Probably will need to be fixed
* in the future.
*/
qint32 numMergeJobs;
qint32 numStrokeJobs;
updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs);
if (!numMergeJobs && !numStrokeJobs) {
KisSpontaneousJob *job = m_spontaneousJobsList.takeFirst();
updaterContext.addSpontaneousJob(job);
jobAdded = true;
}
}
return jobAdded;
}
-void KisSimpleUpdateQueue::addUpdateJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail)
+void KisSimpleUpdateQueue::addUpdateJob(KisNodeSP node, const QVector<QRect> &rects, const QRect& cropRect, int levelOfDetail)
{
- addJob(node, rc, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE);
+ addJob(node, rects, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE);
}
+void KisSimpleUpdateQueue::addUpdateJob(KisNodeSP node, const QRect &rc, const QRect& cropRect, int levelOfDetail)
+{
+ addJob(node, {rc}, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE);
+}
+
+
void KisSimpleUpdateQueue::addUpdateNoFilthyJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail)
{
- addJob(node, rc, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE_NO_FILTHY);
+ addJob(node, {rc}, cropRect, levelOfDetail, KisBaseRectsWalker::UPDATE_NO_FILTHY);
}
void KisSimpleUpdateQueue::addFullRefreshJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail)
{
- addJob(node, rc, cropRect, levelOfDetail, KisBaseRectsWalker::FULL_REFRESH);
+ addJob(node, {rc}, cropRect, levelOfDetail, KisBaseRectsWalker::FULL_REFRESH);
}
-void KisSimpleUpdateQueue::addJob(KisNodeSP node, const QRect& rc,
+void KisSimpleUpdateQueue::addJob(KisNodeSP node, const QVector<QRect> &rects,
const QRect& cropRect,
int levelOfDetail,
KisBaseRectsWalker::UpdateType type)
{
- if(trySplitJob(node, rc, cropRect, levelOfDetail, type)) return;
- if(tryMergeJob(node, rc, cropRect, levelOfDetail, type)) return;
+ QList<KisBaseRectsWalkerSP> walkers;
- KisBaseRectsWalkerSP walker;
+ Q_FOREACH (const QRect &rc, rects) {
+ if (rc.isEmpty()) continue;
- if (type == KisBaseRectsWalker::UPDATE) {
- walker = new KisMergeWalker(cropRect, KisMergeWalker::DEFAULT);
- }
- else if (type == KisBaseRectsWalker::FULL_REFRESH) {
- walker = new KisFullRefreshWalker(cropRect);
- }
- else if (type == KisBaseRectsWalker::UPDATE_NO_FILTHY) {
- walker = new KisMergeWalker(cropRect, KisMergeWalker::NO_FILTHY);
- }
- /* else if(type == KisBaseRectsWalker::UNSUPPORTED) fatalKrita; */
+ KisBaseRectsWalkerSP walker;
- walker->collectRects(node, rc);
+ if(trySplitJob(node, rc, cropRect, levelOfDetail, type)) continue;
+ if(tryMergeJob(node, rc, cropRect, levelOfDetail, type)) continue;
- m_lock.lock();
- m_updatesList.append(walker);
- m_lock.unlock();
+ if (type == KisBaseRectsWalker::UPDATE) {
+ walker = new KisMergeWalker(cropRect, KisMergeWalker::DEFAULT);
+ }
+ else if (type == KisBaseRectsWalker::FULL_REFRESH) {
+ walker = new KisFullRefreshWalker(cropRect);
+ }
+ else if (type == KisBaseRectsWalker::UPDATE_NO_FILTHY) {
+ walker = new KisMergeWalker(cropRect, KisMergeWalker::NO_FILTHY);
+ }
+ /* else if(type == KisBaseRectsWalker::UNSUPPORTED) fatalKrita; */
+
+ walker->collectRects(node, rc);
+ walkers.append(walker);
+ }
+
+ if (!walkers.isEmpty()) {
+ m_lock.lock();
+ m_updatesList.append(walkers);
+ m_lock.unlock();
+ }
}
void KisSimpleUpdateQueue::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
QMutexLocker locker(&m_lock);
KisSpontaneousJob *item;
KisMutableSpontaneousJobsListIterator iter(m_spontaneousJobsList);
iter.toBack();
while(iter.hasPrevious()) {
item = iter.previous();
if (spontaneousJob->overrides(item)) {
iter.remove();
delete item;
}
}
m_spontaneousJobsList.append(spontaneousJob);
}
bool KisSimpleUpdateQueue::isEmpty() const
{
QMutexLocker locker(&m_lock);
return m_updatesList.isEmpty() && m_spontaneousJobsList.isEmpty();
}
qint32 KisSimpleUpdateQueue::sizeMetric() const
{
QMutexLocker locker(&m_lock);
return m_updatesList.size() + m_spontaneousJobsList.size();
}
bool KisSimpleUpdateQueue::trySplitJob(KisNodeSP node, const QRect& rc,
const QRect& cropRect,
int levelOfDetail,
KisBaseRectsWalker::UpdateType type)
{
if(rc.width() <= m_patchWidth || rc.height() <= m_patchHeight)
return false;
// a bit of recursive splitting...
qint32 firstCol = rc.x() / m_patchWidth;
qint32 firstRow = rc.y() / m_patchHeight;
qint32 lastCol = (rc.x() + rc.width()) / m_patchWidth;
qint32 lastRow = (rc.y() + rc.height()) / m_patchHeight;
+ QVector<QRect> splitRects;
+
for(qint32 i = firstRow; i <= lastRow; i++) {
for(qint32 j = firstCol; j <= lastCol; j++) {
QRect maxPatchRect(j * m_patchWidth, i * m_patchHeight,
m_patchWidth, m_patchHeight);
QRect patchRect = rc & maxPatchRect;
- addJob(node, patchRect, cropRect, levelOfDetail, type);
+ splitRects.append(patchRect);
}
}
+
+ KIS_SAFE_ASSERT_RECOVER_NOOP(!splitRects.isEmpty());
+ addJob(node, splitRects, cropRect, levelOfDetail, type);
+
return true;
}
bool KisSimpleUpdateQueue::tryMergeJob(KisNodeSP node, const QRect& rc,
const QRect& cropRect,
int levelOfDetail,
KisBaseRectsWalker::UpdateType type)
{
QMutexLocker locker(&m_lock);
QRect baseRect = rc;
KisBaseRectsWalkerSP goodCandidate;
KisBaseRectsWalkerSP item;
KisWalkersListIterator iter(m_updatesList);
/**
* We add new jobs to the tail of the list,
* so it's more probable to find a good candidate here.
*/
iter.toBack();
while(iter.hasPrevious()) {
item = iter.previous();
if(item->startNode() != node) continue;
if(item->type() != type) continue;
if(item->cropRect() != cropRect) continue;
if(item->levelOfDetail() != levelOfDetail) continue;
if(joinRects(baseRect, item->requestedRect(), m_maxMergeAlpha)) {
goodCandidate = item;
break;
}
}
if(goodCandidate)
collectJobs(goodCandidate, baseRect, m_maxMergeCollectAlpha);
return (bool)goodCandidate;
}
void KisSimpleUpdateQueue::optimize()
{
QMutexLocker locker(&m_lock);
if(m_updatesList.size() <= 1) return;
KisBaseRectsWalkerSP baseWalker = m_updatesList.first();
QRect baseRect = baseWalker->requestedRect();
collectJobs(baseWalker, baseRect, m_maxCollectAlpha);
}
void KisSimpleUpdateQueue::collectJobs(KisBaseRectsWalkerSP &baseWalker,
QRect baseRect,
const qreal maxAlpha)
{
KisBaseRectsWalkerSP item;
KisMutableWalkersListIterator iter(m_updatesList);
while(iter.hasNext()) {
item = iter.next();
if(item == baseWalker) continue;
if(item->type() != baseWalker->type()) continue;
if(item->startNode() != baseWalker->startNode()) continue;
if(item->cropRect() != baseWalker->cropRect()) continue;
if(item->levelOfDetail() != baseWalker->levelOfDetail()) continue;
if(joinRects(baseRect, item->requestedRect(), maxAlpha)) {
iter.remove();
}
}
if(baseWalker->requestedRect() != baseRect) {
baseWalker->collectRects(baseWalker->startNode(), baseRect);
}
}
bool KisSimpleUpdateQueue::joinRects(QRect& baseRect,
const QRect& newRect, qreal maxAlpha)
{
QRect unitedRect = baseRect | newRect;
if(unitedRect.width() > m_patchWidth || unitedRect.height() > m_patchHeight)
return false;
bool result = false;
qint64 baseWork = baseRect.width() * baseRect.height() +
newRect.width() * newRect.height();
qint64 newWork = unitedRect.width() * unitedRect.height();
qreal alpha = qreal(newWork) / baseWork;
if(alpha < maxAlpha) {
DEBUG_JOIN(baseRect, newRect, alpha);
DECLARE_ACCUMULATOR();
ACCUMULATOR_ADD(baseWork, newWork);
ACCUMULATOR_DEBUG();
baseRect = unitedRect;
result = true;
}
return result;
}
KisWalkersList& KisTestableSimpleUpdateQueue::getWalkersList()
{
return m_updatesList;
}
KisSpontaneousJobsList& KisTestableSimpleUpdateQueue::getSpontaneousJobsList()
{
return m_spontaneousJobsList;
}
diff --git a/libs/image/kis_simple_update_queue.h b/libs/image/kis_simple_update_queue.h
index 9f9fb77df7..e51065c53d 100644
--- a/libs/image/kis_simple_update_queue.h
+++ b/libs/image/kis_simple_update_queue.h
@@ -1,115 +1,116 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_SIMPLE_UPDATE_QUEUE_H
#define __KIS_SIMPLE_UPDATE_QUEUE_H
#include <QMutex>
#include "kis_updater_context.h"
typedef QList<KisBaseRectsWalkerSP> KisWalkersList;
typedef QListIterator<KisBaseRectsWalkerSP> KisWalkersListIterator;
typedef QMutableListIterator<KisBaseRectsWalkerSP> KisMutableWalkersListIterator;
typedef QList<KisSpontaneousJob*> KisSpontaneousJobsList;
typedef QListIterator<KisSpontaneousJob*> KisSpontaneousJobsListIterator;
typedef QMutableListIterator<KisSpontaneousJob*> KisMutableSpontaneousJobsListIterator;
class KRITAIMAGE_EXPORT KisSimpleUpdateQueue
{
public:
KisSimpleUpdateQueue();
virtual ~KisSimpleUpdateQueue();
void processQueue(KisUpdaterContext &updaterContext);
- void addUpdateJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail);
+ void addUpdateJob(KisNodeSP node, const QVector<QRect> &rects, const QRect& cropRect, int levelOfDetail);
+ void addUpdateJob(KisNodeSP node, const QRect &rc, const QRect& cropRect, int levelOfDetail);
void addUpdateNoFilthyJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail);
void addFullRefreshJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail);
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob);
void optimize();
bool isEmpty() const;
qint32 sizeMetric() const;
void updateSettings();
int overrideLevelOfDetail() const;
protected:
- void addJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail, KisBaseRectsWalker::UpdateType type);
+ void addJob(KisNodeSP node, const QVector<QRect> &rects, const QRect& cropRect, int levelOfDetail, KisBaseRectsWalker::UpdateType type);
bool processOneJob(KisUpdaterContext &updaterContext);
bool trySplitJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail, KisBaseRectsWalker::UpdateType type);
bool tryMergeJob(KisNodeSP node, const QRect& rc, const QRect& cropRect, int levelOfDetail, KisBaseRectsWalker::UpdateType type);
void collectJobs(KisBaseRectsWalkerSP &baseWalker, QRect baseRect,
const qreal maxAlpha);
bool joinRects(QRect& baseRect, const QRect& newRect, qreal maxAlpha);
protected:
mutable QMutex m_lock;
KisWalkersList m_updatesList;
KisSpontaneousJobsList m_spontaneousJobsList;
/**
* Parameters of optimization
* (loaded from a configuration file)
*/
/**
* Big update areas are split into a set of smaller
* ones, m_patchWidth and m_patchHeight represent the
* size of these areas.
*/
qint32 m_patchWidth;
qint32 m_patchHeight;
/**
* Maximum coefficient of work while regular optimization()
*/
qreal m_maxCollectAlpha;
/**
* Maximum coefficient of work when to rects are considered
* similar and are merged in tryMergeJob()
*/
qreal m_maxMergeAlpha;
/**
* The coefficient of work used while collecting phase of tryToMerge()
*/
qreal m_maxMergeCollectAlpha;
int m_overrideLevelOfDetail;
};
class KRITAIMAGE_EXPORT KisTestableSimpleUpdateQueue : public KisSimpleUpdateQueue
{
public:
KisWalkersList& getWalkersList();
KisSpontaneousJobsList& getSpontaneousJobsList();
};
#endif /* __KIS_SIMPLE_UPDATE_QUEUE_H */
diff --git a/libs/image/kis_stroke.cpp b/libs/image/kis_stroke.cpp
index d940602317..4d7d67b572 100644
--- a/libs/image/kis_stroke.cpp
+++ b/libs/image/kis_stroke.cpp
@@ -1,315 +1,336 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_stroke.h"
#include "kis_stroke_strategy.h"
KisStroke::KisStroke(KisStrokeStrategy *strokeStrategy, Type type, int levelOfDetail)
: m_strokeStrategy(strokeStrategy),
m_strokeInitialized(false),
m_strokeEnded(false),
m_strokeSuspended(false),
m_isCancelled(false),
- m_prevJobSequential(false),
m_worksOnLevelOfDetail(levelOfDetail),
m_type(type)
{
m_initStrategy.reset(m_strokeStrategy->createInitStrategy());
m_dabStrategy.reset(m_strokeStrategy->createDabStrategy());
m_cancelStrategy.reset(m_strokeStrategy->createCancelStrategy());
m_finishStrategy.reset(m_strokeStrategy->createFinishStrategy());
m_suspendStrategy.reset(m_strokeStrategy->createSuspendStrategy());
m_resumeStrategy.reset(m_strokeStrategy->createResumeStrategy());
+ m_strokeStrategy->notifyUserStartedStroke();
+
if(!m_initStrategy) {
m_strokeInitialized = true;
}
else {
enqueue(m_initStrategy.data(), m_strokeStrategy->createInitData());
}
}
KisStroke::~KisStroke()
{
Q_ASSERT(m_strokeEnded);
Q_ASSERT(m_jobsQueue.isEmpty());
}
bool KisStroke::supportsSuspension()
{
return !m_strokeInitialized || (m_suspendStrategy && m_resumeStrategy);
}
void KisStroke::suspendStroke(KisStrokeSP recipient)
{
if (!m_strokeInitialized || m_strokeSuspended ||
(m_strokeEnded && !hasJobs())) {
return;
}
KIS_ASSERT_RECOVER_NOOP(m_suspendStrategy && m_resumeStrategy);
prepend(m_resumeStrategy.data(),
m_strokeStrategy->createResumeData(),
worksOnLevelOfDetail(), false);
recipient->prepend(m_suspendStrategy.data(),
m_strokeStrategy->createSuspendData(),
worksOnLevelOfDetail(), false);
m_strokeSuspended = true;
}
void KisStroke::addJob(KisStrokeJobData *data)
{
Q_ASSERT(!m_strokeEnded || m_isCancelled);
enqueue(m_dabStrategy.data(), data);
}
+void KisStroke::addMutatedJobs(const QVector<KisStrokeJobData *> list)
+{
+ // factory methods can return null, if no action is needed
+ if (!m_dabStrategy) {
+ qDeleteAll(list);
+ return;
+ }
+
+ // Find first non-alien (non-suspend/non-resume) job
+ //
+ // Please note that this algorithm will stop working at the day we start
+ // adding alien jobs not to the beginning of the stroke, but to other places.
+ // Right now both suspend and resume jobs are added to the beginning of
+ // the stroke.
+
+ auto it = std::find_if(m_jobsQueue.begin(), m_jobsQueue.end(),
+ [] (KisStrokeJob *job) {
+ return job->isOwnJob();
+ });
+
+
+ Q_FOREACH (KisStrokeJobData *data, list) {
+ it = m_jobsQueue.insert(it, new KisStrokeJob(m_dabStrategy.data(), data, worksOnLevelOfDetail(), true));
+ ++it;
+ }
+}
+
KisStrokeJob* KisStroke::popOneJob()
{
KisStrokeJob *job = dequeue();
if(job) {
- m_prevJobSequential = job->isSequential() || job->isBarrier();
-
m_strokeInitialized = true;
m_strokeSuspended = false;
}
return job;
}
KUndo2MagicString KisStroke::name() const
{
return m_strokeStrategy->name();
}
bool KisStroke::hasJobs() const
{
return !m_jobsQueue.isEmpty();
}
qint32 KisStroke::numJobs() const
{
return m_jobsQueue.size();
}
void KisStroke::endStroke()
{
Q_ASSERT(!m_strokeEnded);
m_strokeEnded = true;
enqueue(m_finishStrategy.data(), m_strokeStrategy->createFinishData());
+ m_strokeStrategy->notifyUserEndedStroke();
}
/**
* About cancelling the stroke
* There may be four different states of the stroke, when cancel
* is requested:
* 1) Not initialized, has jobs -- just clear the queue
* 2) Initialized, has jobs, not finished -- clear the queue,
* enqueue the cancel job
* 5) Initialized, no jobs, not finished -- enqueue the cancel job
* 3) Initialized, has jobs, finished -- clear the queue, enqueue
* the cancel job
* 4) Initialized, no jobs, finished -- it's too late to cancel
* anything
* 6) Initialized, has jobs, cancelled -- cancelling twice is a permitted
* operation, though it does nothing
*/
void KisStroke::cancelStroke()
{
// case 6
if (m_isCancelled) return;
const bool effectivelyInitialized =
m_strokeInitialized || m_strokeStrategy->needsExplicitCancel();
if(!effectivelyInitialized) {
/**
* Lod0 stroke cannot be suspended and !initialized at the
* same time, because the suspend job is created iff the
* stroke has already done some meaningful work.
*
* At the same time, LodN stroke can be prepended with a
* 'suspend' job even when it has not been started yet. That
* is obvious: we should suspend the other stroke before doing
* anything else.
*/
KIS_ASSERT_RECOVER_NOOP(type() == LODN ||
sanityCheckAllJobsAreCancellable());
clearQueueOnCancel();
}
else if(effectivelyInitialized &&
(!m_jobsQueue.isEmpty() || !m_strokeEnded)) {
clearQueueOnCancel();
enqueue(m_cancelStrategy.data(),
m_strokeStrategy->createCancelData());
}
// else {
// too late ...
// }
m_isCancelled = true;
m_strokeEnded = true;
}
bool KisStroke::canCancel() const
{
return m_isCancelled || !m_strokeInitialized ||
!m_jobsQueue.isEmpty() || !m_strokeEnded;
}
bool KisStroke::sanityCheckAllJobsAreCancellable() const
{
Q_FOREACH (KisStrokeJob *item, m_jobsQueue) {
if (!item->isCancellable()) {
return false;
}
}
return true;
}
void KisStroke::clearQueueOnCancel()
{
QQueue<KisStrokeJob*>::iterator it = m_jobsQueue.begin();
while (it != m_jobsQueue.end()) {
if ((*it)->isCancellable()) {
delete (*it);
it = m_jobsQueue.erase(it);
} else {
++it;
}
}
}
bool KisStroke::isInitialized() const
{
return m_strokeInitialized;
}
bool KisStroke::isEnded() const
{
return m_strokeEnded;
}
bool KisStroke::isCancelled() const
{
return m_isCancelled;
}
bool KisStroke::isExclusive() const
{
return m_strokeStrategy->isExclusive();
}
bool KisStroke::supportsWrapAroundMode() const
{
return m_strokeStrategy->supportsWrapAroundMode();
}
int KisStroke::worksOnLevelOfDetail() const
{
return m_worksOnLevelOfDetail;
}
bool KisStroke::canForgetAboutMe() const
{
return m_strokeStrategy->canForgetAboutMe();
}
-bool KisStroke::prevJobSequential() const
+qreal KisStroke::balancingRatioOverride() const
{
- return m_prevJobSequential;
-}
-
-bool KisStroke::nextJobSequential() const
-{
- return !m_jobsQueue.isEmpty() ?
- m_jobsQueue.head()->isSequential() : false;
+ return m_strokeStrategy->balancingRatioOverride();
}
-bool KisStroke::nextJobBarrier() const
+KisStrokeJobData::Sequentiality KisStroke::nextJobSequentiality() const
{
return !m_jobsQueue.isEmpty() ?
- m_jobsQueue.head()->isBarrier() : false;
+ m_jobsQueue.head()->sequentiality() : KisStrokeJobData::SEQUENTIAL;
}
void KisStroke::enqueue(KisStrokeJobStrategy *strategy,
KisStrokeJobData *data)
{
// factory methods can return null, if no action is needed
if(!strategy) {
delete data;
return;
}
m_jobsQueue.enqueue(new KisStrokeJob(strategy, data, worksOnLevelOfDetail(), true));
}
void KisStroke::prepend(KisStrokeJobStrategy *strategy,
KisStrokeJobData *data,
int levelOfDetail,
- bool isCancellable)
+ bool isOwnJob)
{
// factory methods can return null, if no action is needed
if(!strategy) {
delete data;
return;
}
// LOG_MERGE_FIXME:
Q_UNUSED(levelOfDetail);
- m_jobsQueue.prepend(new KisStrokeJob(strategy, data, worksOnLevelOfDetail(), isCancellable));
+ m_jobsQueue.prepend(new KisStrokeJob(strategy, data, worksOnLevelOfDetail(), isOwnJob));
}
KisStrokeJob* KisStroke::dequeue()
{
return !m_jobsQueue.isEmpty() ? m_jobsQueue.dequeue() : 0;
}
void KisStroke::setLodBuddy(KisStrokeSP buddy)
{
m_lodBuddy = buddy;
}
KisStrokeSP KisStroke::lodBuddy() const
{
return m_lodBuddy;
}
KisStroke::Type KisStroke::type() const
{
if (m_type == LOD0) {
KIS_ASSERT_RECOVER_NOOP(m_lodBuddy && "LOD0 strokes must always have a buddy");
} else if (m_type == LODN) {
KIS_ASSERT_RECOVER_NOOP(m_worksOnLevelOfDetail > 0 && "LODN strokes must work on LOD > 0!");
} else if (m_type == LEGACY) {
KIS_ASSERT_RECOVER_NOOP(m_worksOnLevelOfDetail == 0 && "LEGACY strokes must work on LOD == 0!");
}
return m_type;
}
diff --git a/libs/image/kis_stroke.h b/libs/image/kis_stroke.h
index 196c9a6bda..9d740e1813 100644
--- a/libs/image/kis_stroke.h
+++ b/libs/image/kis_stroke.h
@@ -1,127 +1,125 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_STROKE_H
#define __KIS_STROKE_H
#include <QQueue>
#include <QScopedPointer>
#include <kis_types.h>
#include "kritaimage_export.h"
#include "kis_stroke_job.h"
class KisStrokeStrategy;
class KUndo2MagicString;
class KRITAIMAGE_EXPORT KisStroke
{
public:
enum Type {
LEGACY,
LOD0,
LODN,
SUSPEND,
RESUME
};
public:
KisStroke(KisStrokeStrategy *strokeStrategy, Type type = LEGACY, int levelOfDetail = 0);
~KisStroke();
void addJob(KisStrokeJobData *data);
+ void addMutatedJobs(const QVector<KisStrokeJobData *> list);
KUndo2MagicString name() const;
bool hasJobs() const;
qint32 numJobs() const;
KisStrokeJob* popOneJob();
void endStroke();
void cancelStroke();
bool canCancel() const;
bool supportsSuspension();
void suspendStroke(KisStrokeSP recipient);
bool isInitialized() const;
bool isEnded() const;
bool isCancelled() const;
bool isExclusive() const;
bool supportsWrapAroundMode() const;
int worksOnLevelOfDetail() const;
bool canForgetAboutMe() const;
+ qreal balancingRatioOverride() const;
- bool prevJobSequential() const;
- bool nextJobSequential() const;
-
- bool nextJobBarrier() const;
+ KisStrokeJobData::Sequentiality nextJobSequentiality() const;
void setLodBuddy(KisStrokeSP buddy);
KisStrokeSP lodBuddy() const;
Type type() const;
private:
void enqueue(KisStrokeJobStrategy *strategy,
KisStrokeJobData *data);
// for suspend/resume jobs
void prepend(KisStrokeJobStrategy *strategy,
KisStrokeJobData *data,
int levelOfDetail,
- bool isCancellable);
+ bool isOwnJob);
KisStrokeJob* dequeue();
void clearQueueOnCancel();
bool sanityCheckAllJobsAreCancellable() const;
private:
// for testing use only, do not use in real code
friend class KisStrokeTest;
friend class KisStrokeStrategyUndoCommandBasedTest;
QQueue<KisStrokeJob*>& testingGetQueue() {
return m_jobsQueue;
}
private:
// the strategies are owned by the stroke
QScopedPointer<KisStrokeStrategy> m_strokeStrategy;
QScopedPointer<KisStrokeJobStrategy> m_initStrategy;
QScopedPointer<KisStrokeJobStrategy> m_dabStrategy;
QScopedPointer<KisStrokeJobStrategy> m_cancelStrategy;
QScopedPointer<KisStrokeJobStrategy> m_finishStrategy;
QScopedPointer<KisStrokeJobStrategy> m_suspendStrategy;
QScopedPointer<KisStrokeJobStrategy> m_resumeStrategy;
QQueue<KisStrokeJob*> m_jobsQueue;
bool m_strokeInitialized;
bool m_strokeEnded;
bool m_strokeSuspended;
bool m_isCancelled; // cancelled strokes are always 'ended' as well
- bool m_prevJobSequential;
int m_worksOnLevelOfDetail;
Type m_type;
KisStrokeSP m_lodBuddy;
};
#endif /* __KIS_STROKE_H */
diff --git a/libs/image/kis_stroke_job.h b/libs/image/kis_stroke_job.h
index 35f28ea195..2a2b2adaa1 100644
--- a/libs/image/kis_stroke_job.h
+++ b/libs/image/kis_stroke_job.h
@@ -1,95 +1,103 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_STROKE_JOB_H
#define __KIS_STROKE_JOB_H
#include "kis_runnable.h"
#include "kis_stroke_job_strategy.h"
class KisStrokeJob : public KisRunnable
{
public:
KisStrokeJob(KisStrokeJobStrategy *strategy,
KisStrokeJobData *data,
int levelOfDetail,
- bool isCancellable)
+ bool isOwnJob)
: m_dabStrategy(strategy),
m_dabData(data),
m_levelOfDetail(levelOfDetail),
- m_isCancellable(isCancellable)
+ m_isOwnJob(isOwnJob)
{
}
~KisStrokeJob() override {
delete m_dabData;
}
void run() override {
m_dabStrategy->run(m_dabData);
}
+ KisStrokeJobData::Sequentiality sequentiality() const {
+ return m_dabData ? m_dabData->sequentiality() : KisStrokeJobData::SEQUENTIAL;
+ }
+
bool isSequential() const {
// Default value is 'SEQUENTIAL'
return m_dabData ? m_dabData->isSequential() : true;
}
bool isBarrier() const {
// Default value is simply 'SEQUENTIAL', *not* 'BARRIER'
return m_dabData ? m_dabData->isBarrier() : false;
}
bool isExclusive() const {
// Default value is 'NORMAL'
return m_dabData ? m_dabData->isExclusive() : false;
}
int levelOfDetail() const {
return m_levelOfDetail;
}
bool isCancellable() const {
- return m_isCancellable;
+ return m_isOwnJob;
+ }
+
+ bool isOwnJob() const {
+ return m_isOwnJob;
}
private:
// for testing use only, do not use in real code
friend QString getJobName(KisStrokeJob *job);
friend QString getCommandName(KisStrokeJob *job);
friend int cancelSeqNo(KisStrokeJob *job);
KisStrokeJobStrategy* testingGetDabStrategy() {
return m_dabStrategy;
}
KisStrokeJobData* testingGetDabData() {
return m_dabData;
}
private:
// Shared between different jobs
KisStrokeJobStrategy *m_dabStrategy;
// Owned by the job
KisStrokeJobData *m_dabData;
int m_levelOfDetail;
- bool m_isCancellable;
+ bool m_isOwnJob;
};
#endif /* __KIS_STROKE_JOB_H */
diff --git a/libs/image/kis_stroke_job_strategy.h b/libs/image/kis_stroke_job_strategy.h
index ddfcf5b34d..45bb6eecc5 100644
--- a/libs/image/kis_stroke_job_strategy.h
+++ b/libs/image/kis_stroke_job_strategy.h
@@ -1,74 +1,75 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_STROKE_JOB_STRATEGY_H
#define __KIS_STROKE_JOB_STRATEGY_H
#include "kritaimage_export.h"
class KRITAIMAGE_EXPORT KisStrokeJobData
{
public:
enum Sequentiality {
CONCURRENT,
SEQUENTIAL,
- BARRIER
+ BARRIER,
+ UNIQUELY_CONCURRENT
};
enum Exclusivity {
NORMAL,
EXCLUSIVE
};
public:
KisStrokeJobData(Sequentiality sequentiality = SEQUENTIAL,
Exclusivity exclusivity = NORMAL);
virtual ~KisStrokeJobData();
bool isBarrier() const;
bool isSequential() const;
bool isExclusive() const;
- Sequentiality sequentiality() { return m_sequentiality; };
- Exclusivity exclusivity() { return m_exclusivity; };
+ Sequentiality sequentiality() { return m_sequentiality; }
+ Exclusivity exclusivity() { return m_exclusivity; }
virtual KisStrokeJobData* createLodClone(int levelOfDetail);
protected:
KisStrokeJobData(const KisStrokeJobData &rhs);
private:
Sequentiality m_sequentiality;
Exclusivity m_exclusivity;
};
class KRITAIMAGE_EXPORT KisStrokeJobStrategy
{
public:
KisStrokeJobStrategy();
virtual ~KisStrokeJobStrategy();
virtual void run(KisStrokeJobData *data) = 0;
private:
};
#endif /* __KIS_STROKE_JOB_STRATEGY_H */
diff --git a/libs/image/kis_stroke_strategy.cpp b/libs/image/kis_stroke_strategy.cpp
index cbb6faad9c..f79b45d31b 100644
--- a/libs/image/kis_stroke_strategy.cpp
+++ b/libs/image/kis_stroke_strategy.cpp
@@ -1,208 +1,250 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_stroke_strategy.h"
#include <KoCompositeOpRegistry.h>
#include "kis_stroke_job_strategy.h"
+#include "KisStrokesQueueMutatedJobInterface.h"
KisStrokeStrategy::KisStrokeStrategy(QString id, const KUndo2MagicString &name)
: m_exclusive(false),
m_supportsWrapAroundMode(false),
m_needsIndirectPainting(false),
m_indirectPaintingCompositeOp(COMPOSITE_ALPHA_DARKEN),
m_clearsRedoOnStart(true),
m_requestsOtherStrokesToEnd(true),
m_canForgetAboutMe(false),
m_needsExplicitCancel(false),
+ m_balancingRatioOverride(-1.0),
m_id(id),
- m_name(name)
+ m_name(name),
+ m_mutatedJobsInterface(0)
{
}
KisStrokeStrategy::KisStrokeStrategy(const KisStrokeStrategy &rhs)
: m_exclusive(rhs.m_exclusive),
m_supportsWrapAroundMode(rhs.m_supportsWrapAroundMode),
m_needsIndirectPainting(rhs.m_needsIndirectPainting),
m_indirectPaintingCompositeOp(rhs.m_indirectPaintingCompositeOp),
m_clearsRedoOnStart(rhs.m_clearsRedoOnStart),
m_requestsOtherStrokesToEnd(rhs.m_requestsOtherStrokesToEnd),
m_canForgetAboutMe(rhs.m_canForgetAboutMe),
m_needsExplicitCancel(rhs.m_needsExplicitCancel),
+ m_balancingRatioOverride(rhs.m_balancingRatioOverride),
m_id(rhs.m_id),
- m_name(rhs.m_name)
+ m_name(rhs.m_name),
+ m_mutatedJobsInterface(0)
{
- KIS_ASSERT_RECOVER_NOOP(!rhs.m_cancelStrokeId &&
+ KIS_ASSERT_RECOVER_NOOP(!rhs.m_cancelStrokeId && !m_mutatedJobsInterface &&
"After the stroke has been started, no copying must happen");
}
KisStrokeStrategy::~KisStrokeStrategy()
{
}
+void KisStrokeStrategy::notifyUserStartedStroke()
+{
+}
+
+void KisStrokeStrategy::notifyUserEndedStroke()
+{
+}
KisStrokeJobStrategy* KisStrokeStrategy::createInitStrategy()
{
return 0;
}
KisStrokeJobStrategy* KisStrokeStrategy::createFinishStrategy()
{
return 0;
}
KisStrokeJobStrategy* KisStrokeStrategy::createCancelStrategy()
{
return 0;
}
KisStrokeJobStrategy* KisStrokeStrategy::createDabStrategy()
{
return 0;
}
KisStrokeJobStrategy* KisStrokeStrategy::createSuspendStrategy()
{
return 0;
}
KisStrokeJobStrategy* KisStrokeStrategy::createResumeStrategy()
{
return 0;
}
KisStrokeJobData* KisStrokeStrategy::createInitData()
{
return 0;
}
KisStrokeJobData* KisStrokeStrategy::createFinishData()
{
return 0;
}
KisStrokeJobData* KisStrokeStrategy::createCancelData()
{
return 0;
}
KisStrokeJobData* KisStrokeStrategy::createSuspendData()
{
return 0;
}
KisStrokeJobData* KisStrokeStrategy::createResumeData()
{
return 0;
}
KisStrokeStrategy* KisStrokeStrategy::createLodClone(int levelOfDetail)
{
Q_UNUSED(levelOfDetail);
return 0;
}
bool KisStrokeStrategy::isExclusive() const
{
return m_exclusive;
}
bool KisStrokeStrategy::supportsWrapAroundMode() const
{
return m_supportsWrapAroundMode;
}
bool KisStrokeStrategy::needsIndirectPainting() const
{
return m_needsIndirectPainting;
}
QString KisStrokeStrategy::indirectPaintingCompositeOp() const
{
return m_indirectPaintingCompositeOp;
}
QString KisStrokeStrategy::id() const
{
return m_id;
}
KUndo2MagicString KisStrokeStrategy::name() const
{
return m_name;
}
+void KisStrokeStrategy::setMutatedJobsInterface(KisStrokesQueueMutatedJobInterface *mutatedJobsInterface)
+{
+ m_mutatedJobsInterface = mutatedJobsInterface;
+}
+
+void KisStrokeStrategy::addMutatedJobs(const QVector<KisStrokeJobData *> list)
+{
+ KIS_SAFE_ASSERT_RECOVER(m_mutatedJobsInterface && m_cancelStrokeId) {
+ qDeleteAll(list);
+ return;
+ }
+
+ m_mutatedJobsInterface->addMutatedJobs(m_cancelStrokeId, list);
+}
+
+void KisStrokeStrategy::addMutatedJob(KisStrokeJobData *data)
+{
+ addMutatedJobs({data});
+}
+
void KisStrokeStrategy::setExclusive(bool value)
{
m_exclusive = value;
}
void KisStrokeStrategy::setSupportsWrapAroundMode(bool value)
{
m_supportsWrapAroundMode = value;
}
void KisStrokeStrategy::setNeedsIndirectPainting(bool value)
{
m_needsIndirectPainting = value;
}
void KisStrokeStrategy::setIndirectPaintingCompositeOp(const QString &id)
{
m_indirectPaintingCompositeOp = id;
}
bool KisStrokeStrategy::clearsRedoOnStart() const
{
return m_clearsRedoOnStart;
}
void KisStrokeStrategy::setClearsRedoOnStart(bool value)
{
m_clearsRedoOnStart = value;
}
bool KisStrokeStrategy::requestsOtherStrokesToEnd() const
{
return m_requestsOtherStrokesToEnd;
}
void KisStrokeStrategy::setRequestsOtherStrokesToEnd(bool value)
{
m_requestsOtherStrokesToEnd = value;
}
bool KisStrokeStrategy::canForgetAboutMe() const
{
return m_canForgetAboutMe;
}
void KisStrokeStrategy::setCanForgetAboutMe(bool value)
{
m_canForgetAboutMe = value;
}
bool KisStrokeStrategy::needsExplicitCancel() const
{
return m_needsExplicitCancel;
}
void KisStrokeStrategy::setNeedsExplicitCancel(bool value)
{
m_needsExplicitCancel = value;
}
+
+qreal KisStrokeStrategy::balancingRatioOverride() const
+{
+ return m_balancingRatioOverride;
+}
+
+void KisStrokeStrategy::setBalancingRatioOverride(qreal value)
+{
+ m_balancingRatioOverride = value;
+}
diff --git a/libs/image/kis_stroke_strategy.h b/libs/image/kis_stroke_strategy.h
index d38c94ed3b..34e48086e5 100644
--- a/libs/image/kis_stroke_strategy.h
+++ b/libs/image/kis_stroke_strategy.h
@@ -1,139 +1,213 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_STROKE_STRATEGY_H
#define __KIS_STROKE_STRATEGY_H
#include <QString>
#include "kis_types.h"
#include "kundo2magicstring.h"
#include "kritaimage_export.h"
+
class KisStrokeJobStrategy;
class KisStrokeJobData;
+class KisStrokesQueueMutatedJobInterface;
class KRITAIMAGE_EXPORT KisStrokeStrategy
{
public:
KisStrokeStrategy(QString id = QString(), const KUndo2MagicString &name = KUndo2MagicString());
virtual ~KisStrokeStrategy();
+ /**
+ * notifyUserStartedStroke() is a callback used by the strokes system to notify
+ * when the user adds the stroke to the strokes queue. That moment corresponds
+ * to the user calling strokesFacade->startStroke(strategy) and might happen much
+ * earlier than the first job being executed.
+ *
+ * NOTE: this method will be executed in the context of the GUI thread!
+ */
+ virtual void notifyUserStartedStroke();
+
+ /**
+ * notifyUserEndedStroke() is a callback used by the strokes system to notify
+ * when the user ends the stroke. That moment corresponds to the user calling
+ * strokesFacade->endStroke(id) and might happen much earlier when the stroke
+ * even started its execution.
+ *
+ * NOTE: this method will be executed in the context of the GUI thread!
+ */
+ virtual void notifyUserEndedStroke();
+
virtual KisStrokeJobStrategy* createInitStrategy();
virtual KisStrokeJobStrategy* createFinishStrategy();
virtual KisStrokeJobStrategy* createCancelStrategy();
virtual KisStrokeJobStrategy* createDabStrategy();
virtual KisStrokeJobStrategy* createSuspendStrategy();
virtual KisStrokeJobStrategy* createResumeStrategy();
virtual KisStrokeJobData* createInitData();
virtual KisStrokeJobData* createFinishData();
virtual KisStrokeJobData* createCancelData();
virtual KisStrokeJobData* createSuspendData();
virtual KisStrokeJobData* createResumeData();
virtual KisStrokeStrategy* createLodClone(int levelOfDetail);
bool isExclusive() const;
bool supportsWrapAroundMode() const;
bool needsIndirectPainting() const;
QString indirectPaintingCompositeOp() const;
/**
* Returns true if mere start of the stroke should cancel all the
* pending redo tasks.
*
* This method should return true in almost all circumstances
* except if we are running an undo or redo stroke.
*/
bool clearsRedoOnStart() const;
/**
* Returns true if the other currently running strokes should be
* politely asked to exit. The default value is 'true'.
*
* The only known exception right now is
* KisRegenerateFrameStrokeStrategy which does not requests ending
* of any actions, since it performs purely background action.
*/
bool requestsOtherStrokesToEnd() const;
/**
* Returns true if the update scheduler can cancel this stroke
* when some other stroke is going to be started. This makes the
* "forgettable" stroke very low priority.
*
* Default is 'false'.
*/
bool canForgetAboutMe() const;
bool needsExplicitCancel() const;
+ /**
+ * \see setBalancingRatioOverride() for details
+ */
+ qreal balancingRatioOverride() const;
+
QString id() const;
KUndo2MagicString name() const;
/**
* Set up by the strokes queue during the stroke initialization
*/
void setCancelStrokeId(KisStrokeId id) { m_cancelStrokeId = id; }
+ void setMutatedJobsInterface(KisStrokesQueueMutatedJobInterface *mutatedJobsInterface);
+
protected:
+ // testing surrogate class
+ friend class KisMutatableDabStrategy;
+
/**
* The cancel job may populate the stroke with some new jobs
* for cancelling. To achieve this it needs the stroke id.
*
* WARNING: you can't add new jobs in any places other than
* cancel job, because the stroke may be ended in any moment
* by the user and the sequence of jobs will be broken
*/
KisStrokeId cancelStrokeId() { return m_cancelStrokeId; }
+ /**
+ * This function is supposed to be called by internal asynchronous
+ * jobs. It allows adding subtasks that may be executed concurrently.
+ *
+ * Requirements:
+ * * must be called *only* from within the context of the strokes
+ * worker thread during exectution one of its jobs
+ *
+ * Guarantees:
+ * * the added job is guaranteed to be executed in some time after
+ * the currently executed job, *before* the next SEQUENTIAL or
+ * BARRIER job
+ * * if the currently executed job is CUNCURRENTthe mutated job *may*
+ * start execution right after adding to the queue without waiting for
+ * its parent to complete. Though this behavior is *not* guaranteed,
+ * because addMutatedJob does not initiate processQueues(), because
+ * it may lead to a deadlock.
+ */
+ void addMutatedJobs(const QVector<KisStrokeJobData*> list);
+
+ /**
+ * Convenience override for addMutatedJobs()
+ */
+ void addMutatedJob(KisStrokeJobData *data);
+
+
// you are not supposed to change these parameters
// after the KisStroke object has been created
void setExclusive(bool value);
void setSupportsWrapAroundMode(bool value);
void setNeedsIndirectPainting(bool value);
void setIndirectPaintingCompositeOp(const QString &id);
void setClearsRedoOnStart(bool value);
void setRequestsOtherStrokesToEnd(bool value);
void setCanForgetAboutMe(bool value);
void setNeedsExplicitCancel(bool value);
+ /**
+ * Set override for the desired scheduler balancing ratio:
+ *
+ * ratio = stroke jobs / update jobs
+ *
+ * If \p value < 1.0, then the priority is given to updates, if
+ * the value is higher than 1.0, then the priority is given
+ * to stroke jobs.
+ *
+ * Special value -1.0, suggests the scheduler to use the default value
+ * set by the user's config file (which is 100.0 by default).
+ */
+ void setBalancingRatioOverride(qreal value);
+
protected:
/**
* Protected c-tor, used for cloning of hi-level strategies
*/
KisStrokeStrategy(const KisStrokeStrategy &rhs);
private:
bool m_exclusive;
bool m_supportsWrapAroundMode;
bool m_needsIndirectPainting;
QString m_indirectPaintingCompositeOp;
bool m_clearsRedoOnStart;
bool m_requestsOtherStrokesToEnd;
bool m_canForgetAboutMe;
bool m_needsExplicitCancel;
+ qreal m_balancingRatioOverride;
QString m_id;
KUndo2MagicString m_name;
KisStrokeId m_cancelStrokeId;
+ KisStrokesQueueMutatedJobInterface *m_mutatedJobsInterface;
};
#endif /* __KIS_STROKE_STRATEGY_H */
diff --git a/libs/image/kis_strokes_queue.cpp b/libs/image/kis_strokes_queue.cpp
index f3e4f629a3..a916b78472 100644
--- a/libs/image/kis_strokes_queue.cpp
+++ b/libs/image/kis_strokes_queue.cpp
@@ -1,795 +1,834 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_strokes_queue.h"
#include <QQueue>
#include <QMutex>
#include <QMutexLocker>
#include "kis_stroke.h"
#include "kis_updater_context.h"
#include "kis_stroke_job_strategy.h"
#include "kis_stroke_strategy.h"
#include "kis_undo_stores.h"
#include "kis_post_execution_undo_adapter.h"
typedef QQueue<KisStrokeSP> StrokesQueue;
typedef QQueue<KisStrokeSP>::iterator StrokesQueueIterator;
#include "kis_image_interfaces.h"
class KisStrokesQueue::LodNUndoStrokesFacade : public KisStrokesFacade
{
public:
LodNUndoStrokesFacade(KisStrokesQueue *_q) : q(_q) {}
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override {
return q->startLodNUndoStroke(strokeStrategy);
}
void addJob(KisStrokeId id, KisStrokeJobData *data) override {
KisStrokeSP stroke = id.toStrongRef();
KIS_SAFE_ASSERT_RECOVER_NOOP(stroke);
KIS_SAFE_ASSERT_RECOVER_NOOP(!stroke->lodBuddy());
KIS_SAFE_ASSERT_RECOVER_NOOP(stroke->type() == KisStroke::LODN);
q->addJob(id, data);
}
void endStroke(KisStrokeId id) override {
KisStrokeSP stroke = id.toStrongRef();
KIS_SAFE_ASSERT_RECOVER_NOOP(stroke);
KIS_SAFE_ASSERT_RECOVER_NOOP(!stroke->lodBuddy());
KIS_SAFE_ASSERT_RECOVER_NOOP(stroke->type() == KisStroke::LODN);
q->endStroke(id);
}
bool cancelStroke(KisStrokeId id) override {
Q_UNUSED(id);
qFatal("Not implemented");
return false;
}
private:
KisStrokesQueue *q;
};
struct Q_DECL_HIDDEN KisStrokesQueue::Private {
Private(KisStrokesQueue *_q)
: q(_q),
openedStrokesCounter(0),
needsExclusiveAccess(false),
wrapAroundModeSupported(false),
+ balancingRatioOverride(-1.0),
currentStrokeLoaded(false),
lodNNeedsSynchronization(true),
desiredLevelOfDetail(0),
nextDesiredLevelOfDetail(0),
lodNStrokesFacade(_q),
lodNPostExecutionUndoAdapter(&lodNUndoStore, &lodNStrokesFacade) {}
KisStrokesQueue *q;
StrokesQueue strokesQueue;
int openedStrokesCounter;
bool needsExclusiveAccess;
bool wrapAroundModeSupported;
+ qreal balancingRatioOverride;
bool currentStrokeLoaded;
bool lodNNeedsSynchronization;
int desiredLevelOfDetail;
int nextDesiredLevelOfDetail;
QMutex mutex;
KisLodSyncStrokeStrategyFactory lod0ToNStrokeStrategyFactory;
KisSuspendResumeStrategyFactory suspendUpdatesStrokeStrategyFactory;
KisSuspendResumeStrategyFactory resumeUpdatesStrokeStrategyFactory;
KisSurrogateUndoStore lodNUndoStore;
LodNUndoStrokesFacade lodNStrokesFacade;
KisPostExecutionUndoAdapter lodNPostExecutionUndoAdapter;
void cancelForgettableStrokes();
void startLod0ToNStroke(int levelOfDetail, bool forgettable);
bool canUseLodN() const;
StrokesQueueIterator findNewLod0Pos();
StrokesQueueIterator findNewLodNPos(KisStrokeSP lodN);
bool shouldWrapInSuspendUpdatesStroke() const;
void switchDesiredLevelOfDetail(bool forced);
bool hasUnfinishedStrokes() const;
void tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke);
};
KisStrokesQueue::KisStrokesQueue()
: m_d(new Private(this))
{
}
KisStrokesQueue::~KisStrokesQueue()
{
Q_FOREACH (KisStrokeSP stroke, m_d->strokesQueue) {
stroke->cancelStroke();
}
delete m_d;
}
template <class StrokePair, class StrokesQueue>
typename StrokesQueue::iterator
-executeStrokePair(const StrokePair &pair, StrokesQueue &queue, typename StrokesQueue::iterator it, KisStroke::Type type, int levelOfDetail) {
+executeStrokePair(const StrokePair &pair, StrokesQueue &queue, typename StrokesQueue::iterator it, KisStroke::Type type, int levelOfDetail, KisStrokesQueueMutatedJobInterface *mutatedJobsInterface) {
KisStrokeStrategy *strategy = pair.first;
QList<KisStrokeJobData*> jobsData = pair.second;
KisStrokeSP stroke(new KisStroke(strategy, type, levelOfDetail));
strategy->setCancelStrokeId(stroke);
+ strategy->setMutatedJobsInterface(mutatedJobsInterface);
it = queue.insert(it, stroke);
Q_FOREACH (KisStrokeJobData *jobData, jobsData) {
stroke->addJob(jobData);
}
stroke->endStroke();
return it;
}
void KisStrokesQueue::Private::startLod0ToNStroke(int levelOfDetail, bool forgettable)
{
// precondition: lock held!
// precondition: lod > 0
KIS_ASSERT_RECOVER_RETURN(levelOfDetail);
if (!this->lod0ToNStrokeStrategyFactory) return;
KisLodSyncPair syncPair = this->lod0ToNStrokeStrategyFactory(forgettable);
- executeStrokePair(syncPair, this->strokesQueue, this->strokesQueue.end(), KisStroke::LODN, levelOfDetail);
+ executeStrokePair(syncPair, this->strokesQueue, this->strokesQueue.end(), KisStroke::LODN, levelOfDetail, q);
this->lodNNeedsSynchronization = false;
}
void KisStrokesQueue::Private::cancelForgettableStrokes()
{
if (!strokesQueue.isEmpty() && !hasUnfinishedStrokes()) {
Q_FOREACH (KisStrokeSP stroke, strokesQueue) {
KIS_ASSERT_RECOVER_NOOP(stroke->isEnded());
if (stroke->canForgetAboutMe()) {
stroke->cancelStroke();
}
}
}
}
bool KisStrokesQueue::Private::canUseLodN() const
{
Q_FOREACH (KisStrokeSP stroke, strokesQueue) {
if (stroke->type() == KisStroke::LEGACY) {
return false;
}
}
return true;
}
bool KisStrokesQueue::Private::shouldWrapInSuspendUpdatesStroke() const
{
Q_FOREACH (KisStrokeSP stroke, strokesQueue) {
if (stroke->isCancelled()) continue;
if (stroke->type() == KisStroke::RESUME) {
/**
* Resuming process is long-running and consists of
* multiple actions, therefore, if it has already started,
* we cannot use it to guard our new stroke, so just skip it.
* see https://phabricator.kde.org/T2542
*/
if (stroke->isInitialized()) continue;
return false;
}
}
return true;
}
StrokesQueueIterator KisStrokesQueue::Private::findNewLod0Pos()
{
StrokesQueueIterator it = strokesQueue.begin();
StrokesQueueIterator end = strokesQueue.end();
for (; it != end; ++it) {
if ((*it)->isCancelled()) continue;
if ((*it)->type() == KisStroke::RESUME) {
// \see a comment in shouldWrapInSuspendUpdatesStroke()
if ((*it)->isInitialized()) continue;
return it;
}
}
return it;
}
StrokesQueueIterator KisStrokesQueue::Private::findNewLodNPos(KisStrokeSP lodN)
{
StrokesQueueIterator it = strokesQueue.begin();
StrokesQueueIterator end = strokesQueue.end();
for (; it != end; ++it) {
if ((*it)->isCancelled()) continue;
if (((*it)->type() == KisStroke::SUSPEND ||
(*it)->type() == KisStroke::RESUME) &&
(*it)->isInitialized()) {
// \see a comment in shouldWrapInSuspendUpdatesStroke()
continue;
}
if ((*it)->type() == KisStroke::LOD0 ||
(*it)->type() == KisStroke::SUSPEND ||
(*it)->type() == KisStroke::RESUME) {
if (it != end && it == strokesQueue.begin()) {
KisStrokeSP head = *it;
if (head->supportsSuspension()) {
head->suspendStroke(lodN);
}
}
return it;
}
}
return it;
}
KisStrokeId KisStrokesQueue::startLodNUndoStroke(KisStrokeStrategy *strokeStrategy)
{
QMutexLocker locker(&m_d->mutex);
KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->lodNNeedsSynchronization);
KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->desiredLevelOfDetail > 0);
KisStrokeSP buddy(new KisStroke(strokeStrategy, KisStroke::LODN, m_d->desiredLevelOfDetail));
strokeStrategy->setCancelStrokeId(buddy);
+ strokeStrategy->setMutatedJobsInterface(this);
m_d->strokesQueue.insert(m_d->findNewLodNPos(buddy), buddy);
KisStrokeId id(buddy);
m_d->openedStrokesCounter++;
return id;
}
KisStrokeId KisStrokesQueue::startStroke(KisStrokeStrategy *strokeStrategy)
{
QMutexLocker locker(&m_d->mutex);
KisStrokeSP stroke;
KisStrokeStrategy* lodBuddyStrategy;
m_d->cancelForgettableStrokes();
if (m_d->desiredLevelOfDetail &&
m_d->canUseLodN() &&
(lodBuddyStrategy =
strokeStrategy->createLodClone(m_d->desiredLevelOfDetail))) {
if (m_d->lodNNeedsSynchronization) {
m_d->startLod0ToNStroke(m_d->desiredLevelOfDetail, false);
}
stroke = KisStrokeSP(new KisStroke(strokeStrategy, KisStroke::LOD0, 0));
KisStrokeSP buddy(new KisStroke(lodBuddyStrategy, KisStroke::LODN, m_d->desiredLevelOfDetail));
lodBuddyStrategy->setCancelStrokeId(buddy);
+ lodBuddyStrategy->setMutatedJobsInterface(this);
stroke->setLodBuddy(buddy);
m_d->strokesQueue.insert(m_d->findNewLodNPos(buddy), buddy);
if (m_d->shouldWrapInSuspendUpdatesStroke()) {
KisSuspendResumePair suspendPair = m_d->suspendUpdatesStrokeStrategyFactory();
KisSuspendResumePair resumePair = m_d->resumeUpdatesStrokeStrategyFactory();
StrokesQueueIterator it = m_d->findNewLod0Pos();
- it = executeStrokePair(resumePair, m_d->strokesQueue, it, KisStroke::RESUME, 0);
+ it = executeStrokePair(resumePair, m_d->strokesQueue, it, KisStroke::RESUME, 0, this);
it = m_d->strokesQueue.insert(it, stroke);
- it = executeStrokePair(suspendPair, m_d->strokesQueue, it, KisStroke::SUSPEND, 0);
+ it = executeStrokePair(suspendPair, m_d->strokesQueue, it, KisStroke::SUSPEND, 0, this);
} else {
m_d->strokesQueue.insert(m_d->findNewLod0Pos(), stroke);
}
} else {
stroke = KisStrokeSP(new KisStroke(strokeStrategy, KisStroke::LEGACY, 0));
m_d->strokesQueue.enqueue(stroke);
}
KisStrokeId id(stroke);
strokeStrategy->setCancelStrokeId(id);
+ strokeStrategy->setMutatedJobsInterface(this);
m_d->openedStrokesCounter++;
if (stroke->type() == KisStroke::LEGACY) {
m_d->lodNNeedsSynchronization = true;
}
return id;
}
void KisStrokesQueue::addJob(KisStrokeId id, KisStrokeJobData *data)
{
QMutexLocker locker(&m_d->mutex);
KisStrokeSP stroke = id.toStrongRef();
Q_ASSERT(stroke);
KisStrokeSP buddy = stroke->lodBuddy();
if (buddy) {
KisStrokeJobData *clonedData =
data->createLodClone(buddy->worksOnLevelOfDetail());
KIS_ASSERT_RECOVER_RETURN(clonedData);
buddy->addJob(clonedData);
}
stroke->addJob(data);
}
+void KisStrokesQueue::addMutatedJobs(KisStrokeId id, const QVector<KisStrokeJobData *> list)
+{
+ QMutexLocker locker(&m_d->mutex);
+
+ KisStrokeSP stroke = id.toStrongRef();
+ Q_ASSERT(stroke);
+
+ stroke->addMutatedJobs(list);
+}
+
void KisStrokesQueue::endStroke(KisStrokeId id)
{
QMutexLocker locker(&m_d->mutex);
KisStrokeSP stroke = id.toStrongRef();
Q_ASSERT(stroke);
stroke->endStroke();
m_d->openedStrokesCounter--;
KisStrokeSP buddy = stroke->lodBuddy();
if (buddy) {
buddy->endStroke();
}
}
bool KisStrokesQueue::cancelStroke(KisStrokeId id)
{
QMutexLocker locker(&m_d->mutex);
KisStrokeSP stroke = id.toStrongRef();
if(stroke) {
stroke->cancelStroke();
m_d->openedStrokesCounter--;
KisStrokeSP buddy = stroke->lodBuddy();
if (buddy) {
buddy->cancelStroke();
}
}
return stroke;
}
bool KisStrokesQueue::Private::hasUnfinishedStrokes() const
{
Q_FOREACH (KisStrokeSP stroke, strokesQueue) {
if (!stroke->isEnded()) {
return true;
}
}
return false;
}
bool KisStrokesQueue::tryCancelCurrentStrokeAsync()
{
bool anythingCanceled = false;
QMutexLocker locker(&m_d->mutex);
/**
* We cancel only ended strokes. This is done to avoid
* handling dangling pointers problem (KisStrokeId). The owner
* of a stroke will cancel the stroke itself if needed.
*/
if (!m_d->strokesQueue.isEmpty() &&
!m_d->hasUnfinishedStrokes()) {
anythingCanceled = true;
Q_FOREACH (KisStrokeSP currentStroke, m_d->strokesQueue) {
KIS_ASSERT_RECOVER_NOOP(currentStroke->isEnded());
currentStroke->cancelStroke();
// we shouldn't cancel buddies...
if (currentStroke->type() == KisStroke::LOD0) {
/**
* If the buddy has already finished, we cannot undo it because
* it doesn't store any undo data. Therefore we just regenerate
* the LOD caches.
*/
m_d->lodNNeedsSynchronization = true;
}
}
}
/**
* NOTE: We do not touch the openedStrokesCounter here since
* we work with closed id's only here
*/
return anythingCanceled;
}
UndoResult KisStrokesQueue::tryUndoLastStrokeAsync()
{
UndoResult result = UNDO_FAIL;
QMutexLocker locker(&m_d->mutex);
std::reverse_iterator<StrokesQueue::ConstIterator> it(m_d->strokesQueue.constEnd());
std::reverse_iterator<StrokesQueue::ConstIterator> end(m_d->strokesQueue.constBegin());
KisStrokeSP lastStroke;
KisStrokeSP lastBuddy;
bool buddyFound = false;
for (; it != end; ++it) {
if ((*it)->type() == KisStroke::LEGACY) {
break;
}
if (!lastStroke && (*it)->type() == KisStroke::LOD0 && !(*it)->isCancelled()) {
lastStroke = *it;
lastBuddy = lastStroke->lodBuddy();
KIS_SAFE_ASSERT_RECOVER(lastBuddy) {
lastStroke.clear();
lastBuddy.clear();
break;
}
}
KIS_SAFE_ASSERT_RECOVER(!lastStroke || *it == lastBuddy || (*it)->type() != KisStroke::LODN) {
lastStroke.clear();
lastBuddy.clear();
break;
}
if (lastStroke && *it == lastBuddy) {
KIS_SAFE_ASSERT_RECOVER(lastBuddy->type() == KisStroke::LODN) {
lastStroke.clear();
lastBuddy.clear();
break;
}
buddyFound = true;
break;
}
}
if (!lastStroke) return UNDO_FAIL;
if (!lastStroke->isEnded()) return UNDO_FAIL;
if (lastStroke->isCancelled()) return UNDO_FAIL;
KIS_SAFE_ASSERT_RECOVER_NOOP(!buddyFound ||
lastStroke->isCancelled() == lastBuddy->isCancelled());
KIS_SAFE_ASSERT_RECOVER_NOOP(lastBuddy->isEnded());
if (!lastStroke->canCancel()) {
return UNDO_WAIT;
}
lastStroke->cancelStroke();
if (buddyFound && lastBuddy->canCancel()) {
lastBuddy->cancelStroke();
} else {
// TODO: assert that checks that there is no other lodn strokes
locker.unlock();
m_d->lodNUndoStore.undo();
m_d->lodNUndoStore.purgeRedoState();
locker.relock();
}
result = UNDO_OK;
return result;
}
void KisStrokesQueue::Private::tryClearUndoOnStrokeCompletion(KisStrokeSP finishingStroke)
{
if (finishingStroke->type() != KisStroke::RESUME) return;
bool hasResumeStrokes = false;
bool hasLod0Strokes = false;
Q_FOREACH (KisStrokeSP stroke, strokesQueue) {
if (stroke == finishingStroke) continue;
hasLod0Strokes |= stroke->type() == KisStroke::LOD0;
hasResumeStrokes |= stroke->type() == KisStroke::RESUME;
}
KIS_SAFE_ASSERT_RECOVER_NOOP(!hasLod0Strokes || hasResumeStrokes);
if (!hasResumeStrokes && !hasLod0Strokes) {
lodNUndoStore.clear();
}
}
void KisStrokesQueue::processQueue(KisUpdaterContext &updaterContext,
bool externalJobsPending)
{
updaterContext.lock();
m_d->mutex.lock();
while(updaterContext.hasSpareThread() &&
processOneJob(updaterContext,
externalJobsPending));
m_d->mutex.unlock();
updaterContext.unlock();
}
bool KisStrokesQueue::needsExclusiveAccess() const
{
return m_d->needsExclusiveAccess;
}
bool KisStrokesQueue::wrapAroundModeSupported() const
{
return m_d->wrapAroundModeSupported;
}
+qreal KisStrokesQueue::balancingRatioOverride() const
+{
+ return m_d->balancingRatioOverride;
+}
+
bool KisStrokesQueue::isEmpty() const
{
QMutexLocker locker(&m_d->mutex);
return m_d->strokesQueue.isEmpty();
}
qint32 KisStrokesQueue::sizeMetric() const
{
QMutexLocker locker(&m_d->mutex);
if(m_d->strokesQueue.isEmpty()) return 0;
// just a rough approximation
return qMax(1, m_d->strokesQueue.head()->numJobs()) * m_d->strokesQueue.size();
}
void KisStrokesQueue::Private::switchDesiredLevelOfDetail(bool forced)
{
if (forced || nextDesiredLevelOfDetail != desiredLevelOfDetail) {
Q_FOREACH (KisStrokeSP stroke, strokesQueue) {
if (stroke->type() != KisStroke::LEGACY)
return;
}
const bool forgettable =
forced && !lodNNeedsSynchronization &&
desiredLevelOfDetail == nextDesiredLevelOfDetail;
desiredLevelOfDetail = nextDesiredLevelOfDetail;
lodNNeedsSynchronization |= !forgettable;
if (desiredLevelOfDetail) {
startLod0ToNStroke(desiredLevelOfDetail, forgettable);
}
}
}
void KisStrokesQueue::explicitRegenerateLevelOfDetail()
{
QMutexLocker locker(&m_d->mutex);
m_d->switchDesiredLevelOfDetail(true);
}
void KisStrokesQueue::setDesiredLevelOfDetail(int lod)
{
QMutexLocker locker(&m_d->mutex);
if (lod == m_d->nextDesiredLevelOfDetail) return;
m_d->nextDesiredLevelOfDetail = lod;
m_d->switchDesiredLevelOfDetail(false);
}
void KisStrokesQueue::notifyUFOChangedImage()
{
QMutexLocker locker(&m_d->mutex);
m_d->lodNNeedsSynchronization = true;
}
void KisStrokesQueue::debugDumpAllStrokes()
{
QMutexLocker locker(&m_d->mutex);
qDebug() <<"===";
Q_FOREACH (KisStrokeSP stroke, m_d->strokesQueue) {
qDebug() << ppVar(stroke->name()) << ppVar(stroke->type()) << ppVar(stroke->numJobs()) << ppVar(stroke->isInitialized()) << ppVar(stroke->isCancelled());
}
qDebug() <<"===";
}
void KisStrokesQueue::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory)
{
m_d->lod0ToNStrokeStrategyFactory = factory;
}
void KisStrokesQueue::setSuspendUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory)
{
m_d->suspendUpdatesStrokeStrategyFactory = factory;
}
void KisStrokesQueue::setResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory)
{
m_d->resumeUpdatesStrokeStrategyFactory = factory;
}
KisPostExecutionUndoAdapter *KisStrokesQueue::lodNPostExecutionUndoAdapter() const
{
return &m_d->lodNPostExecutionUndoAdapter;
}
KUndo2MagicString KisStrokesQueue::currentStrokeName() const
{
QMutexLocker locker(&m_d->mutex);
if(m_d->strokesQueue.isEmpty()) return KUndo2MagicString();
return m_d->strokesQueue.head()->name();
}
bool KisStrokesQueue::hasOpenedStrokes() const
{
QMutexLocker locker(&m_d->mutex);
return m_d->openedStrokesCounter;
}
bool KisStrokesQueue::processOneJob(KisUpdaterContext &updaterContext,
bool externalJobsPending)
{
if(m_d->strokesQueue.isEmpty()) return false;
bool result = false;
- qint32 numMergeJobs;
- qint32 numStrokeJobs;
- updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs);
+ const int levelOfDetail = updaterContext.currentLevelOfDetail();
+
+ const KisUpdaterContextSnapshotEx snapshot = updaterContext.getContextSnapshotEx();
- int levelOfDetail = updaterContext.currentLevelOfDetail();
+ const bool hasStrokeJobs = !(snapshot == ContextEmpty ||
+ snapshot == HasMergeJob);
+ const bool hasMergeJobs = snapshot & HasMergeJob;
- if(checkStrokeState(numStrokeJobs, levelOfDetail) &&
- checkExclusiveProperty(numMergeJobs, numStrokeJobs) &&
- checkSequentialProperty(numMergeJobs, numStrokeJobs) &&
- checkBarrierProperty(numMergeJobs, numStrokeJobs,
- externalJobsPending)) {
+ if(checkStrokeState(hasStrokeJobs, levelOfDetail) &&
+ checkExclusiveProperty(hasMergeJobs, hasStrokeJobs) &&
+ checkSequentialProperty(snapshot, externalJobsPending)) {
KisStrokeSP stroke = m_d->strokesQueue.head();
updaterContext.addStrokeJob(stroke->popOneJob());
result = true;
}
return result;
}
bool KisStrokesQueue::checkStrokeState(bool hasStrokeJobsRunning,
int runningLevelOfDetail)
{
KisStrokeSP stroke = m_d->strokesQueue.head();
bool result = false;
/**
* We cannot start/continue a stroke if its LOD differs from
* the one that is running on CPU
*/
bool hasLodCompatibility = checkLevelOfDetailProperty(runningLevelOfDetail);
bool hasJobs = stroke->hasJobs();
/**
* The stroke may be cancelled very fast. In this case it will
* end up in the state:
*
* !stroke->isInitialized() && stroke->isEnded() && !stroke->hasJobs()
*
* This means that !isInitialised() doesn't imply there are any
* jobs present.
*/
if(!stroke->isInitialized() && hasJobs && hasLodCompatibility) {
/**
* It might happen that the stroke got initialized, but its job was not
* started due to some other reasons like exclusivity. Therefore the
* stroke might end up in loaded, but uninitialized state.
*/
if (!m_d->currentStrokeLoaded) {
m_d->needsExclusiveAccess = stroke->isExclusive();
m_d->wrapAroundModeSupported = stroke->supportsWrapAroundMode();
+ m_d->balancingRatioOverride = stroke->balancingRatioOverride();
m_d->currentStrokeLoaded = true;
}
result = true;
}
else if(hasJobs && hasLodCompatibility) {
/**
* If the stroke has no initialization phase, then it can
* arrive here unloaded.
*/
if (!m_d->currentStrokeLoaded) {
m_d->needsExclusiveAccess = stroke->isExclusive();
m_d->wrapAroundModeSupported = stroke->supportsWrapAroundMode();
+ m_d->balancingRatioOverride = stroke->balancingRatioOverride();
m_d->currentStrokeLoaded = true;
}
result = true;
}
else if(stroke->isEnded() && !hasJobs && !hasStrokeJobsRunning) {
m_d->tryClearUndoOnStrokeCompletion(stroke);
m_d->strokesQueue.dequeue(); // deleted by shared pointer
m_d->needsExclusiveAccess = false;
m_d->wrapAroundModeSupported = false;
+ m_d->balancingRatioOverride = -1.0;
m_d->currentStrokeLoaded = false;
m_d->switchDesiredLevelOfDetail(false);
if(!m_d->strokesQueue.isEmpty()) {
result = checkStrokeState(false, runningLevelOfDetail);
}
}
return result;
}
-bool KisStrokesQueue::checkExclusiveProperty(qint32 numMergeJobs,
- qint32 numStrokeJobs)
+bool KisStrokesQueue::checkExclusiveProperty(bool hasMergeJobs,
+ bool hasStrokeJobs)
{
+ Q_UNUSED(hasStrokeJobs);
+
if(!m_d->strokesQueue.head()->isExclusive()) return true;
- Q_UNUSED(numMergeJobs);
- Q_UNUSED(numStrokeJobs);
- Q_ASSERT(!(numMergeJobs && numStrokeJobs));
- return numMergeJobs == 0;
+ return hasMergeJobs == 0;
}
-bool KisStrokesQueue::checkSequentialProperty(qint32 numMergeJobs,
- qint32 numStrokeJobs)
+bool KisStrokesQueue::checkSequentialProperty(KisUpdaterContextSnapshotEx snapshot,
+ bool externalJobsPending)
{
- Q_UNUSED(numMergeJobs);
-
KisStrokeSP stroke = m_d->strokesQueue.head();
- if(!stroke->prevJobSequential() && !stroke->nextJobSequential()) return true;
- Q_ASSERT(!stroke->prevJobSequential() || numStrokeJobs <= 1);
- return numStrokeJobs == 0;
-}
+ if (snapshot & HasSequentialJob ||
+ snapshot & HasBarrierJob) {
+ return false;
+ }
-bool KisStrokesQueue::checkBarrierProperty(qint32 numMergeJobs,
- qint32 numStrokeJobs,
- bool externalJobsPending)
-{
- KisStrokeSP stroke = m_d->strokesQueue.head();
- if(!stroke->nextJobBarrier()) return true;
+ KisStrokeJobData::Sequentiality nextSequentiality =
+ stroke->nextJobSequentiality();
+
+ if (nextSequentiality == KisStrokeJobData::UNIQUELY_CONCURRENT &&
+ snapshot & HasUniquelyConcurrentJob) {
+
+ return false;
+ }
+
+ if (nextSequentiality == KisStrokeJobData::SEQUENTIAL &&
+ (snapshot & HasUniquelyConcurrentJob ||
+ snapshot & HasConcurrentJob)) {
- return !numMergeJobs && !numStrokeJobs && !externalJobsPending;
+ return false;
+ }
+
+ if (nextSequentiality == KisStrokeJobData::BARRIER &&
+ (snapshot & HasUniquelyConcurrentJob ||
+ snapshot & HasConcurrentJob ||
+ snapshot & HasMergeJob ||
+ externalJobsPending)) {
+
+ return false;
+ }
+
+ return true;
}
bool KisStrokesQueue::checkLevelOfDetailProperty(int runningLevelOfDetail)
{
KisStrokeSP stroke = m_d->strokesQueue.head();
return runningLevelOfDetail < 0 ||
stroke->worksOnLevelOfDetail() == runningLevelOfDetail;
}
diff --git a/libs/image/kis_strokes_queue.h b/libs/image/kis_strokes_queue.h
index 08e8639a56..b7fda7dd85 100644
--- a/libs/image/kis_strokes_queue.h
+++ b/libs/image/kis_strokes_queue.h
@@ -1,101 +1,106 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_STROKES_QUEUE_H
#define __KIS_STROKES_QUEUE_H
#include "kritaimage_export.h"
#include "kundo2magicstring.h"
#include "kis_types.h"
#include "kis_stroke_job_strategy.h"
#include "kis_stroke_strategy.h"
#include "kis_stroke_strategy_factory.h"
#include "kis_strokes_queue_undo_result.h"
-
+#include "KisStrokesQueueMutatedJobInterface.h"
+#include "KisUpdaterContextSnapshotEx.h"
class KisUpdaterContext;
class KisStroke;
class KisStrokeStrategy;
class KisStrokeJobData;
class KisPostExecutionUndoAdapter;
-class KRITAIMAGE_EXPORT KisStrokesQueue
+class KRITAIMAGE_EXPORT KisStrokesQueue : public KisStrokesQueueMutatedJobInterface
{
public:
KisStrokesQueue();
~KisStrokesQueue();
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy);
void addJob(KisStrokeId id, KisStrokeJobData *data);
void endStroke(KisStrokeId id);
bool cancelStroke(KisStrokeId id);
bool tryCancelCurrentStrokeAsync();
UndoResult tryUndoLastStrokeAsync();
void processQueue(KisUpdaterContext &updaterContext,
bool externalJobsPending);
bool needsExclusiveAccess() const;
bool isEmpty() const;
qint32 sizeMetric() const;
KUndo2MagicString currentStrokeName() const;
bool hasOpenedStrokes() const;
bool wrapAroundModeSupported() const;
+ qreal balancingRatioOverride() const;
void setDesiredLevelOfDetail(int lod);
void explicitRegenerateLevelOfDetail();
void setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory);
void setSuspendUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory);
void setResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory);
KisPostExecutionUndoAdapter* lodNPostExecutionUndoAdapter() const;
/**
* Notifies the queue, that someone else (neither strokes nor the
* queue itself have changed the image. It means that the caches
* should be regenerated
*/
void notifyUFOChangedImage();
void debugDumpAllStrokes();
+ // interface for KisStrokeStrategy only!
+ void addMutatedJobs(KisStrokeId id, const QVector<KisStrokeJobData*> list) final;
+
private:
bool processOneJob(KisUpdaterContext &updaterContext,
bool externalJobsPending);
bool checkStrokeState(bool hasStrokeJobsRunning,
int runningLevelOfDetail);
- bool checkExclusiveProperty(qint32 numMergeJobs, qint32 numStrokeJobs);
- bool checkSequentialProperty(qint32 numMergeJobs, qint32 numStrokeJobs);
- bool checkBarrierProperty(qint32 numMergeJobs, qint32 numStrokeJobs,
+ bool checkExclusiveProperty(bool hasMergeJobs, bool hasStrokeJobs);
+ bool checkSequentialProperty(KisUpdaterContextSnapshotEx snapshot, bool externalJobsPending);
+ bool checkBarrierProperty(bool hasMergeJobs, bool hasStrokeJobs,
bool externalJobsPending);
bool checkLevelOfDetailProperty(int runningLevelOfDetail);
class LodNUndoStrokesFacade;
KisStrokeId startLodNUndoStroke(KisStrokeStrategy *strokeStrategy);
private:
struct Private;
Private * const m_d;
};
#endif /* __KIS_STROKES_QUEUE_H */
diff --git a/libs/image/kis_suspend_projection_updates_stroke_strategy.cpp b/libs/image/kis_suspend_projection_updates_stroke_strategy.cpp
index 3f890c5dc5..638b43bd25 100644
--- a/libs/image/kis_suspend_projection_updates_stroke_strategy.cpp
+++ b/libs/image/kis_suspend_projection_updates_stroke_strategy.cpp
@@ -1,303 +1,305 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_suspend_projection_updates_stroke_strategy.h"
#include <kis_image.h>
#include <krita_utils.h>
#include <kis_projection_updates_filter.h>
inline uint qHash(const QRect &rc) {
return rc.x() +
(rc.y() << 16) +
(rc.width() << 8) +
(rc.height() << 24);
}
struct KisSuspendProjectionUpdatesStrokeStrategy::Private
{
KisImageWSP image;
bool suspend;
class SuspendLod0Updates : public KisProjectionUpdatesFilter
{
struct Request {
Request() : resetAnimationCache(false) {}
Request(const QRect &_rect, bool _resetAnimationCache)
: rect(_rect), resetAnimationCache(_resetAnimationCache)
{
}
QRect rect;
bool resetAnimationCache;
};
typedef QHash<KisNodeSP, QVector<Request> > RectsHash;
public:
SuspendLod0Updates()
{
}
- bool filter(KisImage *image, KisNode *node, const QRect &rect, bool resetAnimationCache) override {
+ bool filter(KisImage *image, KisNode *node, const QVector<QRect> &rects, bool resetAnimationCache) override {
if (image->currentLevelOfDetail() > 0) return false;
QMutexLocker l(&m_mutex);
- m_requestsHash[KisNodeSP(node)].append(Request(rect, resetAnimationCache));
+
+ Q_FOREACH(const QRect &rc, rects) {
+ m_requestsHash[KisNodeSP(node)].append(Request(rc, resetAnimationCache));
+ }
+
return true;
}
static inline QRect alignRect(const QRect &rc, const int step) {
static const int decstep = step - 1;
static const int invstep = ~decstep;
int x0, y0, x1, y1;
rc.getCoords(&x0, &y0, &x1, &y1);
x0 &= invstep;
y0 &= invstep;
x1 |= decstep;
y1 |= decstep;
QRect result;
result.setCoords(x0, y0, x1, y1);
return result;
}
void notifyUpdates(KisNodeGraphListener *listener) {
RectsHash::const_iterator it = m_requestsHash.constBegin();
RectsHash::const_iterator end = m_requestsHash.constEnd();
const int step = 64;
for (; it != end; ++it) {
KisNodeSP node = it.key();
QRegion region;
bool resetAnimationCache = false;
Q_FOREACH (const Request &req, it.value()) {
region += alignRect(req.rect, step);
resetAnimationCache |= req.resetAnimationCache;
}
- Q_FOREACH (const QRect &rc, region.rects()) {
- // FIXME: constness: port rPU to SP
- listener->requestProjectionUpdate(const_cast<KisNode*>(node.data()), rc, resetAnimationCache);
- }
+ // FIXME: constness: port rPU to SP
+ listener->requestProjectionUpdate(const_cast<KisNode*>(node.data()), region.rects(), resetAnimationCache);
}
}
private:
RectsHash m_requestsHash;
QMutex m_mutex;
};
class SuspendData : public KisStrokeJobData {
public:
SuspendData()
: KisStrokeJobData(SEQUENTIAL)
{}
};
class ResumeAndIssueGraphUpdatesData : public KisStrokeJobData {
public:
ResumeAndIssueGraphUpdatesData()
: KisStrokeJobData(SEQUENTIAL)
{}
};
class UpdatesBarrierData : public KisStrokeJobData {
public:
UpdatesBarrierData()
: KisStrokeJobData(BARRIER)
{}
};
class IssueCanvasUpdatesData : public KisStrokeJobData {
public:
IssueCanvasUpdatesData(QRect _updateRect)
: KisStrokeJobData(CONCURRENT),
updateRect(_updateRect)
{}
QRect updateRect;
};
};
KisSuspendProjectionUpdatesStrokeStrategy::KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP image, bool suspend)
: KisSimpleStrokeStrategy(suspend ? "suspend_stroke_strategy" : "resume_stroke_strategy"),
m_d(new Private)
{
m_d->image = image;
m_d->suspend = suspend;
/**
* Here we add a dumb INIT job so that KisStrokesQueue would know that the
* stroke has already started or not. When the queue reaches the resume
* stroke ans starts its execution, no Lod0 can execute anymore. So all the
* new Lod0 strokes should go to the end of the queue and wrapped into
* their own Suspend/Resume pair.
*/
enableJob(JOB_INIT, true);
enableJob(JOB_DOSTROKE, true);
enableJob(JOB_CANCEL, true);
setNeedsExplicitCancel(true);
}
KisSuspendProjectionUpdatesStrokeStrategy::~KisSuspendProjectionUpdatesStrokeStrategy()
{
}
/**
* When the Lod0 stroke is being recalculated in the background we
* should block all the updates it issues to avoid user distraction.
* The result of the final stroke should be shown to the user in the
* very end when everything is fully ready. Ideally the use should not
* notice that the image has changed :)
*
* (Don't mix this process with suspend/resume capabilities of a
* single stroke. That is a different system!)
*
* The process of the Lod0 regeneration consists of the following:
*
* 1) Suspend stroke executes. It sets a special updates filter on the
* image. The filter blocks all the updates and saves them in an
* internal structure to be emitted in the future.
*
* 2) Lod0 strokes are being recalculated. All their updates are
* blocked and saved in the filter.
*
* 3) Resume stroke starts:
*
* 3.1) First it disables emitting of sigImageUpdated() so the gui
* will not get any update notifications.
*
* 3.2) Then it enables updates themselves.
*
* 3.3) Initiates all the updates that were requested by the Lod0
* stroke. The node graph is regenerated, but the GUI does
* not get this change.
*
* 3.4) Special barrier job waits for all the updates to finish
* and, when they are done, enables GUI notifications again.
*
* 3.5) In a multithreaded way emits the GUI notifications for the
* entire image. Multithreaded way is used to conform the
* double-stage update principle of KisCanvas2.
*/
void KisSuspendProjectionUpdatesStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
{
Private::SuspendData *suspendData = dynamic_cast<Private::SuspendData*>(data);
Private::ResumeAndIssueGraphUpdatesData *resumeData = dynamic_cast<Private::ResumeAndIssueGraphUpdatesData*>(data);
Private::UpdatesBarrierData *barrierData = dynamic_cast<Private::UpdatesBarrierData*>(data);
Private::IssueCanvasUpdatesData *canvasUpdates = dynamic_cast<Private::IssueCanvasUpdatesData*>(data);
KisImageSP image = m_d->image.toStrongRef();
if (!image) {
return;
}
if (suspendData) {
image->setProjectionUpdatesFilter(
KisProjectionUpdatesFilterSP(new Private::SuspendLod0Updates()));
} else if (resumeData) {
image->disableUIUpdates();
resumeAndIssueUpdates(false);
} else if (barrierData) {
image->enableUIUpdates();
} else if (canvasUpdates) {
image->notifyProjectionUpdated(canvasUpdates->updateRect);
}
}
QList<KisStrokeJobData*> KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP /*image*/)
{
QList<KisStrokeJobData*> jobsData;
jobsData << new Private::SuspendData();
return jobsData;
}
QList<KisStrokeJobData*> KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP _image)
{
QList<KisStrokeJobData*> jobsData;
jobsData << new Private::ResumeAndIssueGraphUpdatesData();
jobsData << new Private::UpdatesBarrierData();
using KritaUtils::splitRectIntoPatches;
using KritaUtils::optimalPatchSize;
KisImageSP image = _image;
QVector<QRect> rects = splitRectIntoPatches(image->bounds(), optimalPatchSize());
Q_FOREACH (const QRect &rc, rects) {
jobsData << new Private::IssueCanvasUpdatesData(rc);
}
return jobsData;
}
void KisSuspendProjectionUpdatesStrokeStrategy::resumeAndIssueUpdates(bool dropUpdates)
{
KisImageSP image = m_d->image.toStrongRef();
if (!image) {
return;
}
KisProjectionUpdatesFilterSP filter =
image->projectionUpdatesFilter();
if (!filter) return;
Private::SuspendLod0Updates *localFilter =
dynamic_cast<Private::SuspendLod0Updates*>(filter.data());
if (localFilter) {
image->setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP());
if (!dropUpdates) {
localFilter->notifyUpdates(image.data());
}
}
}
void KisSuspendProjectionUpdatesStrokeStrategy::cancelStrokeCallback()
{
KisImageSP image = m_d->image.toStrongRef();
if (!image) {
return;
}
/**
* We shouldn't emit any ad-hoc updates when cancelling the
* stroke. It generates weird temporary holes on the canvas,
* making the user feel awful, thinking his image got
* corrupted. We will just emit a common refreshGraphAsync() that
* will do all the work in a beautiful way
*/
resumeAndIssueUpdates(true);
if (!m_d->suspend) {
// FIXME: optimize
image->refreshGraphAsync();
}
}
diff --git a/libs/image/kis_types.h b/libs/image/kis_types.h
index e7bf16c44a..e875996a92 100644
--- a/libs/image/kis_types.h
+++ b/libs/image/kis_types.h
@@ -1,302 +1,306 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KISTYPES_H_
#define KISTYPES_H_
#include <QVector>
#include <QPoint>
#include <QList>
#include "kritaimage_export.h"
template<class T>
class KisWeakSharedPtr;
template<class T>
class KisSharedPtr;
template<class T> class QSharedPointer;
template<class T> class QWeakPointer;
template <class T>
uint qHash(KisSharedPtr<T> ptr) {
return qHash(ptr.data());
}
template <class T>
uint qHash(KisWeakSharedPtr<T> ptr) {
return qHash(ptr.data());
}
/**
* Define lots of shared pointer versions of Krita classes.
* Shared pointer classes have the advantage of near automatic
* memory management (but beware of circular references)
* These types should never be passed by reference,
* because that will mess up their reference counter.
*
* An example of the naming pattern used:
*
* KisPaintDeviceSP is a KisSharedPtr of KisPaintDevice
* KisPaintDeviceWSP is a KisWeakSharedPtr of KisPaintDevice
* vKisPaintDeviceSP is a QVector of KisPaintDeviceSP
* vKisPaintDeviceSP_it is an iterator of vKisPaintDeviceSP
*
*/
class KisImage;
typedef KisSharedPtr<KisImage> KisImageSP;
typedef KisWeakSharedPtr<KisImage> KisImageWSP;
class KisPaintDevice;
typedef KisSharedPtr<KisPaintDevice> KisPaintDeviceSP;
typedef KisWeakSharedPtr<KisPaintDevice> KisPaintDeviceWSP;
typedef QVector<KisPaintDeviceSP> vKisPaintDeviceSP;
typedef vKisPaintDeviceSP::iterator vKisPaintDeviceSP_it;
class KisFixedPaintDevice;
typedef KisSharedPtr<KisFixedPaintDevice> KisFixedPaintDeviceSP;
class KisMask;
typedef KisSharedPtr<KisMask> KisMaskSP;
typedef KisWeakSharedPtr<KisMask> KisMaskWSP;
class KisNode;
typedef KisSharedPtr<KisNode> KisNodeSP;
typedef KisWeakSharedPtr<KisNode> KisNodeWSP;
typedef QVector<KisNodeSP> vKisNodeSP;
typedef vKisNodeSP::iterator vKisNodeSP_it;
typedef vKisNodeSP::const_iterator vKisNodeSP_cit;
class KisBaseNode;
typedef KisSharedPtr<KisBaseNode> KisBaseNodeSP;
typedef KisWeakSharedPtr<KisBaseNode> KisBaseNodeWSP;
class KisEffectMask;
typedef KisSharedPtr<KisEffectMask> KisEffectMaskSP;
typedef KisWeakSharedPtr<KisEffectMask> KisEffectMaskWSP;
class KisFilterMask;
typedef KisSharedPtr<KisFilterMask> KisFilterMaskSP;
typedef KisWeakSharedPtr<KisFilterMask> KisFilterMaskWSP;
class KisTransformMask;
typedef KisSharedPtr<KisTransformMask> KisTransformMaskSP;
typedef KisWeakSharedPtr<KisTransformMask> KisTransformMaskWSP;
class KisTransformMaskParamsInterface;
typedef QSharedPointer<KisTransformMaskParamsInterface> KisTransformMaskParamsInterfaceSP;
typedef QWeakPointer<KisTransformMaskParamsInterface> KisTransformMaskParamsInterfaceWSP;
class KisTransparencyMask;
typedef KisSharedPtr<KisTransparencyMask> KisTransparencyMaskSP;
typedef KisWeakSharedPtr<KisTransparencyMask> KisTransparencyMaskWSP;
class KisColorizeMask;
typedef KisSharedPtr<KisColorizeMask> KisColorizeMaskSP;
typedef KisWeakSharedPtr<KisColorizeMask> KisColorizeMaskWSP;
class KisLayer;
typedef KisSharedPtr<KisLayer> KisLayerSP;
typedef KisWeakSharedPtr<KisLayer> KisLayerWSP;
class KisShapeLayer;
typedef KisSharedPtr<KisShapeLayer> KisShapeLayerSP;
class KisPaintLayer;
typedef KisSharedPtr<KisPaintLayer> KisPaintLayerSP;
class KisAdjustmentLayer;
typedef KisSharedPtr<KisAdjustmentLayer> KisAdjustmentLayerSP;
class KisGeneratorLayer;
typedef KisSharedPtr<KisGeneratorLayer> KisGeneratorLayerSP;
class KisCloneLayer;
typedef KisSharedPtr<KisCloneLayer> KisCloneLayerSP;
typedef KisWeakSharedPtr<KisCloneLayer> KisCloneLayerWSP;
class KisGroupLayer;
typedef KisSharedPtr<KisGroupLayer> KisGroupLayerSP;
typedef KisWeakSharedPtr<KisGroupLayer> KisGroupLayerWSP;
+class KisFileLayer;
+typedef KisSharedPtr<KisFileLayer> KisFileLayerSP;
+typedef KisWeakSharedPtr<KisFileLayer> KisFileLayerWSP;
+
class KisSelection;
typedef KisSharedPtr<KisSelection> KisSelectionSP;
typedef KisWeakSharedPtr<KisSelection> KisSelectionWSP;
class KisSelectionComponent;
typedef KisSharedPtr<KisSelectionComponent> KisSelectionComponentSP;
class KisSelectionMask;
typedef KisSharedPtr<KisSelectionMask> KisSelectionMaskSP;
class KisPixelSelection;
typedef KisSharedPtr<KisPixelSelection> KisPixelSelectionSP;
class KisHistogram;
typedef KisSharedPtr<KisHistogram> KisHistogramSP;
typedef QVector<QPoint> vKisSegments;
class KisFilter;
typedef KisSharedPtr<KisFilter> KisFilterSP;
class KisLayerStyleFilter;
typedef KisSharedPtr<KisLayerStyleFilter> KisLayerStyleFilterSP;
class KisGenerator;
typedef KisSharedPtr<KisGenerator> KisGeneratorSP;
class KisConvolutionKernel;
typedef KisSharedPtr<KisConvolutionKernel> KisConvolutionKernelSP;
class KisAnnotation;
typedef KisSharedPtr<KisAnnotation> KisAnnotationSP;
typedef QVector<KisAnnotationSP> vKisAnnotationSP;
typedef vKisAnnotationSP::iterator vKisAnnotationSP_it;
typedef vKisAnnotationSP::const_iterator vKisAnnotationSP_cit;
class KisAnimationFrameCache;
typedef KisSharedPtr<KisAnimationFrameCache> KisAnimationFrameCacheSP;
typedef KisWeakSharedPtr<KisAnimationFrameCache> KisAnimationFrameCacheWSP;
class KisPaintingAssistant;
typedef QSharedPointer<KisPaintingAssistant> KisPaintingAssistantSP;
typedef QWeakPointer<KisPaintingAssistant> KisPaintingAssistantWSP;
// Repeat iterators
class KisHLineIterator2;
template<class T> class KisRepeatHLineIteratorPixelBase;
typedef KisRepeatHLineIteratorPixelBase< KisHLineIterator2 > KisRepeatHLineConstIteratorNG;
typedef KisSharedPtr<KisRepeatHLineConstIteratorNG> KisRepeatHLineConstIteratorSP;
class KisVLineIterator2;
template<class T> class KisRepeatVLineIteratorPixelBase;
typedef KisRepeatVLineIteratorPixelBase< KisVLineIterator2 > KisRepeatVLineConstIteratorNG;
typedef KisSharedPtr<KisRepeatVLineConstIteratorNG> KisRepeatVLineConstIteratorSP;
// NG Iterators
class KisHLineIteratorNG;
typedef KisSharedPtr<KisHLineIteratorNG> KisHLineIteratorSP;
class KisHLineConstIteratorNG;
typedef KisSharedPtr<KisHLineConstIteratorNG> KisHLineConstIteratorSP;
class KisVLineIteratorNG;
typedef KisSharedPtr<KisVLineIteratorNG> KisVLineIteratorSP;
class KisVLineConstIteratorNG;
typedef KisSharedPtr<KisVLineConstIteratorNG> KisVLineConstIteratorSP;
class KisRandomConstAccessorNG;
typedef KisSharedPtr<KisRandomConstAccessorNG> KisRandomConstAccessorSP;
class KisRandomAccessorNG;
typedef KisSharedPtr<KisRandomAccessorNG> KisRandomAccessorSP;
class KisRandomSubAccessor;
typedef KisSharedPtr<KisRandomSubAccessor> KisRandomSubAccessorSP;
// Things
typedef QVector<QPointF> vQPointF;
class KisPaintOpPreset;
typedef KisSharedPtr<KisPaintOpPreset> KisPaintOpPresetSP;
typedef KisWeakSharedPtr<KisPaintOpPreset> KisPaintOpPresetWSP;
template <typename T>
class KisPinnedSharedPtr;
class KisPaintOpSettings;
typedef KisPinnedSharedPtr<KisPaintOpSettings> KisPaintOpSettingsSP;
template <typename T>
class KisRestrictedSharedPtr;
typedef KisRestrictedSharedPtr<KisPaintOpSettings> KisPaintOpSettingsRestrictedSP;
class KisPaintOp;
typedef KisSharedPtr<KisPaintOp> KisPaintOpSP;
class KoID;
typedef QList<KoID> KoIDList;
class KoUpdater;
template<class T> class QPointer;
typedef QPointer<KoUpdater> KoUpdaterPtr;
class KisProcessingVisitor;
typedef KisSharedPtr<KisProcessingVisitor> KisProcessingVisitorSP;
class KUndo2Command;
typedef QSharedPointer<KUndo2Command> KUndo2CommandSP;
typedef QList<KisNodeSP> KisNodeList;
typedef QSharedPointer<KisNodeList> KisNodeListSP;
typedef QList<KisPaintDeviceSP> KisPaintDeviceList;
class KisStroke;
typedef QSharedPointer<KisStroke> KisStrokeSP;
typedef QWeakPointer<KisStroke> KisStrokeWSP;
typedef KisStrokeWSP KisStrokeId;
class KisFilterConfiguration;
typedef KisPinnedSharedPtr<KisFilterConfiguration> KisFilterConfigurationSP;
class KisPropertiesConfiguration;
typedef KisPinnedSharedPtr<KisPropertiesConfiguration> KisPropertiesConfigurationSP;
class KisLockedProperties;
typedef KisSharedPtr<KisLockedProperties> KisLockedPropertiesSP;
class KisProjectionUpdatesFilter;
typedef QSharedPointer<KisProjectionUpdatesFilter> KisProjectionUpdatesFilterSP;
class KisAbstractProjectionPlane;
typedef QSharedPointer<KisAbstractProjectionPlane> KisAbstractProjectionPlaneSP;
typedef QWeakPointer<KisAbstractProjectionPlane> KisAbstractProjectionPlaneWSP;
class KisProjectionLeaf;
typedef QSharedPointer<KisProjectionLeaf> KisProjectionLeafSP;
typedef QWeakPointer<KisProjectionLeaf> KisProjectionLeafWSP;
class KisKeyframe;
typedef QSharedPointer<KisKeyframe> KisKeyframeSP;
typedef QWeakPointer<KisKeyframe> KisKeyframeWSP;
class KisFilterChain;
typedef KisSharedPtr<KisFilterChain> KisFilterChainSP;
class KisProofingConfiguration;
typedef QSharedPointer<KisProofingConfiguration> KisProofingConfigurationSP;
typedef QWeakPointer<KisProofingConfiguration> KisProofingConfigurationWSP;
class KisLayerComposition;
typedef QSharedPointer<KisLayerComposition> KisLayerCompositionSP;
typedef QWeakPointer<KisLayerComposition> KisLayerCompositionWSP;
#include <QSharedPointer>
#include <QWeakPointer>
#include <kis_shared_ptr.h>
#include <kis_restricted_shared_ptr.h>
#include <kis_pinned_shared_ptr.h>
#endif // KISTYPES_H_
diff --git a/libs/image/kis_update_job_item.h b/libs/image/kis_update_job_item.h
index debaf3d6be..3ac09e3436 100644
--- a/libs/image/kis_update_job_item.h
+++ b/libs/image/kis_update_job_item.h
@@ -1,232 +1,257 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_UPDATE_JOB_ITEM_H
#define __KIS_UPDATE_JOB_ITEM_H
+#include <atomic>
+
#include <QRunnable>
#include <QReadWriteLock>
#include "kis_stroke_job.h"
#include "kis_spontaneous_job.h"
#include "kis_base_rects_walker.h"
#include "kis_async_merger.h"
class KisUpdateJobItem : public QObject, public QRunnable
{
Q_OBJECT
public:
- enum Type {
- EMPTY,
+ enum class Type : int {
+ EMPTY = 0,
+ WAITING,
MERGE,
STROKE,
SPONTANEOUS
};
public:
KisUpdateJobItem(QReadWriteLock *exclusiveJobLock)
: m_exclusiveJobLock(exclusiveJobLock),
- m_type(EMPTY),
+ m_atomicType(Type::EMPTY),
m_runnableJob(0)
{
setAutoDelete(false);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(m_atomicType.is_lock_free());
}
~KisUpdateJobItem() override
{
delete m_runnableJob;
}
void run() override {
if (!isRunning()) return;
/**
* Here we break the idea of QThreadPool a bit. Ideally, we should split the
* jobs into distinct QRunnable objects and pass all of them to QThreadPool.
* That is a nice idea, but it doesn't work well when the jobs are small enough
* and the number of available cores is high (>4 cores). It this case the
* threads just tend to execute the job very quickly and go to sleep, which is
- * an expencive operation.
+ * an expensive operation.
*
* To overcome this problem we try to bulk-process the jobs. In sigJobFinished()
* signal (which is DirectConnection), the context may add the job to ourselves(!!!),
* so we switch from "done" state into "running" again.
*/
- while (isRunning()) {
- m_isExecuting.ref();
+ while (1) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(isRunning());
if(m_exclusive) {
m_exclusiveJobLock->lockForWrite();
} else {
m_exclusiveJobLock->lockForRead();
}
- if(m_type == MERGE) {
+ if(m_atomicType == Type::MERGE) {
runMergeJob();
} else {
- Q_ASSERT(m_type == STROKE || m_type == SPONTANEOUS);
+ KIS_ASSERT(m_atomicType == Type::STROKE ||
+ m_atomicType == Type::SPONTANEOUS);
+
m_runnableJob->run();
- delete m_runnableJob;
- m_runnableJob = 0;
}
setDone();
emit sigDoSomeUsefulWork();
+
+ // may flip the current state from Waiting -> Running again
emit sigJobFinished();
m_exclusiveJobLock->unlock();
- m_isExecuting.deref();
+ // try to exit the loop. Please note, that noone can flip the state from
+ // WAITING to EMPTY except ourselves!
+ Type expectedValue = Type::WAITING;
+ if (m_atomicType.compare_exchange_strong(expectedValue, Type::EMPTY)) {
+ break;
+ }
}
}
inline void runMergeJob() {
- Q_ASSERT(m_type == MERGE);
+ Q_ASSERT(m_atomicType == Type::MERGE);
// dbgKrita << "Executing merge job" << m_walker->changeRect()
// << "on thread" << QThread::currentThreadId();
m_merger.startMerge(*m_walker);
QRect changeRect = m_walker->changeRect();
emit sigContinueUpdate(changeRect);
}
- inline void setWalker(KisBaseRectsWalkerSP walker) {
- m_type = MERGE;
+ // return true if the thread should actually be started
+ inline bool setWalker(KisBaseRectsWalkerSP walker) {
+ KIS_ASSERT(m_atomicType <= Type::WAITING);
+
m_accessRect = walker->accessRect();
m_changeRect = walker->changeRect();
m_walker = walker;
m_exclusive = false;
m_runnableJob = 0;
+
+ const Type oldState = m_atomicType.exchange(Type::MERGE);
+ return oldState == Type::EMPTY;
}
- inline void setStrokeJob(KisStrokeJob *strokeJob) {
- m_type = STROKE;
+ // return true if the thread should actually be started
+ inline bool setStrokeJob(KisStrokeJob *strokeJob) {
+ KIS_ASSERT(m_atomicType <= Type::WAITING);
+
m_runnableJob = strokeJob;
+ m_strokeJobSequentiality = strokeJob->sequentiality();
m_exclusive = strokeJob->isExclusive();
m_walker = 0;
m_accessRect = m_changeRect = QRect();
+
+ const Type oldState = m_atomicType.exchange(Type::STROKE);
+ return oldState == Type::EMPTY;
}
- inline void setSpontaneousJob(KisSpontaneousJob *spontaneousJob) {
- m_type = SPONTANEOUS;
+ // return true if the thread should actually be started
+ inline bool setSpontaneousJob(KisSpontaneousJob *spontaneousJob) {
+ KIS_ASSERT(m_atomicType <= Type::WAITING);
+
m_runnableJob = spontaneousJob;
m_exclusive = false;
m_walker = 0;
m_accessRect = m_changeRect = QRect();
+
+ const Type oldState = m_atomicType.exchange(Type::SPONTANEOUS);
+ return oldState == Type::EMPTY;
}
inline void setDone() {
m_walker = 0;
+ delete m_runnableJob;
m_runnableJob = 0;
- m_type = EMPTY;
+ m_atomicType = Type::WAITING;
}
inline bool isRunning() const {
- return m_type != EMPTY;
+ return m_atomicType >= Type::MERGE;
}
inline Type type() const {
- return m_type;
+ return m_atomicType;
}
inline const QRect& accessRect() const {
return m_accessRect;
}
inline const QRect& changeRect() const {
return m_changeRect;
}
- inline bool hasThreadAttached() const {
- return m_isExecuting;
+ inline KisStrokeJobData::Sequentiality strokeJobSequentiality() const {
+ return m_strokeJobSequentiality;
}
Q_SIGNALS:
void sigContinueUpdate(const QRect& rc);
void sigDoSomeUsefulWork();
void sigJobFinished();
private:
/**
* Open walker and stroke job for the testing suite.
* Please, do not use it in production code.
*/
friend class KisSimpleUpdateQueueTest;
friend class KisStrokesQueueTest;
friend class KisUpdateSchedulerTest;
friend class KisTestableUpdaterContext;
inline KisBaseRectsWalkerSP walker() const {
return m_walker;
}
inline KisStrokeJob* strokeJob() const {
KisStrokeJob *job = dynamic_cast<KisStrokeJob*>(m_runnableJob);
Q_ASSERT(job);
return job;
}
inline void testingSetDone() {
- if(m_type == STROKE) {
- delete m_runnableJob;
- }
setDone();
}
private:
/**
* \see KisUpdaterContext::m_exclusiveJobLock
*/
QReadWriteLock *m_exclusiveJobLock;
bool m_exclusive;
- volatile Type m_type;
+ std::atomic<Type> m_atomicType;
+
+ volatile KisStrokeJobData::Sequentiality m_strokeJobSequentiality;
/**
* Runnable jobs part
* The job is owned by the context and deleted after completion
*/
KisRunnable *m_runnableJob;
/**
* Merge jobs part
*/
KisBaseRectsWalkerSP m_walker;
KisAsyncMerger m_merger;
/**
* These rects cache actual values from the walker
* to eliminate concurrent access to a walker structure
*/
QRect m_accessRect;
QRect m_changeRect;
-
- QAtomicInt m_isExecuting;
};
#endif /* __KIS_UPDATE_JOB_ITEM_H */
diff --git a/libs/image/kis_update_scheduler.cpp b/libs/image/kis_update_scheduler.cpp
index dd198505e4..1fc6e373e1 100644
--- a/libs/image/kis_update_scheduler.cpp
+++ b/libs/image/kis_update_scheduler.cpp
@@ -1,493 +1,504 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_update_scheduler.h"
#include "klocalizedstring.h"
#include "kis_image_config.h"
#include "kis_merge_walker.h"
#include "kis_full_refresh_walker.h"
#include "kis_updater_context.h"
#include "kis_simple_update_queue.h"
#include "kis_strokes_queue.h"
#include "kis_queues_progress_updater.h"
#include "KisUpdateSchedulerConfigNotifier.h"
#include <QReadWriteLock>
#include "kis_lazy_wait_condition.h"
#include <mutex>
//#define DEBUG_BALANCING
#ifdef DEBUG_BALANCING
#define DEBUG_BALANCING_METRICS(decidedFirst, excl) \
dbgKrita << "Balance decision:" << decidedFirst \
<< "(" << excl << ")" \
<< "updates:" << m_d->updatesQueue.sizeMetric() \
<< "strokes:" << m_d->strokesQueue.sizeMetric()
#else
#define DEBUG_BALANCING_METRICS(decidedFirst, excl)
#endif
struct Q_DECL_HIDDEN KisUpdateScheduler::Private {
Private(KisUpdateScheduler *_q, KisProjectionUpdateListener *p)
: q(_q)
, updaterContext(KisImageConfig().maxNumberOfThreads(), q)
, projectionUpdateListener(p)
{}
KisUpdateScheduler *q;
KisSimpleUpdateQueue updatesQueue;
KisStrokesQueue strokesQueue;
KisUpdaterContext updaterContext;
bool processingBlocked = false;
- qreal balancingRatio = 1.0; // updates-queue-size/strokes-queue-size
+ qreal defaultBalancingRatio = 1.0; // desired strokes-queue-size / updates-queue-size
KisProjectionUpdateListener *projectionUpdateListener;
KisQueuesProgressUpdater *progressUpdater = 0;
QAtomicInt updatesLockCounter;
QReadWriteLock updatesStartLock;
KisLazyWaitCondition updatesFinishedCondition;
+
+ qreal balancingRatio() const {
+ const qreal strokeRatioOverride = strokesQueue.balancingRatioOverride();
+ return strokeRatioOverride > 0 ? strokeRatioOverride : defaultBalancingRatio;
+ }
};
KisUpdateScheduler::KisUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener, QObject *parent)
: QObject(parent),
m_d(new Private(this, projectionUpdateListener))
{
updateSettings();
connectSignals();
}
KisUpdateScheduler::KisUpdateScheduler()
: m_d(new Private(this, 0))
{
}
KisUpdateScheduler::~KisUpdateScheduler()
{
delete m_d->progressUpdater;
delete m_d;
}
void KisUpdateScheduler::setThreadsLimit(int value)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->processingBlocked);
barrierLock();
m_d->updaterContext.lock();
m_d->updaterContext.setThreadsLimit(value);
m_d->updaterContext.unlock();
unlock(false);
}
int KisUpdateScheduler::threadsLimit() const
{
std::lock_guard<KisUpdaterContext> l(m_d->updaterContext);
return m_d->updaterContext.threadsLimit();
}
void KisUpdateScheduler::connectSignals()
{
connect(&m_d->updaterContext, SIGNAL(sigContinueUpdate(const QRect&)),
SLOT(continueUpdate(const QRect&)),
Qt::DirectConnection);
connect(&m_d->updaterContext, SIGNAL(sigDoSomeUsefulWork()),
SLOT(doSomeUsefulWork()), Qt::DirectConnection);
connect(&m_d->updaterContext, SIGNAL(sigSpareThreadAppeared()),
SLOT(spareThreadAppeared()), Qt::DirectConnection);
connect(KisUpdateSchedulerConfigNotifier::instance(), SIGNAL(configChanged()),
SLOT(updateSettings()));
}
void KisUpdateScheduler::setProgressProxy(KoProgressProxy *progressProxy)
{
delete m_d->progressUpdater;
m_d->progressUpdater = progressProxy ?
new KisQueuesProgressUpdater(progressProxy, this) : 0;
}
void KisUpdateScheduler::progressUpdate()
{
if (!m_d->progressUpdater) return;
if(!m_d->strokesQueue.hasOpenedStrokes()) {
QString jobName = m_d->strokesQueue.currentStrokeName().toString();
if(jobName.isEmpty()) {
jobName = i18n("Updating...");
}
int sizeMetric = m_d->strokesQueue.sizeMetric();
if (!sizeMetric) {
sizeMetric = m_d->updatesQueue.sizeMetric();
}
m_d->progressUpdater->updateProgress(sizeMetric, jobName);
}
else {
m_d->progressUpdater->hide();
}
}
-void KisUpdateScheduler::updateProjection(KisNodeSP node, const QRect& rc, const QRect &cropRect)
+void KisUpdateScheduler::updateProjection(KisNodeSP node, const QVector<QRect> &rects, const QRect &cropRect)
+{
+ m_d->updatesQueue.addUpdateJob(node, rects, cropRect, currentLevelOfDetail());
+ processQueues();
+}
+
+void KisUpdateScheduler::updateProjection(KisNodeSP node, const QRect &rc, const QRect &cropRect)
{
m_d->updatesQueue.addUpdateJob(node, rc, cropRect, currentLevelOfDetail());
processQueues();
}
void KisUpdateScheduler::updateProjectionNoFilthy(KisNodeSP node, const QRect& rc, const QRect &cropRect)
{
m_d->updatesQueue.addUpdateNoFilthyJob(node, rc, cropRect, currentLevelOfDetail());
processQueues();
}
void KisUpdateScheduler::fullRefreshAsync(KisNodeSP root, const QRect& rc, const QRect &cropRect)
{
m_d->updatesQueue.addFullRefreshJob(root, rc, cropRect, currentLevelOfDetail());
processQueues();
}
void KisUpdateScheduler::fullRefresh(KisNodeSP root, const QRect& rc, const QRect &cropRect)
{
KisBaseRectsWalkerSP walker = new KisFullRefreshWalker(cropRect);
walker->collectRects(root, rc);
bool needLock = true;
if(m_d->processingBlocked) {
warnImage << "WARNING: Calling synchronous fullRefresh under a scheduler lock held";
warnImage << "We will not assert for now, but please port caller's to strokes";
warnImage << "to avoid this warning";
needLock = false;
}
if(needLock) lock();
m_d->updaterContext.lock();
Q_ASSERT(m_d->updaterContext.isJobAllowed(walker));
m_d->updaterContext.addMergeJob(walker);
m_d->updaterContext.waitForDone();
m_d->updaterContext.unlock();
if(needLock) unlock(true);
}
void KisUpdateScheduler::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
m_d->updatesQueue.addSpontaneousJob(spontaneousJob);
processQueues();
}
KisStrokeId KisUpdateScheduler::startStroke(KisStrokeStrategy *strokeStrategy)
{
KisStrokeId id = m_d->strokesQueue.startStroke(strokeStrategy);
processQueues();
return id;
}
void KisUpdateScheduler::addJob(KisStrokeId id, KisStrokeJobData *data)
{
m_d->strokesQueue.addJob(id, data);
processQueues();
}
void KisUpdateScheduler::endStroke(KisStrokeId id)
{
m_d->strokesQueue.endStroke(id);
processQueues();
}
bool KisUpdateScheduler::cancelStroke(KisStrokeId id)
{
bool result = m_d->strokesQueue.cancelStroke(id);
processQueues();
return result;
}
bool KisUpdateScheduler::tryCancelCurrentStrokeAsync()
{
return m_d->strokesQueue.tryCancelCurrentStrokeAsync();
}
UndoResult KisUpdateScheduler::tryUndoLastStrokeAsync()
{
return m_d->strokesQueue.tryUndoLastStrokeAsync();
}
bool KisUpdateScheduler::wrapAroundModeSupported() const
{
return m_d->strokesQueue.wrapAroundModeSupported();
}
void KisUpdateScheduler::setDesiredLevelOfDetail(int lod)
{
m_d->strokesQueue.setDesiredLevelOfDetail(lod);
/**
* The queue might have started an internal stroke for
* cache synchronization. Process the queues to execute
* it if needed.
*/
processQueues();
}
void KisUpdateScheduler::explicitRegenerateLevelOfDetail()
{
m_d->strokesQueue.explicitRegenerateLevelOfDetail();
// \see a comment in setDesiredLevelOfDetail()
processQueues();
}
int KisUpdateScheduler::currentLevelOfDetail() const
{
int levelOfDetail = -1;
if (levelOfDetail < 0) {
levelOfDetail = m_d->updaterContext.currentLevelOfDetail();
}
if (levelOfDetail < 0) {
levelOfDetail = m_d->updatesQueue.overrideLevelOfDetail();
}
if (levelOfDetail < 0) {
levelOfDetail = 0;
}
return levelOfDetail;
}
void KisUpdateScheduler::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory)
{
m_d->strokesQueue.setLod0ToNStrokeStrategyFactory(factory);
}
void KisUpdateScheduler::setSuspendUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory)
{
m_d->strokesQueue.setSuspendUpdatesStrokeStrategyFactory(factory);
}
void KisUpdateScheduler::setResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory)
{
m_d->strokesQueue.setResumeUpdatesStrokeStrategyFactory(factory);
}
KisPostExecutionUndoAdapter *KisUpdateScheduler::lodNPostExecutionUndoAdapter() const
{
return m_d->strokesQueue.lodNPostExecutionUndoAdapter();
}
void KisUpdateScheduler::updateSettings()
{
m_d->updatesQueue.updateSettings();
KisImageConfig config;
- m_d->balancingRatio = config.schedulerBalancingRatio();
+ m_d->defaultBalancingRatio = config.schedulerBalancingRatio();
setThreadsLimit(config.maxNumberOfThreads());
}
void KisUpdateScheduler::lock()
{
m_d->processingBlocked = true;
m_d->updaterContext.waitForDone();
}
void KisUpdateScheduler::unlock(bool resetLodLevels)
{
if (resetLodLevels) {
/**
* Legacy strokes may have changed the image while we didn't
* control it. Notify the queue to take it into account.
*/
m_d->strokesQueue.notifyUFOChangedImage();
}
m_d->processingBlocked = false;
processQueues();
}
bool KisUpdateScheduler::isIdle()
{
bool result = false;
if (tryBarrierLock()) {
result = true;
unlock(false);
}
return result;
}
void KisUpdateScheduler::waitForDone()
{
do {
processQueues();
m_d->updaterContext.waitForDone();
} while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty());
}
bool KisUpdateScheduler::tryBarrierLock()
{
if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) {
return false;
}
m_d->processingBlocked = true;
m_d->updaterContext.waitForDone();
if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) {
m_d->processingBlocked = false;
processQueues();
return false;
}
return true;
}
void KisUpdateScheduler::barrierLock()
{
do {
m_d->processingBlocked = false;
processQueues();
m_d->processingBlocked = true;
m_d->updaterContext.waitForDone();
} while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty());
}
void KisUpdateScheduler::processQueues()
{
wakeUpWaitingThreads();
if(m_d->processingBlocked) return;
if(m_d->strokesQueue.needsExclusiveAccess()) {
DEBUG_BALANCING_METRICS("STROKES", "X");
m_d->strokesQueue.processQueue(m_d->updaterContext,
!m_d->updatesQueue.isEmpty());
if(!m_d->strokesQueue.needsExclusiveAccess()) {
tryProcessUpdatesQueue();
}
}
- else if(m_d->balancingRatio * m_d->strokesQueue.sizeMetric() > m_d->updatesQueue.sizeMetric()) {
+ else if(m_d->balancingRatio() * m_d->strokesQueue.sizeMetric() > m_d->updatesQueue.sizeMetric()) {
DEBUG_BALANCING_METRICS("STROKES", "N");
m_d->strokesQueue.processQueue(m_d->updaterContext,
!m_d->updatesQueue.isEmpty());
tryProcessUpdatesQueue();
}
else {
DEBUG_BALANCING_METRICS("UPDATES", "N");
tryProcessUpdatesQueue();
m_d->strokesQueue.processQueue(m_d->updaterContext,
!m_d->updatesQueue.isEmpty());
}
progressUpdate();
}
void KisUpdateScheduler::blockUpdates()
{
m_d->updatesFinishedCondition.initWaiting();
m_d->updatesLockCounter.ref();
while(haveUpdatesRunning()) {
m_d->updatesFinishedCondition.wait();
}
m_d->updatesFinishedCondition.endWaiting();
}
void KisUpdateScheduler::unblockUpdates()
{
m_d->updatesLockCounter.deref();
processQueues();
}
void KisUpdateScheduler::wakeUpWaitingThreads()
{
if(m_d->updatesLockCounter && !haveUpdatesRunning()) {
m_d->updatesFinishedCondition.wakeAll();
}
}
void KisUpdateScheduler::tryProcessUpdatesQueue()
{
QReadLocker locker(&m_d->updatesStartLock);
if(m_d->updatesLockCounter) return;
m_d->updatesQueue.processQueue(m_d->updaterContext);
}
bool KisUpdateScheduler::haveUpdatesRunning()
{
QWriteLocker locker(&m_d->updatesStartLock);
qint32 numMergeJobs, numStrokeJobs;
m_d->updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs);
return numMergeJobs;
}
void KisUpdateScheduler::continueUpdate(const QRect &rect)
{
Q_ASSERT(m_d->projectionUpdateListener);
m_d->projectionUpdateListener->notifyProjectionUpdated(rect);
}
void KisUpdateScheduler::doSomeUsefulWork()
{
m_d->updatesQueue.optimize();
}
void KisUpdateScheduler::spareThreadAppeared()
{
processQueues();
}
KisTestableUpdateScheduler::KisTestableUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener,
qint32 threadCount)
{
Q_UNUSED(threadCount);
updateSettings();
m_d->projectionUpdateListener = projectionUpdateListener;
// The queue will update settings in a constructor itself
// m_d->updatesQueue = new KisTestableSimpleUpdateQueue();
// m_d->strokesQueue = new KisStrokesQueue();
// m_d->updaterContext = new KisTestableUpdaterContext(threadCount);
connectSignals();
}
KisTestableUpdaterContext* KisTestableUpdateScheduler::updaterContext()
{
return dynamic_cast<KisTestableUpdaterContext*>(&m_d->updaterContext);
}
KisTestableSimpleUpdateQueue* KisTestableUpdateScheduler::updateQueue()
{
return dynamic_cast<KisTestableSimpleUpdateQueue*>(&m_d->updatesQueue);
}
diff --git a/libs/image/kis_update_scheduler.h b/libs/image/kis_update_scheduler.h
index fbd394d83d..85fcc204a5 100644
--- a/libs/image/kis_update_scheduler.h
+++ b/libs/image/kis_update_scheduler.h
@@ -1,252 +1,253 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_UPDATE_SCHEDULER_H
#define __KIS_UPDATE_SCHEDULER_H
#include <QObject>
#include "kritaimage_export.h"
#include "kis_types.h"
#include "kis_image_interfaces.h"
#include "kis_stroke_strategy_factory.h"
#include "kis_strokes_queue_undo_result.h"
class QRect;
class KoProgressProxy;
class KisProjectionUpdateListener;
class KisSpontaneousJob;
class KisPostExecutionUndoAdapter;
class KRITAIMAGE_EXPORT KisUpdateScheduler : public QObject, public KisStrokesFacade
{
Q_OBJECT
public:
KisUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener, QObject *parent = 0);
~KisUpdateScheduler() override;
/**
* Set the number of threads used by the scheduler
*/
void setThreadsLimit(int value);
/**
* Return the number of threads available to the scheduler
*/
int threadsLimit() const;
/**
* Sets the proxy that is going to be notified about the progress
* of processing of the queues. If you want to switch the proxy
* on runtime, you should do it under the lock held.
*
* \see lock(), unlock()
*/
void setProgressProxy(KoProgressProxy *progressProxy);
/**
* Blocks processing of the queues.
* The function will wait until all the executing jobs
* are finished.
* NOTE: you may add new jobs while the block held, but they
* will be delayed until unlock() is called.
*
* \see unlock()
*/
void lock();
/**
* Unblocks the process and calls processQueues()
*
* \see processQueues()
*/
void unlock(bool resetLodLevels = true);
/**
* Waits until all the running jobs are finished.
*
* If some other thread adds jobs in parallel, then you may
* wait forever. If you you don't want it, consider lock() instead.
*
* \see lock()
*/
void waitForDone();
/**
* Waits until the queues become empty, then blocks the processing.
* To unblock processing you should use unlock().
*
* If some other thread adds jobs in parallel, then you may
* wait forever. If you you don't want it, consider lock() instead.
*
* \see unlock(), lock()
*/
void barrierLock();
/**
* Works like barrier lock, but returns false immediately if barrierLock
* can't be acquired.
*
* \see barrierLock()
*/
bool tryBarrierLock();
/**
* Tells if there are no strokes or updates are running at the
* moment. Internally calls to tryBarrierLock(), so it is not O(1).
*/
bool isIdle();
/**
* Blocks all the updates from execution. It doesn't affect
* strokes execution in any way. This type of lock is supposed
* to be held by the strokes themselves when they need a short
* access to some parts of the projection of the image.
*
* From all the other places you should use usual lock()/unlock()
* methods
*
* \see lock(), unlock()
*/
void blockUpdates();
/**
* Unblocks updates from execution previously locked by blockUpdates()
*
* \see blockUpdates()
*/
void unblockUpdates();
- void updateProjection(KisNodeSP node, const QRect& rc, const QRect &cropRect);
+ void updateProjection(KisNodeSP node, const QVector<QRect> &rects, const QRect &cropRect);
+ void updateProjection(KisNodeSP node, const QRect &rc, const QRect &cropRect);
void updateProjectionNoFilthy(KisNodeSP node, const QRect& rc, const QRect &cropRect);
void fullRefreshAsync(KisNodeSP root, const QRect& rc, const QRect &cropRect);
void fullRefresh(KisNodeSP root, const QRect& rc, const QRect &cropRect);
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob);
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override;
void addJob(KisStrokeId id, KisStrokeJobData *data) override;
void endStroke(KisStrokeId id) override;
bool cancelStroke(KisStrokeId id) override;
/**
* Sets the desired level of detail on which the strokes should
* work. Please note that this configuration will be applied
* starting from the next stroke. Please also note that this value
* is not guaranteed to coincide with the one returned by
* currentLevelOfDetail()
*/
void setDesiredLevelOfDetail(int lod);
/**
* Explicitly start regeneration of LoD planes of all the devices
* in the image. This call should be performed when the user is idle,
* just to make the quality of image updates better.
*/
void explicitRegenerateLevelOfDetail();
/**
* Install a factory of a stroke strategy, that will be started
* every time when the scheduler needs to synchronize LOD caches
* of all the paint devices of the image.
*/
void setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory);
/**
* Install a factory of a stroke strategy, that will be started
* every time when the scheduler needs to postpone all the updates
* of the *LOD0* strokes.
*/
void setSuspendUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory);
/**
* \see setSuspendUpdatesStrokeStrategyFactory()
*/
void setResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory);
KisPostExecutionUndoAdapter* lodNPostExecutionUndoAdapter() const;
/**
* tryCancelCurrentStrokeAsync() checks whether there is a
* *running* stroke (which is being executed at this very moment)
* which is not still open by the owner (endStroke() or
* cancelStroke() have already been called) and cancels it.
*
* \return true if some stroke has been found and cancelled
*
* \note This method is *not* part of KisStrokesFacade! It is too
* low level for KisImage. In KisImage it is combined with
* more high level requestStrokeCancellation().
*/
bool tryCancelCurrentStrokeAsync();
UndoResult tryUndoLastStrokeAsync();
bool wrapAroundModeSupported() const;
int currentLevelOfDetail() const;
protected:
// Trivial constructor for testing support
KisUpdateScheduler();
void connectSignals();
void processQueues();
protected Q_SLOTS:
/**
* Called when it is necessary to reread configuration
*/
void updateSettings();
private Q_SLOTS:
void continueUpdate(const QRect &rect);
void doSomeUsefulWork();
void spareThreadAppeared();
private:
friend class UpdatesBlockTester;
bool haveUpdatesRunning();
void tryProcessUpdatesQueue();
void wakeUpWaitingThreads();
void progressUpdate();
protected:
struct Private;
Private * const m_d;
};
class KisTestableUpdaterContext;
class KisTestableSimpleUpdateQueue;
class KRITAIMAGE_EXPORT KisTestableUpdateScheduler : public KisUpdateScheduler
{
public:
KisTestableUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener,
qint32 threadCount);
KisTestableUpdaterContext* updaterContext();
KisTestableSimpleUpdateQueue* updateQueue();
using KisUpdateScheduler::processQueues;
};
#endif /* __KIS_UPDATE_SCHEDULER_H */
diff --git a/libs/image/kis_updater_context.cpp b/libs/image/kis_updater_context.cpp
index 3fdc0331b3..222f95d68c 100644
--- a/libs/image/kis_updater_context.cpp
+++ b/libs/image/kis_updater_context.cpp
@@ -1,287 +1,322 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_updater_context.h"
#include <QThread>
#include <QThreadPool>
#include "kis_update_job_item.h"
#include "kis_stroke_job.h"
const int KisUpdaterContext::useIdealThreadCountTag = -1;
KisUpdaterContext::KisUpdaterContext(qint32 threadCount, QObject *parent)
: QObject(parent)
{
if(threadCount <= 0) {
threadCount = QThread::idealThreadCount();
threadCount = threadCount > 0 ? threadCount : 1;
}
setThreadsLimit(threadCount);
}
KisUpdaterContext::~KisUpdaterContext()
{
m_threadPool.waitForDone();
for(qint32 i = 0; i < m_jobs.size(); i++)
delete m_jobs[i];
}
void KisUpdaterContext::getJobsSnapshot(qint32 &numMergeJobs,
qint32 &numStrokeJobs)
{
numMergeJobs = 0;
numStrokeJobs = 0;
Q_FOREACH (const KisUpdateJobItem *item, m_jobs) {
- if(item->type() == KisUpdateJobItem::MERGE ||
- item->type() == KisUpdateJobItem::SPONTANEOUS) {
+ if(item->type() == KisUpdateJobItem::Type::MERGE ||
+ item->type() == KisUpdateJobItem::Type::SPONTANEOUS) {
numMergeJobs++;
}
- else if(item->type() == KisUpdateJobItem::STROKE) {
+ else if(item->type() == KisUpdateJobItem::Type::STROKE) {
numStrokeJobs++;
}
}
}
+KisUpdaterContextSnapshotEx KisUpdaterContext::getContextSnapshotEx() const
+{
+ KisUpdaterContextSnapshotEx state = ContextEmpty;
+
+ Q_FOREACH (const KisUpdateJobItem *item, m_jobs) {
+ if (item->type() == KisUpdateJobItem::Type::MERGE ||
+ item->type() == KisUpdateJobItem::Type::SPONTANEOUS) {
+ state |= HasMergeJob;
+ } else if(item->type() == KisUpdateJobItem::Type::STROKE) {
+ switch (item->strokeJobSequentiality()) {
+ case KisStrokeJobData::SEQUENTIAL:
+ state |= HasSequentialJob;
+ break;
+ case KisStrokeJobData::CONCURRENT:
+ state |= HasConcurrentJob;
+ break;
+ case KisStrokeJobData::BARRIER:
+ state |= HasBarrierJob;
+ break;
+ case KisStrokeJobData::UNIQUELY_CONCURRENT:
+ state |= HasUniquelyConcurrentJob;
+ break;
+ }
+ }
+ }
+
+ return state;
+}
+
int KisUpdaterContext::currentLevelOfDetail() const
{
return m_lodCounter.readLod();
}
bool KisUpdaterContext::hasSpareThread()
{
bool found = false;
Q_FOREACH (const KisUpdateJobItem *item, m_jobs) {
if(!item->isRunning()) {
found = true;
break;
}
}
return found;
}
bool KisUpdaterContext::isJobAllowed(KisBaseRectsWalkerSP walker)
{
int lod = this->currentLevelOfDetail();
if (lod >= 0 && walker->levelOfDetail() != lod) return false;
bool intersects = false;
Q_FOREACH (const KisUpdateJobItem *item, m_jobs) {
if(item->isRunning() && walkerIntersectsJob(walker, item)) {
intersects = true;
break;
}
}
return !intersects;
}
/**
* NOTE: In theory, isJobAllowed() and addMergeJob() should be merged into
* one atomic method like `bool push()`, because this implementation
* of KisUpdaterContext will not work in case of multiple
* producers. But currently we have only one producer (one thread
* in a time), that is guaranteed by the lock()/unlock() pair in
* KisAbstractUpdateQueue::processQueue.
*/
void KisUpdaterContext::addMergeJob(KisBaseRectsWalkerSP walker)
{
m_lodCounter.addLod(walker->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
- m_jobs[jobIndex]->setWalker(walker);
+ const bool shouldStartThread = m_jobs[jobIndex]->setWalker(walker);
// it might happen that we call this function from within
// the thread itself, right when it finished its work
- if (!m_jobs[jobIndex]->hasThreadAttached()) {
+ if (shouldStartThread) {
m_threadPool.start(m_jobs[jobIndex]);
}
}
/**
* This variant is for use in a testing suite only
*/
void KisTestableUpdaterContext::addMergeJob(KisBaseRectsWalkerSP walker)
{
m_lodCounter.addLod(walker->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
- m_jobs[jobIndex]->setWalker(walker);
+ const bool shouldStartThread = m_jobs[jobIndex]->setWalker(walker);
+
// HINT: Not calling start() here
+ Q_UNUSED(shouldStartThread);
}
void KisUpdaterContext::addStrokeJob(KisStrokeJob *strokeJob)
{
m_lodCounter.addLod(strokeJob->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
- m_jobs[jobIndex]->setStrokeJob(strokeJob);
+ const bool shouldStartThread = m_jobs[jobIndex]->setStrokeJob(strokeJob);
// it might happen that we call this function from within
// the thread itself, right when it finished its work
- if (!m_jobs[jobIndex]->hasThreadAttached()) {
+ if (shouldStartThread) {
m_threadPool.start(m_jobs[jobIndex]);
}
}
/**
* This variant is for use in a testing suite only
*/
void KisTestableUpdaterContext::addStrokeJob(KisStrokeJob *strokeJob)
{
m_lodCounter.addLod(strokeJob->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
- m_jobs[jobIndex]->setStrokeJob(strokeJob);
+ const bool shouldStartThread = m_jobs[jobIndex]->setStrokeJob(strokeJob);
+
// HINT: Not calling start() here
+ Q_UNUSED(shouldStartThread);
}
void KisUpdaterContext::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
m_lodCounter.addLod(spontaneousJob->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
- m_jobs[jobIndex]->setSpontaneousJob(spontaneousJob);
+ const bool shouldStartThread = m_jobs[jobIndex]->setSpontaneousJob(spontaneousJob);
// it might happen that we call this function from within
// the thread itself, right when it finished its work
- if (!m_jobs[jobIndex]->hasThreadAttached()) {
+ if (shouldStartThread) {
m_threadPool.start(m_jobs[jobIndex]);
}
}
/**
* This variant is for use in a testing suite only
*/
void KisTestableUpdaterContext::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
m_lodCounter.addLod(spontaneousJob->levelOfDetail());
qint32 jobIndex = findSpareThread();
Q_ASSERT(jobIndex >= 0);
- m_jobs[jobIndex]->setSpontaneousJob(spontaneousJob);
+ const bool shouldStartThread = m_jobs[jobIndex]->setSpontaneousJob(spontaneousJob);
+
// HINT: Not calling start() here
+ Q_UNUSED(shouldStartThread);
}
void KisUpdaterContext::waitForDone()
{
m_threadPool.waitForDone();
}
bool KisUpdaterContext::walkerIntersectsJob(KisBaseRectsWalkerSP walker,
const KisUpdateJobItem* job)
{
return (walker->accessRect().intersects(job->changeRect())) ||
(job->accessRect().intersects(walker->changeRect()));
}
qint32 KisUpdaterContext::findSpareThread()
{
for(qint32 i=0; i < m_jobs.size(); i++)
if(!m_jobs[i]->isRunning())
return i;
return -1;
}
void KisUpdaterContext::slotJobFinished()
{
m_lodCounter.removeLod();
// Be careful. This slot can be called asynchronously without locks.
emit sigSpareThreadAppeared();
}
void KisUpdaterContext::lock()
{
m_lock.lock();
}
void KisUpdaterContext::unlock()
{
m_lock.unlock();
}
void KisUpdaterContext::setThreadsLimit(int value)
{
m_threadPool.setMaxThreadCount(value);
for (int i = 0; i < m_jobs.size(); i++) {
KIS_SAFE_ASSERT_RECOVER_RETURN(!m_jobs[i]->isRunning());
// don't delete the jobs until all of them are checked!
}
for (int i = 0; i < m_jobs.size(); i++) {
delete m_jobs[i];
}
m_jobs.resize(value);
for(qint32 i = 0; i < m_jobs.size(); i++) {
m_jobs[i] = new KisUpdateJobItem(&m_exclusiveJobLock);
connect(m_jobs[i], SIGNAL(sigContinueUpdate(const QRect&)),
SIGNAL(sigContinueUpdate(const QRect&)),
Qt::DirectConnection);
connect(m_jobs[i], SIGNAL(sigDoSomeUsefulWork()),
SIGNAL(sigDoSomeUsefulWork()), Qt::DirectConnection);
connect(m_jobs[i], SIGNAL(sigJobFinished()),
SLOT(slotJobFinished()), Qt::DirectConnection);
}
}
int KisUpdaterContext::threadsLimit() const
{
KIS_SAFE_ASSERT_RECOVER_NOOP(m_jobs.size() == m_threadPool.maxThreadCount());
return m_jobs.size();
}
KisTestableUpdaterContext::KisTestableUpdaterContext(qint32 threadCount)
: KisUpdaterContext(threadCount)
{
}
KisTestableUpdaterContext::~KisTestableUpdaterContext() {
clear();
}
const QVector<KisUpdateJobItem*> KisTestableUpdaterContext::getJobs()
{
return m_jobs;
}
void KisTestableUpdaterContext::clear()
{
Q_FOREACH (KisUpdateJobItem *item, m_jobs) {
item->testingSetDone();
}
m_lodCounter.testingClear();
}
diff --git a/libs/image/kis_updater_context.h b/libs/image/kis_updater_context.h
index 55a02271a8..15421d6516 100644
--- a/libs/image/kis_updater_context.h
+++ b/libs/image/kis_updater_context.h
@@ -1,190 +1,195 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_UPDATER_CONTEXT_H
#define __KIS_UPDATER_CONTEXT_H
#include <QObject>
#include <QMutex>
#include <QReadWriteLock>
#include <QThreadPool>
#include "kis_base_rects_walker.h"
#include "kis_async_merger.h"
#include "kis_lock_free_lod_counter.h"
+#include "KisUpdaterContextSnapshotEx.h"
class KisUpdateJobItem;
class KisSpontaneousJob;
class KisStrokeJob;
class KRITAIMAGE_EXPORT KisUpdaterContext : public QObject
{
Q_OBJECT
public:
static const int useIdealThreadCountTag;
public:
KisUpdaterContext(qint32 threadCount = useIdealThreadCountTag, QObject *parent = 0);
~KisUpdaterContext() override;
/**
* Returns the number of currently running jobs of each type.
* To use this information you should lock the context beforehand.
*
* \see lock()
*/
void getJobsSnapshot(qint32 &numMergeJobs, qint32 &numStrokeJobs);
+ KisUpdaterContextSnapshotEx getContextSnapshotEx() const;
+
/**
* Returns the current level of detail of all the running jobs in the
* context. If there are no jobs, returns -1.
*/
int currentLevelOfDetail() const;
/**
* Check whether there is a spare thread for running
* one more job
*/
bool hasSpareThread();
/**
* Checks whether the walker intersects with any
* of currently executing walkers. If it does,
* it is not allowed to go in. It should be called
* with the lock held.
*
* \see lock()
*/
bool isJobAllowed(KisBaseRectsWalkerSP walker);
/**
* Registers the job and starts executing it.
* The caller must ensure that the context is locked
* with lock(), job is allowed with isWalkerAllowed() and
* there is a spare thread for running it with hasSpareThread()
*
* \see lock()
* \see isWalkerAllowed()
* \see hasSpareThread()
*/
virtual void addMergeJob(KisBaseRectsWalkerSP walker);
/**
* Adds a stroke job to the context. The prerequisites are
* the same as for addMergeJob()
* \see addMergeJob()
*/
virtual void addStrokeJob(KisStrokeJob *strokeJob);
/**
* Adds a spontaneous job to the context. The prerequisites are
* the same as for addMergeJob()
* \see addMergeJob()
*/
virtual void addSpontaneousJob(KisSpontaneousJob *spontaneousJob);
/**
* Block execution of the caller until all the jobs are finished
*/
void waitForDone();
/**
* Locks the context to guarantee an exclusive access
* to the context
*/
void lock();
/**
* Unlocks the context
*
* \see lock()
*/
void unlock();
/**
* Set the number of threads available for this updater context
* WARNING: one cannot change the number of threads if there is
* at least one job running in the context! So before
* calling this method make sure you do two things:
* 1) barrierLock() the update scheduler
* 2) lock() the context
*/
void setThreadsLimit(int value);
/**
* Return the number of available threads in the context. Make sure you
* lock the context before calling this function!
*/
int threadsLimit() const;
Q_SIGNALS:
void sigContinueUpdate(const QRect& rc);
void sigDoSomeUsefulWork();
void sigSpareThreadAppeared();
protected Q_SLOTS:
void slotJobFinished();
protected:
static bool walkerIntersectsJob(KisBaseRectsWalkerSP walker,
const KisUpdateJobItem* job);
qint32 findSpareThread();
protected:
/**
* The lock is shared by all the child update job items.
* When an item wants to run a usual (non-exclusive) job,
* it locks the lock for read access. When an exclusive
* access is requested, it locks it for write
*/
QReadWriteLock m_exclusiveJobLock;
QMutex m_lock;
QVector<KisUpdateJobItem*> m_jobs;
QThreadPool m_threadPool;
KisLockFreeLodCounter m_lodCounter;
};
class KRITAIMAGE_EXPORT KisTestableUpdaterContext : public KisUpdaterContext
{
public:
/**
* Creates an explicit number of threads
*/
KisTestableUpdaterContext(qint32 threadCount);
~KisTestableUpdaterContext() override;
/**
* The only difference - it doesn't start execution
* of the job
*/
void addMergeJob(KisBaseRectsWalkerSP walker) override;
void addStrokeJob(KisStrokeJob *strokeJob) override;
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob) override;
const QVector<KisUpdateJobItem*> getJobs();
void clear();
};
+
+
#endif /* __KIS_UPDATER_CONTEXT_H */
diff --git a/libs/image/krita_utils.cpp b/libs/image/krita_utils.cpp
index bd419d1367..54ee2e43ae 100644
--- a/libs/image/krita_utils.cpp
+++ b/libs/image/krita_utils.cpp
@@ -1,444 +1,474 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "krita_utils.h"
#include <QtCore/qmath.h>
#include <QRect>
#include <QRegion>
#include <QPainterPath>
#include <QPolygonF>
#include <QPen>
#include <QPainter>
#include "kis_algebra_2d.h"
#include <KoColorSpaceRegistry.h>
#include "kis_image_config.h"
#include "kis_debug.h"
#include "kis_node.h"
#include "kis_sequential_iterator.h"
#include "kis_random_accessor_ng.h"
+#include <KisRenderedDab.h>
+
namespace KritaUtils
{
QSize optimalPatchSize()
{
KisImageConfig cfg;
return QSize(cfg.updatePatchWidth(),
cfg.updatePatchHeight());
}
QVector<QRect> splitRectIntoPatches(const QRect &rc, const QSize &patchSize)
{
QVector<QRect> patches;
qint32 firstCol = rc.x() / patchSize.width();
qint32 firstRow = rc.y() / patchSize.height();
qint32 lastCol = (rc.x() + rc.width()) / patchSize.width();
qint32 lastRow = (rc.y() + rc.height()) / patchSize.height();
for(qint32 i = firstRow; i <= lastRow; i++) {
for(qint32 j = firstCol; j <= lastCol; j++) {
QRect maxPatchRect(j * patchSize.width(), i * patchSize.height(),
patchSize.width(), patchSize.height());
QRect patchRect = rc & maxPatchRect;
if (!patchRect.isEmpty()) {
patches.append(patchRect);
}
}
}
return patches;
}
QVector<QRect> splitRegionIntoPatches(const QRegion &region, const QSize &patchSize)
{
QVector<QRect> patches;
Q_FOREACH (const QRect rect, region.rects()) {
patches << KritaUtils::splitRectIntoPatches(rect, patchSize);
}
return patches;
}
bool checkInTriangle(const QRectF &rect,
const QPolygonF &triangle)
{
return triangle.intersected(rect).boundingRect().isValid();
}
QRegion KRITAIMAGE_EXPORT splitTriangles(const QPointF &center,
const QVector<QPointF> &points)
{
Q_ASSERT(points.size());
Q_ASSERT(!(points.size() & 1));
QVector<QPolygonF> triangles;
QRect totalRect;
for (int i = 0; i < points.size(); i += 2) {
QPolygonF triangle;
triangle << center;
triangle << points[i];
triangle << points[i+1];
totalRect |= triangle.boundingRect().toAlignedRect();
triangles << triangle;
}
const int step = 64;
const int right = totalRect.x() + totalRect.width();
const int bottom = totalRect.y() + totalRect.height();
QRegion dirtyRegion;
for (int y = totalRect.y(); y < bottom;) {
int nextY = qMin((y + step) & ~(step-1), bottom);
for (int x = totalRect.x(); x < right;) {
int nextX = qMin((x + step) & ~(step-1), right);
QRect rect(x, y, nextX - x, nextY - y);
Q_FOREACH (const QPolygonF &triangle, triangles) {
if(checkInTriangle(rect, triangle)) {
dirtyRegion |= rect;
break;
}
}
x = nextX;
}
y = nextY;
}
return dirtyRegion;
}
QRegion KRITAIMAGE_EXPORT splitPath(const QPainterPath &path)
{
QRect totalRect = path.boundingRect().toAlignedRect();
// adjust the rect for antialiasing to work
totalRect = totalRect.adjusted(-1,-1,1,1);
const int step = 64;
const int right = totalRect.x() + totalRect.width();
const int bottom = totalRect.y() + totalRect.height();
QRegion dirtyRegion;
for (int y = totalRect.y(); y < bottom;) {
int nextY = qMin((y + step) & ~(step-1), bottom);
for (int x = totalRect.x(); x < right;) {
int nextX = qMin((x + step) & ~(step-1), right);
QRect rect(x, y, nextX - x, nextY - y);
if(path.intersects(rect)) {
dirtyRegion |= rect;
}
x = nextX;
}
y = nextY;
}
return dirtyRegion;
}
QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value)
{
return QString("%1").arg(value, 6, 'f', 1);
}
qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue)
{
qreal maxDimension = qMax(bounds.width(), bounds.height());
return qMax(portion * maxDimension, minValue);
}
bool tryMergePoints(QPainterPath &path,
const QPointF &startPoint,
const QPointF &endPoint,
qreal &distance,
qreal distanceThreshold,
bool lastSegment)
{
qreal length = (endPoint - startPoint).manhattanLength();
if (lastSegment || length > distanceThreshold) {
if (lastSegment) {
qreal wrappedLength =
(endPoint - QPointF(path.elementAt(0))).manhattanLength();
if (length < distanceThreshold ||
wrappedLength < distanceThreshold) {
return true;
}
}
distance = 0;
return false;
}
distance += length;
if (distance > distanceThreshold) {
path.lineTo(endPoint);
distance = 0;
}
return true;
}
QPainterPath trySimplifyPath(const QPainterPath &path, qreal lengthThreshold)
{
QPainterPath newPath;
QPointF startPoint;
qreal distance = 0;
int count = path.elementCount();
for (int i = 0; i < count; i++) {
QPainterPath::Element e = path.elementAt(i);
QPointF endPoint = QPointF(e.x, e.y);
switch (e.type) {
case QPainterPath::MoveToElement:
newPath.moveTo(endPoint);
break;
case QPainterPath::LineToElement:
if (!tryMergePoints(newPath, startPoint, endPoint,
distance, lengthThreshold, i == count - 1)) {
newPath.lineTo(endPoint);
}
break;
case QPainterPath::CurveToElement: {
Q_ASSERT(i + 2 < count);
if (!tryMergePoints(newPath, startPoint, endPoint,
distance, lengthThreshold, i == count - 1)) {
e = path.elementAt(i + 1);
Q_ASSERT(e.type == QPainterPath::CurveToDataElement);
QPointF ctrl1 = QPointF(e.x, e.y);
e = path.elementAt(i + 2);
Q_ASSERT(e.type == QPainterPath::CurveToDataElement);
QPointF ctrl2 = QPointF(e.x, e.y);
newPath.cubicTo(ctrl1, ctrl2, endPoint);
}
i += 2;
}
default:
;
}
startPoint = endPoint;
}
return newPath;
}
QList<QPainterPath> splitDisjointPaths(const QPainterPath &path)
{
QList<QPainterPath> resultList;
QList<QPolygonF> inputPolygons = path.toSubpathPolygons();
Q_FOREACH (const QPolygonF &poly, inputPolygons) {
QPainterPath testPath;
testPath.addPolygon(poly);
if (resultList.isEmpty()) {
resultList.append(testPath);
continue;
}
QPainterPath mergedPath = testPath;
for (auto it = resultList.begin(); it != resultList.end(); /*noop*/) {
if (it->intersects(testPath)) {
mergedPath.addPath(*it);
it = resultList.erase(it);
} else {
++it;
}
}
resultList.append(mergedPath);
}
return resultList;
}
quint8 mergeOpacity(quint8 opacity, quint8 parentOpacity)
{
if (parentOpacity != OPACITY_OPAQUE_U8) {
opacity = (int(opacity) * parentOpacity) / OPACITY_OPAQUE_U8;
}
return opacity;
}
QBitArray mergeChannelFlags(const QBitArray &childFlags, const QBitArray &parentFlags)
{
QBitArray flags = childFlags;
if (!flags.isEmpty() &&
!parentFlags.isEmpty() &&
flags.size() == parentFlags.size()) {
flags &= parentFlags;
} else if (!parentFlags.isEmpty()) {
flags = parentFlags;
}
return flags;
}
bool compareChannelFlags(QBitArray f1, QBitArray f2)
{
if (f1.isNull() && f2.isNull()) return true;
if (f1.isNull()) {
f1.fill(true, f2.size());
}
if (f2.isNull()) {
f2.fill(true, f1.size());
}
return f1 == f2;
}
QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value) {
return value ? i18n("on") : i18n("off");
}
KisNodeSP nearestNodeAfterRemoval(KisNodeSP node)
{
KisNodeSP newNode = node->nextSibling();
if (!newNode) {
newNode = node->prevSibling();
}
if (!newNode) {
newNode = node->parent();
}
return newNode;
}
void renderExactRect(QPainter *p, const QRect &rc)
{
p->drawRect(rc.adjusted(0,0,-1,-1));
}
void renderExactRect(QPainter *p, const QRect &rc, const QPen &pen)
{
QPen oldPen = p->pen();
p->setPen(pen);
renderExactRect(p, rc);
p->setPen(oldPen);
}
QImage convertQImageToGrayA(const QImage &image)
{
QImage dstImage(image.size(), QImage::Format_ARGB32);
// TODO: if someone feel bored, a more optimized version of this would be welcome
const QSize size = image.size();
for(int i = 0; i < size.height(); ++i) {
for(int j = 0; j < size.width(); ++j) {
const QRgb pixel = image.pixel(i,j);
const int gray = qGray(pixel);
dstImage.setPixel(i, j, qRgba(gray, gray, gray, qAlpha(pixel)));
}
}
return dstImage;
}
QColor blendColors(const QColor &c1, const QColor &c2, qreal r1)
{
const qreal r2 = 1.0 - r1;
return QColor::fromRgbF(
c1.redF() * r1 + c2.redF() * r2,
c1.greenF() * r1 + c2.greenF() * r2,
c1.blueF() * r1 + c2.blueF() * r2);
}
void applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<void(quint8)> func) {
KisSequentialConstIterator dstIt(dev, rc);
do {
const quint8 *dstPtr = dstIt.rawDataConst();
func(*dstPtr);
} while (dstIt.nextPixel());
}
void filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<quint8(quint8)> func) {
KisSequentialIterator dstIt(dev, rc);
do {
quint8 *dstPtr = dstIt.rawData();
*dstPtr = func(*dstPtr);
} while (dstIt.nextPixel());
}
qreal estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion) {
const KoColorSpace *cs = dev->colorSpace();
const qreal linearPortion = std::sqrt(samplePortion);
const qreal ratio = qreal(rect.width()) / rect.height();
const int xStep = qMax(1, qRound(1.0 / linearPortion * ratio));
const int yStep = qMax(1, qRound(1.0 / linearPortion / ratio));
int numTransparentPixels = 0;
int numPixels = 0;
KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG(rect.x(), rect.y());
for (int y = rect.y(); y <= rect.bottom(); y += yStep) {
for (int x = rect.x(); x <= rect.right(); x += xStep) {
it->moveTo(x, y);
const quint8 alpha = cs->opacityU8(it->rawDataConst());
if (alpha != OPACITY_OPAQUE_U8) {
numTransparentPixels++;
}
numPixels++;
}
}
return qreal(numTransparentPixels) / numPixels;
}
+
+ void mirrorDab(Qt::Orientation dir, const QPoint &center, KisRenderedDab *dab)
+ {
+ const QRect rc = dab->realBounds();
+
+ if (dir == Qt::Horizontal) {
+ const int mirrorX = -((rc.x() + rc.width()) - center.x()) + center.x();
+
+ dab->device->mirror(true, false);
+ dab->offset.rx() = mirrorX;
+ } else /* if (dir == Qt::Vertical) */ {
+ const int mirrorY = -((rc.y() + rc.height()) - center.y()) + center.y();
+
+ dab->device->mirror(false, true);
+ dab->offset.ry() = mirrorY;
+ }
+ }
+
+ void mirrorRect(Qt::Orientation dir, const QPoint &center, QRect *rc)
+ {
+ if (dir == Qt::Horizontal) {
+ const int mirrorX = -((rc->x() + rc->width()) - center.x()) + center.x();
+ rc->moveLeft(mirrorX);
+ } else /* if (dir == Qt::Vertical) */ {
+ const int mirrorY = -((rc->y() + rc->height()) - center.y()) + center.y();
+ rc->moveTop(mirrorY);
+ }
+ }
}
diff --git a/libs/image/krita_utils.h b/libs/image/krita_utils.h
index 45e6fab3e0..cc3e04d740 100644
--- a/libs/image/krita_utils.h
+++ b/libs/image/krita_utils.h
@@ -1,101 +1,105 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KRITA_UTILS_H
#define __KRITA_UTILS_H
class QRect;
class QRectF;
class QSize;
class QPen;
class QPointF;
class QPainterPath;
class QBitArray;
class QPainter;
+class KisRenderedDab;
#include <QVector>
#include "kritaimage_export.h"
#include "kis_types.h"
#include "krita_container_utils.h"
#include <functional>
namespace KritaUtils
{
QSize KRITAIMAGE_EXPORT optimalPatchSize();
QVector<QRect> KRITAIMAGE_EXPORT splitRectIntoPatches(const QRect &rc, const QSize &patchSize);
QVector<QRect> KRITAIMAGE_EXPORT splitRegionIntoPatches(const QRegion &region, const QSize &patchSize);
QRegion KRITAIMAGE_EXPORT splitTriangles(const QPointF &center,
const QVector<QPointF> &points);
QRegion KRITAIMAGE_EXPORT splitPath(const QPainterPath &path);
QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value);
qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue);
QPainterPath KRITAIMAGE_EXPORT trySimplifyPath(const QPainterPath &path, qreal lengthThreshold);
/**
* Split a path \p path into a set of disjoint (non-intersectable)
* paths if possible.
*
* It tries to follow odd-even fill rule, but has a small problem:
* If you have three selections included into each other twice,
* then the smallest selection will be included into the final subpath,
* although it shouldn't according to odd-even-fill rule. It is still
* to be fixed.
*/
QList<QPainterPath> KRITAIMAGE_EXPORT splitDisjointPaths(const QPainterPath &path);
quint8 KRITAIMAGE_EXPORT mergeOpacity(quint8 opacity, quint8 parentOpacity);
QBitArray KRITAIMAGE_EXPORT mergeChannelFlags(const QBitArray &flags, const QBitArray &parentFlags);
bool KRITAIMAGE_EXPORT compareChannelFlags(QBitArray f1, QBitArray f2);
QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value);
KisNodeSP KRITAIMAGE_EXPORT nearestNodeAfterRemoval(KisNodeSP node);
/**
* When drawing a rect Qt uses quite a weird algorithm. It
* draws 4 lines:
* o at X-es: rect.x() and rect.right() + 1
* o at Y-s: rect.y() and rect.bottom() + 1
*
* Which means that bottom and right lines of the rect are painted
* outside the virtual rectangle the rect defines. This methods overcome this issue by
* painting the adjusted rect.
*/
void KRITAIMAGE_EXPORT renderExactRect(QPainter *p, const QRect &rc);
/**
* \see renderExactRect(QPainter *p, const QRect &rc)
*/
void KRITAIMAGE_EXPORT renderExactRect(QPainter *p, const QRect &rc, const QPen &pen);
QImage KRITAIMAGE_EXPORT convertQImageToGrayA(const QImage &image);
QColor KRITAIMAGE_EXPORT blendColors(const QColor &c1, const QColor &c2, qreal r1);
void KRITAIMAGE_EXPORT applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<void(quint8)> func);
void KRITAIMAGE_EXPORT filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<quint8(quint8)> func);
qreal KRITAIMAGE_EXPORT estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion);
+
+ void KRITAIMAGE_EXPORT mirrorDab(Qt::Orientation dir, const QPoint &center, KisRenderedDab *dab);
+ void KRITAIMAGE_EXPORT mirrorRect(Qt::Orientation dir, const QPoint &center, QRect *rc);
}
#endif /* __KRITA_UTILS_H */
diff --git a/libs/image/metadata/kis_meta_data_value.cc b/libs/image/metadata/kis_meta_data_value.cc
index 408cbffe1c..31e4dd6674 100644
--- a/libs/image/metadata/kis_meta_data_value.cc
+++ b/libs/image/metadata/kis_meta_data_value.cc
@@ -1,462 +1,468 @@
/*
* Copyright (c) 2007,2010 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_meta_data_value.h"
#include <QDate>
#include <QPoint>
#include <QPointF>
#include <QRegExp>
#include <QVariant>
#include <QStringList>
#include <klocalizedstring.h>
#include <kis_debug.h>
using namespace KisMetaData;
struct Q_DECL_HIDDEN Value::Private {
Private() : type(Invalid) {}
union {
QVariant* variant;
QList<Value>* array;
QMap<QString, Value>* structure;
KisMetaData::Rational* rational;
} value;
ValueType type;
QMap<QString, Value> propertyQualifiers;
};
Value::Value() : d(new Private)
{
d->type = Invalid;
}
Value::Value(const QVariant& variant) : d(new Private)
{
d->type = Value::Variant;
d->value.variant = new QVariant(variant);
}
Value::Value(const QList<Value>& array, ValueType type) : d(new Private)
{
Q_ASSERT(type == OrderedArray || type == UnorderedArray || type == AlternativeArray || type == LangArray);
d->value.array = new QList<Value>(array);
d->type = type; // TODO: I am hesitating about LangArray to keep them as array or convert them to maps
}
Value::Value(const QMap<QString, Value>& structure) : d(new Private)
{
d->type = Structure;
d->value.structure = new QMap<QString, Value>(structure);
}
Value::Value(const KisMetaData::Rational& signedRational) : d(new Private)
{
d->type = Value::Rational;
d->value.rational = new KisMetaData::Rational(signedRational);
}
Value::Value(const Value& v) : d(new Private)
{
d->type = Invalid;
*this = v;
}
Value& Value::operator=(const Value & v)
{
d->type = v.d->type;
d->propertyQualifiers = v.d->propertyQualifiers;
switch (d->type) {
case Invalid:
break;
case Variant:
d->value.variant = new QVariant(*v.d->value.variant);
break;
case OrderedArray:
case UnorderedArray:
case AlternativeArray:
case LangArray:
d->value.array = new QList<Value>(*v.d->value.array);
break;
case Structure:
d->value.structure = new QMap<QString, Value>(*v.d->value.structure);
break;
case Rational:
d->value.rational = new KisMetaData::Rational(*v.d->value.rational);
}
return *this;
}
Value::~Value()
{
delete d;
}
void Value::addPropertyQualifier(const QString& _name, const Value& _value)
{
d->propertyQualifiers[_name] = _value;
}
const QMap<QString, Value>& Value::propertyQualifiers() const
{
return d->propertyQualifiers;
}
Value::ValueType Value::type() const
{
return d->type;
}
double Value::asDouble() const
{
switch (type()) {
case Variant:
return d->value.variant->toDouble(0);
case Rational:
return d->value.rational->numerator / (double)d->value.rational->denominator;
default:
return 0.0;
}
return 0.0;
}
int Value::asInteger() const
{
switch (type()) {
case Variant:
return d->value.variant->toInt(0);
case Rational:
return d->value.rational->numerator / d->value.rational->denominator;
default:
return 0;
}
return 0;
}
QVariant Value::asVariant() const
{
switch (type()) {
case Variant:
return *d->value.variant;
case Rational:
return QVariant(QString("%1 / %2").arg(d->value.rational->numerator).arg(d->value.rational->denominator));
default: break;
}
return QVariant();
}
bool Value::setVariant(const QVariant& variant)
{
switch (type()) {
case KisMetaData::Value::Invalid:
*this = KisMetaData::Value(variant);
return true;
case Rational: {
QRegExp rx("([^\\/]*)\\/([^\\/]*)");
rx.indexIn(variant.toString());
}
case KisMetaData::Value::Variant: {
if (d->value.variant->type() == variant.type()) {
*d->value.variant = variant;
return true;
}
}
return true;
default:
break;
}
return false;
}
bool Value::setStructureVariant(const QString& fieldNAme, const QVariant& variant)
{
if (type() == Structure) {
return (*d->value.structure)[fieldNAme].setVariant(variant);
}
return false;
}
bool Value::setArrayVariant(int index, const QVariant& variant)
{
if (isArray()) {
for (int i = d->value.array->size(); i <= index; ++i) {
d->value.array->append(Value());
}
(*d->value.array)[index].setVariant(variant);
}
return false;
}
KisMetaData::Rational Value::asRational() const
{
if (d->type == Rational) {
return *d->value.rational;
}
return KisMetaData::Rational();
}
QList<Value> Value::asArray() const
{
if (isArray()) {
return *d->value.array;
}
return QList<Value>();
}
bool Value::isArray() const
{
return type() == OrderedArray || type() == UnorderedArray || type() == AlternativeArray;
}
QMap<QString, KisMetaData::Value> Value::asStructure() const
{
if (type() == Structure) {
return *d->value.structure;
}
return QMap<QString, KisMetaData::Value>();
}
QDebug operator<<(QDebug debug, const Value &v)
{
switch (v.type()) {
case Value::Invalid:
debug.nospace() << "invalid value";
break;
case Value::Variant:
debug.nospace() << "Variant: " << v.asVariant();
break;
case Value::OrderedArray:
case Value::UnorderedArray:
case Value::AlternativeArray:
case Value::LangArray:
debug.nospace() << "Array: " << v.asArray();
break;
case Value::Structure:
debug.nospace() << "Structure: " << v.asStructure();
break;
case Value::Rational:
debug.nospace() << "Rational: " << v.asRational().numerator << " / " << v.asRational().denominator;
break;
}
return debug.space();
}
bool Value::operator==(const Value& rhs) const
{
if (d->type != rhs.d->type) return false;
switch (d->type) {
case Value::Invalid:
return true;
case Value::Variant:
return asVariant() == rhs.asVariant();
case Value::OrderedArray:
case Value::UnorderedArray:
case Value::AlternativeArray:
case Value::LangArray:
return asArray() == rhs.asArray();
case Value::Structure:
return asStructure() == rhs.asStructure();
case Value::Rational:
return asRational() == rhs.asRational();
}
return false;
}
Value& Value::operator+=(const Value & v)
{
switch (d->type) {
case Value::Invalid:
Q_ASSERT(v.type() == Value::Invalid);
break;
case Value::Variant:
Q_ASSERT(v.type() == Value::Variant);
{
QVariant v1 = *d->value.variant;
QVariant v2 = *v.d->value.variant;
Q_ASSERT(v2.canConvert(v1.type()));
switch (v1.type()) {
default:
break;
case QVariant::Date: {
QDate date;
date.fromJulianDay(v1.toDate().toJulianDay()
+ v2.toDate().toJulianDay());
*d->value.variant = date;
}
break;
case QVariant::DateTime: {
QDateTime dt;
+#if QT_VERSION >= 0x050900
+ dt.fromSecsSinceEpoch(
+ v1.toDateTime().toSecsSinceEpoch()
+ + v2.toDateTime().toSecsSinceEpoch());
+#else
dt.fromTime_t(
v1.toDateTime().toTime_t()
+ v2.toDateTime().toTime_t());
+#endif
*d->value.variant = dt;
}
break;
case QVariant::Double:
*d->value.variant = v1.toDouble() + v2.toDouble();
break;
case QVariant::Int:
*d->value.variant = v1.toInt() + v2.toInt();
break;
case QVariant::List:
*d->value.variant = v1.toList() + v2.toList();
break;
case QVariant::LongLong:
*d->value.variant = v1.toLongLong() + v2.toLongLong();
break;
case QVariant::Point:
*d->value.variant = v1.toPoint() + v2.toPoint();
break;
case QVariant::PointF:
*d->value.variant = v1.toPointF() + v2.toPointF();
break;
case QVariant::String:
*d->value.variant = QVariant(v1.toString() + v2.toString());
break;
case QVariant::StringList:
*d->value.variant = v1.toStringList() + v2.toStringList();
break;
case QVariant::Time: {
QTime t1 = v1.toTime();
QTime t2 = v2.toTime();
int h = t1.hour() + t2.hour();
int m = t1.minute() + t2.minute();
int s = t1.second() + t2.second();
int ms = t1.msec() + t2.msec();
if (ms > 999) {
ms -= 999; s++;
}
if (s > 60) {
s -= 60; m++;
}
if (m > 60) {
m -= 60; h++;
}
if (h > 24) {
h -= 24;
}
*d->value.variant = QTime(h, m, s, ms);
}
break;
case QVariant::UInt:
*d->value.variant = v1.toUInt() + v2.toUInt();
break;
case QVariant::ULongLong:
*d->value.variant = v1.toULongLong() + v2.toULongLong();
break;
}
}
break;
case Value::OrderedArray:
case Value::UnorderedArray:
case Value::AlternativeArray: {
if (v.isArray()) {
*(d->value.array) += *(v.d->value.array);
} else {
d->value.array->append(v);
}
}
break;
case Value::LangArray: {
Q_ASSERT(v.type() == Value::LangArray);
}
break;
case Value::Structure: {
Q_ASSERT(v.type() == Value::Structure);
break;
}
case Value::Rational: {
Q_ASSERT(v.type() == Value::Rational);
d->value.rational->numerator =
(d->value.rational->numerator
* v.d->value.rational->denominator)
+ (v.d->value.rational->numerator
* d->value.rational->denominator);
d->value.rational->denominator *= v.d->value.rational->denominator;
break;
}
}
return *this;
}
QMap<QString, KisMetaData::Value> Value::asLangArray() const
{
Q_ASSERT(d->type == LangArray);
QMap<QString, KisMetaData::Value> langArray;
Q_FOREACH (const KisMetaData::Value& val, *d->value.array) {
Q_ASSERT(val.d->propertyQualifiers.contains("xml:lang")); // TODO propably worth to have an assert for this in the constructor as well
KisMetaData::Value valKeyVal = val.d->propertyQualifiers.value("xml:lang");
Q_ASSERT(valKeyVal.type() == Variant);
QVariant valKeyVar = valKeyVal.asVariant();
Q_ASSERT(valKeyVar.type() == QVariant::String);
langArray[valKeyVar.toString()] = val;
}
return langArray;
}
QString Value::toString() const
{
switch (type()) {
case Value::Invalid:
return i18n("Invalid value.");
case Value::Variant:
return d->value.variant->toString();
break;
case Value::OrderedArray:
case Value::UnorderedArray:
case Value::AlternativeArray:
case Value::LangArray: {
QString r = QString("[%1]{ ").arg(d->value.array->size());
for (int i = 0; i < d->value.array->size(); ++i) {
const Value& val = d->value.array->at(i);
r += val.toString();
if (i != d->value.array->size() - 1) {
r += ',';
}
r += ' ';
}
r += '}';
return r;
}
case Value::Structure: {
QString r = "{ ";
QList<QString> fields = d->value.structure->keys();
for (int i = 0; i < fields.count(); ++i) {
const QString& field = fields[i];
const Value& val = d->value.structure->value(field);
r += field + " => " + val.toString();
if (i != d->value.array->size() - 1) {
r += ',';
}
r += ' ';
}
r += '}';
return r;
}
break;
case Value::Rational:
return QString("%1 / %2").arg(d->value.rational->numerator).arg(d->value.rational->denominator);
}
return i18n("Invalid value.");
}
diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt
index 9c3b5f62ba..280bd167f5 100644
--- a/libs/image/tests/CMakeLists.txt
+++ b/libs/image/tests/CMakeLists.txt
@@ -1,238 +1,242 @@
# cmake in some versions for some not yet known reasons fails to run automoc
# on random targets (changing target names already has an effect)
# As temporary workaround skipping build of tests on these versions for now
# See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html
# extend range of affected cmake versions as needed
if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND
NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3)
message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)")
set (HAVE_FAILING_CMAKE TRUE)
else()
set (HAVE_FAILING_CMAKE FALSE)
endif()
include_directories(
${CMAKE_SOURCE_DIR}/libs/image/metadata
${CMAKE_BINARY_DIR}/libs/image/
${CMAKE_SOURCE_DIR}/libs/image/
${CMAKE_SOURCE_DIR}/libs/image/brushengine
${CMAKE_SOURCE_DIR}/libs/image/tiles3
${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap
${CMAKE_SOURCE_DIR}/sdk/tests
)
include_Directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${Boost_INCLUDE_DIRS}
)
if(HAVE_VC)
include_directories(${Vc_INCLUDE_DIR})
endif()
include(ECMAddTests)
include(KritaAddBrokenUnitTest)
macro_add_unittest_definitions()
set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp)
ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui)
add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources})
target_link_libraries(KisRandomGeneratorDemo kritaimage)
ecm_mark_as_test(KisRandomGeneratorDemo)
ecm_add_tests(
kis_base_node_test.cpp
kis_fast_math_test.cpp
kis_node_test.cpp
kis_node_facade_test.cpp
kis_fixed_paint_device_test.cpp
kis_layer_test.cpp
kis_effect_mask_test.cpp
kis_iterator_test.cpp
kis_painter_test.cpp
kis_selection_test.cpp
kis_count_visitor_test.cpp
kis_projection_test.cpp
kis_properties_configuration_test.cpp
kis_transaction_test.cpp
kis_pixel_selection_test.cpp
kis_group_layer_test.cpp
kis_paint_layer_test.cpp
kis_adjustment_layer_test.cpp
kis_annotation_test.cpp
kis_change_profile_visitor_test.cpp
kis_clone_layer_test.cpp
kis_colorspace_convert_visitor_test.cpp
kis_convolution_painter_test.cpp
kis_crop_processing_visitor_test.cpp
kis_processing_applicator_test.cpp
kis_datamanager_test.cpp
kis_fill_painter_test.cpp
kis_filter_configuration_test.cpp
kis_filter_test.cpp
kis_filter_processing_information_test.cpp
kis_filter_registry_test.cpp
kis_filter_strategy_test.cpp
kis_gradient_painter_test.cpp
kis_image_commands_test.cpp
kis_image_test.cpp
kis_image_signal_router_test.cpp
kis_iterators_ng_test.cpp
kis_iterator_benchmark.cpp
kis_updater_context_test.cpp
kis_simple_update_queue_test.cpp
kis_stroke_test.cpp
kis_simple_stroke_strategy_test.cpp
kis_stroke_strategy_undo_command_based_test.cpp
kis_strokes_queue_test.cpp
kis_macro_test.cpp
kis_mask_test.cpp
kis_math_toolbox_test.cpp
kis_name_server_test.cpp
kis_node_commands_test.cpp
kis_node_graph_listener_test.cpp
kis_node_visitor_test.cpp
kis_paint_information_test.cpp
kis_distance_information_test.cpp
kis_paintop_test.cpp
kis_pattern_test.cpp
kis_recorded_action_factory_registry_test.cpp
kis_recorded_action_test.cpp
kis_recorded_filter_action_test.cpp
kis_selection_mask_test.cpp
kis_shared_ptr_test.cpp
kis_bsplines_test.cpp
kis_warp_transform_worker_test.cpp
kis_liquify_transform_worker_test.cpp
kis_transparency_mask_test.cpp
kis_types_test.cpp
kis_vec_test.cpp
kis_filter_config_widget_test.cpp
kis_mask_generator_test.cpp
kis_cubic_curve_test.cpp
kis_node_query_path_test.cpp
kis_fixed_point_maths_test.cpp
kis_filter_weights_buffer_test.cpp
kis_filter_weights_applicator_test.cpp
kis_fill_interval_test.cpp
kis_fill_interval_map_test.cpp
kis_scanline_fill_test.cpp
kis_psd_layer_style_test.cpp
kis_layer_style_projection_plane_test.cpp
kis_lod_capable_layer_offset_test.cpp
kis_algebra_2d_test.cpp
kis_marker_painter_test.cpp
kis_lazy_brush_test.cpp
kis_colorize_mask_test.cpp
NAME_PREFIX "krita-image-"
LINK_LIBRARIES kritaimage Qt5::Test)
ecm_add_test(kis_layer_style_filter_environment_test.cpp
TEST_NAME kritaimage-layer_style_filter_environment_test
LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test)
ecm_add_test(kis_asl_parser_test.cpp
TEST_NAME kritalibpsd-asl_parser_test
LINK_LIBRARIES kritapsd kritapigment kritawidgetutils kritacommand Qt5::Xml Qt5::Test)
+ecm_add_test(KisPerStrokeRandomSourceTest.cpp
+ TEST_NAME KisPerStrokeRandomSourceTest
+ LINK_LIBRARIES kritaimage Qt5::Test)
+
# ecm_add_test(kis_dom_utils_test.cpp
# TEST_NAME krita-image-DomUtils-Test
# LINK_LIBRARIES kritaimage Qt5::Test)
# kisdoc dep
# kis_transform_worker_test.cpp
# TEST_NAME krita-image-KisTransformWorkerTest
#LINK_LIBRARIES kritaimage Qt5::Test)
# kisdoc
# kis_perspective_transform_worker_test.cpp
# TEST_NAME krita-image-KisPerspectiveTransformWorkerTest
#LINK_LIBRARIES kritaimage Qt5::Test)
# kis_cs_conversion_test.cpp
# TEST_NAME krita-image-KisCsConversionTest
# LINK_LIBRARIES kritaimage Qt5::Test)
# kisdoc
# kis_processings_test.cpp
# TEST_NAME krita-image-KisProcessingsTest
#LINK_LIBRARIES kritaimage Qt5::Test)
# image/tests cannot use stuff that needs kisdocument
# kis_projection_leaf_test.cpp
# TEST_NAME kritaimage-projection_leaf_test
# LINK_LIBRARIES kritaimage Qt5::Test)
if (NOT HAVE_FAILING_CMAKE)
krita_add_broken_unit_test(kis_paint_device_test.cpp
TEST_NAME krita-image-KisPaintDeviceTest
LINK_LIBRARIES kritaimage kritaodf Qt5::Test)
else()
message(WARNING "Skipping KisPaintDeviceTest!!!!!!!!!!!!!!")
endif()
if (NOT HAVE_FAILING_CMAKE)
krita_add_broken_unit_test(kis_filter_mask_test.cpp
TEST_NAME krita-image-KisFilterMaskTest
LINK_LIBRARIES kritaimage Qt5::Test)
else()
message(WARNING "Skipping KisFilterMaskTest!!!!!!!!!!!!!!")
endif()
krita_add_broken_unit_test(kis_transform_mask_test.cpp
TEST_NAME krita-image-KisTransformMaskTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_histogram_test.cpp
TEST_NAME krita-image-KisHistogramTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_walkers_test.cpp
TEST_NAME krita-image-KisWalkersTest
LINK_LIBRARIES kritaimage Qt5::Test)
#krita_add_broken_unit_test(kis_async_merger_test.cpp
# TEST_NAME krita-image-KisAsyncMergerTest
# LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_update_scheduler_test.cpp
TEST_NAME krita-image-KisUpdateSchedulerTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_queues_progress_updater_test.cpp
TEST_NAME krita-image-KisQueuesProgressUpdaterTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_cage_transform_worker_test.cpp
TEST_NAME krita-image-KisCageTransformWorkerTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_meta_data_test.cpp
TEST_NAME krita-image-KisMetaDataTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_random_generator_test.cpp
TEST_NAME krita-image-KisRandomGeneratorTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_keyframing_test.cpp
TEST_NAME krita-image-Keyframing-Test
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_image_animation_interface_test.cpp
TEST_NAME krita-image-ImageAnimationInterface-Test
LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_onion_skin_compositor_test.cpp
TEST_NAME krita-image-OnionSkinCompositor-Test
LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_layer_styles_test.cpp
TEST_NAME krita-image-LayerStylesTest
LINK_LIBRARIES kritaimage Qt5::Test)
diff --git a/libs/image/tests/KisPerStrokeRandomSourceTest.cpp b/libs/image/tests/KisPerStrokeRandomSourceTest.cpp
new file mode 100644
index 0000000000..bc496d3668
--- /dev/null
+++ b/libs/image/tests/KisPerStrokeRandomSourceTest.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisPerStrokeRandomSourceTest.h"
+
+#include "brushengine/KisPerStrokeRandomSource.h"
+
+#include <QTest>
+
+void KisPerStrokeRandomSourceTest::testIndependent()
+{
+ bool sourcesDiffer = false;
+
+ /**
+ * Theoretically, the we can get two equal 1000-pcs sequences, but it is highly improbable
+ */
+ for (int i = 0; i < 1000; i++) {
+ KisPerStrokeRandomSource s1;
+ KisPerStrokeRandomSource s2;
+
+ if (s1.generate("mykey", 0, 1000) != s2.generate("mykey", 0, 1000)) {
+ sourcesDiffer = true;
+ break;
+ }
+ }
+
+ QVERIFY(sourcesDiffer);
+}
+
+void KisPerStrokeRandomSourceTest::testDependent()
+{
+ bool allSame = true;
+
+ for (int i = 0; i < 1000; i++) {
+ KisPerStrokeRandomSource s1;
+ KisPerStrokeRandomSource s2(s1);
+
+ if (s1.generate("mykey", 0, 1000) != s2.generate("mykey", 0, 1000)) {
+ allSame = false;
+ break;
+ }
+ }
+
+ QVERIFY(allSame);
+}
+
+void KisPerStrokeRandomSourceTest::testDifferentKeys()
+{
+ bool sourcesDiffer = false;
+
+ /**
+ * Theoretically, the we can get two equal 1000-pcs sequences, but it is highly improbable
+ */
+ for (int i = 0; i < 1000; i++) {
+ KisPerStrokeRandomSource s1;
+ KisPerStrokeRandomSource s2(s1);
+
+ if (s1.generate("mykey1", 0, 1000) != s2.generate("mykey2", 0, 1000)) {
+ sourcesDiffer = true;
+ break;
+ }
+ }
+
+ QVERIFY(sourcesDiffer);
+}
+
+QTEST_MAIN(KisPerStrokeRandomSourceTest)
diff --git a/plugins/paintops/chalk/chalk_paintop_plugin.h b/libs/image/tests/KisPerStrokeRandomSourceTest.h
similarity index 63%
copy from plugins/paintops/chalk/chalk_paintop_plugin.h
copy to libs/image/tests/KisPerStrokeRandomSourceTest.h
index 0d78033afd..e405ce8f76 100644
--- a/plugins/paintops/chalk/chalk_paintop_plugin.h
+++ b/libs/image/tests/KisPerStrokeRandomSourceTest.h
@@ -1,36 +1,33 @@
/*
- * Copyright (c) 2008 Lukáš Tvrdý (lukast.dev@gmail.com)
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef CHALK_PAINTOP_PLUGIN_H_
-#define CHALK_PAINTOP_PLUGIN_H_
+#ifndef KISPERSTROKERANDOMSOURCETEST_H
+#define KISPERSTROKERANDOMSOURCETEST_H
-#include <QObject>
-#include <QVariant>
+#include <QtTest>
-/**
- * A plugin wrapper that adds the paintop factories to the paintop registry.
- */
-class ChalkPaintOpPlugin : public QObject
+class KisPerStrokeRandomSourceTest : public QObject
{
Q_OBJECT
-public:
- ChalkPaintOpPlugin(QObject *parent, const QVariantList &);
- ~ChalkPaintOpPlugin() override;
+private Q_SLOTS:
+ void testIndependent();
+ void testDependent();
+ void testDifferentKeys();
};
-#endif // CHALK_PAINTOP_PLUGIN_H_
+#endif // KISPERSTROKERANDOMSOURCETEST_H
diff --git a/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_3.png b/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_3.png
new file mode 100644
index 0000000000..239c08da06
Binary files /dev/null and b/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_3.png differ
diff --git a/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6.png b/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6.png
new file mode 100644
index 0000000000..f58815060c
Binary files /dev/null and b/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6.png differ
diff --git a/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6_sel.png b/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6_sel.png
new file mode 100644
index 0000000000..5d31b6803a
Binary files /dev/null and b/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6_sel.png differ
diff --git a/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6_varyop.png b/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6_varyop.png
new file mode 100644
index 0000000000..4e5c38adcc
Binary files /dev/null and b/libs/image/tests/data/kispainter_test/massive_bitblt_full_update_6_varyop.png differ
diff --git a/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_3.png b/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_3.png
new file mode 100644
index 0000000000..6de92a53b3
Binary files /dev/null and b/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_3.png differ
diff --git a/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6.png b/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6.png
new file mode 100644
index 0000000000..87f8e58250
Binary files /dev/null and b/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6.png differ
diff --git a/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6_sel.png b/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6_sel.png
new file mode 100644
index 0000000000..856c402a2c
Binary files /dev/null and b/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6_sel.png differ
diff --git a/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6_varyop.png b/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6_varyop.png
new file mode 100644
index 0000000000..6ff304e66e
Binary files /dev/null and b/libs/image/tests/data/kispainter_test/massive_bitblt_partial_update_6_varyop.png differ
diff --git a/libs/image/tests/kis_distance_information_test.cpp b/libs/image/tests/kis_distance_information_test.cpp
index 6f65be1f8c..91f3085ea1 100644
--- a/libs/image/tests/kis_distance_information_test.cpp
+++ b/libs/image/tests/kis_distance_information_test.cpp
@@ -1,182 +1,182 @@
#include "kis_distance_information_test.h"
#include <QTest>
#include <QDomDocument>
#include <QDomElement>
#include <QPointF>
#include "kis_algebra_2d.h"
#include "kis_distance_information.h"
#include "kis_spacing_information.h"
#include "kis_timing_information.h"
#include "kis_paint_information.h"
void KisDistanceInformationTest::testInitInfo()
{
// Test equality checking operators.
testInitInfoEquality();
// Test XML cloning.
testInitInfoXMLClone();
}
void KisDistanceInformationTest::testInterpolation()
{
// Set up a scenario for interpolation.
QPointF startPos;
QPointF endPos(100.0, -50.0);
qreal dist = KisAlgebra2D::norm(endPos - startPos);
qreal startTime = 0.0;
qreal endTime = 1000.0;
qreal interval = endTime - startTime;
KisPaintInformation p1(startPos, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, startTime, 0.0);
KisPaintInformation p2(endPos, 1.0, 0.0, 0.0, 5.0, 0.0, 1.0, endTime, 0.0);
// Test interpolation with various spacing settings.
static const qreal interpTolerance = 0.000001;
KisDistanceInformation dist1;
dist1.updateSpacing(KisSpacingInformation(dist/10.0));
dist1.updateTiming(KisTimingInformation());
testInterpolationImpl(p1, p2, dist1, 1.0/10.0, false, false, interpTolerance);
KisDistanceInformation dist2(interval/2.0, interval/3.0);
dist2.updateSpacing(KisSpacingInformation(dist*2.0));
dist2.updateTiming(KisTimingInformation(interval*1.5));
testInterpolationImpl(p1, p2, dist2, -1.0, true, true, interpTolerance);
KisDistanceInformation dist3(interval/1.5, interval*2.0);
dist3.updateSpacing(KisSpacingInformation(dist*2.1));
dist3.updateTiming(KisTimingInformation(interval*1.6));
testInterpolationImpl(p1, p2, dist3, -1.0, true, false, interpTolerance);
KisDistanceInformation dist4(interval*1.3, interval/2.4);
dist4.updateSpacing(KisSpacingInformation(dist*1.7));
dist4.updateTiming(KisTimingInformation(interval*1.9));
testInterpolationImpl(p1, p2, dist4, -1.0, false, true, interpTolerance);
KisDistanceInformation dist5(interval*1.1, interval*1.2);
dist5.updateSpacing(KisSpacingInformation(dist/40.0));
dist5.updateTiming(KisTimingInformation());
testInterpolationImpl(p1, p2, dist5, 1.0/40.0, false, false, interpTolerance);
KisDistanceInformation dist6;
dist6.updateSpacing(KisSpacingInformation(false, 1.0));
dist6.updateTiming(KisTimingInformation());
testInterpolationImpl(p1, p2, dist6, -1.0, false, false, interpTolerance);
KisDistanceInformation dist7;
dist7.updateSpacing(KisSpacingInformation(false, 1.0));
dist7.updateTiming(KisTimingInformation(interval/20.0));
testInterpolationImpl(p1, p2, dist7, 1.0/20.0, false, false, interpTolerance);
KisDistanceInformation dist8;
dist8.updateSpacing(KisSpacingInformation(dist/10.0));
dist8.updateTiming(KisTimingInformation(interval/15.0));
testInterpolationImpl(p1, p2, dist8, 1.0/15.0, false, false, interpTolerance);
KisDistanceInformation dist9;
dist9.updateSpacing(KisSpacingInformation(dist/15.0));
dist9.updateTiming(KisTimingInformation(interval/10.0));
testInterpolationImpl(p1, p2, dist9, 1.0/15.0, false, false, interpTolerance);
KisDistanceInformation dist10;
dist10.updateSpacing(KisSpacingInformation(dist*2.0));
dist10.updateTiming(KisTimingInformation(interval*1.5));
testInterpolationImpl(p1, p2, dist10, -1.0, false, false, interpTolerance);
KisDistanceInformation dist11;
qreal a = 50.0;
qreal b = 25.0;
dist11.updateSpacing(KisSpacingInformation(QPointF(a * 2.0, b * 2.0), 0.0, false));
dist11.updateTiming(KisTimingInformation());
// Compute the expected interpolation factor; we are using anisotropic spacing here.
qreal angle = KisAlgebra2D::directionBetweenPoints(startPos, endPos, 0.0);
qreal cosTermSqrt = qCos(angle) / a;
qreal sinTermSqrt = qSin(angle) / b;
qreal spacingDist = 2.0 / qSqrt(cosTermSqrt * cosTermSqrt + sinTermSqrt * sinTermSqrt);
qreal expectedInterp = spacingDist / dist;
testInterpolationImpl(p1, p2, dist11, expectedInterp, false, false, interpTolerance);
}
void KisDistanceInformationTest::testInitInfoEquality() const
{
KisDistanceInitInfo info1;
KisDistanceInitInfo info2;
QVERIFY(info1 == info2);
QVERIFY(!(info1 != info2));
- KisDistanceInitInfo info3(0.1, 0.5);
- KisDistanceInitInfo info4(0.1, 0.5);
+ KisDistanceInitInfo info3(0.1, 0.5, 4);
+ KisDistanceInitInfo info4(0.1, 0.5, 4);
QVERIFY(info3 == info4);
QVERIFY(!(info3 != info4));
- KisDistanceInitInfo info5(QPointF(1.1, -10.7), 100.0, 3.3);
- KisDistanceInitInfo info6(QPointF(1.1, -10.7), 100.0, 3.3);
+ KisDistanceInitInfo info5(QPointF(1.1, -10.7), 3.3, 7);
+ KisDistanceInitInfo info6(QPointF(1.1, -10.7), 3.3, 7);
QVERIFY(info5 == info6);
QVERIFY(!(info5 != info6));
- KisDistanceInitInfo info7(QPointF(-12.3, 24.0), 104.0, 5.0, 20.1, 35.7);
- KisDistanceInitInfo info8(QPointF(-12.3, 24.0), 104.0, 5.0, 20.1, 35.7);
+ KisDistanceInitInfo info7(QPointF(-12.3, 24.0), 5.0, 20.1, 35.7, 9);
+ KisDistanceInitInfo info8(QPointF(-12.3, 24.0), 5.0, 20.1, 35.7, 9);
QVERIFY(info7 == info8);
QVERIFY(!(info7 != info8));
QVERIFY(info1 != info3);
QVERIFY(info1 != info5);
QVERIFY(info1 != info7);
QVERIFY(info3 != info5);
QVERIFY(info3 != info7);
QVERIFY(info5 != info7);
}
void KisDistanceInformationTest::testInitInfoXMLClone() const
{
// Note: Numeric values used here must be values that get serialized to XML exactly (e.g. small
// integers). Otherwise, roundoff error in serialization may cause a failure.
KisDistanceInitInfo info1;
QDomDocument doc;
QDomElement elt1 = doc.createElement("Test1");
info1.toXML(doc, elt1);
KisDistanceInitInfo clone1 = KisDistanceInitInfo::fromXML(elt1);
QVERIFY(clone1 == info1);
- KisDistanceInitInfo info2(40.0, 2.0);
+ KisDistanceInitInfo info2(40.0, 2.0, 3);
QDomElement elt2 = doc.createElement("Test2");
info2.toXML(doc, elt2);
KisDistanceInitInfo clone2 = KisDistanceInitInfo::fromXML(elt2);
QVERIFY(clone2 == info2);
- KisDistanceInitInfo info3(QPointF(-8.0, -5.0), 0.0, 60.0);
+ KisDistanceInitInfo info3(QPointF(-8.0, -5.0), 60.0, 3);
QDomElement elt3 = doc.createElement("Test3");
info3.toXML(doc, elt3);
KisDistanceInitInfo clone3 = KisDistanceInitInfo::fromXML(elt3);
QVERIFY(clone3 == info3);
- KisDistanceInitInfo info4(QPointF(0.0, 9.0), 10.0, 6.0, 1.0, 3.0);
+ KisDistanceInitInfo info4(QPointF(0.0, 9.0), 6.0, 1.0, 3.0, 8);
QDomElement elt4 = doc.createElement("Test4");
info4.toXML(doc, elt4);
KisDistanceInitInfo clone4 = KisDistanceInitInfo::fromXML(elt4);
QVERIFY(clone4 == info4);
}
void KisDistanceInformationTest::testInterpolationImpl(const KisPaintInformation &p1,
const KisPaintInformation &p2,
KisDistanceInformation &dist,
qreal interpFactor,
bool needSpacingUpdate,
bool needTimingUpdate,
qreal interpTolerance) const
{
qreal actualInterpFactor = dist.getNextPointPosition(p1.pos(), p2.pos(), p1.currentTime(),
p2.currentTime());
QVERIFY(qAbs(interpFactor - actualInterpFactor) <= interpTolerance);
QCOMPARE(dist.needsSpacingUpdate(), needSpacingUpdate);
QCOMPARE(dist.needsTimingUpdate(), needTimingUpdate);
}
QTEST_MAIN(KisDistanceInformationTest)
diff --git a/libs/image/tests/kis_image_test.cpp b/libs/image/tests/kis_image_test.cpp
index 026a30d32b..0580d67aa1 100644
--- a/libs/image/tests/kis_image_test.cpp
+++ b/libs/image/tests/kis_image_test.cpp
@@ -1,1159 +1,1160 @@
/*
* Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_image_test.h"
#include <QApplication>
#include <QTest>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpace.h>
#include "filter/kis_filter.h"
#include "filter/kis_filter_configuration.h"
#include "filter/kis_filter_registry.h"
#include "kis_image.h"
#include "kis_paint_layer.h"
#include "kis_group_layer.h"
#include "kis_adjustment_layer.h"
#include "kis_selection.h"
#include <kis_debug.h>
#include <kis_layer_composition.h>
#include "kis_keyframe_channel.h"
#include "kis_selection_mask.h"
#include "kis_layer_utils.h"
#include "kis_annotation.h"
#include "KisProofingConfiguration.h"
#include "kis_undo_stores.h"
#define IMAGE_WIDTH 128
#define IMAGE_HEIGHT 128
void KisImageTest::layerTests()
{
KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests");
QVERIFY(image->rootLayer() != 0);
QVERIFY(image->rootLayer()->firstChild() == 0);
KisLayerSP layer = new KisPaintLayer(image, "layer 1", OPACITY_OPAQUE_U8);
image->addNode(layer);
QVERIFY(image->rootLayer()->firstChild()->objectName() == layer->objectName());
}
void KisImageTest::benchmarkCreation()
{
const QRect imageRect(0,0,3000,2000);
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QList<KisImageSP> images;
QList<KisSurrogateUndoStore*> stores;
QBENCHMARK {
for (int i = 0; i < 10; i++) {
stores << new KisSurrogateUndoStore();
}
for (int i = 0; i < 10; i++) {
KisImageSP image = new KisImage(stores.takeLast(), imageRect.width(), imageRect.height(), cs, "test image");
images << image;
}
}
}
#include "testutil.h"
#include "kis_stroke_strategy.h"
#include <functional>
class ForbiddenLodStrokeStrategy : public KisStrokeStrategy
{
public:
ForbiddenLodStrokeStrategy(std::function<void()> lodCallback)
: m_lodCallback(lodCallback)
{
}
KisStrokeStrategy* createLodClone(int levelOfDetail) override {
Q_UNUSED(levelOfDetail);
m_lodCallback();
return 0;
}
private:
std::function<void()> m_lodCallback;
};
void notifyVar(bool *value) {
*value = true;
}
void KisImageTest::testBlockLevelOfDetail()
{
TestUtil::MaskParent p;
QCOMPARE(p.image->currentLevelOfDetail(), 0);
p.image->setDesiredLevelOfDetail(1);
p.image->waitForDone();
QCOMPARE(p.image->currentLevelOfDetail(), 0);
{
bool lodCreated = false;
KisStrokeId id = p.image->startStroke(
new ForbiddenLodStrokeStrategy(
std::bind(&notifyVar, &lodCreated)));
p.image->endStroke(id);
p.image->waitForDone();
QVERIFY(lodCreated);
}
p.image->setLevelOfDetailBlocked(true);
{
bool lodCreated = false;
KisStrokeId id = p.image->startStroke(
new ForbiddenLodStrokeStrategy(
std::bind(&notifyVar, &lodCreated)));
p.image->endStroke(id);
p.image->waitForDone();
QVERIFY(!lodCreated);
}
p.image->setLevelOfDetailBlocked(false);
p.image->setDesiredLevelOfDetail(1);
{
bool lodCreated = false;
KisStrokeId id = p.image->startStroke(
new ForbiddenLodStrokeStrategy(
std::bind(&notifyVar, &lodCreated)));
p.image->endStroke(id);
p.image->waitForDone();
QVERIFY(lodCreated);
}
}
void KisImageTest::testConvertImageColorSpace()
{
const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest");
KisPaintDeviceSP device1 = new KisPaintDevice(cs8);
KisLayerSP paint1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, device1);
KisFilterSP filter = KisFilterRegistry::instance()->value("blur");
Q_ASSERT(filter);
KisFilterConfigurationSP configuration = filter->defaultConfiguration();
Q_ASSERT(configuration);
KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0);
image->addNode(paint1, image->root());
image->addNode(blur1, image->root());
image->refreshGraph();
const KoColorSpace *cs16 = KoColorSpaceRegistry::instance()->rgb16();
image->lock();
image->convertImageColorSpace(cs16,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
image->unlock();
QVERIFY(*cs16 == *image->colorSpace());
QVERIFY(*cs16 == *image->root()->colorSpace());
QVERIFY(*cs16 == *paint1->colorSpace());
QVERIFY(*cs16 == *blur1->colorSpace());
QVERIFY(!image->root()->compositeOp());
QVERIFY(*cs16 == *paint1->compositeOp()->colorSpace());
QVERIFY(*cs16 == *blur1->compositeOp()->colorSpace());
image->refreshGraph();
}
void KisImageTest::testGlobalSelection()
{
const KoColorSpace *cs8 = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(0, 1000, 1000, cs8, "stest");
QCOMPARE(image->globalSelection(), KisSelectionSP(0));
QCOMPARE(image->canReselectGlobalSelection(), false);
QCOMPARE(image->root()->childCount(), 0U);
KisSelectionSP selection1 = new KisSelection(new KisDefaultBounds(image));
KisSelectionSP selection2 = new KisSelection(new KisDefaultBounds(image));
image->setGlobalSelection(selection1);
QCOMPARE(image->globalSelection(), selection1);
QCOMPARE(image->canReselectGlobalSelection(), false);
QCOMPARE(image->root()->childCount(), 1U);
image->setGlobalSelection(selection2);
QCOMPARE(image->globalSelection(), selection2);
QCOMPARE(image->canReselectGlobalSelection(), false);
QCOMPARE(image->root()->childCount(), 1U);
image->deselectGlobalSelection();
QCOMPARE(image->globalSelection(), KisSelectionSP(0));
QCOMPARE(image->canReselectGlobalSelection(), true);
QCOMPARE(image->root()->childCount(), 0U);
image->reselectGlobalSelection();
QCOMPARE(image->globalSelection(), selection2);
QCOMPARE(image->canReselectGlobalSelection(), false);
QCOMPARE(image->root()->childCount(), 1U);
// mixed deselecting/setting/reselecting
image->deselectGlobalSelection();
QCOMPARE(image->globalSelection(), KisSelectionSP(0));
QCOMPARE(image->canReselectGlobalSelection(), true);
QCOMPARE(image->root()->childCount(), 0U);
image->setGlobalSelection(selection1);
QCOMPARE(image->globalSelection(), selection1);
QCOMPARE(image->canReselectGlobalSelection(), false);
QCOMPARE(image->root()->childCount(), 1U);
}
void KisImageTest::testCloneImage()
{
KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests");
QVERIFY(image->rootLayer() != 0);
QVERIFY(image->rootLayer()->firstChild() == 0);
KisAnnotationSP annotation = new KisAnnotation("mytype", "mydescription", QByteArray());
image->addAnnotation(annotation);
QVERIFY(image->annotation("mytype"));
KisProofingConfigurationSP proofing = toQShared(new KisProofingConfiguration());
image->setProofingConfiguration(proofing);
QVERIFY(image->proofingConfiguration());
const KoColor defaultColor(Qt::green, image->colorSpace());
image->setDefaultProjectionColor(defaultColor);
QCOMPARE(image->defaultProjectionColor(), defaultColor);
KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8);
image->addNode(layer);
KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8);
image->addNode(layer2);
QVERIFY(layer->visible());
QVERIFY(layer2->visible());
QVERIFY(TestUtil::findNode(image->root(), "layer1"));
QVERIFY(TestUtil::findNode(image->root(), "layer2"));
QUuid uuid1 = layer->uuid();
QUuid uuid2 = layer2->uuid();
{
KisImageSP newImage = image->clone();
KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1");
KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2");
QVERIFY(newLayer1);
QVERIFY(newLayer2);
QVERIFY(newLayer1->uuid() != uuid1);
QVERIFY(newLayer2->uuid() != uuid2);
KisAnnotationSP newAnnotation = newImage->annotation("mytype");
QVERIFY(newAnnotation);
QVERIFY(newAnnotation != annotation);
+
KisProofingConfigurationSP newProofing = newImage->proofingConfiguration();
QVERIFY(newProofing);
QVERIFY(newProofing != proofing);
QCOMPARE(newImage->defaultProjectionColor(), defaultColor);
}
{
KisImageSP newImage = image->clone(true);
KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1");
KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2");
QVERIFY(newLayer1);
QVERIFY(newLayer2);
QVERIFY(newLayer1->uuid() == uuid1);
QVERIFY(newLayer2->uuid() == uuid2);
}
}
void KisImageTest::testLayerComposition()
{
KisImageSP image = new KisImage(0, IMAGE_WIDTH, IMAGE_WIDTH, 0, "layer tests");
QVERIFY(image->rootLayer() != 0);
QVERIFY(image->rootLayer()->firstChild() == 0);
KisLayerSP layer = new KisPaintLayer(image, "layer1", OPACITY_OPAQUE_U8);
image->addNode(layer);
KisLayerSP layer2 = new KisPaintLayer(image, "layer2", OPACITY_OPAQUE_U8);
image->addNode(layer2);
QVERIFY(layer->visible());
QVERIFY(layer2->visible());
KisLayerComposition comp(image, "comp 1");
comp.store();
layer2->setVisible(false);
QVERIFY(layer->visible());
QVERIFY(!layer2->visible());
KisLayerComposition comp2(image, "comp 2");
comp2.store();
KisLayerCompositionSP comp3 = toQShared(new KisLayerComposition(image, "comp 3"));
comp3->store();
image->addComposition(comp3);
comp.apply();
QVERIFY(layer->visible());
QVERIFY(layer2->visible());
comp2.apply();
QVERIFY(layer->visible());
QVERIFY(!layer2->visible());
comp.apply();
QVERIFY(layer->visible());
QVERIFY(layer2->visible());
KisImageSP newImage = image->clone();
KisNodeSP newLayer1 = TestUtil::findNode(newImage->root(), "layer1");
KisNodeSP newLayer2 = TestUtil::findNode(newImage->root(), "layer2");
QVERIFY(newLayer1);
QVERIFY(newLayer2);
QVERIFY(newLayer1->visible());
QVERIFY(newLayer2->visible());
KisLayerComposition newComp1(comp, newImage);
newComp1.apply();
QVERIFY(newLayer1->visible());
QVERIFY(newLayer2->visible());
KisLayerComposition newComp2(comp2, newImage);
newComp2.apply();
QVERIFY(newLayer1->visible());
QVERIFY(!newLayer2->visible());
newComp1.apply();
QVERIFY(newLayer1->visible());
QVERIFY(newLayer2->visible());
QVERIFY(!newImage->compositions().isEmpty());
KisLayerCompositionSP newComp3 = newImage->compositions().first();
newComp3->apply();
QVERIFY(newLayer1->visible());
QVERIFY(!newLayer2->visible());
}
#include "testutil.h"
#include "kis_group_layer.h"
#include "kis_transparency_mask.h"
#include "kis_psd_layer_style.h"
struct FlattenTestImage
{
FlattenTestImage()
: refRect(0,0,512,512)
, p(refRect)
{
image = p.image;
undoStore = p.undoStore;
layer1 = p.layer;
layer5 = new KisPaintLayer(p.image, "paint5", 0.4 * OPACITY_OPAQUE_U8);
layer5->disableAlphaChannel(true);
layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8);
tmask = new KisTransparencyMask();
// check channel flags
// make addition composite op
group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8);
layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8);
layer4 = new KisPaintLayer(p.image, "paint4", OPACITY_OPAQUE_U8);
layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8);
layer7 = new KisPaintLayer(p.image, "paint7", OPACITY_OPAQUE_U8);
layer8 = new KisPaintLayer(p.image, "paint8", OPACITY_OPAQUE_U8);
layer7->setCompositeOpId(COMPOSITE_ADD);
layer8->setCompositeOpId(COMPOSITE_ADD);
QRect rect1(100, 100, 100, 100);
QRect rect2(150, 150, 150, 150);
QRect tmaskRect(200,200,100,100);
QRect rect3(400, 100, 100, 100);
QRect rect4(500, 100, 100, 100);
QRect rect5(50, 50, 100, 100);
QRect rect6(50, 250, 100, 100);
QRect rect7(50, 350, 50, 50);
QRect rect8(50, 400, 50, 50);
layer1->paintDevice()->fill(rect1, KoColor(Qt::red, p.image->colorSpace()));
layer2->paintDevice()->fill(rect2, KoColor(Qt::green, p.image->colorSpace()));
tmask->testingInitSelection(tmaskRect, layer2);
layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, p.image->colorSpace()));
layer4->paintDevice()->fill(rect4, KoColor(Qt::yellow, p.image->colorSpace()));
layer5->paintDevice()->fill(rect5, KoColor(Qt::green, p.image->colorSpace()));
layer6->paintDevice()->fill(rect6, KoColor(Qt::cyan, p.image->colorSpace()));
layer7->paintDevice()->fill(rect7, KoColor(Qt::red, p.image->colorSpace()));
layer8->paintDevice()->fill(rect8, KoColor(Qt::green, p.image->colorSpace()));
KisPSDLayerStyleSP style(new KisPSDLayerStyle());
style->dropShadow()->setEffectEnabled(true);
style->dropShadow()->setDistance(10.0);
style->dropShadow()->setSpread(80.0);
style->dropShadow()->setSize(10);
style->dropShadow()->setNoise(0);
style->dropShadow()->setKnocksOut(false);
style->dropShadow()->setOpacity(80.0);
layer2->setLayerStyle(style);
layer2->setCompositeOpId(COMPOSITE_ADD);
group1->setCompositeOpId(COMPOSITE_ADD);
p.image->addNode(layer5);
p.image->addNode(layer2);
p.image->addNode(tmask, layer2);
p.image->addNode(group1);
p.image->addNode(layer3, group1);
p.image->addNode(layer4, group1);
p.image->addNode(layer6);
p.image->addNode(layer7);
p.image->addNode(layer8);
p.image->initialRefreshGraph();
// dbgKrita << ppVar(layer1->exactBounds());
// dbgKrita << ppVar(layer5->exactBounds());
// dbgKrita << ppVar(layer2->exactBounds());
// dbgKrita << ppVar(group1->exactBounds());
// dbgKrita << ppVar(layer3->exactBounds());
// dbgKrita << ppVar(layer4->exactBounds());
TestUtil::ExternalImageChecker chk("flatten", "imagetest");
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
}
QRect refRect;
TestUtil::MaskParent p;
KisImageSP image;
KisSurrogateUndoStore *undoStore;
KisPaintLayerSP layer1;
KisPaintLayerSP layer2;
KisTransparencyMaskSP tmask;
KisGroupLayerSP group1;
KisPaintLayerSP layer3;
KisPaintLayerSP layer4;
KisPaintLayerSP layer5;
KisPaintLayerSP layer6;
KisPaintLayerSP layer7;
KisPaintLayerSP layer8;
};
template<class ContainerTest>
KisLayerSP flattenLayerHelper(ContainerTest &p, KisLayerSP layer, bool nothingHappens = false)
{
QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP)));
//p.image->flattenLayer(layer);
KisLayerUtils::flattenLayer(p.image, layer);
p.image->waitForDone();
if (nothingHappens) {
Q_ASSERT(!spy.count());
return layer;
}
Q_ASSERT(spy.count() == 1);
QList<QVariant> arguments = spy.takeFirst();
KisNodeSP newNode = arguments.first().value<KisNodeSP>();
KisLayerSP newLayer = qobject_cast<KisLayer*>(newNode.data());
return newLayer;
}
void KisImageTest::testFlattenLayer()
{
FlattenTestImage p;
TestUtil::ExternalImageChecker chk("flatten", "imagetest");
{
QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD);
KisLayerSP newLayer = flattenLayerHelper(p, p.layer2);
//KisLayerSP newLayer = p.image->flattenLayer(p.layer2);
//p.image->waitForDone();
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
}
{
QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD);
KisLayerSP newLayer = flattenLayerHelper(p, p.group1);
//KisLayerSP newLayer = p.image->flattenLayer(p.group1);
//p.image->waitForDone();
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_group1_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD);
QCOMPARE(newLayer->exactBounds(), QRect(400, 100, 200, 100));
}
{
QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(p.layer5->alphaChannelDisabled(), true);
KisLayerSP newLayer = flattenLayerHelper(p, p.layer5, true);
//KisLayerSP newLayer = p.image->flattenLayer(p.layer5);
//p.image->waitForDone();
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_layer5_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(newLayer->exactBounds(), QRect(50, 50, 100, 100));
QCOMPARE(newLayer->alphaChannelDisabled(), true);
}
}
#include <metadata/kis_meta_data_merge_strategy_registry.h>
template<class ContainerTest>
KisLayerSP mergeHelper(ContainerTest &p, KisLayerSP layer)
{
KisNodeSP parent = layer->parent();
const int newIndex = parent->index(layer) - 1;
p.image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop"));
//KisLayerUtils::mergeDown(p.image, layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop"));
p.image->waitForDone();
KisLayerSP newLayer = qobject_cast<KisLayer*>(parent->at(newIndex).data());
return newLayer;
}
void KisImageTest::testMergeDown()
{
FlattenTestImage p;
TestUtil::ExternalImageChecker img("flatten", "imagetest");
TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest");
{
QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(p.layer5->alphaChannelDisabled(), true);
KisLayerSP newLayer = mergeHelper(p, p.layer5);
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(newLayer->alphaChannelDisabled(), false);
}
{
QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD);
QCOMPARE(p.layer2->alphaChannelDisabled(), false);
KisLayerSP newLayer = mergeHelper(p, p.layer2);
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "02_layer2_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 213, 217));
QCOMPARE(newLayer->alphaChannelDisabled(), false);
}
{
QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD);
QCOMPARE(p.group1->alphaChannelDisabled(), false);
KisLayerSP newLayer = mergeHelper(p, p.group1);
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "03_group1_mergedown_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(newLayer->exactBounds(), QRect(100, 100, 500, 217));
QCOMPARE(newLayer->alphaChannelDisabled(), false);
}
}
void KisImageTest::testMergeDownDestinationInheritsAlpha()
{
FlattenTestImage p;
TestUtil::ExternalImageChecker img("flatten", "imagetest");
TestUtil::ExternalImageChecker chk("mergedown_dst_inheritsalpha", "imagetest");
{
QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD);
QCOMPARE(p.layer2->alphaChannelDisabled(), false);
KisLayerSP newLayer = mergeHelper(p, p.layer2);
// WARN: this check is suspicious!
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_proj_merged_layer2_over_layer5_IA"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer2_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(newLayer->exactBounds(), QRect(50,50, 263, 267));
QCOMPARE(newLayer->alphaChannelDisabled(), false);
}
}
void KisImageTest::testMergeDownDestinationCustomCompositeOp()
{
FlattenTestImage p;
TestUtil::ExternalImageChecker img("flatten", "imagetest");
TestUtil::ExternalImageChecker chk("mergedown_dst_customop", "imagetest");
{
QCOMPARE(p.layer6->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(p.layer6->alphaChannelDisabled(), false);
QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD);
QCOMPARE(p.group1->alphaChannelDisabled(), false);
KisLayerSP newLayer = mergeHelper(p, p.layer6);
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer6_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250));
QCOMPARE(newLayer->alphaChannelDisabled(), false);
}
}
void KisImageTest::testMergeDownDestinationSameCompositeOpLayerStyle()
{
FlattenTestImage p;
TestUtil::ExternalImageChecker img("flatten", "imagetest");
TestUtil::ExternalImageChecker chk("mergedown_sameop_ls", "imagetest");
{
QCOMPARE(p.group1->compositeOpId(), COMPOSITE_ADD);
QCOMPARE(p.group1->alphaChannelDisabled(), false);
QCOMPARE(p.layer2->compositeOpId(), COMPOSITE_ADD);
QCOMPARE(p.layer2->alphaChannelDisabled(), false);
KisLayerSP newLayer = mergeHelper(p, p.group1);
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(newLayer->exactBounds(), QRect(197, 100, 403, 217));
QCOMPARE(newLayer->alphaChannelDisabled(), false);
}
}
void KisImageTest::testMergeDownDestinationSameCompositeOp()
{
FlattenTestImage p;
TestUtil::ExternalImageChecker img("flatten", "imagetest");
TestUtil::ExternalImageChecker chk("mergedown_sameop_fastpath", "imagetest");
{
QCOMPARE(p.layer8->compositeOpId(), COMPOSITE_ADD);
QCOMPARE(p.layer8->alphaChannelDisabled(), false);
QCOMPARE(p.layer7->compositeOpId(), COMPOSITE_ADD);
QCOMPARE(p.layer7->alphaChannelDisabled(), false);
KisLayerSP newLayer = mergeHelper(p, p.layer8);
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_ADD);
QCOMPARE(newLayer->exactBounds(), QRect(50, 350, 50, 100));
QCOMPARE(newLayer->alphaChannelDisabled(), false);
}
}
#include "kis_image_animation_interface.h"
void KisImageTest::testMergeDownMultipleFrames()
{
FlattenTestImage p;
TestUtil::ExternalImageChecker img("flatten", "imagetest");
TestUtil::ExternalImageChecker chk("mergedown_simple", "imagetest");
QSet<int> initialFrames;
{
KisLayerSP l = p.layer5;
l->enableAnimation();
KisKeyframeChannel *channel = l->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
channel->addKeyframe(10);
channel->addKeyframe(20);
channel->addKeyframe(30);
QCOMPARE(channel->keyframeCount(), 4);
initialFrames = KisLayerUtils::fetchLayerFramesRecursive(l);
QCOMPARE(initialFrames.size(), 4);
}
{
QCOMPARE(p.layer5->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(p.layer5->alphaChannelDisabled(), true);
KisLayerSP newLayer = mergeHelper(p, p.layer5);
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer5_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(newLayer->alphaChannelDisabled(), false);
QVERIFY(newLayer->isAnimated());
QSet<int> newFrames = KisLayerUtils::fetchLayerFramesRecursive(newLayer);
QCOMPARE(newFrames, initialFrames);
foreach (int frame, newFrames) {
KisImageAnimationInterface *interface = p.image->animationInterface();
int savedSwitchedTime = 0;
interface->saveAndResetCurrentTime(frame, &savedSwitchedTime);
QCOMPARE(newLayer->exactBounds(), QRect(100,100,100,100));
interface->restoreCurrentTime(&savedSwitchedTime);
}
p.undoStore->undo();
p.image->waitForDone();
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
}
}
template<class ContainerTest>
KisNodeSP mergeMultipleHelper(ContainerTest &p, QList<KisNodeSP> selectedNodes, KisNodeSP putAfter)
{
QSignalSpy spy(p.image.data(), SIGNAL(sigNodeAddedAsync(KisNodeSP)));
p.image->mergeMultipleLayers(selectedNodes, putAfter);
//KisLayerUtils::mergeMultipleLayers(p.image, selectedNodes, putAfter);
p.image->waitForDone();
Q_ASSERT(spy.count() == 1);
QList<QVariant> arguments = spy.takeFirst();
KisNodeSP newNode = arguments.first().value<KisNodeSP>();
return newNode;
}
void KisImageTest::testMergeMultiple()
{
FlattenTestImage p;
TestUtil::ExternalImageChecker img("flatten", "imagetest");
TestUtil::ExternalImageChecker chk("mergemultiple", "imagetest");
{
QList<KisNodeSP> selectedNodes;
selectedNodes << p.layer2
<< p.group1
<< p.layer6;
{
KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0);
//KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0);
//p.image->waitForDone();
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250));
}
}
p.p.undoStore->undo();
p.image->waitForDone();
// Test reversed order, the result must be the same
{
QList<KisNodeSP> selectedNodes;
selectedNodes << p.layer6
<< p.group1
<< p.layer2;
{
KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0);
//KisNodeSP newLayer = p.image->mergeMultipleLayers(selectedNodes, 0);
//p.image->waitForDone();
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_layer8_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(newLayer->exactBounds(), QRect(50, 100, 550, 250));
}
}
}
void testMergeCrossColorSpaceImpl(bool useProjectionColorSpace, bool swapSpaces)
{
QRect refRect;
TestUtil::MaskParent p;
KisPaintLayerSP layer1;
KisPaintLayerSP layer2;
KisPaintLayerSP layer3;
const KoColorSpace *cs2 = useProjectionColorSpace ?
p.image->colorSpace() : KoColorSpaceRegistry::instance()->lab16();
const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16();
if (swapSpaces) {
std::swap(cs2, cs3);
}
dbgKrita << "Testing testMergeCrossColorSpaceImpl:";
dbgKrita << " " << ppVar(cs2);
dbgKrita << " " << ppVar(cs3);
layer1 = p.layer;
layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8, cs2);
layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8, cs3);
QRect rect1(100, 100, 100, 100);
QRect rect2(150, 150, 150, 150);
QRect rect3(250, 250, 200, 200);
layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace()));
layer2->paintDevice()->fill(rect2, KoColor(Qt::green, layer2->colorSpace()));
layer3->paintDevice()->fill(rect3, KoColor(Qt::blue, layer3->colorSpace()));
p.image->addNode(layer2);
p.image->addNode(layer3);
p.image->initialRefreshGraph();
{
KisLayerSP newLayer = mergeHelper(p, layer3);
QCOMPARE(newLayer->colorSpace(), p.image->colorSpace());
p.undoStore->undo();
p.image->waitForDone();
}
{
layer2->disableAlphaChannel(true);
KisLayerSP newLayer = mergeHelper(p, layer3);
QCOMPARE(newLayer->colorSpace(), p.image->colorSpace());
p.undoStore->undo();
p.image->waitForDone();
}
}
void KisImageTest::testMergeCrossColorSpace()
{
testMergeCrossColorSpaceImpl(true, false);
testMergeCrossColorSpaceImpl(true, true);
testMergeCrossColorSpaceImpl(false, false);
testMergeCrossColorSpaceImpl(false, true);
}
void KisImageTest::testMergeSelectionMasks()
{
QRect refRect;
TestUtil::MaskParent p;
QRect rect1(100, 100, 100, 100);
QRect rect2(150, 150, 150, 150);
QRect rect3(50, 50, 100, 100);
KisPaintLayerSP layer1 = p.layer;
layer1->paintDevice()->fill(rect1, KoColor(Qt::red, layer1->colorSpace()));
p.image->initialRefreshGraph();
KisSelectionSP sel = new KisSelection(layer1->paintDevice()->defaultBounds());
sel->pixelSelection()->select(rect2, MAX_SELECTED);
KisSelectionMaskSP mask1 = new KisSelectionMask(p.image);
mask1->initSelection(sel, layer1);
p.image->addNode(mask1, layer1);
QVERIFY(!layer1->selection());
mask1->setActive(true);
QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150));
sel->pixelSelection()->select(rect3, MAX_SELECTED);
KisSelectionMaskSP mask2 = new KisSelectionMask(p.image);
mask2->initSelection(sel, layer1);
p.image->addNode(mask2, layer1);
QCOMPARE(layer1->selection()->selectedExactRect(), QRect(150,150,150,150));
mask2->setActive(true);
QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250));
QList<KisNodeSP> selectedNodes;
selectedNodes << mask2
<< mask1;
{
KisNodeSP newLayer = mergeMultipleHelper(p, selectedNodes, 0);
QCOMPARE(newLayer->parent(), KisNodeSP(layer1));
QCOMPARE((int)layer1->childCount(), 1);
QCOMPARE(layer1->selection()->selectedExactRect(), QRect(50,50,250,250));
}
}
void KisImageTest::testFlattenImage()
{
FlattenTestImage p;
KisImageSP image = p.image;
TestUtil::ExternalImageChecker img("flatten", "imagetest");
{
KisLayerUtils::flattenImage(p.image);
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
p.undoStore->undo();
p.image->waitForDone();
QVERIFY(img.checkDevice(p.image->projection(), p.image, "00_initial"));
}
}
struct FlattenPassThroughTestImage
{
FlattenPassThroughTestImage()
: refRect(0,0,512,512)
, p(refRect)
{
image = p.image;
undoStore = p.undoStore;
group1 = new KisGroupLayer(p.image, "group1", OPACITY_OPAQUE_U8);
layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8);
layer3 = new KisPaintLayer(p.image, "paint3", OPACITY_OPAQUE_U8);
group4 = new KisGroupLayer(p.image, "group4", OPACITY_OPAQUE_U8);
layer5 = new KisPaintLayer(p.image, "paint5", OPACITY_OPAQUE_U8);
layer6 = new KisPaintLayer(p.image, "paint6", OPACITY_OPAQUE_U8);
QRect rect2(100, 100, 100, 100);
QRect rect3(150, 150, 100, 100);
QRect rect5(200, 200, 100, 100);
QRect rect6(250, 250, 100, 100);
group1->setPassThroughMode(true);
layer2->paintDevice()->fill(rect2, KoColor(Qt::red, p.image->colorSpace()));
layer3->paintDevice()->fill(rect3, KoColor(Qt::green, p.image->colorSpace()));
group4->setPassThroughMode(true);
layer5->paintDevice()->fill(rect5, KoColor(Qt::blue, p.image->colorSpace()));
layer6->paintDevice()->fill(rect6, KoColor(Qt::yellow, p.image->colorSpace()));
p.image->addNode(group1);
p.image->addNode(layer2, group1);
p.image->addNode(layer3, group1);
p.image->addNode(group4);
p.image->addNode(layer5, group4);
p.image->addNode(layer6, group4);
p.image->initialRefreshGraph();
TestUtil::ExternalImageChecker chk("passthrough", "imagetest");
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
}
QRect refRect;
TestUtil::MaskParent p;
KisImageSP image;
KisSurrogateUndoStore *undoStore;
KisGroupLayerSP group1;
KisPaintLayerSP layer2;
KisPaintLayerSP layer3;
KisGroupLayerSP group4;
KisPaintLayerSP layer5;
KisPaintLayerSP layer6;
};
void KisImageTest::testFlattenPassThroughLayer()
{
FlattenPassThroughTestImage p;
TestUtil::ExternalImageChecker chk("passthrough", "imagetest");
{
QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(p.group1->passThroughMode(), true);
KisLayerSP newLayer = flattenLayerHelper(p, p.group1);
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(chk.checkDevice(newLayer->projection(), p.image, "01_group1_layerproj"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QVERIFY(newLayer->inherits("KisPaintLayer"));
}
}
void KisImageTest::testMergeTwoPassThroughLayers()
{
FlattenPassThroughTestImage p;
TestUtil::ExternalImageChecker chk("passthrough", "imagetest");
{
QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(p.group1->passThroughMode(), true);
KisLayerSP newLayer = mergeHelper(p, p.group4);
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
QCOMPARE(newLayer->compositeOpId(), COMPOSITE_OVER);
QVERIFY(newLayer->inherits("KisGroupLayer"));
}
}
void KisImageTest::testMergePaintOverPassThroughLayer()
{
FlattenPassThroughTestImage p;
TestUtil::ExternalImageChecker chk("passthrough", "imagetest");
{
QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(p.group1->passThroughMode(), true);
KisLayerSP newLayer = flattenLayerHelper(p, p.group4);
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(newLayer->inherits("KisPaintLayer"));
newLayer = mergeHelper(p, newLayer);
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(newLayer->inherits("KisPaintLayer"));
}
}
void KisImageTest::testMergePassThroughOverPaintLayer()
{
FlattenPassThroughTestImage p;
TestUtil::ExternalImageChecker chk("passthrough", "imagetest");
{
QCOMPARE(p.group1->compositeOpId(), COMPOSITE_OVER);
QCOMPARE(p.group1->passThroughMode(), true);
KisLayerSP newLayer = flattenLayerHelper(p, p.group1);
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(newLayer->inherits("KisPaintLayer"));
newLayer = mergeHelper(p, p.group4);
QVERIFY(chk.checkDevice(p.image->projection(), p.image, "00_initial"));
QVERIFY(newLayer->inherits("KisPaintLayer"));
}
}
QTEST_MAIN(KisImageTest)
diff --git a/libs/image/tests/kis_paint_device_test.cpp b/libs/image/tests/kis_paint_device_test.cpp
index 364cb31a84..a0072fcf9d 100644
--- a/libs/image/tests/kis_paint_device_test.cpp
+++ b/libs/image/tests/kis_paint_device_test.cpp
@@ -1,2269 +1,2269 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_paint_device_test.h"
#include <QTest>
#include <QTime>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoStore.h>
#include "kis_paint_device_writer.h"
#include "kis_painter.h"
#include "kis_types.h"
#include "kis_paint_device.h"
#include "kis_layer.h"
#include "kis_paint_layer.h"
#include "kis_selection.h"
#include "kis_datamanager.h"
#include "kis_global.h"
#include "testutil.h"
#include "kis_transaction.h"
#include "kis_image.h"
class KisFakePaintDeviceWriter : public KisPaintDeviceWriter {
public:
KisFakePaintDeviceWriter(KoStore *store)
: m_store(store)
{
}
bool write(const QByteArray &data) override {
return (m_store->write(data) == data.size());
}
bool write(const char* data, qint64 length) override {
return (m_store->write(data, length) == length);
}
KoStore *m_store;
};
void KisPaintDeviceTest::testCreation()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QVERIFY(dev->objectName().isEmpty());
dev = new KisPaintDevice(cs);
QVERIFY(*dev->colorSpace() == *cs);
QVERIFY(dev->x() == 0);
QVERIFY(dev->y() == 0);
QVERIFY(dev->pixelSize() == cs->pixelSize());
QVERIFY(dev->channelCount() == cs->channelCount());
QVERIFY(dev->dataManager() != 0);
KisImageSP image = new KisImage(0, 1000, 1000, cs, "merge test");
KisPaintLayerSP layer = new KisPaintLayer(image, "bla", 125);
dev = new KisPaintDevice(layer.data(), cs);
QVERIFY(*dev->colorSpace() == *cs);
QVERIFY(dev->x() == 0);
QVERIFY(dev->y() == 0);
QVERIFY(dev->pixelSize() == cs->pixelSize());
QVERIFY(dev->channelCount() == cs->channelCount());
QVERIFY(dev->dataManager() != 0);
// Let the layer go out of scope and see what happens
{
KisPaintLayerSP l2 = new KisPaintLayer(image, "blabla", 250);
dev = new KisPaintDevice(l2.data(), cs);
}
}
void KisPaintDeviceTest::testStore()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
KoStore * readStore =
KoStore::createStore(QString(FILES_DATA_DIR) + QDir::separator() + "store_test.kra", KoStore::Read);
readStore->open("built image/layers/layer0");
QVERIFY(dev->read(readStore->device()));
readStore->close();
delete readStore;
QVERIFY(dev->exactBounds() == QRect(0, 0, 100, 100));
KoStore * writeStore =
KoStore::createStore(QString(FILES_OUTPUT_DIR) + QDir::separator() + "store_test_out.kra", KoStore::Write);
KisFakePaintDeviceWriter fakeWriter(writeStore);
writeStore->open("built image/layers/layer0");
QVERIFY(dev->write(fakeWriter));
writeStore->close();
delete writeStore;
KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
readStore =
KoStore::createStore(QString(FILES_OUTPUT_DIR) + QDir::separator() + "store_test_out.kra", KoStore::Read);
readStore->open("built image/layers/layer0");
QVERIFY(dev2->read(readStore->device()));
readStore->close();
delete readStore;
QVERIFY(dev2->exactBounds() == QRect(0, 0, 100, 100));
QPoint pt;
if (!TestUtil::comparePaintDevices(pt, dev, dev2)) {
QFAIL(QString("Loading a saved image is not pixel perfect, first different pixel: %1,%2 ").arg(pt.x()).arg(pt.y()).toLatin1());
}
}
void KisPaintDeviceTest::testGeometry()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
quint8* pixel = new quint8[cs->pixelSize()];
cs->fromQColor(Qt::white, pixel);
dev->fill(0, 0, 512, 512, pixel);
QCOMPARE(dev->exactBounds(), QRect(0, 0, 512, 512));
QCOMPARE(dev->extent(), QRect(0, 0, 512, 512));
dev->moveTo(10, 10);
QCOMPARE(dev->exactBounds(), QRect(10, 10, 512, 512));
QCOMPARE(dev->extent(), QRect(10, 10, 512, 512));
dev->crop(50, 50, 50, 50);
QCOMPARE(dev->exactBounds(), QRect(50, 50, 50, 50));
QCOMPARE(dev->extent(), QRect(10, 10, 128, 128));
QColor c;
dev->clear(QRect(50, 50, 50, 50));
dev->pixel(80, 80, &c);
QVERIFY(c.alpha() == OPACITY_TRANSPARENT_U8);
dev->fill(0, 0, 512, 512, pixel);
dev->pixel(80, 80, &c);
QVERIFY(c == Qt::white);
QVERIFY(c.alpha() == OPACITY_OPAQUE_U8);
dev->clear();
dev->pixel(80, 80, &c);
QVERIFY(c.alpha() == OPACITY_TRANSPARENT_U8);
QVERIFY(dev->extent().isEmpty());
QVERIFY(dev->exactBounds().isEmpty());
}
void KisPaintDeviceTest::testClear()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QVERIFY(!dev->extent().isValid());
QVERIFY(!dev->exactBounds().isValid());
dev->clear();
QVERIFY(!dev->extent().isValid());
QVERIFY(!dev->exactBounds().isValid());
QRect fillRect1(50, 100, 150, 100);
dev->fill(fillRect1, KoColor(Qt::red, cs));
QCOMPARE(dev->extent(), QRect(0, 64, 256, 192));
QCOMPARE(dev->exactBounds(), fillRect1);
dev->clear(QRect(100, 100, 100, 100));
QCOMPARE(dev->extent(), QRect(0, 64, 256, 192));
QCOMPARE(dev->exactBounds(), QRect(50, 100, 50, 100));
dev->clear();
QVERIFY(!dev->extent().isValid());
QVERIFY(!dev->exactBounds().isValid());
}
void KisPaintDeviceTest::testCrop()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
quint8* pixel = new quint8[cs->pixelSize()];
cs->fromQColor(Qt::white, pixel);
dev->fill(-14, 8, 433, 512, pixel);
QVERIFY(dev->exactBounds() == QRect(-14, 8, 433, 512));
// Crop inside
dev->crop(50, 50, 150, 150);
QVERIFY(dev->exactBounds() == QRect(50, 50, 150, 150));
// Crop outside, pd should not grow
dev->crop(0, 0, 1000, 1000);
QVERIFY(dev->exactBounds() == QRect(50, 50, 150, 150));
}
void KisPaintDeviceTest::testRoundtripReadWrite()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "tile.png");
dev->convertFromQImage(image, 0);
quint8* bytes = new quint8[cs->pixelSize() * image.width() * image.height()];
memset(bytes, 0, image.width() * image.height() * dev->pixelSize());
dev->readBytes(bytes, image.rect());
KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
dev2->writeBytes(bytes, image.rect());
QVERIFY(dev2->exactBounds() == image.rect());
dev2->convertToQImage(0, 0, 0, image.width(), image.height()).save("readwrite.png");
QPoint pt;
if (!TestUtil::comparePaintDevices(pt, dev, dev2)) {
QFAIL(QString("Failed round trip using readBytes and writeBytes, first different pixel: %1,%2 ").arg(pt.x()).arg(pt.y()).toLatin1());
}
}
void logFailure(const QString & reason, const KoColorSpace * srcCs, const KoColorSpace * dstCs)
{
QString profile1("no profile");
QString profile2("no profile");
if (srcCs->profile())
profile1 = srcCs->profile()->name();
if (dstCs->profile())
profile2 = dstCs->profile()->name();
QWARN(QString("Failed %1 %2 -> %3 %4 %5")
.arg(srcCs->name())
.arg(profile1)
.arg(dstCs->name())
.arg(profile2)
.arg(reason)
.toLatin1());
}
void KisPaintDeviceTest::testColorSpaceConversion()
{
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "tile.png");
const KoColorSpace* srcCs = KoColorSpaceRegistry::instance()->rgb8();
const KoColorSpace* dstCs = KoColorSpaceRegistry::instance()->lab16();
KisPaintDeviceSP dev = new KisPaintDevice(srcCs);
dev->convertFromQImage(image, 0);
dev->moveTo(10, 10); // Unalign with tile boundaries
KUndo2Command* cmd = dev->convertTo(dstCs);
QCOMPARE(dev->exactBounds(), QRect(10, 10, image.width(), image.height()));
QCOMPARE(dev->pixelSize(), dstCs->pixelSize());
QVERIFY(*dev->colorSpace() == *dstCs);
cmd->redo();
cmd->undo();
QCOMPARE(dev->exactBounds(), QRect(10, 10, image.width(), image.height()));
QCOMPARE(dev->pixelSize(), srcCs->pixelSize());
QVERIFY(*dev->colorSpace() == *srcCs);
delete cmd;
}
void KisPaintDeviceTest::testRoundtripConversion()
{
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png");
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
QImage result = dev->convertToQImage(0, 0, 0, 640, 441);
QPoint errpoint;
if (!TestUtil::compareQImages(errpoint, image, result)) {
image.save("kis_paint_device_test_test_roundtrip_qimage.png");
result.save("kis_paint_device_test_test_roundtrip_result.png");
QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisPaintDeviceTest::testFastBitBlt()
{
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png");
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dstDev = new KisPaintDevice(cs);
KisPaintDeviceSP srcDev = new KisPaintDevice(cs);
srcDev->convertFromQImage(image, 0);
QRect cloneRect(100,100,200,200);
QPoint errpoint;
QVERIFY(dstDev->fastBitBltPossible(srcDev));
dstDev->fastBitBlt(srcDev, cloneRect);
QImage srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
cloneRect.width(), cloneRect.height());
QImage dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
cloneRect.width(), cloneRect.height());
if (!TestUtil::compareQImages(errpoint, srcImage, dstImage)) {
QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
// Test Rough version
dstDev->clear();
dstDev->fastBitBltRough(srcDev, cloneRect);
srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
cloneRect.width(), cloneRect.height());
dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
cloneRect.width(), cloneRect.height());
if (!TestUtil::compareQImages(errpoint, srcImage, dstImage)) {
QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
srcDev->moveTo(10,10);
QVERIFY(!dstDev->fastBitBltPossible(srcDev));
}
void KisPaintDeviceTest::testMakeClone()
{
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png");
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP srcDev = new KisPaintDevice(cs);
srcDev->convertFromQImage(image, 0);
srcDev->moveTo(10,10);
const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->lab16();
KisPaintDeviceSP dstDev = new KisPaintDevice(weirdCS);
dstDev->moveTo(1000,1000);
QVERIFY(!dstDev->fastBitBltPossible(srcDev));
QRect cloneRect(100,100,200,200);
QPoint errpoint;
dstDev->makeCloneFrom(srcDev, cloneRect);
QVERIFY(*dstDev->colorSpace() == *srcDev->colorSpace());
QCOMPARE(dstDev->pixelSize(), srcDev->pixelSize());
QCOMPARE(dstDev->x(), srcDev->x());
QCOMPARE(dstDev->y(), srcDev->y());
QCOMPARE(dstDev->exactBounds(), cloneRect);
QImage srcImage = srcDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
cloneRect.width(), cloneRect.height());
QImage dstImage = dstDev->convertToQImage(0, cloneRect.x(), cloneRect.y(),
cloneRect.width(), cloneRect.height());
if (!TestUtil::compareQImages(errpoint, dstImage, srcImage)) {
QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisPaintDeviceTest::testThumbnail()
{
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png");
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
{
KisPaintDeviceSP thumb = dev->createThumbnailDevice(50, 50);
QRect rc = thumb->exactBounds();
QVERIFY(rc.width() <= 50);
QVERIFY(rc.height() <= 50);
}
{
QImage thumb = dev->createThumbnail(50, 50);
QVERIFY(!thumb.isNull());
QVERIFY(thumb.width() <= 50);
QVERIFY(thumb.height() <= 50);
image.save("kis_paint_device_test_test_thumbnail.png");
}
}
void KisPaintDeviceTest::testThumbnailDeviceWithOffset()
{
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png");
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
dev->setX(10);
dev->setY(10);
QImage thumb = dev->createThumbnail(640,441,QRect(10,10,640,441));
image.save("oring.png");
thumb.save("thumb.png");
QPoint pt;
QVERIFY(TestUtil::compareQImages(pt, thumb, image));
}
void KisPaintDeviceTest::testCaching()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
quint8* whitePixel = new quint8[cs->pixelSize()];
cs->fromQColor(Qt::white, whitePixel);
quint8* blackPixel = new quint8[cs->pixelSize()];
cs->fromQColor(Qt::black, blackPixel);
dev->fill(0, 0, 512, 512, whitePixel);
QImage thumb1 = dev->createThumbnail(50, 50);
QRect exactBounds1 = dev->exactBounds();
dev->fill(0, 0, 768, 768, blackPixel);
QImage thumb2 = dev->createThumbnail(50, 50);
QRect exactBounds2 = dev->exactBounds();
dev->moveTo(10, 10);
QImage thumb3 = dev->createThumbnail(50, 50);
QRect exactBounds3 = dev->exactBounds();
dev->crop(50, 50, 50, 50);
QImage thumb4 = dev->createThumbnail(50, 50);
QRect exactBounds4 = dev->exactBounds();
QVERIFY(thumb1 != thumb2);
QVERIFY(thumb2 == thumb3); // Cache miss, but image is the same
QVERIFY(thumb3 != thumb4);
QVERIFY(thumb4 != thumb1);
QCOMPARE(exactBounds1, QRect(0,0,512,512));
QCOMPARE(exactBounds2, QRect(0,0,768,768));
QCOMPARE(exactBounds3, QRect(10,10,768,768));
QCOMPARE(exactBounds4, QRect(50,50,50,50));
}
void KisPaintDeviceTest::testRegion()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
quint8* whitePixel = new quint8[cs->pixelSize()];
cs->fromQColor(Qt::white, whitePixel);
dev->fill(0, 0, 10, 10, whitePixel);
dev->fill(70, 70, 10, 10, whitePixel);
dev->fill(129, 0, 10, 10, whitePixel);
dev->fill(0, 1030, 10, 10, whitePixel);
QRegion referenceRegion;
referenceRegion += QRect(0,0,64,64);
referenceRegion += QRect(64,64,64,64);
referenceRegion += QRect(128,0,64,64);
referenceRegion += QRect(0,1024,64,64);
QCOMPARE(dev->exactBounds(), QRect(0,0,139,1040));
QCOMPARE(dev->region(), referenceRegion);
}
void KisPaintDeviceTest::testPixel()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QColor c = Qt::red;
quint8 opacity = 125;
c.setAlpha(opacity);
dev->setPixel(5, 5, c);
QColor c2;
dev->pixel(5, 5, &c2);
QVERIFY(c == c2);
QVERIFY(opacity == c2.alpha());
}
void KisPaintDeviceTest::testPlanarReadWrite()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
quint8* pixel = new quint8[cs->pixelSize()];
cs->fromQColor(QColor(255, 200, 155, 100), pixel);
dev->fill(0, 0, 5000, 5000, pixel);
delete[] pixel;
QColor c1;
dev->pixel(5, 5, &c1);
QVector<quint8*> planes = dev->readPlanarBytes(500, 500, 100, 100);
QVector<quint8*> swappedPlanes;
QCOMPARE((int)planes.size(), (int)dev->channelCount());
for (int i = 0; i < 100*100; i++) {
// BGRA encoded
QVERIFY(planes.at(2)[i] == 255);
QVERIFY(planes.at(1)[i] == 200);
QVERIFY(planes.at(0)[i] == 155);
QVERIFY(planes.at(3)[i] == 100);
}
for (uint i = 1; i < dev->channelCount() + 1; ++i) {
swappedPlanes.append(planes[dev->channelCount() - i]);
}
dev->writePlanarBytes(swappedPlanes, 0, 0, 100, 100);
dev->convertToQImage(0, 0, 0, 1000, 1000).save("planar.png");
dev->pixel(5, 5, &c1);
QVERIFY(c1.red() == 200);
QVERIFY(c1.green() == 255);
QVERIFY(c1.blue() == 100);
QVERIFY(c1.alpha() == 155);
dev->pixel(75, 50, &c1);
QVERIFY(c1.red() == 200);
QVERIFY(c1.green() == 255);
QVERIFY(c1.blue() == 100);
QVERIFY(c1.alpha() == 155);
// check if one of the planes is Null.
Q_ASSERT(planes.size() == 4);
delete [] planes[2];
planes[2] = 0;
dev->writePlanarBytes(planes, 0, 0, 100, 100);
dev->convertToQImage(0, 0, 0, 1000, 1000).save("planar_noR.png");
dev->pixel(75, 50, &c1);
QCOMPARE(c1.red(), 200);
QCOMPARE(c1.green(), 200);
QCOMPARE(c1.blue(), 155);
QCOMPARE(c1.alpha(), 100);
QVector<quint8*>::iterator i;
for (i = planes.begin(); i != planes.end(); ++i)
{
delete [] *i;
}
swappedPlanes.clear();
}
void KisPaintDeviceTest::testBltPerformance()
{
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent.png");
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP fdev = new KisPaintDevice(cs);
fdev->convertFromQImage(image, 0);
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->fill(0, 0, 640, 441, KoColor(Qt::white, cs).data());
QTime t;
t.start();
int x;
for (x = 0; x < 1000; ++x) {
KisPainter gc(dev);
gc.bitBlt(QPoint(0, 0), fdev, image.rect());
}
dbgKrita << x
<< "blits"
<< " done in "
<< t.elapsed()
<< "ms";
}
void KisPaintDeviceTest::testDeviceDuplication()
{
QRect fillRect(0,0,64,64);
quint8 fillPixel[4]={255,255,255,255};
QRect clearRect(10,10,20,20);
QImage referenceImage;
QImage resultImage;
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP device = new KisPaintDevice(cs);
// dbgKrita<<"FILLING";
device->fill(fillRect.left(), fillRect.top(),
fillRect.width(), fillRect.height(),fillPixel);
referenceImage = device->convertToQImage(0);
KisTransaction transaction1(device);
// dbgKrita<<"CLEARING";
device->clear(clearRect);
transaction1.revert();
resultImage = device->convertToQImage(0);
QVERIFY(resultImage == referenceImage);
KisPaintDeviceSP clone = new KisPaintDevice(*device);
KisTransaction transaction(clone);
// dbgKrita<<"CLEARING";
clone->clear(clearRect);
transaction.revert();
resultImage = clone->convertToQImage(0);
QVERIFY(resultImage == referenceImage);
}
void KisPaintDeviceTest::testTranslate()
{
QRect fillRect(0,0,64,64);
quint8 fillPixel[4]={255,255,255,255};
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP device = new KisPaintDevice(cs);
device->fill(fillRect.left(), fillRect.top(),
fillRect.width(), fillRect.height(),fillPixel);
device->setX(-10);
device->setY(10);
QCOMPARE(device->exactBounds(), QRect(-10,10,64,64));
QCOMPARE(device->extent(), QRect(-10,10,64,64));
}
void KisPaintDeviceTest::testOpacity()
{
// blt a semi-transparent image on a white paint device
QImage image(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent.png");
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP fdev = new KisPaintDevice(cs);
fdev->convertFromQImage(image, 0);
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->fill(0, 0, 640, 441, KoColor(Qt::white, cs).data());
KisPainter gc(dev);
gc.bitBlt(QPoint(0, 0), fdev, image.rect());
QImage result = dev->convertToQImage(0, 0, 0, 640, 441);
QImage checkResult(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa_transparent_result.png");
QPoint errpoint;
if (!TestUtil::compareQImages(errpoint, checkResult, result, 1)) {
checkResult.save("kis_paint_device_test_test_blt_fixed_opactiy_expected.png");
result.save("kis_paint_device_test_test_blt_fixed_opacity_result.png");
QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1());
}
}
void KisPaintDeviceTest::testExactBoundsWeirdNullAlphaCase()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QVERIFY(dev->exactBounds().isEmpty());
dev->fill(QRect(10,10,10,10), KoColor(Qt::white, cs));
QCOMPARE(dev->exactBounds(), QRect(10,10,10,10));
const quint8 weirdPixelData[4] = {0,10,0,0};
KoColor weirdColor(weirdPixelData, cs);
dev->setPixel(6,6,weirdColor);
// such weird pixels should not change our opinion about
// device's size
QCOMPARE(dev->exactBounds(), QRect(10,10,10,10));
}
void KisPaintDeviceTest::benchmarkExactBoundsNullDefaultPixel()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QVERIFY(dev->exactBounds().isEmpty());
QRect fillRect(60,60, 1930, 1930);
dev->fill(fillRect, KoColor(Qt::white, cs));
QRect measuredRect;
QBENCHMARK {
// invalidate the cache
dev->setDirty();
measuredRect = dev->exactBounds();
}
QCOMPARE(measuredRect, fillRect);
}
void KisPaintDeviceTest::testAmortizedExactBounds()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QVERIFY(dev->exactBounds().isEmpty());
QRect fillRect(60,60, 833, 833);
QRect extent(0,0,896,896);
dev->fill(fillRect, KoColor(Qt::white, cs));
QCOMPARE(dev->exactBounds(), fillRect);
QCOMPARE(dev->extent(), extent);
QCOMPARE(dev->exactBoundsAmortized(), fillRect);
dev->setDirty();
QCOMPARE(dev->exactBoundsAmortized(), fillRect);
dev->setDirty();
QCOMPARE(dev->exactBoundsAmortized(), extent);
QTest::qSleep(1100);
QCOMPARE(dev->exactBoundsAmortized(), fillRect);
}
void KisPaintDeviceTest::testNonDefaultPixelArea()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
QVERIFY(dev->exactBounds().isEmpty());
QVERIFY(dev->nonDefaultPixelArea().isEmpty());
KoColor defPixel(Qt::red, cs);
dev->setDefaultPixel(defPixel);
QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect);
QVERIFY(dev->nonDefaultPixelArea().isEmpty());
QRect fillRect(10,11,18,14);
dev->fill(fillRect, KoColor(Qt::white, cs));
QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect);
QCOMPARE(dev->nonDefaultPixelArea(), fillRect);
// non-default pixel variant should also handle weird pixels
const quint8 weirdPixelData[4] = {0,10,0,0};
KoColor weirdColor(weirdPixelData, cs);
dev->setPixel(100,100,weirdColor);
// such weird pixels should not change our opinion about
// device's size
QCOMPARE(dev->exactBounds(), KisDefaultBounds::infiniteRect);
QCOMPARE(dev->nonDefaultPixelArea(), fillRect | QRect(100,100,1,1));
}
void KisPaintDeviceTest::testExactBoundsNonTransparent()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(0, 1000, 1000, cs, "merge test");
KisPaintLayerSP layer = new KisPaintLayer(image, "bla", 125);
KisPaintDeviceSP dev = layer->paintDevice();
QVERIFY(dev);
QRect imageRect(0,0,1000,1000);
KoColor defPixel(Qt::red, cs);
dev->setDefaultPixel(defPixel);
QCOMPARE(dev->exactBounds(), imageRect);
QVERIFY(dev->nonDefaultPixelArea().isEmpty());
KoColor fillPixel(Qt::white, cs);
dev->fill(imageRect, KoColor(Qt::white, cs));
QCOMPARE(dev->exactBounds(), imageRect);
QCOMPARE(dev->nonDefaultPixelArea(), imageRect);
dev->fill(QRect(1000,0, 1, 1000), KoColor(Qt::white, cs));
QCOMPARE(dev->exactBounds(), QRect(0,0,1001,1000));
QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,0,1001,1000));
dev->fill(QRect(0,1000, 1000, 1), KoColor(Qt::white, cs));
QCOMPARE(dev->exactBounds(), QRect(0,0,1001,1001));
QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,0,1001,1001));
dev->fill(QRect(0,-1, 1000, 1), KoColor(Qt::white, cs));
QCOMPARE(dev->exactBounds(), QRect(0,-1,1001,1002));
QCOMPARE(dev->nonDefaultPixelArea(), QRect(0,-1,1001,1002));
dev->fill(QRect(-1,0, 1, 1000), KoColor(Qt::white, cs));
QCOMPARE(dev->exactBounds(), QRect(-1,-1,1002,1002));
QCOMPARE(dev->nonDefaultPixelArea(), QRect(-1,-1,1002,1002));
}
KisPaintDeviceSP createWrapAroundPaintDevice(const KoColorSpace *cs)
{
struct TestingDefaultBounds : public KisDefaultBoundsBase {
QRect bounds() const override {
return QRect(0,0,20,20);
}
bool wrapAroundMode() const override {
return true;
}
int currentLevelOfDetail() const override {
return 0;
}
int currentTime() const override {
return 0;
}
bool externalFrameActive() const override {
return false;
}
};
KisPaintDeviceSP dev = new KisPaintDevice(cs);
KisDefaultBoundsBaseSP bounds = new TestingDefaultBounds();
dev->setDefaultBounds(bounds);
return dev;
}
void checkReadWriteRoundTrip(KisPaintDeviceSP dev,
const QRect &rc)
{
KisPaintDeviceSP deviceCopy = new KisPaintDevice(*dev.data());
QRect readRect(10, 10, 20, 20);
int bufSize = rc.width() * rc.height() * dev->pixelSize();
QScopedArrayPointer<quint8> buf1(new quint8[bufSize]);
deviceCopy->readBytes(buf1.data(), rc);
deviceCopy->clear();
QVERIFY(deviceCopy->extent().isEmpty());
QScopedArrayPointer<quint8> buf2(new quint8[bufSize]);
deviceCopy->writeBytes(buf1.data(), rc);
deviceCopy->readBytes(buf2.data(), rc);
QVERIFY(!memcmp(buf1.data(), buf2.data(), bufSize));
}
void KisPaintDeviceTest::testReadBytesWrapAround()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs);
KoColor c1(Qt::red, cs);
KoColor c2(Qt::green, cs);
dev->setPixel(3, 3, c1);
dev->setPixel(18, 18, c2);
const int pixelSize = dev->pixelSize();
{
QRect readRect(10, 10, 20, 20);
QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
readRect.height() *
pixelSize]);
dev->readBytes(buf.data(), readRect);
//dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final1.png");
QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize));
checkReadWriteRoundTrip(dev, readRect);
}
{
// check weird case when the read rect is larger than wrap rect
QRect readRect(10, 10, 30, 30);
QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
readRect.height() *
pixelSize]);
dev->readBytes(buf.data(), readRect);
//dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final2.png");
QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize));
checkReadWriteRoundTrip(dev, readRect);
}
{
// even more large
QRect readRect(10, 10, 40, 40);
QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
readRect.height() *
pixelSize]);
dev->readBytes(buf.data(), readRect);
//dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final3.png");
QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 7) * pixelSize, c2.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 8) * pixelSize, c2.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (7 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (8 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (27 + readRect.width() * 27) * pixelSize, c2.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (28 + readRect.width() * 28) * pixelSize, c2.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (32 + readRect.width() * 12) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (33 + readRect.width() * 13) * pixelSize, c1.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (12 + readRect.width() * 32) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (13 + readRect.width() * 33) * pixelSize, c1.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (32 + readRect.width() * 32) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (33 + readRect.width() * 33) * pixelSize, c1.data(), pixelSize));
checkReadWriteRoundTrip(dev, readRect);
}
{
// check if the wrap rect contains the read rect entirely
QRect readRect(1, 1, 10, 10);
QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
readRect.height() *
pixelSize]);
dev->readBytes(buf.data(), readRect);
//dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final4.png");
QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize));
checkReadWriteRoundTrip(dev, readRect);
}
{
// check if the wrap happens only on vertical side of the rect
QRect readRect(1, 1, 29, 10);
QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
readRect.height() *
pixelSize]);
dev->readBytes(buf.data(), readRect);
//dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final5.png");
QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (21 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (22 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize));
checkReadWriteRoundTrip(dev, readRect);
}
{
// check if the wrap happens only on horizontal side of the rect
QRect readRect(1, 1, 10, 29);
QScopedArrayPointer<quint8> buf(new quint8[readRect.width() *
readRect.height() *
pixelSize]);
dev->readBytes(buf.data(), readRect);
//dev->convertToQImage(0, readRect.x(), readRect.y(), readRect.width(), readRect.height()).save("final6.png");
QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 1) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 2) * pixelSize, c1.data(), pixelSize));
QVERIFY(memcmp(buf.data() + (1 + readRect.width() * 21) * pixelSize, c1.data(), pixelSize));
QVERIFY(!memcmp(buf.data() + (2 + readRect.width() * 22) * pixelSize, c1.data(), pixelSize));
checkReadWriteRoundTrip(dev, readRect);
}
}
#include "kis_random_accessor_ng.h"
void KisPaintDeviceTest::testWrappedRandomAccessor()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs);
KoColor c1(Qt::red, cs);
KoColor c2(Qt::green, cs);
dev->setPixel(3, 3, c1);
dev->setPixel(18, 18, c2);
const int pixelSize = dev->pixelSize();
int x;
int y;
x = 3;
y = 3;
KisRandomAccessorSP dstIt = dev->createRandomAccessorNG(x, y);
QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
QCOMPARE(dstIt->numContiguousColumns(x), 17);
QCOMPARE(dstIt->numContiguousRows(y), 17);
x = 23;
y = 23;
dstIt->moveTo(x, y);
QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
QCOMPARE(dstIt->numContiguousColumns(x), 17);
QCOMPARE(dstIt->numContiguousRows(y), 17);
x = 3;
y = 23;
dstIt->moveTo(x, y);
QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
QCOMPARE(dstIt->numContiguousColumns(x), 17);
QCOMPARE(dstIt->numContiguousRows(y), 17);
x = 23;
y = 3;
dstIt->moveTo(x, y);
QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
QCOMPARE(dstIt->numContiguousColumns(x), 17);
QCOMPARE(dstIt->numContiguousRows(y), 17);
x = -17;
y = 3;
dstIt->moveTo(x, y);
QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
QCOMPARE(dstIt->numContiguousColumns(x), 17);
QCOMPARE(dstIt->numContiguousRows(y), 17);
x = 3;
y = -17;
dstIt->moveTo(x, y);
QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
QCOMPARE(dstIt->numContiguousColumns(x), 17);
QCOMPARE(dstIt->numContiguousRows(y), 17);
x = -17;
y = -17;
dstIt->moveTo(x, y);
QVERIFY(!memcmp(dstIt->rawData(), c1.data(), pixelSize));
QCOMPARE(dstIt->numContiguousColumns(x), 17);
QCOMPARE(dstIt->numContiguousRows(y), 17);
}
#include "kis_iterator_ng.h"
static bool nextRowGeneral(KisHLineIteratorSP it, int y, const QRect &rc) {
it->nextRow();
return y < rc.height();
}
static bool nextRowGeneral(KisVLineIteratorSP it, int y, const QRect &rc) {
it->nextColumn();
return y < rc.width();
}
template <class T>
bool checkXY(const QPoint &pt, const QPoint &realPt) {
Q_UNUSED(pt);
Q_UNUSED(realPt);
return false;
}
template <>
bool checkXY<KisHLineIteratorSP>(const QPoint &pt, const QPoint &realPt) {
return pt == realPt;
}
template <>
bool checkXY<KisVLineIteratorSP>(const QPoint &pt, const QPoint &realPt) {
return pt.x() == realPt.y() && pt.y() == realPt.x();
}
#include <kis_wrapped_rect.h>
template <class T>
bool checkConseqPixels(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) {
Q_UNUSED(value);
Q_UNUSED(pt);
Q_UNUSED(wrappedRect);
return false;
}
template <>
bool checkConseqPixels<KisHLineIteratorSP>(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) {
int x = KisWrappedRect::xToWrappedX(pt.x(), wrappedRect.wrapRect());
int borderX = wrappedRect.originalRect().x() + wrappedRect.wrapRect().width();
int conseq = x >= borderX ? wrappedRect.wrapRect().right() - x + 1 : borderX - x;
conseq = qMin(conseq, wrappedRect.originalRect().right() - pt.x() + 1);
return value == conseq;
}
template <>
bool checkConseqPixels<KisVLineIteratorSP>(int value, const QPoint &pt, const KisWrappedRect &wrappedRect) {
Q_UNUSED(pt);
Q_UNUSED(wrappedRect);
return value == 1;
}
template <class IteratorSP>
IteratorSP createIterator(KisPaintDeviceSP dev, const QRect &rc) {
Q_UNUSED(dev);
Q_UNUSED(rc);
return 0;
}
template <>
KisHLineIteratorSP createIterator(KisPaintDeviceSP dev,
const QRect &rc) {
return dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
}
template <>
KisVLineIteratorSP createIterator(KisPaintDeviceSP dev,
const QRect &rc) {
return dev->createVLineIteratorNG(rc.x(), rc.y(), rc.height());
}
template <class IteratorSP>
void testWrappedLineIterator(QString testName, const QRect &rect)
{
testName = QString("%1_%2_%3_%4_%5")
.arg(testName)
.arg(rect.x())
.arg(rect.y())
.arg(rect.width())
.arg(rect.height());
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs);
// test rect fits the wrap rect in both dimensions
IteratorSP it = createIterator<IteratorSP>(dev, rect);
int y = 0;
do {
int x = 0;
do {
quint8 *data = it->rawData();
data[0] = 10 * x;
data[1] = 10 * y;
data[2] = 0;
data[3] = 255;
x++;
} while (it->nextPixel());
} while (nextRowGeneral(it, ++y, rect));
QRect rc = dev->defaultBounds()->bounds() | dev->exactBounds();
QImage result = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QVERIFY(TestUtil::checkQImage(result, "paint_device_test", "wrapped_iterators", testName));
}
template <class IteratorSP>
void testWrappedLineIterator(const QString &testName)
{
testWrappedLineIterator<IteratorSP>(testName, QRect(10,10,20,20));
testWrappedLineIterator<IteratorSP>(testName, QRect(10,10,10,20));
testWrappedLineIterator<IteratorSP>(testName, QRect(10,10,20,10));
testWrappedLineIterator<IteratorSP>(testName, QRect(10,10,10,10));
testWrappedLineIterator<IteratorSP>(testName, QRect(0,0,20,20));
}
void KisPaintDeviceTest::testWrappedHLineIterator()
{
testWrappedLineIterator<KisHLineIteratorSP>("hline_iterator");
}
void KisPaintDeviceTest::testWrappedVLineIterator()
{
testWrappedLineIterator<KisVLineIteratorSP>("vline_iterator");
}
template <class IteratorSP>
void testWrappedLineIteratorReadMoreThanBounds(QString testName)
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs);
KisPaintDeviceSP dst = new KisPaintDevice(cs);
// fill device with a gradient
QRect bounds = dev->defaultBounds()->bounds();
for (int y = bounds.y(); y < bounds.y() + bounds.height(); y++) {
for (int x = bounds.x(); x < bounds.x() + bounds.width(); x++) {
QColor c((10 * x) % 255, (10 * y) % 255, 0, 255);
dev->setPixel(x, y, c);
}
}
// test rect doesn't fit the wrap rect in both dimentions
const QRect &rect(bounds.adjusted(-6,-6,8,8));
KisRandomAccessorSP dstIt = dst->createRandomAccessorNG(rect.x(), rect.y());
IteratorSP it = createIterator<IteratorSP>(dev, rect);
for (int y = rect.y(); y < rect.y() + rect.height(); y++) {
for (int x = rect.x(); x < rect.x() + rect.width(); x++) {
quint8 *data = it->rawData();
QVERIFY(checkConseqPixels<IteratorSP>(it->nConseqPixels(), QPoint(x, y), KisWrappedRect(rect, bounds)));
dstIt->moveTo(x, y);
memcpy(dstIt->rawData(), data, cs->pixelSize());
QVERIFY(checkXY<IteratorSP>(QPoint(it->x(), it->y()), QPoint(x,y)));
bool stepDone = it->nextPixel();
QCOMPARE(stepDone, x < rect.x() + rect.width() - 1);
}
if (!nextRowGeneral(it, y, rect)) break;
}
testName = QString("%1_%2_%3_%4_%5")
.arg(testName)
.arg(rect.x())
.arg(rect.y())
.arg(rect.width())
.arg(rect.height());
QRect rc = rect;
QImage result = dst->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QImage ref = dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height());
QVERIFY(TestUtil::checkQImage(result, "paint_device_test", "wrapped_iterators_huge", testName, 1));
}
void KisPaintDeviceTest::testWrappedHLineIteratorReadMoreThanBounds()
{
testWrappedLineIteratorReadMoreThanBounds<KisHLineIteratorSP>("hline_iterator");
}
void KisPaintDeviceTest::testWrappedVLineIteratorReadMoreThanBounds()
{
testWrappedLineIteratorReadMoreThanBounds<KisVLineIteratorSP>("vline_iterator");
}
void KisPaintDeviceTest::testMoveWrapAround()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = createWrapAroundPaintDevice(cs);
KoColor c1(Qt::red, cs);
KoColor c2(Qt::green, cs);
dev->setPixel(3, 3, c1);
dev->setPixel(18, 18, c2);
// QRect rc = dev->defaultBounds()->bounds();
//dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()).save("move0.png");
QCOMPARE(dev->exactBounds(), QRect(3,3,16,16));
dev->moveTo(QPoint(10,10));
QCOMPARE(dev->exactBounds(), QRect(8,8,6,6));
//dev->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()).save("move1.png");
}
#include "kis_lock_free_cache.h"
#define NUM_TYPES 3
// high-concurrency
#define NUM_CYCLES 500000
#define NUM_THREADS 4
struct TestingCache : KisLockFreeCache<int> {
int calculateNewValue() const override {
return m_realValue;
}
QAtomicInt m_realValue;
};
class CacheStressJob : public QRunnable
{
public:
CacheStressJob(TestingCache &cache)
: m_cache(cache),
m_oldValue(0)
{
}
void run() override {
for(qint32 i = 0; i < NUM_CYCLES; i++) {
qint32 type = i % NUM_TYPES;
switch(type) {
case 0:
m_cache.m_realValue.ref();
m_oldValue = m_cache.m_realValue;
m_cache.invalidate();
break;
case 1:
{
int newValue = m_cache.getValue();
Q_ASSERT(newValue >= m_oldValue);
Q_UNUSED(newValue);
}
break;
case 3:
QTest::qSleep(3);
break;
}
}
}
private:
TestingCache &m_cache;
int m_oldValue;
};
void KisPaintDeviceTest::testCacheState()
{
TestingCache cache;
QList<CacheStressJob*> jobsList;
CacheStressJob *job;
for(qint32 i = 0; i < NUM_THREADS; i++) {
//job = new CacheStressJob(value, cacheValue, cacheState);
job = new CacheStressJob(cache);
job->setAutoDelete(true);
jobsList.append(job);
}
QThreadPool pool;
pool.setMaxThreadCount(NUM_THREADS);
Q_FOREACH (job, jobsList) {
pool.start(job);
}
pool.waitForDone();
}
struct TestingLodDefaultBounds : public KisDefaultBoundsBase {
TestingLodDefaultBounds(const QRect &bounds = QRect(0,0,100,100))
: m_lod(0), m_bounds(bounds) {}
QRect bounds() const override {
return m_bounds;
}
bool wrapAroundMode() const override {
return false;
}
int currentLevelOfDetail() const override {
return m_lod;
}
int currentTime() const override {
return 0;
}
bool externalFrameActive() const override {
return false;
}
void testingSetLevelOfDetail(int lod) {
m_lod = lod;
}
private:
int m_lod;
QRect m_bounds;
};
void fillGradientDevice(KisPaintDeviceSP dev, const QRect &rect, bool flat = false)
{
if (flat) {
dev->fill(rect, KoColor(Qt::red, dev->colorSpace()));
} else {
// fill device with a gradient
KisSequentialIterator it(dev, rect);
do {
QColor c((10 * it.x()) & 0xFF, (10 * it.y()) & 0xFF, 0, 255);
KoColor color(c, dev->colorSpace());
memcpy(it.rawData(), color.data(), dev->pixelSize());
} while (it.nextPixel());
}
}
#include "kis_lod_transform.h"
void KisPaintDeviceTest::testLodTransform()
{
const int lod = 2; // round to 4
KisLodTransform t(lod);
QRect rc1(-16, -16, 8, 8);
QRect rc2(-16, -16, 7, 7);
QRect rc3(-15, -15, 7, 7);
QCOMPARE(t.alignedRect(rc1, lod), rc1);
QCOMPARE(t.alignedRect(rc2, lod), rc1);
QCOMPARE(t.alignedRect(rc3, lod), rc1);
}
#include "krita_utils.h"
void syncLodCache(KisPaintDeviceSP dev, int levelOfDetail)
{
KisPaintDevice::LodDataStruct* s = dev->createLodDataStruct(levelOfDetail);
QRegion region = dev->regionForLodSyncing();
Q_FOREACH(QRect rect2, KritaUtils::splitRegionIntoPatches(region, KritaUtils::optimalPatchSize())) {
dev->updateLodDataStruct(s, rect2);
}
dev->uploadLodDataStruct(s);
}
void KisPaintDeviceTest::testLodDevice()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds();
dev->setDefaultBounds(bounds);
// fill device with a gradient
// QRect rect = dev->defaultBounds()->bounds();
// fillGradientDevice(dev, rect);
fillGradientDevice(dev, QRect(50,50,30,30));
QCOMPARE(dev->exactBounds(), QRect(50,50,30,30));
QImage result;
qDebug() << ppVar(dev->exactBounds());
result = dev->convertToQImage(0,0,0,100,100);
/*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test",
"lod", "initial"));
bounds->testingSetLevelOfDetail(1);
syncLodCache(dev, 1);
QCOMPARE(dev->exactBounds(), QRect(25,25,15,15));
qDebug() << ppVar(dev->exactBounds());
result = dev->convertToQImage(0,0,0,100,100);
/*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test",
"lod", "lod1"));
bounds->testingSetLevelOfDetail(2);
QCOMPARE(dev->exactBounds(), QRect(25,25,15,15));
qDebug() << ppVar(dev->exactBounds());
result = dev->convertToQImage(0,0,0,100,100);
/*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test",
"lod", "lod1"));
syncLodCache(dev, 2);
QCOMPARE(dev->exactBounds(), QRect(12,12,8,8));
qDebug() << ppVar(dev->exactBounds());
result = dev->convertToQImage(0,0,0,100,100);
/*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test",
"lod", "lod2"));
bounds->testingSetLevelOfDetail(0);
dev->setX(20);
dev->setY(10);
bounds->testingSetLevelOfDetail(1);
syncLodCache(dev, 1);
QCOMPARE(dev->exactBounds(), QRect(35,30,15,15));
qDebug() << ppVar(dev->exactBounds()) << ppVar(dev->x()) << ppVar(dev->y());
result = dev->convertToQImage(0,0,0,100,100);
/*QVERIFY*/(TestUtil::checkQImage(result, "paint_device_test",
"lod", "lod1-offset-6-14"));
}
void KisPaintDeviceTest::benchmarkLod1Generation()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,6000,4000));
dev->setDefaultBounds(bounds);
// fill device with a gradient
QRect rect = dev->defaultBounds()->bounds();
fillGradientDevice(dev, rect, true);
QBENCHMARK {
bounds->testingSetLevelOfDetail(1);
syncLodCache(dev, 1);
}
}
void KisPaintDeviceTest::benchmarkLod2Generation()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,6000,4000));
dev->setDefaultBounds(bounds);
// fill device with a gradient
QRect rect = dev->defaultBounds()->bounds();
fillGradientDevice(dev, rect, true);
QBENCHMARK {
bounds->testingSetLevelOfDetail(2);
syncLodCache(dev, 2);
}
}
void KisPaintDeviceTest::benchmarkLod3Generation()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,3000,2000));
dev->setDefaultBounds(bounds);
// fill device with a gradient
QRect rect = dev->defaultBounds()->bounds();
fillGradientDevice(dev, rect, true);
QBENCHMARK {
bounds->testingSetLevelOfDetail(3);
syncLodCache(dev, 3);
}
}
void KisPaintDeviceTest::benchmarkLod4Generation()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
TestingLodDefaultBounds *bounds = new TestingLodDefaultBounds(QRect(0,0,3000,2000));
dev->setDefaultBounds(bounds);
// fill device with a gradient
QRect rect = dev->defaultBounds()->bounds();
fillGradientDevice(dev, rect, true);
QBENCHMARK {
bounds->testingSetLevelOfDetail(4);
syncLodCache(dev, 4);
}
}
#include "kis_keyframe_channel.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_paint_device_frames_interface.h"
#include "testing_timed_default_bounds.h"
void KisPaintDeviceTest::testFramesLeaking()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
dev->setDefaultBounds(bounds);
KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content);
QVERIFY(channel);
KisPaintDeviceFramesInterface *i = dev->framesInterface();
QVERIFY(i);
QCOMPARE(i->frames().size(), 1);
KisPaintDeviceFramesInterface::TestingDataObjects o;
// Itinial state: one frame, m_data is kept separate
o = i->testingGetDataObjects();
QVERIFY(o.m_data);
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 1);
QVERIFY(o.m_currentData == o.m_frames[0]);
// add keyframe at position 10
channel->addKeyframe(10);
// two frames, m_data has a default empty value
o = i->testingGetDataObjects();
QVERIFY(o.m_data);
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
// add keyframe at position 20
channel->addKeyframe(20);
// three frames, m_data is default, current frame is 0
o = i->testingGetDataObjects();
QVERIFY(o.m_data);
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 3);
QVERIFY(o.m_currentData == o.m_frames[0]);
// switch to frame 10
bounds->testingSetTime(10);
// three frames, m_data is default, current frame is 10
o = i->testingGetDataObjects();
QVERIFY(o.m_data);
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QVERIFY(o.m_currentData == o.m_frames[1]);
QCOMPARE(o.m_frames.size(), 3);
// switch to frame 20
bounds->testingSetTime(20);
// three frames, m_data is default, current frame is 20
o = i->testingGetDataObjects();
QVERIFY(o.m_data);
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QVERIFY(o.m_currentData == o.m_frames[2]);
QCOMPARE(o.m_frames.size(), 3);
// switch to frame 15
bounds->testingSetTime(15);
// three frames, m_data is default, current frame is 10
o = i->testingGetDataObjects();
QVERIFY(o.m_data);
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QVERIFY(o.m_currentData == o.m_frames[1]);
QCOMPARE(o.m_frames.size(), 3);
KisKeyframeSP key;
// deletion of frame 0 is forbidden
key = channel->keyframeAt(0);
QVERIFY(key);
QVERIFY(channel->deleteKeyframe(key));
// delete keyframe at position 11
key = channel->activeKeyframeAt(11);
QVERIFY(key);
QCOMPARE(key->time(), 10);
QVERIFY(channel->deleteKeyframe(key));
// two frames, m_data is default, current frame is 0
o = i->testingGetDataObjects();
QVERIFY(o.m_data);
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
//QVERIFY(o.m_currentData == o.m_frames[0]);
QCOMPARE(o.m_frames.size(), 2);
// deletion of frame 0 is forbidden
key = channel->activeKeyframeAt(11);
QVERIFY(key);
QCOMPARE(key->time(), 0);
QVERIFY(channel->deleteKeyframe(key));
// nothing changed
o = i->testingGetDataObjects();
QVERIFY(o.m_data);
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
//QVERIFY(o.m_currentData == o.m_frames[0]);
QCOMPARE(o.m_frames.size(), 2);
// delete keyframe at position 20
key = channel->activeKeyframeAt(20);
QVERIFY(key);
QCOMPARE(key->time(), 20);
QVERIFY(channel->deleteKeyframe(key));
// one keyframe is left at position 0, m_data is default
o = i->testingGetDataObjects();
QVERIFY(o.m_data);
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
//QVERIFY(o.m_currentData == o.m_frames[0]);
QCOMPARE(o.m_frames.size(), 1);
// ensure all the objects in the list of all objects are unique
QList<KisPaintDeviceData*> allObjects = i->testingGetDataObjectsList();
QSet<KisPaintDeviceData*> uniqueObjects;
Q_FOREACH (KisPaintDeviceData *obj, allObjects) {
if (!obj) continue;
QVERIFY(!uniqueObjects.contains(obj));
uniqueObjects.insert(obj);
}
}
void KisPaintDeviceTest::testFramesUndoRedo()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
dev->setDefaultBounds(bounds);
KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content);
QVERIFY(channel);
KisPaintDeviceFramesInterface *i = dev->framesInterface();
QVERIFY(i);
QCOMPARE(i->frames().size(), 1);
KisPaintDeviceFramesInterface::TestingDataObjects o;
// Itinial state: one frame, m_data shared
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 1);
QVERIFY(o.m_currentData == o.m_frames[0]);
// add a keyframe
KUndo2Command cmdAdd;
int frameId = -1;
const int time = 1;
channel->addKeyframe(time, &cmdAdd);
frameId = channel->frameIdAt(time);
//int frameId = i->createFrame(false, 0, QPoint(), &cmdAdd);
QCOMPARE(frameId, 1);
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdAdd.undo();
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 1);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdAdd.redo();
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
KUndo2Command cmdRemove;
KisKeyframeSP keyframe = channel->keyframeAt(time);
QVERIFY(keyframe);
channel->deleteKeyframe(keyframe, &cmdRemove);
//i->deleteFrame(1, &cmdRemove);
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 1);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdRemove.undo();
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdRemove.redo();
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 1);
QVERIFY(o.m_currentData == o.m_frames[0]);
}
void KisPaintDeviceTest::testFramesUndoRedoWithChannel()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
dev->setDefaultBounds(bounds);
KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content);
QVERIFY(channel);
KisPaintDeviceFramesInterface *i = dev->framesInterface();
QVERIFY(i);
QCOMPARE(i->frames().size(), 1);
KisPaintDeviceFramesInterface::TestingDataObjects o;
// Itinial state: one frame, m_data shared
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 1);
QVERIFY(o.m_currentData == o.m_frames[0]);
// add a keyframe
KUndo2Command cmdAdd;
KisKeyframeSP frame = channel->addKeyframe(10, &cmdAdd);
QVERIFY(channel->keyframeAt(10));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdAdd.undo();
QVERIFY(!channel->keyframeAt(10));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 1);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdAdd.redo();
QVERIFY(channel->keyframeAt(10));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
KUndo2Command cmdRemove;
channel->deleteKeyframe(frame, &cmdRemove);
QVERIFY(!channel->keyframeAt(10));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 1);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdRemove.undo();
QVERIFY(channel->keyframeAt(10));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdRemove.redo();
QVERIFY(!channel->keyframeAt(10));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 1);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdRemove.undo();
QVERIFY(channel->keyframeAt(10));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
KUndo2Command cmdMove;
channel->moveKeyframe(frame, 12, &cmdMove);
QVERIFY(!channel->keyframeAt(10));
QVERIFY(channel->keyframeAt(12));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdMove.undo();
QVERIFY(channel->keyframeAt(10));
QVERIFY(!channel->keyframeAt(12));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
cmdMove.redo();
QVERIFY(!channel->keyframeAt(10));
QVERIFY(channel->keyframeAt(12));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // default m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
QVERIFY(o.m_currentData == o.m_frames[0]);
}
void fillRect(KisPaintDeviceSP dev, int time, const QRect &rc, TestUtil::TestingTimedDefaultBounds *bounds)
{
KUndo2Command parentCommand;
KisRasterKeyframeChannel *channel = dev->keyframeChannel();
KisKeyframeSP frame = channel->addKeyframe(time, &parentCommand);
const int oldTime = bounds->currentTime();
bounds->testingSetTime(time);
KoColor color(Qt::red, dev->colorSpace());
dev->fill(rc, color);
bounds->testingSetTime(oldTime);
}
bool checkRect(KisPaintDeviceSP dev, int time, const QRect &rc, TestUtil::TestingTimedDefaultBounds *bounds)
{
const int oldTime = bounds->currentTime();
bounds->testingSetTime(time);
bool result = dev->exactBounds() == rc;
if (!result) {
qDebug() << "Failed to check frame:" << ppVar(time) << ppVar(rc) << ppVar(dev->exactBounds());
}
bounds->testingSetTime(oldTime);
return result;
}
void testCrossDeviceFrameCopyImpl(bool useChannel)
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev1 = new KisPaintDevice(cs);
KisPaintDeviceSP dev2 = new KisPaintDevice(cs);
const KoColorSpace *cs3 = KoColorSpaceRegistry::instance()->rgb16();
KisPaintDeviceSP dev3 = new KisPaintDevice(cs3);
TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
dev1->setDefaultBounds(bounds);
dev2->setDefaultBounds(bounds);
dev3->setDefaultBounds(bounds);
KisRasterKeyframeChannel *channel1 = dev1->createKeyframeChannel(KisKeyframeChannel::Content);
KisPaintDeviceFramesInterface *i1 = dev1->framesInterface();
QVERIFY(channel1);
QVERIFY(i1);
KisRasterKeyframeChannel *channel2 = dev2->createKeyframeChannel(KisKeyframeChannel::Content);
KisPaintDeviceFramesInterface *i2 = dev2->framesInterface();
QVERIFY(channel2);
QVERIFY(i2);
KisRasterKeyframeChannel *channel3 = dev3->createKeyframeChannel(KisKeyframeChannel::Content);
KisPaintDeviceFramesInterface *i3 = dev3->framesInterface();
QVERIFY(channel3);
QVERIFY(i3);
fillRect(dev1, 10, QRect(100,100,100,100), bounds);
fillRect(dev2, 20, QRect(200,200,100,100), bounds);
fillRect(dev3, 30, QRect(300,300,100,100), bounds);
QCOMPARE(dev1->exactBounds(), QRect());
const int dstFrameId1 = channel1->frameIdAt(10);
const int srcFrameId2 = channel2->frameIdAt(20);
const int srcFrameId3 = channel3->frameIdAt(30);
KUndo2Command cmd1;
if (!useChannel) {
dev1->framesInterface()->uploadFrame(srcFrameId2, dstFrameId1, dev2);
} else {
KisKeyframeSP k = channel1->copyExternalKeyframe(channel2, 20, 10, &cmd1);
}
QCOMPARE(dev1->exactBounds(), QRect());
QVERIFY(checkRect(dev1, 10, QRect(200,200,100,100), bounds));
if (useChannel) {
cmd1.undo();
QVERIFY(checkRect(dev1, 10, QRect(100,100,100,100), bounds));
}
KUndo2Command cmd2;
if (!useChannel) {
dev1->framesInterface()->uploadFrame(srcFrameId3, dstFrameId1, dev3);
} else {
KisKeyframeSP k = channel1->copyExternalKeyframe(channel3, 30, 10, &cmd2);
}
QCOMPARE(dev1->exactBounds(), QRect());
QVERIFY(checkRect(dev1, 10, QRect(300,300,100,100), bounds));
if (useChannel) {
cmd2.undo();
QVERIFY(checkRect(dev1, 10, QRect(100,100,100,100), bounds));
}
}
void KisPaintDeviceTest::testCrossDeviceFrameCopyDirect()
{
testCrossDeviceFrameCopyImpl(false);
}
void KisPaintDeviceTest::testCrossDeviceFrameCopyChannel()
{
testCrossDeviceFrameCopyImpl(true);
}
#include "kis_surrogate_undo_adapter.h"
void KisPaintDeviceTest::testLazyFrameCreation()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
dev->setDefaultBounds(bounds);
KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content);
QVERIFY(channel);
KisPaintDeviceFramesInterface *i = dev->framesInterface();
QVERIFY(i);
QCOMPARE(i->frames().size(), 1);
bounds->testingSetTime(10);
QCOMPARE(i->frames().size(), 1);
KisSurrogateUndoAdapter undoAdapter;
{
KisTransaction transaction1(dev);
transaction1.commit(&undoAdapter);
}
QCOMPARE(i->frames().size(), 2);
undoAdapter.undoAll();
QCOMPARE(i->frames().size(), 1);
undoAdapter.redoAll();
QCOMPARE(i->frames().size(), 2);
}
void KisPaintDeviceTest::testCopyPaintDeviceWithFrames()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dev = new KisPaintDevice(cs);
TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds();
dev->setDefaultBounds(bounds);
KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KisKeyframeChannel::Content);
QVERIFY(channel);
KisPaintDeviceFramesInterface *i = dev->framesInterface();
QVERIFY(i);
QCOMPARE(i->frames().size(), 1);
KisPaintDeviceFramesInterface::TestingDataObjects o;
// Itinial state: one frame, m_data shared
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 1);
QVERIFY(o.m_currentData == o.m_frames[0]);
// add a keyframe
KUndo2Command cmdAdd;
KisKeyframeSP frame = channel->addKeyframe(10, &cmdAdd);
QVERIFY(channel->keyframeAt(10));
o = i->testingGetDataObjects();
QVERIFY(o.m_data); // m_data should always be present
QVERIFY(!o.m_lodData);
QVERIFY(!o.m_externalFrameData);
QCOMPARE(o.m_frames.size(), 2);
//QVERIFY(o.m_currentData == o.m_frames[0]);
KisPaintDeviceSP newDev = new KisPaintDevice(*dev, true, 0);
QVERIFY(channel->keyframeAt(0));
QVERIFY(channel->keyframeAt(10));
}
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/variance.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/max.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_smallint.hpp>
#include "KoCompositeOpRegistry.h"
using namespace boost::accumulators;
accumulator_set<qreal, stats<tag::variance, tag::max, tag::min> > accum;
void KisPaintDeviceTest::testCompositionAssociativity()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
qsrand(500);
boost::mt11213b _rnd0(qrand());
boost::mt11213b _rnd1(qrand());
boost::mt11213b _rnd2(qrand());
boost::mt11213b _rnd3(qrand());
boost::uniform_smallint<int> rnd0(0, 255);
boost::uniform_smallint<int> rnd1(0, 255);
boost::uniform_smallint<int> rnd2(0, 255);
boost::uniform_smallint<int> rnd3(0, 255);
QList<KoCompositeOp*> allCompositeOps = cs->compositeOps();
Q_FOREACH (const KoCompositeOp *op, allCompositeOps) {
accumulator_set<qreal, stats<tag::variance, tag::max, tag::min> > accum;
const int numIterations = 10000;
for (int j = 0; j < numIterations; j++) {
KoColor c1(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs);
KoColor c2(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs);
KoColor c3(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs);
//KoColor c4(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs);
//KoColor c5(QColor(rnd0(_rnd0), rnd1(_rnd1), rnd2(_rnd2), rnd3(_rnd3)), cs);
KoColor r1(QColor(Qt::transparent), cs);
KoColor r2(QColor(Qt::transparent), cs);
KoColor r3(QColor(Qt::transparent), cs);
op->composite(r1.data(), 0, c1.data(), 0, 0,0, 1,1, 255);
op->composite(r1.data(), 0, c2.data(), 0, 0,0, 1,1, 255);
op->composite(r1.data(), 0, c3.data(), 0, 0,0, 1,1, 255);
//op->composite(r1.data(), 0, c4.data(), 0, 0,0, 1,1, 255);
//op->composite(r1.data(), 0, c5.data(), 0, 0,0, 1,1, 255);
op->composite(r3.data(), 0, c2.data(), 0, 0,0, 1,1, 255);
op->composite(r3.data(), 0, c3.data(), 0, 0,0, 1,1, 255);
//op->composite(r3.data(), 0, c4.data(), 0, 0,0, 1,1, 255);
//op->composite(r3.data(), 0, c5.data(), 0, 0,0, 1,1, 255);
op->composite(r2.data(), 0, c1.data(), 0, 0,0, 1,1, 255);
op->composite(r2.data(), 0, r3.data(), 0, 0,0, 1,1, 255);
const quint8 *p1 = r1.data();
const quint8 *p2 = r2.data();
if (memcmp(p1, p2, 4) != 0) {
for (int i = 0; i < 4; i++) {
accum(qAbs(p1[i] - p2[i]));
}
}
}
qDebug("Errors for op %25s err rate %7.2f var %7.2f max %7.2f",
- op->id().toAscii().data(),
+ op->id().toLatin1().data(),
(qreal(count(accum)) / (4 * numIterations)),
variance(accum),
count(accum) > 0 ? (max)(accum) : 0);
}
}
QTEST_MAIN(KisPaintDeviceTest)
diff --git a/libs/image/tests/kis_painter_test.cpp b/libs/image/tests/kis_painter_test.cpp
index d7b53cbe9a..7ae36d03a7 100644
--- a/libs/image/tests/kis_painter_test.cpp
+++ b/libs/image/tests/kis_painter_test.cpp
@@ -1,522 +1,800 @@
/*
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_painter_test.h"
#include <QTest>
#include <kis_debug.h>
#include <QRect>
#include <QTime>
#include <QtXml>
#include <KoChannelInfo.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoCompositeOpRegistry.h>
#include "kis_datamanager.h"
#include "kis_types.h"
#include "kis_paint_device.h"
#include "kis_painter.h"
#include "kis_pixel_selection.h"
#include "kis_fill_painter.h"
#include <kis_fixed_paint_device.h>
#include "testutil.h"
#include <kis_iterator_ng.h>
void KisPainterTest::allCsApplicator(void (KisPainterTest::* funcPtr)(const KoColorSpace*cs))
{
QList<const KoColorSpace*> colorsapces = KoColorSpaceRegistry::instance()->allColorSpaces(KoColorSpaceRegistry::AllColorSpaces, KoColorSpaceRegistry::OnlyDefaultProfile);
Q_FOREACH (const KoColorSpace* cs, colorsapces) {
QString csId = cs->id();
// ALL THESE COLORSPACES ARE BROKEN: WE NEED UNITTESTS FOR COLORSPACES!
if (csId.startsWith("KS")) continue;
if (csId.startsWith("Xyz")) continue;
if (csId.startsWith('Y')) continue;
if (csId.contains("AF")) continue;
if (csId == "GRAYU16") continue; // No point in testing bounds with a cs without alpha
if (csId == "GRAYU8") continue; // No point in testing bounds with a cs without alpha
dbgKrita << "Testing with cs" << csId;
if (cs && cs->compositeOp(COMPOSITE_OVER) != 0) {
(this->*funcPtr)(cs);
} else {
dbgKrita << "Cannot bitBlt for cs" << csId;
}
}
}
void KisPainterTest::testSimpleBlt(const KoColorSpace * cs)
{
KisPaintDeviceSP dst = new KisPaintDevice(cs);
KisPaintDeviceSP src = new KisPaintDevice(cs);
KoColor c(Qt::red, cs);
c.setOpacity(quint8(128));
src->fill(20, 20, 20, 20, c.data());
QCOMPARE(src->exactBounds(), QRect(20, 20, 20, 20));
const KoCompositeOp* op;
{
op = cs->compositeOp(COMPOSITE_OVER);
KisPainter painter(dst);
painter.setCompositeOp(op);
painter.bitBlt(50, 50, src, 20, 20, 20, 20);
painter.end();
QCOMPARE(dst->exactBounds(), QRect(50,50,20,20));
}
dst->clear();
{
op = cs->compositeOp(COMPOSITE_COPY);
KisPainter painter(dst);
painter.setCompositeOp(op);
painter.bitBlt(50, 50, src, 20, 20, 20, 20);
painter.end();
QCOMPARE(dst->exactBounds(), QRect(50,50,20,20));
}
}
void KisPainterTest::testSimpleBlt()
{
allCsApplicator(&KisPainterTest::testSimpleBlt);
}
/*
Note: the bltSelection tests assume the following geometry:
0,0 0,30
+---------+------+
| 10,10 | |
| +----+ |
| |####| |
| |####| |
+----+----+ |
| 20,20 |
| |
| |
+----------------+
30,30
*/
void KisPainterTest::testPaintDeviceBltSelection(const KoColorSpace * cs)
{
KisPaintDeviceSP dst = new KisPaintDevice(cs);
KisPaintDeviceSP src = new KisPaintDevice(cs);
KoColor c(Qt::red, cs);
c.setOpacity(quint8(128));
src->fill(0, 0, 20, 20, c.data());
QCOMPARE(src->exactBounds(), QRect(0, 0, 20, 20));
KisSelectionSP selection = new KisSelection();
selection->pixelSelection()->select(QRect(10, 10, 20, 20));
selection->updateProjection();
QCOMPARE(selection->selectedExactRect(), QRect(10, 10, 20, 20));
KisPainter painter(dst);
painter.setSelection(selection);
painter.bitBlt(0, 0, src, 0, 0, 30, 30);
painter.end();
QImage image = dst->convertToQImage(0);
image.save("blt_Selection_" + cs->name() + ".png");
QCOMPARE(dst->exactBounds(), QRect(10, 10, 10, 10));
const KoCompositeOp* op = cs->compositeOp(COMPOSITE_SUBTRACT);
if (op->id() == COMPOSITE_SUBTRACT) {
KisPaintDeviceSP dst2 = new KisPaintDevice(cs);
KisPainter painter2(dst2);
painter2.setSelection(selection);
painter2.setCompositeOp(op);
painter2.bitBlt(0, 0, src, 0, 0, 30, 30);
painter2.end();
QCOMPARE(dst2->exactBounds(), QRect(10, 10, 10, 10));
}
}
void KisPainterTest::testPaintDeviceBltSelection()
{
allCsApplicator(&KisPainterTest::testPaintDeviceBltSelection);
}
void KisPainterTest::testPaintDeviceBltSelectionIrregular(const KoColorSpace * cs)
{
KisPaintDeviceSP dst = new KisPaintDevice(cs);
KisPaintDeviceSP src = new KisPaintDevice(cs);
KisFillPainter gc(src);
gc.fillRect(0, 0, 20, 20, KoColor(Qt::red, cs));
gc.end();
QCOMPARE(src->exactBounds(), QRect(0, 0, 20, 20));
KisSelectionSP sel = new KisSelection();
KisPixelSelectionSP psel = sel->pixelSelection();
psel->select(QRect(10, 15, 20, 15));
psel->select(QRect(15, 10, 15, 5));
QCOMPARE(psel->selectedExactRect(), QRect(10, 10, 20, 20));
QCOMPARE(TestUtil::alphaDevicePixel(psel, 13, 13), MIN_SELECTED);
KisPainter painter(dst);
painter.setSelection(sel);
painter.bitBlt(0, 0, src, 0, 0, 30, 30);
painter.end();
QImage image = dst->convertToQImage(0);
image.save("blt_Selection_irregular" + cs->name() + ".png");
QCOMPARE(dst->exactBounds(), QRect(10, 10, 10, 10));
Q_FOREACH (KoChannelInfo * channel, cs->channels()) {
// Only compare alpha if there actually is an alpha channel in
// this colorspace
if (channel->channelType() == KoChannelInfo::ALPHA) {
QColor c;
dst->pixel(13, 13, &c);
QCOMPARE((int) c.alpha(), (int) OPACITY_TRANSPARENT_U8);
}
}
}
void KisPainterTest::testPaintDeviceBltSelectionIrregular()
{
allCsApplicator(&KisPainterTest::testPaintDeviceBltSelectionIrregular);
}
void KisPainterTest::testPaintDeviceBltSelectionInverted(const KoColorSpace * cs)
{
KisPaintDeviceSP dst = new KisPaintDevice(cs);
KisPaintDeviceSP src = new KisPaintDevice(cs);
KisFillPainter gc(src);
gc.fillRect(0, 0, 30, 30, KoColor(Qt::red, cs));
gc.end();
QCOMPARE(src->exactBounds(), QRect(0, 0, 30, 30));
KisSelectionSP sel = new KisSelection();
KisPixelSelectionSP psel = sel->pixelSelection();
psel->select(QRect(10, 10, 20, 20));
psel->invert();
sel->updateProjection();
KisPainter painter(dst);
painter.setSelection(sel);
painter.bitBlt(0, 0, src, 0, 0, 30, 30);
painter.end();
QCOMPARE(dst->exactBounds(), QRect(0, 0, 30, 30));
}
void KisPainterTest::testPaintDeviceBltSelectionInverted()
{
allCsApplicator(&KisPainterTest::testPaintDeviceBltSelectionInverted);
}
void KisPainterTest::testSelectionBltSelection()
{
KisPixelSelectionSP src = new KisPixelSelection();
src->select(QRect(0, 0, 20, 20));
QCOMPARE(src->selectedExactRect(), QRect(0, 0, 20, 20));
KisSelectionSP sel = new KisSelection();
KisPixelSelectionSP Selection = sel->pixelSelection();
Selection->select(QRect(10, 10, 20, 20));
QCOMPARE(Selection->selectedExactRect(), QRect(10, 10, 20, 20));
sel->updateProjection();
KisPixelSelectionSP dst = new KisPixelSelection();
KisPainter painter(dst);
painter.setSelection(sel);
painter.bitBlt(0, 0, src, 0, 0, 30, 30);
painter.end();
QCOMPARE(dst->selectedExactRect(), QRect(10, 10, 10, 10));
KisSequentialConstIterator it(dst, QRect(10, 10, 10, 10));
do {
// These are selections, so only one channel and it should
// be totally selected
QCOMPARE(it.oldRawData()[0], MAX_SELECTED);
} while (it.nextPixel());
}
/*
Test with non-square selection
0,0 0,30
+-----------+------+
| 13,13 | |
| x +--+ |
| +--+##| |
| |#####| |
+-----+-----+ |
| 20,20 |
| |
| |
+------------------+
30,30
*/
void KisPainterTest::testSelectionBltSelectionIrregular()
{
KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
KisPixelSelectionSP src = new KisPixelSelection();
src->select(QRect(0, 0, 20, 20));
QCOMPARE(src->selectedExactRect(), QRect(0, 0, 20, 20));
KisSelectionSP sel = new KisSelection();
KisPixelSelectionSP Selection = sel->pixelSelection();
Selection->select(QRect(10, 15, 20, 15));
Selection->select(QRect(15, 10, 15, 5));
QCOMPARE(Selection->selectedExactRect(), QRect(10, 10, 20, 20));
QCOMPARE(TestUtil::alphaDevicePixel(Selection, 13, 13), MIN_SELECTED);
sel->updateProjection();
KisPixelSelectionSP dst = new KisPixelSelection();
KisPainter painter(dst);
painter.setSelection(sel);
painter.bitBlt(0, 0, src, 0, 0, 30, 30);
painter.end();
QCOMPARE(dst->selectedExactRect(), QRect(10, 10, 10, 10));
QCOMPARE(TestUtil::alphaDevicePixel(dst, 13, 13), MIN_SELECTED);
}
void KisPainterTest::testSelectionBitBltFixedSelection()
{
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dst = new KisPaintDevice(cs);
KisPaintDeviceSP src = new KisPaintDevice(cs);
KoColor c(Qt::red, cs);
c.setOpacity(quint8(128));
src->fill(0, 0, 20, 20, c.data());
QCOMPARE(src->exactBounds(), QRect(0, 0, 20, 20));
KisFixedPaintDeviceSP fixedSelection = new KisFixedPaintDevice(cs);
fixedSelection->setRect(QRect(0, 0, 20, 20));
fixedSelection->initialize();
KoColor fill(Qt::white, cs);
fixedSelection->fill(5, 5, 10, 10, fill.data());
fixedSelection->convertTo(KoColorSpaceRegistry::instance()->alpha8());
KisPainter painter(dst);
painter.bitBltWithFixedSelection(0, 0, src, fixedSelection, 20, 20);
painter.end();
QCOMPARE(dst->exactBounds(), QRect(5, 5, 10, 10));
/*
dbgKrita << "canary1.5";
dst->clear();
painter.begin(dst);
painter.bitBltWithFixedSelection(0, 0, src, fixedSelection, 10, 20);
painter.end();
dbgKrita << "canary2";
QCOMPARE(dst->exactBounds(), QRect(5, 5, 5, 10));
dst->clear();
painter.begin(dst);
painter.bitBltWithFixedSelection(0, 0, src, fixedSelection, 5, 5, 5, 5, 10, 20);
painter.end();
dbgKrita << "canary3";
QCOMPARE(dst->exactBounds(), QRect(5, 5, 5, 10));
dst->clear();
painter.begin(dst);
painter.bitBltWithFixedSelection(5, 5, src, fixedSelection, 10, 20);
painter.end();
dbgKrita << "canary4";
QCOMPARE(dst->exactBounds(), QRect(10, 10, 5, 10));
*/
}
void KisPainterTest::testSelectionBitBltEraseCompositeOp()
{
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintDeviceSP dst = new KisPaintDevice(cs);
KoColor c(Qt::red, cs);
dst->fill(0, 0, 150, 150, c.data());
KisPaintDeviceSP src = new KisPaintDevice(cs);
KoColor c2(Qt::black, cs);
src->fill(50, 50, 50, 50, c2.data());
KisSelectionSP sel = new KisSelection();
KisPixelSelectionSP selection = sel->pixelSelection();
selection->select(QRect(25, 25, 100, 100));
sel->updateProjection();
const KoCompositeOp* op = cs->compositeOp(COMPOSITE_ERASE);
KisPainter painter(dst);
painter.setSelection(sel);
painter.setCompositeOp(op);
painter.bitBlt(0, 0, src, 0, 0, 150, 150);
painter.end();
//dst->convertToQImage(0).save("result.png");
QRect erasedRect(50, 50, 50, 50);
KisSequentialConstIterator it(dst, QRect(0, 0, 150, 150));
do {
if(!erasedRect.contains(it.x(), it.y())) {
QVERIFY(memcmp(it.oldRawData(), c.data(), cs->pixelSize()) == 0);
}
} while (it.nextPixel());
}
void KisPainterTest::testSimpleAlphaCopy()
{
KisPaintDeviceSP src = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
KisPaintDeviceSP dst = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
quint8 p = 128;
src->fill(0, 0, 100, 100, &p);
QVERIFY(src->exactBounds() == QRect(0, 0, 100, 100));
KisPainter gc(dst);
gc.setCompositeOp(KoColorSpaceRegistry::instance()->alpha8()->compositeOp(COMPOSITE_COPY));
gc.bitBlt(QPoint(0, 0), src, src->exactBounds());
gc.end();
QCOMPARE(dst->exactBounds(), QRect(0, 0, 100, 100));
}
void KisPainterTest::checkPerformance()
{
KisPaintDeviceSP src = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
KisPaintDeviceSP dst = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
quint8 p = 128;
src->fill(0, 0, 10000, 5000, &p);
KisSelectionSP sel = new KisSelection();
sel->pixelSelection()->select(QRect(0, 0, 10000, 5000), 128);
sel->updateProjection();
QTime t;
t.start();
for (int i = 0; i < 10; ++i) {
KisPainter gc(dst);
gc.bitBlt(0, 0, src, 0, 0, 10000, 5000);
}
t.restart();
for (int i = 0; i < 10; ++i) {
KisPainter gc(dst, sel);
gc.bitBlt(0, 0, src, 0, 0, 10000, 5000);
}
}
void KisPainterTest::testBitBltOldData()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8();
KisPaintDeviceSP src = new KisPaintDevice(cs);
KisPaintDeviceSP dst = new KisPaintDevice(cs);
quint8 defaultPixel = 0;
quint8 p1 = 128;
quint8 p2 = 129;
quint8 p3 = 130;
KoColor defaultColor(&defaultPixel, cs);
KoColor color1(&p1, cs);
KoColor color2(&p2, cs);
KoColor color3(&p3, cs);
QRect fillRect(0,0,5000,5000);
src->fill(fillRect, color1);
KisPainter srcGc(src);
srcGc.beginTransaction();
src->fill(fillRect, color2);
KisPainter dstGc(dst);
dstGc.bitBltOldData(QPoint(), src, fillRect);
QVERIFY(TestUtil::checkAlphaDeviceFilledWithPixel(dst, fillRect, p1));
dstGc.end();
srcGc.deleteTransaction();
}
void KisPainterTest::benchmarkBitBlt()
{
quint8 p = 128;
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8();
KisPaintDeviceSP src = new KisPaintDevice(cs);
KisPaintDeviceSP dst = new KisPaintDevice(cs);
KoColor color(&p, cs);
QRect fillRect(0,0,5000,5000);
src->fill(fillRect, color);
QBENCHMARK {
KisPainter gc(dst);
gc.bitBlt(QPoint(), src, fillRect);
}
}
void KisPainterTest::benchmarkBitBltOldData()
{
quint8 p = 128;
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8();
KisPaintDeviceSP src = new KisPaintDevice(cs);
KisPaintDeviceSP dst = new KisPaintDevice(cs);
KoColor color(&p, cs);
QRect fillRect(0,0,5000,5000);
src->fill(fillRect, color);
QBENCHMARK {
KisPainter gc(dst);
gc.bitBltOldData(QPoint(), src, fillRect);
}
}
+#include "kis_paint_device_debug_utils.h"
+#include "KisRenderedDab.h"
+
+void testMassiveBltFixedImpl(int numRects, bool varyOpacity = false, bool useSelection = false)
+{
+ const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
+ KisPaintDeviceSP dst = new KisPaintDevice(cs);
+
+ QList<QColor> colors;
+ colors << Qt::red;
+ colors << Qt::green;
+ colors << Qt::blue;
+
+ QRect devicesRect;
+ QList<KisRenderedDab> devices;
+
+ for (int i = 0; i < numRects; i++) {
+ const QRect rc(10 + i * 10, 10 + i * 10, 30, 30);
+ KisFixedPaintDeviceSP dev = new KisFixedPaintDevice(cs);
+ dev->setRect(rc);
+ dev->initialize();
+ dev->fill(rc, KoColor(colors[i % 3], cs));
+ dev->fill(kisGrowRect(rc, -5), KoColor(Qt::white, cs));
+
+ KisRenderedDab dab;
+ dab.device = dev;
+ dab.offset = dev->bounds().topLeft();
+ dab.opacity = varyOpacity ? qreal(1 + i) / numRects : 1.0;
+ dab.flow = 1.0;
+
+ devices << dab;
+ devicesRect |= rc;
+ }
+
+ KisSelectionSP selection;
+
+ if (useSelection) {
+ selection = new KisSelection();
+ selection->pixelSelection()->select(kisGrowRect(devicesRect, -7));
+ }
+
+ const QString opacityPostfix = varyOpacity ? "_varyop" : "";
+ const QString selectionPostfix = useSelection ? "_sel" : "";
+
+ const QRect fullRect = kisGrowRect(devicesRect, 10);
+
+ {
+ KisPainter painter(dst);
+ painter.setSelection(selection);
+ painter.bltFixed(fullRect, devices);
+ painter.end();
+ QVERIFY(TestUtil::checkQImage(dst->convertToQImage(0, fullRect),
+ "kispainter_test",
+ "massive_bitblt",
+ QString("full_update_%1%2%3")
+ .arg(numRects)
+ .arg(opacityPostfix)
+ .arg(selectionPostfix)));
+ }
+
+ dst->clear();
+
+ {
+ KisPainter painter(dst);
+ painter.setSelection(selection);
+
+ for (int i = fullRect.x(); i <= fullRect.center().x(); i += 10) {
+ const QRect rc(i, fullRect.y(), 10, fullRect.height());
+ painter.bltFixed(rc, devices);
+ }
+
+ painter.end();
+
+ QVERIFY(TestUtil::checkQImage(dst->convertToQImage(0, fullRect),
+ "kispainter_test",
+ "massive_bitblt",
+ QString("partial_update_%1%2%3")
+ .arg(numRects)
+ .arg(opacityPostfix)
+ .arg(selectionPostfix)));
+
+ }
+}
+
+void KisPainterTest::testMassiveBltFixedSingleTile()
+{
+ testMassiveBltFixedImpl(3);
+}
+
+void KisPainterTest::testMassiveBltFixedMultiTile()
+{
+ testMassiveBltFixedImpl(6);
+}
+
+void KisPainterTest::testMassiveBltFixedMultiTileWithOpacity()
+{
+ testMassiveBltFixedImpl(6, true);
+}
+
+void KisPainterTest::testMassiveBltFixedMultiTileWithSelection()
+{
+ testMassiveBltFixedImpl(6, false, true);
+}
+
+void KisPainterTest::testMassiveBltFixedCornerCases()
+{
+ const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
+ KisPaintDeviceSP dst = new KisPaintDevice(cs);
+
+ QList<KisRenderedDab> devices;
+
+ QVERIFY(dst->extent().isEmpty());
+
+ {
+ // empty devices, shouldn't crash
+ KisPainter painter(dst);
+ painter.bltFixed(QRect(60,60,20,20), devices);
+ painter.end();
+ }
+
+ QVERIFY(dst->extent().isEmpty());
+
+ const QRect rc(10,10,20,20);
+ KisFixedPaintDeviceSP dev = new KisFixedPaintDevice(cs);
+ dev->setRect(rc);
+ dev->initialize();
+ dev->fill(rc, KoColor(Qt::white, cs));
+
+ devices.append(KisRenderedDab(dev));
+
+ {
+ // rect outside the devices bounds, shouldn't crash
+ KisPainter painter(dst);
+ painter.bltFixed(QRect(60,60,20,20), devices);
+ painter.end();
+ }
+
+ QVERIFY(dst->extent().isEmpty());
+}
+
+
+#include "kis_paintop_utils.h"
+#include "kis_algebra_2d.h"
+
+void benchmarkMassiveBltFixedImpl(int numDabs, int size, qreal spacing, int idealNumPatches, Qt::Orientations direction)
+{
+ const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
+ KisPaintDeviceSP dst = new KisPaintDevice(cs);
+
+ QList<QColor> colors;
+ colors << QColor(255, 0, 0, 200);
+ colors << QColor(0, 255, 0, 200);
+ colors << QColor(0, 0, 255, 200);
+
+ QRect devicesRect;
+ QList<KisRenderedDab> devices;
+
+ const int step = spacing * size;
+
+ for (int i = 0; i < numDabs; i++) {
+ const QRect rc =
+ direction == Qt::Horizontal ? QRect(10 + i * step, 0, size, size) :
+ direction == Qt::Vertical ? QRect(0, 10 + i * step, size, size) :
+ QRect(10 + i * step, 10 + i * step, size, size);
+
+ KisFixedPaintDeviceSP dev = new KisFixedPaintDevice(cs);
+ dev->setRect(rc);
+ dev->initialize();
+ dev->fill(rc, KoColor(colors[i % 3], cs));
+ dev->fill(kisGrowRect(rc, -5), KoColor(Qt::white, cs));
+
+ KisRenderedDab dab;
+ dab.device = dev;
+ dab.offset = dev->bounds().topLeft();
+ dab.opacity = 1.0;
+ dab.flow = 1.0;
+
+ devices << dab;
+ devicesRect |= rc;
+ }
+
+ const QRect fullRect = kisGrowRect(devicesRect, 10);
+
+ {
+ KisPainter painter(dst);
+ painter.bltFixed(fullRect, devices);
+ painter.end();
+ //QVERIFY(TestUtil::checkQImage(dst->convertToQImage(0, fullRect),
+ // "kispainter_test",
+ // "massive_bitblt_benchmark",
+ // "initial"));
+ dst->clear();
+ }
+
+ QElapsedTimer t;
+
+ qint64 massiveTime = 0;
+ int massiveTries = 0;
+ int numRects = 0;
+ int avgPatchSize = 0;
+
+ for (int i = 0; i < 50 || massiveTime > 5000000; i++) {
+ QVector<QRect> rects = KisPaintOpUtils::splitDabsIntoRects(devices, idealNumPatches, size, spacing);
+ numRects = rects.size();
+
+ // HACK: please calculate real *average*!
+ avgPatchSize = KisAlgebra2D::maxDimension(rects.first());
+
+ t.start();
+
+ KisPainter painter(dst);
+ Q_FOREACH (const QRect &rc, rects) {
+ painter.bltFixed(rc, devices);
+ }
+ painter.end();
+
+ massiveTime += t.nsecsElapsed() / 1000;
+ massiveTries++;
+ dst->clear();
+ }
+
+ qint64 linearTime = 0;
+ int linearTries = 0;
+
+ for (int i = 0; i < 50 || linearTime > 5000000; i++) {
+ t.start();
+
+ KisPainter painter(dst);
+ Q_FOREACH (const KisRenderedDab &dab, devices) {
+ painter.setOpacity(255 * dab.opacity);
+ painter.setFlow(255 * dab.flow);
+ painter.bltFixed(dab.offset, dab.device, dab.device->bounds());
+ }
+ painter.end();
+
+ linearTime += t.nsecsElapsed() / 1000;
+ linearTries++;
+ dst->clear();
+ }
+
+ const qreal avgMassive = qreal(massiveTime) / massiveTries;
+ const qreal avgLinear = qreal(linearTime) / linearTries;
+
+ const QString directionMark =
+ direction == Qt::Horizontal ? "H" :
+ direction == Qt::Vertical ? "V" : "D";
+
+ qDebug()
+ << "D:" << size
+ << "S:" << spacing
+ << "N:" << numDabs
+ << "P (px):" << avgPatchSize
+ << "R:" << numRects
+ << "Dir:" << directionMark
+ << "\t"
+ << qPrintable(QString("Massive (usec): %1").arg(QString::number(avgMassive, 'f', 2), 8))
+ << "\t"
+ << qPrintable(QString("Linear (usec): %1").arg(QString::number(avgLinear, 'f', 2), 8))
+ << (avgMassive < avgLinear ? "*" : " ")
+ << qPrintable(QString("%1")
+ .arg(QString::number((avgMassive - avgLinear) / avgLinear * 100.0, 'f', 2), 8))
+ << qRound(size + size * spacing * (numDabs - 1));
+}
+
+
+void KisPainterTest::benchmarkMassiveBltFixed()
+{
+ const qreal sp = 0.14;
+ const int idealThreadCount = 8;
+
+ for (int d = 50; d < 301; d += 50) {
+ for (int n = 1; n < 150; n = qCeil(n * 1.5)) {
+ benchmarkMassiveBltFixedImpl(n, d, sp, idealThreadCount, Qt::Horizontal);
+ benchmarkMassiveBltFixedImpl(n, d, sp, idealThreadCount, Qt::Vertical);
+ benchmarkMassiveBltFixedImpl(n, d, sp, idealThreadCount, Qt::Vertical | Qt::Horizontal);
+ }
+ }
+}
QTEST_MAIN(KisPainterTest)
diff --git a/libs/image/tests/kis_painter_test.h b/libs/image/tests/kis_painter_test.h
index 3bcaf109ad..4aa0763786 100644
--- a/libs/image/tests/kis_painter_test.h
+++ b/libs/image/tests/kis_painter_test.h
@@ -1,60 +1,70 @@
/*
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PAINTER_TEST_H
#define KIS_PAINTER_TEST_H
#include <QtTest>
class KoColorSpace;
class KisPainterTest : public QObject
{
Q_OBJECT
private:
void allCsApplicator(void (KisPainterTest::* funcPtr)(const KoColorSpace*cs));
void testSimpleBlt(const KoColorSpace * cs);
void testPaintDeviceBltSelection(const KoColorSpace * cs);
void testPaintDeviceBltSelectionIrregular(const KoColorSpace * cs);
void testPaintDeviceBltSelectionInverted(const KoColorSpace * cs);
void checkPerformance();
private Q_SLOTS:
void testSimpleBlt();
void testSelectionBltSelectionIrregular(); // Irregular selection
void testPaintDeviceBltSelectionInverted(); // Inverted selection
void testPaintDeviceBltSelectionIrregular(); // Irregular selection
void testPaintDeviceBltSelection(); // Square selection
void testSelectionBltSelection(); // Square selection
void testSimpleAlphaCopy();
void testSelectionBitBltFixedSelection();
void testSelectionBitBltEraseCompositeOp();
void testBitBltOldData();
void benchmarkBitBlt();
void benchmarkBitBltOldData();
+ void testMassiveBltFixedSingleTile();
+ void testMassiveBltFixedMultiTile();
+
+ void testMassiveBltFixedMultiTileWithOpacity();
+
+ void testMassiveBltFixedMultiTileWithSelection();
+
+ void testMassiveBltFixedCornerCases();
+
+ void benchmarkMassiveBltFixed();
};
#endif
diff --git a/libs/image/tests/kis_strokes_queue_test.cpp b/libs/image/tests/kis_strokes_queue_test.cpp
index 8960b80206..59c91e13ee 100644
--- a/libs/image/tests/kis_strokes_queue_test.cpp
+++ b/libs/image/tests/kis_strokes_queue_test.cpp
@@ -1,647 +1,838 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_strokes_queue_test.h"
#include <QTest>
#include "scheduler_utils.h"
#include "kis_strokes_queue.h"
#include "kis_updater_context.h"
#include "kis_update_job_item.h"
#include "kis_merge_walker.h"
void KisStrokesQueueTest::testSequentialJobs()
{
KisStrokesQueue queue;
KisStrokeId id = queue.startStroke(new KisTestingStrokeStrategy("tri_", false));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.endStroke(id);
KisTestableUpdaterContext context(2);
QVector<KisUpdateJobItem*> jobs;
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "tri_init");
VERIFY_EMPTY(jobs[1]);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "tri_dab");
COMPARE_NAME(jobs[1], "tri_dab");
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "tri_dab");
VERIFY_EMPTY(jobs[1]);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "tri_finish");
VERIFY_EMPTY(jobs[1]);
}
void KisStrokesQueueTest::testConcurrentSequentialBarrier()
{
KisStrokesQueue queue;
KisStrokeId id = queue.startStroke(new KisTestingStrokeStrategy("tri_", false));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.endStroke(id);
// make the number of threads higher
KisTestableUpdaterContext context(3);
QVector<KisUpdateJobItem*> jobs;
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "tri_init");
VERIFY_EMPTY(jobs[1]);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "tri_dab");
COMPARE_NAME(jobs[1], "tri_dab");
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "tri_finish");
VERIFY_EMPTY(jobs[1]);
}
void KisStrokesQueueTest::testExclusiveStrokes()
{
KisStrokesQueue queue;
KisStrokeId id = queue.startStroke(new KisTestingStrokeStrategy("excl_", true));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.endStroke(id);
// well, this walker is not initialized... but who cares?
KisBaseRectsWalkerSP walker = new KisMergeWalker(QRect());
KisTestableUpdaterContext context(2);
QVector<KisUpdateJobItem*> jobs;
context.addMergeJob(walker);
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_WALKER(jobs[0], walker);
VERIFY_EMPTY(jobs[1]);
QCOMPARE(queue.needsExclusiveAccess(), true);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "excl_init");
VERIFY_EMPTY(jobs[1]);
QCOMPARE(queue.needsExclusiveAccess(), true);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "excl_dab");
COMPARE_NAME(jobs[1], "excl_dab");
QCOMPARE(queue.needsExclusiveAccess(), true);
context.clear();
context.addMergeJob(walker);
queue.processQueue(context, false);
COMPARE_WALKER(jobs[0], walker);
VERIFY_EMPTY(jobs[1]);
QCOMPARE(queue.needsExclusiveAccess(), true);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "excl_dab");
VERIFY_EMPTY(jobs[1]);
QCOMPARE(queue.needsExclusiveAccess(), true);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "excl_finish");
VERIFY_EMPTY(jobs[1]);
QCOMPARE(queue.needsExclusiveAccess(), true);
context.clear();
queue.processQueue(context, false);
QCOMPARE(queue.needsExclusiveAccess(), false);
}
void KisStrokesQueueTest::testBarrierStrokeJobs()
{
KisStrokesQueue queue;
KisStrokeId id = queue.startStroke(new KisTestingStrokeStrategy("nor_", false));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::BARRIER));
queue.addJob(id, new KisStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.endStroke(id);
// yes, this walker is not initialized again... but who cares?
KisBaseRectsWalkerSP walker = new KisMergeWalker(QRect());
bool externalJobsPending = false;
KisTestableUpdaterContext context(3);
QVector<KisUpdateJobItem*> jobs;
queue.processQueue(context, externalJobsPending);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "nor_init");
VERIFY_EMPTY(jobs[1]);
VERIFY_EMPTY(jobs[2]);
context.clear();
queue.processQueue(context, externalJobsPending);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "nor_dab");
VERIFY_EMPTY(jobs[1]);
VERIFY_EMPTY(jobs[2]);
// Now some updates has come...
context.addMergeJob(walker);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "nor_dab");
COMPARE_WALKER(jobs[1], walker);
VERIFY_EMPTY(jobs[2]);
// No difference for the queue
queue.processQueue(context, externalJobsPending);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "nor_dab");
COMPARE_WALKER(jobs[1], walker);
VERIFY_EMPTY(jobs[2]);
// Even more updates has come...
externalJobsPending = true;
// Still no difference for the queue
queue.processQueue(context, externalJobsPending);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "nor_dab");
COMPARE_WALKER(jobs[1], walker);
VERIFY_EMPTY(jobs[2]);
// Now clear the context
context.clear();
// And still no difference for the queue
queue.processQueue(context, externalJobsPending);
jobs = context.getJobs();
VERIFY_EMPTY(jobs[0]);
VERIFY_EMPTY(jobs[1]);
VERIFY_EMPTY(jobs[2]);
// Process the last update...
context.addMergeJob(walker);
externalJobsPending = false;
// Yep, the queue is still waiting
queue.processQueue(context, externalJobsPending);
jobs = context.getJobs();
COMPARE_WALKER(jobs[0], walker);
VERIFY_EMPTY(jobs[1]);
VERIFY_EMPTY(jobs[2]);
context.clear();
// Finally, we can do our work. Barrier job is executed alone
queue.processQueue(context, externalJobsPending);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "nor_dab");
VERIFY_EMPTY(jobs[1]);
VERIFY_EMPTY(jobs[2]);
// Barrier job has finished
context.clear();
jobs = context.getJobs();
VERIFY_EMPTY(jobs[0]);
VERIFY_EMPTY(jobs[1]);
VERIFY_EMPTY(jobs[2]);
// fetch the last (concurrent) one
queue.processQueue(context, externalJobsPending);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "nor_dab");
VERIFY_EMPTY(jobs[1]);
VERIFY_EMPTY(jobs[2]);
context.clear();
// finish the stroke
queue.processQueue(context, externalJobsPending);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "nor_finish");
VERIFY_EMPTY(jobs[1]);
VERIFY_EMPTY(jobs[2]);
context.clear();
}
void KisStrokesQueueTest::testStrokesOverlapping()
{
KisStrokesQueue queue;
KisStrokeId id = queue.startStroke(new KisTestingStrokeStrategy("1_", false, true));
queue.addJob(id, 0);
// comment out this line to catch an assert
queue.endStroke(id);
id = queue.startStroke(new KisTestingStrokeStrategy("2_", false, true));
queue.addJob(id, 0);
queue.endStroke(id);
// uncomment this line to catch an assert
// queue.addJob(id, 0);
KisTestableUpdaterContext context(2);
QVector<KisUpdateJobItem*> jobs;
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "1_dab");
VERIFY_EMPTY(jobs[1]);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "2_dab");
VERIFY_EMPTY(jobs[1]);
}
void KisStrokesQueueTest::testImmediateCancel()
{
KisStrokesQueue queue;
KisTestableUpdaterContext context(2);
KisStrokeId id = queue.startStroke(new KisTestingStrokeStrategy("1_", false, false));
queue.cancelStroke(id);
// this should not crash
queue.processQueue(context, false);
}
void KisStrokesQueueTest::testOpenedStrokeCounter()
{
KisStrokesQueue queue;
QVERIFY(!queue.hasOpenedStrokes());
KisStrokeId id0 = queue.startStroke(new KisTestingStrokeStrategy("0"));
QVERIFY(queue.hasOpenedStrokes());
KisStrokeId id1 = queue.startStroke(new KisTestingStrokeStrategy("1"));
QVERIFY(queue.hasOpenedStrokes());
queue.endStroke(id0);
QVERIFY(queue.hasOpenedStrokes());
queue.endStroke(id1);
QVERIFY(!queue.hasOpenedStrokes());
KisTestableUpdaterContext context(2);
queue.processQueue(context, false); context.clear();
queue.processQueue(context, false); context.clear();
queue.processQueue(context, false); context.clear();
queue.processQueue(context, false); context.clear();
}
void KisStrokesQueueTest::testAsyncCancelWhileOpenedStroke()
{
KisStrokesQueue queue;
KisStrokeId id = queue.startStroke(new KisTestingStrokeStrategy("nor_", false));
queue.addJob(id, 0);
queue.addJob(id, 0);
queue.addJob(id, 0);
// no async cancelling until the stroke is ended by the owner
QVERIFY(!queue.tryCancelCurrentStrokeAsync());
queue.endStroke(id);
QVERIFY(queue.tryCancelCurrentStrokeAsync());
bool externalJobsPending = false;
KisTestableUpdaterContext context(3);
QVector<KisUpdateJobItem*> jobs;
queue.processQueue(context, externalJobsPending);
// no? really?
jobs = context.getJobs();
VERIFY_EMPTY(jobs[0]);
VERIFY_EMPTY(jobs[1]);
VERIFY_EMPTY(jobs[2]);
}
struct KisStrokesQueueTest::LodStrokesQueueTester {
LodStrokesQueueTester(bool real = false)
: fakeContext(2),
realContext(2),
context(!real ? fakeContext : realContext)
{
queue.setSuspendUpdatesStrokeStrategyFactory(
[]() {
return KisSuspendResumePair(
new KisTestingStrokeStrategy("susp_u_", false, true, true),
QList<KisStrokeJobData*>());
});
queue.setResumeUpdatesStrokeStrategyFactory(
[]() {
return KisSuspendResumePair(
new KisTestingStrokeStrategy("resu_u_", false, true, true),
QList<KisStrokeJobData*>());
});
queue.setLod0ToNStrokeStrategyFactory(
[](bool forgettable) {
Q_UNUSED(forgettable);
return KisSuspendResumePair(
new KisTestingStrokeStrategy("sync_u_", false, true, true),
QList<KisStrokeJobData*>());
});
}
KisStrokesQueue queue;
KisTestableUpdaterContext fakeContext;
KisUpdaterContext realContext;
KisUpdaterContext &context;
QVector<KisUpdateJobItem*> jobs;
void processQueueNoAdd() {
if (&context != &fakeContext) return;
fakeContext.clear();
jobs = fakeContext.getJobs();
VERIFY_EMPTY(jobs[0]);
VERIFY_EMPTY(jobs[1]);
}
+ void processQueueNoContextClear() {
+ queue.processQueue(context, false);
+
+ if (&context == &realContext) {
+ context.waitForDone();
+ }
+ }
+
void processQueue() {
processQueueNoAdd();
queue.processQueue(context, false);
if (&context == &realContext) {
context.waitForDone();
}
}
+ void checkNothing() {
+ KIS_ASSERT(&context == &fakeContext);
+
+ jobs = fakeContext.getJobs();
+ VERIFY_EMPTY(jobs[0]);
+ VERIFY_EMPTY(jobs[1]);
+ }
+
+ void checkJobs(const QStringList &list) {
+ KIS_ASSERT(&context == &fakeContext);
+
+ jobs = fakeContext.getJobs();
+
+ for (int i = 0; i < 2; i++) {
+ if (list.size() <= i) {
+ VERIFY_EMPTY(jobs[i]);
+ } else {
+ QVERIFY(jobs[i]->isRunning());
+ COMPARE_NAME(jobs[i], list[i]);
+ }
+ }
+
+ QCOMPARE(queue.needsExclusiveAccess(), false);
+ }
+
void checkOnlyJob(const QString &name) {
KIS_ASSERT(&context == &fakeContext);
jobs = fakeContext.getJobs();
COMPARE_NAME(jobs[0], name);
VERIFY_EMPTY(jobs[1]);
QCOMPARE(queue.needsExclusiveAccess(), false);
}
void checkOnlyExecutedJob(const QString &name) {
realContext.waitForDone();
QVERIFY(!globalExecutedDabs.isEmpty());
QCOMPARE(globalExecutedDabs[0], name);
QCOMPARE(globalExecutedDabs.size(), 1);
globalExecutedDabs.clear();
}
+
+ void checkExecutedJobs(const QStringList &list) {
+ realContext.waitForDone();
+
+ QCOMPARE(globalExecutedDabs, list);
+ globalExecutedDabs.clear();
+ }
+
+ void checkNothingExecuted() {
+ realContext.waitForDone();
+ QVERIFY(globalExecutedDabs.isEmpty());
+ }
};
void KisStrokesQueueTest::testStrokesLevelOfDetail()
{
LodStrokesQueueTester t;
KisStrokesQueue &queue = t.queue;
// create a stroke with LOD0 + LOD2
queue.setDesiredLevelOfDetail(2);
+
+ // process sync-lodn-planes stroke
+ t.processQueue();
+ t.checkOnlyJob("sync_u_init");
+
KisStrokeId id2 = queue.startStroke(new KisTestingStrokeStrategy("lod_", false, true));
queue.addJob(id2, new KisTestingStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.endStroke(id2);
// create a update with LOD == 0 (default one)
// well, this walker is not initialized... but who cares?
KisBaseRectsWalkerSP walker = new KisMergeWalker(QRect());
KisTestableUpdaterContext context(2);
QVector<KisUpdateJobItem*> jobs;
context.addMergeJob(walker);
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_WALKER(jobs[0], walker);
VERIFY_EMPTY(jobs[1]);
QCOMPARE(queue.needsExclusiveAccess(), false);
context.clear();
jobs = context.getJobs();
VERIFY_EMPTY(jobs[0]);
VERIFY_EMPTY(jobs[1]);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "clone2_lod_dab");
VERIFY_EMPTY(jobs[1]);
QCOMPARE(queue.needsExclusiveAccess(), false);
// walker of a different LOD must not be allowed
QCOMPARE(context.isJobAllowed(walker), false);
context.clear();
context.addMergeJob(walker);
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_WALKER(jobs[0], walker);
COMPARE_NAME(jobs[1], "susp_u_init");
QCOMPARE(queue.needsExclusiveAccess(), false);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "lod_dab");
VERIFY_EMPTY(jobs[1]);
QCOMPARE(queue.needsExclusiveAccess(), false);
context.clear();
queue.processQueue(context, false);
jobs = context.getJobs();
COMPARE_NAME(jobs[0], "resu_u_init");
VERIFY_EMPTY(jobs[1]);
QCOMPARE(queue.needsExclusiveAccess(), false);
context.clear();
}
#include <kundo2command.h>
#include <kis_post_execution_undo_adapter.h>
struct TestUndoCommand : public KUndo2Command
{
TestUndoCommand(const QString &text) : KUndo2Command(kundo2_noi18n(text)) {}
void undo() override {
ENTER_FUNCTION();
undoCount++;
}
void redo() override {
ENTER_FUNCTION();
redoCount++;
}
int undoCount = 0;
int redoCount = 0;
};
void KisStrokesQueueTest::testLodUndoBase()
{
LodStrokesQueueTester t;
KisStrokesQueue &queue = t.queue;
// create a stroke with LOD0 + LOD2
queue.setDesiredLevelOfDetail(2);
+
+ // process sync-lodn-planes stroke
+ t.processQueue();
+ t.checkOnlyJob("sync_u_init");
+
KisStrokeId id1 = queue.startStroke(new KisTestingStrokeStrategy("str1_", false, true));
queue.addJob(id1, new KisTestingStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.endStroke(id1);
KisStrokeId id2 = queue.startStroke(new KisTestingStrokeStrategy("str2_", false, true));
queue.addJob(id2, new KisTestingStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.endStroke(id2);
t.processQueue();
t.checkOnlyJob("clone2_str1_dab");
QSharedPointer<TestUndoCommand> undoStr1(new TestUndoCommand("str1_undo"));
queue.lodNPostExecutionUndoAdapter()->addCommand(undoStr1);
t.processQueue();
t.checkOnlyJob("clone2_str2_dab");
QSharedPointer<TestUndoCommand> undoStr2(new TestUndoCommand("str2_undo"));
queue.lodNPostExecutionUndoAdapter()->addCommand(undoStr2);
t.processQueue();
t.checkOnlyJob("susp_u_init");
t.processQueue();
t.checkOnlyJob("str1_dab");
t.processQueue();
t.checkOnlyJob("str2_dab");
t.processQueue();
t.checkOnlyJob("resu_u_init");
}
void KisStrokesQueueTest::testLodUndoBase2()
{
LodStrokesQueueTester t(true);
KisStrokesQueue &queue = t.queue;
// create a stroke with LOD0 + LOD2
queue.setDesiredLevelOfDetail(2);
KisStrokeId id1 = queue.startStroke(new KisTestingStrokeStrategy("str1_", false, true, false, true));
queue.addJob(id1, new KisTestingStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.endStroke(id1);
KisStrokeId id2 = queue.startStroke(new KisTestingStrokeStrategy("str2_", false, true, false, true));
queue.addJob(id2, new KisTestingStrokeJobData(KisStrokeJobData::CONCURRENT));
queue.endStroke(id2);
t.processQueue();
t.checkOnlyExecutedJob("sync_u_init");
t.processQueue();
t.checkOnlyExecutedJob("clone2_str1_dab");
QSharedPointer<TestUndoCommand> undoStr1(new TestUndoCommand("str1_undo"));
queue.lodNPostExecutionUndoAdapter()->addCommand(undoStr1);
t.processQueue();
t.checkOnlyExecutedJob("clone2_str2_dab");
QSharedPointer<TestUndoCommand> undoStr2(new TestUndoCommand("str2_undo"));
queue.lodNPostExecutionUndoAdapter()->addCommand(undoStr2);
t.processQueue();
t.checkOnlyExecutedJob("susp_u_init");
queue.tryUndoLastStrokeAsync();
t.processQueue();
while (queue.currentStrokeName() == kundo2_noi18n("str2_undo")) {
//queue.debugPrintStrokes();
t.processQueue();
}
QCOMPARE(undoStr2->undoCount, 1);
t.checkOnlyExecutedJob("str1_dab");
t.processQueue();
t.checkOnlyExecutedJob("str2_cancel");
t.processQueue();
t.checkOnlyExecutedJob("resu_u_init");
}
+void KisStrokesQueueTest::testMutatedJobs()
+{
+ LodStrokesQueueTester t(true);
+ KisStrokesQueue &queue = t.queue;
+
+ KisStrokeId id1 = queue.startStroke(new KisTestingStrokeStrategy("str1_", false, true, false, true));
+
+ queue.addJob(id1,
+ new KisTestingStrokeJobData(
+ KisStrokeJobData::CONCURRENT,
+ KisStrokeJobData::NORMAL,
+ true, "1"));
+
+ queue.addJob(id1,
+ new KisTestingStrokeJobData(
+ KisStrokeJobData::SEQUENTIAL,
+ KisStrokeJobData::NORMAL,
+ false, "2"));
+
+ queue.endStroke(id1);
+
+ t.processQueue();
+
+ t.checkOnlyExecutedJob("str1_dab_1");
+
+ t.processQueue();
+
+ QStringList refList;
+ refList << "str1_dab_mutated" << "str1_dab_mutated";
+ t.checkExecutedJobs(refList);
+
+ t.processQueue();
+ t.checkOnlyExecutedJob("str1_dab_mutated");
+
+ t.processQueue();
+ t.checkOnlyExecutedJob("str1_dab_2");
+
+ t.processQueue();
+ t.checkNothingExecuted();
+}
+
+QString sequentialityToString(KisStrokeJobData::Sequentiality seq) {
+ QString result = "<unknown>";
+
+ switch (seq) {
+ case KisStrokeJobData::SEQUENTIAL:
+ result = "SEQUENTIAL";
+ break;
+ case KisStrokeJobData::UNIQUELY_CONCURRENT:
+ result = "UNIQUELY_CONCURRENT";
+ break;
+ case KisStrokeJobData::BARRIER:
+ result = "BARRIER";
+ break;
+ case KisStrokeJobData::CONCURRENT:
+ result = "CONCURRENT";
+ break;
+ }
+
+ return result;
+}
+
+void KisStrokesQueueTest::checkJobsOverlapping(LodStrokesQueueTester &t,
+ KisStrokeId id,
+ KisStrokeJobData::Sequentiality first,
+ KisStrokeJobData::Sequentiality second,
+ bool allowed)
+{
+ t.queue.addJob(id, new KisTestingStrokeJobData(first,
+ KisStrokeJobData::NORMAL, false, "first"));
+ t.processQueue();
+ t.checkJobs({"str1_dab_first"});
+
+ t.queue.addJob(id, new KisTestingStrokeJobData(second,
+ KisStrokeJobData::NORMAL, false, "second"));
+
+ qDebug() << QString(" test %1 after %2 allowed: %3 ")
+ .arg(sequentialityToString(second), 24)
+ .arg(sequentialityToString(first), 24)
+ .arg(allowed);
+
+ if (allowed) {
+ t.processQueueNoContextClear();
+ t.checkJobs({"str1_dab_first", "str1_dab_second"});
+ } else {
+ t.processQueueNoContextClear();
+ t.checkJobs({"str1_dab_first"});
+
+ t.processQueue();
+ t.checkJobs({"str1_dab_second"});
+ }
+
+ t.processQueueNoAdd();
+ t.checkNothing();
+}
+
+void KisStrokesQueueTest::testUniquelyConcurrentJobs()
+{
+ LodStrokesQueueTester t;
+ KisStrokesQueue &queue = t.queue;
+
+ KisStrokeId id1 = queue.startStroke(new KisTestingStrokeStrategy("str1_", false, true));
+ queue.addJob(id1, new KisTestingStrokeJobData(KisStrokeJobData::CONCURRENT));
+ queue.addJob(id1, new KisTestingStrokeJobData(KisStrokeJobData::CONCURRENT));
+ queue.endStroke(id1);
+
+ { // manual test
+ t.processQueue();
+ t.checkJobs({"str1_dab", "str1_dab"});
+
+ queue.addJob(id1, new KisTestingStrokeJobData(KisStrokeJobData::CONCURRENT));
+ t.processQueue();
+ t.checkJobs({"str1_dab"});
+
+ queue.addJob(id1, new KisTestingStrokeJobData(KisStrokeJobData::UNIQUELY_CONCURRENT,
+ KisStrokeJobData::NORMAL, false, "ucon"));
+ t.processQueueNoContextClear();
+ t.checkJobs({"str1_dab", "str1_dab_ucon"});
+
+ t.processQueueNoAdd();
+ t.checkNothing();
+ }
+
+ // Test various cases of overlapping
+
+ checkJobsOverlapping(t, id1, KisStrokeJobData::UNIQUELY_CONCURRENT, KisStrokeJobData::CONCURRENT, true);
+ checkJobsOverlapping(t, id1, KisStrokeJobData::UNIQUELY_CONCURRENT, KisStrokeJobData::UNIQUELY_CONCURRENT, false);
+ checkJobsOverlapping(t, id1, KisStrokeJobData::UNIQUELY_CONCURRENT, KisStrokeJobData::SEQUENTIAL, false);
+ checkJobsOverlapping(t, id1, KisStrokeJobData::UNIQUELY_CONCURRENT, KisStrokeJobData::BARRIER, false);
+
+ checkJobsOverlapping(t, id1, KisStrokeJobData::CONCURRENT, KisStrokeJobData::UNIQUELY_CONCURRENT , true);
+ checkJobsOverlapping(t, id1, KisStrokeJobData::UNIQUELY_CONCURRENT, KisStrokeJobData::UNIQUELY_CONCURRENT, false);
+ checkJobsOverlapping(t, id1, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::UNIQUELY_CONCURRENT, false);
+ checkJobsOverlapping(t, id1, KisStrokeJobData::BARRIER, KisStrokeJobData::UNIQUELY_CONCURRENT, false);
+}
+
QTEST_MAIN(KisStrokesQueueTest)
diff --git a/libs/image/tests/kis_strokes_queue_test.h b/libs/image/tests/kis_strokes_queue_test.h
index 3fdefdf84c..b412132025 100644
--- a/libs/image/tests/kis_strokes_queue_test.h
+++ b/libs/image/tests/kis_strokes_queue_test.h
@@ -1,46 +1,50 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_STROKES_QUEUE_TEST_H
#define __KIS_STROKES_QUEUE_TEST_H
#include <QtTest>
-
+#include "kis_types.h"
+#include "kis_stroke_job_strategy.h"
class KisStrokesQueueTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testSequentialJobs();
void testConcurrentSequentialBarrier();
void testExclusiveStrokes();
void testBarrierStrokeJobs();
void testStrokesOverlapping();
void testImmediateCancel();
void testOpenedStrokeCounter();
void testAsyncCancelWhileOpenedStroke();
void testStrokesLevelOfDetail();
void testLodUndoBase();
void testLodUndoBase2();
+ void testMutatedJobs();
+ void testUniquelyConcurrentJobs();
private:
struct LodStrokesQueueTester;
+ static void checkJobsOverlapping(LodStrokesQueueTester &t, KisStrokeId id, KisStrokeJobData::Sequentiality first, KisStrokeJobData::Sequentiality second, bool allowed);
};
#endif /* __KIS_STROKES_QUEUE_TEST_H */
diff --git a/libs/image/tests/scheduler_utils.h b/libs/image/tests/scheduler_utils.h
index 13ff5be0e3..6b5d683969 100644
--- a/libs/image/tests/scheduler_utils.h
+++ b/libs/image/tests/scheduler_utils.h
@@ -1,231 +1,281 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __SCHEDULER_UTILS_H
#define __SCHEDULER_UTILS_H
#include <QRect>
#include "kis_merge_walker.h"
#include "kis_stroke_strategy.h"
#include "kis_stroke_job.h"
#include "kis_spontaneous_job.h"
#include "kis_stroke.h"
#include "kis_image.h"
#define SCOMPARE(s1, s2) QCOMPARE(QString(s1), QString(s2))
#define COMPARE_WALKER(item, walker) \
QCOMPARE(item->walker(), walker)
#define COMPARE_NAME(item, name) \
QCOMPARE(getJobName(item->strokeJob()), QString(name))
#define VERIFY_EMPTY(item) \
QVERIFY(!item->isRunning())
void executeStrokeJobs(KisStroke *stroke) {
KisStrokeJob *job;
while((job = stroke->popOneJob())) {
job->run();
delete job;
}
}
bool checkWalker(KisBaseRectsWalkerSP walker, const QRect &rect, int lod = 0) {
if(walker->requestedRect() == rect && walker->levelOfDetail() == lod) {
return true;
}
else {
dbgKrita << "walker rect:" << walker->requestedRect();
dbgKrita << "expected rect:" << rect;
dbgKrita << "walker lod:" << walker->levelOfDetail();
dbgKrita << "expected lod:" << lod;
return false;
}
}
class KisNoopSpontaneousJob : public KisSpontaneousJob
{
public:
KisNoopSpontaneousJob(bool overridesEverything = false, int lod = 0)
: m_overridesEverything(overridesEverything),
m_lod(lod)
{
}
void run() override {
}
bool overrides(const KisSpontaneousJob *otherJob) override {
Q_UNUSED(otherJob);
return m_overridesEverything;
}
int levelOfDetail() const override {
return m_lod;
}
private:
bool m_overridesEverything;
int m_lod;
};
static QStringList globalExecutedDabs;
class KisNoopDabStrategy : public KisStrokeJobStrategy
{
public:
KisNoopDabStrategy(QString name)
: m_name(name),
m_isMarked(false)
{}
void run(KisStrokeJobData *data) override {
Q_UNUSED(data);
globalExecutedDabs << m_name;
}
- QString name() {
+ virtual QString name(KisStrokeJobData *data) const {
+ Q_UNUSED(data);
return m_name;
}
void setMarked() {
m_isMarked = true;
}
bool isMarked() const {
return m_isMarked;
}
private:
QString m_name;
bool m_isMarked;
};
class KisTestingStrokeJobData : public KisStrokeJobData
{
public:
KisTestingStrokeJobData(Sequentiality sequentiality = SEQUENTIAL,
- Exclusivity exclusivity = NORMAL)
- : KisStrokeJobData(sequentiality, exclusivity)
+ Exclusivity exclusivity = NORMAL,
+ bool addMutatedJobs = false,
+ const QString &customSuffix = QString())
+ : KisStrokeJobData(sequentiality, exclusivity),
+ m_addMutatedJobs(addMutatedJobs),
+ m_customSuffix(customSuffix)
{
}
KisTestingStrokeJobData(const KisTestingStrokeJobData &rhs)
- : KisStrokeJobData(rhs)
+ : KisStrokeJobData(rhs),
+ m_addMutatedJobs(rhs.m_addMutatedJobs)
{
}
KisStrokeJobData* createLodClone(int levelOfDetail) override {
Q_UNUSED(levelOfDetail);
return new KisTestingStrokeJobData(*this);
}
+
+ bool m_addMutatedJobs = false;
+ bool m_isMutated = false;
+ QString m_customSuffix;
+};
+
+class KisMutatableDabStrategy : public KisNoopDabStrategy
+{
+public:
+ KisMutatableDabStrategy(const QString &name, KisStrokeStrategy *parentStrokeStrategy)
+ : KisNoopDabStrategy(name),
+ m_parentStrokeStrategy(parentStrokeStrategy)
+ {
+ }
+
+ void run(KisStrokeJobData *data) override {
+ KisTestingStrokeJobData *td = dynamic_cast<KisTestingStrokeJobData*>(data);
+
+ if (td && td->m_isMutated) {
+ globalExecutedDabs << QString("%1_mutated").arg(name(data));
+ } else if (td && td->m_addMutatedJobs) {
+ globalExecutedDabs << name(data);
+
+ for (int i = 0; i < 3; i++) {
+ KisTestingStrokeJobData *newData =
+ new KisTestingStrokeJobData(td->sequentiality(), td->exclusivity(), false);
+ newData->m_isMutated = true;
+ m_parentStrokeStrategy->addMutatedJob(newData);
+ }
+ } else {
+ globalExecutedDabs << name(data);
+ }
+ }
+
+ virtual QString name(KisStrokeJobData *data) const {
+ const QString baseName = KisNoopDabStrategy::name(data);
+
+ KisTestingStrokeJobData *td = dynamic_cast<KisTestingStrokeJobData*>(data);
+ return !td || td->m_customSuffix.isEmpty() ? baseName : QString("%1_%2").arg(baseName).arg(td->m_customSuffix);
+ }
+
+private:
+ KisStrokeStrategy *m_parentStrokeStrategy = 0;
};
+
class KisTestingStrokeStrategy : public KisStrokeStrategy
{
public:
KisTestingStrokeStrategy(const QString &prefix = QString(),
bool exclusive = false,
bool inhibitServiceJobs = false,
bool forceAllowInitJob = false,
bool forceAllowCancelJob = false)
: KisStrokeStrategy(prefix, kundo2_noi18n(prefix)),
m_prefix(prefix),
m_inhibitServiceJobs(inhibitServiceJobs),
m_forceAllowInitJob(forceAllowInitJob),
m_forceAllowCancelJob(forceAllowCancelJob),
m_cancelSeqNo(0)
{
setExclusive(exclusive);
}
KisTestingStrokeStrategy(const KisTestingStrokeStrategy &rhs, int levelOfDetail)
: KisStrokeStrategy(rhs),
m_prefix(rhs.m_prefix),
m_inhibitServiceJobs(rhs.m_inhibitServiceJobs),
m_forceAllowInitJob(rhs.m_forceAllowInitJob),
m_cancelSeqNo(rhs.m_cancelSeqNo)
{
m_prefix = QString("clone%1_%2").arg(levelOfDetail).arg(m_prefix);
}
KisStrokeJobStrategy* createInitStrategy() override {
return m_forceAllowInitJob || !m_inhibitServiceJobs ?
new KisNoopDabStrategy(m_prefix + "init") : 0;
}
KisStrokeJobStrategy* createFinishStrategy() override {
return !m_inhibitServiceJobs ?
new KisNoopDabStrategy(m_prefix + "finish") : 0;
}
KisStrokeJobStrategy* createCancelStrategy() override {
return m_forceAllowCancelJob || !m_inhibitServiceJobs ?
new KisNoopDabStrategy(m_prefix + "cancel") : 0;
}
KisStrokeJobStrategy* createDabStrategy() override {
- return new KisNoopDabStrategy(m_prefix + "dab");
+ return new KisMutatableDabStrategy(m_prefix + "dab", this);
}
KisStrokeStrategy* createLodClone(int levelOfDetail) override {
return new KisTestingStrokeStrategy(*this, levelOfDetail);
}
class CancelData : public KisStrokeJobData
{
public:
CancelData(int seqNo) : m_seqNo(seqNo) {}
int seqNo() const { return m_seqNo; }
private:
int m_seqNo;
};
KisStrokeJobData* createCancelData() override {
return new CancelData(m_cancelSeqNo++);
}
private:
QString m_prefix;
bool m_inhibitServiceJobs;
int m_forceAllowInitJob;
bool m_forceAllowCancelJob;
int m_cancelSeqNo;
};
inline QString getJobName(KisStrokeJob *job) {
KisNoopDabStrategy *pointer =
dynamic_cast<KisNoopDabStrategy*>(job->testingGetDabStrategy());
- Q_ASSERT(pointer);
+ KIS_ASSERT(pointer);
- return pointer->name();
+ return pointer->name(job->testingGetDabData());
}
inline int cancelSeqNo(KisStrokeJob *job) {
KisTestingStrokeStrategy::CancelData *pointer =
dynamic_cast<KisTestingStrokeStrategy::CancelData*>
(job->testingGetDabData());
Q_ASSERT(pointer);
return pointer->seqNo();
}
#endif /* __SCHEDULER_UTILS_H */
diff --git a/libs/image/tiles3/kis_tiled_data_manager.cc b/libs/image/tiles3/kis_tiled_data_manager.cc
index 8bcf684f8d..df29afcb77 100644
--- a/libs/image/tiles3/kis_tiled_data_manager.cc
+++ b/libs/image/tiles3/kis_tiled_data_manager.cc
@@ -1,812 +1,818 @@
/*
* Copyright (c) 2004 C. Boemann <cbo@boemann.dk>
* (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
* (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* 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 <QRect>
#include <QVector>
#include "kis_tile.h"
#include "kis_tiled_data_manager.h"
#include "kis_tile_data_wrapper.h"
#include "kis_tiled_data_manager_p.h"
#include "kis_memento_manager.h"
#include "swap/kis_legacy_tile_compressor.h"
#include "swap/kis_tile_compressor_factory.h"
#include "kis_paint_device_writer.h"
#include "kis_global.h"
/* The data area is divided into tiles each say 64x64 pixels (defined at compiletime)
* The tiles are laid out in a matrix that can have negative indexes.
* The matrix grows automatically if needed (a call for writeacces to a tile
* outside the current extent)
* Even though the matrix has grown it may still not contain tiles at specific positions.
* They are created on demand
*/
KisTiledDataManager::KisTiledDataManager(quint32 pixelSize,
const quint8 *defaultPixel)
{
/* See comment in destructor for details */
m_mementoManager = new KisMementoManager();
m_hashTable = new KisTileHashTable(m_mementoManager);
m_pixelSize = pixelSize;
m_defaultPixel = new quint8[m_pixelSize];
setDefaultPixel(defaultPixel);
m_extentMinX = qint32_MAX;
m_extentMinY = qint32_MAX;
m_extentMaxX = qint32_MIN;
m_extentMaxY = qint32_MIN;
}
KisTiledDataManager::KisTiledDataManager(const KisTiledDataManager &dm)
: KisShared()
{
/* See comment in destructor for details */
/* We do not clone the history of the device, there is no usecase for it */
m_mementoManager = new KisMementoManager();
m_mementoManager->setDefaultTileData(dm.m_hashTable->defaultTileData());
m_hashTable = new KisTileHashTable(*dm.m_hashTable, m_mementoManager);
m_pixelSize = dm.m_pixelSize;
m_defaultPixel = new quint8[m_pixelSize];
/**
* We won't call setDefaultTileData here, as defaultTileDatas
* has already been made shared in m_hashTable(dm->m_hashTable)
*/
memcpy(m_defaultPixel, dm.m_defaultPixel, m_pixelSize);
m_extentMinX = dm.m_extentMinX;
m_extentMinY = dm.m_extentMinY;
m_extentMaxX = dm.m_extentMaxX;
m_extentMaxY = dm.m_extentMaxY;
}
KisTiledDataManager::~KisTiledDataManager()
{
/**
* Here is an explanation why we use hash table and The Memento Manager
* dynamically allocated We need to destroy them in that very order. The
* reason is that when hash table destroying all her child tiles they all
* cry about it to The Memento Manager using a pointer. So The Memento
* Manager sould be alive during that destruction. We could use shared
* pointers instead, but they create too much overhead.
*/
delete m_hashTable;
delete m_mementoManager;
delete[] m_defaultPixel;
}
void KisTiledDataManager::setDefaultPixel(const quint8 *defaultPixel)
{
QWriteLocker locker(&m_lock);
setDefaultPixelImpl(defaultPixel);
}
void KisTiledDataManager::setDefaultPixelImpl(const quint8 *defaultPixel)
{
KisTileData *td = KisTileDataStore::instance()->createDefaultTileData(pixelSize(), defaultPixel);
m_hashTable->setDefaultTileData(td);
m_mementoManager->setDefaultTileData(td);
memcpy(m_defaultPixel, defaultPixel, pixelSize());
}
bool KisTiledDataManager::write(KisPaintDeviceWriter &store)
{
QReadLocker locker(&m_lock);
bool retval = true;
if(CURRENT_VERSION == LEGACY_VERSION) {
char str[80];
sprintf(str, "%d\n", m_hashTable->numTiles());
retval = store.write(str, strlen(str));
}
else {
retval = writeTilesHeader(store, m_hashTable->numTiles());
}
KisTileHashTableConstIterator iter(m_hashTable);
KisTileSP tile;
KisAbstractTileCompressorSP compressor =
KisTileCompressorFactory::create(CURRENT_VERSION);
while ((tile = iter.tile())) {
retval = compressor->writeTile(tile, store);
if (!retval) {
warnFile << "Failed to write tile";
break;
}
iter.next();
}
return retval;
}
bool KisTiledDataManager::read(QIODevice *stream)
{
if (!stream) return false;
clear();
QWriteLocker locker(&m_lock);
KisMementoSP nothing = m_mementoManager->getMemento();
if (!stream) {
m_mementoManager->commit();
return false;
}
const qint32 maxLineLength = 79; // Legacy magic
QByteArray line = stream->readLine(maxLineLength);
line = line.trimmed();
quint32 numTiles;
qint32 tilesVersion = LEGACY_VERSION;
if (line[0] == 'V') {
QList<QByteArray> lineItems = line.split(' ');
QString keyword = lineItems.takeFirst();
Q_ASSERT(keyword == "VERSION");
tilesVersion = lineItems.takeFirst().toInt();
if(!processTilesHeader(stream, numTiles))
return false;
}
else {
numTiles = line.toUInt();
}
KisAbstractTileCompressorSP compressor =
KisTileCompressorFactory::create(tilesVersion);
bool readSuccess = true;
for (quint32 i = 0; i < numTiles; i++) {
if (!compressor->readTile(stream, this)) {
readSuccess = false;
}
}
m_mementoManager->commit();
return readSuccess;
}
bool KisTiledDataManager::writeTilesHeader(KisPaintDeviceWriter &store, quint32 numTiles)
{
QString buffer;
buffer = QString("VERSION %1\n"
"TILEWIDTH %2\n"
"TILEHEIGHT %3\n"
"PIXELSIZE %4\n"
"DATA %5\n")
.arg(CURRENT_VERSION)
.arg(KisTileData::WIDTH)
.arg(KisTileData::HEIGHT)
.arg(pixelSize())
.arg(numTiles);
return store.write(buffer.toLatin1());
}
#define takeOneLine(stream, maxLine, keyword, value) \
do { \
QByteArray line = stream->readLine(maxLine); \
line = line.trimmed(); \
QList<QByteArray> lineItems = line.split(' '); \
keyword = lineItems.takeFirst(); \
value = lineItems.takeFirst().toInt(); \
} while(0) \
bool KisTiledDataManager::processTilesHeader(QIODevice *stream, quint32 &numTiles)
{
/**
* We assume that there is only one version of this header
* possible. In case we invent something new, it'll be quite easy
* to modify the behavior
*/
const qint32 maxLineLength = 25;
const qint32 totalNumTests = 4;
bool foundDataMark = false;
qint32 testsPassed = 0;
QString keyword;
qint32 value;
while(!foundDataMark && stream->canReadLine()) {
takeOneLine(stream, maxLineLength, keyword, value);
if (keyword == "TILEWIDTH") {
if(value != KisTileData::WIDTH)
goto wrongString;
}
else if (keyword == "TILEHEIGHT") {
if(value != KisTileData::HEIGHT)
goto wrongString;
}
else if (keyword == "PIXELSIZE") {
if((quint32)value != pixelSize())
goto wrongString;
}
else if (keyword == "DATA") {
numTiles = value;
foundDataMark = true;
}
else {
goto wrongString;
}
testsPassed++;
}
if(testsPassed != totalNumTests) {
warnTiles << "Not enough fields of tiles header present"
<< testsPassed << "of" << totalNumTests;
}
return testsPassed == totalNumTests;
wrongString:
warnTiles << "Wrong string in tiles header:" << keyword << value;
return false;
}
void KisTiledDataManager::purge(const QRect& area)
{
QWriteLocker locker(&m_lock);
QList<KisTileSP> tilesToDelete;
{
const qint32 tileDataSize = KisTileData::HEIGHT * KisTileData::WIDTH * pixelSize();
KisTileData *tileData = m_hashTable->defaultTileData();
tileData->blockSwapping();
const quint8 *defaultData = tileData->data();
KisTileHashTableConstIterator iter(m_hashTable);
KisTileSP tile;
while ((tile = iter.tile())) {
if (tile->extent().intersects(area)) {
tile->lockForRead();
if(memcmp(defaultData, tile->data(), tileDataSize) == 0) {
tilesToDelete.push_back(tile);
}
tile->unlock();
}
iter.next();
}
tileData->unblockSwapping();
}
Q_FOREACH (KisTileSP tile, tilesToDelete) {
m_hashTable->deleteTile(tile);
}
recalculateExtent();
}
quint8* KisTiledDataManager::duplicatePixel(qint32 num, const quint8 *pixel)
{
const qint32 pixelSize = this->pixelSize();
/* FIXME: Make a fun filling here */
quint8 *dstBuf = new quint8[num * pixelSize];
quint8 *dstIt = dstBuf;
for (qint32 i = 0; i < num; i++) {
memcpy(dstIt, pixel, pixelSize);
dstIt += pixelSize;
}
return dstBuf;
}
void KisTiledDataManager::clear(QRect clearRect, const quint8 *clearPixel)
{
QWriteLocker locker(&m_lock);
if (clearPixel == 0)
clearPixel = m_defaultPixel;
if (clearRect.isEmpty())
return;
const qint32 pixelSize = this->pixelSize();
bool pixelBytesAreDefault = !memcmp(clearPixel, m_defaultPixel, pixelSize);
bool pixelBytesAreTheSame = true;
for (qint32 i = 0; i < pixelSize; ++i) {
if (clearPixel[i] != clearPixel[0]) {
pixelBytesAreTheSame = false;
break;
}
}
if (pixelBytesAreDefault) {
clearRect &= extentImpl();
}
qint32 firstColumn = xToCol(clearRect.left());
qint32 lastColumn = xToCol(clearRect.right());
qint32 firstRow = yToRow(clearRect.top());
qint32 lastRow = yToRow(clearRect.bottom());
const quint32 rowStride = KisTileData::WIDTH * pixelSize;
// Generate one row
quint8 *clearPixelData = 0;
quint32 maxRunLength = qMin(clearRect.width(), KisTileData::WIDTH);
clearPixelData = duplicatePixel(maxRunLength, clearPixel);
KisTileData *td = 0;
if (!pixelBytesAreDefault &&
clearRect.width() >= KisTileData::WIDTH &&
clearRect.height() >= KisTileData::HEIGHT) {
td = KisTileDataStore::instance()->createDefaultTileData(pixelSize, clearPixel);
td->acquire();
}
bool needsRecalculateExtent = false;
for (qint32 row = firstRow; row <= lastRow; ++row) {
for (qint32 column = firstColumn; column <= lastColumn; ++column) {
QRect tileRect(column*KisTileData::WIDTH, row*KisTileData::HEIGHT,
KisTileData::WIDTH, KisTileData::HEIGHT);
QRect clearTileRect = clearRect & tileRect;
if (clearTileRect == tileRect) {
// Clear whole tile
m_hashTable->deleteTile(column, row);
- needsRecalculateExtent = true;
+
+ if (!needsRecalculateExtent &&
+ (m_extentMinX == tileRect.left() || m_extentMaxX == tileRect.right() ||
+ m_extentMinY == tileRect.top() || m_extentMaxY == tileRect.bottom())) {
+
+ needsRecalculateExtent = true;
+ }
if (!pixelBytesAreDefault) {
KisTileSP clearedTile = KisTileSP(new KisTile(column, row, td, m_mementoManager));
m_hashTable->addTile(clearedTile);
updateExtent(column, row);
}
} else {
const qint32 lineSize = clearTileRect.width() * pixelSize;
qint32 rowsRemaining = clearTileRect.height();
KisTileDataWrapper tw(this,
clearTileRect.left(),
clearTileRect.top(),
KisTileDataWrapper::WRITE);
quint8* tileIt = tw.data();
if (pixelBytesAreTheSame) {
while (rowsRemaining > 0) {
memset(tileIt, *clearPixelData, lineSize);
tileIt += rowStride;
rowsRemaining--;
}
} else {
while (rowsRemaining > 0) {
memcpy(tileIt, clearPixelData, lineSize);
tileIt += rowStride;
rowsRemaining--;
}
}
}
}
}
if (needsRecalculateExtent) {
recalculateExtent();
}
if (td) td->release();
delete[] clearPixelData;
}
void KisTiledDataManager::clear(QRect clearRect, quint8 clearValue)
{
quint8 *buf = new quint8[pixelSize()];
memset(buf, clearValue, pixelSize());
clear(clearRect, buf);
delete[] buf;
}
void KisTiledDataManager::clear(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *clearPixel)
{
clear(QRect(x, y, w, h), clearPixel);
}
void KisTiledDataManager::clear(qint32 x, qint32 y, qint32 w, qint32 h, quint8 clearValue)
{
clear(QRect(x, y, w, h), clearValue);
}
void KisTiledDataManager::clear()
{
QWriteLocker locker(&m_lock);
m_hashTable->clear();
m_extentMinX = qint32_MAX;
m_extentMinY = qint32_MAX;
m_extentMaxX = qint32_MIN;
m_extentMaxY = qint32_MIN;
}
template<bool useOldSrcData>
void KisTiledDataManager::bitBltImpl(KisTiledDataManager *srcDM, const QRect &rect)
{
QWriteLocker locker(&m_lock);
if (rect.isEmpty()) return;
const qint32 pixelSize = this->pixelSize();
const quint32 rowStride = KisTileData::WIDTH * pixelSize;
qint32 firstColumn = xToCol(rect.left());
qint32 lastColumn = xToCol(rect.right());
qint32 firstRow = yToRow(rect.top());
qint32 lastRow = yToRow(rect.bottom());
for (qint32 row = firstRow; row <= lastRow; ++row) {
for (qint32 column = firstColumn; column <= lastColumn; ++column) {
// this is the only variation in the template
KisTileSP srcTile = useOldSrcData ?
srcDM->getOldTile(column, row) :
srcDM->getTile(column, row, false);
QRect tileRect(column*KisTileData::WIDTH, row*KisTileData::HEIGHT,
KisTileData::WIDTH, KisTileData::HEIGHT);
QRect cloneTileRect = rect & tileRect;
if (cloneTileRect == tileRect) {
// Clone whole tile
m_hashTable->deleteTile(column, row);
srcTile->lockForRead();
KisTileData *td = srcTile->tileData();
KisTileSP clonedTile = KisTileSP(new KisTile(column, row, td, m_mementoManager));
srcTile->unlock();
m_hashTable->addTile(clonedTile);
updateExtent(column, row);
} else {
const qint32 lineSize = cloneTileRect.width() * pixelSize;
qint32 rowsRemaining = cloneTileRect.height();
KisTileDataWrapper tw(this,
cloneTileRect.left(),
cloneTileRect.top(),
KisTileDataWrapper::WRITE);
srcTile->lockForRead();
// We suppose that the shift in both tiles is the same
const quint8* srcTileIt = srcTile->data() + tw.offset();
quint8* dstTileIt = tw.data();
while (rowsRemaining > 0) {
memcpy(dstTileIt, srcTileIt, lineSize);
srcTileIt += rowStride;
dstTileIt += rowStride;
rowsRemaining--;
}
srcTile->unlock();
}
}
}
}
template<bool useOldSrcData>
void KisTiledDataManager::bitBltRoughImpl(KisTiledDataManager *srcDM, const QRect &rect)
{
QWriteLocker locker(&m_lock);
if (rect.isEmpty()) return;
qint32 firstColumn = xToCol(rect.left());
qint32 lastColumn = xToCol(rect.right());
qint32 firstRow = yToRow(rect.top());
qint32 lastRow = yToRow(rect.bottom());
for (qint32 row = firstRow; row <= lastRow; ++row) {
for (qint32 column = firstColumn; column <= lastColumn; ++column) {
/**
* We are cloning whole tiles here so let's not be so boring
* to check any borders :)
*/
// this is the only variation in the template
KisTileSP srcTile = useOldSrcData ?
srcDM->getOldTile(column, row) :
srcDM->getTile(column, row, false);
m_hashTable->deleteTile(column, row);
srcTile->lockForRead();
KisTileData *td = srcTile->tileData();
KisTileSP clonedTile = KisTileSP(new KisTile(column, row, td, m_mementoManager));
srcTile->unlock();
m_hashTable->addTile(clonedTile);
updateExtent(column, row);
}
}
}
void KisTiledDataManager::bitBlt(KisTiledDataManager *srcDM, const QRect &rect)
{
bitBltImpl<false>(srcDM, rect);
}
void KisTiledDataManager::bitBltOldData(KisTiledDataManager *srcDM, const QRect &rect)
{
bitBltImpl<true>(srcDM, rect);
}
void KisTiledDataManager::bitBltRough(KisTiledDataManager *srcDM, const QRect &rect)
{
bitBltRoughImpl<false>(srcDM, rect);
}
void KisTiledDataManager::bitBltRoughOldData(KisTiledDataManager *srcDM, const QRect &rect)
{
bitBltRoughImpl<true>(srcDM, rect);
}
void KisTiledDataManager::setExtent(qint32 x, qint32 y, qint32 w, qint32 h)
{
setExtent(QRect(x, y, w, h));
}
void KisTiledDataManager::setExtent(QRect newRect)
{
QRect oldRect = extent();
newRect = newRect.normalized();
// Do nothing if the desired size is bigger than we currently are:
// that is handled by the autoextending automatically
if (newRect.contains(oldRect)) return;
QWriteLocker locker(&m_lock);
KisTileSP tile;
QRect tileRect;
{
KisTileHashTableIterator iter(m_hashTable);
while (!iter.isDone()) {
tile = iter.tile();
tileRect = tile->extent();
if (newRect.contains(tileRect)) {
//do nothing
iter.next();
} else if (newRect.intersects(tileRect)) {
QRect intersection = newRect & tileRect;
intersection.translate(- tileRect.topLeft());
const qint32 pixelSize = this->pixelSize();
tile->lockForWrite();
quint8* data = tile->data();
quint8* ptr;
/* FIXME: make it faster */
for (int y = 0; y < KisTileData::HEIGHT; y++) {
for (int x = 0; x < KisTileData::WIDTH; x++) {
if (!intersection.contains(x, y)) {
ptr = data + pixelSize * (y * KisTileData::WIDTH + x);
memcpy(ptr, m_defaultPixel, pixelSize);
}
}
}
tile->unlock();
iter.next();
} else {
iter.deleteCurrent();
}
}
}
recalculateExtent();
}
void KisTiledDataManager::recalculateExtent()
{
m_extentMinX = qint32_MAX;
m_extentMinY = qint32_MAX;
m_extentMaxX = qint32_MIN;
m_extentMaxY = qint32_MIN;
KisTileHashTableConstIterator iter(m_hashTable);
KisTileSP tile;
while ((tile = iter.tile())) {
updateExtent(tile->col(), tile->row());
iter.next();
}
}
void KisTiledDataManager::updateExtent(qint32 col, qint32 row)
{
const qint32 tileMinX = col * KisTileData::WIDTH;
const qint32 tileMinY = row * KisTileData::HEIGHT;
const qint32 tileMaxX = tileMinX + KisTileData::WIDTH - 1;
const qint32 tileMaxY = tileMinY + KisTileData::HEIGHT - 1;
m_extentMinX = qMin(m_extentMinX, tileMinX);
m_extentMaxX = qMax(m_extentMaxX, tileMaxX);
m_extentMinY = qMin(m_extentMinY, tileMinY);
m_extentMaxY = qMax(m_extentMaxY, tileMaxY);
}
QRect KisTiledDataManager::extentImpl() const
{
qint32 x = m_extentMinX;
qint32 y = m_extentMinY;
qint32 w = (m_extentMaxX >= m_extentMinX) ? m_extentMaxX - m_extentMinX + 1 : 0;
qint32 h = (m_extentMaxY >= m_extentMinY) ? m_extentMaxY - m_extentMinY + 1 : 0;
return QRect(x, y, w, h);
}
void KisTiledDataManager::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const
{
QRect rect = extent();
rect.getRect(&x, &y, &w, &h);
}
QRect KisTiledDataManager::extent() const
{
QReadLocker locker(&m_lock);
return extentImpl();
}
QRegion KisTiledDataManager::region() const
{
QRegion region;
KisTileHashTableConstIterator iter(m_hashTable);
KisTileSP tile;
while ((tile = iter.tile())) {
region += tile->extent();
iter.next();
}
return region;
}
void KisTiledDataManager::setPixel(qint32 x, qint32 y, const quint8 * data)
{
QWriteLocker locker(&m_lock);
KisTileDataWrapper tw(this, x, y, KisTileDataWrapper::WRITE);
memcpy(tw.data(), data, pixelSize());
}
void KisTiledDataManager::writeBytes(const quint8 *data,
qint32 x, qint32 y,
qint32 width, qint32 height,
qint32 dataRowStride)
{
QWriteLocker locker(&m_lock);
// Actial bytes reading/writing is done in private header
writeBytesBody(data, x, y, width, height, dataRowStride);
}
void KisTiledDataManager::readBytes(quint8 *data,
qint32 x, qint32 y,
qint32 width, qint32 height,
qint32 dataRowStride) const
{
QReadLocker locker(&m_lock);
// Actual bytes reading/writing is done in private header
readBytesBody(data, x, y, width, height, dataRowStride);
}
QVector<quint8*>
KisTiledDataManager::readPlanarBytes(QVector<qint32> channelSizes,
qint32 x, qint32 y,
qint32 width, qint32 height) const
{
QReadLocker locker(&m_lock);
// Actial bytes reading/writing is done in private header
return readPlanarBytesBody(channelSizes, x, y, width, height);
}
void KisTiledDataManager::writePlanarBytes(QVector<quint8*> planes,
QVector<qint32> channelSizes,
qint32 x, qint32 y,
qint32 width, qint32 height)
{
QWriteLocker locker(&m_lock);
// Actial bytes reading/writing is done in private header
bool allChannelsPresent = true;
Q_FOREACH (const quint8* plane, planes) {
if (!plane) {
allChannelsPresent = false;
break;
}
}
if (allChannelsPresent) {
writePlanarBytesBody<true>(planes, channelSizes, x, y, width, height);
} else {
writePlanarBytesBody<false>(planes, channelSizes, x, y, width, height);
}
}
qint32 KisTiledDataManager::numContiguousColumns(qint32 x, qint32 minY, qint32 maxY) const
{
qint32 numColumns;
Q_UNUSED(minY);
Q_UNUSED(maxY);
if (x >= 0) {
numColumns = KisTileData::WIDTH - (x % KisTileData::WIDTH);
} else {
numColumns = ((-x - 1) % KisTileData::WIDTH) + 1;
}
return numColumns;
}
qint32 KisTiledDataManager::numContiguousRows(qint32 y, qint32 minX, qint32 maxX) const
{
qint32 numRows;
Q_UNUSED(minX);
Q_UNUSED(maxX);
if (y >= 0) {
numRows = KisTileData::HEIGHT - (y % KisTileData::HEIGHT);
} else {
numRows = ((-y - 1) % KisTileData::HEIGHT) + 1;
}
return numRows;
}
qint32 KisTiledDataManager::rowStride(qint32 x, qint32 y) const
{
Q_UNUSED(x);
Q_UNUSED(y);
return KisTileData::WIDTH * pixelSize();
}
void KisTiledDataManager::releaseInternalPools()
{
KisTileData::releaseInternalPools();
}
diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp
index eebc07d58a..a171608279 100644
--- a/libs/libkis/Document.cpp
+++ b/libs/libkis/Document.cpp
@@ -1,700 +1,700 @@
/*
* Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "Document.h"
#include <QPointer>
#include <QUrl>
#include <QDomDocument>
#include <KoColorSpaceConstants.h>
#include <KoXmlReader.h>
#include <KisDocument.h>
#include <kis_colorspace_convert_visitor.h>
#include <kis_image.h>
#include <KisPart.h>
#include <kis_paint_device.h>
#include <KisMainWindow.h>
#include <kis_node_manager.h>
#include <kis_node_selection_adapter.h>
#include <KisViewManager.h>
#include <kis_file_layer.h>
#include <kis_adjustment_layer.h>
#include <kis_mask.h>
#include <kis_clone_layer.h>
#include <kis_group_layer.h>
#include <kis_filter_mask.h>
#include <kis_transform_mask.h>
#include <kis_transparency_mask.h>
#include <kis_selection_mask.h>
#include <kis_effect_mask.h>
#include <kis_paint_layer.h>
#include <kis_generator_layer.h>
#include <kis_shape_layer.h>
#include <kis_filter_configuration.h>
#include <kis_selection.h>
#include <KisMimeDatabase.h>
#include <kis_filter_strategy.h>
#include <kis_guides_config.h>
#include <kis_coordinates_converter.h>
#include <KoColorSpace.h>
#include <KoColorProfile.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorConversionTransformation.h>
#include <KoDocumentInfo.h>
#include <InfoObject.h>
#include <Node.h>
#include <Selection.h>
struct Document::Private {
Private() {}
QPointer<KisDocument> document;
};
Document::Document(KisDocument *document, QObject *parent)
: QObject(parent)
, d(new Private)
{
d->document = document;
}
Document::~Document()
{
delete d;
}
bool Document::operator==(const Document &other) const
{
return (d->document == other.d->document);
}
bool Document::operator!=(const Document &other) const
{
return !(operator==(other));
}
bool Document::batchmode() const
{
if (!d->document) return false;
return d->document->fileBatchMode();
}
void Document::setBatchmode(bool value)
{
if (!d->document) return;
d->document->setFileBatchMode(value);
}
Node *Document::activeNode() const
{
QList<KisNodeSP> activeNodes;
Q_FOREACH(QPointer<KisView> view, KisPart::instance()->views()) {
if (view && view->document() == d->document) {
activeNodes << view->currentNode();
}
}
if (activeNodes.size() > 0) {
return new Node(d->document->image(), activeNodes.first());
}
return new Node(d->document->image(), d->document->image()->root()->firstChild());
}
void Document::setActiveNode(Node* value)
{
if (!value->node()) return;
KisMainWindow *mainWin = KisPart::instance()->currentMainwindow();
if (!mainWin) return;
KisViewManager *viewManager = mainWin->viewManager();
if (!viewManager) return;
if (viewManager->document() != d->document) return;
KisNodeManager *nodeManager = viewManager->nodeManager();
if (!nodeManager) return;
KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter();
if (!selectionAdapter) return;
selectionAdapter->setActiveNode(value->node());
}
QList<Node *> Document::topLevelNodes() const
{
if (!d->document) return QList<Node *>();
Node n(d->document->image(), d->document->image()->rootLayer());
return n.childNodes();
}
Node *Document::nodeByName(const QString &name) const
{
if (!d->document) return 0;
KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name);
return new Node(d->document->image(), node);
}
QString Document::colorDepth() const
{
if (!d->document) return "";
return d->document->image()->colorSpace()->colorDepthId().id();
}
QString Document::colorModel() const
{
if (!d->document) return "";
return d->document->image()->colorSpace()->colorModelId().id();
}
QString Document::colorProfile() const
{
if (!d->document) return "";
return d->document->image()->colorSpace()->profile()->name();
}
bool Document::setColorProfile(const QString &value)
{
if (!d->document) return false;
if (!d->document->image()) return false;
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value);
if (!profile) return false;
bool retval = d->document->image()->assignImageProfile(profile);
d->document->image()->setModified();
d->document->image()->initialRefreshGraph();
return retval;
}
bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile)
{
if (!d->document) return false;
if (!d->document->image()) return false;
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile);
if (!colorSpace) return false;
d->document->image()->convertImageColorSpace(colorSpace,
KoColorConversionTransformation::IntentPerceptual,
KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization);
d->document->image()->setModified();
d->document->image()->initialRefreshGraph();
return true;
}
QString Document::documentInfo() const
{
QDomDocument doc = KisDocument::createDomDocument("document-info"
/*DTD name*/, "document-info" /*tag name*/, "1.1");
doc = d->document->documentInfo()->save(doc);
return doc.toString();
}
void Document::setDocumentInfo(const QString &document)
{
KoXmlDocument doc;
QString errorMsg;
int errorLine, errorColumn;
doc.setContent(document, &errorMsg, &errorLine, &errorColumn);
d->document->documentInfo()->load(doc);
}
QString Document::fileName() const
{
- if (!d->document) return QString::null;
+ if (!d->document) return QString();
return d->document->url().toLocalFile();
}
void Document::setFileName(QString value)
{
if (!d->document) return;
d->document->setUrl(QUrl::fromLocalFile(value));
}
int Document::height() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->height();
}
void Document::setHeight(int value)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(d->document->image()->bounds().x(),
d->document->image()->bounds().y(),
d->document->image()->width(),
value);
}
QString Document::name() const
{
if (!d->document) return "";
return d->document->documentInfo()->aboutInfo("title");
}
void Document::setName(QString value)
{
if (!d->document) return;
d->document->documentInfo()->setAboutInfo("title", value);
}
int Document::resolution() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return qRound(d->document->image()->xRes() * 72);
}
void Document::setResolution(int value)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
d->document->image()->setResolution(value / 72.0, value / 72.0);
}
Node *Document::rootNode() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return new Node(image, image->root());
}
Selection *Document::selection() const
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
if (!d->document->image()->globalSelection()) return 0;
return new Selection(d->document->image()->globalSelection());
}
void Document::setSelection(Selection* value)
{
if (!d->document) return;
if (!d->document->image()) return;
if (value) {
d->document->image()->setGlobalSelection(value->selection());
}
else {
d->document->image()->setGlobalSelection(0);
}
}
int Document::width() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->width();
}
void Document::setWidth(int value)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(d->document->image()->bounds().x(),
d->document->image()->bounds().y(),
value,
d->document->image()->height());
}
int Document::xOffset() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->bounds().x();
}
void Document::setXOffset(int x)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(x,
d->document->image()->bounds().y(),
d->document->image()->width(),
d->document->image()->height());
}
int Document::yOffset() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->bounds().y();
}
void Document::setYOffset(int y)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(d->document->image()->bounds().x(),
y,
d->document->image()->width(),
d->document->image()->height());
}
double Document::xRes() const
{
if (!d->document) return 0.0;
if (!d->document->image()) return 0.0;
return d->document->image()->xRes();
}
void Document::setXRes(double xRes) const
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->setResolution(xRes, d->document->image()->yRes());
}
double Document::yRes() const
{
if (!d->document) return 0.0;
if (!d->document->image()) return 0.0;
return d->document->image()->yRes();
}
void Document::setYRes(double yRes) const
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->setResolution(d->document->image()->xRes(), yRes);
}
QByteArray Document::pixelData(int x, int y, int w, int h) const
{
QByteArray ba;
if (!d->document) return ba;
KisImageSP image = d->document->image();
if (!image) return ba;
KisPaintDeviceSP dev = image->projection();
ba.resize(w * h * dev->pixelSize());
dev->readBytes(reinterpret_cast<quint8*>(ba.data()), x, y, w, h);
return ba;
}
bool Document::close()
{
bool retval = d->document->closeUrl(false);
Q_FOREACH(KisView *view, KisPart::instance()->views()) {
if (view->document() == d->document) {
view->close();
view->deleteLater();
}
}
KisPart::instance()->removeDocument(d->document);
d->document = 0;
return retval;
}
void Document::crop(int x, int y, int w, int h)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
QRect rc(x, y, w, h);
image->cropImage(rc);
}
bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration)
{
if (!d->document) return false;
const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename);
const QByteArray outputFormat = outputFormatString.toLatin1();
return d->document->exportDocumentSync(QUrl::fromLocalFile(filename), outputFormat, exportConfiguration.configuration());
}
void Document::flatten()
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->flatten();
}
void Document::resizeImage(int x, int y, int w, int h)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
QRect rc;
rc.setX(x);
rc.setY(y);
rc.setWidth(w);
rc.setHeight(h);
image->resizeImage(rc);
}
void Document::scaleImage(int w, int h, int xres, int yres, QString strategy)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
QRect rc = image->bounds();
rc.setWidth(w);
rc.setHeight(h);
KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy);
if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic");
image->scaleImage(rc.size(), xres, yres, actualStrategy);
}
void Document::rotateImage(double radians)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
image->rotateImage(radians);
}
void Document::shearImage(double angleX, double angleY)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
image->shear(angleX, angleY);
}
bool Document::save()
{
if (!d->document) return false;
bool retval = d->document->save(true, 0);
d->document->waitForSavingToComplete();
return retval;
}
bool Document::saveAs(const QString &filename)
{
if (!d->document) return false;
const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename);
const QByteArray outputFormat = outputFormatString.toLatin1();
bool retval = d->document->saveAs(QUrl::fromLocalFile(filename), outputFormat, true);
d->document->waitForSavingToComplete();
return retval;
}
Node* Document::createNode(const QString &name, const QString &nodeType)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
Node *node = 0;
if (nodeType == "paintlayer") {
node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType == "grouplayer") {
node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType == "filelayer") {
node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType == "filterlayer") {
node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0));
}
else if (nodeType == "filllayer") {
node = new Node(image, new KisGeneratorLayer(image, name, 0, 0));
}
else if (nodeType == "clonelayer") {
node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType == "vectorlayer") {
node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType == "transparencymask") {
node = new Node(image, new KisTransparencyMask());
}
else if (nodeType == "filtermask") {
node = new Node(image, new KisFilterMask());
}
else if (nodeType == "transformmask") {
node = new Node(image, new KisTransformMask());
}
else if (nodeType == "selectionmask") {
node = new Node(image, new KisSelectionMask(image));
}
return node;
}
QImage Document::projection(int x, int y, int w, int h) const
{
if (!d->document || !d->document->image()) return QImage();
return d->document->image()->convertToQImage(x, y, w, h, 0);
}
QImage Document::thumbnail(int w, int h) const
{
if (!d->document || !d->document->image()) return QImage();
return d->document->generatePreview(QSize(w, h)).toImage();
}
void Document::lock()
{
if (!d->document || !d->document->image()) return;
d->document->image()->barrierLock();
}
void Document::unlock()
{
if (!d->document || !d->document->image()) return;
d->document->image()->unlock();
}
void Document::waitForDone()
{
if (!d->document || !d->document->image()) return;
d->document->image()->waitForDone();
}
bool Document::tryBarrierLock()
{
if (!d->document || !d->document->image()) return false;
return d->document->image()->tryBarrierLock();
}
bool Document::isIdle()
{
if (!d->document || !d->document->image()) return false;
return d->document->image()->isIdle();
}
void Document::refreshProjection()
{
if (!d->document || !d->document->image()) return;
d->document->image()->refreshGraph();
}
QList<qreal> Document::horizontalGuides() const
{
QList<qreal> lines;
if (!d->document || !d->document->image()) return lines;
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform().inverted();
QList<qreal> untransformedLines = d->document->guidesConfig().horizontalGuideLines();
for (int i = 0; i< untransformedLines.size(); i++) {
qreal line = untransformedLines[i];
lines.append(transform.map(QPointF(line, line)).x());
}
return lines;
}
QList<qreal> Document::verticalGuides() const
{
QList<qreal> lines;
if (!d->document || !d->document->image()) return lines;
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform().inverted();
QList<qreal> untransformedLines = d->document->guidesConfig().verticalGuideLines();
for (int i = 0; i< untransformedLines.size(); i++) {
qreal line = untransformedLines[i];
lines.append(transform.map(QPointF(line, line)).y());
}
return lines;
}
bool Document::guidesVisible() const
{
return d->document->guidesConfig().lockGuides();
}
bool Document::guidesLocked() const
{
return d->document->guidesConfig().showGuides();
}
void Document::setHorizontalGuides(const QList<qreal> &lines)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform();
QList<qreal> transformedLines;
for (int i = 0; i< lines.size(); i++) {
qreal line = lines[i];
transformedLines.append(transform.map(QPointF(line, line)).x());
}
config.setHorizontalGuideLines(transformedLines);
d->document->setGuidesConfig(config);
}
void Document::setVerticalGuides(const QList<qreal> &lines)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform();
QList<qreal> transformedLines;
for (int i = 0; i< lines.size(); i++) {
qreal line = lines[i];
transformedLines.append(transform.map(QPointF(line, line)).y());
}
config.setVerticalGuideLines(transformedLines);
d->document->setGuidesConfig(config);
}
void Document::setGuidesVisible(bool visible)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
config.setShowGuides(visible);
d->document->setGuidesConfig(config);
}
void Document::setGuidesLocked(bool locked)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
config.setLockGuides(locked);
d->document->setGuidesConfig(config);
}
QPointer<KisDocument> Document::document() const
{
return d->document;
}
diff --git a/libs/libqml/DocumentListModel.cpp b/libs/libqml/DocumentListModel.cpp
index 5a1af15ae8..eeef8bc785 100644
--- a/libs/libqml/DocumentListModel.cpp
+++ b/libs/libqml/DocumentListModel.cpp
@@ -1,182 +1,188 @@
/* This file is part of the KDE project
* Copyright (C) 2012 KO GmbH. Contact: Boudewijn Rempt <boud@kogmbh.com>
*
* 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 "DocumentListModel.h"
#include <QTimer>
#include <QLocale>
#include <klocalizedstring.h>
class DocumentListModel::Private
{
public:
Private( DocumentListModel *qq) : q(qq), filter(DocumentListModel::UnknownType) { }
void relayout();
DocumentListModel* q;
QList<DocumentInfo> allDocumentInfos;
QList<DocumentInfo> currentDocumentInfos;
DocumentType filter;
QString searchPattern;
QTimer *timer;
};
QHash<QString, DocumentListModel::DocumentType> DocumentListModel::sm_extensions = QHash<QString, DocumentListModel::DocumentType>();
DocumentListModel::DocumentListModel(QObject *parent)
: QAbstractListModel(parent), d(new Private(this))
{
qRegisterMetaType<DocumentListModel::DocumentInfo>();
+}
+
+DocumentListModel::~DocumentListModel()
+{
+}
+
+QHash<int, QByteArray> DocumentListModel::roleNames() const
+{
QHash<int, QByteArray> roleNames = QAbstractListModel::roleNames();
roleNames[FileNameRole] = "fileName";
roleNames[FilePathRole] = "filePath";
roleNames[DocTypeRole] = "docType";
roleNames[FileSizeRole] = "fileSize";
roleNames[AuthorNameRole] = "authorName";
roleNames[AccessedTimeRole] = "accessedTime";
roleNames[ModifiedTimeRole] = "modifiedTime";
roleNames[UUIDRole] = "uuid";
- setRoleNames(roleNames);
-}
-DocumentListModel::~DocumentListModel()
-{
+ return roleNames;
}
void DocumentListModel::addDocument(const DocumentInfo &info)
{
if (d->allDocumentInfos.contains(info))
{
return;
}
d->allDocumentInfos.append(info);
}
int DocumentListModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return d->currentDocumentInfos.count();
}
int DocumentListModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 1;
}
QVariant DocumentListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
const int row = index.row();
const DocumentInfo &info = d->currentDocumentInfos[row];
switch (role) {
case FileNameRole: // intentional fall through
case Qt::DisplayRole: return info.fileName;
case FilePathRole: return info.filePath;
case DocTypeRole: return info.docType;
case FileSizeRole: return info.fileSize;
case AuthorNameRole: return info.authorName;
case AccessedTimeRole: return prettyTime(info.accessedTime);
case ModifiedTimeRole: return prettyTime(info.modifiedTime);
case UUIDRole: return info.uuid;
default: return QVariant();
}
}
QString DocumentListModel::prettyTime( const QDateTime& theTime)
{
return QLocale().toString(theTime, QLocale::LongFormat);
}
QVariant DocumentListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(section)
Q_UNUSED(orientation)
Q_UNUSED(role)
return QVariant();
}
DocumentListModel::DocumentType DocumentListModel::filter()
{
return d->filter;
}
void DocumentListModel::setFilter( DocumentListModel::DocumentType newFilter)
{
d->filter = newFilter;
d->relayout();
}
DocumentListModel::DocumentType DocumentListModel::typeForFile ( const QString& file )
{
if (sm_extensions.isEmpty()) {
sm_extensions["odt"] = TextDocumentType;
sm_extensions["fodt"] = TextDocumentType;
sm_extensions["doc"] = TextDocumentType;
sm_extensions["docx"] = TextDocumentType;
sm_extensions["txt"] = TextDocumentType;
sm_extensions["odp"] = PresentationType;
sm_extensions["fodp"] = PresentationType;
sm_extensions["ppt"] = PresentationType;
sm_extensions["pptx"] = PresentationType;
sm_extensions["ods"] = SpreadsheetType;
sm_extensions["fods"] = SpreadsheetType;
sm_extensions["xls"] = SpreadsheetType;
sm_extensions["xlsx"] = SpreadsheetType;
sm_extensions["pdf"] = PDFDocumentType;
}
QString ext = file.split('.').last().toLower();
if (sm_extensions.contains(ext)) {
return sm_extensions.value(ext);
}
return UnknownType;
}
void DocumentListModel::Private::relayout()
{
emit q->layoutAboutToBeChanged();
QList<DocumentInfo> newList;
Q_FOREACH (const DocumentInfo &docInfo, allDocumentInfos) {
if (filter == UnknownType || docInfo.docType == filter) {
if (searchPattern.isEmpty() || docInfo.fileName.contains(searchPattern, Qt::CaseInsensitive)) {
newList.append(docInfo);
}
}
}
currentDocumentInfos = newList;
emit q->layoutChanged();
- q->reset(); // ## Required for <= Qt 4.7.2
+ q->beginResetModel();
+ q->endResetModel();
}
diff --git a/libs/libqml/DocumentListModel.h b/libs/libqml/DocumentListModel.h
index b9c448868f..afb34bfabf 100644
--- a/libs/libqml/DocumentListModel.h
+++ b/libs/libqml/DocumentListModel.h
@@ -1,100 +1,102 @@
/* This file is part of the KDE project
* Copyright (C) 2012 KO GmbH. Contact: Boudewijn Rempt <boud@kogmbh.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KRITA_SKETCH_DOCUMENTLISTMODEL_H
#define KRITA_SKETCH_DOCUMENTLISTMODEL_H
#include <QAbstractListModel>
#include <QDateTime>
#include <QColor>
#include <QUrl>
#include <QFont>
#include "krita_sketch_export.h"
class KRITA_SKETCH_EXPORT DocumentListModel : public QAbstractListModel
{
Q_OBJECT
Q_ENUMS(DocumentType)
Q_PROPERTY(DocumentType filter READ filter WRITE setFilter)
public:
DocumentListModel(QObject *parent = 0);
~DocumentListModel();
+ QHash<int, QByteArray> roleNames() const;
+
enum CustomRoles {
FileNameRole = Qt::UserRole + 1,
FilePathRole,
DocTypeRole,
FileSizeRole,
AuthorNameRole,
AccessedTimeRole,
ModifiedTimeRole,
UUIDRole,
};
enum DocumentType {
UnknownType,
TextDocumentType,
PresentationType,
SpreadsheetType,
PDFDocumentType,
};
struct DocumentInfo {
bool operator==(const DocumentInfo &other) const {
return filePath == other.filePath;
}
QString filePath;
QString fileName;
DocumentType docType;
QString fileSize;
QString authorName;
QDateTime accessedTime;
QDateTime modifiedTime;
QString uuid;
};
// reimp from QAbstractListModel
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
DocumentType filter();
static QString prettyTime(const QDateTime &theTime);
static DocumentType typeForFile(const QString &file);
public Q_SLOTS:
void addDocument(const DocumentListModel::DocumentInfo &info);
void setFilter(DocumentType newFilter);
private:
class Private;
const QScopedPointer<Private> d;
static QHash<QString, DocumentType> sm_extensions;
};
Q_DECLARE_METATYPE(DocumentListModel::DocumentInfo);
#endif // KRITA_SKETCH_DOCUMENTLISTMODEL_H
diff --git a/libs/libqml/plugins/components/Button.qml b/libs/libqml/plugins/components/Button.qml
index adb3c2d8c6..9663fb94c4 100644
--- a/libs/libqml/plugins/components/Button.qml
+++ b/libs/libqml/plugins/components/Button.qml
@@ -1,184 +1,188 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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.
*/
import QtQuick 2.3
import org.krita.sketch 1.0
Item {
id: base;
signal clicked();
property alias image: icon.source;
property color color: Settings.theme.color("components/button/base");
property alias border: fill.border;
property alias radius: fill.radius;
property alias text: label.text;
property color textColor: Settings.theme.color("components/button/text");
property alias textSize: label.font.pixelSize;
property alias bold: label.font.bold;
property bool shadow: false;
property bool enabled: true; // XXX: visualize disabledness
property alias asynchronous: icon.asynchronous;
property bool highlight: false;
property color highlightColor: Settings.theme.color("components/button/highlight");
property bool checkable: false;
property bool checked: false;
property color checkedColor: Settings.theme.color("components/button/checked");
property bool hasFocus: false;
property string tooltip: "";
width: Constants.GridWidth;
height: Constants.GridHeight;
Rectangle {
id: fill;
anchors.fill: parent;
anchors.margins: 0;
color: base.highlight && mouse.pressed && base.enabled ? base.highlightColor : base.color;
visible: true
Rectangle {
anchors {
left: parent.left;
right: parent.right;
bottom: parent.bottom;
margins: fill.radius / 2;
}
height: fill.radius / 2;
radius: fill.radius / 4;
color: base.textColor;
visible: base.hasFocus;
opacity: 0.3
}
Rectangle {
id: checkedVisualiser;
opacity: base.checked ? 1 : 0;
Behavior on opacity { NumberAnimation { duration: Constants.AnimationDuration; } }
anchors.fill: parent;
anchors.margins: 2;
color: base.checkedColor;
radius: base.height === base.width ? base.height / 2 - 1 : base.radius;
}
Image {
id: icon;
anchors.fill: parent;
anchors.margins: 8;
fillMode: Image.PreserveAspectFit;
smooth: true;
asynchronous: true;
opacity: base.enabled ? 1 : 0.7;
Behavior on opacity { NumberAnimation { duration: Constants.AnimationDuration; } }
sourceSize.width: width > height ? height : width;
sourceSize.height: width > height ? height : width;
}
Label {
id: label;
anchors.verticalCenter: parent.verticalCenter;
height: font.pixelSize;
width: parent.width;
horizontalAlignment: Text.AlignHCenter;
elide: Text.ElideRight;
opacity: base.enabled ? 1 : 0.7;
color: base.textColor;
}
// Rectangle {
// id: enabledVisualiser;
// opacity: base.enabled ? 0 : 0.7;
// anchors.fill: parent;
// color: "black";
// }
}
SimpleTouchArea {
anchors.fill: parent;
onTouched: {
if (base.enabled) {
base.clicked();
if ( base.checkable ) {
base.checked = !base.checked;
}
}
}
}
MouseArea {
id: mouse;
anchors.fill: parent;
hoverEnabled: true;
acceptedButtons: Qt.LeftButton | Qt.RightButton;
-
+
onClicked: {
if(mouse.button == Qt.LeftButton && base.enabled) {
base.clicked();
if ( base.checkable ) {
base.checked = !base.checked;
}
} else if(mouse.button == Qt.RightButton && base.tooltip != "") {
tooltip.show(base.width / 2, 0);
}
}
onEntered: {
hoverDelayTimer.start();
}
onPositionChanged: {
if(hoverDelayTimer.running) {
hoverDelayTimer.restart();
}
}
onExited: {
hoverDelayTimer.stop();
tooltip.hide();
}
}
Timer {
id: hoverDelayTimer;
interval: 1000;
onTriggered: { if(base.tooltip != "") tooltip.show(base.width / 2, 0) }
}
Tooltip {
id: tooltip;
text: base.tooltip;
}
states: State {
name: "pressed";
when: (mouse.pressed || base.checked) && enabled;
PropertyChanges {
- target: mouse
+ target: fill
+ color: base.highlightColor
anchors.topMargin: 0
}
}
transitions: Transition {
+ from: "";
+ to: "down";
+ reversible: true;
ParallelAnimation {
- NumberAnimation { properties: "size"; duration: 50; }
- ColorAnimation { duration: 50; }
+ NumberAnimation { properties: "size"; duration: 150; }
+ ColorAnimation { duration: 150; }
}
}
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/ColorDepthModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/ColorDepthModel.cpp
index e322344ecf..d9222bc5d7 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/ColorDepthModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/ColorDepthModel.cpp
@@ -1,96 +1,100 @@
/*
* This file is part of the KDE project
* Copyright (C) 2014 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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 "ColorDepthModel.h"
#include <libs/pigment/KoColorSpaceRegistry.h>
class ColorDepthModel::Private
{
public:
Private() { }
QString colorModelId;
QList<KoID> colorDepths;
};
ColorDepthModel::ColorDepthModel(QObject* parent)
: QAbstractListModel(parent), d(new Private)
{
- QHash<int, QByteArray> roleNames;
- roleNames.insert(TextRole, "text");
- setRoleNames(roleNames);
}
ColorDepthModel::~ColorDepthModel()
{
delete d;
}
+QHash<int, QByteArray> ColorDepthModel::roleNames() const
+{
+ QHash<int, QByteArray> roleNames;
+ roleNames.insert(TextRole, "text");
+ return roleNames;
+}
+
int ColorDepthModel::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return d->colorDepths.count();
}
QVariant ColorDepthModel::data(const QModelIndex& index, int role) const
{
if(!index.isValid() || index.row() < 0 || index.row() >= d->colorDepths.count())
return QVariant();
if(role == TextRole) {
return d->colorDepths.at(index.row()).name();
}
return QVariant();
}
QString ColorDepthModel::colorModelId() const
{
return d->colorModelId;
}
void ColorDepthModel::setColorModelId(const QString& id)
{
if(id != d->colorModelId) {
d->colorModelId = id;
if(d->colorDepths.count() > 0) {
beginRemoveRows(QModelIndex(), 0, d->colorDepths.count() - 1);
endRemoveRows();
}
d->colorDepths = KoColorSpaceRegistry::instance()->colorDepthList(d->colorModelId, KoColorSpaceRegistry::OnlyUserVisible);
if(d->colorDepths.count() > 0) {
beginInsertRows(QModelIndex(), 0, d->colorDepths.count() - 1);
endInsertRows();
}
emit colorModelIdChanged();
}
}
QString ColorDepthModel::id(int index)
{
if(index < 0 || index >= d->colorDepths.count())
return QString();
return d->colorDepths.at(index).id();
}
int ColorDepthModel::indexOf(const QString& id)
{
return d->colorDepths.indexOf(KoID(id));
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/ColorDepthModel.h b/libs/libqml/plugins/kritasketchplugin/models/ColorDepthModel.h
index 1397990d87..687d105dbd 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/ColorDepthModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/ColorDepthModel.h
@@ -1,57 +1,57 @@
/*
* This file is part of the KDE project
* Copyright (C) 2014 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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 COLORDEPTHMODEL_H
#define COLORDEPTHMODEL_H
#include <QAbstractListModel>
class ColorDepthModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString colorModelId READ colorModelId WRITE setColorModelId NOTIFY colorModelIdChanged)
public:
enum Roles {
TextRole = Qt::UserRole + 1,
};
explicit ColorDepthModel(QObject* parent = 0);
virtual ~ColorDepthModel();
-
+ QHash<int, QByteArray> roleNames() const;
virtual QVariant data(const QModelIndex& index, int role) const;
virtual int rowCount(const QModelIndex& parent) const;
QString colorModelId() const;
Q_INVOKABLE QString id(int index);
Q_INVOKABLE int indexOf(const QString& id);
public Q_SLOTS:
void setColorModelId(const QString& id);
Q_SIGNALS:
void colorModelIdChanged();
private:
class Private;
Private * const d;
};
#endif // COLORDEPTHMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/ColorModelModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/ColorModelModel.cpp
index 9efd3d2860..f8327e4f83 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/ColorModelModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/ColorModelModel.cpp
@@ -1,76 +1,80 @@
/*
* This file is part of the KDE project
* Copyright (C) 2014 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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 "ColorModelModel.h"
#include <libs/pigment/KoColorSpaceRegistry.h>
class ColorModelModel::Private
{
public:
Private() { }
QList<KoID> colorModels;
};
ColorModelModel::ColorModelModel(QObject* parent)
: QAbstractListModel(parent), d(new Private)
{
d->colorModels = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible);
-
- QHash<int, QByteArray> roleNames;
- roleNames.insert(TextRole, "text");
- setRoleNames(roleNames);
}
ColorModelModel::~ColorModelModel()
{
delete d;
}
+QHash<int, QByteArray> ColorModelModel::roleNames() const
+{
+ QHash<int, QByteArray> roleNames;
+ roleNames.insert(TextRole, "text");
+
+ return roleNames;
+}
+
int ColorModelModel::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return d->colorModels.count();
}
QVariant ColorModelModel::data(const QModelIndex& index, int role) const
{
if(!index.isValid() || index.row() < 0 || index.row() >= d->colorModels.count())
{
return QVariant();
}
if( role == TextRole )
return d->colorModels.at(index.row()).name();
return QVariant();
}
QString ColorModelModel::id(int index)
{
if(index < 0 || index >= d->colorModels.count())
return QString();
return d->colorModels.at(index).id();
}
int ColorModelModel::indexOf(const QString& id)
{
return d->colorModels.indexOf(KoID(id));
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/ColorModelModel.h b/libs/libqml/plugins/kritasketchplugin/models/ColorModelModel.h
index 01768ebfb6..24e593e99b 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/ColorModelModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/ColorModelModel.h
@@ -1,48 +1,48 @@
/*
* This file is part of the KDE project
* Copyright (C) 2014 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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 COLORMODELMODEL_H
#define COLORMODELMODEL_H
#include <QAbstractListModel>
class ColorModelModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
TextRole = Qt::UserRole + 1,
};
ColorModelModel(QObject* parent = 0);
~ColorModelModel();
-
+ QHash<int, QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex& parent) const;
virtual QVariant data(const QModelIndex& index, int role) const;
Q_INVOKABLE QString id(int index);
Q_INVOKABLE int indexOf(const QString& id);
private:
class Private;
Private * const d;
};
#endif // COLORMODELMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/ColorProfileModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/ColorProfileModel.cpp
index 457ccaea99..7e87a5e530 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/ColorProfileModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/ColorProfileModel.cpp
@@ -1,130 +1,135 @@
/*
* This file is part of the KDE project
* Copyright (C) 2014 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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 "ColorProfileModel.h"
#include <libs/pigment/KoColorSpaceRegistry.h>
#include <libs/pigment/KoColorProfile.h>
class ColorProfileModel::Private
{
public:
Private(ColorProfileModel* qq) : q(qq), defaultProfile(-1) { }
void updateProfiles();
ColorProfileModel* q;
QString colorModelId;
QString colorDepthId;
QString colorSpaceId;
int defaultProfile;
QList<const KoColorProfile*> colorProfiles;
};
ColorProfileModel::ColorProfileModel(QObject* parent)
: QAbstractListModel(parent), d(new Private(this))
{
- QHash<int, QByteArray> roleNames;
- roleNames.insert(TextRole, "text");
- setRoleNames(roleNames);
}
ColorProfileModel::~ColorProfileModel()
{
delete d;
}
+QHash<int, QByteArray> ColorProfileModel::roleNames() const
+{
+ QHash<int, QByteArray> roleNames;
+ roleNames.insert(TextRole, "text");
+
+ return roleNames;
+}
+
int ColorProfileModel::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return d->colorProfiles.count();
}
QVariant ColorProfileModel::data(const QModelIndex& index, int role) const
{
if(!index.isValid() || index.row() < 0 || index.row() >= d->colorProfiles.count())
return QVariant();
if(role == TextRole) {
return d->colorProfiles.at(index.row())->name();
}
return QVariant();
}
QString ColorProfileModel::colorModelId() const
{
return d->colorModelId;
}
void ColorProfileModel::setColorModelId(const QString& id)
{
if(id != d->colorModelId) {
d->colorModelId = id;
d->updateProfiles();
emit colorModelIdChanged();
}
}
QString ColorProfileModel::colorDepthId() const
{
return d->colorDepthId;
}
void ColorProfileModel::setColorDepthId(const QString& id)
{
if(id != d->colorDepthId) {
d->colorDepthId = id;
d->updateProfiles();
emit colorDepthIdChanged();
}
}
int ColorProfileModel::defaultProfile() const
{
return d->defaultProfile;
}
QString ColorProfileModel::id(int index)
{
return d->colorProfiles.at(index)->name();
}
void ColorProfileModel::Private::updateProfiles()
{
if(colorDepthId.isEmpty() || colorModelId.isEmpty())
return;
q->beginResetModel();
colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModelId, colorDepthId);
colorProfiles = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId);
QString profile = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId);
for(int i = 0; i < colorProfiles.count(); ++i) {
if(colorProfiles.at(i)->name() == profile) {
defaultProfile = i;
break;
}
}
q->endResetModel();
emit q->defaultProfileChanged();
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/ColorProfileModel.h b/libs/libqml/plugins/kritasketchplugin/models/ColorProfileModel.h
index eec6d1a95f..49205dadb1 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/ColorProfileModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/ColorProfileModel.h
@@ -1,64 +1,64 @@
/*
* This file is part of the KDE project
* Copyright (C) 2014 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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 COLORPROFILEMODEL_H
#define COLORPROFILEMODEL_H
#include <QAbstractListModel>
class ColorProfileModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString colorModelId READ colorModelId WRITE setColorModelId NOTIFY colorModelIdChanged)
Q_PROPERTY(QString colorDepthId READ colorDepthId WRITE setColorDepthId NOTIFY colorDepthIdChanged)
Q_PROPERTY(int defaultProfile READ defaultProfile NOTIFY defaultProfileChanged)
public:
enum Roles {
TextRole = Qt::UserRole + 1,
};
-
+
explicit ColorProfileModel(QObject* parent = 0);
virtual ~ColorProfileModel();
-
+ QHash<int, QByteArray> roleNames() const;
virtual QVariant data(const QModelIndex& index, int role) const;
virtual int rowCount(const QModelIndex& parent) const;
QString colorModelId() const;
QString colorDepthId() const;
int defaultProfile() const;
Q_INVOKABLE QString id(int index);
public Q_SLOTS:
void setColorModelId(const QString& id);
void setColorDepthId(const QString& id);
Q_SIGNALS:
void colorModelIdChanged();
void colorDepthIdChanged();
void defaultProfileChanged();
private:
class Private;
Private * const d;
};
#endif // COLORDEPTHMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.cpp
index 7c5cf2fb11..c0f8cb65ab 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.cpp
@@ -1,478 +1,482 @@
/*
Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
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 "CompositeOpModel.h"
#include <kis_composite_ops_model.h>
#include <KisViewManager.h>
#include <kis_canvas_resource_provider.h>
#include <kis_tool.h>
#include <kis_canvas2.h>
#include <input/kis_input_manager.h>
#include <kis_node_manager.h>
#include <kis_node.h>
#include <kis_layer.h>
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paintop_settings.h>
#include <brushengine/kis_paintop_registry.h>
#include <brushengine/kis_paintop_config_widget.h>
#include <KoCompositeOpRegistry.h>
#include <KoColorSpace.h>
#include <KoToolManager.h>
class CompositeOpModel::Private
{
public:
Private(CompositeOpModel* qq)
: q(qq)
, model(KisCompositeOpListModel::sharedInstance())
, view(0)
, eraserMode(0)
, opacity(0)
, opacityEnabled(false)
, flow(0)
, flowEnabled(false)
, size(0)
, sizeEnabled(false)
, presetsEnabled(true)
{};
CompositeOpModel* q;
KisCompositeOpListModel* model;
KisViewManager* view;
QString currentCompositeOpID;
QString prevCompositeOpID;
bool eraserMode;
QMap<KisPaintOpPreset*, KisPaintOpConfigWidget*> settingsWidgets;
qreal opacity;
bool opacityEnabled;
qreal flow;
bool flowEnabled;
qreal size;
bool sizeEnabled;
bool presetsEnabled;
KisPaintOpPresetSP currentPreset;
void updateCompositeOp(QString compositeOpID)
{
if (!view)
return;
KisNodeSP node = view->resourceProvider()->currentNode();
if (node && node->paintDevice())
{
if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID))
compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id();
if (compositeOpID != currentCompositeOpID)
{
q->setEraserMode(compositeOpID == COMPOSITE_ERASE);
currentPreset->settings()->setProperty("CompositeOp", compositeOpID);
//m_optionWidget->setConfiguration(m_activePreset->settings().data());
view->resourceProvider()->setCurrentCompositeOp(compositeOpID);
prevCompositeOpID = currentCompositeOpID;
currentCompositeOpID = compositeOpID;
}
}
emit q->currentCompositeOpIDChanged();
}
void ofsChanged()
{
if (presetsEnabled && !currentPreset.isNull() && !currentPreset->settings().isNull())
{
// IMPORTANT: set the PaintOp size before setting the other properties
// it wont work the other way
- qreal sizeDiff = size - currentPreset->settings()->paintOpSize();
+ //qreal sizeDiff = size - currentPreset->settings()->paintOpSize();
//currentPreset->settings()->changePaintOpSize(sizeDiff, 0);
if (currentPreset->settings()->hasProperty("OpacityValue"))
currentPreset->settings()->setProperty("OpacityValue", opacity);
if (currentPreset->settings()->hasProperty("FlowValue"))
currentPreset->settings()->setProperty("FlowValue", flow);
//m_optionWidget->setConfiguration(d->currentPreset->settings().data());
}
if (view)
{
view->resourceProvider()->setOpacity(opacity);
}
}
};
CompositeOpModel::CompositeOpModel(QObject* parent)
: QAbstractListModel(parent)
, d(new Private(this))
{
connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)),
this, SLOT(slotToolChanged(KoCanvasController*,int)));
- QHash<int,QByteArray> roles;
- roles[TextRole] = "text";
- roles[IsCategoryRole] = "isCategory";
- setRoleNames(roles);
}
CompositeOpModel::~CompositeOpModel()
{
delete d;
}
+QHash<int, QByteArray> CompositeOpModel::roleNames() const
+{
+ QHash<int,QByteArray> roles;
+ roles[TextRole] = "text";
+ roles[IsCategoryRole] = "isCategory";
+ return roles;
+}
+
QVariant CompositeOpModel::data(const QModelIndex& index, int role) const
{
QVariant data;
if (index.isValid())
{
QModelIndex otherIndex = d->model->index(index.row(), index.column(), QModelIndex());
switch(role)
{
case TextRole:
data = d->model->data(otherIndex, Qt::DisplayRole);
break;
case IsCategoryRole:
data = d->model->data(otherIndex, __CategorizedListModelBase::IsHeaderRole);
break;
default:
break;
}
}
return data;
}
int CompositeOpModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return d->model->rowCount(QModelIndex());
}
void CompositeOpModel::activateItem(int index)
{
if (index > -1 && index < d->model->rowCount(QModelIndex()))
{
KoID compositeOp;
if (d->model->entryAt(compositeOp, d->model->index(index)))
d->updateCompositeOp(compositeOp.id());
}
}
QObject* CompositeOpModel::view() const
{
return d->view;
}
void CompositeOpModel::setView(QObject* newView)
{
if (d->view)
{
d->view->canvasBase()->disconnect(this);
d->view->canvasBase()->globalInputManager()->disconnect(this);
d->view->nodeManager()->disconnect(this);
}
d->view = qobject_cast<KisViewManager*>( newView );
if (d->view)
{
if (d->view->canvasBase() && d->view->canvasBase()->resourceManager()) {
connect(d->view->canvasBase()->resourceManager(), SIGNAL(canvasResourceChanged(int, const QVariant&)),
this, SLOT(resourceChanged(int, const QVariant&)));
}
if (d->view->nodeManager()) {
connect(d->view->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)),
this, SLOT(currentNodeChanged(KisLayerSP)));
}
slotToolChanged(0, 0);
}
emit viewChanged();
}
bool CompositeOpModel::eraserMode() const
{
return d->eraserMode;
}
void CompositeOpModel::setEraserMode(bool newEraserMode)
{
if (d->eraserMode != newEraserMode)
{
d->eraserMode = newEraserMode;
if (d->eraserMode)
d->updateCompositeOp(COMPOSITE_ERASE);
else
d->updateCompositeOp(d->prevCompositeOpID);
emit eraserModeChanged();
}
}
qreal CompositeOpModel::flow() const
{
return d->flow;
}
void CompositeOpModel::setFlow(qreal newFlow)
{
if (d->flow != newFlow)
{
d->flow = newFlow;
d->ofsChanged();
emit flowChanged();
}
}
bool CompositeOpModel::flowEnabled() const
{
return d->flowEnabled;
}
void CompositeOpModel::setFlowEnabled(bool newFlowEnabled)
{
d->flowEnabled = newFlowEnabled;
emit flowEnabledChanged();
}
qreal CompositeOpModel::opacity() const
{
return d->opacity;
}
void CompositeOpModel::setOpacity(qreal newOpacity)
{
if (d->opacity != newOpacity)
{
d->opacity = newOpacity;
d->ofsChanged();
emit opacityChanged();
}
}
bool CompositeOpModel::opacityEnabled() const
{
return d->opacityEnabled;
}
void CompositeOpModel::setOpacityEnabled(bool newOpacityEnabled)
{
d->opacityEnabled = newOpacityEnabled;
emit opacityEnabledChanged();
}
qreal CompositeOpModel::size() const
{
return d->size;
}
void CompositeOpModel::setSize(qreal newSize)
{
if (d->size != newSize)
{
d->size = newSize;
d->ofsChanged();
emit sizeChanged();
}
}
bool CompositeOpModel::sizeEnabled() const
{
return d->sizeEnabled;
}
void CompositeOpModel::setSizeEnabled(bool newSizeEnabled)
{
d->sizeEnabled = newSizeEnabled;
emit sizeEnabledChanged();
}
void CompositeOpModel::changePaintopValue(QString propertyName, QVariant value)
{
if (propertyName == "size" && value.toReal() != d->size)
setSize(value.toReal());
else if (propertyName == "opacity" && value.toReal() != d->opacity)
setOpacity(value.toReal());
else if (propertyName == "flow" && value.toReal() != d->flow)
setFlow(value.toReal());
}
bool CompositeOpModel::mirrorHorizontally() const
{
if (d->view)
return d->view->resourceProvider()->mirrorHorizontal();
return false;
}
void CompositeOpModel::setMirrorHorizontally(bool newMirrorHorizontally)
{
if (d->view && d->view->resourceProvider()->mirrorHorizontal() != newMirrorHorizontally)
{
d->view->resourceProvider()->setMirrorHorizontal(newMirrorHorizontally);
emit mirrorHorizontallyChanged();
}
}
bool CompositeOpModel::mirrorVertically() const
{
if (d->view)
return d->view->resourceProvider()->mirrorVertical();
return false;
}
void CompositeOpModel::setMirrorVertically(bool newMirrorVertically)
{
if (d->view && d->view->resourceProvider()->mirrorVertical() != newMirrorVertically)
{
d->view->resourceProvider()->setMirrorVertical(newMirrorVertically);
emit mirrorVerticallyChanged();
}
}
void CompositeOpModel::slotToolChanged(KoCanvasController* canvas, int toolId)
{
Q_UNUSED(canvas);
Q_UNUSED(toolId);
if (!d->view) return;
if (!d->view->canvasBase()) return;
QString id = KoToolManager::instance()->activeToolId();
KisTool* tool = dynamic_cast<KisTool*>(KoToolManager::instance()->toolById(d->view->canvasBase(), id));
if (tool) {
int flags = tool->flags();
if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) {
//setWidgetState(ENABLE_COMPOSITEOP|ENABLE_OPACITY);
d->opacityEnabled = true;
}
else {
//setWidgetState(DISABLE_COMPOSITEOP|DISABLE_OPACITY);
d->opacityEnabled = false;
}
if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) {
d->flowEnabled = true;
d->sizeEnabled = true;
d->presetsEnabled = true;
}
else {
d->flowEnabled = false;
d->sizeEnabled = false;
d->presetsEnabled = false;
}
}
else {
d->opacityEnabled = false;
d->flowEnabled = false;
d->sizeEnabled = false;
}
emit opacityEnabledChanged();
emit flowEnabledChanged();
emit sizeEnabledChanged();
}
void CompositeOpModel::resourceChanged(int key, const QVariant& /*v*/)
{
if (d->view && d->view->canvasBase() && d->view->canvasBase()->resourceManager() && d->view->resourceProvider()) {
if (key == KisCanvasResourceProvider::MirrorHorizontal) {
emit mirrorHorizontallyChanged();
return;
}
else if(key == KisCanvasResourceProvider::MirrorVertical) {
emit mirrorVerticallyChanged();
return;
}
KisPaintOpPresetSP preset = d->view->canvasBase()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
if (preset && d->currentPreset.data() != preset.data()) {
d->currentPreset = preset;
if (!d->settingsWidgets.contains(preset.data())) {
d->settingsWidgets[preset.data()] = KisPaintOpRegistry::instance()->get(preset->paintOp().id())->createConfigWidget(0);
d->settingsWidgets[preset.data()]->setImage(d->view->image());
d->settingsWidgets[preset.data()]->setConfiguration(preset->settings());
}
if (d->settingsWidgets[preset.data()]) {
preset->settings()->setOptionsWidget(d->settingsWidgets[preset.data()]);
}
d->size = preset->settings()->paintOpSize();
emit sizeChanged();
if (preset->settings()->hasProperty("OpacityValue")) {
d->opacityEnabled = true;
d->opacity = preset->settings()->getProperty("OpacityValue").toReal();
}
else {
d->opacityEnabled = false;
d->opacity = 1;
}
d->view->resourceProvider()->setOpacity(d->opacity);
emit opacityChanged();
emit opacityEnabledChanged();
if (preset->settings()->hasProperty("FlowValue")) {
d->flowEnabled = true;
d->flow = preset->settings()->getProperty("FlowValue").toReal();
}
else {
d->flowEnabled = false;
d->flow = 1;
}
emit flowChanged();
emit flowEnabledChanged();
QString compositeOp = preset->settings()->getString("CompositeOp");
// This is a little odd, but the logic here is that the opposite of an eraser is a normal composite op (so we just select over, aka normal)
// This means that you can switch your eraser over to being a painting tool by turning off the eraser again.
if (compositeOp == COMPOSITE_ERASE) {
d->currentCompositeOpID = COMPOSITE_OVER;
d->eraserMode = true;
}
else {
d->eraserMode = false;
}
emit eraserModeChanged();
d->updateCompositeOp(compositeOp);
}
}
}
void CompositeOpModel::currentNodeChanged(KisLayerSP newNode)
{
Q_UNUSED(newNode);
if (d->eraserMode) {
d->eraserMode = false;
d->updateCompositeOp(d->prevCompositeOpID);
emit eraserModeChanged();
}
}
int CompositeOpModel::indexOf(QString compositeOpId)
{
return d->model->indexOf(KoID(compositeOpId)).row();
}
QString CompositeOpModel::currentCompositeOpID() const
{
return d->currentCompositeOpID;
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.h b/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.h
index 546dc0e16b..57cdba623e 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/CompositeOpModel.h
@@ -1,114 +1,115 @@
/*
Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
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 COMPOSITEOPMODEL_H
#define COMPOSITEOPMODEL_H
#include <QModelIndex>
#include <kis_layer.h>
#include <kis_types.h>
class KoCanvasController;
class CompositeOpModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QObject* view READ view WRITE setView NOTIFY viewChanged);
Q_PROPERTY(bool eraserMode READ eraserMode WRITE setEraserMode NOTIFY eraserModeChanged);
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged);
Q_PROPERTY(bool opacityEnabled READ opacityEnabled WRITE setOpacityEnabled NOTIFY opacityEnabledChanged);
Q_PROPERTY(qreal flow READ flow WRITE setFlow NOTIFY flowChanged);
Q_PROPERTY(bool flowEnabled READ flowEnabled WRITE setFlowEnabled NOTIFY flowEnabledChanged);
Q_PROPERTY(qreal size READ size WRITE setSize NOTIFY sizeChanged);
Q_PROPERTY(bool sizeEnabled READ sizeEnabled WRITE setSizeEnabled NOTIFY sizeEnabledChanged);
Q_PROPERTY(bool mirrorHorizontally READ mirrorHorizontally WRITE setMirrorHorizontally NOTIFY mirrorHorizontallyChanged);
Q_PROPERTY(bool mirrorVertically READ mirrorVertically WRITE setMirrorVertically NOTIFY mirrorVerticallyChanged);
Q_PROPERTY(QString currentCompositeOpID READ currentCompositeOpID NOTIFY currentCompositeOpIDChanged);
public:
enum CompositeOpModelRoles {
TextRole = Qt::UserRole + 1,
IsCategoryRole
};
explicit CompositeOpModel(QObject* parent = 0);
virtual ~CompositeOpModel();
+ QHash<int, QByteArray> roleNames() const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
Q_INVOKABLE void activateItem(int index);
QObject* view() const;
void setView(QObject* newView);
bool eraserMode() const;
void setEraserMode(bool newEraserMode);
qreal opacity() const;
void setOpacity(qreal newOpacity);
bool opacityEnabled() const;
void setOpacityEnabled(bool newOpacityEnabled);
qreal flow() const;
void setFlow(qreal newFlow);
bool flowEnabled() const;
void setFlowEnabled(bool newFlowEnabled);
qreal size() const;
void setSize(qreal newSize);
bool sizeEnabled() const;
void setSizeEnabled(bool newSizeEnabled);
bool mirrorHorizontally() const;
void setMirrorHorizontally(bool newMirrorHorizontally);
bool mirrorVertically() const;
void setMirrorVertically(bool newMirrorVertically);
QString currentCompositeOpID() const;
Q_INVOKABLE void changePaintopValue(QString propertyName, QVariant value);
Q_INVOKABLE int indexOf(QString compositeOpId);
Q_SIGNALS:
void viewChanged();
void eraserModeChanged();
void opacityChanged();
void opacityEnabledChanged();
void flowChanged();
void flowEnabledChanged();
void sizeChanged();
void sizeEnabledChanged();
void mirrorHorizontallyChanged();
void mirrorVerticallyChanged();
void currentCompositeOpIDChanged();
private Q_SLOTS:
void slotToolChanged(KoCanvasController* canvas, int toolId);
void resourceChanged(int key, const QVariant& v);
void currentNodeChanged(KisLayerSP newNode);
private:
class Private;
Private* d;
};
#endif // COMPOSITEOPMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/FileSystemModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/FileSystemModel.cpp
index d14149fab4..a13980fdd5 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/FileSystemModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/FileSystemModel.cpp
@@ -1,162 +1,162 @@
/* This file is part of the KDE project
*
* Copyright (c) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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 "FileSystemModel.h"
#include <QDateTime>
#include <KisMimeDatabase.h>
#include <QFileInfo>
#include <QDir>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QDebug>
class FileSystemModel::Private
{
public:
QDir dir;
QFileInfoList list;
static const QString drivesPath;
};
const QString FileSystemModel::Private::drivesPath("special://drives");
FileSystemModel::FileSystemModel(QObject* parent)
: QAbstractListModel(parent), d(new Private)
{
d->dir.setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
d->dir.setSorting(QDir::DirsFirst | QDir::Name | QDir::IgnoreCase);
}
FileSystemModel::~FileSystemModel()
{
delete d;
}
QVariant FileSystemModel::data(const QModelIndex& index, int role) const
{
if (index.isValid()) {
const QFileInfo &fileInfo = d->list.at(index.row());
switch(role) {
case FileNameRole:
return fileInfo.fileName();
break;
case FilePathRole:
return fileInfo.absoluteFilePath();
break;
case FileIconRole:
return fileInfo.isDir() ? "inode/directory" : QString("image://recentimage/%1").arg(fileInfo.absoluteFilePath());
break;
case FileDateRole:
return fileInfo.lastModified().toString(Qt::SystemLocaleShortDate);
}
}
return QVariant();
}
int FileSystemModel::rowCount(const QModelIndex&) const
{
return d->list.count();
}
void FileSystemModel::classBegin()
{
}
void FileSystemModel::componentComplete()
{
- setPath(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ setPath(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
}
QString FileSystemModel::path()
{
QString path = d->dir.absolutePath();
if (path.isEmpty()) {
return Private::drivesPath;
} else {
return d->dir.absolutePath();
}
}
void FileSystemModel::setPath(const QString& path)
{
if (path != d->dir.path()) {
if (d->list.count() > 0) {
beginRemoveRows(QModelIndex(), 0, d->list.count() - 1);
endRemoveRows();
}
if (path != Private::drivesPath) {
d->dir.setPath(path);
d->dir.refresh();
d->list = d->dir.entryInfoList();
if (d->list.count() > 0) {
beginInsertRows(QModelIndex(), 0, d->list.count() - 1);
endInsertRows();
}
} else {
d->dir.setPath("");
d->dir.refresh();
d->list = QDir::drives();
beginInsertRows(QModelIndex(), 0, d->list.count() - 1);
endInsertRows();
}
emit pathChanged();
}
}
QString FileSystemModel::parentFolder()
{
if (path() != Private::drivesPath) {
if (QRegExp("^[A-Z]{1,3}:/$").exactMatch(path())) {
return Private::drivesPath;
} else {
QDir root(path());
root.cdUp();
return root.path();
}
} else {
return Private::drivesPath;
}
}
QString FileSystemModel::filter()
{
return d->dir.nameFilters().join(" ");
}
void FileSystemModel::setFilter(const QString& filter)
{
d->dir.setNameFilters(filter.split(" "));
}
QHash<int, QByteArray> FileSystemModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(FileNameRole, "fileName");
roles.insert(FilePathRole, "path");
roles.insert(FileIconRole, "icon");
roles.insert(FileDateRole, "date");
return roles;
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.cpp
index 486f141033..cd425b7122 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.cpp
@@ -1,309 +1,316 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 "FiltersCategoryModel.h"
#include "FiltersModel.h"
#include <PropertyContainer.h>
#include <filter/kis_filter_registry.h>
#include <filter/kis_filter.h>
#include <filter/kis_filter_configuration.h>
#include <kis_filter_mask.h>
#include <kis_layer.h>
#include <kis_selection.h>
#include <kis_config_widget.h>
#include <KisViewManager.h>
#include <kis_node_manager.h>
#include <kis_selection_manager.h>
#include <kis_canvas2.h>
#include <kis_filter_manager.h>
#include <QApplication>
+#include <algorithm>
+
bool categoryLessThan(const FiltersModel* s1, const FiltersModel* s2)
{
return s1->categoryName.toLower() < s2->categoryName.toLower();
}
class FiltersCategoryModel::Private
{
public:
Private(FiltersCategoryModel* qq)
: q(qq)
, currentCategory(-1)
, view(0)
, previewEnabled(false)
, previewFilterID(-1)
, previewTimer(new QTimer())
{
previewTimer->setInterval(150);
previewTimer->setSingleShot(true);
connect(previewTimer, SIGNAL(timeout()), q, SLOT(updatePreview()));
}
FiltersCategoryModel* q;
int currentCategory;
KisViewManager* view;
QList<FiltersModel*> categories;
FiltersModel* categoryByName(const QString& name)
{
FiltersModel* category = 0;
for(int i = 0; i < categories.count(); ++i)
{
if (categories.at(i)->categoryId == name)
{
category = categories[i];
break;
}
}
return category;
}
void refreshContents()
{
q->beginResetModel();
qDeleteAll(categories);
categories.clear();
QList<KisFilterSP> filters = KisFilterRegistry::instance()->values();
QList<QString> tmpCategoryIDs;
Q_FOREACH (const KisFilterSP filter, filters) {
Q_ASSERT(filter);
FiltersModel* cat = 0;
if (!tmpCategoryIDs.contains(filter->menuCategory().id())) {
cat = new FiltersModel(q);
cat->categoryId = filter->menuCategory().id();
cat->categoryName = filter->menuCategory().name();
cat->setView(view);
categories << cat;
tmpCategoryIDs << filter->menuCategory().id();
connect(cat, SIGNAL(configurationChanged(int)), q, SLOT(filterConfigurationChanged(int)));
connect(cat, SIGNAL(filterActivated(int)), q, SLOT(filterActivated(int)));
}
else
cat = categoryByName(filter->menuCategory().id());
cat->addFilter(filter);
qApp->processEvents();
}
- qSort(categories.begin(), categories.end(), categoryLessThan);
+ std::sort(categories.begin(), categories.end(), categoryLessThan);
q->endResetModel();
}
bool previewEnabled;
KisFilterMaskSP mask;
KisNodeSP node;
int previewFilterID;
KisFilterConfigurationSP newConfig;
QTimer* previewTimer;
};
FiltersCategoryModel::FiltersCategoryModel(QObject* parent)
: QAbstractListModel(parent)
, d(new Private(this))
{
- QHash<int, QByteArray> roles;
- roles[TextRole] = "text";
- setRoleNames(roles);
}
FiltersCategoryModel::~FiltersCategoryModel()
{
delete d;
}
+QHash<int, QByteArray> FiltersCategoryModel::roleNames() const
+{
+ QHash<int, QByteArray> roles;
+ roles[TextRole] = "text";
+
+ return roles;
+}
+
QVariant FiltersCategoryModel::data(const QModelIndex& index, int role) const
{
QVariant data;
if (index.isValid())
{
switch(role)
{
case TextRole:
data = d->categories[index.row()]->categoryName;
break;
default:
break;
}
}
return data;
}
int FiltersCategoryModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return d->categories.count();
}
QObject* FiltersCategoryModel::filterModel() const
{
if (d->currentCategory == -1)
return 0;
return d->categories[d->currentCategory];
}
void FiltersCategoryModel::activateItem(int index)
{
if (index > -1 && index < d->categories.count())
{
d->currentCategory = index;
emit filterModelChanged();
}
}
QObject* FiltersCategoryModel::view() const
{
return d->view;
}
void FiltersCategoryModel::setView(QObject* newView)
{
if (d->view)
{
setPreviewEnabled(false);
d->view->nodeManager()->disconnect(this);
d->view->selectionManager()->disconnect(this);
}
d->view = qobject_cast<KisViewManager*>( newView );
if (d->view)
{
d->refreshContents();
connect(d->view->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)), this, SLOT(activeLayerChanged(KisLayerSP)));
connect(d->view->selectionManager(), SIGNAL(currentSelectionChanged()), this, SLOT(activeSelectionChanged()));
}
emit viewChanged();
}
void FiltersCategoryModel::activeLayerChanged(KisLayerSP layer)
{
Q_UNUSED(layer);
setPreviewEnabled(false);
}
void FiltersCategoryModel::activeSelectionChanged()
{
setPreviewEnabled(false);
}
void FiltersCategoryModel::filterActivated(int index)
{
Q_UNUSED(index);
setPreviewEnabled(false);
}
void FiltersCategoryModel::filterConfigurationChanged(int index, FiltersModel* model)
{
d->previewFilterID = index;
if (d->previewEnabled && index > -1)
{
if (!model) {
model = qobject_cast<FiltersModel*>(sender());
}
if (!model) {
return;
}
KisFilterConfigurationSP config;
KisFilter* filter = model->filter(index);
if (filter->showConfigurationWidget() && filter->id() != QLatin1String("colortransfer")) {
KisConfigWidget* wdg = filter->createConfigurationWidget(0, d->view->activeNode()->original());
wdg->deleteLater();
config = KisFilterConfigurationSP(KisFilterRegistry::instance()->cloneConfiguration(dynamic_cast<KisFilterConfiguration*>(wdg->configuration().data())));
}
else {
config = KisFilterConfigurationSP(KisFilterRegistry::instance()->cloneConfiguration(filter->defaultConfiguration()));
}
QObject* configuration = d->categories[d->currentCategory]->configuration(index);
Q_FOREACH (const QByteArray& propName, configuration->dynamicPropertyNames()) {
config->setProperty(QString(propName), configuration->property(propName));
}
config->setCurve(qobject_cast<PropertyContainer*>(configuration)->curve());
config->setCurves(qobject_cast<PropertyContainer*>(configuration)->curves());
configuration->deleteLater();
d->newConfig = config;
d->previewTimer->start();
}
}
void FiltersCategoryModel::updatePreview()
{
d->view->filterManager()->apply(d->newConfig);
}
bool FiltersCategoryModel::previewEnabled() const
{
return d->previewEnabled;
}
void FiltersCategoryModel::filterSelected(int index)
{
if (d->previewEnabled)
filterConfigurationChanged(index, d->categories[d->currentCategory]);
}
void FiltersCategoryModel::setPreviewEnabled(bool enabled)
{
if (d->previewEnabled != enabled)
{
d->previewEnabled = enabled;
emit previewEnabledChanged();
if (enabled)
filterConfigurationChanged(d->previewFilterID, d->categories[d->currentCategory]);
else
d->view->filterManager()->cancel();
}
}
int FiltersCategoryModel::categoryIndexForConfig(QObject* config)
{
PropertyContainer* configuration = qobject_cast<PropertyContainer*>(config);
if (!configuration)
return -1;
FiltersModel* model = 0;
int i = 0;
while(model == 0 && i < d->categories.count())
{
FiltersModel* cat = d->categories.at(i);
// i know there's no check here - but a category is not created unless there
// is something to put in it
for(int j = 0; j < cat->rowCount(); ++j)
{
if (cat->filter(j)->id() == configuration->name())
return i;
}
++i;
}
return -1;
}
int FiltersCategoryModel::filterIndexForConfig(int categoryIndex, QObject* filterConfig)
{
PropertyContainer* configuration = qobject_cast<PropertyContainer*>(filterConfig);
if (!configuration)
return -1;
if (categoryIndex < 0 || categoryIndex > d->categories.count() - 1)
return -1;
FiltersModel* cat = d->categories.at(categoryIndex);
// i know there's no check here - but a category is not created unless there
// is something to put in it
for(int j = 0; j < cat->rowCount(); ++j)
{
if (cat->filter(j)->id() == configuration->name())
return j;
}
return -1;
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.h b/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.h
index 157d4d6af3..4be4ad842c 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/FiltersCategoryModel.h
@@ -1,72 +1,73 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 FILTERSCATEGORYMODEL_H
#define FILTERSCATEGORYMODEL_H
#include <QModelIndex>
#include <kis_types.h>
class FiltersModel;
class FiltersCategoryModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QObject* view READ view WRITE setView NOTIFY viewChanged)
Q_PROPERTY(QObject* filterModel READ filterModel NOTIFY filterModelChanged);
Q_PROPERTY(bool previewEnabled READ previewEnabled WRITE setPreviewEnabled NOTIFY previewEnabledChanged);
public:
enum FiltersCategoryModelRoles {
TextRole = Qt::UserRole + 1
};
explicit FiltersCategoryModel(QObject* parent = 0);
virtual ~FiltersCategoryModel();
+ QHash<int, QByteArray> roleNames() const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
QObject* filterModel() const;
Q_INVOKABLE void activateItem(int index);
QObject* view() const;
void setView(QObject* newView);
bool previewEnabled() const;
void setPreviewEnabled(bool enabled);
Q_INVOKABLE void filterSelected(int index);
Q_INVOKABLE int categoryIndexForConfig(QObject* config);
Q_INVOKABLE int filterIndexForConfig(int categoryIndex, QObject* filterConfig);
Q_SIGNALS:
void viewChanged();
void filterModelChanged();
void previewEnabledChanged();
private Q_SLOTS:
void activeLayerChanged(KisLayerSP layer);
void activeSelectionChanged();
void filterConfigurationChanged(int index, FiltersModel* model = 0);
void filterActivated(int index);
void updatePreview();
private:
class Private;
Private* d;
};
#endif // FILTERSCATEGORYMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/FiltersModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/FiltersModel.cpp
index fa246874d4..42c685cd91 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/FiltersModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/FiltersModel.cpp
@@ -1,202 +1,207 @@
-/* This file is part of the KDE project
+/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 "FiltersModel.h"
#include <PropertyContainer.h>
#include <filter/kis_filter.h>
#include <filter/kis_filter_configuration.h>
#include <kis_config_widget.h>
#include <KisViewManager.h>
#include <kis_filter_manager.h>
class FiltersModel::Private
{
public:
Private()
: view(0)
{};
KisViewManager* view;
QList<KisFilterSP> filters;
QList<KisFilterConfigurationSP> configurations;
};
FiltersModel::FiltersModel(QObject* parent)
: QAbstractListModel(parent)
, d(new Private)
{
- QHash<int, QByteArray> roles;
- roles[TextRole] = "text";
- setRoleNames(roles);
}
FiltersModel::~FiltersModel()
{
delete d;
}
+QHash<int, QByteArray> FiltersModel::roleNames() const
+{
+ QHash<int, QByteArray> roles;
+ roles[TextRole] = "text";
+
+ return roles;
+}
+
QVariant FiltersModel::data(const QModelIndex& index, int role) const
{
QVariant data;
if (index.isValid())
{
switch(role)
{
case TextRole:
data = d->filters[index.row()]->name();
break;
default:
break;
}
}
return data;
}
int FiltersModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return d->filters.count();
}
bool FiltersModel::filterRequiresConfiguration(int index)
{
if (index > -1 && index < d->filters.count())
{
return d->filters[index]->showConfigurationWidget();
}
return false;
}
QString FiltersModel::filterID(int index)
{
if (index > -1 && index < d->filters.count())
{
return d->filters[index]->id();
}
return QLatin1String("");
}
void FiltersModel::activateFilter(int index)
{
if (index > -1 && index < d->filters.count())
{
if (d->configurations[index])
{
d->view->filterManager()->apply(d->configurations[index]);
}
else
{
d->view->filterManager()->apply(KisFilterConfigurationSP(d->filters[index]->defaultConfiguration()));
}
d->view->filterManager()->finish();
emit filterActivated(index);
}
}
KisFilter* FiltersModel::filter(int index)
{
if (index > -1 && index < d->filters.count())
{
return d->filters[index].data();
}
return 0;
}
void FiltersModel::addFilter(KisFilterSP filter)
{
if (!d->view || !d->view->activeNode()) return;
if (!filter.isNull())
{
int newRow = d->filters.count();
beginInsertRows(QModelIndex(), newRow, newRow);
d->filters << filter;
// We're not asking for the config widget config for color transfer
// The reason for this is that the completion widget is VERY slow to destruct on
// Windows. This can be removed once that bug has been alleviated at some later
// point in time, but for now it has no side effects, as this filter's default
// config is fine anyway.
if (filter->showConfigurationWidget() && filter->id() != QLatin1String("colortransfer")) {
KisConfigWidget* wdg = filter->createConfigurationWidget(0, d->view->activeNode()->original());
wdg->deleteLater();
d->configurations << KisFilterConfigurationSP(dynamic_cast<KisFilterConfiguration*>(wdg->configuration().data()));
}
else {
d->configurations << KisFilterConfigurationSP(filter->defaultConfiguration());
}
endInsertRows();
}
}
QObject* FiltersModel::view() const
{
return d->view;
}
void FiltersModel::setView(QObject* newView)
{
d->view = qobject_cast<KisViewManager*>( newView );
emit viewChanged();
}
QObject* FiltersModel::configuration(int index)
{
// If index is out of bounds, return /something/ for the object work on at least.
if (index < 0 || index > d->configurations.count() - 1)
return new PropertyContainer("", this);
PropertyContainer* config = new PropertyContainer(d->filters[index]->id(), this);
if (!d->configurations[index]) {
// if we have a config widget to show, reinitialise the configuration, just in case
if(d->filters[index]->showConfigurationWidget() && d->filters[index]->id() != QLatin1String("colortransfer")) {
KisConfigWidget* wdg = d->filters[index]->createConfigurationWidget(0, d->view->activeNode()->original());
wdg->deleteLater();
d->configurations[index] = KisFilterConfigurationSP(dynamic_cast<KisFilterConfiguration*>(wdg->configuration().data()));
}
// If we've not got one already, assign the default configuration to the cache
else {
d->configurations[index] = KisFilterConfigurationSP(d->filters[index]->defaultConfiguration());
}
}
QMap<QString, QVariant> props = d->configurations[index]->getProperties();
QMap<QString, QVariant>::const_iterator i;
for(i = props.constBegin(); i != props.constEnd(); ++i)
{
config->setProperty(i.key().toLatin1(), i.value());
}
config->setCurve(d->configurations[index]->curve());
config->setCurves(d->configurations[index]->curves());
return config;
}
void FiltersModel::setConfiguration(int index, QObject* configuration)
{
if (qobject_cast< PropertyContainer* >(configuration) && index > -1 && index < d->configurations.count() - 1)
{
KisFilterConfigurationSP config = d->configurations[index];
Q_FOREACH (const QByteArray& propName, configuration->dynamicPropertyNames())
{
config->setProperty(QString(propName), configuration->property(propName));
}
config->setCurve(qobject_cast< PropertyContainer* >(configuration)->curve());
config->setCurves(qobject_cast< PropertyContainer* >(configuration)->curves());
d->configurations[index] = config;
emit configurationChanged(index);
}
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/FiltersModel.h b/libs/libqml/plugins/kritasketchplugin/models/FiltersModel.h
index 381016385e..d4033ee63c 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/FiltersModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/FiltersModel.h
@@ -1,63 +1,64 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 FILTERSMODEL_H
#define FILTERSMODEL_H
#include <QModelIndex>
#include <kis_types.h>
class FiltersModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QObject* view READ view WRITE setView NOTIFY viewChanged)
public:
enum FiltersModelRoles {
TextRole = Qt::UserRole + 1
};
explicit FiltersModel(QObject* parent = 0);
virtual ~FiltersModel();
+ QHash<int, QByteArray> roleNames() const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
Q_INVOKABLE bool filterRequiresConfiguration(int index);
Q_INVOKABLE QString filterID(int index);
Q_INVOKABLE void activateFilter(int index);
KisFilter* filter(int index);
QString categoryId;
QString categoryName;
void addFilter(KisFilterSP filter);
QObject* view() const;
void setView(QObject* newView);
Q_INVOKABLE QObject* configuration(int index);
Q_INVOKABLE void setConfiguration(int index, QObject* configuration);
Q_SIGNALS:
void viewChanged();
void configurationChanged(int index);
void filterActivated(int index);
private:
class Private;
Private* d;
};
#endif // FILTERSMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/KeyboardModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/KeyboardModel.cpp
index 222f91a687..cff4091cf0 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/KeyboardModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/KeyboardModel.cpp
@@ -1,290 +1,295 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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 "KeyboardModel.h"
struct Key {
explicit Key(QString keyText, KeyboardModel::KeyType key = KeyboardModel::NormalKey, float size = 1.0f)
: text(keyText),
keyType(key),
width(size)
{
}
QString text;
KeyboardModel::KeyType keyType;
float width;
};
class KeyboardModel::Private
{
public:
Private()
: mode(NormalMode),
currentKeys(&normalKeys),
useBuiltIn(true)
{
#ifdef Q_OS_WIN
useBuiltIn = false;
#endif
}
KeyboardMode mode;
QList<Key> *currentKeys;
QList<Key> normalKeys;
QList<Key> capitalKeys;
QList<Key> numericKeys;
bool useBuiltIn;
};
KeyboardModel::KeyboardModel(QObject* parent)
: QAbstractListModel(parent), d(new Private)
{
- QHash<int, QByteArray> roleNames;
- roleNames.insert(TextRole, "text");
- roleNames.insert(TypeRole, "keyType");
- roleNames.insert(WidthRole, "width");
- setRoleNames(roleNames);
}
KeyboardModel::~KeyboardModel()
{
delete d;
}
+QHash<int, QByteArray> KeyboardModel::roleNames() const
+{
+ QHash<int, QByteArray> roleNames;
+ roleNames.insert(TextRole, "text");
+ roleNames.insert(TypeRole, "keyType");
+ roleNames.insert(WidthRole, "width");
+
+ return roleNames;
+}
+
void KeyboardModel::classBegin()
{
}
void KeyboardModel::componentComplete()
{
//Set up keys
//Normal mode
//First row
d->normalKeys.append(Key("q"));
d->normalKeys.append(Key("w"));
d->normalKeys.append(Key("e"));
d->normalKeys.append(Key("r"));
d->normalKeys.append(Key("t"));
d->normalKeys.append(Key("y"));
d->normalKeys.append(Key("u"));
d->normalKeys.append(Key("i"));
d->normalKeys.append(Key("o"));
d->normalKeys.append(Key("p"));
d->normalKeys.append(Key(QChar(0x2190), BackspaceKey, 2.0f));
//Second row
d->normalKeys.append(Key("", SpacerKey, 0.5f));
d->normalKeys.append(Key("a"));
d->normalKeys.append(Key("s"));
d->normalKeys.append(Key("d"));
d->normalKeys.append(Key("f"));
d->normalKeys.append(Key("g"));
d->normalKeys.append(Key("h"));
d->normalKeys.append(Key("j"));
d->normalKeys.append(Key("k"));
d->normalKeys.append(Key("l"));
d->normalKeys.append(Key("'"));
d->normalKeys.append(Key("Enter", EnterKey, 1.5f));
//Third row
d->normalKeys.append(Key(QChar(0x2191), ShiftKey));
d->normalKeys.append(Key("z"));
d->normalKeys.append(Key("x"));
d->normalKeys.append(Key("c"));
d->normalKeys.append(Key("v"));
d->normalKeys.append(Key("b"));
d->normalKeys.append(Key("n"));
d->normalKeys.append(Key("m"));
d->normalKeys.append(Key(","));
d->normalKeys.append(Key("."));
d->normalKeys.append(Key("?"));
d->normalKeys.append(Key(QChar(0x2191), ShiftKey));
//Fourth row
d->normalKeys.append(Key("&123", NumericModeKey));
d->normalKeys.append(Key("Ctrl", SpacerKey));
d->normalKeys.append(Key(QChar(0x263A), SpacerKey));
d->normalKeys.append(Key(" ", NormalKey, 6.f));
d->normalKeys.append(Key("<", LeftArrowKey));
d->normalKeys.append(Key(">", RightArrowKey));
d->normalKeys.append(Key("Close", CloseKey));
//Capital mode
//First row
d->capitalKeys.append(Key("Q"));
d->capitalKeys.append(Key("W"));
d->capitalKeys.append(Key("E"));
d->capitalKeys.append(Key("R"));
d->capitalKeys.append(Key("T"));
d->capitalKeys.append(Key("Y"));
d->capitalKeys.append(Key("U"));
d->capitalKeys.append(Key("I"));
d->capitalKeys.append(Key("O"));
d->capitalKeys.append(Key("P"));
d->capitalKeys.append(Key(QChar(0x2190), BackspaceKey, 2.0f));
//Second row
d->capitalKeys.append(Key("", SpacerKey, 0.5f));
d->capitalKeys.append(Key("A"));
d->capitalKeys.append(Key("S"));
d->capitalKeys.append(Key("D"));
d->capitalKeys.append(Key("F"));
d->capitalKeys.append(Key("G"));
d->capitalKeys.append(Key("H"));
d->capitalKeys.append(Key("J"));
d->capitalKeys.append(Key("K"));
d->capitalKeys.append(Key("L"));
d->capitalKeys.append(Key("\""));
d->capitalKeys.append(Key("Enter", EnterKey, 1.5f));
//Third row
d->capitalKeys.append(Key(QChar(0x2191), ShiftKey));
d->capitalKeys.append(Key("Z"));
d->capitalKeys.append(Key("X"));
d->capitalKeys.append(Key("C"));
d->capitalKeys.append(Key("V"));
d->capitalKeys.append(Key("B"));
d->capitalKeys.append(Key("N"));
d->capitalKeys.append(Key("M"));
d->capitalKeys.append(Key(";"));
d->capitalKeys.append(Key(":"));
d->capitalKeys.append(Key("!"));
d->capitalKeys.append(Key(QChar(0x2191), ShiftKey));
//Fourth row
d->capitalKeys.append(Key("&123", NumericModeKey));
d->capitalKeys.append(Key("Ctrl", SpacerKey));
d->capitalKeys.append(Key(QChar(0x263A), SpacerKey));
d->capitalKeys.append(Key(" ", NormalKey, 6.f));
d->capitalKeys.append(Key("<", LeftArrowKey));
d->capitalKeys.append(Key(">", RightArrowKey));
d->capitalKeys.append(Key("Close", CloseKey));
//Capital mode
//First row
d->numericKeys.append(Key("Tab", SpacerKey));
d->numericKeys.append(Key("!"));
d->numericKeys.append(Key("@"));
d->numericKeys.append(Key("#"));
d->numericKeys.append(Key("$"));
d->numericKeys.append(Key("%"));
d->numericKeys.append(Key("&"));
d->numericKeys.append(Key("", SpacerKey, 0.5f));
d->numericKeys.append(Key("1"));
d->numericKeys.append(Key("2"));
d->numericKeys.append(Key("3"));
d->numericKeys.append(Key("", SpacerKey, 0.5f));
d->numericKeys.append(Key(QChar(0x2190), BackspaceKey));
//Second row
d->numericKeys.append(Key("<", SpacerKey));
d->numericKeys.append(Key("("));
d->numericKeys.append(Key(")"));
d->numericKeys.append(Key("-"));
d->numericKeys.append(Key("_"));
d->numericKeys.append(Key("="));
d->numericKeys.append(Key("+"));
d->numericKeys.append(Key("", SpacerKey, 0.5f));
d->numericKeys.append(Key("4"));
d->numericKeys.append(Key("5"));
d->numericKeys.append(Key("6"));
d->numericKeys.append(Key("", SpacerKey, 0.5f));
d->numericKeys.append(Key("Enter", EnterKey));
//Third row
d->numericKeys.append(Key(">", SpacerKey));
d->numericKeys.append(Key("\\"));
d->numericKeys.append(Key(";"));
d->numericKeys.append(Key(":"));
d->numericKeys.append(Key("\""));
d->numericKeys.append(Key("*"));
d->numericKeys.append(Key("/"));
d->numericKeys.append(Key("", SpacerKey, 0.5f));
d->numericKeys.append(Key("7"));
d->numericKeys.append(Key("9"));
d->numericKeys.append(Key("9"));
d->numericKeys.append(Key("", SpacerKey, 1.5f));
//Fourth row
d->numericKeys.append(Key("&123", NumericModeKey));
d->numericKeys.append(Key("Ctrl", SpacerKey));
d->numericKeys.append(Key(QChar(0x263A), SpacerKey));
d->numericKeys.append(Key("<", LeftArrowKey));
d->numericKeys.append(Key(">", RightArrowKey));
d->numericKeys.append(Key(" ", NormalKey, 2.f));
d->numericKeys.append(Key("", SpacerKey, 0.5f));
d->numericKeys.append(Key("0", NormalKey, 2.f));
d->numericKeys.append(Key("."));
d->numericKeys.append(Key("", SpacerKey, 0.5f));
d->numericKeys.append(Key("Close", CloseKey));
}
QVariant KeyboardModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
switch(role) {
case TextRole:
return d->currentKeys->at(index.row()).text;
case TypeRole:
return QVariant::fromValue<int>(d->currentKeys->at(index.row()).keyType);
case WidthRole:
return d->currentKeys->at(index.row()).width;
default:
break;
}
return QVariant();
}
int KeyboardModel::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent)
return d->currentKeys->count();
}
KeyboardModel::KeyboardMode KeyboardModel::keyboardMode() const
{
return d->mode;
}
void KeyboardModel::setKeyboardMode(KeyboardModel::KeyboardMode mode)
{
if (mode != d->mode) {
d->mode = mode;
beginRemoveRows(QModelIndex(), 0, d->currentKeys->count() - 1);
endRemoveRows();
switch(d->mode) {
case NormalMode:
d->currentKeys = &d->normalKeys;
break;
case CapitalMode:
d->currentKeys = &d->capitalKeys;
break;
case NumericMode:
d->currentKeys = &d->numericKeys;
break;
}
beginInsertRows(QModelIndex(), 0, d->currentKeys->count() - 1);
endInsertRows();
emit keyboardModeChanged();
}
}
bool KeyboardModel::useBuiltIn() const
{
return d->useBuiltIn;
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/KeyboardModel.h b/libs/libqml/plugins/kritasketchplugin/models/KeyboardModel.h
index a7ebe3d5e0..fe9379d05e 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/KeyboardModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/KeyboardModel.h
@@ -1,82 +1,84 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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 KEYBOARDMODEL_H
#define KEYBOARDMODEL_H
#include <QAbstractListModel>
#include <QQmlParserStatus>
class KeyboardModel : public QAbstractListModel, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(KeyboardMode mode READ keyboardMode WRITE setKeyboardMode NOTIFY keyboardModeChanged)
Q_PROPERTY(bool useBuiltIn READ useBuiltIn NOTIFY useBuiltInChanged)
public:
enum Roles {
TextRole = Qt::UserRole + 1,
TypeRole,
WidthRole
};
enum KeyboardMode {
NormalMode,
CapitalMode,
NumericMode
};
Q_ENUMS(KeyboardMode)
enum KeyType {
NormalKey,
SpacerKey,
ShiftKey,
EnterKey,
BackspaceKey,
NumericModeKey,
CloseKey,
LeftArrowKey,
RightArrowKey
};
Q_ENUMS(KeyType)
explicit KeyboardModel(QObject* parent = 0);
virtual ~KeyboardModel();
+ QHash<int, QByteArray> roleNames() const;
+
virtual void classBegin();
virtual void componentComplete();
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
KeyboardMode keyboardMode() const;
void setKeyboardMode(KeyboardModel::KeyboardMode mode);
bool useBuiltIn() const;
Q_SIGNALS:
void keyboardModeChanged();
bool useBuiltInChanged();
private:
class Private;
Private * const d;
};
#endif // KEYBOARDMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp
index ff6493db84..be39cd2375 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp
@@ -1,1048 +1,1055 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 "LayerModel.h"
#include "LayerThumbProvider.h"
#include <PropertyContainer.h>
#include <kis_node_model.h>
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <kis_node_manager.h>
#include <kis_dummies_facade_base.h>
#include <KisDocument.h>
#include <kis_composite_ops_model.h>
#include <kis_node.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_group_layer.h>
#include <kis_paint_layer.h>
#include <kis_filter_mask.h>
#include <kis_shape_controller.h>
#include <kis_adjustment_layer.h>
#include <kis_selection_manager.h>
#include <filter/kis_filter.h>
#include <filter/kis_filter_configuration.h>
#include <filter/kis_filter_registry.h>
#include <KoShapeBasedDocumentBase.h>
#include <KoProperties.h>
#include <QQmlEngine>
#include <kis_base_node.h>
struct LayerModelMetaInfo {
LayerModelMetaInfo()
: canMoveUp(false)
, canMoveRight(false)
, canMoveDown(false)
, canMoveLeft(false)
, depth(-1)
{}
bool canMoveUp;
bool canMoveRight;
bool canMoveDown;
bool canMoveLeft;
int depth;
};
class LayerModel::Private {
public:
Private(LayerModel* qq)
: q(qq)
, nodeModel(new KisNodeModel(qq))
, aboutToRemoveRoots(false)
, canvas(0)
, nodeManager(0)
, image(0)
, activeNode(0)
, declarativeEngine(0)
, thumbProvider(0)
, updateActiveLayerWithNewFilterConfigTimer(new QTimer(qq))
, imageChangedTimer(new QTimer(qq))
{
QList<KisFilterSP> tmpFilters = KisFilterRegistry::instance()->values();
Q_FOREACH (const KisFilterSP& filter, tmpFilters)
{
filters[filter.data()->id()] = filter.data();
}
updateActiveLayerWithNewFilterConfigTimer->setInterval(0);
updateActiveLayerWithNewFilterConfigTimer->setSingleShot(true);
connect(updateActiveLayerWithNewFilterConfigTimer, SIGNAL(timeout()), qq, SLOT(updateActiveLayerWithNewFilterConfig()));
imageChangedTimer->setInterval(250);
imageChangedTimer->setSingleShot(true);
connect(imageChangedTimer, SIGNAL(timeout()), qq, SLOT(imageHasChanged()));
}
LayerModel* q;
QList<KisNodeSP> layers;
QHash<const KisNode*, LayerModelMetaInfo> layerMeta;
KisNodeModel* nodeModel;
bool aboutToRemoveRoots;
KisViewManager* view;
KisCanvas2* canvas;
QPointer<KisNodeManager> nodeManager;
KisImageWSP image;
KisNodeSP activeNode;
QQmlEngine* declarativeEngine;
LayerThumbProvider* thumbProvider;
QHash<QString, const KisFilter*> filters;
KisFilterConfigurationSP newConfig;
QTimer* updateActiveLayerWithNewFilterConfigTimer;
QTimer* imageChangedTimer;
static int counter()
{
static int count = 0;
return count++;
}
static QStringList layerClassNames()
{
QStringList list;
list << "KisGroupLayer";
list << "KisPaintLayer";
list << "KisFilterMask";
list << "KisAdjustmentLayer";
return list;
}
int deepChildCount(KisNodeSP layer)
{
quint32 childCount = layer->childCount();
QList<KisNodeSP> children = layer->childNodes(layerClassNames(), KoProperties());
for(quint32 i = 0; i < childCount; ++i)
childCount += deepChildCount(children.at(i));
return childCount;
}
void rebuildLayerList(KisNodeSP layer = 0)
{
bool refreshingFromRoot = false;
if (!image)
{
layers.clear();
return;
}
if (layer == 0)
{
refreshingFromRoot = true;
layers.clear();
layer = image->rootLayer();
}
// implementation node: The root node is not a visible node, and so
// is never added to the list of layers
QList<KisNodeSP> children = layer->childNodes(layerClassNames(), KoProperties());
if (children.count() == 0)
return;
for(quint32 i = children.count(); i > 0; --i)
{
layers << children.at(i-1);
rebuildLayerList(children.at(i-1));
}
if (refreshingFromRoot)
refreshLayerMovementAbilities();
}
void refreshLayerMovementAbilities()
{
layerMeta.clear();
if (layers.count() == 0)
return;
for(int i = 0; i < layers.count(); ++i)
{
const KisNodeSP layer = layers.at(i);
LayerModelMetaInfo ability;
if (i > 0)
ability.canMoveUp = true;
if (i < layers.count() - 1)
ability.canMoveDown = true;
KisNodeSP parent = layer;
while(parent)
{
++ability.depth;
parent = parent->parent();
}
if (ability.depth > 1)
ability.canMoveLeft = true;
if (i < layers.count() - 1 && qobject_cast<const KisGroupLayer*>(layers.at(i + 1).constData()))
ability.canMoveRight = true;
layerMeta[layer] = ability;
}
}
};
LayerModel::LayerModel(QObject* parent)
: QAbstractListModel(parent)
, d(new Private(this))
{
- QHash<int, QByteArray> roles;
- roles[IconRole] = "icon";
- roles[NameRole] = "name";
- roles[ActiveLayerRole] = "activeLayer";
- roles[OpacityRole] = "opacity";
- roles[PercentOpacityRole] = "percentOpacity";
- roles[VisibleRole] = "visible";
- roles[LockedRole] = "locked";
- roles[CompositeDetailsRole] = "compositeDetails";
- roles[FilterRole] = "filter";
- roles[ChildCountRole] = "childCount";
- roles[DeepChildCountRole] = "deepChildCount";
- roles[DepthRole] = "depth";
- roles[PreviousItemDepthRole] = "previousItemDepth";
- roles[NextItemDepthRole] = "nextItemDepth";
- roles[CanMoveDownRole] = "canMoveDown";
- roles[CanMoveLeftRole] = "canMoveLeft";
- roles[CanMoveRightRole] = "canMoveRight";
- roles[CanMoveUpRole] = "canMoveUp";
- setRoleNames(roles);
connect(d->nodeModel, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)),
this, SLOT(source_rowsAboutToBeInserted(QModelIndex, int, int)));
connect(d->nodeModel, SIGNAL(rowsInserted(QModelIndex, int, int)),
this, SLOT(source_rowsInserted(QModelIndex, int, int)));
connect(d->nodeModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)),
this, SLOT(source_rowsAboutToBeRemoved(QModelIndex, int, int)));
connect(d->nodeModel, SIGNAL(rowsRemoved(QModelIndex, int, int)),
this, SLOT(source_rowsRemoved(QModelIndex, int, int)));
connect(d->nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(source_dataChanged(QModelIndex,QModelIndex)));
connect(d->nodeModel, SIGNAL(modelReset()),
this, SLOT(source_modelReset()));
connect(d->nodeModel, SIGNAL(layoutAboutToBeChanged()), this, SIGNAL(layoutAboutToBeChanged()));
connect(d->nodeModel, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged()));
}
LayerModel::~LayerModel()
{
delete d;
}
+QHash<int, QByteArray> LayerModel::roleNames() const
+{
+ QHash<int, QByteArray> roles;
+ roles[IconRole] = "icon";
+ roles[NameRole] = "name";
+ roles[ActiveLayerRole] = "activeLayer";
+ roles[OpacityRole] = "opacity";
+ roles[PercentOpacityRole] = "percentOpacity";
+ roles[VisibleRole] = "visible";
+ roles[LockedRole] = "locked";
+ roles[CompositeDetailsRole] = "compositeDetails";
+ roles[FilterRole] = "filter";
+ roles[ChildCountRole] = "childCount";
+ roles[DeepChildCountRole] = "deepChildCount";
+ roles[DepthRole] = "depth";
+ roles[PreviousItemDepthRole] = "previousItemDepth";
+ roles[NextItemDepthRole] = "nextItemDepth";
+ roles[CanMoveDownRole] = "canMoveDown";
+ roles[CanMoveLeftRole] = "canMoveLeft";
+ roles[CanMoveRightRole] = "canMoveRight";
+ roles[CanMoveUpRole] = "canMoveUp";
+
+ return roles;
+}
+
QObject* LayerModel::view() const
{
return d->view;
}
void LayerModel::setView(QObject *newView)
{
KisViewManager* view = qobject_cast<KisViewManager*>(newView);
// This has to happen very early, and we will be filling it back up again soon anyway...
if (d->canvas) {
d->canvas->disconnectCanvasObserver(this);
disconnect(d->image, 0, this, 0);
disconnect(d->nodeManager, 0, this, 0);
disconnect(d->nodeModel, 0, d->nodeManager, 0);
disconnect(d->nodeModel, SIGNAL(nodeActivated(KisNodeSP)), this, SLOT(currentNodeChanged(KisNodeSP)));
d->image = 0;
d->nodeManager = 0;
d->layers.clear();
d->activeNode.clear();
d->canvas = 0;
d->nodeModel->setDummiesFacade(0, 0, 0, 0, 0);
}
d->view = view;
if (!d->view) {
return;
}
d->canvas = view->canvasBase();
d->thumbProvider = new LayerThumbProvider();
d->thumbProvider->setLayerModel(this);
d->thumbProvider->setLayerID(Private::counter());
// QT5TODO: results in a crash
d->declarativeEngine->addImageProvider(QString("layerthumb%1").arg(d->thumbProvider->layerID()), d->thumbProvider);
if (d->canvas) {
d->image = d->canvas->imageView()->image();
d->nodeManager = d->canvas->viewManager()->nodeManager();
KisDummiesFacadeBase *kritaDummiesFacade = dynamic_cast<KisDummiesFacadeBase*>(d->canvas->imageView()->document()->shapeController());
KisShapeController *shapeController = dynamic_cast<KisShapeController*>(d->canvas->imageView()->document()->shapeController());
d->nodeModel->setDummiesFacade(kritaDummiesFacade, d->image, shapeController, d->nodeManager->nodeSelectionAdapter(), d->nodeManager->nodeInsertionAdapter());
connect(d->image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted()));
connect(d->image, SIGNAL(sigNodeChanged(KisNodeSP)), SLOT(nodeChanged(KisNodeSP)));
connect(d->image, SIGNAL(sigImageUpdated(QRect)), SLOT(imageChanged()));
connect(d->image, SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(aboutToRemoveNode(KisNodeSP)));
// cold start
currentNodeChanged(d->nodeManager->activeNode());
// Connection KisNodeManager -> KisLayerBox
connect(d->nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(currentNodeChanged(KisNodeSP)));
d->rebuildLayerList();
- reset();
+ beginResetModel();
+ endResetModel();
}
}
QObject* LayerModel::engine() const
{
return d->declarativeEngine;
}
void LayerModel::setEngine(QObject* newEngine)
{
d->declarativeEngine = qobject_cast<QQmlEngine*>(newEngine);
emit engineChanged();
}
QString LayerModel::fullImageThumbUrl() const
{
return QString("image://layerthumb%1/fullimage/%2").arg(d->thumbProvider->layerID()).arg(QDateTime::currentMSecsSinceEpoch());
}
void LayerModel::currentNodeChanged(KisNodeSP newActiveNode)
{
if (!d->activeNode.isNull()) {
QModelIndex oldIndex = d->nodeModel->indexFromNode(d->activeNode);
source_dataChanged(oldIndex, oldIndex);
}
d->activeNode = newActiveNode;
emitActiveChanges();
if (!d->activeNode.isNull()) {
QModelIndex oldIndex = d->nodeModel->indexFromNode(d->activeNode);
source_dataChanged(oldIndex, oldIndex);
}
}
QVariant LayerModel::data(const QModelIndex& index, int role) const
{
QVariant data;
if (index.isValid()) {
index.internalPointer();
KisNodeSP node = d->layers.at(index.row());
if (node.isNull())
return data;
KisNodeSP parent;
switch(role)
{
case IconRole:
if (dynamic_cast<const KisGroupLayer*>(node.constData()))
data = QLatin1String("../images/svg/icon-layer_group-black.svg");
else if (dynamic_cast<const KisFilterMask*>(node.constData()))
data = QLatin1String("../images/svg/icon-layer_filter-black.svg");
else if (dynamic_cast<const KisAdjustmentLayer*>(node.constData()))
data = QLatin1String("../images/svg/icon-layer_filter-black.svg");
else
// We add the currentMSecsSinceEpoch to ensure we force an update (even with cache turned
// off, we still apparently get some caching behaviour on delegates in QML)
data = QString("image://layerthumb%1/%2/%3").arg(d->thumbProvider->layerID()).arg(index.row()).arg(QDateTime::currentMSecsSinceEpoch());
break;
case NameRole:
data = node->name();
break;
case ActiveLayerRole:
data = (node == d->activeNode);
break;
case OpacityRole:
data = node->opacity();
break;
case PercentOpacityRole:
data = node->percentOpacity();
break;
case VisibleRole:
data = node->visible();
break;
case LockedRole:
data = node->userLocked();
break;
case CompositeDetailsRole:
// composite op goes here...
if (node->compositeOp())
data = node->compositeOp()->description();
break;
case FilterRole:
break;
case ChildCountRole:
data = node->childNodes(d->layerClassNames(), KoProperties()).count();
break;
case DeepChildCountRole:
data = d->deepChildCount(d->layers.at(index.row()));
break;
case DepthRole:
data = d->layerMeta[node.data()].depth;
break;
case PreviousItemDepthRole:
if (index.row() == 0)
data = -1;
else
data = d->layerMeta[d->layers[index.row() - 1].data()].depth;
break;
case NextItemDepthRole:
if (index.row() == d->layers.count() - 1)
data = -1;
else
data = d->layerMeta[d->layers[index.row() + 1].data()].depth;
break;
case CanMoveDownRole:
data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveDown;
break;
case CanMoveLeftRole:
data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveLeft;
break;
case CanMoveRightRole:
data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveRight;
break;
case CanMoveUpRole:
data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveUp;
break;
default:
break;
}
}
return data;
}
int LayerModel::rowCount(const QModelIndex& parent) const
{
if ( parent.isValid() ) {
return 0;
}
return d->layers.count();
}
QVariant LayerModel::headerData(int section, Qt::Orientation orientation, int role) const
{
return QAbstractItemModel::headerData(section, orientation, role);
}
void LayerModel::setActive(int index)
{
if (index > -1 && index < d->layers.count()) {
KisNodeSP newNode = d->layers.at(index);
d->nodeManager->slotUiActivatedNode(newNode);
currentNodeChanged(newNode);
}
}
void LayerModel::moveUp()
{
KisNodeSP node = d->nodeManager->activeNode();
KisNodeSP parent = node->parent();
KisNodeSP grandParent = parent->parent();
if (!d->nodeManager->activeNode()->nextSibling()) {
//dbgKrita << "Active node apparently has no next sibling, however that has happened...";
if (!grandParent)
return;
//dbgKrita << "Node has grandparent";
if (!grandParent->parent() && node->inherits("KisMask"))
return;
//dbgKrita << "Node isn't a mask";
d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent) + 1);
}
else {
//dbgKrita << "Move node directly";
d->nodeManager->lowerNode();
}
}
void LayerModel::moveDown()
{
KisNodeSP node = d->nodeManager->activeNode();
KisNodeSP parent = node->parent();
KisNodeSP grandParent = parent->parent();
if (!d->nodeManager->activeNode()->prevSibling()) {
//dbgKrita << "Active node apparently has no previous sibling, however that has happened...";
if (!grandParent)
return;
//dbgKrita << "Node has grandparent";
if (!grandParent->parent() && node->inherits("KisMask"))
return;
//dbgKrita << "Node isn't a mask";
d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent));
} else
{
//dbgKrita << "Move node directly";
d->nodeManager->raiseNode();
}
}
void LayerModel::moveLeft()
{
KisNodeSP node = d->nodeManager->activeNode();
KisNodeSP parent = node->parent();
KisNodeSP grandParent = parent->parent();
quint16 nodeIndex = parent->index(node);
if (!grandParent)
return;
if (!grandParent->parent() && node->inherits("KisMask"))
return;
if (nodeIndex <= parent->childCount() / 2) {
d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent));
}
else {
d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent) + 1);
}
}
void LayerModel::moveRight()
{
KisNodeSP node = d->nodeManager->activeNode();
KisNodeSP parent = d->nodeManager->activeNode()->parent();
KisNodeSP newParent;
int nodeIndex = parent->index(node);
int indexAbove = nodeIndex + 1;
int indexBelow = nodeIndex - 1;
if (parent->at(indexBelow) && parent->at(indexBelow)->allowAsChild(node)) {
newParent = parent->at(indexBelow);
d->nodeManager->moveNodeAt(node, newParent, newParent->childCount());
}
else if (parent->at(indexAbove) && parent->at(indexAbove)->allowAsChild(node)) {
newParent = parent->at(indexAbove);
d->nodeManager->moveNodeAt(node, newParent, 0);
}
else {
return;
}
}
void LayerModel::clear()
{
d->canvas->viewManager()->selectionManager()->clear();
}
void LayerModel::clone()
{
d->nodeManager->duplicateActiveNode();
}
void LayerModel::setLocked(int index, bool newLocked)
{
if (index > -1 && index < d->layers.count()) {
if(d->layers[index]->userLocked() == newLocked)
return;
d->layers[index]->setUserLocked(newLocked);
QModelIndex idx = createIndex(index, 0);
dataChanged(idx, idx);
}
}
void LayerModel::setOpacity(int index, float newOpacity)
{
if (index > -1 && index < d->layers.count()) {
if(qFuzzyCompare(d->layers[index]->opacity() + 1, newOpacity + 1))
return;
d->layers[index]->setOpacity(newOpacity);
d->layers[index]->setDirty();
QModelIndex idx = createIndex(index, 0);
dataChanged(idx, idx);
}
}
void LayerModel::setVisible(int index, bool newVisible)
{
if (index > -1 && index < d->layers.count()) {
KisBaseNode::PropertyList props = d->layers[index]->sectionModelProperties();
if(props[0].state == newVisible)
return;
KisBaseNode::Property prop = props[0];
prop.state = newVisible;
props[0] = prop;
d->nodeModel->setData( d->nodeModel->indexFromNode(d->layers[index]), QVariant::fromValue<KisBaseNode::PropertyList>(props), KisNodeModel::PropertiesRole );
d->layers[index]->setDirty(d->layers[index]->extent());
QModelIndex idx = createIndex(index, 0);
dataChanged(idx, idx);
}
}
QImage LayerModel::layerThumbnail(QString layerID) const
{
// So, yeah, this is a complete cheatery hack. However, it ensures
// we actually get updates when we want them (every time the image is supposed
// to be changed). Had hoped we could avoid it, but apparently not.
int index = layerID.section(QChar('/'), 0, 0).toInt();
QImage thumb;
if (index > -1 && index < d->layers.count()) {
if (d->thumbProvider)
thumb = d->layers[index]->createThumbnail(120, 120);
}
return thumb;
}
void LayerModel::deleteCurrentLayer()
{
d->activeNode.clear();
d->nodeManager->removeNode();
}
void LayerModel::deleteLayer(int index)
{
if (index > -1 && index < d->layers.count()) {
if (d->activeNode == d->layers.at(index))
d->activeNode.clear();
d->nodeManager->slotUiActivatedNode(d->layers.at(index));
d->nodeManager->removeNode();
d->rebuildLayerList();
- reset();
+ beginResetModel();
+ endResetModel();
}
}
void LayerModel::addLayer(int layerType)
{
switch(layerType) {
case 0:
d->nodeManager->createNode("KisPaintLayer");
break;
case 1:
d->nodeManager->createNode("KisGroupLayer");
break;
case 2:
d->nodeManager->createNode("KisFilterMask", true);
break;
default:
break;
}
}
void LayerModel::source_rowsAboutToBeInserted(QModelIndex /*p*/, int /*from*/, int /*to*/)
{
beginResetModel();
}
void LayerModel::source_rowsInserted(QModelIndex /*p*/, int, int)
{
d->rebuildLayerList();
emit countChanged();
endResetModel();
}
void LayerModel::source_rowsAboutToBeRemoved(QModelIndex /*p*/, int /*from*/, int /*to*/)
{
beginResetModel();
}
void LayerModel::source_rowsRemoved(QModelIndex, int, int)
{
d->rebuildLayerList();
emit countChanged();
endResetModel();
}
void LayerModel::source_dataChanged(QModelIndex /*tl*/, QModelIndex /*br*/)
{
QModelIndex top = createIndex(0, 0);
QModelIndex bottom = createIndex(d->layers.count() - 1, 0);
dataChanged(top, bottom);
}
void LayerModel::source_modelReset()
{
beginResetModel();
d->rebuildLayerList();
d->activeNode.clear();
if (d->layers.count() > 0)
{
d->nodeManager->slotUiActivatedNode(d->layers.at(0));
currentNodeChanged(d->layers.at(0));
}
emit countChanged();
endResetModel();
}
void LayerModel::notifyImageDeleted()
{
}
void LayerModel::nodeChanged(KisNodeSP node)
{
QModelIndex index = createIndex(d->layers.indexOf(node), 0);
dataChanged(index, index);
}
void LayerModel::imageChanged()
{
d->imageChangedTimer->start();
}
void LayerModel::imageHasChanged()
{
QModelIndex top = createIndex(0, 0);
QModelIndex bottom = createIndex(d->layers.count() - 1, 0);
dataChanged(top, bottom);
}
void LayerModel::aboutToRemoveNode(KisNodeSP node)
{
Q_UNUSED(node)
QTimer::singleShot(0, this, SLOT(source_modelReset()));
}
void LayerModel::emitActiveChanges()
{
emit activeFilterConfigChanged();
emit activeNameChanged();
emit activeTypeChanged();
emit activeCompositeOpChanged();
emit activeOpacityChanged();
emit activeVisibleChanged();
emit activeLockedChanged();
emit activeRChannelActiveChanged();
emit activeGChannelActiveChanged();
emit activeBChannelActiveChanged();
emit activeAChannelActiveChanged();
emit activeRChannelLockedChanged();
emit activeGChannelLockedChanged();
emit activeBChannelLockedChanged();
emit activeAChannelLockedChanged();
}
QString LayerModel::activeName() const
{
if (d->activeNode.isNull())
return QString();
return d->activeNode->name();
}
void LayerModel::setActiveName(QString newName)
{
if (d->activeNode.isNull())
return;
d->activeNode->setName(newName);
emit activeNameChanged();
}
QString LayerModel::activeType() const
{
return d->activeNode->metaObject()->className();
}
int LayerModel::activeCompositeOp() const
{
if (d->activeNode.isNull())
return 0;
KoID entry(d->activeNode->compositeOp()->id());
QModelIndex idx = KisCompositeOpListModel::sharedInstance()->indexOf(entry);
if (idx.isValid())
return idx.row();
return 0;
}
void LayerModel::setActiveCompositeOp(int newOp)
{
if (d->activeNode.isNull())
return;
KoID entry;
if (KisCompositeOpListModel::sharedInstance()->entryAt(entry, KisCompositeOpListModel::sharedInstance()->index(newOp)))
{
d->activeNode->setCompositeOpId(entry.id());
d->activeNode->setDirty();
emit activeCompositeOpChanged();
}
}
int LayerModel::activeOpacity() const
{
if (d->activeNode.isNull())
return 0;
return d->activeNode->opacity();
}
void LayerModel::setActiveOpacity(int newOpacity)
{
d->activeNode->setOpacity(newOpacity);
d->activeNode->setDirty();
emit activeOpacityChanged();
}
bool LayerModel::activeVisible() const
{ if (d->activeNode.isNull())
return false;
return d->activeNode->visible();
}
void LayerModel::setActiveVisibile(bool newVisible)
{
if (d->activeNode.isNull())
return;
setVisible(d->layers.indexOf(d->activeNode), newVisible);
emit activeVisibleChanged();
}
bool LayerModel::activeLocked() const
{
if (d->activeNode.isNull())
return false;
return d->activeNode->userLocked();
}
void LayerModel::setActiveLocked(bool newLocked)
{
if (d->activeNode.isNull())
return;
d->activeNode->setUserLocked(newLocked);
emit activeLockedChanged();
}
bool LayerModel::activeAChannelActive() const
{
KisLayer* layer = qobject_cast<KisLayer*>(d->activeNode.data());
bool state = false;
if (layer)
state = !layer->alphaChannelDisabled();
return state;
}
void LayerModel::setActiveAChannelActive(bool newActive)
{
KisLayer* layer = qobject_cast<KisLayer*>(d->activeNode.data());
if (layer)
{
layer->disableAlphaChannel(!newActive);
layer->setDirty();
emit activeAChannelActiveChanged();
}
}
bool LayerModel::activeAChannelLocked() const
{
KisPaintLayer* layer = qobject_cast<KisPaintLayer*>(d->activeNode.data());
bool state = false;
if (layer)
state = layer->alphaLocked();
return state;
}
void LayerModel::setActiveAChannelLocked(bool newLocked)
{
KisPaintLayer* layer = qobject_cast<KisPaintLayer*>(d->activeNode.data());
if (layer)
{
layer->setAlphaLocked(newLocked);
emit activeAChannelLockedChanged();
}
}
bool getActiveChannel(KisNodeSP node, int channelIndex)
{
KisLayer* layer = qobject_cast<KisLayer*>(node.data());
bool flag = false;
if (layer)
{
QBitArray flags = layer->channelFlags();
if (channelIndex < flags.size()) {
flag = flags[channelIndex];
}
}
return flag;
}
bool getLockedChannel(KisNodeSP node, int channelIndex)
{
KisPaintLayer* layer = qobject_cast<KisPaintLayer*>(node.data());
bool flag = false;
if (layer) {
QBitArray flags = layer->channelLockFlags();
flags = flags.isEmpty() ? layer->colorSpace()->channelFlags(true, true) : flags;
flag = flags[channelIndex];
}
return flag;
}
void setChannelActive(KisNodeSP node, int channelIndex, bool newActive)
{
KisLayer* layer = qobject_cast<KisLayer*>(node.data());
if (layer) {
QBitArray flags = layer->channelFlags();
flags.setBit(channelIndex, newActive);
layer->setChannelFlags(flags);
layer->setDirty();
}
}
void setChannelLocked(KisNodeSP node, int channelIndex, bool newLocked)
{
KisPaintLayer* layer = qobject_cast<KisPaintLayer*>(node.data());
if (layer) {
QBitArray flags = layer->channelLockFlags();
flags = flags.isEmpty() ? layer->colorSpace()->channelFlags(true, true) : flags;
flags.setBit(channelIndex, newLocked);
layer->setChannelLockFlags(flags);
}
}
bool LayerModel::activeBChannelActive() const
{
return getActiveChannel(d->activeNode, 2);
}
void LayerModel::setActiveBChannelActive(bool newActive)
{
setChannelActive(d->activeNode, 2, newActive);
emit activeBChannelActiveChanged();
}
bool LayerModel::activeBChannelLocked() const
{
return getLockedChannel(d->activeNode, 2);
}
void LayerModel::setActiveBChannelLocked(bool newLocked)
{
setChannelLocked(d->activeNode, 2, newLocked);
emit activeBChannelLockedChanged();
}
bool LayerModel::activeGChannelActive() const
{
return getActiveChannel(d->activeNode, 1);
}
void LayerModel::setActiveGChannelActive(bool newActive)
{
setChannelActive(d->activeNode, 1, newActive);
emit activeGChannelActiveChanged();
}
bool LayerModel::activeGChannelLocked() const
{
return getLockedChannel(d->activeNode, 1);
}
void LayerModel::setActiveGChannelLocked(bool newLocked)
{
setChannelLocked(d->activeNode, 1, newLocked);
emit activeGChannelLockedChanged();
}
bool LayerModel::activeRChannelActive() const
{
return getActiveChannel(d->activeNode, 0);
}
void LayerModel::setActiveRChannelActive(bool newActive)
{
setChannelActive(d->activeNode, 0, newActive);
emit activeRChannelActiveChanged();
}
bool LayerModel::activeRChannelLocked() const
{
return getLockedChannel(d->activeNode, 0);
}
void LayerModel::setActiveRChannelLocked(bool newLocked)
{
setChannelLocked(d->activeNode, 0, newLocked);
emit activeRChannelLockedChanged();
}
QObject* LayerModel::activeFilterConfig() const
{
QMap<QString, QVariant> props;
QString filterId;
KisFilterMask* filterMask = qobject_cast<KisFilterMask*>(d->activeNode.data());
if (filterMask) {
props = filterMask->filter()->getProperties();
filterId = filterMask->filter()->name();
}
else {
KisAdjustmentLayer* adjustmentLayer = qobject_cast<KisAdjustmentLayer*>(d->activeNode.data());
if (adjustmentLayer)
{
props = adjustmentLayer->filter()->getProperties();
filterId = adjustmentLayer->filter()->name();
}
}
PropertyContainer* config = new PropertyContainer(filterId, 0);
QMap<QString, QVariant>::const_iterator i;
for(i = props.constBegin(); i != props.constEnd(); ++i) {
config->setProperty(i.key().toLatin1(), i.value());
//dbgKrita << "Getting active config..." << i.key() << i.value();
}
return config;
}
void LayerModel::setActiveFilterConfig(QObject* newConfig)
{
if (d->activeNode.isNull())
return;
PropertyContainer* config = qobject_cast<PropertyContainer*>(newConfig);
if (!config)
return;
//dbgKrita << "Attempting to set new config" << config->name();
KisFilterConfigurationSP realConfig = d->filters.value(config->name())->factoryConfiguration();
QMap<QString, QVariant>::const_iterator i;
for(i = realConfig->getProperties().constBegin(); i != realConfig->getProperties().constEnd(); ++i)
{
realConfig->setProperty(i.key(), config->property(i.key().toLatin1()));
//dbgKrita << "Creating config..." << i.key() << i.value();
}
// The following code causes sporadic crashes, and disabling causes leaks. So, leaks it must be, for now.
// The cause is the lack of a smart pointer interface for passing filter configs around
// Must be remedied, but for now...
// if (d->newConfig)
// delete(d->newConfig);
d->newConfig = realConfig;
//d->updateActiveLayerWithNewFilterConfigTimer->start();
updateActiveLayerWithNewFilterConfig();
}
void LayerModel::updateActiveLayerWithNewFilterConfig()
{
if (!d->newConfig)
return;
//dbgKrita << "Setting new config..." << d->newConfig->name();
KisFilterMask* filterMask = qobject_cast<KisFilterMask*>(d->activeNode.data());
if (filterMask)
{
//dbgKrita << "Filter mask";
if (filterMask->filter() == d->newConfig)
return;
//dbgKrita << "Setting filter mask";
filterMask->setFilter(d->newConfig);
}
else
{
KisAdjustmentLayer* adjustmentLayer = qobject_cast<KisAdjustmentLayer*>(d->activeNode.data());
if (adjustmentLayer)
{
//dbgKrita << "Adjustment layer";
if (adjustmentLayer->filter() == d->newConfig)
return;
//dbgKrita << "Setting filter on adjustment layer";
adjustmentLayer->setFilter(d->newConfig);
}
else
{
//dbgKrita << "UNKNOWN, BAIL OUT!";
}
}
d->newConfig = 0;
d->activeNode->setDirty(d->activeNode->extent());
d->image->setModified();
QTimer::singleShot(100, this, SIGNAL(activeFilterConfigChanged()));
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/LayerModel.h b/libs/libqml/plugins/kritasketchplugin/models/LayerModel.h
index d801e5e690..e774f46838 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/LayerModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/LayerModel.h
@@ -1,172 +1,172 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 LAYERMODEL_H
#define LAYERMODEL_H
#include <QAbstractListModel>
#include <QImage>
#include <kis_types.h>
class LayerModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QObject* view READ view WRITE setView NOTIFY viewChanged)
Q_PROPERTY(QObject* engine READ engine WRITE setEngine NOTIFY engineChanged)
// This might seem a slightly odd position, but think of it as the thumbnail of all the currently visible layers merged down
Q_PROPERTY(QString fullImageThumbUrl READ fullImageThumbUrl NOTIFY viewChanged);
Q_PROPERTY(int count READ rowCount NOTIFY countChanged);
Q_PROPERTY(QString activeName READ activeName WRITE setActiveName NOTIFY activeNameChanged);
Q_PROPERTY(QString activeType READ activeType NOTIFY activeTypeChanged);
Q_PROPERTY(int activeCompositeOp READ activeCompositeOp WRITE setActiveCompositeOp NOTIFY activeCompositeOpChanged);
Q_PROPERTY(int activeOpacity READ activeOpacity WRITE setActiveOpacity NOTIFY activeOpacityChanged);
Q_PROPERTY(bool activeVisible READ activeVisible WRITE setActiveVisibile NOTIFY activeVisibleChanged);
Q_PROPERTY(bool activeLocked READ activeLocked WRITE setActiveLocked NOTIFY activeLockedChanged);
Q_PROPERTY(bool activeRChannelActive READ activeRChannelActive WRITE setActiveRChannelActive NOTIFY activeRChannelActiveChanged);
Q_PROPERTY(bool activeGChannelActive READ activeGChannelActive WRITE setActiveGChannelActive NOTIFY activeGChannelActiveChanged);
Q_PROPERTY(bool activeBChannelActive READ activeBChannelActive WRITE setActiveBChannelActive NOTIFY activeBChannelActiveChanged);
Q_PROPERTY(bool activeAChannelActive READ activeAChannelActive WRITE setActiveAChannelActive NOTIFY activeAChannelActiveChanged);
Q_PROPERTY(bool activeRChannelLocked READ activeRChannelLocked WRITE setActiveRChannelLocked NOTIFY activeRChannelLockedChanged);
Q_PROPERTY(bool activeGChannelLocked READ activeGChannelLocked WRITE setActiveGChannelLocked NOTIFY activeGChannelLockedChanged);
Q_PROPERTY(bool activeBChannelLocked READ activeBChannelLocked WRITE setActiveBChannelLocked NOTIFY activeBChannelLockedChanged);
Q_PROPERTY(bool activeAChannelLocked READ activeAChannelLocked WRITE setActiveAChannelLocked NOTIFY activeAChannelLockedChanged);
Q_PROPERTY(QObject* activeFilterConfig READ activeFilterConfig WRITE setActiveFilterConfig NOTIFY activeFilterConfigChanged);
public:
enum LayerRoles {
IconRole = Qt::UserRole + 1,
NameRole,
ActiveLayerRole,
OpacityRole,
PercentOpacityRole,
VisibleRole,
LockedRole,
CompositeDetailsRole,
FilterRole,
ChildCountRole,
DeepChildCountRole,
DepthRole,
PreviousItemDepthRole,
NextItemDepthRole,
CanMoveLeftRole,
CanMoveRightRole,
CanMoveUpRole,
CanMoveDownRole
};
explicit LayerModel(QObject* parent = 0);
virtual ~LayerModel();
-
+ QHash<int, QByteArray> roleNames() const;
QObject* view() const;
void setView(QObject* newView);
QObject* engine() const;
void setEngine(QObject* newEngine);
QString fullImageThumbUrl() const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
Q_INVOKABLE virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Q_INVOKABLE void setActive(int index);
Q_INVOKABLE void moveUp();
Q_INVOKABLE void moveDown();
Q_INVOKABLE void moveLeft();
Q_INVOKABLE void moveRight();
void emitActiveChanges();
Q_INVOKABLE void setOpacity(int index, float newOpacity);
Q_INVOKABLE void setVisible(int index, bool newVisible);
Q_INVOKABLE void setLocked(int index, bool newLocked);
QImage layerThumbnail(QString layerID) const;
Q_INVOKABLE void clear();
Q_INVOKABLE void clone();
Q_INVOKABLE void deleteCurrentLayer();
Q_INVOKABLE void deleteLayer(int index);
Q_INVOKABLE void addLayer(int layerType);
QString activeName() const;
void setActiveName(QString newName);
QString activeType() const;
int activeCompositeOp() const;
void setActiveCompositeOp(int newOp);
int activeOpacity() const;
void setActiveOpacity(int newOpacity);
bool activeVisible() const;
void setActiveVisibile(bool newVisible);
bool activeLocked() const;
void setActiveLocked(bool newLocked);
bool activeRChannelActive() const;
void setActiveRChannelActive(bool newActive);
bool activeGChannelActive() const;
void setActiveGChannelActive(bool newActive);
bool activeBChannelActive() const;
void setActiveBChannelActive(bool newActive);
bool activeAChannelActive() const;
void setActiveAChannelActive(bool newActive);
bool activeRChannelLocked() const;
void setActiveRChannelLocked(bool newLocked);
bool activeGChannelLocked() const;
void setActiveGChannelLocked(bool newLocked);
bool activeBChannelLocked() const;
void setActiveBChannelLocked(bool newLocked);
bool activeAChannelLocked() const;
void setActiveAChannelLocked(bool newLocked);
QObject* activeFilterConfig() const;
void setActiveFilterConfig(QObject* newConfig);
Q_SIGNALS:
void viewChanged();
void engineChanged();
void countChanged();
void activeNameChanged();
void activeTypeChanged();
void activeCompositeOpChanged();
void activeOpacityChanged();
void activeVisibleChanged();
void activeLockedChanged();
void activeRChannelActiveChanged();
void activeGChannelActiveChanged();
void activeBChannelActiveChanged();
void activeAChannelActiveChanged();
void activeRChannelLockedChanged();
void activeGChannelLockedChanged();
void activeBChannelLockedChanged();
void activeAChannelLockedChanged();
void activeFilterConfigChanged();
private Q_SLOTS:
void source_rowsAboutToBeInserted(QModelIndex, int, int);
void source_rowsAboutToBeRemoved(QModelIndex, int, int);
void source_rowsInserted(QModelIndex, int, int);
void source_rowsRemoved(QModelIndex, int, int);
void source_dataChanged(QModelIndex, QModelIndex);
void source_modelReset();
void currentNodeChanged(KisNodeSP newActiveNode);
void notifyImageDeleted();
void nodeChanged(KisNodeSP node);
void imageChanged();
void imageHasChanged();
void aboutToRemoveNode(KisNodeSP node);
void updateActiveLayerWithNewFilterConfig();
private:
class Private;
Private* d;
};
#endif // LAYERMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp
index 59cedee2ac..90af9b254d 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.cpp
@@ -1,141 +1,147 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 "PaletteColorsModel.h"
#include <KisViewManager.h>
#include <kis_canvas_resource_provider.h>
#include <resources/KoColorSet.h>
class PaletteColorsModel::Private {
public:
Private()
: colorSet(0)
, view(0)
{}
KoColorSet* colorSet;
KisViewManager* view;
};
PaletteColorsModel::PaletteColorsModel(QObject *parent)
: QAbstractListModel(parent)
, d(new Private)
{
- QHash<int, QByteArray> roles;
- roles[ImageRole] = "image";
- roles[TextRole] = "text";
- setRoleNames(roles);
}
PaletteColorsModel::~PaletteColorsModel()
{
delete d;
}
+QHash<int, QByteArray> PaletteColorsModel::roleNames() const
+{
+ QHash<int, QByteArray> roles;
+ roles[ImageRole] = "image";
+ roles[TextRole] = "text";
+
+ return roles;
+}
+
int PaletteColorsModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
if (!d->colorSet)
return 0;
return d->colorSet->nColors();
}
QVariant PaletteColorsModel::data(const QModelIndex &index, int role) const
{
QVariant result;
QColor color;
if (index.isValid() && d->colorSet)
{
switch(role)
{
case ImageRole:
color = d->colorSet->getColorGlobal(index.row()).color.toQColor();
result = QString("image://color/%1,%2,%3,%4").arg(color.redF()).arg(color.greenF()).arg(color.blueF()).arg(color.alphaF());
break;
case TextRole:
result = d->colorSet->getColorGlobal(index.row()).name;
break;
default:
break;
}
}
return result;
}
QVariant PaletteColorsModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(orientation);
QVariant result;
if (section == 0)
{
switch(role)
{
case ImageRole:
result = QString("Thumbnail");
break;
case TextRole:
result = QString("Name");
break;
default:
break;
}
}
return result;
}
void PaletteColorsModel::setColorSet(QObject *newColorSet)
{
d->colorSet = qobject_cast<KoColorSet*>(newColorSet);
- reset();
+ beginResetModel();
+ endResetModel();
emit colorSetChanged();
}
QObject* PaletteColorsModel::colorSet() const
{
return d->colorSet;
}
QObject* PaletteColorsModel::view() const
{
return d->view;
}
void PaletteColorsModel::setView(QObject* newView)
{
d->view = qobject_cast<KisViewManager*>( newView );
emit viewChanged();
}
void PaletteColorsModel::activateColor(int index, bool setBackgroundColor)
{
if ( !d->view )
return;
if (index >= 0 && index < (int)d->colorSet->nColors())
{
if (setBackgroundColor)
d->view->resourceProvider()->setBGColor(d->colorSet->getColorGlobal(index).color);
else
d->view->resourceProvider()->setFGColor( d->colorSet->getColorGlobal(index).color);
emit colorChanged(d->colorSet->getColorGlobal(index).color.toQColor(), setBackgroundColor);
}
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.h b/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.h
index b3a99c5060..cfd53e5504 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/PaletteColorsModel.h
@@ -1,61 +1,61 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 PALETTECOLORSMODEL_H
#define PALETTECOLORSMODEL_H
#include <QAbstractListModel>
#include <QColor>
class PaletteColorsModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QObject* colorSet READ colorSet WRITE setColorSet NOTIFY colorSetChanged)
Q_PROPERTY(QObject* view READ view WRITE setView NOTIFY viewChanged)
public:
enum PaletteColorsRoles {
ImageRole = Qt::UserRole + 1,
TextRole
};
explicit PaletteColorsModel(QObject *parent = 0);
virtual ~PaletteColorsModel();
-
+ QHash<int, QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QObject* view() const;
void setView(QObject* newView);
QObject* colorSet() const;
void setColorSet(QObject* newColorSet);
Q_SIGNALS:
void colorChanged(QColor newColor, bool backgroundChanged);
void colorSetChanged();
void viewChanged();
public Q_SLOTS:
void activateColor(int index, bool setBackgroundColor);
private:
class Private;
Private* d;
};
#endif // PALETTECOLORSMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/PaletteModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/PaletteModel.cpp
index a0929908b5..cbb18f3af5 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/PaletteModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/PaletteModel.cpp
@@ -1,116 +1,121 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 "PaletteModel.h"
#include <resources/KoColorSet.h>
#include <KoResourceServerAdapter.h>
#include <KoResourceServerProvider.h>
#include <kis_resource_server_provider.h>
class PaletteModel::Private {
public:
Private(QObject* q)
: currentSet(0)
{
KoResourceServer<KoColorSet>* rServer = KoResourceServerProvider::instance()->paletteServer();
serverAdaptor = new KoResourceServerAdapter<KoColorSet>(rServer, q);
serverAdaptor->connectToResourceServer();
}
KoResourceServerAdapter<KoColorSet>* serverAdaptor;
KoColorSet* currentSet;
};
PaletteModel::PaletteModel(QObject *parent)
: QAbstractListModel(parent)
, d(new Private(this))
{
- QHash<int, QByteArray> roles;
- roles[ImageRole] = "image";
- roles[TextRole] = "text";
- setRoleNames(roles);
}
PaletteModel::~PaletteModel()
{
delete d;
}
+QHash<int, QByteArray> PaletteModel::roleNames() const
+{
+ QHash<int, QByteArray> roles;
+ roles[ImageRole] = "image";
+ roles[TextRole] = "text";
+
+ return roles;
+}
+
int PaletteModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return d->serverAdaptor->resources().count();
}
QVariant PaletteModel::data(const QModelIndex &index, int role) const
{
QVariant result;
if (index.isValid())
{
switch(role)
{
case ImageRole:
result = "../images/help-about.png";
break;
case TextRole:
result = d->serverAdaptor->resources().at(index.row())->name();
break;
default:
break;
}
}
return result;
}
QVariant PaletteModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(orientation);
QVariant result;
if (section == 0)
{
switch(role)
{
case ImageRole:
result = QString("Thumbnail");
break;
case TextRole:
result = QString("Name");
break;
default:
break;
}
}
return result;
}
void PaletteModel::itemActivated(int index)
{
QList<KoResource*> resources = d->serverAdaptor->resources();
if (index >= 0 && index < resources.count())
{
d->currentSet = dynamic_cast<KoColorSet*>(resources.at(index));
emit colorSetChanged();
}
}
QObject* PaletteModel::colorSet() const
{
return d->currentSet;
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/PaletteModel.h b/libs/libqml/plugins/kritasketchplugin/models/PaletteModel.h
index 05df5fbf00..db342afcd9 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/PaletteModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/PaletteModel.h
@@ -1,53 +1,53 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 PALETTEMODEL_H
#define PALETTEMODEL_H
#include <QAbstractListModel>
class PaletteModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QObject* colorSet READ colorSet NOTIFY colorSetChanged)
public:
enum PaletteRoles {
ImageRole = Qt::UserRole + 1,
TextRole
};
explicit PaletteModel(QObject *parent = 0);
virtual ~PaletteModel();
-
+ QHash<int, QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QObject* colorSet() const;
public Q_SLOTS:
void itemActivated(int index);
Q_SIGNALS:
void colorSetChanged();
private:
class Private;
Private* d;
};
#endif // PALETTEMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/PresetModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/PresetModel.cpp
index 3874ff7d57..6db3c2fd46 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/PresetModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/PresetModel.cpp
@@ -1,214 +1,219 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 "PresetModel.h"
#include <KoResourceServerAdapter.h>
#include <kis_resource_server_provider.h>
#include <KisViewManager.h>
#include <kis_canvas_resource_provider.h>
#include <kis_canvas2.h>
#include <kis_paintop_box.h>
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paintop_factory.h>
#include <brushengine/kis_paintop_registry.h>
#include <kis_node.h>
#include <kis_image.h>
class PresetModel::Private {
public:
Private()
: view(0)
{
rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
}
KisPaintOpPresetResourceServer * rserver;
QString currentPreset;
KisViewManager* view;
KisPaintOpPresetSP defaultPreset(const KoID& paintOp)
{
QString defaultName = paintOp.id() + ".kpp";
QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName);
KisPaintOpPresetSP preset = new KisPaintOpPreset(path);
if (!preset->load())
preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp);
Q_ASSERT(preset);
Q_ASSERT(preset->valid());
return preset;
}
void setCurrentPaintop(const KoID& paintop, KisPaintOpPresetSP preset)
{
preset = (!preset) ? defaultPreset(paintop) : preset;
Q_ASSERT(preset && preset->settings());
// handle the settings and expose it through a a simple QObject property
//m_optionWidget->setConfiguration(preset->settings());
#if 0
preset->settings()->setNode(view->resourceProvider()->currentNode());
#endif
KisPaintOpFactory* paintOp = KisPaintOpRegistry::instance()->get(paintop.id());
QString pixFilename = KoResourcePaths::findResource("kis_images", paintOp->pixmap());
view->resourceProvider()->setPaintOpPreset(preset);
}
};
PresetModel::PresetModel(QObject *parent)
: QAbstractListModel(parent)
, d(new Private)
{
- QHash<int, QByteArray> roles;
- roles[ImageRole] = "image";
- roles[TextRole] = "text";
- roles[NameRole] = "name";
- setRoleNames(roles);
}
PresetModel::~PresetModel()
{
delete d;
}
+QHash<int, QByteArray> PresetModel::roleNames() const
+{
+ QHash<int, QByteArray> roles;
+ roles[ImageRole] = "image";
+ roles[TextRole] = "text";
+ roles[NameRole] = "name";
+
+ return roles;
+}
+
int PresetModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return d->rserver->resources().count();
}
QVariant PresetModel::data(const QModelIndex &index, int role) const
{
QVariant result;
if (index.isValid())
{
switch(role)
{
case ImageRole:
result = QString("image://presetthumb/%1").arg(index.row());
break;
case TextRole:
result = d->rserver->resources().at(index.row())->name().replace(QLatin1String("_"), QLatin1String(" "));
break;
case NameRole:
result = d->rserver->resources().at(index.row())->name();
break;
default:
result = "";
break;
}
}
return result;
}
QVariant PresetModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(orientation);
QVariant result;
if (section == 0)
{
switch(role)
{
case ImageRole:
result = QString("Thumbnail");
break;
case TextRole:
result = QString("Name");
break;
default:
result = "";
break;
}
}
return result;
}
QObject* PresetModel::view() const
{
return d->view;
}
void PresetModel::setView(QObject* newView)
{
d->view = qobject_cast<KisViewManager*>( newView );
if (d->view && d->view->canvasBase()) {
connect(d->view->canvasBase()->resourceManager(), SIGNAL(canvasResourceChanged(int, const QVariant&)),
this, SLOT(resourceChanged(int, const QVariant&)));
}
emit viewChanged();
}
QString PresetModel::currentPreset() const
{
return d->currentPreset;
}
void PresetModel::setCurrentPreset(QString presetName)
{
activatePreset(nameToIndex(presetName));
// not emitting here, as that happens when the resource changes... this is more
// a polite request than an actual setter, due to the nature of the resource system.
}
int PresetModel::nameToIndex(QString presetName) const
{
int index = 0;
QList<KisPaintOpPresetSP> resources = d->rserver->resources();
for(int i = 0; i < resources.count(); ++i)
{
if (resources.at(i)->name() == presetName || resources.at(i)->name().replace(QLatin1String("_"), QLatin1String(" ")) == presetName)
{
index = i;
break;
}
}
return index;
}
void PresetModel::activatePreset(int index)
{
if ( !d->view )
return;
QList<KisPaintOpPresetSP> resources = d->rserver->resources();
if (index >= 0 && index < resources.count()) {
KisPaintOpPresetSP preset = resources.at( index );
d->setCurrentPaintop(preset->paintOp(), preset);
}
}
void PresetModel::resourceChanged(int /*key*/, const QVariant& /*v*/)
{
if (d->view)
{
KisPaintOpPresetSP preset = d->view->canvasBase()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
if (preset && d->currentPreset != preset->name())
{
d->currentPreset = preset->name();
emit currentPresetChanged();
}
}
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/PresetModel.h b/libs/libqml/plugins/kritasketchplugin/models/PresetModel.h
index a91fb016f6..34dd4320c5 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/PresetModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/PresetModel.h
@@ -1,64 +1,64 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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 PRESETMODEL_H
#define PRESETMODEL_H
#include <QAbstractListModel>
class PresetModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QObject* view READ view WRITE setView NOTIFY viewChanged)
Q_PROPERTY(QString currentPreset READ currentPreset WRITE setCurrentPreset NOTIFY currentPresetChanged)
public:
enum PresetRoles {
ImageRole = Qt::UserRole + 1,
TextRole,
NameRole
};
explicit PresetModel(QObject *parent = 0);
virtual ~PresetModel();
-
+ QHash<int, QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QObject* view() const;
void setView(QObject* newView);
QString currentPreset() const;
void setCurrentPreset(QString presetName);
Q_INVOKABLE int nameToIndex(QString presetName) const;
Q_SIGNALS:
void viewChanged();
void currentPresetChanged();
public Q_SLOTS:
void activatePreset(int index);
void resourceChanged(int key, const QVariant& v);
private:
class Private;
Private* d;
};
#endif // PRESETMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/RecentImagesModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/RecentImagesModel.cpp
index 1cbfcf3834..2911273c20 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/RecentImagesModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/RecentImagesModel.cpp
@@ -1,149 +1,154 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Boudewijn Rempt <boud@kogmbh.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "RecentImagesModel.h"
#include "RecentFileManager.h"
#include <QFile>
#include <QFileInfo>
#include <QDateTime>
class RecentImagesModel::Private {
public:
RecentFileManager *recentFileManager;
};
RecentImagesModel::RecentImagesModel(QObject *parent)
: QAbstractListModel(parent)
, d(new Private())
{
d->recentFileManager = 0;
+}
+
+RecentImagesModel::~RecentImagesModel()
+{
+ delete d;
+}
+QHash<int, QByteArray> RecentImagesModel::roleNames() const
+{
QHash<int, QByteArray> roles;
roles[ImageRole] = "image";
roles[TextRole] = "text";
roles[UrlRole] = "url";
roles[NameRole] = "name";
roles[DateRole] = "filedate";
- setRoleNames(roles);
-}
-RecentImagesModel::~RecentImagesModel()
-{
- delete d;
+ return roles;
}
int RecentImagesModel::rowCount(const QModelIndex &/*parent*/) const
{
if (d->recentFileManager)
return d->recentFileManager->size();
else
return 0;
}
QVariant RecentImagesModel::data(const QModelIndex &index, int role) const
{
QVariant result;
if (!d->recentFileManager) return result;
if (index.isValid())
{
Q_ASSERT(index.row() < d->recentFileManager->size());
QString key = d->recentFileManager->recentFileName(index.row());
QString value = d->recentFileManager->recentFile(index.row());
switch(role)
{
case ImageRole:
result = QString("image://recentimage/%1").arg(value);
break;
case TextRole:
result = QFileInfo(value).completeBaseName();
break;
case UrlRole:
result = value;
break;
case NameRole:
result = key;
case DateRole:
{
QFile f(value);
if (f.exists()) {
QFileInfo fi(value);
result = fi.lastModified().toString("dd-mm-yyyy (hh:mm)");
}
}
default:
result = "";
break;
}
}
return result;
}
QVariant RecentImagesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
Q_UNUSED(orientation);
QVariant result;
if (section == 0)
{
switch(role)
{
case ImageRole:
result = QString("Thumbnail");
break;
case TextRole:
result = QString("Name");
break;
case UrlRole:
case NameRole:
case DateRole:
default:
result = "";
break;
}
}
return result;
}
QObject *RecentImagesModel::recentFileManager() const
{
return d->recentFileManager;
}
void RecentImagesModel::setRecentFileManager(QObject *recentFileManager)
{
disconnect(d->recentFileManager);
d->recentFileManager = qobject_cast<RecentFileManager*>(recentFileManager);
connect(d->recentFileManager, SIGNAL(recentFilesListChanged()), SLOT(recentFilesListChanged()));
emit recentFileManagerChanged();
}
void RecentImagesModel::recentFilesListChanged()
{
- reset();
+ beginResetModel();
+ endResetModel();
}
void RecentImagesModel::addRecent(const QString &_url)
{
if (d->recentFileManager)
d->recentFileManager->addRecent(_url);
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/RecentImagesModel.h b/libs/libqml/plugins/kritasketchplugin/models/RecentImagesModel.h
index 3ac80f2271..92e65f47dc 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/RecentImagesModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/RecentImagesModel.h
@@ -1,62 +1,62 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Boudewijn Rempt <boud@kogmbh.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef RECENTIMAGESMODEL_H
#define RECENTIMAGESMODEL_H
#include <QAbstractListModel>
class RecentImagesModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QObject* recentFileManager READ recentFileManager WRITE setRecentFileManager NOTIFY recentFileManagerChanged)
public:
enum PresetRoles {
ImageRole = Qt::UserRole + 1,
TextRole,
UrlRole,
NameRole,
DateRole
};
explicit RecentImagesModel(QObject *parent = 0);
virtual ~RecentImagesModel();
-
+ QHash<int, QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QObject* recentFileManager() const;
void setRecentFileManager(QObject* recentFileManager);
Q_SIGNALS:
void recentFileManagerChanged();
public Q_SLOTS:
void addRecent(const QString &fileName);
private:
class Private;
Private* d;
private Q_SLOTS:
void recentFilesListChanged();
};
#endif // RECENTIMAGESMODEL_H
diff --git a/libs/libqml/plugins/kritasketchplugin/models/TemplatesModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/TemplatesModel.cpp
index 1290f766ec..ddf1a28152 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/TemplatesModel.cpp
+++ b/libs/libqml/plugins/kritasketchplugin/models/TemplatesModel.cpp
@@ -1,180 +1,183 @@
/*
* This file is part of the KDE project
* Copyright (C) 2014 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "TemplatesModel.h"
#include <KisTemplateTree.h>
#include <KisTemplateGroup.h>
#include <KisTemplate.h>
#include <KisPart.h>
#include <kis_icon.h>
struct TemplatesModel::ItemData {
public:
ItemData()
: favourite(false)
, groupFolded(false)
{};
QString name;
QString description;
QString file;
QString icon;
QString groupName;
bool favourite;
bool groupFolded;
};
class TemplatesModel::Private {
public:
Private() {}
~Private() { qDeleteAll(items);}
QList<ItemData*> items;
};
TemplatesModel::TemplatesModel(QObject* parent)
: QAbstractListModel(parent)
, d(new Private)
{
- QHash<int, QByteArray> roleNames;
- roleNames[NameRole] = "name";
- roleNames[DescriptionRole] = "description";
- roleNames[FileRole] = "file";
- roleNames[IconRole] = "icon";
- roleNames[GroupName] = "groupName";
- roleNames[GroupFolded] = "groupFolded";
- setRoleNames(roleNames);
-
- // Prefill a couple of
+ // Prefill a couple of
ItemData* customItem = new ItemData();
customItem->name = "Custom Image";
customItem->file = "custom";
customItem->icon = "filenew-black";
d->items << customItem;
ItemData* clipItem = new ItemData();
clipItem->name = "From Clipboard";
clipItem->file = "clip";
clipItem->icon = "fileclip-black";
d->items << clipItem;
ItemData* screenItem = new ItemData();
screenItem->name = "Blank Image (Screen Size)";
screenItem->file = "screen";
screenItem->icon = "filenew-black";
d->items << screenItem;
ItemData* a4pItem = new ItemData();
a4pItem->name = "Blank Image (A4 Portrait)";
a4pItem->file = "a4p";
a4pItem->icon = "A4portrait-black";
d->items << a4pItem;
ItemData* a4lItem = new ItemData();
a4lItem->name = "Blank Image (A4 Landscape)";
a4lItem->file = "a4l";
a4lItem->icon = "A4landscape-black";
d->items << a4lItem;
populate();
}
TemplatesModel::~TemplatesModel()
{
delete d;
}
+QHash<int, QByteArray> TemplatesModel::roleNames() const
+{
+ QHash<int, QByteArray> roleNames;
+ roleNames[NameRole] = "name";
+ roleNames[DescriptionRole] = "description";
+ roleNames[FileRole] = "file";
+ roleNames[IconRole] = "icon";
+ roleNames[GroupName] = "groupName";
+ roleNames[GroupFolded] = "groupFolded";
+ return roleNames;
+}
+
QVariant TemplatesModel::data(const QModelIndex& index, int role) const
{
QVariant data;
if(index.isValid() && index.row() > -1 && index.row() < d->items.count())
{
ItemData* item = d->items[index.row()];
switch(role) {
case NameRole:
data = item->name;
break;
case DescriptionRole:
data = item->description;
break;
case FileRole:
data = item->file;
break;
case IconRole:
data = item->icon;
break;
case GroupName:
data = item->groupName;
break;
case GroupFolded:
data = item->groupFolded;
break;
default:
break;
}
}
return data;
}
int TemplatesModel::rowCount(const QModelIndex& parent) const
{
if(parent.isValid())
return 0;
return d->items.count();
}
QString TemplatesModel::groupNameOf(int index) const
{
if(index > 0 && index < d->items.count())
return d->items[index]->groupName;
return QString();
}
void TemplatesModel::toggleGroup(const QString& name)
{
Q_FOREACH (ItemData* item, d->items) {
if(item->groupName == name)
item->groupFolded = !item->groupFolded;
}
dataChanged(index(0), index(d->items.count() - 1));
}
void TemplatesModel::populate()
{
KisTemplateTree templateTree( QStringLiteral("templates/"), true);
Q_FOREACH (KisTemplateGroup *group, templateTree.groups()) {
if (group->isHidden()) {
continue;
}
Q_FOREACH (KisTemplate* t, group->templates()) {
if (t->isHidden())
continue;
ItemData* item = new ItemData();
item->name = t->name();
item->description = t->description();
item->file = QString("template://").append(t->file());
// QT5TODO: support custom pictures and icon symbol ids again
// item->icon = KIconLoader::global()->iconPath(t->picture(), KIconLoader::Desktop);
item->icon = "filenew-black";
item->groupName = group->name();
item->groupFolded = true; // default hide groups
d->items << item;
}
}
}
diff --git a/libs/libqml/plugins/kritasketchplugin/models/TemplatesModel.h b/libs/libqml/plugins/kritasketchplugin/models/TemplatesModel.h
index 09277a595e..ed3742ac02 100644
--- a/libs/libqml/plugins/kritasketchplugin/models/TemplatesModel.h
+++ b/libs/libqml/plugins/kritasketchplugin/models/TemplatesModel.h
@@ -1,55 +1,56 @@
/*
* This file is part of the KDE project
* Copyright (C) 2014 Dan Leinir Turthra Jensen <admin@leinir.dk>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TEMPLATESMODEL_H
#define TEMPLATESMODEL_H
#include <QAbstractListModel>
class TemplatesModel : public QAbstractListModel
{
Q_OBJECT
public:
enum TemplateRoles {
NameRole = Qt::UserRole + 1,
DescriptionRole,
FileRole,
IconRole,
GroupName,
GroupFolded
};
explicit TemplatesModel(QObject* parent = 0);
virtual ~TemplatesModel();
+ QHash<int, QByteArray> roleNames() const;
virtual QVariant data(const QModelIndex& index, int role) const;
virtual int rowCount(const QModelIndex& parent) const;
Q_INVOKABLE QString groupNameOf(int index) const;
Q_INVOKABLE void toggleGroup(const QString& name);
Q_SLOT void populate();
private:
struct ItemData;
class Private;
Private* d;
};
#endif // TEMPLATESMODEL_H
diff --git a/libs/libqml/qmlthemes/default/colors.js b/libs/libqml/qmlthemes/default/colors.js
index dddf2f2613..d3b4f080e6 100644
--- a/libs/libqml/qmlthemes/default/colors.js
+++ b/libs/libqml/qmlthemes/default/colors.js
@@ -1,292 +1,292 @@
/* This file is part of the KDE project
* Copyright (C) 2014 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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.
*/
var values = {
base: {
base: "#a0a0a0",
text: "#ffffff",
header: "#000000",
headerText: "#ffffff",
},
components: {
button: {
- base: "transparent",
+ base: "gray",
text: "#ffffff",
- highlight: "transparent",
+ highlight: "lightgray",
checked: Qt.rgba(1.0, 1.0, 1.0, 0.7),
},
colorSwatch: {
border: "silver",
},
dialog: {
modalOverlay: Qt.rgba(0.0, 0.0, 0.0, 0.5),
background: {
start: "#F7F8FC",
stop: "#F0F0FA",
},
header: "#9AA1B2",
headerText: "#ffffff",
progress: {
background: "#ffffff",
border: "silver",
bar: "gray",
},
button: "#9AA1B2",
buttonText: "#ffffff",
},
expandingListView: {
selection: {
border: "#ffffff",
fill: Qt.rgba(1.0, 1.0, 1.0, 0.4),
text: Qt.rgba(0.0, 0.0, 0.0, 0.65),
},
list: {
background: Qt.rgba(1.0, 1.0, 1.0, 0.4),
item: "#ffffff",
itemBorder: "silver",
itemText: Qt.rgba(0.0, 0.0, 0.0, 0.65),
},
},
header: "#ffffff",
label: "#323232",
listItem: {
background: {
start: "#FBFBFB",
stop: "#F0F0F0",
},
title: "#000000",
description: "#333333",
},
messageStack: {
background: "gray",
border: "silver",
text: "white",
button: {
fill: "gray",
border: "silver",
},
},
newImageList: {
start: "#FBFBFB",
stop: "#F0F0F0",
},
newsList: {
listItem: {
start: "#FBFBFB",
stop: "#F0F0F0",
moreLink: "#999999",
},
title: "#000000",
date: "#666666",
description: "#000000",
backLink: "#999999",
},
panelTextField: {
border: "silver",
background: Qt.rgba(1.0, 1.0, 1.0, 0.4),
enabled: Qt.rgba(0.0, 0.0, 0.0, 0.0),
disabled: Qt.rgba(0.0, 0.0, 0.0, 0.7),
},
rangeInput: {
},
recentFilesList: {
start: "#FBFBFB",
stop: "#F0F0F0",
},
scrollDecorator: {
base: "silver",
border: "transparent"
},
slider: {
background: {
fill: Qt.rgba(1.0, 1.0, 1.0, 0.75),
border: "silver",
},
handle: {
fill: "silver",
border: "transparent",
},
},
textField: {
background: Qt.rgba(1.0, 1.0, 1.0, 0.75),
border: {
normal: "silver",
error: "red",
focus: "gray",
},
placeholder: "#5a5a5a",
},
textFieldMultiline: {
background: Qt.rgba(1.0, 1.0, 1.0, 0.75),
border: "silver",
},
},
panels: {
dropArea: {
fill: Qt.rgba(1.0, 1.0, 1.0, 0.25),
border: "#ffffff",
},
base: {
base: "#a0a0a0",
text: "#ffffff",
header: "#000000",
headerText: "#ffffff",
},
presets: {
base: "#a0a0a0",
text: "#ffffff",
header: "#000000",
headerText: "#ffffff",
preset: {
active: "#D7D7D7",
inactive: "transparent",
}
},
layers: {
base: "#a0a0a0",
text: "#ffffff",
header: "#000000",
headerText: "#ffffff",
subheader: "#cfcfcf",
editorButtons: {
active: "#EAEAEA",
inactive: "transparent",
text: "gray",
border: "silver",
},
layer: {
active: Qt.rgba(1.0, 1.0, 1.0, 0.5),
inactive: Qt.rgba(1.0, 1.0, 1.0, 0.2),
background: "#d7d7d7",
text: "#000000",
visible: "#ffffff",
locked: "#ffffff",
},
},
selection: {
base: "#a0a0a0",
text: "#ffffff",
header: "#000000",
headerText: "#ffffff",
buttons: {
color: Qt.rgba(1.0, 1.0, 1.0, 0.4),
text: "black",
border: "silver"
}
},
filter: {
base: "#a0a0a0",
text: "#ffffff",
header: "#000000",
headerText: "#ffffff",
},
color: {
base: "#a0a0a0",
text: "#ffffff",
header: "#000000",
headerText: "#ffffff",
},
tool: {
base: "#a0a0a0",
text: "#ffffff",
header: "#000000",
headerText: "#ffffff",
subheader: "#cfcfcf",
},
menu: {
base: "#000000",
text: "#ffffff",
buttonHighlight: "#dcdcdc",
},
newImage: {
background: "#ffffff",
header: {
start: "#707070",
stop: "#565656",
text: "#ffffff",
}
},
openImage: {
background: "#ffffff",
header: {
start: "#707070",
stop: "#565656",
text: "#ffffff",
}
},
},
pages: {
welcome: {
background: "#ffffff",
open:{
header: {
start: "#707070",
stop: "#565656",
text: "#ffffff",
},
},
create: {
header: {
start: "#565656",
stop: "#707070",
text: "#ffffff",
},
},
news: {
header: {
start: "#707070",
stop: "#565656",
text: "#ffffff",
},
},
},
open: {
background: "#ffffff",
location: "#ffffff",
},
save: {
background: "#ffffff",
location: "#ffffff",
footer: "#000000",
},
customImagePage: {
background: "#ffffff",
groupBox: "lightGray",
controls: {
background: Qt.rgba(1.0, 1.0, 1.0, 0.4),
border: "white",
},
}
},
}
diff --git a/libs/pigment/KoColor.cpp b/libs/pigment/KoColor.cpp
index c7bb76b817..ccadc4c9bb 100644
--- a/libs/pigment/KoColor.cpp
+++ b/libs/pigment/KoColor.cpp
@@ -1,284 +1,298 @@
/*
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoColor.h"
#include <QColor>
#include <QDomDocument>
#include "DebugPigment.h"
#include "KoColorModelStandardIds.h"
#include "KoColorProfile.h"
#include "KoColorSpace.h"
#include "KoColorSpaceRegistry.h"
#include "KoChannelInfo.h"
-const KoColor *KoColor::s_prefab = nullptr;
+#include <QGlobalStatic>
-void KoColor::init()
+namespace {
+
+struct DefaultKoColorInitializer
{
- KIS_ASSERT(s_prefab == nullptr);
- KoColor *prefab = new KoColor(KoColorSpaceRegistry::instance()->rgb16(0));
- prefab->m_colorSpace->fromQColor(Qt::black, prefab->m_data);
- prefab->m_colorSpace->setOpacity(prefab->m_data, OPACITY_OPAQUE_U8, 1);
- s_prefab = prefab;
+ DefaultKoColorInitializer() {
+ const KoColorSpace *defaultColorSpace = KoColorSpaceRegistry::instance()->rgb16(0);
+ KIS_ASSERT(defaultColorSpace);
+
+ value = new KoColor(Qt::black, defaultColorSpace);
#ifndef NODEBUG
#ifndef QT_NO_DEBUG
- // warn about rather expensive checks in assertPermanentColorspace().
- qWarning() << "KoColor debug runtime checks are active.";
+ // warn about rather expensive checks in assertPermanentColorspace().
+ qWarning() << "KoColor debug runtime checks are active.";
#endif
#endif
+ }
+
+ KoColor *value = 0;
+};
+
+Q_GLOBAL_STATIC(DefaultKoColorInitializer, s_defaultKoColor);
+
+}
+
+
+KoColor::KoColor() {
+ *this = *s_defaultKoColor->value;
}
KoColor::KoColor(const KoColorSpace * colorSpace)
{
Q_ASSERT(colorSpace);
m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
m_size = m_colorSpace->pixelSize();
Q_ASSERT(m_size <= MAX_PIXEL_SIZE);
memset(m_data, 0, m_size);
}
KoColor::KoColor(const QColor & color, const KoColorSpace * colorSpace)
{
Q_ASSERT(color.isValid());
Q_ASSERT(colorSpace);
m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
m_size = m_colorSpace->pixelSize();
Q_ASSERT(m_size <= MAX_PIXEL_SIZE);
memset(m_data, 0, m_size);
m_colorSpace->fromQColor(color, m_data);
}
KoColor::KoColor(const quint8 * data, const KoColorSpace * colorSpace)
{
Q_ASSERT(colorSpace);
Q_ASSERT(data);
m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
m_size = m_colorSpace->pixelSize();
Q_ASSERT(m_size <= MAX_PIXEL_SIZE);
memmove(m_data, data, m_size);
}
KoColor::KoColor(const KoColor &src, const KoColorSpace * colorSpace)
{
Q_ASSERT(colorSpace);
m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
m_size = m_colorSpace->pixelSize();
Q_ASSERT(m_size <= MAX_PIXEL_SIZE);
memset(m_data, 0, m_size);
src.colorSpace()->convertPixelsTo(src.m_data, m_data, colorSpace, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}
void KoColor::convertTo(const KoColorSpace * cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
//dbgPigment <<"Our colormodel:" << d->colorSpace->id().name()
// << ", new colormodel: " << cs->id().name() << "\n";
if (*m_colorSpace == *cs)
return;
quint8 data[MAX_PIXEL_SIZE];
const size_t size = cs->pixelSize();
Q_ASSERT(size <= MAX_PIXEL_SIZE);
memset(data, 0, size);
m_colorSpace->convertPixelsTo(m_data, data, cs, 1, renderingIntent, conversionFlags);
memcpy(m_data, data, size);
m_size = size;
m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(cs);
}
void KoColor::convertTo(const KoColorSpace * cs)
{
convertTo(cs,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
}
void KoColor::setProfile(const KoColorProfile *profile)
{
const KoColorSpace *dstColorSpace =
KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
if (!dstColorSpace) return;
m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(dstColorSpace);
}
void KoColor::setColor(const quint8 * data, const KoColorSpace * colorSpace)
{
Q_ASSERT(colorSpace);
const size_t size = colorSpace->pixelSize();
Q_ASSERT(size <= MAX_PIXEL_SIZE);
memcpy(m_data, data, size);
m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
}
// To save the user the trouble of doing color->colorSpace()->toQColor(color->data(), &c, &a, profile
void KoColor::toQColor(QColor *c) const
{
Q_ASSERT(c);
if (m_colorSpace) {
m_colorSpace->toQColor(m_data, c);
}
}
QColor KoColor::toQColor() const
{
QColor c;
toQColor(&c);
return c;
}
void KoColor::fromQColor(const QColor& c)
{
if (m_colorSpace) {
m_colorSpace->fromQColor(c, m_data);
}
}
#ifndef NDEBUG
void KoColor::dump() const
{
dbgPigment <<"KoColor (" << this <<")," << m_colorSpace->id() <<"";
QList<KoChannelInfo *> channels = m_colorSpace->channels();
QList<KoChannelInfo *>::const_iterator begin = channels.constBegin();
QList<KoChannelInfo *>::const_iterator end = channels.constEnd();
for (QList<KoChannelInfo *>::const_iterator it = begin; it != end; ++it) {
KoChannelInfo * ch = (*it);
// XXX: setNum always takes a byte.
if (ch->size() == sizeof(quint8)) {
// Byte
dbgPigment <<"Channel (byte):" << ch->name() <<":" << QString().setNum(m_data[ch->pos()]) <<"";
} else if (ch->size() == sizeof(quint16)) {
// Short (may also by an nvidia half)
dbgPigment <<"Channel (short):" << ch->name() <<":" << QString().setNum(*((const quint16 *)(m_data+ch->pos()))) <<"";
} else if (ch->size() == sizeof(quint32)) {
// Integer (may also be float... Find out how to distinguish these!)
dbgPigment <<"Channel (int):" << ch->name() <<":" << QString().setNum(*((const quint32 *)(m_data+ch->pos()))) <<"";
}
}
}
#endif
void KoColor::fromKoColor(const KoColor& src)
{
src.colorSpace()->convertPixelsTo(src.m_data, m_data, colorSpace(), 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}
const KoColorProfile *KoColor::profile() const
{
return m_colorSpace->profile();
}
void KoColor::toXML(QDomDocument& doc, QDomElement& colorElt) const
{
m_colorSpace->colorToXML(m_data, doc, colorElt);
}
void KoColor::setOpacity(quint8 alpha)
{
m_colorSpace->setOpacity(m_data, alpha, 1);
}
void KoColor::setOpacity(qreal alpha)
{
m_colorSpace->setOpacity(m_data, alpha, 1);
}
quint8 KoColor::opacityU8() const
{
return m_colorSpace->opacityU8(m_data);
}
qreal KoColor::opacityF() const
{
return m_colorSpace->opacityF(m_data);
}
KoColor KoColor::fromXML(const QDomElement& elt, const QString& bitDepthId)
{
bool ok;
return fromXML(elt, bitDepthId, &ok);
}
KoColor KoColor::fromXML(const QDomElement& elt, const QString& bitDepthId, bool* ok)
{
*ok = true;
QString modelId;
if (elt.tagName() == "CMYK") {
modelId = CMYKAColorModelID.id();
} else if (elt.tagName() == "RGB") {
modelId = RGBAColorModelID.id();
} else if (elt.tagName() == "sRGB") {
modelId = RGBAColorModelID.id();
} else if (elt.tagName() == "Lab") {
modelId = LABAColorModelID.id();
} else if (elt.tagName() == "XYZ") {
modelId = XYZAColorModelID.id();
} else if (elt.tagName() == "Gray") {
modelId = GrayAColorModelID.id();
} else if (elt.tagName() == "YCbCr") {
modelId = YCbCrAColorModelID.id();
}
QString profileName;
if (elt.tagName() != "sRGB") {
profileName = elt.attribute("space", "");
if (!KoColorSpaceRegistry::instance()->profileByName(profileName)) {
profileName.clear();
}
}
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, bitDepthId, profileName);
if (cs == 0) {
QList<KoID> list = KoColorSpaceRegistry::instance()->colorDepthList(modelId, KoColorSpaceRegistry::AllColorSpaces);
if (!list.empty()) {
cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, list[0].id(), profileName);
}
}
if (cs) {
KoColor c(cs);
// TODO: Provide a way for colorFromXML() to notify the caller if parsing failed. Currently it returns default values on failure.
cs->colorFromXML(c.data(), elt);
return c;
} else {
*ok = false;
return KoColor();
}
}
QString KoColor::toQString(const KoColor &color)
{
QStringList ls;
Q_FOREACH (KoChannelInfo *channel, KoChannelInfo::displayOrderSorted(color.colorSpace()->channels())) {
int realIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), color.colorSpace()->channels());
ls << channel->name();
ls << color.colorSpace()->channelValueText(color.data(), realIndex);
}
return ls.join(" ");
}
diff --git a/libs/pigment/KoColor.h b/libs/pigment/KoColor.h
index 5eb3859a95..3fa5e7be15 100644
--- a/libs/pigment/KoColor.h
+++ b/libs/pigment/KoColor.h
@@ -1,239 +1,228 @@
/*
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOCOLOR_H
#define KOCOLOR_H
#include <QColor>
#include <QMetaType>
#include "kritapigment_export.h"
#include "KoColorConversionTransformation.h"
#include "KoColorSpaceRegistry.h"
#include "KoColorSpaceTraits.h"
#include <boost/operators.hpp>
#include "kis_assert.h"
class QDomDocument;
class QDomElement;
class KoColorProfile;
class KoColorSpace;
/**
* A KoColor describes a color in a certain colorspace. The color is stored in a buffer
* that can be manipulated by the function of the color space.
*/
class KRITAPIGMENT_EXPORT KoColor : public boost::equality_comparable<KoColor>
{
public:
- static void init();
-
/// Create an empty KoColor. It will be valid, but also black and transparent
- KoColor() {
- const KoColor * const prefab = s_prefab;
-
- // assert that KoColor::init was called and everything is set up properly.
- KIS_ASSERT_X(prefab != nullptr, "KoColor::KoColor()", "KoColor not initialized yet.");
-
- *this = *prefab;
- }
+ KoColor();
/// Create a null KoColor. It will be valid, but all channels will be set to 0
explicit KoColor(const KoColorSpace * colorSpace);
/// Create a KoColor from a QColor. The QColor is immediately converted to native. The QColor
/// is assumed to have the current monitor profile.
KoColor(const QColor & color, const KoColorSpace * colorSpace);
/// Create a KoColor using a native color strategy. The data is copied.
KoColor(const quint8 * data, const KoColorSpace * colorSpace);
/// Create a KoColor by converting src into another colorspace
KoColor(const KoColor &src, const KoColorSpace * colorSpace);
/// Copy constructor -- deep copies the colors.
KoColor(const KoColor & rhs) {
*this = rhs;
}
/**
* assignment operator to copy the data from the param color into this one.
* @param other the color we are going to copy
* @return this color
*/
inline KoColor &operator=(const KoColor &rhs) {
if (&rhs == this) {
return *this;
}
m_colorSpace = rhs.m_colorSpace;
m_size = rhs.m_size;
memcpy(m_data, rhs.m_data, m_size);
assertPermanentColorspace();
return *this;
}
bool operator==(const KoColor &other) const {
if (*colorSpace() != *other.colorSpace())
return false;
Q_ASSERT(m_size == other.m_size);
return memcmp(m_data, other.m_data, m_size) == 0;
}
/// return the current colorSpace
const KoColorSpace * colorSpace() const {
return m_colorSpace;
}
/// return the current profile
const KoColorProfile *profile() const;
/// Convert this KoColor to the specified colorspace. If the specified colorspace is the
/// same as the original colorspace, do nothing. Returns the converted KoColor.
void convertTo(const KoColorSpace * cs,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags);
void convertTo(const KoColorSpace * cs);
/// assign new profile without converting pixel data
void setProfile(const KoColorProfile *profile);
/// Replace the existing color data, and colorspace with the specified data.
/// The data is copied.
void setColor(const quint8 * data, const KoColorSpace * colorSpace = 0);
/// Convert the color from src and replace the value of the current color with the converted data.
/// Don't convert the color if src and this have the same colorspace.
void fromKoColor(const KoColor& src);
/// a convenience method for the above.
void toQColor(QColor *c) const;
/// a convenience method for the above.
QColor toQColor() const;
/**
* Convenient function to set the opacity of the color.
*/
void setOpacity(quint8 alpha);
void setOpacity(qreal alpha);
/**
* Convenient function that return the opacity of the color
*/
quint8 opacityU8() const;
qreal opacityF() const;
/// Convenient function for converting from a QColor
void fromQColor(const QColor& c);
/**
* @return the buffer associated with this color object to be used with the
* transformation object created by the color space of this KoColor
* or to copy to a different buffer from the same color space
*/
quint8 * data() {
return m_data;
}
/**
* @return the buffer associated with this color object to be used with the
* transformation object created by the color space of this KoColor
* or to copy to a different buffer from the same color space
*/
const quint8 * data() const {
return m_data;
}
/**
* Serialize this color following Create's swatch color specification available
* at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
*
* This function doesn't create the <color /> element but rather the <CMYK />,
* <sRGB />, <RGB /> ... elements. It is assumed that colorElt is the <color />
* element.
*
* @param colorElt root element for the serialization, it is assumed that this
* element is <color />
* @param doc is the document containing colorElt
*/
void toXML(QDomDocument& doc, QDomElement& colorElt) const;
/**
* Unserialize a color following Create's swatch color specification available
* at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
*
* @param elt the element to unserialize (<CMYK />, <sRGB />, <RGB />)
* @param bitDepthId the bit depth is unspecified by the spec, this allow to select
* a preferred bit depth for creating the KoColor object (if that
* bit depth isn't available, this function will randomly select
* an other bit depth)
* @return the unserialize color, or an empty color object if the function failed
* to unserialize the color
*/
static KoColor fromXML(const QDomElement& elt, const QString & bitDepthId);
/**
* Unserialize a color following Create's swatch color specification available
* at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
*
* @param elt the element to unserialize (<CMYK />, <sRGB />, <RGB />)
* @param bitDepthId the bit depth is unspecified by the spec, this allow to select
* a preferred bit depth for creating the KoColor object (if that
* bit depth isn't available, this function will randomly select
* an other bit depth)
* @param ok If a an error occurs, *ok is set to false; otherwise it's set to true
* @return the unserialize color, or an empty color object if the function failed
* to unserialize the color
*/
static KoColor fromXML(const QDomElement& elt, const QString & bitDepthId, bool* ok);
/**
* @brief toQString create a user-visible string of the channel names and the channel values
* @param color the color to create the string from
* @return a string that can be used to display the values of this color to the user.
*/
static QString toQString(const KoColor &color);
#ifndef NODEBUG
/// use qDebug calls to print internal info
void dump() const;
#endif
private:
inline void assertPermanentColorspace() {
#ifndef NODEBUG
if (m_colorSpace) {
Q_ASSERT(*m_colorSpace == *KoColorSpaceRegistry::instance()->permanentColorspace(m_colorSpace));
}
#endif
}
const KoColorSpace *m_colorSpace;
quint8 m_data[MAX_PIXEL_SIZE];
quint8 m_size;
-
- static const KoColor *s_prefab;
};
Q_DECLARE_METATYPE(KoColor)
#endif
diff --git a/libs/pigment/KoColorSpace.cpp b/libs/pigment/KoColorSpace.cpp
index c240a0fb05..fce965ed3f 100644
--- a/libs/pigment/KoColorSpace.cpp
+++ b/libs/pigment/KoColorSpace.cpp
@@ -1,819 +1,820 @@
/*
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoColorSpace.h"
#include "KoColorSpace_p.h"
#include "KoChannelInfo.h"
#include "DebugPigment.h"
#include "KoCompositeOp.h"
#include "KoColorTransformation.h"
#include "KoColorTransformationFactory.h"
#include "KoColorTransformationFactoryRegistry.h"
#include "KoColorConversionCache.h"
#include "KoColorConversionSystem.h"
#include "KoColorSpaceRegistry.h"
#include "KoColorProfile.h"
#include "KoCopyColorConversionTransformation.h"
#include "KoFallBackColorTransformation.h"
#include "KoUniqueNumberForIdServer.h"
#include "KoMixColorsOp.h"
#include "KoConvolutionOp.h"
#include "KoCompositeOpRegistry.h"
#include "KoColorSpaceEngine.h"
#include <QThreadStorage>
#include <QByteArray>
#include <QBitArray>
#include <QPolygonF>
#include <QPointF>
#include <math.h>
KoColorSpace::KoColorSpace()
: d(new Private())
{
}
KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp)
: d(new Private())
{
d->id = id;
d->idNumber = KoUniqueNumberForIdServer::instance()->numberForId(d->id);
d->name = name;
d->mixColorsOp = mixColorsOp;
d->convolutionOp = convolutionOp;
d->transfoToRGBA16 = 0;
d->transfoFromRGBA16 = 0;
d->transfoToLABA16 = 0;
d->transfoFromLABA16 = 0;
d->gamutXYY = QPolygonF();
d->TRCXYY = QPolygonF();
d->colorants = QVector <qreal> (0);
d->lumaCoefficients = QVector <qreal> (0);
d->iccEngine = 0;
d->deletability = NotOwnedByRegistry;
}
KoColorSpace::~KoColorSpace()
{
Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete);
qDeleteAll(d->compositeOps);
Q_FOREACH (KoChannelInfo * channel, d->channels) {
delete channel;
}
if (d->deletability == NotOwnedByRegistry) {
KoColorConversionCache* cache = KoColorSpaceRegistry::instance()->colorConversionCache();
if (cache) {
cache->colorSpaceIsDestroyed(this);
}
}
delete d->mixColorsOp;
delete d->convolutionOp;
delete d->transfoToRGBA16;
delete d->transfoFromRGBA16;
delete d->transfoToLABA16;
delete d->transfoFromLABA16;
delete d;
}
bool KoColorSpace::operator==(const KoColorSpace& rhs) const
{
const KoColorProfile* p1 = rhs.profile();
const KoColorProfile* p2 = profile();
return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2));
}
QString KoColorSpace::id() const
{
return d->id;
}
QString KoColorSpace::name() const
{
return d->name;
}
//Color space info stuff.
QPolygonF KoColorSpace::gamutXYY() const
{
if (d->gamutXYY.empty()) {
//now, let's decide on the boundary. This is a bit tricky because icc profiles can be both matrix-shaper and cLUT at once if the maker so pleases.
//first make a list of colors.
qreal max = 1.0;
if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") {
//boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general.
max = this->channels()[0]->getUIMax();
}
int samples = 5;//amount of samples in our color space.
const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32");
quint8 *data = new quint8[pixelSize()];
quint8 data2[16]; // xyza f32 is 4 floats, that is 16 bytes per pixel.
//QVector <qreal> sampleCoordinates(pow(colorChannelCount(),samples));
//sampleCoordinates.fill(0.0);
// This is fixed to 5 since the maximum number of channels are 5 for CMYKA
QVector <float> channelValuesF(5);//for getting the coordinates.
for(int x=0;x<samples;x++){
if (colorChannelCount()==1) {//gray
channelValuesF[0]=(max/(samples-1))*(x);
channelValuesF[1]=max;
fromNormalisedChannelsValue(data, channelValuesF);
convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
xyzColorSpace->normalisedChannelsValue(data2, channelValuesF);
qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
d->gamutXYY << QPointF(x,y);
} else {
for(int y=0;y<samples;y++){
for(int z=0;z<samples;z++){
if (colorChannelCount()==4) {
for(int k=0;k<samples;k++){
channelValuesF[0] = (max / (samples - 1)) * (x);
channelValuesF[1] = (max / (samples - 1)) * (y);
channelValuesF[2] = (max / (samples - 1)) * (z);
channelValuesF[3] = (max / (samples - 1)) * (k);
channelValuesF[4] = max;
fromNormalisedChannelsValue(data, channelValuesF);
convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
xyzColorSpace->normalisedChannelsValue(data2, channelValuesF);
qreal x = channelValuesF[0] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]);
qreal y = channelValuesF[1] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]);
d->gamutXYY<< QPointF(x,y);
}
} else {
channelValuesF[0]=(max/(samples-1))*(x);
channelValuesF[1]=(max/(samples-1))*(y);
channelValuesF[2]=(max/(samples-1))*(z);
channelValuesF[3]=max;
if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz.
fromNormalisedChannelsValue(data, channelValuesF);
convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
}
qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
d->gamutXYY<< QPointF(x,y);
}
}
}
}
}
delete[] data;
//if we ever implement a boundary-checking thing I'd add it here.
return d->gamutXYY;
} else {
return d->gamutXYY;
}
}
QPolygonF KoColorSpace::estimatedTRCXYY() const
{
if (d->TRCXYY.empty()){
qreal max = 1.0;
if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") {
//boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general.
max = this->channels()[0]->getUIMax();
}
const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32");
quint8 *data = new quint8[pixelSize()];
- quint8 data2[xyzColorSpace->pixelSize()];
+ quint8 *data2 = new quint8[xyzColorSpace->pixelSize()];
// This is fixed to 5 since the maximum number of channels are 5 for CMYKA
QVector <float> channelValuesF(5);//for getting the coordinates.
for (quint32 i=0; i<colorChannelCount(); i++) {
qreal colorantY=1.0;
if (colorModelId().id()!="CMYKA") {
for (int j=5; j>0; j--){
channelValuesF.fill(0.0);
channelValuesF[i] = ((max/4)*(5-j));
if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz.
fromNormalisedChannelsValue(data, channelValuesF);
convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
}
if (j==0) {
colorantY = channelValuesF[1];
if (d->colorants.size()<2){
d->colorants.resize(3*colorChannelCount());
d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
d->colorants[i+2]= channelValuesF[1];
}
}
d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(5-j)));
}
} else {
for (int j=0; j<5; j++){
channelValuesF.fill(0.0);
channelValuesF[i] = ((max/4)*(j));
fromNormalisedChannelsValue(data, channelValuesF);
convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
if (j==0) {
colorantY = channelValuesF[1];
if (d->colorants.size()<2){
d->colorants.resize(3*colorChannelCount());
d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
d->colorants[i+2]= channelValuesF[1];
}
}
d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(j)));
}
}
}
delete[] data;
+ delete[] data2;
return d->TRCXYY;
} else {
return d->TRCXYY;
}
}
QVector <qreal> KoColorSpace::colorants() const
{
if (d->colorants.size()>1){
return d->colorants;
} else if (profile() && profile()->hasColorants()) {
d->colorants.resize(3*colorChannelCount());
d->colorants = profile()->getColorantsxyY();
return d->colorants;
} else {
estimatedTRCXYY();
return d->colorants;
}
}
QVector <qreal> KoColorSpace::lumaCoefficients() const
{
if (d->lumaCoefficients.size()>1){
return d->lumaCoefficients;
} else {
d->lumaCoefficients.resize(3);
if (colorModelId().id()!="RGBA") {
d->lumaCoefficients.fill(0.33);
} else {
colorants();
if (d->colorants[2]<0 || d->colorants[5]<0 || d->colorants[8]<0) {
d->lumaCoefficients[0]=0.2126;
d->lumaCoefficients[1]=0.7152;
d->lumaCoefficients[2]=0.0722;
} else {
d->lumaCoefficients[0]=d->colorants[2];
d->lumaCoefficients[1]=d->colorants[5];
d->lumaCoefficients[2]=d->colorants[8];
}
}
return d->lumaCoefficients;
}
}
QList<KoChannelInfo *> KoColorSpace::channels() const
{
return d->channels;
}
QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const
{
QBitArray ba(d->channels.size());
if (!color && !alpha) return ba;
for (int i = 0; i < d->channels.size(); ++i) {
KoChannelInfo * channel = d->channels.at(i);
if ((color && channel->channelType() == KoChannelInfo::COLOR) ||
(alpha && channel->channelType() == KoChannelInfo::ALPHA))
ba.setBit(i, true);
}
return ba;
}
void KoColorSpace::addChannel(KoChannelInfo * ci)
{
d->channels.push_back(ci);
}
bool KoColorSpace::hasCompositeOp(const QString& id) const
{
return d->compositeOps.contains(id);
}
QList<KoCompositeOp*> KoColorSpace::compositeOps() const
{
return d->compositeOps.values();
}
KoMixColorsOp* KoColorSpace::mixColorsOp() const
{
return d->mixColorsOp;
}
KoConvolutionOp* KoColorSpace::convolutionOp() const
{
return d->convolutionOp;
}
const KoCompositeOp * KoColorSpace::compositeOp(const QString & id) const
{
const QHash<QString, KoCompositeOp*>::ConstIterator it = d->compositeOps.constFind(id);
if (it != d->compositeOps.constEnd()) {
return it.value();
}
else {
warnPigment << "Asking for non-existent composite operation " << id << ", returning " << COMPOSITE_OVER;
return d->compositeOps.value(COMPOSITE_OVER);
}
}
void KoColorSpace::addCompositeOp(const KoCompositeOp * op)
{
if (op->colorSpace()->id() == id()) {
d->compositeOps.insert(op->id(), const_cast<KoCompositeOp*>(op));
}
}
const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const
{
if (!d->transfoToLABA16) {
d->transfoToLABA16 = KoColorSpaceRegistry::instance()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
}
return d->transfoToLABA16;
}
const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const
{
if (!d->transfoFromLABA16) {
d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(), this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
}
return d->transfoFromLABA16;
}
const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const
{
if (!d->transfoToRGBA16) {
d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
}
return d->transfoToRGBA16;
}
const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const
{
if (!d->transfoFromRGBA16) {
d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16() , this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
}
return d->transfoFromRGBA16;
}
void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
toLabA16Converter()->transform(src, dst, nPixels);
}
void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
fromLabA16Converter()->transform(src, dst, nPixels);
}
void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
toRgbA16Converter()->transform(src, dst, nPixels);
}
void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
fromRgbA16Converter()->transform(src, dst, nPixels);
}
KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
if (*this == *dstColorSpace) {
return new KoCopyColorConversionTransformation(this);
} else {
return KoColorSpaceRegistry::instance()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags);
}
}
bool KoColorSpace::convertPixelsTo(const quint8 * src,
quint8 * dst,
const KoColorSpace * dstColorSpace,
quint32 numPixels,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
if (*this == *dstColorSpace) {
if (src != dst) {
memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize());
}
} else {
KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags);
cct.transformation()->transform(src, dst, numPixels);
}
return true;
}
KoColorConversionTransformation * KoColorSpace::createProofingTransform(const KoColorSpace *dstColorSpace, const KoColorSpace *proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const
{
if (!d->iccEngine) {
d->iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
}
if (!d->iccEngine) return 0;
return d->iccEngine->createColorProofingTransformation(this, dstColorSpace, proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning, adaptationState);
}
bool KoColorSpace::proofPixelsTo(const quint8 *src,
quint8 *dst,
quint32 numPixels,
KoColorConversionTransformation *proofingTransform) const
{
proofingTransform->transform(src, dst, numPixels);
//the transform is deleted in the destructor.
return true;
}
void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
Q_ASSERT_X(*op->colorSpace() == *this, "KoColorSpace::bitBlt", QString("Composite op is for color space %1 (%2) while this is %3 (%4)").arg(op->colorSpace()->id()).arg(op->colorSpace()->profile()->name()).arg(id()).arg(profile()->name()).toLatin1());
if(params.rows <= 0 || params.cols <= 0)
return;
if(!(*this == *srcSpace)) {
if (preferCompositionInSourceColorSpace() &&
srcSpace->hasCompositeOp(op->id())) {
quint32 conversionDstBufferStride = params.cols * srcSpace->pixelSize();
QVector<quint8> * conversionDstCache = threadLocalConversionCache(params.rows * conversionDstBufferStride);
quint8* conversionDstData = conversionDstCache->data();
for(qint32 row=0; row<params.rows; row++) {
convertPixelsTo(params.dstRowStart + row * params.dstRowStride,
conversionDstData + row * conversionDstBufferStride, srcSpace, params.cols,
renderingIntent, conversionFlags);
}
// FIXME: do not calculate the otherOp every time
const KoCompositeOp *otherOp = srcSpace->compositeOp(op->id());
KoCompositeOp::ParameterInfo paramInfo(params);
paramInfo.dstRowStart = conversionDstData;
paramInfo.dstRowStride = conversionDstBufferStride;
otherOp->composite(paramInfo);
for(qint32 row=0; row<params.rows; row++) {
srcSpace->convertPixelsTo(conversionDstData + row * conversionDstBufferStride,
params.dstRowStart + row * params.dstRowStride, this, params.cols,
renderingIntent, conversionFlags);
}
} else {
quint32 conversionBufferStride = params.cols * pixelSize();
QVector<quint8> * conversionCache = threadLocalConversionCache(params.rows * conversionBufferStride);
quint8* conversionData = conversionCache->data();
for(qint32 row=0; row<params.rows; row++) {
srcSpace->convertPixelsTo(params.srcRowStart + row * params.srcRowStride,
conversionData + row * conversionBufferStride, this, params.cols,
renderingIntent, conversionFlags);
}
KoCompositeOp::ParameterInfo paramInfo(params);
paramInfo.srcRowStart = conversionData;
paramInfo.srcRowStride = conversionBufferStride;
op->composite(paramInfo);
}
}
else {
op->composite(params);
}
}
QVector<quint8> * KoColorSpace::threadLocalConversionCache(quint32 size) const
{
QVector<quint8> * ba = 0;
if (!d->conversionCache.hasLocalData()) {
ba = new QVector<quint8>(size, '0');
d->conversionCache.setLocalData(ba);
} else {
ba = d->conversionCache.localData();
if ((quint8)ba->size() < size)
ba->resize(size);
}
return ba;
}
KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash<QString, QVariant> & parameters) const
{
KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id);
if (!factory) return 0;
QPair<KoID, KoID> model(colorModelId(), colorDepthId());
QList< QPair<KoID, KoID> > models = factory->supportedModels();
if (models.isEmpty() || models.contains(model)) {
return factory->createTransformation(this, parameters);
} else {
// Find the best solution
// TODO use the color conversion cache
KoColorConversionTransformation* csToFallBack = 0;
KoColorConversionTransformation* fallBackToCs = 0;
KoColorSpaceRegistry::instance()->createColorConverters(this, models, csToFallBack, fallBackToCs);
Q_ASSERT(csToFallBack);
Q_ASSERT(fallBackToCs);
KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters);
return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo);
}
}
void KoColorSpace::increaseLuminosity(quint8 * pixel, qreal step) const{
int channelnumber = channelCount();
QVector <double> channelValues(channelnumber);
QVector <float> channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;i<channelnumber;i++){
channelValues[i]=channelValuesF[i];
}
if (profile()->hasTRC()){
//only linearise and crunch the luma if there's a TRC
profile()->linearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
luma = pow(luma, 1/2.2);
luma = qMin(1.0, luma + step);
luma = pow(luma, 2.2);
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
} else {
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
luma = qMin(1.0, luma + step);
channelValues = fromHSY(&hue, &sat, &luma);
}
for (int i=0;i<channelnumber;i++){
channelValuesF[i]=channelValues[i];
}
fromNormalisedChannelsValue(pixel, channelValuesF);
setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::decreaseLuminosity(quint8 * pixel, qreal step) const {
int channelnumber = channelCount();
QVector <double> channelValues(channelnumber);
QVector <float> channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;i<channelnumber;i++){
channelValues[i]=channelValuesF[i];
}
if (profile()->hasTRC()){
//only linearise and crunch the luma if there's a TRC
profile()->linearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
luma = pow(luma, 1/2.2);
if (luma-step<0.0) {
luma=0.0;
} else {
luma -= step;
}
luma = pow(luma, 2.2);
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
} else {
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
if (luma-step<0.0) {
luma=0.0;
} else {
luma -= step;
}
channelValues = fromHSY(&hue, &sat, &luma);
}
for (int i=0;i<channelnumber;i++){
channelValuesF[i]=channelValues[i];
}
fromNormalisedChannelsValue(pixel, channelValuesF);
setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseSaturation(quint8 * pixel, qreal step) const{
int channelnumber = channelCount();
QVector <double> channelValues(channelnumber);
QVector <float> channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;i<channelnumber;i++){
channelValues[i]=channelValuesF[i];
}
profile()->linearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
sat += step;
sat = qBound(0.0, sat, 1.0);
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i<channelnumber;i++){
channelValuesF[i]=channelValues[i];
}
fromNormalisedChannelsValue(pixel, channelValuesF);
setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::decreaseSaturation(quint8 * pixel, qreal step) const{
int channelnumber = channelCount();
QVector <double> channelValues(channelnumber);
QVector <float> channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;i<channelnumber;i++){
channelValues[i]=channelValuesF[i];
}
profile()->linearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
sat -= step;
sat = qBound(0.0, sat, 1.0);
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i<channelnumber;i++){
channelValuesF[i]=channelValues[i];
}
fromNormalisedChannelsValue(pixel, channelValuesF);
setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseHue(quint8 * pixel, qreal step) const{
int channelnumber = channelCount(); //doesn't work for cmyka...
QVector <double> channelValues(channelnumber);
QVector <float> channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;i<channelnumber;i++){
channelValues[i]=channelValuesF[i];
}
profile()->linearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
if (hue+step>1.0){
hue=(hue+step)- 1.0;
} else {
hue += step;
}
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i<channelnumber;i++){
channelValuesF[i]=channelValues[i];
}
fromNormalisedChannelsValue(pixel, channelValuesF);
setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::decreaseHue(quint8 * pixel, qreal step) const{
int channelnumber = channelCount();
QVector <double> channelValues(channelnumber);
QVector <float> channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;i<channelnumber;i++){
channelValues[i]=channelValuesF[i];
}
profile()->linearizeFloatValue(channelValues);
qreal hue, sat, luma = 0.0;
toHSY(channelValues, &hue, &sat, &luma);
if (hue-step<0.0){
hue=1.0-(step-hue);
} else {
hue -= step;
}
channelValues = fromHSY(&hue, &sat, &luma);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i<channelnumber;i++){
channelValuesF[i]=channelValues[i];
}
fromNormalisedChannelsValue(pixel, channelValuesF);
setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseRed(quint8 * pixel, qreal step) const{
int channelnumber = channelCount();
QVector <double> channelValues(channelnumber);
QVector <float> channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;i<channelnumber;i++){
channelValues[i]=channelValuesF[i];
}
profile()->linearizeFloatValue(channelValues);
qreal y, u, v = 0.0;
toYUV(channelValues, &y, &u, &v);
u += step;
u = qBound(0.0, u, 1.0);
channelValues = fromYUV(&y, &u, &v);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i<channelnumber;i++){
channelValuesF[i]=channelValues[i];
}
fromNormalisedChannelsValue(pixel, channelValuesF);
setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseGreen(quint8 * pixel, qreal step) const{
int channelnumber = channelCount();
QVector <double> channelValues(channelnumber);
QVector <float> channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;i<channelnumber;i++){
channelValues[i]=channelValuesF[i];
}
profile()->linearizeFloatValue(channelValues);
qreal y, u, v = 0.0;
toYUV(channelValues, &y, &u, &v);
u -= step;
u = qBound(0.0, u, 1.0);
channelValues = fromYUV(&y, &u, &v);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i<channelnumber;i++){
channelValuesF[i]=channelValues[i];
}
fromNormalisedChannelsValue(pixel, channelValuesF);
setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseBlue(quint8 * pixel, qreal step) const{
int channelnumber = channelCount();
QVector <double> channelValues(channelnumber);
QVector <float> channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;i<channelnumber;i++){
channelValues[i]=channelValuesF[i];
}
profile()->linearizeFloatValue(channelValues);
qreal y, u, v = 0.0;
toYUV(channelValues, &y, &u, &v);
v += step;
v = qBound(0.0, v, 1.0);
channelValues = fromYUV(&y, &u, &v);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i<channelnumber;i++){
channelValuesF[i]=channelValues[i];
}
fromNormalisedChannelsValue(pixel, channelValuesF);
setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseYellow(quint8 * pixel, qreal step) const{
int channelnumber = channelCount();
QVector <double> channelValues(channelnumber);
QVector <float> channelValuesF(channelnumber);
normalisedChannelsValue(pixel, channelValuesF);
for (int i=0;i<channelnumber;i++){
channelValues[i]=channelValuesF[i];
}
profile()->linearizeFloatValue(channelValues);
qreal y, u, v = 0.0;
toYUV(channelValues, &y, &u, &v);
v -= step;
v = qBound(0.0, v, 1.0);
channelValues = fromYUV(&y, &u, &v);
profile()->delinearizeFloatValue(channelValues);
for (int i=0;i<channelnumber;i++){
channelValuesF[i]=channelValues[i];
}
fromNormalisedChannelsValue(pixel, channelValuesF);
setOpacity(pixel, 1.0, 1);
}
QImage KoColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
const KoColorProfile *dstProfile,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
QImage img = QImage(width, height, QImage::Format_ARGB32);
const KoColorSpace * dstCS = KoColorSpaceRegistry::instance()->rgb8(dstProfile);
if (data)
this->convertPixelsTo(const_cast<quint8 *>(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags);
return img;
}
bool KoColorSpace::preferCompositionInSourceColorSpace() const
{
return false;
}
diff --git a/libs/pigment/KoCompositeOp.cpp b/libs/pigment/KoCompositeOp.cpp
index 0b619a4af2..66f3e8352e 100644
--- a/libs/pigment/KoCompositeOp.cpp
+++ b/libs/pigment/KoCompositeOp.cpp
@@ -1,176 +1,188 @@
/*
* Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoCompositeOp.h"
#include <klocalizedstring.h>
#include <KoID.h>
#include <QList>
#include "KoColorSpace.h"
#include "KoColorSpaceMaths.h"
QString KoCompositeOp::categoryColor()
{
return i18n("Color");
}
QString KoCompositeOp::categoryArithmetic() { return i18n("Arithmetic"); }
QString KoCompositeOp::categoryNegative() { return i18n("Negative"); }
QString KoCompositeOp::categoryLight() { return i18n("Lighten"); }
QString KoCompositeOp::categoryDark() { return i18n("Darken"); }
QString KoCompositeOp::categoryHSY() { return i18n("HSY"); }
QString KoCompositeOp::categoryHSI() { return i18n("HSI"); }
QString KoCompositeOp::categoryHSL() { return i18n("HSL"); }
QString KoCompositeOp::categoryHSV() { return i18n("HSV"); }
QString KoCompositeOp::categoryMix() { return i18n("Mix"); }
QString KoCompositeOp::categoryMisc() { return i18n("Misc"); }
KoCompositeOp::ParameterInfo::ParameterInfo()
: opacity(1.0f),
flow(1.0f),
lastOpacity(&opacity)
{
}
KoCompositeOp::ParameterInfo::ParameterInfo(const ParameterInfo &rhs)
{
copy(rhs);
}
KoCompositeOp::ParameterInfo& KoCompositeOp::ParameterInfo::operator=(const ParameterInfo &rhs)
{
copy(rhs);
return *this;
}
+void KoCompositeOp::ParameterInfo::setOpacityAndAverage(float _opacity, float _averageOpacity)
+{
+ if (qFuzzyCompare(_opacity, _averageOpacity)) {
+ opacity = _opacity;
+ lastOpacity = &opacity;
+ } else {
+ opacity = _opacity;
+ _lastOpacityData = _averageOpacity;
+ lastOpacity = &_lastOpacityData;
+ }
+}
+
void KoCompositeOp::ParameterInfo::copy(const ParameterInfo &rhs)
{
dstRowStart = rhs.dstRowStart;
dstRowStride = rhs.dstRowStride;
srcRowStart = rhs.srcRowStart;
srcRowStride = rhs.srcRowStride;
maskRowStart = rhs.maskRowStart;
maskRowStride = rhs.maskRowStride;
rows = rhs.rows;
cols = rhs.cols;
opacity = rhs.opacity;
flow = rhs.flow;
_lastOpacityData = rhs._lastOpacityData;
channelFlags = rhs.channelFlags;
lastOpacity = rhs.lastOpacity == &rhs.opacity ?
&opacity : &_lastOpacityData;
}
void KoCompositeOp::ParameterInfo::updateOpacityAndAverage(float value) {
const float exponent = 0.1;
opacity = value;
if (*lastOpacity < opacity) {
lastOpacity = &opacity;
} else {
_lastOpacityData = exponent * opacity + (1.0 - exponent) * (*lastOpacity);
lastOpacity = &_lastOpacityData;
}
}
struct Q_DECL_HIDDEN KoCompositeOp::Private {
const KoColorSpace * colorSpace;
QString id;
QString description;
QString category;
QBitArray defaultChannelFlags;
};
KoCompositeOp::KoCompositeOp() : d(new Private)
{
}
KoCompositeOp::~KoCompositeOp()
{
delete d;
}
KoCompositeOp::KoCompositeOp(const KoColorSpace * cs, const QString& id, const QString& description, const QString & category)
: d(new Private)
{
d->colorSpace = cs;
d->id = id;
d->description = description;
d->category = category;
if (d->category.isEmpty()) {
d->category = categoryMisc();
}
}
void KoCompositeOp::composite(quint8 *dstRowStart, qint32 dstRowStride,
const quint8 *srcRowStart, qint32 srcRowStride,
const quint8 *maskRowStart, qint32 maskRowStride,
qint32 rows, qint32 numColumns,
quint8 opacity, const QBitArray& channelFlags) const
{
KoCompositeOp::ParameterInfo params;
params.dstRowStart = dstRowStart;
params.dstRowStride = dstRowStride;
params.srcRowStart = srcRowStart;
params.srcRowStride = srcRowStride;
params.maskRowStart = maskRowStart;
params.maskRowStride = maskRowStride;
params.rows = rows;
params.cols = numColumns;
params.opacity = float(opacity) / 255.0f;
params.flow = 1.0f;
params.channelFlags = channelFlags;
composite(params);
}
void KoCompositeOp::composite(const KoCompositeOp::ParameterInfo& params) const
{
using namespace Arithmetic;
composite(params.dstRowStart , params.dstRowStride ,
params.srcRowStart , params.srcRowStride ,
params.maskRowStart , params.maskRowStride,
params.rows , params.cols ,
scale<quint8>(params.opacity), params.channelFlags );
}
QString KoCompositeOp::category() const
{
return d->category;
}
QString KoCompositeOp::id() const
{
return d->id;
}
QString KoCompositeOp::description() const
{
return d->description;
}
const KoColorSpace * KoCompositeOp::colorSpace() const
{
return d->colorSpace;
}
diff --git a/libs/pigment/KoCompositeOp.h b/libs/pigment/KoCompositeOp.h
index 76a8b662d7..fb6fe18f56 100644
--- a/libs/pigment/KoCompositeOp.h
+++ b/libs/pigment/KoCompositeOp.h
@@ -1,144 +1,148 @@
/*
* Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOCOMPOSITEOP_H
#define KOCOMPOSITEOP_H
#include <QString>
#include <QList>
#include <QMultiMap>
#include <QBitArray>
+#include <boost/optional.hpp>
+
#include "kritapigment_export.h"
class KoColorSpace;
class KoColorSpace;
/**
* Base for colorspace-specific blending modes.
*/
class KRITAPIGMENT_EXPORT KoCompositeOp
{
public:
static QString categoryColor();
static QString categoryArithmetic();
static QString categoryNegative();
static QString categoryLight();
static QString categoryDark();
static QString categoryHSY();
static QString categoryHSI();
static QString categoryHSL();
static QString categoryHSV();
static QString categoryMix();
static QString categoryMisc();
struct KRITAPIGMENT_EXPORT ParameterInfo
{
ParameterInfo();
ParameterInfo(const ParameterInfo &rhs);
ParameterInfo& operator=(const ParameterInfo &rhs);
quint8* dstRowStart;
qint32 dstRowStride;
const quint8* srcRowStart;
qint32 srcRowStride;
const quint8* maskRowStart;
qint32 maskRowStride;
qint32 rows;
qint32 cols;
float opacity;
float flow;
float _lastOpacityData;
float* lastOpacity;
QBitArray channelFlags;
+ void setOpacityAndAverage(float _opacity, float _averageOpacity);
+
void updateOpacityAndAverage(float value);
private:
inline void copy(const ParameterInfo &rhs);
};
public:
/**
* @param cs a pointer to the color space that can be used with this composite op
* @param id the identifier for this composite op (not user visible)
* @param description a user visible string describing this composite operation
* @param category the name of the category where to put that composite op when displayed
* @param userVisible define whether or not that composite op should be visible in a user
* interface
*/
KoCompositeOp(const KoColorSpace * cs, const QString& id, const QString& description, const QString & category = KoCompositeOp::categoryMisc());
virtual ~KoCompositeOp();
/**
* @return the identifier of this composite op
*/
QString id() const;
/**
* @return the user visible string for this composite op
*/
QString description() const;
/**
* @return the color space that can use and own this composite op
*/
const KoColorSpace * colorSpace() const;
/**
* @return the category associated with the composite op
*/
QString category() const;
// WARNING: A derived class needs to overwrite at least one
// of the following virtual methods or a call to
// composite(...) will lead to an endless recursion/stack overflow
/**
* @param dstRowStart pointer to the start of the byte array we will composite the source on
* @param dstRowStride length of the rows of the block of destination pixels in bytes
* @param srcRowStart pointer to the start of the byte array we will mix with dest
* @param srcRowStride length of the rows of the block of src in bytes
* pixels (may be different from the rowstride of the dst pixels,
* in which case the smaller value is used). If srcRowStride is null
* it is assumed that the source is a constant color.
* @param maskRowStart start of the byte mask that determines whether and if so, then how much of src is used for blending
* @param maskRowStride length of the mask scanlines in bytes
* @param rows number of scanlines to blend
* @param numColumns length of the row of pixels in pixels
* @param opacity transparency with which to blend
* @param channelFlags a bit array that determines which channels should be processed (channels are in the order of the channels in the colorspace)
*/
virtual void composite(quint8 *dstRowStart, qint32 dstRowStride,
const quint8 *srcRowStart, qint32 srcRowStride,
const quint8 *maskRowStart, qint32 maskRowStride,
qint32 rows, qint32 numColumns,
quint8 opacity, const QBitArray& channelFlags=QBitArray()) const;
/**
* Same as previous, but uses a parameter structure
*/
virtual void composite(const ParameterInfo& params) const;
private:
KoCompositeOp();
struct Private;
Private* const d;
};
#endif // KOCOMPOSITEOP_H
diff --git a/libs/psd/asl/kis_asl_reader_utils.h b/libs/psd/asl/kis_asl_reader_utils.h
index 7dfc2ef2a8..469333f0a1 100644
--- a/libs/psd/asl/kis_asl_reader_utils.h
+++ b/libs/psd/asl/kis_asl_reader_utils.h
@@ -1,122 +1,122 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_ASL_READER_UTILS_H
#define __KIS_ASL_READER_UTILS_H
#include "psd_utils.h"
#include <stdexcept>
#include <string>
#include <QtEndian>
/**
* Default value for variable read from a file
*/
#define GARBAGE_VALUE_MARK 999
namespace KisAslReaderUtils {
/**
* Exception that is emitted when any parse error appear.
* Thanks to KisOffsetOnExitVerifier parsing can be continued
* most of the time, based on the offset values written in PSD.
*/
struct KRITAPSD_EXPORT ASLParseException : public std::runtime_error
{
ASLParseException(const QString &msg)
- : std::runtime_error(msg.toAscii().data())
+ : std::runtime_error(msg.toLatin1().data())
{
}
};
}
#define SAFE_READ_EX(device, varname) \
if (!psdread(device, &varname)) { \
QString msg = QString("Failed to read \'%1\' tag!").arg(#varname); \
throw KisAslReaderUtils::ASLParseException(msg); \
}
#define SAFE_READ_SIGNATURE_EX(device, varname, expected) \
if (!psdread(device, &varname) || varname != expected) { \
QString msg = QString("Failed to check signature \'%1\' tag!\n" \
"Value: \'%2\' Expected: \'%3\'") \
.arg(#varname).arg(varname).arg(expected); \
throw KisAslReaderUtils::ASLParseException(msg); \
}
#define SAFE_READ_SIGNATURE_2OPS_EX(device, varname, expected1, expected2) \
if (!psdread(device, &varname) || (varname != expected1 && varname != expected2)) { \
QString msg = QString("Failed to check signature \'%1\' tag!\n" \
"Value: \'%2\' Expected1: \'%3\' Expected2: \'%4\'") \
.arg(#varname).arg(varname).arg(expected1).arg(expected2); \
throw KisAslReaderUtils::ASLParseException(msg); \
}
template <typename T>
inline bool TRY_READ_SIGNATURE_2OPS_EX(QIODevice *device, T expected1, T expected2)
{
T var;
qint64 bytesRead = device->peek((char*)&var, sizeof(T));
if (bytesRead != sizeof(T)) {
return false;
}
var = qFromBigEndian<T>(var);
bool result = var == expected1 || var == expected2;
// If read successfully, adjust current position of the io device
if (result) {
// read, not seek, to support sequential devices
bytesRead = device->read((char*)&var, sizeof(T));
if (bytesRead != sizeof(T)) {
return false;
}
}
return result;
}
namespace KisAslReaderUtils {
/**
* String fetch functions
*
* ASL has 4 types of strings:
*
* - fixed length (4 bytes)
* - variable length (length (4 bytes) + string (var))
* - pascal (length (1 byte) + string (var))
* - unicode string (length (4 bytes) + null-terminated unicode string (var)
*/
KRITAPSD_EXPORT QString readFixedString(QIODevice *device);
KRITAPSD_EXPORT QString readVarString(QIODevice *device);
KRITAPSD_EXPORT QString readPascalString(QIODevice *device);
KRITAPSD_EXPORT QString readUnicodeString(QIODevice *device);
}
#endif /* __KIS_ASL_READER_UTILS_H */
diff --git a/libs/psd/asl/kis_asl_writer.cpp b/libs/psd/asl/kis_asl_writer.cpp
index e3c29b7a26..7a4052e2a3 100644
--- a/libs/psd/asl/kis_asl_writer.cpp
+++ b/libs/psd/asl/kis_asl_writer.cpp
@@ -1,278 +1,278 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_asl_writer.h"
#include <QDomDocument>
#include <QIODevice>
#include "kis_dom_utils.h"
#include "kis_debug.h"
#include "psd_utils.h"
#include "kis_asl_patterns_writer.h"
#include "kis_asl_writer_utils.h"
namespace Private {
using namespace KisAslWriterUtils;
void parseElement(const QDomElement &el, QIODevice *device, bool forceTypeInfo = false)
{
KIS_ASSERT_RECOVER_RETURN(el.tagName() == "node");
QString type = el.attribute("type", "<unknown>");
QString key = el.attribute("key", "");
// should be filtered on a heigher level
KIS_ASSERT_RECOVER_RETURN(key != "Patterns");
if (type == "Descriptor") {
if (!key.isEmpty()) {
writeVarString(key, device);
}
if (!key.isEmpty() || forceTypeInfo) {
writeFixedString("Objc", device);
}
QString classId = el.attribute("classId", "");
QString name = el.attribute("name", "");
writeUnicodeString(name, device);
writeVarString(classId, device);
quint32 numChildren = el.childNodes().size();
SAFE_WRITE_EX(device, numChildren);
QDomNode child = el.firstChild();
while (!child.isNull()) {
parseElement(child.toElement(), device);
child = child.nextSibling();
}
} else if (type == "List") {
writeVarString(key, device);
writeFixedString("VlLs", device);
quint32 numChildren = el.childNodes().size();
SAFE_WRITE_EX(device, numChildren);
QDomNode child = el.firstChild();
while (!child.isNull()) {
parseElement(child.toElement(), device, true);
child = child.nextSibling();
}
} else if (type == "Double") {
double v = KisDomUtils::toDouble(el.attribute("value", "0"));
writeVarString(key, device);
writeFixedString("doub", device);
SAFE_WRITE_EX(device, v);
} else if (type == "UnitFloat") {
double v = KisDomUtils::toDouble(el.attribute("value", "0"));
QString unit = el.attribute("unit", "#Pxl");
writeVarString(key, device);
writeFixedString("UntF", device);
writeFixedString(unit, device);
SAFE_WRITE_EX(device, v);
} else if (type == "Text") {
QString v = el.attribute("value", "");
writeVarString(key, device);
writeFixedString("TEXT", device);
writeUnicodeString(v, device);
} else if (type == "Enum") {
QString v = el.attribute("value", "");
QString typeId = el.attribute("typeId", "DEAD");
writeVarString(key, device);
writeFixedString("enum", device);
writeVarString(typeId, device);
writeVarString(v, device);
} else if (type == "Integer") {
quint32 v = KisDomUtils::toInt(el.attribute("value", "0"));
writeVarString(key, device);
writeFixedString("long", device);
SAFE_WRITE_EX(device, v);
} else if (type == "Boolean") {
quint8 v = KisDomUtils::toInt(el.attribute("value", "0"));
writeVarString(key, device);
writeFixedString("bool", device);
SAFE_WRITE_EX(device, v);
} else {
warnKrita << "WARNING: XML (ASL) Unknown element type:" << type << ppVar(key);
}
}
int calculateNumStyles(const QDomElement &root)
{
int numStyles = 0;
QDomNode child = root.firstChild();
while (!child.isNull()) {
QDomElement el = child.toElement();
QString classId = el.attribute("classId", "");
if (classId == "null") {
numStyles++;
}
child = child.nextSibling();
}
return numStyles;
}
void writeFileImpl(QIODevice *device, const QDomDocument &doc)
{
{
quint16 stylesVersion = 2;
SAFE_WRITE_EX(device, stylesVersion);
}
{
QString signature("8BSL");
- if (!device->write(signature.toAscii().data(), 4)) {
+ if (!device->write(signature.toLatin1().data(), 4)) {
throw ASLWriteException("Failed to write ASL signature");
}
}
{
quint16 patternsVersion = 3;
SAFE_WRITE_EX(device, patternsVersion);
}
{
KisAslWriterUtils::OffsetStreamPusher<quint32> patternsSizeField(device);
KisAslPatternsWriter patternsWriter(doc, device);
patternsWriter.writePatterns();
}
QDomElement root = doc.documentElement();
KIS_ASSERT_RECOVER_RETURN(root.tagName() == "asl");
int numStyles = calculateNumStyles(root);
KIS_ASSERT_RECOVER_RETURN(numStyles > 0);
{
quint32 numStylesTag = numStyles;
SAFE_WRITE_EX(device, numStylesTag);
}
QDomNode child = root.firstChild();
for (int styleIndex = 0; styleIndex < numStyles; styleIndex++) {
KisAslWriterUtils::OffsetStreamPusher<quint32> theOnlyStyleSizeField(device);
KIS_ASSERT_RECOVER_RETURN(!child.isNull());
{
quint32 stylesFormatVersion = 16;
SAFE_WRITE_EX(device, stylesFormatVersion);
}
while (!child.isNull()) {
QDomElement el = child.toElement();
QString key = el.attribute("key", "");
if (key != "Patterns") break;
child = child.nextSibling();
}
parseElement(child.toElement(), device);
child = child.nextSibling();
{
quint32 stylesFormatVersion = 16;
SAFE_WRITE_EX(device, stylesFormatVersion);
}
parseElement(child.toElement(), device);
child = child.nextSibling();
// ASL files' size should be 4-bytes aligned
const qint64 paddingSize = 4 - (device->pos() & 0x3);
if (paddingSize != 4) {
QByteArray padding(paddingSize, '\0');
device->write(padding);
}
}
}
void writePsdLfx2SectionImpl(QIODevice *device, const QDomDocument &doc)
{
QDomElement root = doc.documentElement();
KIS_ASSERT_RECOVER_RETURN(root.tagName() == "asl");
int numStyles = calculateNumStyles(root);
KIS_ASSERT_RECOVER_RETURN(numStyles == 1);
{
quint32 objectEffectsVersion = 0;
SAFE_WRITE_EX(device, objectEffectsVersion);
}
{
quint32 descriptorVersion = 16;
SAFE_WRITE_EX(device, descriptorVersion);
}
QDomNode child = root.firstChild();
while (!child.isNull()) {
QDomElement el = child.toElement();
QString key = el.attribute("key", "");
if (key != "Patterns") break;
child = child.nextSibling();
}
parseElement(child.toElement(), device);
child = child.nextSibling();
// ASL files' size should be 4-bytes aligned
const qint64 paddingSize = 4 - (device->pos() & 0x3);
if (paddingSize != 4) {
QByteArray padding(paddingSize, '\0');
device->write(padding);
}
}
} // namespace
void KisAslWriter::writeFile(QIODevice *device, const QDomDocument &doc)
{
try {
Private::writeFileImpl(device, doc);
} catch (Private::ASLWriteException &e) {
warnKrita << "WARNING: ASL:" << e.what();
}
}
void KisAslWriter::writePsdLfx2SectionEx(QIODevice *device, const QDomDocument &doc)
{
Private::writePsdLfx2SectionImpl(device, doc);
}
diff --git a/libs/psd/asl/kis_asl_writer_utils.cpp b/libs/psd/asl/kis_asl_writer_utils.cpp
index e3f362ddf4..806314586f 100644
--- a/libs/psd/asl/kis_asl_writer_utils.cpp
+++ b/libs/psd/asl/kis_asl_writer_utils.cpp
@@ -1,111 +1,111 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_asl_writer_utils.h"
#include <QUuid>
#include <resources/KoPattern.h>
namespace KisAslWriterUtils {
void writeRect(const QRect &rect, QIODevice *device)
{
{
const quint32 rectY0 = rect.y();
SAFE_WRITE_EX(device, rectY0);
}
{
const quint32 rectX0 = rect.x();
SAFE_WRITE_EX(device, rectX0);
}
{
const quint32 rectY1 = rect.y() + rect.height();
SAFE_WRITE_EX(device, rectY1);
}
{
const quint32 rectX1 = rect.x() + rect.width();
SAFE_WRITE_EX(device, rectX1);
}
}
void writeUnicodeString(const QString &value, QIODevice *device)
{
quint32 len = value.length() + 1;
SAFE_WRITE_EX(device, len);
const quint16 *ptr = value.utf16();
for (quint32 i = 0; i < len; i++) {
SAFE_WRITE_EX(device, ptr[i]);
}
}
void writeVarString(const QString &value, QIODevice *device)
{
quint32 lenTag = value.length() != 4 ? value.length() : 0;
SAFE_WRITE_EX(device, lenTag);
- if (!device->write(value.toAscii().data(), value.length())) {
+ if (!device->write(value.toLatin1().data(), value.length())) {
warnKrita << "WARNING: ASL: Failed to write ASL string" << ppVar(value);
return;
}
}
void writePascalString(const QString &value, QIODevice *device)
{
quint8 lenTag = value.length();
SAFE_WRITE_EX(device, lenTag);
- if (!device->write(value.toAscii().data(), value.length())) {
+ if (!device->write(value.toLatin1().data(), value.length())) {
warnKrita << "WARNING: ASL: Failed to write ASL string" << ppVar(value);
return;
}
}
void writeFixedString(const QString &value, QIODevice *device)
{
KIS_ASSERT_RECOVER_RETURN(value.length() == 4);
- if (!device->write(value.toAscii().data(), value.length())) {
+ if (!device->write(value.toLatin1().data(), value.length())) {
warnKrita << "WARNING: ASL: Failed to write ASL string" << ppVar(value);
return;
}
}
// Write UUID fetched from the file name or generate
QString getPatternUuidLazy(const KoPattern *pattern)
{
QUuid uuid;
QString patternFileName = pattern->filename();
if (patternFileName.endsWith(".pat", Qt::CaseInsensitive)) {
QString strUuid = patternFileName.left(patternFileName.size() - 4);
uuid = QUuid(strUuid);
}
if (uuid.isNull()) {
warnKrita << "WARNING: Saved pattern doesn't have a UUID, generating...";
warnKrita << ppVar(patternFileName) << ppVar(pattern->name());
uuid = QUuid::createUuid();
}
return uuid.toString().mid(1, 36);
}
}
diff --git a/libs/psd/asl/kis_asl_writer_utils.h b/libs/psd/asl/kis_asl_writer_utils.h
index 4053dd348a..bd1c68dab7 100644
--- a/libs/psd/asl/kis_asl_writer_utils.h
+++ b/libs/psd/asl/kis_asl_writer_utils.h
@@ -1,137 +1,137 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_ASL_WRITER_UTILS_H
#define __KIS_ASL_WRITER_UTILS_H
#include <stdexcept>
#include <string>
#include <QIODevice>
#include "psd_utils.h"
#include "kis_debug.h"
#include "kritapsd_export.h"
namespace KisAslWriterUtils {
/**
* Exception that is emitted when any write error appear.
*/
struct KRITAPSD_EXPORT ASLWriteException : public std::runtime_error
{
ASLWriteException(const QString &msg)
- : std::runtime_error(msg.toAscii().data())
+ : std::runtime_error(msg.toLatin1().data())
{
}
};
}
#define SAFE_WRITE_EX(device, varname) \
if (!psdwrite(device, varname)) { \
QString msg = QString("Failed to write \'%1\' tag!").arg(#varname); \
throw KisAslWriterUtils::ASLWriteException(msg); \
}
namespace KisAslWriterUtils {
KRITAPSD_EXPORT void writeRect(const QRect &rect, QIODevice *device);
KRITAPSD_EXPORT void writeUnicodeString(const QString &value, QIODevice *device);
KRITAPSD_EXPORT void writeVarString(const QString &value, QIODevice *device);
KRITAPSD_EXPORT void writePascalString(const QString &value, QIODevice *device);
KRITAPSD_EXPORT void writeFixedString(const QString &value, QIODevice *device);
KRITAPSD_EXPORT QString getPatternUuidLazy(const KoPattern *pattern);
/**
* Align the pointer \p pos by alignment. Grow the pointer
* if needed.
*
* \return the lowest integer not smaller than \p pos that divides by
* alignment
*/
inline qint64 alignOffsetCeil(qint64 pos, qint64 alignment)
{
qint64 mask = alignment - 1;
return (pos + mask) & ~mask;
}
template <class OffsetType>
class OffsetStreamPusher
{
public:
OffsetStreamPusher(QIODevice *device, qint64 alignOnExit = 0, qint64 externalSizeTagOffset = -1)
: m_device(device),
m_alignOnExit(alignOnExit),
m_externalSizeTagOffset(externalSizeTagOffset)
{
m_chunkStartPos = m_device->pos();
if (externalSizeTagOffset < 0) {
const OffsetType fakeObjectSize = OffsetType(0xdeadbeef);
SAFE_WRITE_EX(m_device, fakeObjectSize);
}
}
~OffsetStreamPusher() {
try {
if (m_alignOnExit) {
qint64 currentPos = m_device->pos();
const qint64 alignedPos = alignOffsetCeil(currentPos, m_alignOnExit);
for (; currentPos < alignedPos; currentPos++) {
quint8 padding = 0;
SAFE_WRITE_EX(m_device, padding);
}
}
const qint64 currentPos = m_device->pos();
qint64 writtenDataSize = 0;
qint64 sizeFiledOffset = 0;
if (m_externalSizeTagOffset >= 0) {
writtenDataSize = currentPos - m_chunkStartPos;
sizeFiledOffset = m_externalSizeTagOffset;
} else {
writtenDataSize = currentPos - m_chunkStartPos - sizeof(OffsetType);
sizeFiledOffset = m_chunkStartPos;
}
m_device->seek(sizeFiledOffset);
const OffsetType realObjectSize = writtenDataSize;
SAFE_WRITE_EX(m_device, realObjectSize);
m_device->seek(currentPos);
}
catch(ASLWriteException &e) {
warnKrita << PREPEND_METHOD(e.what());
}
}
private:
qint64 m_chunkStartPos;
QIODevice *m_device;
qint64 m_alignOnExit;
qint64 m_externalSizeTagOffset;
};
}
#endif /* __KIS_ASL_WRITER_UTILS_H */
diff --git a/libs/psd/asl/kis_asl_xml_parser.cpp b/libs/psd/asl/kis_asl_xml_parser.cpp
index 7f53c00827..dde244badb 100644
--- a/libs/psd/asl/kis_asl_xml_parser.cpp
+++ b/libs/psd/asl/kis_asl_xml_parser.cpp
@@ -1,556 +1,556 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_asl_xml_parser.h"
#include <boost/function.hpp>
#include <stdexcept>
#include <string>
#include <QDomDocument>
#include <QIODevice>
#include <QBuffer>
#include <QColor>
#include <QHash>
#include <KoColorSpaceRegistry.h>
#include <resources/KoSegmentGradient.h>
#include "kis_dom_utils.h"
#include "kis_debug.h"
#include "psd_utils.h"
#include "psd.h"
#include "compression.h"
#include "kis_asl_object_catcher.h"
namespace Private {
void parseElement(const QDomElement &el,
const QString &parentPath,
KisAslObjectCatcher &catcher);
class CurveObjectCatcher : public KisAslObjectCatcher
{
public:
void addText(const QString &path, const QString &value) override {
if (path == "/Nm ") {
m_name = value;
} else {
warnKrita << "XML (ASL): failed to parse curve object" << path << value;
}
}
void addPoint(const QString &path, const QPointF &value) override {
if (!m_arrayMode) {
warnKrita << "XML (ASL): failed to parse curve object (array fault)" << path << value << ppVar(m_arrayMode);
}
m_points.append(value);
}
public:
QVector<QPointF> m_points;
QString m_name;
};
QColor parseRGBColorObject(QDomElement parent)
{
QColor color(Qt::black);
QDomNode child = parent.firstChild();
while (!child.isNull()) {
QDomElement childEl = child.toElement();
QString type = childEl.attribute("type", "<unknown>");
QString key = childEl.attribute("key", "");
if (type != "Double") {
warnKrita << "Unknown color component type:" << ppVar(type) << ppVar(key);
return Qt::red;
}
double value = KisDomUtils::toDouble(childEl.attribute("value", "0"));
if (key == "Rd ") {
color.setRed(value);
} else if (key == "Grn ") {
color.setGreen(value);
} else if (key == "Bl ") {
color.setBlue(value);
} else {
warnKrita << "Unknown color key value:" << ppVar(key);
return Qt::red;
}
child = child.nextSibling();
}
return color;
}
void parseColorStopsList(QDomElement parent,
QVector<qreal> &startLocations,
QVector<qreal> &middleOffsets,
QVector<QColor> &colors)
{
QDomNode child = parent.firstChild();
while (!child.isNull()) {
QDomElement childEl = child.toElement();
QString type = childEl.attribute("type", "<unknown>");
QString key = childEl.attribute("key", "");
QString classId = childEl.attribute("classId", "");
if (type == "Descriptor" && classId == "Clrt") {
// sorry for naming...
QDomNode child = childEl.firstChild();
while (!child.isNull()) {
QDomElement childEl = child.toElement();
QString type = childEl.attribute("type", "<unknown>");
QString key = childEl.attribute("key", "");
QString classId = childEl.attribute("classId", "");
if (type == "Integer" && key == "Lctn") {
int value = KisDomUtils::toInt(childEl.attribute("value", "0"));
startLocations.append(qreal(value) / 4096.0);
} else if (type == "Integer" && key == "Mdpn") {
int value = KisDomUtils::toInt(childEl.attribute("value", "0"));
middleOffsets.append(qreal(value) / 100.0);
} else if (type == "Descriptor" && key == "Clr ") {
colors.append(parseRGBColorObject(childEl));
} else if (type == "Enum" && key == "Type") {
QString typeId = childEl.attribute("typeId", "");
if (typeId != "Clry") {
warnKrita << "WARNING: Invalid typeId of a greadient stop type" << typeId;
}
QString value = childEl.attribute("value", "");
if (value == "BckC" || value == "FrgC") {
warnKrita << "WARNING: Using foreground/background colors in ASL gradients is not yet supported";
}
}
child = child.nextSibling();
}
} else {
warnKrita << "WARNING: Unrecognized object in color stops list" << ppVar(type) << ppVar(key) << ppVar(classId);
}
child = child.nextSibling();
}
}
void parseTransparencyStopsList(QDomElement parent,
QVector<qreal> &startLocations,
QVector<qreal> &middleOffsets,
QVector<qreal> &transparencies)
{
QDomNode child = parent.firstChild();
while (!child.isNull()) {
QDomElement childEl = child.toElement();
QString type = childEl.attribute("type", "<unknown>");
QString key = childEl.attribute("key", "");
QString classId = childEl.attribute("classId", "");
if (type == "Descriptor" && classId == "TrnS") {
// sorry for naming again...
QDomNode child = childEl.firstChild();
while (!child.isNull()) {
QDomElement childEl = child.toElement();
QString type = childEl.attribute("type", "<unknown>");
QString key = childEl.attribute("key", "");
if (type == "Integer" && key == "Lctn") {
int value = KisDomUtils::toInt(childEl.attribute("value", "0"));
startLocations.append(qreal(value) / 4096.0);
} else if (type == "Integer" && key == "Mdpn") {
int value = KisDomUtils::toInt(childEl.attribute("value", "0"));
middleOffsets.append(qreal(value) / 100.0);
} else if (type == "UnitFloat" && key == "Opct") {
QString unit = childEl.attribute("unit", "");
if (unit != "#Prc") {
warnKrita << "WARNING: Invalid unit of a greadient stop transparency" << unit;
}
qreal value = KisDomUtils::toDouble(childEl.attribute("value", "100"));
transparencies.append(value / 100.0);
}
child = child.nextSibling();
}
} else {
warnKrita << "WARNING: Unrecognized object in transparency stops list" << ppVar(type) << ppVar(key) << ppVar(classId);
}
child = child.nextSibling();
}
}
inline QString buildPath(const QString &parent, const QString &key) {
return parent + "/" + key;
}
bool tryParseDescriptor(const QDomElement &el,
const QString &path,
const QString &classId,
KisAslObjectCatcher &catcher)
{
bool retval = true;
if (classId == "null") {
catcher.newStyleStarted();
// here we just notify that a new style is started, we haven't
// processed the whole block yet, so return false.
retval = false;
} else if (classId == "RGBC") {
catcher.addColor(path, parseRGBColorObject(el));
} else if (classId == "ShpC") {
CurveObjectCatcher curveCatcher;
QDomNode child = el.firstChild();
while (!child.isNull()) {
parseElement(child.toElement(), "", curveCatcher);
child = child.nextSibling();
}
catcher.addCurve(path, curveCatcher.m_name, curveCatcher.m_points);
} else if (classId == "CrPt") {
QPointF point;
QDomNode child = el.firstChild();
while (!child.isNull()) {
QDomElement childEl = child.toElement();
QString type = childEl.attribute("type", "<unknown>");
QString key = childEl.attribute("key", "");
if (type == "Boolean" && key == "Cnty") {
warnKrita << "WARNING: tryParseDescriptor: The points of the curve object contain \'Cnty\' flag which is unsupported by Krita";
warnKrita << " " << ppVar(type) << ppVar(key) << ppVar(path);
child = child.nextSibling();
continue;
}
if (type != "Double") {
warnKrita << "Unknown point component type:" << ppVar(type) << ppVar(key) << ppVar(path);
return false;
}
double value = KisDomUtils::toDouble(childEl.attribute("value", "0"));
if (key == "Hrzn") {
point.setX(value);
} else if (key == "Vrtc") {
point.setY(value);
} else {
warnKrita << "Unknown point key value:" << ppVar(key) << ppVar(path);
return false;
}
child = child.nextSibling();
}
catcher.addPoint(path, point);
} else if (classId == "Pnt ") {
QPointF point;
QDomNode child = el.firstChild();
while (!child.isNull()) {
QDomElement childEl = child.toElement();
QString type = childEl.attribute("type", "<unknown>");
QString key = childEl.attribute("key", "");
QString unit = childEl.attribute("unit", "");
if (type != "Double" && !(type == "UnitFloat" && unit == "#Prc")) {
warnKrita << "Unknown point component type:" << ppVar(unit) << ppVar(type) << ppVar(key) << ppVar(path);
return false;
}
double value = KisDomUtils::toDouble(childEl.attribute("value", "0"));
if (key == "Hrzn") {
point.setX(value);
} else if (key == "Vrtc") {
point.setY(value);
} else {
warnKrita << "Unknown point key value:" << ppVar(key) << ppVar(path);
return false;
}
child = child.nextSibling();
}
catcher.addPoint(path, point);
} else if (classId == "KisPattern") {
QByteArray patternData;
QString patternUuid;
QDomNode child = el.firstChild();
while (!child.isNull()) {
QDomElement childEl = child.toElement();
QString type = childEl.attribute("type", "<unknown>");
QString key = childEl.attribute("key", "");
if (type == "Text" && key == "Idnt") {
patternUuid = childEl.attribute("value", "");
}
if (type == "KisPatternData" && key == "Data") {
QDomNode dataNode = child.firstChild();
if (!dataNode.isCDATASection()) {
warnKrita << "WARNING: failed to parse KisPatternData XML section!";
continue;
}
QDomCDATASection dataSection = dataNode.toCDATASection();
- QByteArray data = dataSection.data().toAscii();
+ QByteArray data = dataSection.data().toLatin1();
data = QByteArray::fromBase64(data);
data = qUncompress(data);
if (data.isEmpty()) {
warnKrita << "WARNING: failed to parse KisPatternData XML section!";
continue;
}
patternData = data;
}
child = child.nextSibling();
}
if (!patternUuid.isEmpty() && !patternData.isEmpty()) {
QString fileName = QString("%1.pat").arg(patternUuid);
QScopedPointer<KoPattern> pattern(new KoPattern(fileName));
QBuffer buffer(&patternData);
buffer.open(QIODevice::ReadOnly);
pattern->loadPatFromDevice(&buffer);
catcher.addPattern(path, pattern.data());
} else {
warnKrita << "WARNING: failed to load KisPattern XML section!" << ppVar(patternUuid);
}
} else if (classId == "Ptrn") { // reference to an existing pattern
QString patternUuid;
QString patternName;
QDomNode child = el.firstChild();
while (!child.isNull()) {
QDomElement childEl = child.toElement();
QString type = childEl.attribute("type", "<unknown>");
QString key = childEl.attribute("key", "");
if (type == "Text" && key == "Idnt") {
patternUuid = childEl.attribute("value", "");
} else if (type == "Text" && key == "Nm ") {
patternName = childEl.attribute("value", "");
} else {
warnKrita << "WARNING: unrecognized pattern-ref section key:" << ppVar(type) << ppVar(key);
}
child = child.nextSibling();
}
catcher.addPatternRef(path, patternUuid, patternName);
} else if (classId == "Grdn") {
QString gradientName;
qreal gradientSmoothness = 100.0;
QVector<qreal> startLocations;
QVector<qreal> middleOffsets;
QVector<QColor> colors;
QVector<qreal> transpStartLocations;
QVector<qreal> transpMiddleOffsets;
QVector<qreal> transparencies;
QDomNode child = el.firstChild();
while (!child.isNull()) {
QDomElement childEl = child.toElement();
QString type = childEl.attribute("type", "<unknown>");
QString key = childEl.attribute("key", "");
if (type == "Text" && key == "Nm ") {
gradientName = childEl.attribute("value", "");
} else if (type == "Enum" && key == "GrdF") {
QString typeId = childEl.attribute("typeId", "");
QString value = childEl.attribute("value", "");
if (typeId != "GrdF" || value != "CstS") {
warnKrita << "WARNING: Unsupported gradient type (porbably, noise-based):" << value;
return true;
}
} else if (type == "Double" && key == "Intr") {
double value = KisDomUtils::toDouble(childEl.attribute("value", "4096"));
gradientSmoothness = 100.0 * value / 4096.0;
} else if (type == "List" && key == "Clrs") {
parseColorStopsList(childEl, startLocations, middleOffsets, colors);
} else if (type == "List" && key == "Trns") {
parseTransparencyStopsList(childEl, transpStartLocations, transpMiddleOffsets, transparencies);
}
child = child.nextSibling();
}
if (colors.size() < 2) {
warnKrita << "WARNING: ASL gradient has too few stops" << ppVar(colors.size());
}
if (colors.size() != transparencies.size()) {
warnKrita << "WARNING: ASL gradient has inconsistent number of transparency stops. Dropping transparency..." << ppVar(colors.size()) << ppVar(transparencies.size());
transparencies.resize(colors.size());
for (int i = 0; i < colors.size(); i++) {
transparencies[i] = 1.0;
}
}
QString fileName = gradientName + ".ggr";
QSharedPointer<KoSegmentGradient> gradient(new KoSegmentGradient(fileName));
Q_UNUSED(gradientSmoothness);
gradient->setName(gradientName);
for (int i = 1; i < colors.size(); i++) {
QColor startColor = colors[i-1];
QColor endColor = colors[i];
startColor.setAlphaF(transparencies[i-1]);
endColor.setAlphaF(transparencies[i]);
qreal start = startLocations[i-1];
qreal end = startLocations[i];
qreal middle = start + middleOffsets[i-1] * (end - start);
gradient->createSegment(INTERP_LINEAR, COLOR_INTERP_RGB,
start, end, middle,
startColor,
endColor);
}
gradient->setValid(true);
catcher.addGradient(path, gradient);
} else {
retval = false;
}
return retval;
}
void parseElement(const QDomElement &el, const QString &parentPath, KisAslObjectCatcher &catcher)
{
KIS_ASSERT_RECOVER_RETURN(el.tagName() == "node");
QString type = el.attribute("type", "<unknown>");
QString key = el.attribute("key", "");
if (type == "Descriptor") {
QString classId = el.attribute("classId", "<noClassId>");
QString containerName = key.isEmpty() ? classId : key;
QString containerPath = buildPath(parentPath, containerName);
if (!tryParseDescriptor(el, containerPath, classId, catcher)) {
QDomNode child = el.firstChild();
while (!child.isNull()) {
parseElement(child.toElement(), containerPath, catcher);
child = child.nextSibling();
}
}
} else if (type == "List") {
catcher.setArrayMode(true);
QString containerName = key;
QString containerPath = buildPath(parentPath, containerName);
QDomNode child = el.firstChild();
while (!child.isNull()) {
parseElement(child.toElement(), containerPath, catcher);
child = child.nextSibling();
}
catcher.setArrayMode(false);
} else if (type == "Double") {
double v = KisDomUtils::toDouble(el.attribute("value", "0"));
catcher.addDouble(buildPath(parentPath, key), v);
} else if (type == "UnitFloat") {
QString unit = el.attribute("unit", "<unknown>");
double v = KisDomUtils::toDouble(el.attribute("value", "0"));
catcher.addUnitFloat(buildPath(parentPath, key), unit, v);
} else if (type == "Text") {
QString v = el.attribute("value", "");
catcher.addText(buildPath(parentPath, key), v);
} else if (type == "Enum") {
QString v = el.attribute("value", "");
QString typeId = el.attribute("typeId", "<unknown>");
catcher.addEnum(buildPath(parentPath, key), typeId, v);
} else if (type == "Integer") {
int v = KisDomUtils::toInt(el.attribute("value", "0"));
catcher.addInteger(buildPath(parentPath, key), v);
} else if (type == "Boolean") {
int v = KisDomUtils::toInt(el.attribute("value", "0"));
catcher.addBoolean(buildPath(parentPath, key), v);
} else {
warnKrita << "WARNING: XML (ASL) Unknown element type:" << type << ppVar(parentPath) << ppVar(key);
}
}
} // namespace
void KisAslXmlParser::parseXML(const QDomDocument &doc, KisAslObjectCatcher &catcher)
{
QDomElement root = doc.documentElement();
if (root.tagName() != "asl") {
return;
}
QDomNode child = root.firstChild();
while (!child.isNull()) {
Private::parseElement(child.toElement(), "", catcher);
child = child.nextSibling();
}
}
diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index a73e5355eb..ccbe871f0a 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -1,573 +1,577 @@
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile
${EXIV2_INCLUDE_DIR}
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${OCIO_INCLUDE_DIR}
${Boost_INCLUDE_DIRS}
)
add_subdirectory( tests )
if (APPLE)
find_library(FOUNDATION_LIBRARY Foundation)
find_library(APPKIT_LIBRARY AppKit)
endif ()
set(kritaui_LIB_SRCS
canvas/kis_canvas_widget_base.cpp
canvas/kis_canvas2.cpp
canvas/kis_canvas_updates_compressor.cpp
canvas/kis_canvas_controller.cpp
canvas/kis_paintop_transformation_connector.cpp
canvas/kis_display_color_converter.cpp
canvas/kis_display_filter.cpp
canvas/kis_exposure_gamma_correction_interface.cpp
canvas/kis_tool_proxy.cpp
canvas/kis_canvas_decoration.cc
canvas/kis_coordinates_converter.cpp
canvas/kis_grid_manager.cpp
canvas/kis_grid_decoration.cpp
canvas/kis_grid_config.cpp
canvas/kis_prescaled_projection.cpp
canvas/kis_qpainter_canvas.cpp
canvas/kis_projection_backend.cpp
canvas/kis_update_info.cpp
canvas/kis_image_patch.cpp
canvas/kis_image_pyramid.cpp
canvas/kis_infinity_manager.cpp
canvas/kis_change_guides_command.cpp
canvas/kis_guides_decoration.cpp
canvas/kis_guides_manager.cpp
canvas/kis_guides_config.cpp
canvas/kis_snap_config.cpp
canvas/kis_snap_line_strategy.cpp
canvas/KisSnapPointStrategy.cpp
dialogs/kis_about_application.cpp
dialogs/kis_dlg_adj_layer_props.cc
dialogs/kis_dlg_adjustment_layer.cc
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_generator_layer.cpp
dialogs/kis_dlg_file_layer.cpp
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_stroke_selection_properties.cpp
dialogs/kis_dlg_image_properties.cc
dialogs/kis_dlg_layer_properties.cc
dialogs/kis_dlg_preferences.cc
dialogs/slider_and_spin_box_sync.cpp
dialogs/kis_dlg_blacklist_cleanup.cpp
dialogs/kis_dlg_layer_style.cpp
dialogs/kis_dlg_png_import.cpp
dialogs/kis_dlg_import_image_sequence.cpp
dialogs/kis_delayed_save_dialog.cpp
dialogs/kis_dlg_internal_color_selector.cpp
flake/kis_node_dummies_graph.cpp
flake/kis_dummies_facade_base.cpp
flake/kis_dummies_facade.cpp
flake/kis_node_shapes_graph.cpp
flake/kis_node_shape.cpp
flake/kis_shape_controller.cpp
flake/kis_shape_layer.cc
flake/kis_shape_layer_canvas.cpp
flake/kis_shape_selection.cpp
flake/kis_shape_selection_canvas.cpp
flake/kis_shape_selection_model.cpp
flake/kis_take_all_shapes_command.cpp
brushhud/kis_uniform_paintop_property_widget.cpp
brushhud/kis_brush_hud.cpp
brushhud/kis_round_hud_button.cpp
brushhud/kis_dlg_brush_hud_config.cpp
brushhud/kis_brush_hud_properties_list.cpp
brushhud/kis_brush_hud_properties_config.cpp
kis_aspect_ratio_locker.cpp
kis_autogradient.cc
kis_bookmarked_configurations_editor.cc
kis_bookmarked_configurations_model.cc
kis_bookmarked_filter_configurations_model.cc
kis_base_option.cpp
kis_canvas_resource_provider.cpp
kis_derived_resources.cpp
kis_categories_mapper.cpp
kis_categorized_list_model.cpp
kis_categorized_item_delegate.cpp
kis_clipboard.cc
kis_config.cc
kis_config_notifier.cpp
kis_control_frame.cpp
kis_composite_ops_model.cc
kis_paint_ops_model.cpp
kis_cursor.cc
kis_cursor_cache.cpp
kis_custom_pattern.cc
kis_file_layer.cpp
+ kis_change_file_layer_command.h
kis_safe_document_loader.cpp
kis_splash_screen.cpp
kis_filter_manager.cc
kis_filters_model.cc
kis_histogram_view.cc
KisImageBarrierLockerWithFeedback.cpp
kis_image_manager.cc
kis_image_view_converter.cpp
kis_import_catcher.cc
kis_layer_manager.cc
kis_mask_manager.cc
kis_mimedata.cpp
kis_node_commands_adapter.cpp
kis_node_manager.cpp
kis_node_juggler_compressed.cpp
kis_node_selection_adapter.cpp
kis_node_insertion_adapter.cpp
kis_node_model.cpp
kis_node_filter_proxy_model.cpp
kis_model_index_converter_base.cpp
kis_model_index_converter.cpp
kis_model_index_converter_show_all.cpp
kis_painting_assistant.cc
kis_painting_assistants_decoration.cpp
kis_painting_assistants_manager.cpp
kis_paintop_box.cc
kis_paintop_option.cpp
kis_paintop_options_model.cpp
kis_paintop_settings_widget.cpp
kis_popup_palette.cpp
kis_png_converter.cpp
kis_preference_set_registry.cpp
kis_script_manager.cpp
kis_resource_server_provider.cpp
KisSelectedShapesProxy.cpp
kis_selection_decoration.cc
kis_selection_manager.cc
kis_statusbar.cc
kis_zoom_manager.cc
kis_favorite_resource_manager.cpp
kis_workspace_resource.cpp
kis_action.cpp
kis_action_manager.cpp
kis_view_plugin.cpp
kis_canvas_controls_manager.cpp
kis_tooltip_manager.cpp
kis_multinode_property.cpp
kis_stopgradient_editor.cpp
kisexiv2/kis_exif_io.cpp
kisexiv2/kis_exiv2.cpp
kisexiv2/kis_iptc_io.cpp
kisexiv2/kis_xmp_io.cpp
opengl/kis_opengl.cpp
opengl/kis_opengl_canvas2.cpp
opengl/kis_opengl_canvas_debugger.cpp
opengl/kis_opengl_image_textures.cpp
opengl/kis_texture_tile.cpp
opengl/kis_opengl_shader_loader.cpp
+ opengl/kis_texture_tile_info_pool.cpp
kis_fps_decoration.cpp
recorder/kis_node_query_path_editor.cc
recorder/kis_recorded_action_creator.cc
recorder/kis_recorded_action_creator_factory.cc
recorder/kis_recorded_action_creator_factory_registry.cc
recorder/kis_recorded_action_editor_factory.cc
recorder/kis_recorded_action_editor_factory_registry.cc
recorder/kis_recorded_filter_action_editor.cc
recorder/kis_recorded_filter_action_creator.cpp
recorder/kis_recorded_paint_action_editor.cc
tool/kis_selection_tool_helper.cpp
tool/kis_selection_tool_config_widget_helper.cpp
tool/kis_rectangle_constraint_widget.cpp
tool/kis_shape_tool_helper.cpp
tool/kis_tool.cc
tool/kis_delegated_tool_policies.cpp
tool/kis_tool_freehand.cc
tool/kis_speed_smoother.cpp
tool/kis_painting_information_builder.cpp
tool/kis_stabilized_events_sampler.cpp
tool/kis_tool_freehand_helper.cpp
tool/kis_tool_multihand_helper.cpp
tool/kis_figure_painting_tool_helper.cpp
tool/kis_recording_adapter.cpp
tool/kis_tool_paint.cc
tool/kis_tool_shape.cc
tool/kis_tool_ellipse_base.cpp
tool/kis_tool_rectangle_base.cpp
tool/kis_tool_polyline_base.cpp
tool/kis_tool_utils.cpp
tool/kis_resources_snapshot.cpp
tool/kis_smoothing_options.cpp
tool/KisStabilizerDelayedPaintHelper.cpp
+ tool/KisStrokeSpeedMonitor.cpp
tool/strokes/freehand_stroke.cpp
+ tool/strokes/KisStrokeEfficiencyMeasurer.cpp
tool/strokes/kis_painter_based_stroke_strategy.cpp
tool/strokes/kis_filter_stroke_strategy.cpp
tool/strokes/kis_color_picker_stroke_strategy.cpp
widgets/kis_cmb_composite.cc
widgets/kis_cmb_contour.cpp
widgets/kis_cmb_gradient.cpp
widgets/kis_paintop_list_widget.cpp
widgets/kis_cmb_idlist.cc
widgets/kis_color_space_selector.cc
widgets/kis_advanced_color_space_selector.cc
widgets/kis_cie_tongue_widget.cpp
widgets/kis_tone_curve_widget.cpp
widgets/kis_curve_widget.cpp
widgets/kis_custom_image_widget.cc
widgets/kis_image_from_clipboard_widget.cpp
widgets/kis_double_widget.cc
widgets/kis_filter_selector_widget.cc
widgets/kis_gradient_chooser.cc
widgets/kis_gradient_slider_widget.cc
widgets/kis_gradient_slider.cpp
widgets/kis_iconwidget.cc
widgets/kis_mask_widgets.cpp
widgets/kis_meta_data_merge_strategy_chooser_widget.cc
widgets/kis_multi_bool_filter_widget.cc
widgets/kis_multi_double_filter_widget.cc
widgets/kis_multi_integer_filter_widget.cc
widgets/kis_multipliers_double_slider_spinbox.cpp
widgets/kis_paintop_presets_popup.cpp
widgets/kis_tool_options_popup.cpp
widgets/kis_paintop_presets_chooser_popup.cpp
widgets/kis_paintop_presets_save.cpp
widgets/kis_pattern_chooser.cc
widgets/kis_popup_button.cc
widgets/kis_preset_chooser.cpp
widgets/kis_progress_widget.cpp
widgets/kis_selection_options.cc
widgets/kis_scratch_pad.cpp
widgets/kis_scratch_pad_event_filter.cpp
widgets/kis_preset_selector_strip.cpp
widgets/kis_slider_spin_box.cpp
widgets/kis_size_group.cpp
widgets/kis_size_group_p.cpp
widgets/kis_wdg_generator.cpp
widgets/kis_workspace_chooser.cpp
widgets/squeezedcombobox.cpp
widgets/kis_categorized_list_view.cpp
widgets/kis_widget_chooser.cpp
widgets/kis_tool_button.cpp
widgets/kis_floating_message.cpp
widgets/kis_lod_availability_widget.cpp
widgets/kis_color_label_selector_widget.cpp
widgets/kis_color_filter_combo.cpp
widgets/kis_elided_label.cpp
widgets/kis_stopgradient_slider_widget.cpp
widgets/kis_spinbox_color_selector.cpp
widgets/kis_screen_color_picker.cpp
widgets/kis_preset_live_preview_view.cpp
widgets/KoDualColorButton.cpp
widgets/kis_color_input.cpp
widgets/kis_color_button.cpp
widgets/KisVisualColorSelector.cpp
widgets/KisVisualColorSelectorShape.cpp
widgets/KisVisualEllipticalSelectorShape.cpp
widgets/KisVisualRectangleSelectorShape.cpp
widgets/KisVisualTriangleSelectorShape.cpp
widgets/KoStrokeConfigWidget.cpp
widgets/KoFillConfigWidget.cpp
widgets/KoShapeFillWrapper.cpp
utils/kis_document_aware_spin_box_unit_manager.cpp
input/kis_input_manager.cpp
input/kis_input_manager_p.cpp
input/kis_extended_modifiers_mapper.cpp
input/kis_abstract_input_action.cpp
input/kis_tool_invocation_action.cpp
input/kis_pan_action.cpp
input/kis_alternate_invocation_action.cpp
input/kis_rotate_canvas_action.cpp
input/kis_zoom_action.cpp
input/kis_change_frame_action.cpp
input/kis_gamma_exposure_action.cpp
input/kis_show_palette_action.cpp
input/kis_change_primary_setting_action.cpp
input/kis_abstract_shortcut.cpp
input/kis_native_gesture_shortcut.cpp
input/kis_single_action_shortcut.cpp
input/kis_stroke_shortcut.cpp
input/kis_shortcut_matcher.cpp
input/kis_select_layer_action.cpp
input/KisQtWidgetsTweaker.cpp
operations/kis_operation.cpp
operations/kis_operation_configuration.cpp
operations/kis_operation_registry.cpp
operations/kis_operation_ui_factory.cpp
operations/kis_operation_ui_widget.cpp
operations/kis_filter_selection_operation.cpp
actions/kis_selection_action_factories.cpp
actions/KisPasteActionFactory.cpp
input/kis_touch_shortcut.cpp
kis_document_undo_store.cpp
kis_transaction_based_command.cpp
kis_gui_context_command.cpp
kis_gui_context_command_p.cpp
input/kis_tablet_debugger.cpp
input/kis_input_profile_manager.cpp
input/kis_input_profile.cpp
input/kis_shortcut_configuration.cpp
input/config/kis_input_configuration_page.cpp
input/config/kis_edit_profiles_dialog.cpp
input/config/kis_input_profile_model.cpp
input/config/kis_input_configuration_page_item.cpp
input/config/kis_action_shortcuts_model.cpp
input/config/kis_input_type_delegate.cpp
input/config/kis_input_mode_delegate.cpp
input/config/kis_input_button.cpp
input/config/kis_input_editor_delegate.cpp
input/config/kis_mouse_input_editor.cpp
input/config/kis_wheel_input_editor.cpp
input/config/kis_key_input_editor.cpp
processing/fill_processing_visitor.cpp
kis_asl_layer_style_serializer.cpp
kis_psd_layer_style_resource.cpp
canvas/kis_mirror_axis.cpp
kis_abstract_perspective_grid.cpp
KisApplication.cpp
KisAutoSaveRecoveryDialog.cpp
KisDetailsPane.cpp
KisDocument.cpp
KisNodeDelegate.cpp
kis_node_view_visibility_delegate.cpp
KisNodeToolTip.cpp
KisNodeView.cpp
kis_node_view_color_scheme.cpp
KisImportExportFilter.cpp
KisFilterEntry.cpp
KisImportExportManager.cpp
KisImportExportUtils.cpp
kis_async_action_feedback.cpp
KisMainWindow.cpp
KisOpenPane.cpp
KisPart.cpp
KisPrintJob.cpp
KisTemplate.cpp
KisTemplateCreateDia.cpp
KisTemplateGroup.cpp
KisTemplates.cpp
KisTemplatesPane.cpp
KisTemplateTree.cpp
KisUndoStackAction.cpp
KisView.cpp
thememanager.cpp
kis_mainwindow_observer.cpp
KisViewManager.cpp
kis_mirror_manager.cpp
qtlockedfile/qtlockedfile.cpp
qtsingleapplication/qtlocalpeer.cpp
qtsingleapplication/qtsingleapplication.cpp
KisResourceBundle.cpp
KisResourceBundleManifest.cpp
kis_md5_generator.cpp
KisApplicationArguments.cpp
KisNetworkAccessManager.cpp
KisMultiFeedRSSModel.cpp
KisRemoteFileFetcher.cpp
KisPaletteModel.cpp
kis_palette_delegate.cpp
kis_palette_view.cpp
KisColorsetChooser.cpp
KisSaveGroupVisitor.cpp
)
if(WIN32)
if (NOT Qt5Gui_PRIVATE_INCLUDE_DIRS)
message(FATAL_ERROR "Qt5Gui Private header are missing!")
endif()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/kis_tablet_event.cpp
input/wintab/kis_tablet_support_win.cpp
input/wintab/kis_screen_size_choice_dialog.cpp
qtlockedfile/qtlockedfile_win.cpp
input/wintab/kis_tablet_support_win8.cpp
)
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
kis_animation_frame_cache.cpp
kis_animation_cache_populator.cpp
KisAsyncAnimationRendererBase.cpp
KisAsyncAnimationCacheRenderer.cpp
KisAsyncAnimationFramesSavingRenderer.cpp
dialogs/KisAsyncAnimationRenderDialogBase.cpp
dialogs/KisAsyncAnimationCacheRenderDialog.cpp
dialogs/KisAsyncAnimationFramesSaveDialog.cpp
canvas/kis_animation_player.cpp
kis_animation_importer.cpp
KisSyncedAudioPlayback.cpp
)
if(UNIX)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/kis_tablet_event.cpp
input/wintab/kis_tablet_support.cpp
qtlockedfile/qtlockedfile_unix.cpp
)
if(NOT APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/wintab/kis_tablet_support_x11.cpp
input/wintab/qxcbconnection_xi2.cpp
input/wintab/qxcbconnection.cpp
input/wintab/kis_xi2_event_filter.cpp
)
endif()
endif()
if(APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
osx.mm
)
endif()
ki18n_wrap_ui(kritaui_LIB_SRCS
widgets/KoFillConfigWidget.ui
widgets/KoStrokeConfigWidget.ui
forms/wdgdlgpngimport.ui
forms/wdgfullscreensettings.ui
forms/wdgautogradient.ui
forms/wdggeneralsettings.ui
forms/wdgperformancesettings.ui
forms/wdggenerators.ui
forms/wdgbookmarkedconfigurationseditor.ui
forms/wdgapplyprofile.ui
forms/wdgcustompattern.ui
forms/wdglayerproperties.ui
forms/wdgcolorsettings.ui
forms/wdgtabletsettings.ui
forms/wdgcolorspaceselector.ui
forms/wdgcolorspaceselectoradvanced.ui
forms/wdgdisplaysettings.ui
forms/kis_previewwidgetbase.ui
forms/kis_matrix_widget.ui
forms/wdgselectionoptions.ui
forms/wdggeometryoptions.ui
forms/wdgnewimage.ui
forms/wdgimageproperties.ui
forms/wdgmaskfromselection.ui
forms/wdgmasksource.ui
forms/wdgfilterdialog.ui
forms/wdgmetadatamergestrategychooser.ui
forms/wdgpaintoppresets.ui
forms/wdgpaintopsettings.ui
forms/wdgdlggeneratorlayer.ui
forms/wdgdlgfilelayer.ui
forms/wdgfilterselector.ui
forms/wdgfilternodecreation.ui
forms/wdgpaintactioneditor.ui
forms/wdgmultipliersdoublesliderspinbox.ui
forms/wdgnodequerypatheditor.ui
forms/wdgpresetselectorstrip.ui
forms/wdgsavebrushpreset.ui
forms/wdgdlgblacklistcleanup.ui
forms/wdgrectangleconstraints.ui
forms/wdgimportimagesequence.ui
forms/wdgstrokeselectionproperties.ui
forms/KisDetailsPaneBase.ui
forms/KisOpenPaneBase.ui
forms/wdgstopgradienteditor.ui
brushhud/kis_dlg_brush_hud_config.ui
forms/wdgdlginternalcolorselector.ui
dialogs/kis_delayed_save_dialog.ui
input/config/kis_input_configuration_page.ui
input/config/kis_edit_profiles_dialog.ui
input/config/kis_input_configuration_page_item.ui
input/config/kis_mouse_input_editor.ui
input/config/kis_wheel_input_editor.ui
input/config/kis_key_input_editor.ui
layerstyles/wdgBevelAndEmboss.ui
layerstyles/wdgblendingoptions.ui
layerstyles/WdgColorOverlay.ui
layerstyles/wdgContour.ui
layerstyles/wdgdropshadow.ui
layerstyles/WdgGradientOverlay.ui
layerstyles/wdgInnerGlow.ui
layerstyles/wdglayerstyles.ui
layerstyles/WdgPatternOverlay.ui
layerstyles/WdgSatin.ui
layerstyles/WdgStroke.ui
layerstyles/wdgstylesselector.ui
layerstyles/wdgTexture.ui
wdgsplash.ui
input/wintab/kis_screen_size_choice_dialog.ui
)
QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h)
add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} )
generate_export_header(kritaui BASE_NAME kritaui)
target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network
kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES}
)
if (HAVE_QT_MULTIMEDIA)
target_link_libraries(kritaui Qt5::Multimedia)
endif()
if (HAVE_KIO)
target_link_libraries(kritaui KF5::KIOCore)
endif()
if (NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${X11_X11_LIB}
${X11_Xinput_LIB}
${XCB_LIBRARIES})
endif()
if(APPLE)
target_link_libraries(kritaui ${FOUNDATION_LIBRARY})
target_link_libraries(kritaui ${APPKIT_LIBRARY})
endif ()
target_link_libraries(kritaui ${OPENEXR_LIBRARIES})
# Add VSync disable workaround
if(NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras)
endif()
if(X11_FOUND)
target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES})
endif()
target_include_directories(kritaui
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/canvas>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/flake>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/ora>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/tool>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/utils>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/widgets>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/input/wintab>
)
set_target_properties(kritaui
PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS})
if (APPLE)
install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita)
endif ()
diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp
index 9a6597a658..3bf41e75cd 100644
--- a/libs/ui/KisApplication.cpp
+++ b/libs/ui/KisApplication.cpp
@@ -1,812 +1,808 @@
/*
* Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
* Copyright (C) 2012 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisApplication.h"
#include <stdlib.h>
#ifdef Q_OS_WIN
#include <windows.h>
#include <tchar.h>
#endif
#ifdef Q_OS_OSX
#include "osx.h"
#endif
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QDesktopWidget>
#include <QDir>
#include <QFile>
#include <QLocale>
#include <QMessageBox>
#include <QMessageBox>
#include <QProcessEnvironment>
#include <QSettings>
#include <QStandardPaths>
#include <QStringList>
#include <QStyle>
#include <QStyleFactory>
#include <QSysInfo>
#include <QTimer>
#include <QWidget>
#include <klocalizedstring.h>
#include <kdesktopfile.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <KoColorSpaceRegistry.h>
#include <KoPluginLoader.h>
#include <KoShapeRegistry.h>
#include <KoDpi.h>
#include "KoGlobal.h"
#include "KoConfig.h"
#include <resources/KoHashGeneratorProvider.h>
#include <KoResourcePaths.h>
#include <KisMimeDatabase.h>
#include "thememanager.h"
#include "KisPrintJob.h"
#include "KisDocument.h"
#include "KisMainWindow.h"
#include "KisAutoSaveRecoveryDialog.h"
#include "KisPart.h"
#include <kis_icon.h>
#include "kis_md5_generator.h"
#include "kis_splash_screen.h"
#include "kis_config.h"
#include "flake/kis_shape_selection.h"
#include <filter/kis_filter.h>
#include <filter/kis_filter_registry.h>
#include <filter/kis_filter_configuration.h>
#include <generator/kis_generator_registry.h>
#include <generator/kis_generator.h>
#include <brushengine/kis_paintop_registry.h>
#include <metadata/kis_meta_data_io_backend.h>
#include "kisexiv2/kis_exiv2.h"
#include "KisApplicationArguments.h"
#include <kis_debug.h>
#include "kis_action_registry.h"
#include <kis_brush_server.h>
#include <kis_resource_server_provider.h>
#include <KoResourceServerProvider.h>
#include "kis_image_barrier_locker.h"
#include "opengl/kis_opengl.h"
#include "kis_spin_box_unit_manager.h"
#include "kis_document_aware_spin_box_unit_manager.h"
#include "KisViewManager.h"
#include "kis_workspace_resource.h"
#include <KritaVersionWrapper.h>
namespace {
const QTime appStartTime(QTime::currentTime());
}
class KisApplicationPrivate
{
public:
KisApplicationPrivate()
: splashScreen(0)
{}
QPointer<KisSplashScreen> splashScreen;
};
class KisApplication::ResetStarting
{
public:
ResetStarting(KisSplashScreen *splash, int fileCount)
: m_splash(splash)
, m_fileCount(fileCount)
{
}
~ResetStarting() {
if (m_splash) {
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false);
if (m_fileCount > 0 || hideSplash) {
m_splash->hide();
}
else {
m_splash->setWindowFlags(Qt::Dialog);
QRect r(QPoint(), m_splash->size());
m_splash->move(QApplication::desktop()->availableGeometry().center() - r.center());
m_splash->setWindowTitle(qAppName());
m_splash->setParent(0);
Q_FOREACH (QObject *o, m_splash->children()) {
QWidget *w = qobject_cast<QWidget*>(o);
if (w && w->isHidden()) {
w->setVisible(true);
}
}
m_splash->show();
m_splash->activateWindow();
}
}
}
QPointer<KisSplashScreen> m_splash;
int m_fileCount;
};
KisApplication::KisApplication(const QString &key, int &argc, char **argv)
: QtSingleApplication(key, argc, argv)
, d(new KisApplicationPrivate)
, m_autosaveDialog(0)
, m_mainWindow(0)
, m_batchRun(false)
{
#ifdef Q_OS_OSX
setMouseCoalescingEnabled(false);
#endif
QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
setApplicationDisplayName("Krita");
setApplicationName("krita");
// Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird.
// setOrganizationName("krita");
setOrganizationDomain("krita.org");
QString version = KritaVersionWrapper::versionString(true);
setApplicationVersion(version);
setWindowIcon(KisIconUtils::loadIcon("calligrakrita"));
if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) {
QStringList styles = QStringList() << "breeze" << "fusion" << "plastique";
if (!styles.contains(style()->objectName().toLower())) {
Q_FOREACH (const QString & style, styles) {
if (!setStyle(style)) {
qDebug() << "No" << style << "available.";
}
else {
qDebug() << "Set style" << style;
break;
}
}
}
}
else {
qDebug() << "Style override disabled, using" << style()->objectName();
}
KisOpenGL::initialize();
qDebug() << "krita has opengl" << KisOpenGL::hasOpenGL();
}
#if defined(Q_OS_WIN) && defined(ENV32BIT)
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
LPFN_ISWOW64PROCESS fnIsWow64Process;
BOOL isWow64()
{
BOOL bIsWow64 = FALSE;
//IsWow64Process is not available on all supported versions of Windows.
//Use GetModuleHandle to get a handle to the DLL that contains the function
//and GetProcAddress to get a pointer to the function if available.
fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
if(0 != fnIsWow64Process)
{
if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
{
//handle error
}
}
return bIsWow64;
}
#endif
void KisApplication::initializeGlobals(const KisApplicationArguments &args)
{
int dpiX = args.dpiX();
int dpiY = args.dpiY();
if (dpiX > 0 && dpiY > 0) {
KoDpi::setDPI(dpiX, dpiY);
}
}
void KisApplication::addResourceTypes()
{
// All Krita's resource types
KoResourcePaths::addResourceType("kis_pics", "data", "/pics/");
KoResourcePaths::addResourceType("kis_images", "data", "/images/");
KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/");
KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/");
KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/");
KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/");
KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/");
KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/");
KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/");
KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/");
KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/");
KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/");
KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl");
KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true);
KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/");
KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true);
KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true);
KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/");
KoResourcePaths::addResourceType("kis_actions", "data", "/actions");
KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc");
KoResourcePaths::addResourceType("ko_effects", "data", "/effects/");
KoResourcePaths::addResourceType("tags", "data", "/tags/");
KoResourcePaths::addResourceType("templates", "data", "/templates");
KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita");
KoResourcePaths::addResourceType("symbols", "data", "/symbols");
// // Extra directories to look for create resources. (Does anyone actually use that anymore?)
// KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp");
// KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp"));
// KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp");
// KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp"));
// KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp");
// KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp"));
// KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches");
// KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches"));
// Make directories for all resources we can save, and tags
QDir d;
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/");
// Indicate that it is now safe for users of KoResourcePaths to load resources
KoResourcePaths::setReady();
}
void KisApplication::loadResources()
{
setSplashScreenLoadingText(i18n("Loading Gradients..."));
processEvents();
KoResourceServerProvider::instance()->gradientServer(true);
// Load base resources
setSplashScreenLoadingText(i18n("Loading Patterns..."));
processEvents();
KoResourceServerProvider::instance()->patternServer(true);
setSplashScreenLoadingText(i18n("Loading Palettes..."));
processEvents();
KoResourceServerProvider::instance()->paletteServer(false);
setSplashScreenLoadingText(i18n("Loading Brushes..."));
processEvents();
KisBrushServer::instance()->brushServer(true);
// load paintop presets
setSplashScreenLoadingText(i18n("Loading Paint Operations..."));
processEvents();
KisResourceServerProvider::instance()->paintOpPresetServer(true);
// load symbols
setSplashScreenLoadingText(i18n("Loading SVG Symbol Collections..."));
processEvents();
KoResourceServerProvider::instance()->svgSymbolCollectionServer(true);
setSplashScreenLoadingText(i18n("Loading Resource Bundles..."));
processEvents();
KisResourceServerProvider::instance()->resourceBundleServer();
}
void KisApplication::loadPlugins()
{
KoShapeRegistry* r = KoShapeRegistry::instance();
r->add(new KisShapeSelectionFactory());
KisActionRegistry::instance();
KisFilterRegistry::instance();
KisGeneratorRegistry::instance();
KisPaintOpRegistry::instance();
KoColorSpaceRegistry::instance();
// Load the krita-specific tools
setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool..."));
processEvents();
KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"),
QString::fromLatin1("[X-Krita-Version] == 28"));
// Load dockers
setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock..."));
processEvents();
KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"),
QString::fromLatin1("[X-Krita-Version] == 28"));
// XXX_EXIV: make the exiv io backends real plugins
setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO..."));
processEvents();
KisExiv2::initialize();
}
bool KisApplication::start(const KisApplicationArguments &args)
{
KisConfig cfg;
#if defined(Q_OS_WIN)
#ifdef ENV32BIT
if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) {
QMessageBox::information(0,
i18nc("@title:window", "Krita: Warning"),
i18n("You are running a 32 bits build on a 64 bits Windows.\n"
"This is not recommended.\n"
"Please download and install the x64 build instead."));
cfg.writeEntry("WarnedAbout32Bits", true);
}
#endif
#endif
QString opengl = cfg.canvasState();
if (opengl == "OPENGL_NOT_TRIED" ) {
cfg.setCanvasState("TRY_OPENGL");
}
else if (opengl != "OPENGL_SUCCESS") {
cfg.setCanvasState("OPENGL_FAILED");
}
setSplashScreenLoadingText(i18n("Initializing Globals"));
processEvents();
initializeGlobals(args);
const bool doNewImage = args.doNewImage();
const bool doTemplate = args.doTemplate();
const bool print = args.print();
const bool exportAs = args.exportAs();
const bool exportAsPdf = args.exportAsPdf();
const QString exportFileName = args.exportFileName();
m_batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty());
// print & exportAsPdf do user interaction ATM
const bool needsMainWindow = !exportAs;
// only show the mainWindow when no command-line mode option is passed
// TODO: fix print & exportAsPdf to work without mainwindow shown
const bool showmainWindow = !exportAs; // would be !batchRun;
const bool showSplashScreen = !m_batchRun && qEnvironmentVariableIsEmpty("NOSPLASH");
if (showSplashScreen && d->splashScreen) {
d->splashScreen->show();
d->splashScreen->repaint();
processEvents();
}
KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator());
// Initialize all Krita directories etc.
KoGlobal::initialize();
KConfigGroup group(KSharedConfig::openConfig(), "theme");
Digikam::ThemeManager themeManager;
themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark"));
ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done
Q_UNUSED(resetStarting);
// Make sure we can save resources and tags
setSplashScreenLoadingText(i18n("Adding resource types"));
processEvents();
addResourceTypes();
- // now we're set up, and the LcmsEnginePlugin will have access to resource paths for color management,
- // we can finally initialize KoColor.
- KoColor::init();
-
// Load all resources and tags before the plugins do that
loadResources();
// Load the plugins
loadPlugins();
if (needsMainWindow) {
// show a mainWindow asap, if we want that
setSplashScreenLoadingText(i18n("Loading Main Window..."));
processEvents();
m_mainWindow = KisPart::instance()->createMainWindow();
if (showmainWindow) {
m_mainWindow->initializeGeometry();
if (!args.workspace().isEmpty()) {
KoResourceServer<KisWorkspaceResource> * rserver = KisResourceServerProvider::instance()->workspaceServer();
KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace());
if (workspace) {
m_mainWindow->restoreWorkspace(workspace->dockerState());
}
}
if (args.canvasOnly()) {
m_mainWindow->viewManager()->switchCanvasOnly(true);
}
if (args.fullScreen()) {
m_mainWindow->showFullScreen();
}
else {
m_mainWindow->show();
}
}
}
short int numberOfOpenDocuments = 0; // number of documents open
// Check for autosave files that can be restored, if we're not running a batchrun (test, print, export to pdf)
if (!m_batchRun) {
checkAutosaveFiles();
}
setSplashScreenLoadingText(QString()); // done loading, so clear out label
processEvents();
//configure the unit manager
KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder());
connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave.
//the new syntax slot syntax allow to connect to a non q_object static method.
// Create a new image, if needed
if (doNewImage) {
KisDocument *doc = args.image();
if (doc) {
KisPart::instance()->addDocument(doc);
m_mainWindow->addViewAndNotifyLoadingCompleted(doc);
}
}
// Get the command line arguments which we have to parse
int argsCount = args.filenames().count();
if (argsCount > 0) {
// Loop through arguments
short int nPrinted = 0;
for (int argNumber = 0; argNumber < argsCount; argNumber++) {
QString fileName = args.filenames().at(argNumber);
// are we just trying to open a template?
if (doTemplate) {
// called in mix with batch options? ignore and silently skip
if (m_batchRun) {
continue;
}
if (createNewDocFromTemplate(fileName, m_mainWindow)) {
++numberOfOpenDocuments;
}
// now try to load
}
else {
if (exportAs) {
QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName);
if (outputMimetype == "application/octetstream") {
dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl;
return 1;
}
KisDocument *doc = KisPart::instance()->createDocument();
doc->setFileBatchMode(m_batchRun);
doc->openUrl(QUrl::fromLocalFile(fileName));
qApp->processEvents(); // For vector layers to be updated
doc->setFileBatchMode(true);
if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) {
dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage();
}
nPrinted++;
QTimer::singleShot(0, this, SLOT(quit()));
}
else if (m_mainWindow) {
KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
if (m_mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) {
if (print) {
m_mainWindow->slotFilePrint();
nPrinted++;
// TODO: trigger closing of app once printing is done
}
else if (exportAsPdf) {
KisPrintJob *job = m_mainWindow->exportToPdf(exportFileName);
if (job)
connect (job, SIGNAL(destroyed(QObject*)), m_mainWindow,
SLOT(slotFileQuit()), Qt::QueuedConnection);
nPrinted++;
} else {
// Normal case, success
numberOfOpenDocuments++;
}
} else {
// .... if failed
// delete doc; done by openDocument
}
}
}
}
if (m_batchRun) {
return nPrinted > 0;
}
}
// fixes BUG:369308 - Krita crashing on splash screen when loading.
// trying to open a file before Krita has loaded can cause it to hang and crash
if (d->splashScreen) {
d->splashScreen->displayLinks();
d->splashScreen->displayRecentFiles();
}
// not calling this before since the program will quit there.
return true;
}
KisApplication::~KisApplication()
{
delete d;
}
void KisApplication::setSplashScreen(QWidget *splashScreen)
{
d->splashScreen = qobject_cast<KisSplashScreen*>(splashScreen);
}
void KisApplication::setSplashScreenLoadingText(QString textToLoad)
{
if (d->splashScreen) {
d->splashScreen->loadingLabel->setText(textToLoad);
d->splashScreen->repaint();
}
}
void KisApplication::hideSplashScreen()
{
if (d->splashScreen) {
// hide the splashscreen to see the dialog
d->splashScreen->hide();
}
}
bool KisApplication::notify(QObject *receiver, QEvent *event)
{
try {
return QApplication::notify(receiver, event);
} catch (std::exception &e) {
qWarning("Error %s sending event %i to object %s",
e.what(), event->type(), qPrintable(receiver->objectName()));
} catch (...) {
qWarning("Error <unknown> sending event %i to object %s",
event->type(), qPrintable(receiver->objectName()));
}
return false;
}
void KisApplication::remoteArguments(QByteArray message, QObject *socket)
{
Q_UNUSED(socket);
// check if we have any mainwindow
KisMainWindow *mw = qobject_cast<KisMainWindow*>(qApp->activeWindow());
if (!mw) {
mw = KisPart::instance()->mainWindows().first();
}
if (!mw) {
return;
}
KisApplicationArguments args = KisApplicationArguments::deserialize(message);
const bool doTemplate = args.doTemplate();
const int argsCount = args.filenames().count();
if (argsCount > 0) {
// Loop through arguments
for (int argNumber = 0; argNumber < argsCount; ++argNumber) {
QString filename = args.filenames().at(argNumber);
// are we just trying to open a template?
if (doTemplate) {
createNewDocFromTemplate(filename, mw);
}
else if (QFile(filename).exists()) {
KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
mw->openDocument(QUrl::fromLocalFile(filename), flags);
}
}
}
}
void KisApplication::fileOpenRequested(const QString &url)
{
KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first();
if (mainWindow) {
KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
mainWindow->openDocument(QUrl::fromLocalFile(url), flags);
}
}
void KisApplication::checkAutosaveFiles()
{
if (m_batchRun) return;
// Check for autosave files from a previous run. There can be several, and
// we want to offer a restore for every one. Including a nice thumbnail!
QStringList filters;
filters << QString(".krita-*-*-autosave.kra");
#ifdef Q_OS_WIN
QDir dir = QDir::temp();
#else
QDir dir = QDir::home();
#endif
// all autosave files for our application
QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden);
// Allow the user to make their selection
if (autosaveFiles.size() > 0) {
if (d->splashScreen) {
// hide the splashscreen to see the dialog
d->splashScreen->hide();
}
m_autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow());
QDialog::DialogCode result = (QDialog::DialogCode) m_autosaveDialog->exec();
if (result == QDialog::Accepted) {
QStringList filesToRecover = m_autosaveDialog->recoverableFiles();
Q_FOREACH (const QString &autosaveFile, autosaveFiles) {
if (!filesToRecover.contains(autosaveFile)) {
QFile::remove(dir.absolutePath() + "/" + autosaveFile);
}
}
autosaveFiles = filesToRecover;
} else {
autosaveFiles.clear();
}
if (autosaveFiles.size() > 0) {
QList<QUrl> autosaveUrls;
Q_FOREACH (const QString &autoSaveFile, autosaveFiles) {
const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile);
autosaveUrls << url;
}
if (m_mainWindow) {
Q_FOREACH (const QUrl &url, autosaveUrls) {
KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
m_mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile);
}
}
}
// cleanup
delete m_autosaveDialog;
m_autosaveDialog = nullptr;
}
}
bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow)
{
QString templatePath;
const QUrl templateUrl = QUrl::fromLocalFile(fileName);
if (QFile::exists(fileName)) {
templatePath = templateUrl.toLocalFile();
dbgUI << "using full path...";
}
else {
QString desktopName(fileName);
const QString templatesResourcePath = QStringLiteral("templates/");
QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName);
if (paths.isEmpty()) {
paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName);
}
if (paths.isEmpty()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("No template found for: %1", desktopName));
} else if (paths.count() > 1) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("Too many templates found for: %1", desktopName));
} else {
templatePath = paths.at(0);
}
}
if (!templatePath.isEmpty()) {
QUrl templateBase;
templateBase.setPath(templatePath);
KDesktopFile templateInfo(templatePath);
QString templateName = templateInfo.readUrl();
QUrl templateURL;
templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName);
KisMainWindow::OpenFlags batchFlags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) {
dbgUI << "Template loaded...";
return true;
}
else {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("Template %1 failed to load.", templateURL.toDisplayString()));
}
}
return false;
}
void KisApplication::clearConfig()
{
KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread());
KSharedConfigPtr config = KSharedConfig::openConfig();
// find user settings file
bool createDir = false;
QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir);
QFile configFile(kritarcPath);
if (configFile.exists()) {
// clear file
if (configFile.open(QFile::WriteOnly)) {
configFile.close();
}
else {
QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("Failed to clear %1\n\n"
"Please make sure no other program is using the file and try again.",
kritarcPath),
QMessageBox::Ok, QMessageBox::Ok);
}
}
// reload from disk; with the user file settings cleared,
// this should load any default configuration files shipping with the program
config->reparseConfiguration();
config->sync();
}
void KisApplication::askClearConfig()
{
Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers();
bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier);
if (askClearConfig) {
bool ok = QMessageBox::question(0,
i18nc("@title:window", "Krita"),
i18n("Do you want to clear the settings file?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes;
if (ok) {
clearConfig();
}
}
}
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index fd1d847aec..1cecc03405 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,1657 +1,1656 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h" // XXX: remove
#include <QMessageBox> // XXX: remove
#include <KisMimeDatabase.h>
#include <KoCanvasBase.h>
#include <KoColor.h>
#include <KoColorProfile.h>
#include <KoColorSpaceEngine.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoDocumentInfoDlg.h>
#include <KoDocumentInfo.h>
#include <KoDpi.h>
#include <KoUnit.h>
#include <KoFileDialog.h>
#include <KoID.h>
#include <KoOdfReadStore.h>
#include <KoProgressProxy.h>
#include <KoProgressUpdater.h>
#include <KoSelection.h>
#include <KoShape.h>
#include <KoShapeController.h>
#include <KoStore.h>
#include <KoUpdater.h>
#include <KoXmlWriter.h>
#include <KoXmlReader.h>
#include <KoStoreDevice.h>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kdesktopfile.h>
#include <kconfiggroup.h>
#include <QTemporaryFile>
#include <kbackup.h>
#include <QTextBrowser>
#include <QApplication>
#include <QBuffer>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QDir>
#include <QDomDocument>
#include <QDomElement>
#include <QFileInfo>
#include <QImage>
#include <QList>
#include <QPainter>
#include <QRect>
#include <QScopedPointer>
#include <QSize>
#include <QStringList>
#include <QtGlobal>
#include <QTimer>
#include <QWidget>
#include <QFuture>
#include <QFutureWatcher>
// Krita Image
#include <kis_config.h>
#include <flake/kis_shape_layer.h>
#include <kis_debug.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_name_server.h>
#include <kis_paint_layer.h>
#include <kis_painter.h>
#include <kis_selection.h>
#include <kis_fill_painter.h>
#include <kis_document_undo_store.h>
#include <kis_painting_assistants_decoration.h>
#include <kis_idle_watcher.h>
#include <kis_signal_auto_connection.h>
#include <kis_debug.h>
#include <kis_canvas_widget_base.h>
#include "kis_layer_utils.h"
// Local
#include "KisViewManager.h"
#include "kis_clipboard.h"
#include "widgets/kis_custom_image_widget.h"
#include "canvas/kis_canvas2.h"
#include "flake/kis_shape_controller.h"
#include "kis_statusbar.h"
#include "widgets/kis_progress_widget.h"
#include "kis_canvas_resource_provider.h"
#include "kis_resource_server_provider.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisPart.h"
#include "KisView.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_barrier_lock_adapter.h"
#include <mutex>
#include "kis_config_notifier.h"
#include "kis_async_action_feedback.h"
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include <unistd.h>
using namespace std;
namespace {
constexpr int errorMessageTimeout = 5000;
constexpr int successMessageTimeout = 1000;
}
/**********************************************************
*
* KisDocument
*
**********************************************************/
//static
QString KisDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class UndoStack : public KUndo2Stack
{
public:
UndoStack(KisDocument *doc)
: KUndo2Stack(doc),
m_doc(doc)
{
}
void setIndex(int idx) override {
KisImageWSP image = this->image();
image->requestStrokeCancellation();
if(image->tryBarrierLock()) {
KUndo2Stack::setIndex(idx);
image->unlock();
}
}
void notifySetIndexChangedOneCommand() override {
KisImageWSP image = this->image();
image->unlock();
/**
* Some very weird commands may emit blocking signals to
* the GUI (e.g. KisGuiContextCommand). Here is the best thing
* we can do to avoid the deadlock
*/
while(!image->tryBarrierLock()) {
QApplication::processEvents();
}
}
void undo() override {
KisImageWSP image = this->image();
image->requestUndoDuringStroke();
if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
return;
}
if(image->tryBarrierLock()) {
KUndo2Stack::undo();
image->unlock();
}
}
void redo() override {
KisImageWSP image = this->image();
if(image->tryBarrierLock()) {
KUndo2Stack::redo();
image->unlock();
}
}
private:
KisImageWSP image() {
KisImageWSP currentImage = m_doc->image();
Q_ASSERT(currentImage);
return currentImage;
}
private:
KisDocument *m_doc;
};
class Q_DECL_HIDDEN KisDocument::Private
{
public:
Private(KisDocument *q) :
docInfo(new KoDocumentInfo(q)), // deleted by QObject
importExportManager(new KisImportExportManager(q)), // deleted manually
undoStack(new UndoStack(q)), // deleted by QObject
m_bAutoDetectedMime(false),
modified(false),
readwrite(true),
firstMod(QDateTime::currentDateTime()),
lastMod(firstMod),
nserver(new KisNameServer(1)),
imageIdleWatcher(2000 /*ms*/),
savingLock(&savingMutex)
{
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
Private(const Private &rhs, KisDocument *q) :
docInfo(new KoDocumentInfo(*rhs.docInfo, q)),
unit(rhs.unit),
importExportManager(new KisImportExportManager(q)),
mimeType(rhs.mimeType),
outputMimeType(rhs.outputMimeType),
undoStack(new UndoStack(q)),
guidesConfig(rhs.guidesConfig),
m_bAutoDetectedMime(rhs.m_bAutoDetectedMime),
m_url(rhs.m_url),
m_file(rhs.m_file),
modified(rhs.modified),
readwrite(rhs.readwrite),
firstMod(rhs.firstMod),
lastMod(rhs.lastMod),
nserver(new KisNameServer(*rhs.nserver)),
preActivatedNode(0), // the node is from another hierarchy!
imageIdleWatcher(2000 /*ms*/),
assistants(rhs.assistants), // WARNING: assistants should not store pointers to the document!
gridConfig(rhs.gridConfig),
savingLock(&savingMutex)
{
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
KoDocumentInfo *docInfo = 0;
KoUnit unit;
KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
QTimer autoSaveTimer;
QString lastErrorMessage; // see openFile()
QString lastWarningMessage;
int autoSaveDelay = 300; // in seconds, 0 to disable.
bool modifiedAfterAutosave = false;
bool isAutosaving = false;
bool disregardAutosaveFailure = false;
KUndo2Stack *undoStack = 0;
KisGuidesConfig guidesConfig;
bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // local url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QMutex savingMutex;
bool modified = false;
bool readwrite = false;
QDateTime firstMod;
QDateTime lastMod;
KisNameServer *nserver;
KisImageSP image;
KisImageSP savingImage;
KisNodeWSP preActivatedNode;
KisShapeController* shapeController = 0;
KoShapeController* koShapeController = 0;
KisIdleWatcher imageIdleWatcher;
QScopedPointer<KisSignalAutoConnection> imageIdleConnection;
QList<KisPaintingAssistantSP> assistants;
KisGridConfig gridConfig;
StdLockableWrapper<QMutex> savingLock;
bool modifiedWhileSaving = false;
QScopedPointer<KisDocument> backgroundSaveDocument;
QPointer<KoUpdater> savingUpdater;
QFuture<KisImportExportFilter::ConversionStatus> childSavingFuture;
KritaUtils::ExportFileJob backgroundSaveJob;
bool isRecovered = false;
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
if (image) {
imageIdleConnection.reset(
new KisSignalAutoConnection(
&imageIdleWatcher, SIGNAL(startedIdleMode()),
image.data(), SLOT(explicitRegenerateLevelOfDetail())));
}
}
class StrippedSafeSavingLocker;
};
class KisDocument::Private::StrippedSafeSavingLocker {
public:
StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
: m_locked(false)
, m_image(image)
, m_savingLock(savingMutex)
, m_imageLock(image, true)
{
/**
* Initial try to lock both objects. Locking the image guards
* us from any image composition threads running in the
* background, while savingMutex guards us from entering the
* saving code twice by autosave and main threads.
*
* Since we are trying to lock multiple objects, so we should
* do it in a safe manner.
*/
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
if (!m_locked) {
m_image->requestStrokeEnd();
QApplication::processEvents();
// one more try...
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
}
}
~StrippedSafeSavingLocker() {
if (m_locked) {
m_imageLock.unlock();
m_savingLock.unlock();
}
}
bool successfullyLocked() const {
return m_locked;
}
private:
bool m_locked;
KisImageSP m_image;
StdLockableWrapper<QMutex> m_savingLock;
KisImageBarrierLockAdapter m_imageLock;
};
KisDocument::KisDocument()
: d(new Private(this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(newObjectName());
// preload the krita resources
KisResourceServerProvider::instance();
d->shapeController = new KisShapeController(this, d->nserver),
d->koShapeController = new KoShapeController(0, d->shapeController),
slotConfigChanged();
}
KisDocument::KisDocument(const KisDocument &rhs)
: QObject(),
d(new Private(*rhs.d, this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(rhs.objectName());
d->shapeController = new KisShapeController(this, d->nserver),
d->koShapeController = new KoShapeController(0, d->shapeController),
slotConfigChanged();
// clone the image with keeping the GUIDs of the layers intact
// NOTE: we expect the image to be locked!
setCurrentImage(rhs.image()->clone(true));
if (rhs.d->preActivatedNode) {
// since we clone uuid's, we can use them for lacating new
// nodes. Otherwise we would need to use findSymmetricClone()
d->preActivatedNode =
KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid());
}
}
KisDocument::~KisDocument()
{
// wait until all the pending operations are in progress
waitForSavingToComplete();
/**
* Push a timebomb, which will try to release the memory after
* the document has been deleted
*/
KisPaintDevice::createMemoryReleaseObject()->deleteLater();
d->autoSaveTimer.disconnect(this);
d->autoSaveTimer.stop();
delete d->importExportManager;
// Despite being QObject they needs to be deleted before the image
delete d->shapeController;
delete d->koShapeController;
if (d->image) {
d->image->notifyAboutToBeDeleted();
/**
* WARNING: We should wait for all the internal image jobs to
* finish before entering KisImage's destructor. The problem is,
* while execution of KisImage::~KisImage, all the weak shared
* pointers pointing to the image enter an inconsistent
* state(!). The shared counter is already zero and destruction
* has started, but the weak reference doesn't know about it,
* because KisShared::~KisShared hasn't been executed yet. So all
* the threads running in background and having weak pointers will
* enter the KisImage's destructor as well.
*/
d->image->requestStrokeCancellation();
d->image->waitForDone();
// clear undo commands that can still point to the image
d->undoStack->clear();
d->image->waitForDone();
KisImageWSP sanityCheckPointer = d->image;
Q_UNUSED(sanityCheckPointer);
// The following line trigger the deletion of the image
d->image.clear();
// check if the image has actually been deleted
KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
}
delete d;
}
bool KisDocument::reload()
{
// XXX: reimplement!
return false;
}
KisDocument *KisDocument::clone()
{
return new KisDocument(*this);
}
bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration)
{
QFileInfo filePathInfo(job.filePath);
if (filePathInfo.exists() && !filePathInfo.isWritable()) {
slotCompleteSavingDocument(job,
KisImportExportFilter::CreationError,
i18n("%1 cannot be written to. Please save under a different name.", job.filePath));
return false;
}
KisConfig cfg;
if (cfg.backupFile() && filePathInfo.exists()) {
KBackup::backupFile(job.filePath);
}
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
const QString actionName =
job.flags & KritaUtils::SaveIsExporting ?
i18n("Exporting Document...") :
i18n("Saving Document...");
bool started =
initiateSavingInBackground(actionName,
this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)),
job, exportConfiguration);
if (!started) {
emit canceled(QString());
}
return started;
}
bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
SaveFlags flags = SaveIsExporting;
if (showWarnings) {
flags |= SaveShowWarnings;
}
return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(),
mimeType,
flags),
exportConfiguration);
}
bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
return exportDocumentImpl(ExportFileJob(url.toLocalFile(),
mimeType,
showWarnings ? SaveShowWarnings : SaveNone),
exportConfiguration);
}
bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
return saveAs(url(), mimeType(), showWarnings, exportConfiguration);
}
QByteArray KisDocument::serializeToNativeByteArray()
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
QScopedPointer<KisImportExportFilter> filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export));
filter->setBatchMode(true);
filter->setMimeType(nativeFormatMimeType());
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return byteArray;
}
d->savingImage = d->image;
if (filter->convert(this, &buffer) != KisImportExportFilter::OK) {
qWarning() << "serializeToByteArray():: Could not export to our native format";
}
return byteArray;
}
void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
const QString fileName = QFileInfo(job.filePath).fileName();
if (status != KisImportExportFilter::OK) {
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error mesage",
"Error during saving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
if (!fileBatchMode()) {
const QString filePath = job.filePath;
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage));
}
} else {
if (!(job.flags & KritaUtils::SaveIsExporting)) {
setUrl(QUrl::fromLocalFile(job.filePath));
setLocalFilePath(job.filePath);
setMimeType(job.mimeType);
updateEditingTime(true);
if (!d->modifiedWhileSaving) {
d->undoStack->setClean();
}
setRecovered(false);
removeAutoSaveFiles();
}
emit completed();
emit sigSavingFinished();
emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
}
}
QByteArray KisDocument::mimeType() const
{
return d->mimeType;
}
void KisDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
bool KisDocument::fileBatchMode() const
{
return d->importExportManager->batchMode();
}
void KisDocument::setFileBatchMode(const bool batchMode)
{
d->importExportManager->setBatchMode(batchMode);
}
KisDocument* KisDocument::lockAndCloneForSaving()
{
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return 0;
}
return new KisDocument(*this);
}
bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
{
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return false;
}
d->savingImage = d->image;
const QString fileName = url.toLocalFile();
KisImportExportFilter::ConversionStatus status =
d->importExportManager->
exportDocument(fileName, fileName, mimeType, false, exportConfiguration);
d->savingImage = 0;
return status == KisImportExportFilter::OK;
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration)
{
KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false);
QScopedPointer<KisDocument> clonedDocument(lockAndCloneForSaving());
// we block saving until the current saving is finished!
if (!clonedDocument || !d->savingMutex.tryLock()) {
return false;
}
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false);
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false);
d->backgroundSaveDocument.reset(clonedDocument.take());
d->backgroundSaveJob = job;
d->modifiedWhileSaving = false;
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = true;
}
connect(d->backgroundSaveDocument.data(),
SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)),
this,
SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&)));
connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
receiverObject, receiverMethod, Qt::UniqueConnection);
bool started =
d->backgroundSaveDocument->startExportInBackground(actionName,
job.filePath,
job.filePath,
job.mimeType,
job.flags & KritaUtils::SaveShowWarnings,
exportConfiguration);
return started;
}
void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) {
d->savingMutex.unlock();
return;
}
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = false;
}
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid());
const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
d->backgroundSaveJob = KritaUtils::ExportFileJob();
emit sigCompleteBackgroundSaving(job, status, errorMessage);
}
void KisDocument::slotAutoSave()
{
if (!d->modified || !d->modifiedAfterAutosave) return;
const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
bool started =
initiateSavingInBackground(i18n("Autosaving..."),
this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)),
KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode),
0);
if (!started) {
const int emergencyAutoSaveInterval = 10; // sec
setAutoSaveDelay(emergencyAutoSaveInterval);
} else {
d->modifiedAfterAutosave = false;
}
}
void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
Q_UNUSED(job);
const QString fileName = QFileInfo(job.filePath).fileName();
if (status != KisImportExportFilter::OK) {
const int emergencyAutoSaveInterval = 10; // sec
setAutoSaveDelay(emergencyAutoSaveInterval);
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error mesage",
"Error during autosaving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
} else {
KisConfig cfg;
d->autoSaveDelay = cfg.autoSaveInterval();
if (!d->modifiedWhileSaving) {
d->autoSaveTimer.stop(); // until the next change
} else {
setAutoSaveDelay(d->autoSaveDelay); // restart the timer
}
emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
}
}
bool KisDocument::startExportInBackground(const QString &actionName,
const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration)
{
d->savingImage = d->image;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
d->importExportManager->setUpdater(d->savingUpdater);
}
}
d->childSavingFuture =
d->importExportManager->exportDocumentAsyc(location,
realLocation,
mimeType,
showWarnings,
exportConfiguration);
if (d->childSavingFuture.isCanceled()) return false;
typedef QFutureWatcher<KisImportExportFilter::ConversionStatus> StatusWatcher;
StatusWatcher *watcher = new StatusWatcher();
watcher->setFuture(d->childSavingFuture);
connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
return true;
}
void KisDocument::finishExportInBackground()
{
KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, "");
return;
}
KisImportExportFilter::ConversionStatus status =
d->childSavingFuture.result();
const QString errorMessage = this->errorMessage();
d->savingImage.clear();
d->childSavingFuture = QFuture<KisImportExportFilter::ConversionStatus>();
d->lastErrorMessage.clear();
if (d->savingUpdater) {
d->savingUpdater->setProgress(100);
}
emit sigBackgroundSavingFinished(status, errorMessage);
}
void KisDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setAutoSaveDelay(d->autoSaveDelay);
Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KisDocument::setAutoSaveDelay(int delay)
{
//qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay;
d->autoSaveDelay = delay;
if (isReadWrite() && d->autoSaveDelay > 0) {
d->autoSaveTimer.start(d->autoSaveDelay * 1000);
}
else {
d->autoSaveTimer.stop();
}
}
KoDocumentInfo *KisDocument::documentInfo() const
{
return d->docInfo;
}
bool KisDocument::isModified() const
{
return d->modified;
}
QPixmap KisDocument::generatePreview(const QSize& size)
{
KisImageSP image = d->image;
if (d->savingImage) image = d->savingImage;
if (image) {
QRect bounds = image->bounds();
QSize newSize = bounds.size();
newSize.scale(size, Qt::KeepAspectRatio);
QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
if (px.size() == QSize(0,0)) {
px = QPixmap(newSize);
QPainter gc(&px);
QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
gc.fillRect(px.rect(), checkBrush);
gc.end();
}
return px;
}
return QPixmap(size);
}
QString KisDocument::generateAutoSaveFileName(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
const QString extension (".kra");
QRegularExpression autosavePattern("^\\..+-autosave.kra$");
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#endif
} else {
retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension);
}
//qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
return retval;
}
bool KisDocument::importDocument(const QUrl &_url)
{
bool ret;
dbgUI << "url=" << _url.url();
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
// File --> Import
if (ret) {
dbgUI << "success, resetting url";
resetURL();
setTitleModified();
}
return ret;
}
bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags)
{
if (!_url.isLocalFile()) {
return false;
}
dbgUI << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
QUrl url(_url);
bool autosaveOpened = false;
if (url.isLocalFile() && !fileBatchMode()) {
QString file = url.toLocalFile();
QString asf = generateAutoSaveFileName(file);
if (QFile::exists(asf)) {
KisApplication *kisApp = static_cast<KisApplication*>(qApp);
kisApp->hideSplashScreen();
//dbgUI <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case QMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
return false;
}
}
}
bool ret = openUrlInternal(url);
if (autosaveOpened || flags & RecoveryFile) {
setReadWrite(true); // enable save button
setModified(true);
setRecovered(true);
}
else {
if (!(flags & DontAddToRecent)) {
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
if (ret) {
// Detect readonly local-files; remote files are assumed to be writable
QFileInfo fi(url.toLocalFile());
setReadWrite(fi.isWritable());
}
setRecovered(false);
}
return ret;
}
class DlgLoadMessages : public KoDialog {
public:
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) {
setWindowTitle(title);
setWindowIcon(KisIconUtils::loadIcon("warning"));
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
QHBoxLayout *hlayout = new QHBoxLayout();
QLabel *labelWarning= new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
hlayout->addWidget(labelWarning);
hlayout->addWidget(new QLabel(message));
layout->addLayout(hlayout);
QTextBrowser *browser = new QTextBrowser();
QString warning = "<html><body><p><b>";
if (warnings.size() == 1) {
warning += "</b> Reason:</p>";
}
else {
warning += "</b> Reasons:</p>";
}
warning += "<p/><ul>";
Q_FOREACH(const QString &w, warnings) {
warning += "\n<li>" + w + "</li>";
}
warning += "</ul>";
browser->setHtml(warning);
browser->setMinimumHeight(200);
browser->setMinimumWidth(400);
layout->addWidget(browser);
setMainWidget(page);
setButtons(KoDialog::Ok);
resize(minimumSize());
}
};
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
QString filename = localFilePath();
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
while (path.length() > 0) {
path.chop(1);
typeName = KisMimeDatabase::mimeTypeForFile(path);
//qDebug() << "\t" << path << typeName;
if (!typeName.isEmpty()) {
break;
}
}
//qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
}
dbgUI << localFilePath() << "type:" << typeName;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window && window->viewManager()) {
KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
d->importExportManager->setUpdater(updater);
}
KisImportExportFilter::ConversionStatus status;
status = d->importExportManager->importDocument(localFilePath(), typeName);
if (status != KisImportExportFilter::OK) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()),
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
return false;
}
else if (!warningMessage().isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
undoStack()->clear();
return true;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
}
bool KisDocument::loadNativeFormat(const QString & file_)
{
return openUrl(QUrl::fromLocalFile(file_));
}
void KisDocument::setModified()
{
d->modified = true;
}
void KisDocument::setModified(bool mod)
{
if (mod) {
updateEditingTime(false);
}
if (d->isAutosaving) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
errKrita << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//dbgUI<<" url:" << url.path();
//dbgUI<<" mod="<<mod<<" MParts mod="<<KisParts::ReadWritePart::isModified()<<" isModified="<<isModified();
if (mod && !d->modifiedAfterAutosave) {
// First change since last autosave -> start the autosave timer
setAutoSaveDelay(d->autoSaveDelay);
}
d->modifiedAfterAutosave |= mod;
d->modifiedWhileSaving |= mod;
if (mod == isModified())
return;
d->modified = mod;
if (mod) {
documentInfo()->updateParameters();
}
// This influences the title
setTitleModified();
emit modified(mod);
}
void KisDocument::setRecovered(bool value)
{
d->isRecovered = value;
}
bool KisDocument::isRecovered() const
{
return d->isRecovered;
}
void KisDocument::updateEditingTime(bool forceStoreElapsed)
{
QDateTime now = QDateTime::currentDateTime();
int firstModDelta = d->firstMod.secsTo(now);
int lastModDelta = d->lastMod.secsTo(now);
if (lastModDelta > 30) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
d->firstMod = now;
} else if (firstModDelta > 60 || forceStoreElapsed) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
d->firstMod = now;
}
d->lastMod = now;
}
QString KisDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_OS_WIN
if (url().isLocalFile()) {
_url = QDir::toNativeSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KisDocument::caption() const
{
QString c;
- if (documentInfo()) {
- c = documentInfo()->aboutInfo("title");
- }
const QString _url(url().fileName());
- if (!c.isEmpty() && !_url.isEmpty()) {
- c = QString("%1 - %2").arg(c).arg(_url);
- }
- else if (c.isEmpty()) {
+
+ // if URL is empty...it is probably an unsaved file
+ if (_url.isEmpty()) {
+ c = " [" + i18n("Not Saved") + "] ";
+ } else {
c = _url; // Fall back to document URL
}
+
return c;
}
void KisDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument("krita", tagName, version);
}
//static
QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
bool KisDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
void KisDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KisDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KisDocument::setWarningMessage(const QString& warningMsg)
{
d->lastWarningMessage = warningMsg;
}
QString KisDocument::warningMessage() const
{
return d->lastWarningMessage;
}
void KisDocument::removeAutoSaveFiles()
{
//qDebug() << "removeAutoSaveFiles";
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir
//qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf);
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autosavefile" << asf;
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
//qDebug() << "Autsavefile in $home" << asf;
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autsavefile 2" << asf;
QFile::remove(asf);
}
}
KoUnit KisDocument::unit() const
{
return d->unit;
}
void KisDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
KUndo2Stack *KisDocument::undoStack()
{
return d->undoStack;
}
KisImportExportManager *KisDocument::importExportManager() const
{
return d->importExportManager;
}
void KisDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KisDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KisDocument::endMacro()
{
d->undoStack->endMacro();
}
void KisDocument::slotUndoStackCleanChanged(bool value)
{
setModified(!value);
}
void KisDocument::slotConfigChanged()
{
KisConfig cfg;
d->undoStack->setUndoLimit(cfg.undoStackLimit());
setAutoSaveDelay(cfg.autoSaveInterval());
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
d->gridConfig = config;
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
emit sigGuidesConfigChanged(d->guidesConfig);
}
void KisDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
return new KoDocumentInfoDlg(parent, docInfo);
}
bool KisDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KisDocument::url() const
{
return d->m_url;
}
bool KisDocument::closeUrl(bool promptToSave)
{
if (promptToSave) {
if ( isReadWrite() && isModified()) {
Q_FOREACH (KisView *view, KisPart::instance()->views()) {
if (view && view->document() == this) {
if (!view->queryClose()) {
return false;
}
}
}
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void KisDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KisDocument::localFilePath() const
{
return d->m_file;
}
void KisDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KisDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() )
return false;
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile());
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setUrl(d->m_url);
ret = openFile();
if (ret) {
emit completed();
} else {
emit canceled(QString());
}
return ret;
}
return false;
}
bool KisDocument::newImage(const QString& name,
qint32 width, qint32 height,
const KoColorSpace* cs,
const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisConfig cfg;
KisImageSP image;
KisPaintLayerSP layer;
if (!cs) return false;
QApplication::setOverrideCursor(Qt::BusyCursor);
image = new KisImage(createUndoStore(), width, height, cs, name);
Q_CHECK_PTR(image);
connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
image->setResolution(imageResolution, imageResolution);
image->assignImageProfile(cs->profile());
documentInfo()->setAboutInfo("title", name);
if (name != i18n("Unnamed") && !name.isEmpty()) {
- setUrl(QUrl::fromLocalFile(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation) + '/' + name + ".kra"));
+ setUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + '/' + name + ".kra"));
}
documentInfo()->setAboutInfo("abstract", description);
layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
Q_CHECK_PTR(layer);
if (backgroundAsLayer) {
image->setDefaultProjectionColor(KoColor(cs));
if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) {
layer->paintDevice()->setDefaultPixel(bgColor);
} else {
// Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel
KisFillPainter painter;
painter.begin(layer->paintDevice());
painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8());
}
} else {
image->setDefaultProjectionColor(bgColor);
}
layer->setDirty(QRect(0, 0, width, height));
image->addNode(layer.data(), image->rootLayer().data());
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
cfg.defImageWidth(width);
cfg.defImageHeight(height);
cfg.defImageResolution(imageResolution);
cfg.defColorModel(image->colorSpace()->colorModelId().id());
cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
cfg.defColorProfile(image->colorSpace()->profile()->name());
QApplication::restoreOverrideCursor();
return true;
}
bool KisDocument::isSaving() const
{
const bool result = d->savingMutex.tryLock();
if (result) {
d->savingMutex.unlock();
}
return !result;
}
void KisDocument::waitForSavingToComplete()
{
KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
f.waitForMutex(&d->savingMutex);
}
KoShapeBasedDocumentBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
QList<KisPaintingAssistantSP> KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList<KisPaintingAssistantSP> value)
{
d->assistants = value;
}
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image)
{
if (d->image) {
// Disconnect existing sig/slot connections
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
d->image->initialRefreshGraph();
}
void KisDocument::setImageModified()
{
setModified(true);
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage;
}
diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp
index 85b2cec2d5..27d7bd684d 100644
--- a/libs/ui/KisMainWindow.cpp
+++ b/libs/ui/KisMainWindow.cpp
@@ -1,2464 +1,2488 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000-2006 David Faure <faure@kde.org>
Copyright (C) 2007, 2009 Thomas zander <zander@kde.org>
Copyright (C) 2010 Benjamin Port <port.benjamin@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h"
#include <KoConfig.h>
// qt includes
#include <QApplication>
#include <QByteArray>
#include <QCloseEvent>
+#include <QStandardPaths>
+#include <QDesktopServices>
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDialog>
#include <QDockWidget>
#include <QIcon>
#include <QInputDialog>
#include <QLabel>
#include <QLayout>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QMutex>
#include <QMutexLocker>
#include <QPointer>
#include <QPrintDialog>
#include <QPrinter>
#include <QPrintPreviewDialog>
#include <QToolButton>
#include <QSignalMapper>
#include <QTabBar>
#include <QMoveEvent>
#include <QUrl>
#include <QMessageBox>
#include <QTemporaryFile>
#include <QStatusBar>
#include <QMenu>
#include <QMenuBar>
#include <KisMimeDatabase.h>
#include <QMimeData>
#include <kactioncollection.h>
#include <QAction>
#include <kactionmenu.h>
#include <kis_debug.h>
#include <kedittoolbar.h>
#include <khelpmenu.h>
#include <klocalizedstring.h>
#include <kaboutdata.h>
#include <kis_workspace_resource.h>
#include <input/kis_input_manager.h>
#ifdef HAVE_KIO
#include <krecentdocument.h>
#endif
#include <krecentfilesaction.h>
#include <KoResourcePaths.h>
#include <ktoggleaction.h>
#include <ktoolbar.h>
#include <kmainwindow.h>
#include <kxmlguiwindow.h>
#include <kxmlguifactory.h>
#include <kxmlguiclient.h>
#include <kguiitem.h>
#include <kwindowconfig.h>
#include "KoDockFactoryBase.h"
#include "KoDockWidgetTitleBar.h"
#include "KoDocumentInfoDlg.h"
#include "KoDocumentInfo.h"
#include "KoFileDialog.h"
#include <kis_icon.h>
#include <KoPageLayoutDialog.h>
#include <KoPageLayoutWidget.h>
#include <KoToolManager.h>
#include <KoZoomController.h>
#include "KoToolDocker.h"
#include <KoToolBoxFactory.h>
#include <KoDockRegistry.h>
#include <KoPluginLoader.h>
#include <KoColorSpaceEngine.h>
#include <KoUpdater.h>
#include <KoResourceModel.h>
#include <KisMimeDatabase.h>
#include <brushengine/kis_paintop_settings.h>
#include "dialogs/kis_about_application.h"
#include "dialogs/kis_delayed_save_dialog.h"
#include "dialogs/kis_dlg_preferences.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "KisApplication.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_canvas_resource_provider.h"
#include "kis_clipboard.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_custom_image_widget.h"
#include <KisDocument.h>
#include "KisDocument.h"
#include "KisDocument.h"
#include "kis_group_layer.h"
#include "kis_icon_utils.h"
#include "kis_image_from_clipboard_widget.h"
#include "kis_image.h"
#include <KisImportExportFilter.h>
#include "KisImportExportManager.h"
#include "kis_mainwindow_observer.h"
+#include "kis_memory_statistics_server.h"
#include "kis_node.h"
#include "KisOpenPane.h"
#include "kis_paintop_box.h"
#include "KisPart.h"
#include "KisPrintJob.h"
#include "kis_resource_server_provider.h"
#include "kis_signal_compressor_with_param.h"
+#include "kis_statusbar.h"
#include "KisView.h"
#include "KisViewManager.h"
#include "thememanager.h"
#include "kis_animation_importer.h"
#include "dialogs/kis_dlg_import_image_sequence.h"
#include <KisUpdateSchedulerConfigNotifier.h>
#include <mutex>
#ifdef Q_OS_WIN
#include <QtPlatformHeaders/QWindowsWindowFunctions>
#endif
class ToolDockerFactory : public KoDockFactoryBase
{
public:
ToolDockerFactory() : KoDockFactoryBase() { }
QString id() const override {
return "sharedtooldocker";
}
QDockWidget* createDockWidget() override {
KoToolDocker* dockWidget = new KoToolDocker();
dockWidget->setTabEnabled(false);
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
class Q_DECL_HIDDEN KisMainWindow::Private
{
public:
Private(KisMainWindow *parent)
: q(parent)
, dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent))
, windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent))
, documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent))
, workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent))
, mdiArea(new QMdiArea(parent))
, windowMapper(new QSignalMapper(parent))
, documentMapper(new QSignalMapper(parent))
{
}
~Private() {
qDeleteAll(toolbarList);
}
KisMainWindow *q {0};
KisViewManager *viewManager {0};
QPointer<KisView> activeView;
QList<QAction *> toolbarList;
bool firstTime {true};
bool windowSizeDirty {false};
bool readOnly {false};
bool noCleanup {false};
KisAction *showDocumentInfo {0};
KisAction *saveAction {0};
KisAction *saveActionAs {0};
// KisAction *printAction;
// KisAction *printActionPreview;
// KisAction *exportPdf {0};
KisAction *importAnimation {0};
KisAction *closeAll {0};
// KisAction *reloadFile;
KisAction *importFile {0};
KisAction *exportFile {0};
KisAction *undo {0};
KisAction *redo {0};
KisAction *newWindow {0};
KisAction *close {0};
KisAction *mdiCascade {0};
KisAction *mdiTile {0};
KisAction *mdiNextWindow {0};
KisAction *mdiPreviousWindow {0};
KisAction *toggleDockers {0};
KisAction *toggleDockerTitleBars {0};
KisAction *expandingSpacers[2];
KActionMenu *dockWidgetMenu;
KActionMenu *windowMenu;
KActionMenu *documentMenu;
KActionMenu *workspaceMenu;
KHelpMenu *helpMenu {0};
KRecentFilesAction *recentFiles {0};
KoResourceModel *workspacemodel {0};
QString lastExportLocation;
QMap<QString, QDockWidget *> dockWidgetsMap;
QMap<QDockWidget *, bool> dockWidgetVisibilityMap;
QByteArray dockerStateBeforeHiding;
KoToolDocker *toolOptionsDocker {0};
QCloseEvent *deferredClosingEvent {0};
Digikam::ThemeManager *themeManager {0};
QMdiArea *mdiArea;
QMdiSubWindow *activeSubWindow {0};
QSignalMapper *windowMapper;
QSignalMapper *documentMapper;
QByteArray lastExportedFormat;
QScopedPointer<KisSignalCompressorWithParam<int> > tabSwitchCompressor;
QMutex savingEntryMutex;
KisActionManager * actionManager() {
return viewManager->actionManager();
}
QTabBar* findTabBarHACK() {
QObjectList objects = mdiArea->children();
Q_FOREACH (QObject *object, objects) {
QTabBar *bar = qobject_cast<QTabBar*>(object);
if (bar) {
return bar;
}
}
return 0;
}
};
KisMainWindow::KisMainWindow()
: KXmlGuiWindow()
, d(new Private(this))
{
auto rserver = KisResourceServerProvider::instance()->workspaceServer(false);
QSharedPointer<KoAbstractResourceServerAdapter> adapter(new KoResourceServerAdapter<KisWorkspaceResource>(rserver));
d->workspacemodel = new KoResourceModel(adapter, this);
connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); });
KisConfig cfg;
d->viewManager = new KisViewManager(this, actionCollection());
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this);
setAcceptDrops(true);
setStandardToolBarMenuEnabled(true);
setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
setDockNestingEnabled(true);
qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events
#ifdef Q_OS_OSX
setUnifiedTitleAndToolBarOnMac(true);
#endif
connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts()));
connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons()));
connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu()));
connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu()));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged()));
actionCollection()->addAssociatedWidget(this);
KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager);
KoToolBoxFactory toolBoxFactory;
QDockWidget *toolbox = createDockWidget(&toolBoxFactory);
toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
if (cfg.toolOptionsInDocker()) {
ToolDockerFactory toolDockerFactory;
d->toolOptionsDocker = qobject_cast<KoToolDocker*>(createDockWidget(&toolDockerFactory));
d->toolOptionsDocker->toggleViewAction()->setEnabled(true);
}
QMap<QString, QAction*> dockwidgetActions;
dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction();
Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) {
KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker);
QDockWidget *dw = createDockWidget(factory);
dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction();
}
if (d->toolOptionsDocker) {
dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction();
}
connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList<QPointer<QWidget> >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList<QPointer<QWidget> >)));
Q_FOREACH (QString title, dockwidgetActions.keys()) {
d->dockWidgetMenu->addAction(dockwidgetActions[title]);
}
Q_FOREACH (QDockWidget *wdg, dockWidgets()) {
if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) {
wdg->setVisible(true);
}
}
Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) {
observer->setObservedCanvas(0);
KisMainwindowObserver* mainwindowObserver = dynamic_cast<KisMainwindowObserver*>(observer);
if (mainwindowObserver) {
mainwindowObserver->setMainWindow(d->viewManager);
}
}
d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setTabPosition(QTabWidget::North);
d->mdiArea->setTabsClosable(true);
setCentralWidget(d->mdiArea);
connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated()));
connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*)));
connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*)));
createActions();
setAutoSaveSettings("krita", false);
subWindowActivated();
updateWindowMenu();
if (isHelpMenuEnabled() && !d->helpMenu) {
// workaround for KHelpMenu (or rather KAboutData::applicationData()) internally
// not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard
// not having the app version preset
// fixed hopefully in KF5 5.22.0, patch pending
QGuiApplication *app = qApp;
KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion());
aboutData.setOrganizationDomain(app->organizationDomain().toUtf8());
d->helpMenu = new KHelpMenu(this, aboutData, false);
// workaround-less version:
// d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false);
// The difference between using KActionCollection->addAction() is that
// these actions do not get tied to the MainWindow. What does this all do?
KActionCollection *actions = d->viewManager->actionCollection();
QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents);
QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis);
QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug);
QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage);
QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp);
QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE);
if (helpContentsAction) {
actions->addAction(helpContentsAction->objectName(), helpContentsAction);
}
if (whatsThisAction) {
actions->addAction(whatsThisAction->objectName(), whatsThisAction);
}
if (reportBugAction) {
actions->addAction(reportBugAction->objectName(), reportBugAction);
}
if (switchLanguageAction) {
actions->addAction(switchLanguageAction->objectName(), switchLanguageAction);
}
if (aboutAppAction) {
actions->addAction(aboutAppAction->objectName(), aboutAppAction);
}
if (aboutKdeAction) {
actions->addAction(aboutKdeAction->objectName(), aboutKdeAction);
}
connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication()));
}
// KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves
QAction *helpAction = actionCollection()->action("help_contents");
helpAction->disconnect();
connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual()));
#if 0
//check for colliding shortcuts
QSet<QKeySequence> existingShortcuts;
Q_FOREACH (QAction* action, actionCollection()->actions()) {
if(action->shortcut() == QKeySequence(0)) {
continue;
}
dbgKrita << "shortcut " << action->text() << " " << action->shortcut();
Q_ASSERT(!existingShortcuts.contains(action->shortcut()));
existingShortcuts.insert(action->shortcut());
}
#endif
configChanged();
// If we have customized the toolbars, load that first
setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui"));
setXMLFile(":/kxmlgui5/krita4.xmlgui");
guiFactory()->addClient(this);
// Create and plug toolbar list for Settings menu
QList<QAction *> toolbarList;
Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) {
KToolBar * toolBar = ::qobject_cast<KToolBar *>(it);
if (toolBar) {
if (toolBar->objectName() == "BrushesAndStuff") {
toolBar->setEnabled(false);
}
KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this);
actionCollection()->addAction(toolBar->objectName().toUtf8(), act);
act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle())));
connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool)));
act->setChecked(!toolBar->isHidden());
toolbarList.append(act);
} else {
warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!";
}
}
plugActionList("toolbarlist", toolbarList);
d->toolbarList = toolbarList;
applyToolBarLayout();
d->viewManager->updateGUI();
d->viewManager->updateIcons();
#ifdef Q_OS_WIN
auto w = qApp->activeWindow();
if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true);
#endif
QTimer::singleShot(1000, this, SLOT(checkSanity()));
{
using namespace std::placeholders; // For _1 placeholder
std::function<void (int)> callback(
std::bind(&KisMainWindow::switchTab, this, _1));
d->tabSwitchCompressor.reset(
new KisSignalCompressorWithParam<int>(500, callback, KisSignalCompressor::FIRST_INACTIVE));
}
}
void KisMainWindow::setNoCleanup(bool noCleanup)
{
d->noCleanup = noCleanup;
}
KisMainWindow::~KisMainWindow()
{
// Q_FOREACH (QAction *ac, actionCollection()->actions()) {
// QAction *action = qobject_cast<QAction*>(ac);
// if (action) {
// dbgKrita << "<Action"
// << "name=" << action->objectName()
// << "icon=" << action->icon().name()
// << "text=" << action->text().replace("&", "&amp;")
// << "whatsThis=" << action->whatsThis()
// << "toolTip=" << action->toolTip().replace("<html>", "").replace("</html>", "")
// << "iconText=" << action->iconText().replace("&", "&amp;")
// << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString()
// << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString()
// << "isCheckable=" << QString((action->isChecked() ? "true" : "false"))
// << "statusTip=" << action->statusTip()
// << "/>" ;
// }
// else {
// dbgKrita << "Got a QAction:" << ac->objectName();
// }
// }
// The doc and view might still exist (this is the case when closing the window)
KisPart::instance()->removeMainWindow(this);
if (d->noCleanup)
return;
delete d->viewManager;
delete d;
}
void KisMainWindow::addView(KisView *view)
{
if (d->activeView == view) return;
if (d->activeView) {
d->activeView->disconnect(this);
}
// register the newly created view in the input manager
viewManager()->inputManager()->addTrackedCanvas(view->canvasBase());
showView(view);
updateCaption();
emit restoringDone();
if (d->activeView) {
connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified()));
+ connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption()));
}
}
void KisMainWindow::notifyChildViewDestroyed(KisView *view)
{
viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase());
if (view->canvasBase() == viewManager()->canvasBase()) {
viewManager()->setCurrentView(0);
}
}
void KisMainWindow::showView(KisView *imageView)
{
if (imageView && activeView() != imageView) {
// XXX: find a better way to initialize this!
imageView->setViewManager(d->viewManager);
imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager());
imageView->slotLoadingFinished();
QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView);
subwin->setAttribute(Qt::WA_DeleteOnClose, true);
connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu()));
KisConfig cfg;
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
subwin->setWindowIcon(qApp->windowIcon());
/**
* Hack alert!
*
* Here we explicitly request KoToolManager to emit all the tool
* activation signals, to reinitialize the tool options docker.
*
* That is needed due to a design flaw we have in the
* initialization procedure. The tool in the KoToolManager is
* initialized in KisView::setViewManager() calls, which
* happens early enough. During this call the tool manager
* requests KoCanvasControllerWidget to emit the signal to
* update the widgets in the tool docker. *But* at that moment
* of time the view is not yet connected to the main window,
* because it happens in KisViewManager::setCurrentView a bit
* later. This fact makes the widgets updating signals be lost
* and never reach the tool docker.
*
* So here we just explicitly call the tool activation stub.
*/
KoToolManager::instance()->initializeCurrentToolForCanvas();
if (d->mdiArea->subWindowList().size() == 1) {
imageView->showMaximized();
}
else {
imageView->show();
}
// No, no, no: do not try to call this _before_ the show() has
// been called on the view; only when that has happened is the
// opengl context active, and very bad things happen if we tell
// the dockers to update themselves with a view if the opengl
// context is not active.
setActiveView(imageView);
updateWindowMenu();
updateCaption();
}
}
void KisMainWindow::slotPreferences()
{
if (KisDlgPreferences::editPreferences()) {
KisConfigNotifier::instance()->notifyConfigChanged();
KisUpdateSchedulerConfigNotifier::instance()->notifyConfigChanged();
// XXX: should this be changed for the views in other windows as well?
Q_FOREACH (QPointer<KisView> koview, KisPart::instance()->views()) {
KisViewManager *view = qobject_cast<KisViewManager*>(koview);
if (view) {
// Update the settings for all nodes -- they don't query
// KisConfig directly because they need the settings during
// compositing, and they don't connect to the config notifier
// because nodes are not QObjects (because only one base class
// can be a QObject).
KisNode* node = dynamic_cast<KisNode*>(view->image()->rootLayer().data());
node->updateSettings();
}
}
d->viewManager->showHideScrollbars();
}
}
void KisMainWindow::slotThemeChanged()
{
// save theme changes instantly
KConfigGroup group( KSharedConfig::openConfig(), "theme");
group.writeEntry("Theme", d->themeManager->currentThemeName());
// reload action icons!
Q_FOREACH (QAction *action, actionCollection()->actions()) {
KisIconUtils::updateIcon(action);
}
emit themeChanged();
}
void KisMainWindow::updateReloadFileAction(KisDocument *doc)
{
Q_UNUSED(doc);
// d->reloadFile->setEnabled(doc && !doc->url().isEmpty());
}
void KisMainWindow::setReadWrite(bool readwrite)
{
d->saveAction->setEnabled(readwrite);
d->importFile->setEnabled(readwrite);
d->readOnly = !readwrite;
updateCaption();
}
void KisMainWindow::addRecentURL(const QUrl &url)
{
dbgUI << "KisMainWindow::addRecentURL url=" << url.toDisplayString();
// Add entry to recent documents list
// (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.)
if (!url.isEmpty()) {
bool ok = true;
if (url.isLocalFile()) {
QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp");
for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it)
if (path.contains(*it))
ok = false; // it's in the tmp resource
#ifdef HAVE_KIO
if (ok) {
KRecentDocument::add(QUrl::fromLocalFile(path));
}
#endif
}
#ifdef HAVE_KIO
else {
KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash));
}
#endif
if (ok) {
d->recentFiles->addUrl(url);
}
saveRecentFiles();
}
}
void KisMainWindow::saveRecentFiles()
{
// Save list of recent files
KSharedConfigPtr config = KSharedConfig::openConfig();
d->recentFiles->saveEntries(config->group("RecentFiles"));
config->sync();
// Tell all windows to reload their list, after saving
// Doesn't work multi-process, but it's a start
Q_FOREACH (KMainWindow* window, KMainWindow::memberList()) {
/**
* FIXME: this is a hacking approach of reloading the updated recent files list.
* Sometimes, the result of reading from KConfig right after doing 'sync()' still
* returns old values of the recent files. Reading the same files a bit later
* returns correct "updated" files. I couldn't find the cause of it (DK).
*/
KisMainWindow *mw = static_cast<KisMainWindow *>(window);
if (mw != this) {
mw->reloadRecentFileList();
}
}
}
void KisMainWindow::reloadRecentFileList()
{
d->recentFiles->loadEntries( KSharedConfig::openConfig()->group("RecentFiles"));
}
void KisMainWindow::updateCaption()
{
if (!d->mdiArea->activeSubWindow()) {
updateCaption(QString(), false);
}
- else if (d->activeView && d->activeView->document()){
+ else if (d->activeView && d->activeView->document() && d->activeView->image()){
KisDocument *doc = d->activeView->document();
QString caption(doc->caption());
if (d->readOnly) {
- caption += ' ' + i18n("(write protected)");
+ caption += " [" + i18n("Write Protected") + "] ";
}
if (doc->isRecovered()) {
- caption += ' ' + i18n("[RECOVERED]");
+ caption += " [" + i18n("Recovered") + "] ";
+ }
+
+
+ // new documents aren't saved yet, so we don't need to say it is modified
+ // new files don't have a URL, so we are using that for the check
+ if (!doc->url().isEmpty()) {
+
+ if ( doc->isModified()) {
+ caption += " [" + i18n("Modified") + "] ";
+ }
+ }
+
+ // show the file size for the document
+
+
+ KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0);
+
+
+ if (m_fileSizeStats.imageSize) {
+ caption += QString(" (").append( KisStatusBar::formatSize(m_fileSizeStats.imageSize)).append( ")");
}
- caption += "[*]";
d->activeView->setWindowTitle(caption);
d->activeView->setWindowModified(doc->isModified());
updateCaption(caption, doc->isModified());
if (!doc->url().fileName().isEmpty())
d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName()));
else
d->saveAction->setToolTip(i18n("Save"));
}
}
void KisMainWindow::updateCaption(const QString & caption, bool mod)
{
dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")";
#ifdef KRITA_ALPHA
setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod);
return;
#endif
#ifdef KRITA_BETA
setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod);
return;
#endif
#ifdef KRITA_RC
setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod);
return;
#endif
setCaption(caption, mod);
}
KisView *KisMainWindow::activeView() const
{
if (d->activeView) {
return d->activeView;
}
return 0;
}
bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags)
{
if (!QFile(url.toLocalFile()).exists()) {
if (!flags && BatchMode) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url()));
}
d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list
saveRecentFiles();
return false;
}
return openDocumentInternal(url, flags);
}
bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags)
{
if (!url.isLocalFile()) {
qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url;
return false;
}
KisDocument *newdoc = KisPart::instance()->createDocument();
if (flags & BatchMode) {
newdoc->setFileBatchMode(true);
}
d->firstTime = true;
connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
KisDocument::OpenFlags openFlags = KisDocument::None;
if (flags & RecoveryFile) {
openFlags |= KisDocument::RecoveryFile;
}
bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url);
if (!openRet) {
delete newdoc;
return false;
}
KisPart::instance()->addDocument(newdoc);
updateReloadFileAction(newdoc);
if (!QFileInfo(url.toLocalFile()).isWritable()) {
setReadWrite(false);
}
return true;
}
KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document)
{
KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this);
addView(view);
emit guiLoadingFinished();
return view;
}
QStringList KisMainWindow::showOpenFileDialog(bool isImporting)
{
KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument");
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import));
dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images"));
return dialog.filenames();
}
// Separate from openDocument to handle async loading (remote URLs)
void KisMainWindow::slotLoadCompleted()
{
KisDocument *newdoc = qobject_cast<KisDocument*>(sender());
if (newdoc && newdoc->image()) {
addViewAndNotifyLoadingCompleted(newdoc);
disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
emit loadCompleted();
}
}
void KisMainWindow::slotLoadCanceled(const QString & errMsg)
{
dbgUI << "KisMainWindow::slotLoadCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
// ... can't delete the document, it's the one who emitted the signal...
KisDocument* doc = qobject_cast<KisDocument*>(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
}
void KisMainWindow::slotSaveCanceled(const QString &errMsg)
{
dbgUI << "KisMainWindow::slotSaveCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
slotSaveCompleted();
}
void KisMainWindow::slotSaveCompleted()
{
dbgUI << "KisMainWindow::slotSaveCompleted";
KisDocument* doc = qobject_cast<KisDocument*>(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &)));
if (d->deferredClosingEvent) {
KXmlGuiWindow::closeEvent(d->deferredClosingEvent);
}
}
bool KisMainWindow::hackIsSaving() const
{
StdLockableWrapper<QMutex> wrapper(&d->savingEntryMutex);
std::unique_lock<StdLockableWrapper<QMutex>> l(wrapper, std::try_to_lock);
return !l.owns_lock();
}
bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting)
{
if (!document) {
return true;
}
/**
* Make sure that we cannot enter this method twice!
*
* The lower level functions may call processEvents() so
* double-entry is quite possible to achieve. Here we try to lock
* the mutex, and if it is failed, just cancel saving.
*/
StdLockableWrapper<QMutex> wrapper(&d->savingEntryMutex);
std::unique_lock<StdLockableWrapper<QMutex>> l(wrapper, std::try_to_lock);
if (!l.owns_lock()) return false;
// no busy wait for saving because it is dangerous!
KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this);
dlg.blockIfImageIsBusy();
if (dlg.result() == KisDelayedSaveDialog::Rejected) {
return false;
}
else if (dlg.result() == KisDelayedSaveDialog::Ignored) {
QMessageBox::critical(0,
i18nc("@title:window", "Krita"),
i18n("You are saving a file while the image is "
"still rendering. The saved file may be "
"incomplete or corrupted.\n\n"
"Please select a location where the original "
"file will not be overridden!"));
saveas = true;
}
if (document->isRecovered()) {
saveas = true;
}
bool reset_url;
if (document->url().isEmpty()) {
reset_url = true;
saveas = true;
}
else {
reset_url = false;
}
connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &)));
QUrl oldURL = document->url();
QString oldFile = document->localFilePath();
QByteArray nativeFormat = document->nativeFormatMimeType();
QByteArray oldMimeFormat = document->mimeType();
QUrl suggestedURL = document->url();
QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export);
mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export);
if (!mimeFilter.contains(oldMimeFormat)) {
dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat;
// --- don't setOutputMimeType in case the user cancels the Save As
// dialog and then tries to just plain Save ---
// suggest a different filename extension (yes, we fortunately don't all live in a world of magic :))
QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName();
if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name
suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first();
suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename);
suggestedURL.setPath(suggestedURL.path() + suggestedFilename);
}
// force the user to choose outputMimeType
saveas = true;
}
bool ret = false;
if (document->url().isEmpty() || isExporting || saveas) {
// if you're just File/Save As'ing to change filter options you
// don't want to be reminded about overwriting files etc.
bool justChangingFilterOptions = false;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs");
dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As"));
//qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType());
if (isExporting && !d->lastExportLocation.isEmpty()) {
// Use the location where we last exported to, if it's set, as the opening location for the file dialog
QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath();
// If the document doesn't have a filename yet, use the title
QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName();
// Use the last mimetype we exported to by default
QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat;
QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,");
// Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty
dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true);
dialog.setMimeTypeFilters(mimeFilter, proposedMimeType);
}
else {
// Get the last used location for saving
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString proposedPath = group.readEntry("SaveAs", "");
// if that is empty, get the last used location for loading
if (proposedPath.isEmpty()) {
proposedPath = group.readEntry("OpenDocument", "");
}
// If that is empty, too, use the Pictures location.
if (proposedPath.isEmpty()) {
- proposedPath = QDesktopServices::storageLocation(QDesktopServices::PicturesLocation);
+ proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
}
// But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise
// open the location where the document currently is.
dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true);
// If exporting, default to all supported file types if user is exporting
QByteArray default_mime_type = "";
if (!isExporting) {
// otherwise use the document's mimetype, or if that is empty, kra, which is the savest.
default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType();
}
dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type));
}
QUrl newURL = QUrl::fromUserInput(dialog.filename());
if (newURL.isLocalFile()) {
QString fn = newURL.toLocalFile();
if (QFileInfo(fn).completeSuffix().isEmpty()) {
fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first());
newURL = QUrl::fromLocalFile(fn);
}
}
if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) {
QString fn = newURL.toLocalFile();
QFileInfo info(fn);
document->documentInfo()->setAboutInfo("title", info.baseName());
}
QByteArray outputFormat = nativeFormat;
QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile());
outputFormat = outputFormatString.toLatin1();
if (!isExporting) {
justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType());
}
else {
QString path = QFileInfo(d->lastExportLocation).absolutePath();
QString filename = QFileInfo(document->url().toLocalFile()).baseName();
justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path)
&& (QFileInfo(newURL.toLocalFile()).baseName() == filename)
&& (outputFormat == d->lastExportedFormat);
}
bool bOk = true;
if (newURL.isEmpty()) {
bOk = false;
}
if (bOk) {
bool wantToSave = true;
// don't change this line unless you know what you're doing :)
if (!justChangingFilterOptions) {
if (!document->isNativeFormat(outputFormat))
wantToSave = true;
}
if (wantToSave) {
if (!isExporting) { // Save As
ret = document->saveAs(newURL, outputFormat, true);
if (ret) {
dbgUI << "Successful Save As!";
KisPart::instance()->addRecentURLToAllMainWindows(newURL);
setReadWrite(true);
} else {
dbgUI << "Failed Save As!";
document->setUrl(oldURL);
document->setLocalFilePath(oldFile);
}
}
else { // Export
ret = document->exportDocument(newURL, outputFormat);
if (ret) {
d->lastExportLocation = newURL.toLocalFile();
d->lastExportedFormat = outputFormat;
}
}
} // if (wantToSave) {
else
ret = false;
} // if (bOk) {
else
ret = false;
} else { // saving
// We cannot "export" into the currently
// opened document. We are not Gimp.
KIS_ASSERT_RECOVER_NOOP(!isExporting);
// be sure document has the correct outputMimeType!
if (document->isModified()) {
ret = document->save(true, 0);
}
if (!ret) {
dbgUI << "Failed Save!";
document->setUrl(oldURL);
document->setLocalFilePath(oldFile);
}
}
if (ret && !isExporting) {
document->setRecovered(false);
}
if (!ret && reset_url)
document->resetURL(); //clean the suggested filename as the save dialog was rejected
updateReloadFileAction(document);
updateCaption();
return ret;
}
void KisMainWindow::undo()
{
if (activeView()) {
activeView()->undoAction()->trigger();
d->undo->setText(activeView()->undoAction()->text());
}
}
void KisMainWindow::redo()
{
if (activeView()) {
activeView()->redoAction()->trigger();
d->redo->setText(activeView()->redoAction()->text());
}
}
void KisMainWindow::closeEvent(QCloseEvent *e)
{
d->mdiArea->closeAllSubWindows();
QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only");
if ((action) && (action->isChecked())) {
action->setChecked(false);
}
KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow");
cfg.writeEntry("ko_geometry", saveGeometry().toBase64());
cfg.writeEntry("ko_windowstate", saveState().toBase64());
{
KConfigGroup group( KSharedConfig::openConfig(), "theme");
group.writeEntry("Theme", d->themeManager->currentThemeName());
}
QList<QMdiSubWindow*> childrenList = d->mdiArea->subWindowList();
if (childrenList.isEmpty()) {
d->deferredClosingEvent = e;
if (!d->dockerStateBeforeHiding.isEmpty()) {
restoreState(d->dockerStateBeforeHiding);
}
statusBar()->setVisible(true);
menuBar()->setVisible(true);
saveWindowSettings();
if (d->noCleanup)
return;
if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency
Q_FOREACH (QDockWidget* dockWidget, d->dockWidgetsMap)
dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget));
}
} else {
e->setAccepted(false);
}
}
void KisMainWindow::saveWindowSettings()
{
KSharedConfigPtr config = KSharedConfig::openConfig();
if (d->windowSizeDirty ) {
dbgUI << "KisMainWindow::saveWindowSettings";
KConfigGroup group = config->group("MainWindow");
KWindowConfig::saveWindowSize(windowHandle(), group);
config->sync();
d->windowSizeDirty = false;
}
if (!d->activeView || d->activeView->document()) {
// Save toolbar position into the config file of the app, under the doc's component name
KConfigGroup group = KSharedConfig::openConfig()->group("krita");
saveMainWindowSettings(group);
// Save collapsable state of dock widgets
for (QMap<QString, QDockWidget*>::const_iterator i = d->dockWidgetsMap.constBegin();
i != d->dockWidgetsMap.constEnd(); ++i) {
if (i.value()->widget()) {
KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key());
dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden());
dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool());
dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value()));
dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x());
dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y());
dockGroup.writeEntry("width", (int) i.value()->widget()->width());
dockGroup.writeEntry("height", (int) i.value()->widget()->height());
}
}
}
KSharedConfig::openConfig()->sync();
resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down
}
void KisMainWindow::resizeEvent(QResizeEvent * e)
{
d->windowSizeDirty = true;
KXmlGuiWindow::resizeEvent(e);
}
void KisMainWindow::setActiveView(KisView* view)
{
d->activeView = view;
updateCaption();
actionCollection()->action("edit_undo")->setText(activeView()->undoAction()->text());
actionCollection()->action("edit_redo")->setText(activeView()->redoAction()->text());
d->viewManager->setCurrentView(view);
}
void KisMainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
void KisMainWindow::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) {
Q_FOREACH (const QUrl &url, event->mimeData()->urls()) {
openDocument(url, None);
}
}
}
void KisMainWindow::dragMoveEvent(QDragMoveEvent * event)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) {
qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!";
}
if (tabBar && tabBar->isVisible()) {
QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos()));
if (tabBar->rect().contains(pos)) {
const int tabIndex = tabBar->tabAt(pos);
if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) {
d->tabSwitchCompressor->start(tabIndex);
}
} else if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
}
void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/)
{
if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
void KisMainWindow::switchTab(int index)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar) return;
tabBar->setCurrentIndex(index);
}
void KisMainWindow::slotFileNew()
{
const QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Import);
KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/"));
startupWidget->setWindowModality(Qt::WindowModal);
KisConfig cfg;
int w = cfg.defImageWidth();
int h = cfg.defImageHeight();
const double resolution = cfg.defImageResolution();
const QString colorModel = cfg.defColorModel();
const QString colorDepth = cfg.defaultColorDepth();
const QString colorProfile = cfg.defColorProfile();
CustomDocumentWidgetItem item;
item.widget = new KisCustomImageWidget(startupWidget,
w,
h,
resolution,
colorModel,
colorDepth,
colorProfile,
i18n("Unnamed"));
item.icon = "document-new";
startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
QSize sz = KisClipboard::instance()->clipSize();
if (sz.isValid() && sz.width() != 0 && sz.height() != 0) {
w = sz.width();
h = sz.height();
}
item.widget = new KisImageFromClipboard(startupWidget,
w,
h,
resolution,
colorModel,
colorDepth,
colorProfile,
i18n("Unnamed"));
item.title = i18n("Create from Clipboard");
item.icon = "tab-new";
startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
// calls deleteLater
connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*)));
// calls deleteLater
connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&)));
startupWidget->exec();
// Cancel calls deleteLater...
}
void KisMainWindow::slotImportFile()
{
dbgUI << "slotImportFile()";
slotFileOpen(true);
}
void KisMainWindow::slotFileOpen(bool isImporting)
{
QStringList urls = showOpenFileDialog(isImporting);
if (urls.isEmpty())
return;
Q_FOREACH (const QString& url, urls) {
if (!url.isEmpty()) {
OpenFlags flags = isImporting ? Import : None;
bool res = openDocument(QUrl::fromLocalFile(url), flags);
if (!res) {
warnKrita << "Loading" << url << "failed";
}
}
}
}
void KisMainWindow::slotFileOpenRecent(const QUrl &url)
{
(void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None);
}
void KisMainWindow::slotFileSave()
{
if (saveDocument(d->activeView->document(), false, false)) {
emit documentSaved();
}
}
void KisMainWindow::slotFileSaveAs()
{
if (saveDocument(d->activeView->document(), true, false)) {
emit documentSaved();
}
}
void KisMainWindow::slotExportFile()
{
if (saveDocument(d->activeView->document(), true, true)) {
emit documentSaved();
}
}
KoCanvasResourceManager *KisMainWindow::resourceManager() const
{
return d->viewManager->resourceProvider()->resourceManager();
}
int KisMainWindow::viewCount() const
{
return d->mdiArea->subWindowList().size();
}
bool KisMainWindow::restoreWorkspace(const QByteArray &state)
{
QByteArray oldState = saveState();
const bool showTitlebars = KisConfig().showDockerTitleBars();
// needed because otherwise the layout isn't correctly restored in some situations
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
dock->hide();
dock->titleBarWidget()->setVisible(showTitlebars);
}
bool success = KXmlGuiWindow::restoreState(state);
if (!success) {
KXmlGuiWindow::restoreState(oldState);
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
if (dock->titleBarWidget()) {
dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating());
}
}
return false;
}
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
if (dock->titleBarWidget()) {
const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget();
dock->titleBarWidget()->setVisible(showTitlebars || (dock->isFloating() && isCollapsed));
}
}
return success;
}
KisViewManager *KisMainWindow::viewManager() const
{
return d->viewManager;
}
void KisMainWindow::slotDocumentInfo()
{
if (!d->activeView->document())
return;
KoDocumentInfo *docInfo = d->activeView->document()->documentInfo();
if (!docInfo)
return;
KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo);
if (dlg->exec()) {
if (dlg->isDocumentSaved()) {
d->activeView->document()->setModified(false);
} else {
d->activeView->document()->setModified(true);
}
d->activeView->document()->setTitleModified();
}
delete dlg;
}
bool KisMainWindow::slotFileCloseAll()
{
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
if (subwin) {
if(!subwin->close())
return false;
}
}
updateCaption();
return true;
}
void KisMainWindow::slotFileQuit()
{
if(!slotFileCloseAll())
return;
close();
Q_FOREACH (QPointer<KisMainWindow> mainWin, KisPart::instance()->mainWindows()) {
if (mainWin != this) {
if(!mainWin->slotFileCloseAll())
return;
mainWin->close();
}
}
}
void KisMainWindow::slotFilePrint()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
applyDefaultSettings(printJob->printer());
QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this );
if (printDialog && printDialog->exec() == QDialog::Accepted) {
printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point);
printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()),
activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())),
QPrinter::Inch);
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
}
else {
delete printJob;
}
delete printDialog;
}
void KisMainWindow::slotFilePrintPreview()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
/* Sets the startPrinting() slot to be blocking.
The Qt print-preview dialog requires the printing to be completely blocking
and only return when the full document has been printed.
By default the KisPrintingDialog is non-blocking and
multithreading, setting blocking to true will allow it to be used in the preview dialog */
printJob->setProperty("blocking", true);
QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this);
printJob->setParent(preview); // will take care of deleting the job
connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting()));
preview->exec();
delete preview;
}
KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName)
{
if (!activeView())
return 0;
if (!activeView()->document())
return 0;
KoPageLayout pageLayout;
pageLayout.width = 0;
pageLayout.height = 0;
pageLayout.topMargin = 0;
pageLayout.bottomMargin = 0;
pageLayout.leftMargin = 0;
pageLayout.rightMargin = 0;
if (pdfFileName.isEmpty()) {
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString defaultDir = group.readEntry("SavePdfDialog");
if (defaultDir.isEmpty())
- defaultDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
+ defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QUrl startUrl = QUrl::fromLocalFile(defaultDir);
KisDocument* pDoc = d->activeView->document();
/** if document has a file name, take file name and replace extension with .pdf */
if (pDoc && pDoc->url().isValid()) {
startUrl = pDoc->url();
QString fileName = startUrl.toLocalFile();
fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" );
startUrl = startUrl.adjusted(QUrl::RemoveFilename);
startUrl.setPath(startUrl.path() + fileName );
}
QPointer<KoPageLayoutDialog> layoutDlg(new KoPageLayoutDialog(this, pageLayout));
layoutDlg->setWindowModality(Qt::WindowModal);
if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) {
delete layoutDlg;
return 0;
}
pageLayout = layoutDlg->pageLayout();
delete layoutDlg;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument");
dialog.setCaption(i18n("Export as PDF"));
dialog.setDefaultDir(startUrl.toLocalFile());
dialog.setMimeTypeFilters(QStringList() << "application/pdf");
QUrl url = QUrl::fromUserInput(dialog.filename());
pdfFileName = url.toLocalFile();
if (pdfFileName.isEmpty())
return 0;
}
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return 0;
if (isHidden()) {
printJob->setProperty("noprogressdialog", true);
}
applyDefaultSettings(printJob->printer());
// TODO for remote files we have to first save locally and then upload.
printJob->printer().setOutputFileName(pdfFileName);
printJob->printer().setDocName(pdfFileName);
printJob->printer().setColorMode(QPrinter::Color);
if (pageLayout.format == KoPageFormat::CustomSize) {
printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter);
} else {
printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format));
}
printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter);
switch (pageLayout.orientation) {
case KoPageFormat::Portrait:
printJob->printer().setOrientation(QPrinter::Portrait);
break;
case KoPageFormat::Landscape:
printJob->printer().setOrientation(QPrinter::Landscape);
break;
}
//before printing check if the printer can handle printing
if (!printJob->canPrint()) {
QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file"));
}
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
return printJob;
}
void KisMainWindow::importAnimation()
{
if (!activeView()) return;
KisDocument *document = activeView()->document();
if (!document) return;
KisDlgImportImageSequence dlg(this, document);
if (dlg.exec() == QDialog::Accepted) {
QStringList files = dlg.files();
int firstFrame = dlg.firstFrame();
int step = dlg.step();
KoUpdaterPtr updater =
!document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0;
KisAnimationImporter importer(document->image(), updater);
KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step);
if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty())
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg));
}
activeView()->canvasBase()->refetchDataFromImage();
}
}
void KisMainWindow::slotConfigureToolbars()
{
KConfigGroup group = KSharedConfig::openConfig()->group("krita");
saveMainWindowSettings(group);
KEditToolBar edit(factory(), this);
connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig()));
(void) edit.exec();
applyToolBarLayout();
}
void KisMainWindow::slotNewToolbarConfig()
{
applyMainWindowSettings(KSharedConfig::openConfig()->group("krita"));
KXMLGUIFactory *factory = guiFactory();
Q_UNUSED(factory);
// Check if there's an active view
if (!d->activeView)
return;
plugActionList("toolbarlist", d->toolbarList);
applyToolBarLayout();
}
void KisMainWindow::slotToolbarToggled(bool toggle)
{
//dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true;
// The action (sender) and the toolbar have the same name
KToolBar * bar = toolBar(sender()->objectName());
if (bar) {
if (toggle) {
bar->show();
}
else {
bar->hide();
}
if (d->activeView && d->activeView->document()) {
KConfigGroup group = KSharedConfig::openConfig()->group("krita");
saveMainWindowSettings(group);
}
} else
warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!";
}
void KisMainWindow::viewFullscreen(bool fullScreen)
{
KisConfig cfg;
cfg.setFullscreenMode(fullScreen);
if (fullScreen) {
setWindowState(windowState() | Qt::WindowFullScreen); // set
} else {
setWindowState(windowState() & ~Qt::WindowFullScreen); // reset
}
}
void KisMainWindow::setMaxRecentItems(uint _number)
{
d->recentFiles->setMaxItems(_number);
}
void KisMainWindow::slotReloadFile()
{
KisDocument* document = d->activeView->document();
if (!document || document->url().isEmpty())
return;
if (document->isModified()) {
bool ok = QMessageBox::question(this,
i18nc("@title:window", "Krita"),
i18n("You will lose all changes made since your last save\n"
"Do you want to continue?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes;
if (!ok)
return;
}
QUrl url = document->url();
saveWindowSettings();
if (!document->reload()) {
QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document"));
}
return;
}
QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory)
{
QDockWidget* dockWidget = 0;
if (!d->dockWidgetsMap.contains(factory->id())) {
dockWidget = factory->createDockWidget();
// It is quite possible that a dock factory cannot create the dock; don't
// do anything in that case.
if (!dockWidget) {
warnKrita << "Could not create docker for" << factory->id();
return 0;
}
KoDockWidgetTitleBar *titleBar = dynamic_cast<KoDockWidgetTitleBar*>(dockWidget->titleBarWidget());
// Check if the dock widget is supposed to be collapsable
if (!dockWidget->titleBarWidget()) {
titleBar = new KoDockWidgetTitleBar(dockWidget);
dockWidget->setTitleBarWidget(titleBar);
titleBar->setCollapsable(factory->isCollapsable());
}
titleBar->setFont(KoDockRegistry::dockFont());
dockWidget->setObjectName(factory->id());
dockWidget->setParent(this);
if (dockWidget->widget() && dockWidget->widget()->layout())
dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1);
Qt::DockWidgetArea side = Qt::RightDockWidgetArea;
bool visible = true;
switch (factory->defaultDockPosition()) {
case KoDockFactoryBase::DockTornOff:
dockWidget->setFloating(true); // position nicely?
break;
case KoDockFactoryBase::DockTop:
side = Qt::TopDockWidgetArea; break;
case KoDockFactoryBase::DockLeft:
side = Qt::LeftDockWidgetArea; break;
case KoDockFactoryBase::DockBottom:
side = Qt::BottomDockWidgetArea; break;
case KoDockFactoryBase::DockRight:
side = Qt::RightDockWidgetArea; break;
case KoDockFactoryBase::DockMinimized:
default:
side = Qt::RightDockWidgetArea;
visible = false;
}
KConfigGroup group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id());
side = static_cast<Qt::DockWidgetArea>(group.readEntry("DockArea", static_cast<int>(side)));
if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea;
addDockWidget(side, dockWidget);
if (!visible) {
dockWidget->hide();
}
bool collapsed = factory->defaultCollapsed();
bool locked = false;
group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id());
collapsed = group.readEntry("Collapsed", collapsed);
locked = group.readEntry("Locked", locked);
//dbgKrita << "docker" << factory->id() << dockWidget << "collapsed" << collapsed << "locked" << locked << "titlebar" << titleBar;
if (titleBar && collapsed)
titleBar->setCollapsed(true);
if (titleBar && locked)
titleBar->setLocked(true);
d->dockWidgetsMap.insert(factory->id(), dockWidget);
}
else {
dockWidget = d->dockWidgetsMap[factory->id()];
}
#ifdef Q_OS_OSX
dockWidget->setAttribute(Qt::WA_MacSmallSize, true);
#endif
dockWidget->setFont(KoDockRegistry::dockFont());
connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts()));
return dockWidget;
}
void KisMainWindow::forceDockTabFonts()
{
Q_FOREACH (QObject *child, children()) {
if (child->inherits("QTabBar")) {
((QTabBar *)child)->setFont(KoDockRegistry::dockFont());
}
}
}
QList<QDockWidget*> KisMainWindow::dockWidgets() const
{
return d->dockWidgetsMap.values();
}
QDockWidget* KisMainWindow::dockWidget(const QString &id)
{
if (!d->dockWidgetsMap.contains(id)) return 0;
return d->dockWidgetsMap[id];
}
QList<KoCanvasObserverBase*> KisMainWindow::canvasObservers() const
{
QList<KoCanvasObserverBase*> observers;
Q_FOREACH (QDockWidget *docker, dockWidgets()) {
KoCanvasObserverBase *observer = dynamic_cast<KoCanvasObserverBase*>(docker);
if (observer) {
observers << observer;
}
else {
warnKrita << docker << "is not a canvas observer";
}
}
return observers;
}
void KisMainWindow::toggleDockersVisibility(bool visible)
{
if (!visible) {
d->dockerStateBeforeHiding = saveState();
Q_FOREACH (QObject* widget, children()) {
if (widget->inherits("QDockWidget")) {
QDockWidget* dw = static_cast<QDockWidget*>(widget);
if (dw->isVisible()) {
dw->hide();
}
}
}
}
else {
restoreState(d->dockerStateBeforeHiding);
}
}
void KisMainWindow::slotDocumentTitleModified()
{
updateCaption();
updateReloadFileAction(d->activeView ? d->activeView->document() : 0);
}
void KisMainWindow::subWindowActivated()
{
bool enabled = (activeKisView() != 0);
d->mdiCascade->setEnabled(enabled);
d->mdiNextWindow->setEnabled(enabled);
d->mdiPreviousWindow->setEnabled(enabled);
d->mdiTile->setEnabled(enabled);
d->close->setEnabled(enabled);
d->closeAll->setEnabled(enabled);
setActiveSubWindow(d->mdiArea->activeSubWindow());
Q_FOREACH (QToolBar *tb, toolBars()) {
if (tb->objectName() == "BrushesAndStuff") {
tb->setEnabled(enabled);
}
}
updateCaption();
d->actionManager()->updateGUI();
}
void KisMainWindow::updateWindowMenu()
{
QMenu *menu = d->windowMenu->menu();
menu->clear();
menu->addAction(d->newWindow);
menu->addAction(d->documentMenu);
QMenu *docMenu = d->documentMenu->menu();
docMenu->clear();
Q_FOREACH (QPointer<KisDocument> doc, KisPart::instance()->documents()) {
if (doc) {
QString title = doc->url().toDisplayString();
if (title.isEmpty() && doc->image()) {
title = doc->image()->objectName();
}
QAction *action = docMenu->addAction(title);
action->setIcon(qApp->windowIcon());
connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map()));
d->documentMapper->setMapping(action, doc);
}
}
menu->addAction(d->workspaceMenu);
QMenu *workspaceMenu = d->workspaceMenu->menu();
workspaceMenu->clear();
auto workspaces = KisResourceServerProvider::instance()->workspaceServer(false)->resources();
auto m_this = this;
for (auto &w : workspaces) {
auto action = workspaceMenu->addAction(w->name());
auto ds = w->dockerState();
connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(ds); });
}
workspaceMenu->addSeparator();
connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")),
&QAction::triggered,
this,
[&]() {
QString extensions = d->workspacemodel->extensions();
QStringList mimeTypes;
for(const QString &suffix : extensions.split(":")) {
mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix);
}
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@title:window", "Choose File to Add"));
QString filename = dialog.filename();
d->workspacemodel->importResourceFile(filename);
});
connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")),
&QAction::triggered,
[=]() {
QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."),
i18nc("@label:textbox", "Name:"));
if (name.isEmpty()) return;
auto rserver = KisResourceServerProvider::instance()->workspaceServer();
KisWorkspaceResource* workspace = new KisWorkspaceResource("");
workspace->setDockerState(m_this->saveState());
d->viewManager->resourceProvider()->notifySavingWorkspace(workspace);
workspace->setValid(true);
QString saveLocation = rserver->saveLocation();
bool newName = false;
if(name.isEmpty()) {
newName = true;
name = i18n("Workspace");
}
QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension());
int i = 1;
while (fileInfo.exists()) {
fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension());
i++;
}
workspace->setFilename(fileInfo.filePath());
if(newName) {
name = i18n("Workspace %1", i);
}
workspace->setName(name);
rserver->addResource(workspace);
});
// TODO: What to do about delete?
// workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace..."));
menu->addSeparator();
menu->addAction(d->close);
menu->addAction(d->closeAll);
if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) {
menu->addSeparator();
menu->addAction(d->mdiTile);
menu->addAction(d->mdiCascade);
}
menu->addSeparator();
menu->addAction(d->mdiNextWindow);
menu->addAction(d->mdiPreviousWindow);
menu->addSeparator();
QList<QMdiSubWindow *> windows = d->mdiArea->subWindowList();
for (int i = 0; i < windows.size(); ++i) {
QPointer<KisView>child = qobject_cast<KisView*>(windows.at(i)->widget());
if (child && child->document()) {
QString text;
if (i < 9) {
text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString());
}
else {
text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString());
}
QAction *action = menu->addAction(text);
action->setIcon(qApp->windowIcon());
action->setCheckable(true);
action->setChecked(child == activeKisView());
connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map()));
d->windowMapper->setMapping(action, windows.at(i));
}
}
updateCaption();
}
void KisMainWindow::setActiveSubWindow(QWidget *window)
{
if (!window) return;
QMdiSubWindow *subwin = qobject_cast<QMdiSubWindow *>(window);
//dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow;
if (subwin && subwin != d->activeSubWindow) {
KisView *view = qobject_cast<KisView *>(subwin->widget());
//dbgKrita << "\t" << view << activeView();
if (view && view != activeView()) {
d->mdiArea->setActiveSubWindow(subwin);
setActiveView(view);
}
d->activeSubWindow = subwin;
}
updateWindowMenu();
d->actionManager()->updateGUI();
}
void KisMainWindow::configChanged()
{
KisConfig cfg;
QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry<int>("mdi_viewmode", (int)QMdiArea::TabbedView);
d->mdiArea->setViewMode(viewMode);
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
/**
* Dirty workaround for a bug in Qt (checked on Qt 5.6.1):
*
* If you make a window "Show on top" and then switch to the tabbed mode
* the window will contiue to be painted in its initial "mid-screen"
* position. It will persist here until you explicitly switch to its tab.
*/
if (viewMode == QMdiArea::TabbedView) {
Qt::WindowFlags oldFlags = subwin->windowFlags();
Qt::WindowFlags flags = oldFlags;
flags &= ~Qt::WindowStaysOnTopHint;
flags &= ~Qt::WindowStaysOnBottomHint;
if (flags != oldFlags) {
subwin->setWindowFlags(flags);
subwin->showMaximized();
}
}
}
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark"));
d->actionManager()->updateGUI();
QBrush brush(cfg.getMDIBackgroundColor());
d->mdiArea->setBackground(brush);
QString backgroundImage = cfg.getMDIBackgroundImage();
if (backgroundImage != "") {
QImage image(backgroundImage);
QBrush brush(image);
d->mdiArea->setBackground(brush);
}
d->mdiArea->update();
}
KisView* KisMainWindow::newView(QObject *document)
{
KisDocument *doc = qobject_cast<KisDocument*>(document);
KisView *view = addViewAndNotifyLoadingCompleted(doc);
d->actionManager()->updateGUI();
return view;
}
void KisMainWindow::newWindow()
{
KisPart::instance()->createMainWindow()->show();
}
void KisMainWindow::closeCurrentWindow()
{
d->mdiArea->currentSubWindow()->close();
d->actionManager()->updateGUI();
}
void KisMainWindow::checkSanity()
{
// print error if the lcms engine is not available
if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) {
// need to wait 1 event since exiting here would not work.
m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
if (rserver->resources().isEmpty()) {
m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
}
void KisMainWindow::showErrorAndDie()
{
QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage);
if (m_dieOnError) {
exit(10);
}
}
void KisMainWindow::showAboutApplication()
{
KisAboutApplication dlg(this);
dlg.exec();
}
QPointer<KisView>KisMainWindow::activeKisView()
{
if (!d->mdiArea) return 0;
QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow();
//dbgKrita << "activeKisView" << activeSubWindow;
if (!activeSubWindow) return 0;
return qobject_cast<KisView*>(activeSubWindow->widget());
}
void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList<QPointer<QWidget> > &optionWidgetList)
{
KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController());
bool isOurOwnView = false;
Q_FOREACH (QPointer<KisView> view, KisPart::instance()->views()) {
if (view && view->canvasController() == controller) {
isOurOwnView = view->mainWindow() == this;
}
}
if (!isOurOwnView) return;
Q_FOREACH (QWidget *w, optionWidgetList) {
#ifdef Q_OS_OSX
w->setAttribute(Qt::WA_MacSmallSize, true);
#endif
w->setFont(KoDockRegistry::dockFont());
}
if (d->toolOptionsDocker) {
d->toolOptionsDocker->setOptionWidgets(optionWidgetList);
}
else {
d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList);
}
}
void KisMainWindow::applyDefaultSettings(QPrinter &printer) {
if (!d->activeView) return;
QString title = d->activeView->document()->documentInfo()->aboutInfo("title");
if (title.isEmpty()) {
QFileInfo info(d->activeView->document()->url().fileName());
title = info.baseName();
}
if (title.isEmpty()) {
// #139905
title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(),
QLocale().toString(QDate::currentDate(), QLocale::ShortFormat));
}
printer.setDocName(title);
}
void KisMainWindow::createActions()
{
KisActionManager *actionManager = d->actionManager();
KisConfig cfg;
actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew()));
actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen()));
actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit()));
actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars()));
actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool)));
d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection());
connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles()));
KSharedConfigPtr configPtr = KSharedConfig::openConfig();
d->recentFiles->loadEntries(configPtr->group("RecentFiles"));
d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave()));
d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs()));
d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint()));
// d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview()));
// d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo()));
d->undo ->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo()));
d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->exportPdf = actionManager->createAction("file_export_pdf");
// connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf()));
d->importAnimation = actionManager->createAction("file_import_animation");
connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation()));
d->closeAll = actionManager->createAction("file_close_all");
connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll()));
// d->reloadFile = actionManager->createAction("file_reload_file");
// d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED);
// connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile()));
d->importFile = actionManager->createAction("file_import_file");
connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile()));
d->exportFile = actionManager->createAction("file_export_file");
connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile()));
/* The following entry opens the document information dialog. Since the action is named so it
intends to show data this entry should not have a trailing ellipses (...). */
d->showDocumentInfo = actionManager->createAction("file_documentinfo");
connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo()));
d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this));
d->themeManager->registerThemeActions(actionCollection());
connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged()));
d->toggleDockers = actionManager->createAction("view_toggledockers");
cfg.showDockers(true);
d->toggleDockers->setChecked(true);
connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool)));
d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars");
d->toggleDockerTitleBars->setChecked(cfg.showDockerTitleBars());
connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool)));
actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu);
actionCollection()->addAction("window", d->windowMenu);
d->mdiCascade = actionManager->createAction("windows_cascade");
connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows()));
d->mdiTile = actionManager->createAction("windows_tile");
connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows()));
d->mdiNextWindow = actionManager->createAction("windows_next");
connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow()));
d->mdiPreviousWindow = actionManager->createAction("windows_previous");
connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow()));
d->newWindow = actionManager->createAction("view_newwindow");
connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
d->close = actionManager->createAction("file_close");
connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow()));
actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences()));
for (int i = 0; i < 2; i++) {
d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer"));
d->expandingSpacers[i]->setDefaultWidget(new QWidget(this));
d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]);
}
}
void KisMainWindow::applyToolBarLayout()
{
const bool isPlastiqueStyle = style()->objectName() == "plastique";
Q_FOREACH (KToolBar *toolBar, toolBars()) {
toolBar->layout()->setSpacing(4);
if (isPlastiqueStyle) {
toolBar->setContentsMargins(0, 0, 0, 2);
}
//Hide text for buttons with an icon in the toolbar
Q_FOREACH (QAction *ac, toolBar->actions()){
if (ac->icon().pixmap(QSize(1,1)).isNull() == false){
ac->setPriority(QAction::LowPriority);
}else {
ac->setIcon(QIcon());
}
}
}
}
void KisMainWindow::initializeGeometry()
{
// if the user didn's specify the geometry on the command line (does anyone do that still?),
// we first figure out some good default size and restore the x,y position. See bug 285804Z.
KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow");
QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray()));
if (!restoreGeometry(geom)) {
const int scnum = QApplication::desktop()->screenNumber(parentWidget());
QRect desk = QApplication::desktop()->availableGeometry(scnum);
// if the desktop is virtual then use virtual screen size
if (QApplication::desktop()->isVirtualDesktop()) {
desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum));
}
quint32 x = desk.x();
quint32 y = desk.y();
quint32 w = 0;
quint32 h = 0;
// Default size -- maximize on small screens, something useful on big screens
const int deskWidth = desk.width();
if (deskWidth > 1024) {
// a nice width, and slightly less than total available
// height to componensate for the window decs
w = (deskWidth / 3) * 2;
h = (desk.height() / 3) * 2;
}
else {
w = desk.width();
h = desk.height();
}
x += (desk.width() - w) / 2;
y += (desk.height() - h) / 2;
move(x,y);
setGeometry(geometry().x(), geometry().y(), w, h);
}
restoreWorkspace(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray())));
}
void KisMainWindow::showManual()
{
QDesktopServices::openUrl(QUrl("https://docs.krita.org"));
}
void KisMainWindow::showDockerTitleBars(bool show)
{
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
if (dock->titleBarWidget()) {
const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget();
dock->titleBarWidget()->setVisible(show || (dock->isFloating() && isCollapsed));
}
}
KisConfig cfg;
cfg.setShowDockerTitleBars(show);
}
void KisMainWindow::moveEvent(QMoveEvent *e)
{
if (qApp->desktop()->screenNumber(this) != qApp->desktop()->screenNumber(e->oldPos())) {
KisConfigNotifier::instance()->notifyConfigChanged();
}
}
#include <moc_KisMainWindow.cpp>
diff --git a/libs/ui/KisMainWindow.h b/libs/ui/KisMainWindow.h
index fbe45366a2..0ee88eb53a 100644
--- a/libs/ui/KisMainWindow.h
+++ b/libs/ui/KisMainWindow.h
@@ -1,467 +1,468 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000-2004 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KIS_MAIN_WINDOW_H
#define KIS_MAIN_WINDOW_H
#include "kritaui_export.h"
#include <QPointer>
#include <QPrinter>
#include <xmlgui/kxmlguiwindow.h>
#include <QUrl>
#include <KoCanvasObserverBase.h>
#include <KoCanvasSupervisor.h>
-
#include "KisView.h"
class QCloseEvent;
class QMoveEvent;
struct KoPageLayout;
class KoCanvasResourceManager;
class KisDocument;
class KisPrintJob;
class KoDockFactoryBase;
class QDockWidget;
class KisView;
class KisViewManager;
class KoCanvasController;
/**
* @brief Main window for Krita
*
* This class is used to represent a main window within a Krita session. Each
* main window contains a menubar and some toolbars, and potentially several
* views of several canvases.
*
*/
class KRITAUI_EXPORT KisMainWindow : public KXmlGuiWindow, public KoCanvasSupervisor
{
Q_OBJECT
public:
enum OpenFlag {
None = 0,
Import = 0x1,
BatchMode = 0x2,
RecoveryFile = 0x4
};
Q_DECLARE_FLAGS(OpenFlags, OpenFlag)
public:
/**
* Constructor.
*
* Initializes a Calligra main window (with its basic GUI etc.).
*/
explicit KisMainWindow();
/**
* Destructor.
*/
~KisMainWindow() override;
- /**
- * Update caption from document info - call when document info
- * (title in the about page) changes.
- */
- void updateCaption();
-
// If noCleanup is set, KisMainWindow will not delete the root document
// or part manager on destruction.
void setNoCleanup(bool noCleanup);
/**
* @brief showView shows the given view. Override this if you want to show
* the view in a different way than by making it the central widget, for instance
* as an QMdiSubWindow
*/
virtual void showView(KisView *view);
/**
* @returns the currently active view
*/
KisView *activeView() const;
/**
* Sets the maximum number of recent documents entries.
*/
void setMaxRecentItems(uint _number);
/**
* The document opened a URL -> store into recent documents list.
*/
void addRecentURL(const QUrl &url);
/**
* Load the desired document and show it.
* @param url the URL to open
*
* @return TRUE on success.
*/
bool openDocument(const QUrl &url, OpenFlags flags);
/**
* Saves the document, asking for a filename if necessary.
*
* @param saveas if set to TRUE the user is always prompted for a filename
* @param silent if set to TRUE rootDocument()->setTitleModified will not be called.
*
* @return TRUE on success, false on error or cancel
* (don't display anything in this case, the error dialog box is also implemented here
* but restore the original URL in slotFileSaveAs)
*/
bool saveDocument(KisDocument *document, bool saveas, bool isExporting);
void setReadWrite(bool readwrite);
/// Return the list of dock widgets belonging to this main window.
QList<QDockWidget*> dockWidgets() const;
QDockWidget* dockWidget(const QString &id);
QList<KoCanvasObserverBase*> canvasObservers() const override;
KoCanvasResourceManager *resourceManager() const;
int viewCount() const;
/**
* A wrapper around restoreState
* @param state the saved state
* @return TRUE on success
*/
bool restoreWorkspace(const QByteArray &state);
KisViewManager *viewManager() const;
KisView *addViewAndNotifyLoadingCompleted(KisDocument *document);
QStringList showOpenFileDialog(bool isImporting);
/**
* Shows if the main window is saving anything right now. If the
* user presses Ctrl+W too fast, then the document can be close
* before the saving is completed. I'm not sure if it is fixable
* in any way without avoiding using porcessEvents()
* everywhere (DK)
*
* Don't use it unless you have no option.
*/
bool hackIsSaving() const;
Q_SIGNALS:
/**
* This signal is emitted if the document has been saved successfully.
*/
void documentSaved();
/// This signal is emitted when this windows has finished loading of a
/// document. The document may be opened in another window in the end.
/// In this case, the signal means there is no link between the window
/// and the document anymore.
void loadCompleted();
/// This signal is emitted right after the docker states have been succefully restored from config
void restoringDone();
/// This signal is emitted when the color theme changes
void themeChanged();
/// This signal is emitted when the shortcut key configuration has changed
void keyBindingsChanged();
void guiLoadingFinished();
public Q_SLOTS:
/**
* Slot for opening a new document.
*
* If the current document is empty, the new document replaces it.
* If not, a new mainwindow will be opened for showing the document.
*/
void slotFileNew();
/**
* Slot for opening a saved file.
*
* If the current document is empty, the opened document replaces it.
* If not a new mainwindow will be opened for showing the opened file.
*/
void slotFileOpen(bool isImporting = false);
/**
* Slot for opening a file among the recently opened files.
*
* If the current document is empty, the opened document replaces it.
* If not a new mainwindow will be opened for showing the opened file.
*/
void slotFileOpenRecent(const QUrl &);
/**
* @brief slotPreferences open the preferences dialog
*/
void slotPreferences();
+ /**
+ * Update caption from document info - call when document info
+ * (title in the about page) changes.
+ */
+ void updateCaption();
+
+
+
/**
* Saves the current document with the current name.
*/
void slotFileSave();
// XXX: disabled
KisPrintJob* exportToPdf(QString pdfFileName = QString());
/**
* Update the option widgets to the argument ones, removing the currently set widgets.
*/
void newOptionWidgets(KoCanvasController *controller, const QList<QPointer<QWidget> > & optionWidgetList);
KisView *newView(QObject *document);
void notifyChildViewDestroyed(KisView *view);
private Q_SLOTS:
/**
* Save the list of recent files.
*/
void saveRecentFiles();
void slotLoadCompleted();
void slotLoadCanceled(const QString &);
void slotSaveCompleted();
void slotSaveCanceled(const QString &);
void forceDockTabFonts();
/**
* @internal
*/
void slotDocumentTitleModified();
/**
* Prints the actual document.
*/
void slotFilePrint();
/**
* Saves the current document with a new name.
*/
void slotFileSaveAs();
void slotFilePrintPreview();
void importAnimation();
/**
* Show a dialog with author and document information.
*/
void slotDocumentInfo();
/**
* Closes all open documents.
*/
bool slotFileCloseAll();
/**
* @brief showAboutApplication show the about box
*/
virtual void showAboutApplication();
/**
* Closes the mainwindow.
*/
void slotFileQuit();
/**
* Configure toolbars.
*/
void slotConfigureToolbars();
/**
* Post toolbar config.
* (Plug action lists back in, etc.)
*/
void slotNewToolbarConfig();
/**
* Shows or hides a toolbar
*/
void slotToolbarToggled(bool toggle);
/**
* Toggle full screen on/off.
*/
void viewFullscreen(bool fullScreen);
/**
* Toggle docker titlebars on/off.
*/
void showDockerTitleBars(bool show);
/**
* Reload file
*/
void slotReloadFile();
/**
* File --> Import
*
* This will call slotFileOpen().
*/
void slotImportFile();
/**
* File --> Export
*
* This will call slotFileSaveAs().
*/
void slotExportFile();
/**
* Hide the dockers
*/
void toggleDockersVisibility(bool visible);
/**
* Handle theme changes from theme manager
*/
void slotThemeChanged();
void undo();
void redo();
void updateWindowMenu();
void setActiveSubWindow(QWidget *window);
void configChanged();
void newWindow();
void closeCurrentWindow();
void checkSanity();
/// Quits Krita with error message from m_errorMessage.
void showErrorAndDie();
protected:
void closeEvent(QCloseEvent * e) override;
void resizeEvent(QResizeEvent * e) override;
// QWidget overrides
void dragEnterEvent(QDragEnterEvent * event) override;
void dropEvent(QDropEvent * event) override;
void dragMoveEvent(QDragMoveEvent * event) override;
void dragLeaveEvent(QDragLeaveEvent * event) override;
private:
/**
* Add a the given view to the list of views of this mainwindow.
* This is a private implementation. For public usage please use
* newView() and addViewAndNotifyLoadingCompleted().
*/
void addView(KisView *view);
public Q_SLOTS:
/// Set the active view, this will update the undo/redo actions
void setActiveView(KisView *view);
void subWindowActivated();
private:
friend class KisApplication;
/**
* Returns the dockwidget specified by the @p factory. If the dock widget doesn't exist yet it's created.
* Add a "view_palette_action_menu" action to your view menu if you want to use closable dock widgets.
* @param factory the factory used to create the dock widget if needed
* @return the dock widget specified by @p factory (may be 0)
*/
QDockWidget* createDockWidget(KoDockFactoryBase* factory);
bool openDocumentInternal(const QUrl &url, KisMainWindow::OpenFlags flags = 0);
/**
* Reloads the recent documents list.
*/
void reloadRecentFileList();
/**
* Updates the window caption based on the document info and path.
*/
void updateCaption(const QString & caption, bool mod);
void updateReloadFileAction(KisDocument *doc);
void saveWindowSettings();
QPointer<KisView>activeKisView();
void applyDefaultSettings(QPrinter &printer);
void createActions();
void applyToolBarLayout();
protected:
void moveEvent(QMoveEvent *e) override;
private Q_SLOTS:
void initializeGeometry();
void showManual();
void switchTab(int index);
private:
/**
* Struct used in the list created by createCustomDocumentWidgets()
*/
struct CustomDocumentWidgetItem {
/// Pointer to the custom document widget
QWidget *widget;
/// title used in the sidebar. If left empty it will be displayed as "Custom Document"
QString title;
/// icon used in the sidebar. If left empty it will use the unknown icon
QString icon;
};
class Private;
Private * const d;
QString m_errorMessage;
bool m_dieOnError;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KisMainWindow::OpenFlags)
#endif
diff --git a/libs/ui/KisMultiFeedRSSModel.cpp b/libs/ui/KisMultiFeedRSSModel.cpp
index 5a8c234c72..11028fdefe 100644
--- a/libs/ui/KisMultiFeedRSSModel.cpp
+++ b/libs/ui/KisMultiFeedRSSModel.cpp
@@ -1,209 +1,216 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/
#include "KisMultiFeedRSSModel.h"
#include <QTimer>
#include <QThread>
#include <QXmlStreamReader>
#include <QCoreApplication>
#include <QLocale>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <KisNetworkAccessManager.h>
QString shortenHtml(QString html)
{
html.replace(QLatin1String("<a"), QLatin1String("<i"));
html.replace(QLatin1String("</a"), QLatin1String("</i"));
uint firstParaEndXhtml = (uint) html.indexOf(QLatin1String("</p>"));
uint firstParaEndHtml = (uint) html.indexOf(QLatin1String("<p>"), html.indexOf(QLatin1String("<p>"))+1);
uint firstParaEndBr = (uint) html.indexOf(QLatin1String("<br"));
uint firstParaEnd = qMin(firstParaEndXhtml, firstParaEndHtml);
firstParaEnd = qMin(firstParaEnd, firstParaEndBr);
return html.left(firstParaEnd);
}
class RssReader {
public:
RssItem parseItem() {
RssItem item;
item.source = requestUrl;
item.blogIcon = blogIcon;
item.blogName = blogName;
while (!streamReader.atEnd()) {
switch (streamReader.readNext()) {
case QXmlStreamReader::StartElement:
if (streamReader.name() == QLatin1String("title"))
item.title = streamReader.readElementText();
else if (streamReader.name() == QLatin1String("link"))
item.link = streamReader.readElementText();
else if (streamReader.name() == QLatin1String("pubDate")) {
QString dateStr = streamReader.readElementText();
// fixme: honor time zone!
dateStr = dateStr.left(dateStr.indexOf('+')-1);
item.pubDate = QLocale(QLocale::English).toDateTime(dateStr, "ddd, dd MMM yyyy HH:mm:ss");
}
else if (streamReader.name() == QLatin1String("description"))
item.description = streamReader.readElementText(); //shortenHtml(streamReader.readElementText());
break;
case QXmlStreamReader::EndElement:
if (streamReader.name() == QLatin1String("item"))
return item;
break;
default:
break;
}
}
return RssItem();
}
RssItemList parse(QNetworkReply *reply) {
QUrl source = reply->request().url();
requestUrl = source.toString();
streamReader.setDevice(reply);
RssItemList list;
while (!streamReader.atEnd()) {
switch (streamReader.readNext()) {
case QXmlStreamReader::StartElement:
if (streamReader.name() == QLatin1String("item"))
list.append(parseItem());
else if (streamReader.name() == QLatin1String("title"))
blogName = streamReader.readElementText();
else if (streamReader.name() == QLatin1String("link")) {
if (!streamReader.namespaceUri().isEmpty())
break;
QString favIconString(streamReader.readElementText());
QUrl favIconUrl(favIconString);
favIconUrl.setPath(QLatin1String("favicon.ico"));
blogIcon = favIconUrl.toString();
blogIcon = QString(); // XXX: fix the favicon on krita.org!
}
break;
default:
break;
}
}
return list;
}
private:
QXmlStreamReader streamReader;
QString requestUrl;
QString blogIcon;
QString blogName;
};
MultiFeedRssModel::MultiFeedRssModel(QObject *parent) :
QAbstractListModel(parent),
m_networkAccessManager(new KisNetworkAccessManager),
m_articleCount(0)
{
connect(m_networkAccessManager, SIGNAL(finished(QNetworkReply*)),
SLOT(appendFeedData(QNetworkReply*)), Qt::QueuedConnection);
+}
+
+
+
+MultiFeedRssModel::~MultiFeedRssModel()
+{
+}
+
+QHash<int, QByteArray> MultiFeedRssModel::roleNames() const
+{
QHash<int, QByteArray> roleNames;
roleNames[TitleRole] = "title";
roleNames[DescriptionRole] = "description";
roleNames[PubDateRole] = "pubDate";
roleNames[LinkRole] = "link";
roleNames[BlogNameRole] = "blogName";
roleNames[BlogIconRole] = "blogIcon";
- setRoleNames(roleNames);
-}
-
-MultiFeedRssModel::~MultiFeedRssModel()
-{
+ return roleNames;
}
void MultiFeedRssModel::addFeed(const QString& feed)
{
const QUrl feedUrl(feed);
QMetaObject::invokeMethod(m_networkAccessManager, "getUrl",
Qt::QueuedConnection, Q_ARG(QUrl, feedUrl));
}
bool sortForPubDate(const RssItem& item1, const RssItem& item2)
{
return item1.pubDate > item2.pubDate;
}
void MultiFeedRssModel::appendFeedData(QNetworkReply *reply)
{
RssReader reader;
m_aggregatedFeed.append(reader.parse(reply));
std::sort(m_aggregatedFeed.begin(), m_aggregatedFeed.end(), sortForPubDate);
setArticleCount(m_aggregatedFeed.size());
- reset();
+ beginResetModel();
+ endResetModel();
}
void MultiFeedRssModel::removeFeed(const QString &feed)
{
QMutableListIterator<RssItem> it(m_aggregatedFeed);
while (it.hasNext()) {
RssItem item = it.next();
if (item.source == feed)
it.remove();
}
setArticleCount(m_aggregatedFeed.size());
}
int MultiFeedRssModel::rowCount(const QModelIndex &) const
{
return m_aggregatedFeed.size();
}
QVariant MultiFeedRssModel::data(const QModelIndex &index, int role) const
{
RssItem item = m_aggregatedFeed.at(index.row());
switch (role) {
case Qt::DisplayRole: // fall through
case TitleRole:
return item.title;
case DescriptionRole:
return item.description;
case PubDateRole:
return item.pubDate.toString("dd-MM-yyyy hh:mm");
case LinkRole:
return item.link;
case BlogNameRole:
return item.blogName;
case BlogIconRole:
return item.blogIcon;
}
return QVariant();
}
diff --git a/libs/ui/KisMultiFeedRSSModel.h b/libs/ui/KisMultiFeedRSSModel.h
index d85db72e1b..c87773e46a 100644
--- a/libs/ui/KisMultiFeedRSSModel.h
+++ b/libs/ui/KisMultiFeedRSSModel.h
@@ -1,105 +1,106 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**************************************************************************/
#ifndef MULTIFEEDRSSMODEL_H
#define MULTIFEEDRSSMODEL_H
#include <QAbstractListModel>
#include <QStringList>
#include <QDateTime>
#include <kritaui_export.h>
class QThread;
class QNetworkReply;
class QNetworkAccessManager;
struct RssItem {
QString source;
QString title;
QString link;
QString description;
QString blogName;
QString blogIcon;
QDateTime pubDate;
};
typedef QList<RssItem> RssItemList;
class KisNetworkAccessManager;
enum RssRoles { TitleRole = Qt::UserRole + 1, DescriptionRole, LinkRole,
PubDateRole, BlogNameRole, BlogIconRole
};
class KRITAUI_EXPORT MultiFeedRssModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int articleCount READ articleCount WRITE setArticleCount NOTIFY articleCountChanged)
public:
explicit MultiFeedRssModel(QObject *parent = 0);
~MultiFeedRssModel() override;
+ QHash<int, QByteArray> roleNames() const;
void addFeed(const QString& feed);
void removeFeed(const QString& feed);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int articleCount() const {
return m_articleCount;
}
public Q_SLOTS:
void setArticleCount(int arg) {
if (m_articleCount != arg) {
m_articleCount = arg;
emit articleCountChanged(arg);
}
}
Q_SIGNALS:
void articleCountChanged(int arg);
private Q_SLOTS:
void appendFeedData(QNetworkReply *reply);
private:
QStringList m_sites;
RssItemList m_aggregatedFeed;
QNetworkAccessManager *m_networkAccessManager;
QThread *m_namThread;
int m_articleCount;
};
#endif // MULTIFEEDRSSMODEL_H
diff --git a/libs/ui/KisNodeView.cpp b/libs/ui/KisNodeView.cpp
index b15fca7e12..63c1536c48 100644
--- a/libs/ui/KisNodeView.cpp
+++ b/libs/ui/KisNodeView.cpp
@@ -1,564 +1,569 @@
/*
Copyright (c) 2006 Gábor Lehel <illissius@gmail.com>
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 "KisNodeView.h"
#include "KisNodePropertyAction_p.h"
#include "KisNodeDelegate.h"
#include "kis_node_view_visibility_delegate.h"
#include "kis_node_model.h"
#include "kis_signals_blocker.h"
#include <kconfig.h>
#include <kconfiggroup.h>
#include <kis_icon.h>
#include <ksharedconfig.h>
#include <QtDebug>
#include <QContextMenuEvent>
#include <QHeaderView>
#include <QHelpEvent>
#include <QMenu>
#include <QDrag>
#include <QMouseEvent>
#include <QPersistentModelIndex>
#include <QApplication>
#include <QPainter>
#include <QScrollBar>
#include "kis_node_view_color_scheme.h"
#ifdef HAVE_X11
#define DRAG_WHILE_DRAG_WORKAROUND
#endif
#ifdef DRAG_WHILE_DRAG_WORKAROUND
#define DRAG_WHILE_DRAG_WORKAROUND_START() d->isDragging = true
#define DRAG_WHILE_DRAG_WORKAROUND_STOP() d->isDragging = false
#else
#define DRAG_WHILE_DRAG_WORKAROUND_START()
#define DRAG_WHILE_DRAG_WORKAROUND_STOP()
#endif
class Q_DECL_HIDDEN KisNodeView::Private
{
public:
Private(KisNodeView* _q)
: delegate(_q, _q)
, mode(DetailedMode)
#ifdef DRAG_WHILE_DRAG_WORKAROUND
, isDragging(false)
#endif
{
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup group = config->group("NodeView");
mode = (DisplayMode) group.readEntry("NodeViewMode", (int)MinimalMode);
}
KisNodeDelegate delegate;
DisplayMode mode;
QPersistentModelIndex hovered;
QPoint lastPos;
#ifdef DRAG_WHILE_DRAG_WORKAROUND
bool isDragging;
#endif
};
KisNodeView::KisNodeView(QWidget *parent)
: QTreeView(parent)
, m_draggingFlag(false)
, d(new Private(this))
{
setItemDelegateForColumn(0, &d->delegate);
setMouseTracking(true);
setSelectionBehavior(SelectRows);
setDefaultDropAction(Qt::MoveAction);
setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
setSelectionMode(QAbstractItemView::ExtendedSelection);
header()->hide();
setDragEnabled(true);
setDragDropMode(QAbstractItemView::DragDrop);
setAcceptDrops(true);
setDropIndicatorShown(true);
}
KisNodeView::~KisNodeView()
{
delete d;
}
void KisNodeView::setDisplayMode(DisplayMode mode)
{
if (d->mode != mode) {
d->mode = mode;
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup group = config->group("NodeView");
group.writeEntry("NodeViewMode", (int)mode);
scheduleDelayedItemsLayout();
}
}
KisNodeView::DisplayMode KisNodeView::displayMode() const
{
return d->mode;
}
void KisNodeView::addPropertyActions(QMenu *menu, const QModelIndex &index)
{
KisBaseNode::PropertyList list = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
for (int i = 0, n = list.count(); i < n; ++i) {
if (list.at(i).isMutable) {
PropertyAction *a = new PropertyAction(i, list.at(i), index, menu);
connect(a, SIGNAL(toggled(bool, const QPersistentModelIndex&, int)),
this, SLOT(slotActionToggled(bool, const QPersistentModelIndex&, int)));
menu->addAction(a);
}
}
}
void KisNodeView::updateNode(const QModelIndex &index)
{
dataChanged(index, index);
}
QItemSelectionModel::SelectionFlags KisNodeView::selectionCommand(const QModelIndex &index,
const QEvent *event) const
{
/**
* 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<const QMouseEvent*>(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;
}
}
/**
* Qt 5.6 has a bug: it reads global modifiers, not the ones
* passed from event. So if you paste an item using Ctrl+V it'll
* select multiple layers for you
*/
Qt::KeyboardModifiers globalModifiers = QApplication::keyboardModifiers();
if (!event && globalModifiers != Qt::NoModifier) {
return QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows;
}
return QAbstractItemView::selectionCommand(index, event);
}
QRect KisNodeView::visualRect(const QModelIndex &index) const
{
QRect rc = QTreeView::visualRect(index);
rc.setLeft(0);
return rc;
}
QRect KisNodeView::originalVisualRect(const QModelIndex &index) const
{
return QTreeView::visualRect(index);
}
QModelIndex KisNodeView::indexAt(const QPoint &point) const
{
KisNodeViewColorScheme scm;
QModelIndex index = QTreeView::indexAt(point);
if (!index.isValid() && point.x() < scm.visibilityColumnWidth()) {
index = QTreeView::indexAt(point + QPoint(scm.visibilityColumnWidth(), 0));
}
return index;
}
bool KisNodeView::viewportEvent(QEvent *e)
{
if (model()) {
switch(e->type()) {
case QEvent::MouseButtonPress: {
DRAG_WHILE_DRAG_WORKAROUND_STOP();
const QPoint pos = static_cast<QMouseEvent*>(e)->pos();
d->lastPos = pos;
if (!indexAt(pos).isValid()) {
return QTreeView::viewportEvent(e);
}
QModelIndex index = model()->buddy(indexAt(pos));
if (d->delegate.editorEvent(e, model(), optionForIndex(index), index)) {
return true;
}
} break;
case QEvent::Leave: {
QEvent e(QEvent::Leave);
d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered);
d->hovered = QModelIndex();
} break;
case QEvent::MouseMove: {
#ifdef DRAG_WHILE_DRAG_WORKAROUND
if (d->isDragging) {
return false;
}
#endif
const QPoint pos = static_cast<QMouseEvent*>(e)->pos();
QModelIndex hovered = indexAt(pos);
if (hovered != d->hovered) {
if (d->hovered.isValid()) {
QEvent e(QEvent::Leave);
d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered);
}
if (hovered.isValid()) {
QEvent e(QEvent::Enter);
d->delegate.editorEvent(&e, model(), optionForIndex(hovered), hovered);
}
d->hovered = hovered;
}
/* This is a workaround for a bug in QTreeView that immediately begins a dragging action
when the mouse lands on the decoration/icon of a different index and moves 1 pixel or more */
Qt::MouseButtons buttons = static_cast<QMouseEvent*>(e)->buttons();
if ((Qt::LeftButton | Qt::MidButton) & buttons) {
if ((pos - d->lastPos).manhattanLength() > qApp->startDragDistance()) {
return QTreeView::viewportEvent(e);
}
return true;
}
} break;
case QEvent::ToolTip: {
const QPoint pos = static_cast<QHelpEvent*>(e)->pos();
if (!indexAt(pos).isValid()) {
return QTreeView::viewportEvent(e);
}
QModelIndex index = model()->buddy(indexAt(pos));
return d->delegate.editorEvent(e, model(), optionForIndex(index), index);
} break;
case QEvent::Resize: {
scheduleDelayedItemsLayout();
break;
}
default: break;
}
}
return QTreeView::viewportEvent(e);
}
void KisNodeView::contextMenuEvent(QContextMenuEvent *e)
{
QTreeView::contextMenuEvent(e);
QModelIndex i = indexAt(e->pos());
if (model())
i = model()->buddy(i);
showContextMenu(e->globalPos(), i);
}
void KisNodeView::showContextMenu(const QPoint &globalPos, const QModelIndex &index)
{
emit contextMenuRequested(globalPos, index);
}
void KisNodeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
QTreeView::currentChanged(current, previous);
if (current != previous) {
Q_ASSERT(!current.isValid() || current.model() == model());
model()->setData(current, true, KisNodeModel::ActiveRole);
}
}
void KisNodeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &/*roles*/)
{
QTreeView::dataChanged(topLeft, bottomRight);
for (int x = topLeft.row(); x <= bottomRight.row(); ++x) {
for (int y = topLeft.column(); y <= bottomRight.column(); ++y) {
QModelIndex index = topLeft.sibling(x, y);
if (index.data(KisNodeModel::ActiveRole).toBool()) {
if (currentIndex() != index) {
setCurrentIndex(index);
}
return;
}
}
}
}
void KisNodeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
QTreeView::selectionChanged(selected, deselected);
emit selectionChanged(selectedIndexes());
}
void KisNodeView::slotActionToggled(bool on, const QPersistentModelIndex &index, int num)
{
KisBaseNode::PropertyList list = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
list[num].state = on;
const_cast<QAbstractItemModel*>(index.model())->setData(index, QVariant::fromValue(list), KisNodeModel::PropertiesRole);
}
QStyleOptionViewItem KisNodeView::optionForIndex(const QModelIndex &index) const
{
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(index);
if (index == currentIndex())
option.state |= QStyle::State_HasFocus;
return option;
}
void KisNodeView::startDrag(Qt::DropActions supportedActions)
{
DRAG_WHILE_DRAG_WORKAROUND_START();
if (displayMode() == KisNodeView::ThumbnailMode) {
const QModelIndexList indexes = selectionModel()->selectedIndexes();
if (!indexes.isEmpty()) {
QMimeData *data = model()->mimeData(indexes);
if (!data) {
return;
}
QDrag *drag = new QDrag(this);
drag->setPixmap(createDragPixmap());
drag->setMimeData(data);
//m_dragSource = this;
drag->exec(supportedActions);
}
}
else {
QTreeView::startDrag(supportedActions);
}
}
QPixmap KisNodeView::createDragPixmap() const
{
const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes();
Q_ASSERT(!selectedIndexes.isEmpty());
const int itemCount = selectedIndexes.count();
// If more than one item is dragged, align the items inside a
// rectangular grid. The maximum grid size is limited to 4 x 4 items.
int xCount = 2;
int size = 96;
if (itemCount > 9) {
xCount = 4;
size = KisIconUtils::SizeLarge;
}
else if (itemCount > 4) {
xCount = 3;
size = KisIconUtils::SizeHuge;
}
else if (itemCount < xCount) {
xCount = itemCount;
}
int yCount = itemCount / xCount;
if (itemCount % xCount != 0) {
++yCount;
}
if (yCount > xCount) {
yCount = xCount;
}
// Draw the selected items into the grid cells
QPixmap dragPixmap(xCount * size + xCount - 1, yCount * size + yCount - 1);
dragPixmap.fill(Qt::transparent);
QPainter painter(&dragPixmap);
int x = 0;
int y = 0;
Q_FOREACH (const QModelIndex &selectedIndex, selectedIndexes) {
const QImage img = selectedIndex.data(int(KisNodeModel::BeginThumbnailRole) + size).value<QImage>();
painter.drawPixmap(x, y, QPixmap().fromImage(img.scaled(QSize(size, size), Qt::KeepAspectRatio, Qt::SmoothTransformation)));
x += size + 1;
if (x >= dragPixmap.width()) {
x = 0;
y += size + 1;
}
if (y >= dragPixmap.height()) {
break;
}
}
return dragPixmap;
}
void KisNodeView::resizeEvent(QResizeEvent * event)
{
KisNodeViewColorScheme scm;
header()->setStretchLastSection(false);
header()->setOffset(-scm.visibilityColumnWidth());
header()->resizeSection(0, event->size().width() - scm.visibilityColumnWidth());
setIndentation(scm.indentation());
QTreeView::resizeEvent(event);
}
void KisNodeView::paintEvent(QPaintEvent *event)
{
event->accept();
QTreeView::paintEvent(event);
// Paint the line where the slide should go
if (isDragging() && (displayMode() == KisNodeView::ThumbnailMode)) {
QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height());
int numberRow = cursorPageIndex();
int scrollBarValue = verticalScrollBar()->value();
QPoint point1(0, numberRow * size.height() - scrollBarValue);
QPoint point2(size.width(), numberRow * size.height() - scrollBarValue);
QLineF line(point1, point2);
QPainter painter(this->viewport());
QPen pen = QPen(palette().brush(QPalette::Highlight), 8);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
painter.setOpacity(0.8);
painter.drawLine(line);
}
}
void KisNodeView::drawBranches(QPainter *painter, const QRect &rect,
const QModelIndex &index) const
{
Q_UNUSED(painter);
Q_UNUSED(rect);
Q_UNUSED(index);
/**
* Noop... Everything is going to be painted by KisNodeDelegate.
* So this override basically disables painting of Qt's branch-lines.
*/
}
void KisNodeView::dropEvent(QDropEvent *ev)
{
if (displayMode() == KisNodeView::ThumbnailMode) {
setDraggingFlag(false);
ev->accept();
clearSelection();
if (!model()) {
return;
}
int newIndex = cursorPageIndex();
model()->dropMimeData(ev->mimeData(), ev->dropAction(), newIndex, -1, QModelIndex());
return;
}
QTreeView::dropEvent(ev);
DRAG_WHILE_DRAG_WORKAROUND_STOP();
}
int KisNodeView::cursorPageIndex() const
{
QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height());
int scrollBarValue = verticalScrollBar()->value();
QPoint cursorPosition = QWidget::mapFromGlobal(QCursor::pos());
int numberRow = (cursorPosition.y() + scrollBarValue) / size.height();
//If cursor is at the half button of the page then the move action is performed after the slide, otherwise it is
//performed before the page
if (abs((cursorPosition.y() + scrollBarValue) - size.height()*numberRow) > (size.height()/2)) {
numberRow++;
}
if (numberRow > model()->rowCount(QModelIndex())) {
numberRow = model()->rowCount(QModelIndex());
}
return numberRow;
}
void KisNodeView::dragEnterEvent(QDragEnterEvent *ev)
{
DRAG_WHILE_DRAG_WORKAROUND_START();
+
+ QVariant data = qVariantFromValue(
+ static_cast<void*>(const_cast<QMimeData*>(ev->mimeData())));
+ model()->setData(QModelIndex(), data, KisNodeModel::DropEnabled);
+
QTreeView::dragEnterEvent(ev);
}
void KisNodeView::dragMoveEvent(QDragMoveEvent *ev)
{
DRAG_WHILE_DRAG_WORKAROUND_START();
if (displayMode() == KisNodeView::ThumbnailMode) {
ev->accept();
if (!model()) {
return;
}
QTreeView::dragMoveEvent(ev);
setDraggingFlag();
viewport()->update();
return;
}
QTreeView::dragMoveEvent(ev);
}
void KisNodeView::dragLeaveEvent(QDragLeaveEvent *e)
{
if (displayMode() == KisNodeView::ThumbnailMode) {
setDraggingFlag(false);
} else {
QTreeView::dragLeaveEvent(e);
}
DRAG_WHILE_DRAG_WORKAROUND_STOP();
}
bool KisNodeView::isDragging() const
{
return m_draggingFlag;
}
void KisNodeView::setDraggingFlag(bool flag)
{
m_draggingFlag = flag;
}
diff --git a/libs/ui/KisOpenPane.cpp b/libs/ui/KisOpenPane.cpp
index 0872b35fbb..1bafb75ba2 100644
--- a/libs/ui/KisOpenPane.cpp
+++ b/libs/ui/KisOpenPane.cpp
@@ -1,384 +1,382 @@
/* This file is part of the KDE project
Copyright (C) 2005 Peter Simonsson <psn@linux.se>
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 "KisOpenPane.h"
#include <QLayout>
#include <QLabel>
#include <QImage>
#include <QPainter>
#include <QPen>
#include <QPixmap>
#include <QSize>
#include <QString>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QStyledItemDelegate>
#include <QLinearGradient>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <kis_debug.h>
#include <QUrl>
#include <KoFileDialog.h>
#include <KoIcon.h>
#include "KisTemplateTree.h"
#include "KisTemplateGroup.h"
#include "KisTemplate.h"
#include "KisDetailsPane.h"
#include "KisTemplatesPane.h"
#include "ui_KisOpenPaneBase.h"
#include <limits.h>
#include <kconfiggroup.h>
#include <kis_icon.h>
class KoSectionListItem : public QTreeWidgetItem
{
public:
KoSectionListItem(QTreeWidget* treeWidget, const QString& name, int sortWeight, int widgetIndex = -1)
: QTreeWidgetItem(treeWidget, QStringList() << name), m_sortWeight(sortWeight), m_widgetIndex(widgetIndex) {
Qt::ItemFlags newFlags = Qt::NoItemFlags;
if(m_widgetIndex >= 0)
newFlags |= Qt::ItemIsEnabled | Qt::ItemIsSelectable;
setFlags(newFlags);
}
bool operator<(const QTreeWidgetItem & other) const override {
const KoSectionListItem* item = dynamic_cast<const KoSectionListItem*>(&other);
if (!item)
return 0;
return ((item->sortWeight() - sortWeight()) < 0);
}
int sortWeight() const {
return m_sortWeight;
}
int widgetIndex() const {
return m_widgetIndex;
}
private:
int m_sortWeight;
int m_widgetIndex;
};
class KoSectionListDelegate : public QStyledItemDelegate
{
public:
KoSectionListDelegate(QObject* parent = 0) : QStyledItemDelegate(parent) { }
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
{
QStyledItemDelegate::paint(painter, option, index);
if(!(option.state & (int)(QStyle::State_Active && QStyle::State_Enabled)))
{
int ypos = option.rect.y() + ((option.rect.height() - 2) / 2);
QRect lineRect(option.rect.left(), ypos, option.rect.width(), 2);
QLinearGradient gradient(option.rect.topLeft(), option.rect.bottomRight());
gradient.setColorAt(option.direction == Qt::LeftToRight ? 0 : 1, option.palette.color(QPalette::Text));
gradient.setColorAt(option.direction == Qt::LeftToRight ? 1 : 0, Qt::transparent);
painter->fillRect(lineRect, gradient);
}
}
};
class KisOpenPanePrivate : public Ui_KisOpenPaneBase
{
public:
KisOpenPanePrivate() :
Ui_KisOpenPaneBase() {
m_templatesSeparator = 0;
}
int m_freeCustomWidgetIndex;
KoSectionListItem* m_templatesSeparator;
-
+
};
KisOpenPane::KisOpenPane(QWidget *parent, const QStringList& mimeFilter, const QString& templatesResourcePath)
: QDialog(parent)
, d(new KisOpenPanePrivate)
{
d->setupUi(this);
m_mimeFilter = mimeFilter;
KoSectionListDelegate* delegate = new KoSectionListDelegate(d->m_sectionList);
d->m_sectionList->setItemDelegate(delegate);
connect(d->m_sectionList, SIGNAL(itemSelectionChanged()),
this, SLOT(updateSelectedWidget()));
connect(d->m_sectionList, SIGNAL(itemClicked(QTreeWidgetItem*, int)),
this, SLOT(itemClicked(QTreeWidgetItem*)));
connect(d->m_sectionList, SIGNAL(itemActivated(QTreeWidgetItem*, int)),
this, SLOT(itemClicked(QTreeWidgetItem*)));
-
+
connect(d->cancelButton, SIGNAL(clicked()), this, SLOT(close()));
connect(d->cancelButton, SIGNAL(clicked()), this, SLOT(deleteLater()));
initTemplates(templatesResourcePath);
d->m_freeCustomWidgetIndex = 4;
if (!d->m_sectionList->selectedItems().isEmpty())
{
KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(d->m_sectionList->selectedItems().first());
if (selectedItem) {
d->m_widgetStack->widget(selectedItem->widgetIndex())->setFocus();
}
}
QList<int> sizes;
// Set the sizes of the details pane splitters
KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog"); sizes = cfgGrp.readEntry("DetailsPaneSplitterSizes", sizes);
if (!sizes.isEmpty())
emit splitterResized(0, sizes);
connect(this, SIGNAL(splitterResized(KisDetailsPane*, const QList<int>&)),
this, SLOT(saveSplitterSizes(KisDetailsPane*, const QList<int>&)));
setAcceptDrops(true);
}
KisOpenPane::~KisOpenPane()
{
if (!d->m_sectionList->selectedItems().isEmpty()) {
KoSectionListItem* item = dynamic_cast<KoSectionListItem*>(d->m_sectionList->selectedItems().first());
if (item) {
if (!qobject_cast<KisDetailsPane*>(d->m_widgetStack->widget(item->widgetIndex()))) {
KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
cfgGrp.writeEntry("LastReturnType", item->text(0));
}
}
}
delete d;
}
void KisOpenPane::openFileDialog()
{
KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocument");
dialog.setCaption(i18n("Open Existing Document"));
- dialog.setDefaultDir(qApp->applicationName().contains("krita") || qApp->applicationName().contains("karbon")
- ? QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)
- : QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(m_mimeFilter);
Q_FOREACH (const QString &filename, dialog.filenames()) {
emit openExistingFile(QUrl::fromUserInput(filename));
}
}
void KisOpenPane::initTemplates(const QString& templatesResourcePath)
{
QTreeWidgetItem* selectItem = 0;
QTreeWidgetItem* firstItem = 0;
const int templateOffset = 1000;
if (!templatesResourcePath.isEmpty()) {
KisTemplateTree templateTree(templatesResourcePath, true);
Q_FOREACH (KisTemplateGroup *group, templateTree.groups()) {
if (group->isHidden()) {
continue;
}
if (!d->m_templatesSeparator) {
d->m_templatesSeparator = new KoSectionListItem(d->m_sectionList, "", 999);
}
KisTemplatesPane* pane = new KisTemplatesPane(this, group->name(),
group, templateTree.defaultTemplate());
connect(pane, SIGNAL(openUrl(const QUrl&)), this, SIGNAL(openTemplate(const QUrl&)));
connect(pane, SIGNAL(alwaysUseChanged(KisTemplatesPane*, const QString&)),
this, SIGNAL(alwaysUseChanged(KisTemplatesPane*, const QString&)));
connect(this, SIGNAL(alwaysUseChanged(KisTemplatesPane*, const QString&)),
pane, SLOT(changeAlwaysUseTemplate(KisTemplatesPane*, const QString&)));
connect(pane, SIGNAL(splitterResized(KisDetailsPane*, const QList<int>&)),
this, SIGNAL(splitterResized(KisDetailsPane*, const QList<int>&)));
connect(this, SIGNAL(splitterResized(KisDetailsPane*, const QList<int>&)),
pane, SLOT(resizeSplitter(KisDetailsPane*, const QList<int>&)));
QTreeWidgetItem* item = addPane(group->name(), group->templates().first()->loadPicture(),
pane, group->sortingWeight() + templateOffset);
-
+
if (!firstItem) {
firstItem = item;
}
if (group == templateTree.defaultGroup()) {
firstItem = item;
}
if (pane->isSelected()) {
selectItem = item;
}
}
} else {
firstItem = d->m_sectionList->topLevelItem(0);
}
KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
if (selectItem && (cfgGrp.readEntry("LastReturnType") == "Template")) {
d->m_sectionList->setCurrentItem(selectItem, 0, QItemSelectionModel::ClearAndSelect);
} else if (d->m_sectionList->selectedItems().isEmpty() && firstItem) {
d->m_sectionList->setCurrentItem(firstItem, 0, QItemSelectionModel::ClearAndSelect);
}
}
void KisOpenPane::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls()) {
event->accept();
}
}
void KisOpenPane::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) {
// XXX: when the MVC refactoring is done, this can open a bunch of
// urls, but since the part/document combination is still 1:1
// that won't work for now.
emit openExistingFile(event->mimeData()->urls().first());
}
}
void KisOpenPane::addCustomDocumentWidget(QWidget *widget, const QString& title, const QString& icon)
{
Q_ASSERT(widget);
QString realtitle = title;
if (realtitle.isEmpty())
realtitle = i18n("Custom Document");
QTreeWidgetItem* item = addPane(realtitle, icon, widget, d->m_freeCustomWidgetIndex);
++d->m_freeCustomWidgetIndex;
KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
QString lastActiveItem = cfgGrp.readEntry("LastReturnType");
bool showCustomItemByDefault = cfgGrp.readEntry("ShowCustomDocumentWidgetByDefault", false);
if (lastActiveItem == realtitle || (lastActiveItem.isEmpty() && showCustomItemByDefault)) {
d->m_sectionList->setCurrentItem(item, 0, QItemSelectionModel::ClearAndSelect);
KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(item);
d->m_widgetStack->widget(selectedItem->widgetIndex())->setFocus();
}
}
QTreeWidgetItem* KisOpenPane::addPane(const QString &title, const QString &iconName, QWidget *widget, int sortWeight)
{
if (!widget) {
return 0;
}
int id = d->m_widgetStack->addWidget(widget);
KoSectionListItem* listItem = new KoSectionListItem(d->m_sectionList, title, sortWeight, id);
// resizes icons so they are a bit smaller
QIcon icon = KisIconUtils::loadIcon(iconName);
QPixmap iconPixmap = icon.pixmap(32, 32);
QIcon finalIcon(iconPixmap);
listItem->setIcon(0, finalIcon);
return listItem;
}
QTreeWidgetItem* KisOpenPane::addPane(const QString& title, const QPixmap& icon, QWidget* widget, int sortWeight)
{
if (!widget) {
return 0;
}
int id = d->m_widgetStack->addWidget(widget);
int iconSize = 32;
KoSectionListItem* listItem = new KoSectionListItem(d->m_sectionList, title, sortWeight, id);
if (!icon.isNull()) {
QImage image = icon.toImage();
if ((image.width() > iconSize) || (image.height() > iconSize)) {
image = image.scaled(iconSize, iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
image = image.convertToFormat(QImage::Format_ARGB32);
image = image.copy((image.width() - iconSize) / 2, (image.height() - iconSize) / 2, iconSize, iconSize);
listItem->setIcon(0, QIcon(QPixmap::fromImage(image)));
}
return listItem;
}
void KisOpenPane::updateSelectedWidget()
{
if(!d->m_sectionList->selectedItems().isEmpty())
{
KoSectionListItem* section = dynamic_cast<KoSectionListItem*>(d->m_sectionList->selectedItems().first());
if (!section)
return;
d->m_widgetStack->setCurrentIndex(section->widgetIndex());
}
}
void KisOpenPane::saveSplitterSizes(KisDetailsPane* sender, const QList<int>& sizes)
{
Q_UNUSED(sender);
KConfigGroup cfgGrp( KSharedConfig::openConfig(), "TemplateChooserDialog");
cfgGrp.writeEntry("DetailsPaneSplitterSizes", sizes);
}
void KisOpenPane::itemClicked(QTreeWidgetItem* item)
{
KoSectionListItem* selectedItem = static_cast<KoSectionListItem*>(item);
if (selectedItem && selectedItem->widgetIndex() >= 0) {
d->m_widgetStack->widget(selectedItem->widgetIndex())->setFocus();
- }
+ }
}
diff --git a/libs/ui/KisPaletteModel.cpp b/libs/ui/KisPaletteModel.cpp
index 35d0e30b62..78d9aaab74 100644
--- a/libs/ui/KisPaletteModel.cpp
+++ b/libs/ui/KisPaletteModel.cpp
@@ -1,632 +1,634 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KisPaletteModel.h"
#include <QBrush>
#include <QDomDocument>
#include <QDomElement>
#include <QMimeData>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <resources/KoColorSet.h>
#include <KoColorDisplayRendererInterface.h>
#include <kis_layer.h>
#include <kis_paint_layer.h>
KisPaletteModel::KisPaletteModel(QObject* parent)
: QAbstractTableModel(parent),
m_colorSet(0),
m_displayRenderer(KoDumbColorDisplayRenderer::instance())
{
}
KisPaletteModel::~KisPaletteModel()
{
}
void KisPaletteModel::setDisplayRenderer(KoColorDisplayRendererInterface *displayRenderer)
{
if (displayRenderer) {
if (m_displayRenderer) {
disconnect(m_displayRenderer, 0, this, 0);
}
m_displayRenderer = displayRenderer;
connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()),
SLOT(slotDisplayConfigurationChanged()));
} else {
m_displayRenderer = KoDumbColorDisplayRenderer::instance();
}
}
void KisPaletteModel::slotDisplayConfigurationChanged()
{
- reset();
+ beginResetModel();
+ endResetModel();
}
QModelIndex KisPaletteModel::getLastEntryIndex()
{
int endRow = rowCount();
int endColumn = columnCount();
if (m_colorSet->nColors()>0) {
QModelIndex i = this->index(endRow, endColumn, QModelIndex());
- while (qVariantValue<QStringList>(i.data(RetrieveEntryRole)).isEmpty()) {
+ while (qvariant_cast<QStringList>(i.data(RetrieveEntryRole)).isEmpty()) {
i = this->index(endRow, endColumn);
endColumn -=1;
if (endColumn<0) {
endColumn = columnCount();
endRow-=1;
}
}
return i;
}
return QModelIndex();
}
QVariant KisPaletteModel::data(const QModelIndex& index, int role) const
{
KoColorSetEntry entry;
if (m_colorSet && m_displayRenderer) {
//now to figure out whether we have a groupname row or not.
bool groupNameRow = false;
quint32 indexInGroup = 0;
QString indexGroupName = QString();
int rowstotal = m_colorSet->nColorsGroup()/columnCount();
if (index.row()<=rowstotal && (quint32)(index.row()*columnCount()+index.column())<m_colorSet->nColorsGroup()) {
indexInGroup = (quint32)(index.row()*columnCount()+index.column());
}
if (m_colorSet->nColorsGroup()==0) {
rowstotal+=1; //always add one for the default group when considering groups.
}
Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){
//we make an int for the rows added by the current group.
int newrows = 1+m_colorSet->nColorsGroup(groupName)/columnCount();
if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) {
newrows+=1;
}
if (newrows==0) {
newrows+=1; //always add one for the group when considering groups.
}
quint32 tempIndex = (quint32)((index.row()-(rowstotal+2))*columnCount()+index.column());
if (index.row() == rowstotal+1) {
//rowstotal+1 is taken up by the groupname.
indexGroupName = groupName;
groupNameRow = true;
} else if (index.row() > (rowstotal+1) && index.row() <= rowstotal+newrows &&
tempIndex<m_colorSet->nColorsGroup(groupName)){
//otherwise it's an index to the colors in the group.
indexGroupName = groupName;
indexInGroup = tempIndex;
}
//add the new rows to the totalrows we've looked at.
rowstotal += newrows;
}
if (groupNameRow) {
switch (role) {
case Qt::ToolTipRole:
case Qt::DisplayRole: {
return indexGroupName;
}
case IsHeaderRole: {
return true;
}
case RetrieveEntryRole: {
QStringList entryList;
entryList.append(indexGroupName);
entryList.append(QString::number(0));
return entryList;
}
}
} else {
if (indexInGroup < m_colorSet->nColorsGroup(indexGroupName)) {
entry = m_colorSet->getColorGroup(indexInGroup, indexGroupName);
switch (role) {
case Qt::ToolTipRole:
case Qt::DisplayRole: {
return entry.name;
}
case Qt::BackgroundRole: {
QColor color = m_displayRenderer->toQColor(entry.color);
return QBrush(color);
}
case IsHeaderRole: {
return false;
}
case RetrieveEntryRole: {
QStringList entryList;
entryList.append(indexGroupName);
entryList.append(QString::number(indexInGroup));
return entryList;
}
}
}
}
}
return QVariant();
}
int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const
{
if (!m_colorSet) {
return 0;
}
if (columnCount() > 0) {
int countedrows = m_colorSet->nColorsGroup("")/columnCount();
if (m_colorSet->nColorsGroup()%columnCount() > 0) {
countedrows+=1;
}
if (m_colorSet->nColorsGroup()==0) {
countedrows+=1;
}
Q_FOREACH (QString groupName, m_colorSet->getGroupNames()) {
countedrows += 1; //add one for the name;
countedrows += 1+(m_colorSet->nColorsGroup(groupName)/ columnCount());
if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) {
countedrows+=1;
}
if (m_colorSet->nColorsGroup(groupName)==0) {
countedrows+=1;
}
}
countedrows +=1; //Our code up till now doesn't take 0 into account.
return countedrows;
}
return m_colorSet->nColors()/15 + 1;
}
int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const
{
if (m_colorSet && m_colorSet->columnCount() > 0) {
return m_colorSet->columnCount();
}
return 15;
}
Qt::ItemFlags KisPaletteModel::flags(const QModelIndex& index) const
{
if (index.isValid()) {
return Qt::ItemIsSelectable | Qt::ItemIsEnabled
| Qt::ItemIsUserCheckable
| Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
return Qt::ItemIsDropEnabled;
}
QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const
{
if (m_colorSet) {
//make an int to hold the amount of rows we've looked at. The initial is the total rows in the default group.
int rowstotal = m_colorSet->nColorsGroup()/columnCount();
if (row<=rowstotal && (quint32)(row*columnCount()+column)<m_colorSet->nColorsGroup()) {
//if the total rows are in the default group, we just return an index.
return QAbstractTableModel::index(row, column, parent);
} else if(row<0 && column<0) {
return QAbstractTableModel::index(0, 0, parent);
}
if (m_colorSet->nColorsGroup()==0) {
rowstotal+=1; //always add one for the default group when considering groups.
}
Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){
//we make an int for the rows added by the current group.
int newrows = 1+m_colorSet->nColorsGroup(groupName)/columnCount();
if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) {
newrows+=1;
}
if (m_colorSet->nColorsGroup(groupName)==0) {
newrows+=1; //always add one for the group when considering groups.
}
if (rowstotal + newrows>rowCount()) {
newrows = rowCount() - rowstotal;
}
quint32 tempIndex = (quint32)((row-(rowstotal+2))*columnCount()+column);
if (row == rowstotal+1) {
//rowstotal+1 is taken up by the groupname.
return QAbstractTableModel::index(row, 0, parent);
} else if (row > (rowstotal+1) && row <= rowstotal+newrows && tempIndex<m_colorSet->nColorsGroup(groupName)){
//otherwise it's an index to the colors in the group.
return QAbstractTableModel::index(row, column, parent);
}
//add the new rows to the totalrows we've looked at.
rowstotal += newrows;
}
}
return QModelIndex();
}
void KisPaletteModel::setColorSet(KoColorSet* colorSet)
{
m_colorSet = colorSet;
- reset();
+ beginResetModel();
+ endResetModel();
}
KoColorSet* KisPaletteModel::colorSet() const
{
return m_colorSet;
}
QModelIndex KisPaletteModel::indexFromId(int i) const
{
QModelIndex index = QModelIndex();
if (colorSet()->nColors()==0) {
return index;
}
if (i > colorSet()->nColors()) {
qWarning()<<"index is too big"<<i<<"/"<<colorSet()->nColors();
index = this->index(0,0);
}
if (i < (int)colorSet()->nColorsGroup(0)) {
index = QAbstractTableModel::index(i/columnCount(), i%columnCount());
if (!index.isValid()) {
index = QAbstractTableModel::index(0,0,QModelIndex());
}
return index;
} else {
int rowstotal = 1+m_colorSet->nColorsGroup()/columnCount();
if (m_colorSet->nColorsGroup()==0) {
rowstotal +=1;
}
int totalIndexes = colorSet()->nColorsGroup();
Q_FOREACH (QString groupName, m_colorSet->getGroupNames()){
if (i+1<=totalIndexes+colorSet()->nColorsGroup(groupName) && i+1>totalIndexes) {
int col = (i-totalIndexes)%columnCount();
int row = rowstotal+1+((i-totalIndexes)/columnCount());
index = this->index(row, col);
return index;
} else {
rowstotal += 1+m_colorSet->nColorsGroup(groupName)/columnCount();
totalIndexes += colorSet()->nColorsGroup(groupName);
if (m_colorSet->nColorsGroup(groupName)%columnCount() > 0) {
rowstotal+=1;
}
if (m_colorSet->nColorsGroup(groupName)==0) {
rowstotal+=1; //always add one for the group when considering groups.
}
}
}
}
return index;
}
int KisPaletteModel::idFromIndex(const QModelIndex &index) const
{
if (index.isValid()==false) {
return -1;
qWarning()<<"invalid index";
}
int i=0;
- QStringList entryList = qVariantValue<QStringList>(data(index, RetrieveEntryRole));
+ QStringList entryList = qvariant_cast<QStringList>(data(index, RetrieveEntryRole));
if (entryList.isEmpty()) {
return -1;
qWarning()<<"invalid index, there's no data to retreive here";
}
if (entryList.at(0)==QString()) {
return entryList.at(1).toUInt();
}
i = colorSet()->nColorsGroup("");
//find at which position the group is.
int groupIndex = colorSet()->getGroupNames().indexOf(entryList.at(0));
//add all the groupsizes onto it till we get to our group.
for(int g=0; g<groupIndex; g++) {
i+=colorSet()->nColorsGroup(colorSet()->getGroupNames().at(g));
}
//then add the index.
i += entryList.at(1).toUInt();
return i;
}
KoColorSetEntry KisPaletteModel::colorSetEntryFromIndex(const QModelIndex &index) const
{
KoColorSetEntry blank = KoColorSetEntry();
if (!index.isValid()) {
return blank;
}
- QStringList entryList = qVariantValue<QStringList>(data(index, RetrieveEntryRole));
+ QStringList entryList = qvariant_cast<QStringList>(data(index, RetrieveEntryRole));
if (entryList.isEmpty()) {
return blank;
}
QString groupName = entryList.at(0);
quint32 indexInGroup = entryList.at(1).toUInt();
return m_colorSet->getColorGroup(indexInGroup, groupName);
}
bool KisPaletteModel::addColorSetEntry(KoColorSetEntry entry, QString groupName)
{
int col = m_colorSet->nColorsGroup(groupName)%columnCount();
QModelIndex i = getLastEntryIndex();
if (col+1>columnCount()) {
beginInsertRows(QModelIndex(), i.row(), i.row()+1);
}
if (m_colorSet->nColors()<columnCount()) {
beginInsertColumns(QModelIndex(), m_colorSet->nColors(), m_colorSet->nColors()+1);
}
m_colorSet->add(entry, groupName);
if (col+1>columnCount()) {
endInsertRows();
}
if (m_colorSet->nColors()<columnCount()) {
endInsertColumns();
}
return true;
}
bool KisPaletteModel::removeEntry(QModelIndex index, bool keepColors)
{
- QStringList entryList = qVariantValue<QStringList>(index.data(RetrieveEntryRole));
+ QStringList entryList = qvariant_cast<QStringList>(index.data(RetrieveEntryRole));
if (entryList.empty()) {
return false;
}
QString groupName = entryList.at(0);
quint32 indexInGroup = entryList.at(1).toUInt();
- if (qVariantValue<bool>(index.data(IsHeaderRole))==false) {
+ if (qvariant_cast<bool>(index.data(IsHeaderRole))==false) {
if (index.column()-1<0
&& m_colorSet->nColorsGroup(groupName)%columnCount() <1
&& index.row()-1>0
&& m_colorSet->nColorsGroup(groupName)/columnCount()>0) {
beginRemoveRows(QModelIndex(), index.row(), index.row()-1);
}
m_colorSet->removeAt(indexInGroup, groupName);
if (index.column()-1<0
&& m_colorSet->nColorsGroup(groupName)%columnCount() <1
&& index.row()-1>0
&& m_colorSet->nColorsGroup(groupName)/columnCount()>0) {
endRemoveRows();
}
} else {
beginRemoveRows(QModelIndex(), index.row(), index.row()-1);
m_colorSet->removeGroup(groupName, keepColors);
endRemoveRows();
}
return true;
}
bool KisPaletteModel::addGroup(QString groupName)
{
QModelIndex i = getLastEntryIndex();
beginInsertRows(QModelIndex(), i.row(), i.row()+1);
m_colorSet->addGroup(groupName);
endInsertRows();
return true;
}
bool KisPaletteModel::removeRows(int row, int count, const QModelIndex &parent)
{
Q_ASSERT(!parent.isValid());
int beginRow = qMax(0, row);
int endRow = qMin(row + count - 1, (int)m_colorSet->nColors() - 1);
beginRemoveRows(parent, beginRow, endRow);
// Find the palette entry at row, count, remove from KoColorSet
endRemoveRows();
return true;
}
bool KisPaletteModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent)
{
if (!data->hasFormat("krita/x-colorsetentry") && !data->hasFormat("krita/x-colorsetgroup")) {
return false;
}
if (action == Qt::IgnoreAction) {
return false;
}
int endRow;
int endColumn;
if (!parent.isValid()) {
if (row < 0) {
endRow = indexFromId(m_colorSet->nColors()).row();
endColumn = indexFromId(m_colorSet->nColors()).column();
} else {
endRow = qMin(row, indexFromId(m_colorSet->nColors()).row());
endColumn = qMin(column, m_colorSet->columnCount());
}
} else {
endRow = qMin(parent.row(), rowCount());
endColumn = qMin(parent.column(), columnCount());
}
if (data->hasFormat("krita/x-colorsetgroup")) {
QByteArray encodedData = data->data("krita/x-colorsetgroup");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
while (!stream.atEnd()) {
QString groupName;
stream >> groupName;
QModelIndex index = this->index(endRow, 0);
if (index.isValid()) {
- QStringList entryList = qVariantValue<QStringList>(index.data(RetrieveEntryRole));
+ QStringList entryList = qvariant_cast<QStringList>(index.data(RetrieveEntryRole));
QString groupDroppedOn = QString();
if (!entryList.isEmpty()) {
groupDroppedOn = entryList.at(0);
}
int groupIndex = colorSet()->getGroupNames().indexOf(groupName);
beginMoveRows( QModelIndex(), groupIndex, groupIndex, QModelIndex(), endRow);
m_colorSet->moveGroup(groupName, groupDroppedOn);
m_colorSet->save();
endMoveRows();
++endRow;
}
}
} else {
QByteArray encodedData = data->data("krita/x-colorsetentry");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
while (!stream.atEnd()) {
KoColorSetEntry entry;
QString oldGroupName;
int indexInGroup;
QString colorXml;
stream >> entry.name
>> entry.id
>> entry.spotColor
>> indexInGroup
>> oldGroupName
>> colorXml;
QDomDocument doc;
doc.setContent(colorXml);
QDomElement e = doc.documentElement();
QDomElement c = e.firstChildElement();
if (!c.isNull()) {
QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id());
entry.color = KoColor::fromXML(c, colorDepthId);
}
QModelIndex index = this->index(endRow, endColumn);
- if (qVariantValue<bool>(index.data(IsHeaderRole))){
+ if (qvariant_cast<bool>(index.data(IsHeaderRole))){
endRow+=1;
}
if (index.isValid()) {
/*this is to figure out the row of the old color.
* That way we can in turn avoid moverows from complaining the
* index is out of bounds when using index.
* Makes me wonder if we shouldn't just insert the index of the
* old color when requesting the mimetype...
*/
int i = indexInGroup;
if (oldGroupName != QString()) {
colorSet()->nColorsGroup("");
//find at which position the group is.
int groupIndex = colorSet()->getGroupNames().indexOf(oldGroupName);
//add all the groupsizes onto it till we get to our group.
for(int g=0; g<groupIndex; g++) {
i+=colorSet()->nColorsGroup(colorSet()->getGroupNames().at(g));
}
}
QModelIndex indexOld = indexFromId(i);
if (action == Qt::MoveAction){
if (indexOld.row()!=qMax(endRow, 0) && indexOld.row()!=qMax(endRow+1,1)) {
beginMoveRows(QModelIndex(), indexOld.row(), indexOld.row(), QModelIndex(), qMax(endRow+1,1));
}
if (indexOld.column()!=qMax(endColumn, 0) && indexOld.column()!=qMax(endColumn+1,1)) {
beginMoveColumns(QModelIndex(), indexOld.column(), indexOld.column(), QModelIndex(), qMax(endColumn+1,1));
}
} else {
beginInsertRows(QModelIndex(), endRow, endRow);
}
- QStringList entryList = qVariantValue<QStringList>(index.data(RetrieveEntryRole));
+ QStringList entryList = qvariant_cast<QStringList>(index.data(RetrieveEntryRole));
QString entryInGroup = "0";
QString groupName = QString();
if (!entryList.isEmpty()) {
groupName = entryList.at(0);
entryInGroup = entryList.at(1);
}
int location = entryInGroup.toInt();
// Insert the entry
- if (groupName==oldGroupName && qVariantValue<bool>(index.data(IsHeaderRole))==true) {
+ if (groupName==oldGroupName && qvariant_cast<bool>(index.data(IsHeaderRole))==true) {
groupName=QString();
location=m_colorSet->nColorsGroup();
}
m_colorSet->insertBefore(entry, location, groupName);
if (groupName==oldGroupName && location<indexInGroup) {
indexInGroup+=1;
}
if (action == Qt::MoveAction){
m_colorSet->removeAt(indexInGroup, oldGroupName);
}
m_colorSet->save();
if (action == Qt::MoveAction){
if (indexOld.row()!=qMax(endRow, 0) && indexOld.row()!=qMax(endRow+1,1)) {
endMoveRows();
}
if (indexOld.column()!=qMax(endColumn, 0) && indexOld.column()!=qMax(endColumn+1,1)) {
endMoveColumns();
}
} else {
endInsertRows();
}
++endRow;
}
}
}
return true;
}
QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
QString mimeTypeName = "krita/x-colorsetentry";
//Q_FOREACH(const QModelIndex &index, indexes) {
QModelIndex index = indexes.last();
if (index.isValid()) {
- if (qVariantValue<bool>(index.data(IsHeaderRole))==false) {
+ if (qvariant_cast<bool>(index.data(IsHeaderRole))==false) {
KoColorSetEntry entry = colorSetEntryFromIndex(index);
- QStringList entryList = qVariantValue<QStringList>(index.data(RetrieveEntryRole));
+ QStringList entryList = qvariant_cast<QStringList>(index.data(RetrieveEntryRole));
QString groupName = QString();
int indexInGroup = 0;
if (!entryList.isEmpty()) {
groupName = entryList.at(0);
QString iig = entryList.at(1);
indexInGroup = iig.toInt();
}
QDomDocument doc;
QDomElement root = doc.createElement("Color");
root.setAttribute("bitdepth", entry.color.colorSpace()->colorDepthId().id());
doc.appendChild(root);
entry.color.toXML(doc, root);
stream << entry.name
<< entry.id
<< entry.spotColor
<< indexInGroup
<< groupName
<< doc.toString();
} else {
mimeTypeName = "krita/x-colorsetgroup";
- QStringList entryList = qVariantValue<QStringList>(index.data(RetrieveEntryRole));
+ QStringList entryList = qvariant_cast<QStringList>(index.data(RetrieveEntryRole));
QString groupName = QString();
if (!entryList.isEmpty()) {
groupName = entryList.at(0);
}
stream << groupName;
}
}
mimeData->setData(mimeTypeName, encodedData);
return mimeData;
}
QStringList KisPaletteModel::mimeTypes() const
{
return QStringList() << "krita/x-colorsetentry" << "krita/x-colorsetgroup";
}
Qt::DropActions KisPaletteModel::supportedDropActions() const
{
return Qt::MoveAction;
}
diff --git a/libs/ui/KisResourceBundle.h b/libs/ui/KisResourceBundle.h
index a0a2dd7443..5a5f26f3ad 100644
--- a/libs/ui/KisResourceBundle.h
+++ b/libs/ui/KisResourceBundle.h
@@ -1,159 +1,159 @@
/*
* Copyright (c) 2014 Victor Lafon metabolic.ewilan@hotmail.fr
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KORESOURCEBUNDLE_H
#define KORESOURCEBUNDLE_H
#include <QSet>
#include <QList>
#include <KoXmlWriter.h>
#include <resources/KoResource.h>
#include "KisResourceBundleManifest.h"
#include "kritaui_export.h"
class KoStore;
/**
* @brief The ResourceBundle class
* @details Describe the resource bundles as KoResources
*/
class KRITAUI_EXPORT KisResourceBundle : public KoResource
{
public:
/**
* @brief ResourceBundle : Ctor * @param bundlePath the path of the bundle
*/
KisResourceBundle(QString const& fileName);
/**
* @brief ~ResourceBundle : Dtor
*/
~KisResourceBundle() override;
/**
* @brief defaultFileExtension
* @return the default file extension which should be when saving the resource
*/
QString defaultFileExtension() const override;
/**
* @brief load : Load this resource.
* @return true if succeed, false otherwise.
*/
bool load() override;
bool loadFromDevice(QIODevice *dev) override;
/**
* @brief save : Save this resource.
* @return true if succeed, false otherwise.
*/
bool save() override;
bool saveToDevice(QIODevice* dev) const override;
/**
* @brief install : Install the contents of the resource bundle.
*/
bool install();
/**
* @brief uninstall : Uninstall the resource bundle.
*/
bool uninstall();
/**
* @brief addMeta : Add a Metadata to the resource
* @param type type of the metadata
* @param value value of the metadata
*/
void addMeta(const QString &type, const QString &value);
const QString getMeta(const QString &type, const QString &defaultValue = QString()) const;
/**
* @brief addFile : Add a file to the bundle
* @param fileType type of the resource file
* @param filePath path of the resource file
*/
void addResource(QString fileType, QString filePath, QStringList fileTagList, const QByteArray md5sum);
QList<QString> getTagsList();
/**
* @brief isInstalled
* @return true if the bundle is installed, false otherwise.
*/
bool isInstalled();
/**
* @brief setInstalled
* This allows you to set installed or uninstalled upon loading. This is used with blacklists.
*/
void setInstalled(bool install);
void setThumbnail(QString);
/**
* @brief saveMetadata: saves bundle metadata
* @param store bundle where to save the metadata
*/
void saveMetadata(QScopedPointer<KoStore> &store);
/**
* @brief saveManifest: saves bundle manifest
* @param store bundle where to save the manifest
*/
void saveManifest(QScopedPointer<KoStore> &store);
/**
* @brief recreateBundle
* It recreates the bundle by copying the old bundle information to a new store
* and recalculating the md5 of each resource.
* @param oldStore the old store to be recreated.
*/
void recreateBundle(QScopedPointer<KoStore> &oldStore);
QStringList resourceTypes() const;
- QList<KoResource*> resources(const QString &resType = QString::null) const;
+ QList<KoResource*> resources(const QString &resType = QString()) const;
int resourceCount() const;
private:
void writeMeta(const char *metaTag, const QString &metaKey, KoXmlWriter *writer);
void writeUserDefinedMeta(const QString &metaKey, KoXmlWriter *writer);
private:
QImage m_thumbnail;
KisResourceBundleManifest m_manifest;
QMap<QString, QString> m_metadata;
QSet<QString> m_bundletags;
bool m_installed;
QList<QByteArray> m_gradientsMd5Installed;
QList<QByteArray> m_patternsMd5Installed;
QList<QByteArray> m_brushesMd5Installed;
QList<QByteArray> m_palettesMd5Installed;
QList<QByteArray> m_workspacesMd5Installed;
QList<QByteArray> m_presetsMd5Installed;
QString m_bundleVersion;
};
#endif // KORESOURCEBUNDLE_H
diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp
index 5723756fca..59eb5fe1ea 100644
--- a/libs/ui/KisViewManager.cpp
+++ b/libs/ui/KisViewManager.cpp
@@ -1,1362 +1,1373 @@
/*
* This file is part of KimageShop^WKrayon^WKrita
*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* 1999 Michael Koch <koch@kde.org>
* 1999 Carsten Pfeiffer <pfeiffer@kde.org>
* 2002 Patrick Julien <freak@codepimps.org>
* 2003-2011 Boudewijn Rempt <boud@valdyas.org>
* 2004 Clarence Dang <dang@kde.org>
* 2011 José Luis Vergara <pentalis@gmail.com>
* 2017 L. E. Segovia <leo.segovia@siggraph.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include "KisViewManager.h"
#include <QPrinter>
#include <QAction>
#include <QApplication>
#include <QBuffer>
#include <QByteArray>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QDesktopWidget>
+#include <QDesktopServices>
#include <QGridLayout>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QObject>
#include <QPoint>
#include <QPrintDialog>
#include <QRect>
#include <QScrollBar>
#include <QStatusBar>
#include <QToolBar>
#include <QUrl>
#include <QWidget>
#include <kactioncollection.h>
#include <klocalizedstring.h>
#include <KoResourcePaths.h>
#include <kselectaction.h>
#include <KoCanvasController.h>
#include <KoCompositeOp.h>
#include <KoDockRegistry.h>
#include <KoDockWidgetTitleBar.h>
#include <KoProperties.h>
#include <KoResourceItemChooserSync.h>
#include <KoSelection.h>
#include <KoStore.h>
#include <KoToolManager.h>
#include <KoToolRegistry.h>
#include <KoViewConverter.h>
#include <KoZoomHandler.h>
#include <KoPluginLoader.h>
#include <KoDocumentInfo.h>
#include <KoGlobal.h>
#include <KoColorSpaceRegistry.h>
#include "input/kis_input_manager.h"
#include "canvas/kis_canvas2.h"
#include "canvas/kis_canvas_controller.h"
#include "canvas/kis_grid_manager.h"
#include "dialogs/kis_dlg_blacklist_cleanup.h"
#include "input/kis_input_profile_manager.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include "kis_canvas_controls_manager.h"
#include "kis_canvas_resource_provider.h"
#include "kis_composite_progress_proxy.h"
#include <KoProgressUpdater.h>
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_control_frame.h"
#include "kis_coordinates_converter.h"
#include "KisDocument.h"
#include "kis_favorite_resource_manager.h"
#include "kis_filter_manager.h"
#include "kis_group_layer.h"
#include <kis_image.h>
#include <kis_image_barrier_locker.h>
#include "kis_image_manager.h"
#include <kis_layer.h>
#include "kis_mainwindow_observer.h"
#include "kis_mask_manager.h"
#include "kis_mimedata.h"
#include "kis_mirror_manager.h"
#include "kis_node_commands_adapter.h"
#include "kis_node.h"
#include "kis_node_manager.h"
#include "kis_painting_assistants_manager.h"
#include <kis_paint_layer.h>
#include "kis_paintop_box.h"
#include <brushengine/kis_paintop_preset.h>
#include "KisPart.h"
#include "KisPrintJob.h"
#include <KoUpdater.h>
#include "kis_resource_server_provider.h"
#include "kis_selection.h"
#include "kis_selection_manager.h"
#include "kis_shape_controller.h"
#include "kis_shape_layer.h"
#include <kis_signal_compressor.h>
#include "kis_statusbar.h"
#include <KisTemplateCreateDia.h>
#include <kis_tool_freehand.h>
#include "kis_tooltip_manager.h"
#include <kis_undo_adapter.h>
#include "KisView.h"
#include "kis_zoom_manager.h"
#include "widgets/kis_floating_message.h"
#include "kis_signal_auto_connection.h"
#include "kis_script_manager.h"
#include "kis_icon_utils.h"
#include "kis_guides_manager.h"
#include "kis_derived_resources.h"
#include "dialogs/kis_delayed_save_dialog.h"
#include <kis_image.h>
class BlockingUserInputEventFilter : public QObject
{
bool eventFilter(QObject *watched, QEvent *event) override
{
Q_UNUSED(watched);
if(dynamic_cast<QWheelEvent*>(event)
|| dynamic_cast<QKeyEvent*>(event)
|| dynamic_cast<QMouseEvent*>(event)) {
return true;
}
else {
return false;
}
}
};
class KisViewManager::KisViewManagerPrivate
{
public:
KisViewManagerPrivate(KisViewManager *_q, KActionCollection *_actionCollection, QWidget *_q_parent)
: filterManager(_q)
, createTemplate(0)
, saveIncremental(0)
, saveIncrementalBackup(0)
, openResourcesDirectory(0)
, rotateCanvasRight(0)
, rotateCanvasLeft(0)
, resetCanvasRotation(0)
, wrapAroundAction(0)
, levelOfDetailAction(0)
, showRulersAction(0)
, rulersTrackMouseAction(0)
, zoomTo100pct(0)
, zoomIn(0)
, zoomOut(0)
, selectionManager(_q)
, statusBar(_q)
, controlFrame(_q, _q_parent)
, nodeManager(_q)
, imageManager(_q)
, gridManager(_q)
, canvasControlsManager(_q)
, paintingAssistantsManager(_q)
, actionManager(_q, _actionCollection)
, mainWindow(0)
, showFloatingMessage(true)
, currentImageView(0)
, canvasResourceProvider(_q)
, canvasResourceManager()
, guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q)
, actionCollection(_actionCollection)
, mirrorManager(_q)
, inputManager(_q)
, scriptManager(_q)
, actionAuthor(0)
+ , showPixelGrid(0)
{
- canvasResourceManager.addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter));
- canvasResourceManager.addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter));
- canvasResourceManager.addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter));
- canvasResourceManager.addDerivedResourceConverter(toQShared(new KisFlowResourceConverter));
- canvasResourceManager.addDerivedResourceConverter(toQShared(new KisSizeResourceConverter));
- canvasResourceManager.addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter));
- canvasResourceManager.addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter));
- canvasResourceManager.addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator));
+ KisViewManager::initializeResourceManager(&canvasResourceManager);
}
public:
KisFilterManager filterManager;
KisAction *createTemplate;
KisAction *createCopy;
KisAction *saveIncremental;
KisAction *saveIncrementalBackup;
KisAction *openResourcesDirectory;
KisAction *rotateCanvasRight;
KisAction *rotateCanvasLeft;
KisAction *resetCanvasRotation;
KisAction *wrapAroundAction;
KisAction *levelOfDetailAction;
KisAction *showRulersAction;
KisAction *rulersTrackMouseAction;
KisAction *zoomTo100pct;
KisAction *zoomIn;
KisAction *zoomOut;
KisAction *softProof;
KisAction *gamutCheck;
KisSelectionManager selectionManager;
KisGuidesManager guidesManager;
KisStatusBar statusBar;
QPointer<KoUpdater> persistentImageProgressUpdater;
QScopedPointer<KoProgressUpdater> persistentUnthreadedProgressUpdaterRouter;
QPointer<KoUpdater> persistentUnthreadedProgressUpdater;
KisControlFrame controlFrame;
KisNodeManager nodeManager;
KisImageManager imageManager;
KisGridManager gridManager;
KisCanvasControlsManager canvasControlsManager;
KisPaintingAssistantsManager paintingAssistantsManager;
BlockingUserInputEventFilter blockingEventFilter;
KisActionManager actionManager;
QMainWindow* mainWindow;
QPointer<KisFloatingMessage> savedFloatingMessage;
bool showFloatingMessage;
QPointer<KisView> currentImageView;
KisCanvasResourceProvider canvasResourceProvider;
KoCanvasResourceManager canvasResourceManager;
KisSignalCompressor guiUpdateCompressor;
KActionCollection *actionCollection;
KisMirrorManager mirrorManager;
KisInputManager inputManager;
KisSignalAutoConnectionsStore viewConnections;
KisScriptManager scriptManager;
KSelectAction *actionAuthor; // Select action for author profile.
+ KisAction *showPixelGrid;
QByteArray canvasState;
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
QFlags<Qt::WindowState> windowFlags;
#endif
bool blockUntilOperationsFinishedImpl(KisImageSP image, bool force);
};
-
KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollection)
: d(new KisViewManagerPrivate(this, _actionCollection, parent))
{
d->actionCollection = _actionCollection;
d->mainWindow = dynamic_cast<QMainWindow*>(parent);
d->canvasResourceProvider.setResourceManager(&d->canvasResourceManager);
connect(&d->guiUpdateCompressor, SIGNAL(timeout()), this, SLOT(guiUpdateTimeout()));
createActions();
setupManagers();
// These initialization functions must wait until KisViewManager ctor is complete.
d->statusBar.setup();
d->persistentImageProgressUpdater =
d->statusBar.progressUpdater()->startSubtask(1, "", true);
// reset state to "completed"
d->persistentImageProgressUpdater->setRange(0,100);
d->persistentImageProgressUpdater->setValue(100);
d->persistentUnthreadedProgressUpdater =
d->statusBar.progressUpdater()->startSubtask(1, "", true);
// reset state to "completed"
d->persistentUnthreadedProgressUpdater->setRange(0,100);
d->persistentUnthreadedProgressUpdater->setValue(100);
d->persistentUnthreadedProgressUpdaterRouter.reset(
new KoProgressUpdater(d->persistentUnthreadedProgressUpdater,
KoProgressUpdater::Unthreaded));
d->persistentUnthreadedProgressUpdaterRouter->setAutoNestNames(true);
d->controlFrame.setup(parent);
//Check to draw scrollbars after "Canvas only mode" toggle is created.
this->showHideScrollbars();
QScopedPointer<KoDummyCanvasController> dummy(new KoDummyCanvasController(actionCollection()));
KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data());
QTimer::singleShot(0, this, SLOT(initializeStatusBarVisibility()));
connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)),
d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice)));
connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)),
d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*,int)));
connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)),
resourceProvider(), SLOT(slotNodeActivated(KisNodeSP)));
connect(resourceProvider()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
d->controlFrame.paintopBox(), SLOT(slotCanvasResourceChanged(int,QVariant)));
connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*)));
connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions()));
KisInputProfileManager::instance()->loadProfiles();
KisConfig cfg;
d->showFloatingMessage = cfg.showCanvasMessages();
}
KisViewManager::~KisViewManager()
{
KisConfig cfg;
if (resourceProvider() && resourceProvider()->currentPreset()) {
cfg.writeEntry("LastPreset", resourceProvider()->currentPreset()->name());
cfg.writeKoColor("LastForeGroundColor",resourceProvider()->fgColor());
cfg.writeKoColor("LastBackGroundColor",resourceProvider()->bgColor());
}
cfg.writeEntry("baseLength", KoResourceItemChooserSync::instance()->baseLength());
delete d;
}
+void KisViewManager::initializeResourceManager(KoCanvasResourceManager *resourceManager)
+{
+ resourceManager->addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter));
+ resourceManager->addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter));
+ resourceManager->addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter));
+ resourceManager->addDerivedResourceConverter(toQShared(new KisFlowResourceConverter));
+ resourceManager->addDerivedResourceConverter(toQShared(new KisSizeResourceConverter));
+ resourceManager->addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter));
+ resourceManager->addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter));
+ resourceManager->addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator));
+}
+
KActionCollection *KisViewManager::actionCollection() const
{
return d->actionCollection;
}
void KisViewManager::slotViewAdded(KisView *view)
{
// WARNING: this slot is called even when a view from another main windows is added!
// Don't expect \p view be a child of this view manager!
Q_UNUSED(view);
if (viewCount() == 0) {
d->statusBar.showAllStatusBarItems();
}
}
void KisViewManager::slotViewRemoved(KisView *view)
{
// WARNING: this slot is called even when a view from another main windows is removed!
// Don't expect \p view be a child of this view manager!
Q_UNUSED(view);
if (viewCount() == 0) {
d->statusBar.hideAllStatusBarItems();
}
}
void KisViewManager::setCurrentView(KisView *view)
{
bool first = true;
if (d->currentImageView) {
d->currentImageView->notifyCurrentStateChanged(false);
d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor));
first = false;
KisDocument* doc = d->currentImageView->document();
if (doc) {
doc->image()->compositeProgressProxy()->removeProxy(d->persistentImageProgressUpdater);
doc->disconnect(this);
}
d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar);
d->viewConnections.clear();
}
QPointer<KisView> imageView = qobject_cast<KisView*>(view);
d->currentImageView = imageView;
if (imageView) {
d->softProof->setChecked(imageView->softProofing());
d->gamutCheck->setChecked(imageView->gamutCheck());
// Wait for the async image to have loaded
KisDocument* doc = view->document();
// connect(canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), d->statusBar, SLOT(documentMousePositionChanged(QPointF)));
// Restore the last used brush preset, color and background color.
if (first) {
KisConfig cfg;
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
QString lastPreset = cfg.readEntry("LastPreset", QString("Basic_tip_default"));
KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset);
if (!preset) {
preset = rserver->resourceByName("Basic_tip_default");
}
if (!preset) {
preset = rserver->resources().first();
}
if (preset) {
paintOpBox()->restoreResource(preset.data());
}
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KoColor foreground(Qt::black, cs);
d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground));
KoColor background(Qt::white, cs);
d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background));
}
KisCanvasController *canvasController = dynamic_cast<KisCanvasController*>(d->currentImageView->canvasController());
d->viewConnections.addUniqueConnection(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), doc->image(), SLOT(requestStrokeEndActiveNode()));
d->viewConnections.addUniqueConnection(d->rotateCanvasRight, SIGNAL(triggered()), canvasController, SLOT(rotateCanvasRight15()));
d->viewConnections.addUniqueConnection(d->rotateCanvasLeft, SIGNAL(triggered()),canvasController, SLOT(rotateCanvasLeft15()));
d->viewConnections.addUniqueConnection(d->resetCanvasRotation, SIGNAL(triggered()),canvasController, SLOT(resetCanvasRotation()));
d->viewConnections.addUniqueConnection(d->wrapAroundAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleWrapAroundMode(bool)));
d->wrapAroundAction->setChecked(canvasController->wrapAroundMode());
d->viewConnections.addUniqueConnection(d->levelOfDetailAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleLevelOfDetailMode(bool)));
d->levelOfDetailAction->setChecked(canvasController->levelOfDetailMode());
d->viewConnections.addUniqueConnection(d->currentImageView->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), d->controlFrame.paintopBox(), SLOT(slotColorSpaceChanged(const KoColorSpace*)));
d->viewConnections.addUniqueConnection(d->showRulersAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setShowRulers(bool)));
d->viewConnections.addUniqueConnection(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setRulersTrackMouse(bool)));
d->viewConnections.addUniqueConnection(d->zoomTo100pct, SIGNAL(triggered()), imageView->zoomManager(), SLOT(zoomTo100()));
d->viewConnections.addUniqueConnection(d->zoomIn, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomIn()));
d->viewConnections.addUniqueConnection(d->zoomOut, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomOut()));
d->viewConnections.addUniqueConnection(d->softProof, SIGNAL(toggled(bool)), view, SLOT(slotSoftProofing(bool)) );
d->viewConnections.addUniqueConnection(d->gamutCheck, SIGNAL(toggled(bool)), view, SLOT(slotGamutCheck(bool)) );
// set up progrress reporting
doc->image()->compositeProgressProxy()->addProxy(d->persistentImageProgressUpdater);
d->viewConnections.addUniqueConnection(&d->statusBar, SIGNAL(sigCancellationRequested()), doc->image(), SLOT(requestStrokeCancellation()));
+ d->viewConnections.addUniqueConnection(d->showPixelGrid, SIGNAL(toggled(bool)), canvasController, SLOT(slotTogglePixelGrid(bool)));
+
imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked());
imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked());
showHideScrollbars();
}
d->filterManager.setView(imageView);
d->selectionManager.setView(imageView);
d->guidesManager.setView(imageView);
d->nodeManager.setView(imageView);
d->imageManager.setView(imageView);
d->canvasControlsManager.setView(imageView);
d->actionManager.setView(imageView);
d->gridManager.setView(imageView);
d->statusBar.setView(imageView);
d->paintingAssistantsManager.setView(imageView);
d->mirrorManager.setView(imageView);
if (d->currentImageView) {
d->currentImageView->notifyCurrentStateChanged(true);
d->currentImageView->canvasController()->activate();
d->currentImageView->canvasController()->setFocus();
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)),
resourceProvider(), SLOT(slotImageSizeChanged()));
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigResolutionChanged(double,double)),
resourceProvider(), SLOT(slotOnScreenResolutionChanged()));
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigNodeChanged(KisNodeSP)),
this, SLOT(updateGUI()));
d->viewConnections.addUniqueConnection(
d->currentImageView->zoomManager()->zoomController(),
SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)),
resourceProvider(), SLOT(slotOnScreenResolutionChanged()));
}
d->actionManager.updateGUI();
resourceProvider()->slotImageSizeChanged();
resourceProvider()->slotOnScreenResolutionChanged();
Q_EMIT viewChanged();
}
KoZoomController *KisViewManager::zoomController() const
{
if (d->currentImageView) {
return d->currentImageView->zoomController();
}
return 0;
}
KisImageWSP KisViewManager::image() const
{
if (document()) {
return document()->image();
}
return 0;
}
KisCanvasResourceProvider * KisViewManager::resourceProvider()
{
return &d->canvasResourceProvider;
}
KisCanvas2 * KisViewManager::canvasBase() const
{
if (d && d->currentImageView) {
return d->currentImageView->canvasBase();
}
return 0;
}
QWidget* KisViewManager::canvas() const
{
if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) {
return d->currentImageView->canvasBase()->canvasWidget();
}
return 0;
}
KisStatusBar * KisViewManager::statusBar() const
{
return &d->statusBar;
}
void KisViewManager::addStatusBarItem(QWidget *widget, int stretch, bool permanent)
{
d->statusBar.addStatusBarItem(widget, stretch, permanent);
}
void KisViewManager::removeStatusBarItem(QWidget *widget)
{
d->statusBar.removeStatusBarItem(widget);
}
KisPaintopBox* KisViewManager::paintOpBox() const
{
return d->controlFrame.paintopBox();
}
QPointer<KoUpdater> KisViewManager::createUnthreadedUpdater(const QString &name)
{
return d->persistentUnthreadedProgressUpdaterRouter->startSubtask(1, name, false);
}
QPointer<KoUpdater> KisViewManager::createThreadedUpdater(const QString &name)
{
return d->statusBar.progressUpdater()->startSubtask(1, name, false);
}
KisSelectionManager * KisViewManager::selectionManager()
{
return &d->selectionManager;
}
KisNodeSP KisViewManager::activeNode()
{
return d->nodeManager.activeNode();
}
KisLayerSP KisViewManager::activeLayer()
{
return d->nodeManager.activeLayer();
}
KisPaintDeviceSP KisViewManager::activeDevice()
{
return d->nodeManager.activePaintDevice();
}
KisZoomManager * KisViewManager::zoomManager()
{
if (d->currentImageView) {
return d->currentImageView->zoomManager();
}
return 0;
}
KisFilterManager * KisViewManager::filterManager()
{
return &d->filterManager;
}
KisImageManager * KisViewManager::imageManager()
{
return &d->imageManager;
}
KisInputManager* KisViewManager::inputManager() const
{
return &d->inputManager;
}
KisSelectionSP KisViewManager::selection()
{
if (d->currentImageView) {
return d->currentImageView->selection();
}
return 0;
}
bool KisViewManager::selectionEditable()
{
KisLayerSP layer = activeLayer();
if (layer) {
KoProperties properties;
QList<KisNodeSP> masks = layer->childNodes(QStringList("KisSelectionMask"), properties);
if (masks.size() == 1) {
return masks[0]->isEditable();
}
}
// global selection is always editable
return true;
}
KisUndoAdapter * KisViewManager::undoAdapter()
{
if (!document()) return 0;
KisImageWSP image = document()->image();
Q_ASSERT(image);
return image->undoAdapter();
}
void KisViewManager::createActions()
{
KisConfig cfg;
d->saveIncremental = actionManager()->createAction("save_incremental_version");
connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental()));
d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup");
connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup()));
connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved()));
d->saveIncremental->setEnabled(false);
d->saveIncrementalBackup->setEnabled(false);
KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger");
connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger()));
d->createTemplate = actionManager()->createAction("create_template");
connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate()));
d->createCopy = actionManager()->createAction("create_copy");
connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy()));
d->openResourcesDirectory = actionManager()->createAction("open_resources_directory");
connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory()));
d->rotateCanvasRight = actionManager()->createAction("rotate_canvas_right");
d->rotateCanvasLeft = actionManager()->createAction("rotate_canvas_left");
d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation");
d->wrapAroundAction = actionManager()->createAction("wrap_around_mode");
d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode");
d->softProof = actionManager()->createAction("softProof");
d->gamutCheck = actionManager()->createAction("gamutCheck");
KisAction *tAction = actionManager()->createAction("showStatusBar");
tAction->setChecked(cfg.showStatusBar());
connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool)));
tAction = actionManager()->createAction("view_show_canvas_only");
tAction->setChecked(false);
connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool)));
//Workaround, by default has the same shortcut as mirrorCanvas
KisAction *a = dynamic_cast<KisAction*>(actionCollection()->action("format_italic"));
if (a) {
a->setDefaultShortcut(QKeySequence());
}
a = actionManager()->createAction("edit_blacklist_cleanup");
connect(a, SIGNAL(triggered()), this, SLOT(slotBlacklistCleanup()));
d->showRulersAction = actionManager()->createAction("view_ruler");
d->showRulersAction->setChecked(cfg.showRulers());
connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool)));
d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse");
d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse());
connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool)));
d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct");
d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, "");
d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, "");
d->actionAuthor = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this);
connect(d->actionAuthor, SIGNAL(triggered(const QString &)), this, SLOT(changeAuthorProfile(const QString &)));
actionCollection()->addAction("settings_active_author", d->actionAuthor);
slotUpdateAuthorProfileActions();
+ d->showPixelGrid = actionManager()->createAction("view_pixel_grid");
+ d->showPixelGrid->setChecked(cfg.pixelGridEnabled());
+
}
void KisViewManager::setupManagers()
{
// Create the managers for filters, selections, layers etc.
// XXX: When the currentlayer changes, call updateGUI on all
// managers
d->filterManager.setup(actionCollection(), actionManager());
d->selectionManager.setup(actionManager());
d->guidesManager.setup(actionManager());
d->nodeManager.setup(actionCollection(), actionManager());
d->imageManager.setup(actionManager());
d->gridManager.setup(actionManager());
d->paintingAssistantsManager.setup(actionManager());
d->canvasControlsManager.setup(actionManager());
d->mirrorManager.setup(actionCollection());
d->scriptManager.setup(actionCollection(), actionManager());
}
void KisViewManager::updateGUI()
{
d->guiUpdateCompressor.start();
}
void KisViewManager::slotBlacklistCleanup()
{
KisDlgBlacklistCleanup dialog;
dialog.exec();
}
KisNodeManager * KisViewManager::nodeManager() const
{
return &d->nodeManager;
}
KisActionManager* KisViewManager::actionManager() const
{
return &d->actionManager;
}
KisGridManager * KisViewManager::gridManager() const
{
return &d->gridManager;
}
KisGuidesManager * KisViewManager::guidesManager() const
{
return &d->guidesManager;
}
KisDocument *KisViewManager::document() const
{
if (d->currentImageView && d->currentImageView->document()) {
return d->currentImageView->document();
}
return 0;
}
KisScriptManager *KisViewManager::scriptManager() const
{
return &d->scriptManager;
}
int KisViewManager::viewCount() const
{
KisMainWindow *mw = qobject_cast<KisMainWindow*>(d->mainWindow);
if (mw) {
return mw->viewCount();
}
return 0;
}
bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(KisImageSP image, bool force)
{
const int busyWaitDelay = 1000;
KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow);
dialog.blockIfImageIsBusy();
return dialog.result() == QDialog::Accepted;
}
bool KisViewManager::blockUntilOperationsFinished(KisImageSP image)
{
return d->blockUntilOperationsFinishedImpl(image, false);
}
void KisViewManager::blockUntilOperationsFinishedForced(KisImageSP image)
{
d->blockUntilOperationsFinishedImpl(image, true);
}
void KisViewManager::slotCreateTemplate()
{
if (!document()) return;
KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow());
}
void KisViewManager::slotCreateCopy()
{
KisDocument *srcDoc = document();
if (!srcDoc) return;
if (!this->blockUntilOperationsFinished(srcDoc->image())) return;
KisDocument *doc = 0;
{
KisImageBarrierLocker l(srcDoc->image());
doc = srcDoc->clone();
}
KIS_SAFE_ASSERT_RECOVER_RETURN(doc);
QString name = srcDoc->documentInfo()->aboutInfo("name");
if (name.isEmpty()) {
name = document()->url().toLocalFile();
}
name = i18n("%1 (Copy)", name);
doc->documentInfo()->setAboutInfo("title", name);
KisPart::instance()->addDocument(doc);
KisMainWindow *mw = qobject_cast<KisMainWindow*>(d->mainWindow);
mw->addViewAndNotifyLoadingCompleted(doc);
}
QMainWindow* KisViewManager::qtMainWindow() const
{
if (d->mainWindow)
return d->mainWindow;
//Fallback for when we have not yet set the main window.
QMainWindow* w = qobject_cast<QMainWindow*>(qApp->activeWindow());
if(w)
return w;
return mainWindow();
}
void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow)
{
d->mainWindow = newMainWindow;
}
-
void KisViewManager::slotDocumentSaved()
{
d->saveIncremental->setEnabled(true);
d->saveIncrementalBackup->setEnabled(true);
}
void KisViewManager::slotSaveIncremental()
{
if (!document()) return;
bool foundVersion;
bool fileAlreadyExists;
bool isBackup;
QString version = "000";
QString newVersion;
QString letter;
QString fileName = document()->localFilePath();
// Find current version filenames
// v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well
// Considering our incremental version and backup scheme, format is filename_001~001.ext
QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]");
regex.indexIn(fileName); // Perform the search
QStringList matches = regex.capturedTexts();
foundVersion = matches.at(0).isEmpty() ? false : true;
// Ensure compatibility with Save Incremental Backup
// If this regex is not kept separate, the entire algorithm needs modification;
// It's simpler to just add this.
QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]");
regexAux.indexIn(fileName); // Perform the search
QStringList matchesAux = regexAux.capturedTexts();
isBackup = matchesAux.at(0).isEmpty() ? false : true;
// If the filename has a version, prepare it for incrementation
if (foundVersion) {
version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches
if (version.contains(QRegExp("[a-z]"))) {
version.chop(1); // Trim "."
letter = version.right(1); // Save letter
version.chop(1); // Trim letter
} else {
version.chop(1); // Trim "."
}
version.remove(0, 1); // Trim "_"
} else {
// TODO: this will not work with files extensions like jp2
// ...else, simply add a version to it so the next loop works
QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension
regex2.indexIn(fileName);
QStringList matches2 = regex2.capturedTexts();
QString extensionPlusVersion = matches2.at(0);
extensionPlusVersion.prepend(version);
extensionPlusVersion.prepend("_");
fileName.replace(regex2, extensionPlusVersion);
}
// Prepare the base for new version filename
int intVersion = version.toInt(0);
++intVersion;
QString baseNewVersion = QString::number(intVersion);
while (baseNewVersion.length() < version.length()) {
baseNewVersion.prepend("0");
}
// Check if the file exists under the new name and search until options are exhausted (test appending a to z)
do {
newVersion = baseNewVersion;
newVersion.prepend("_");
if (!letter.isNull()) newVersion.append(letter);
if (isBackup) {
newVersion.append("~");
} else {
newVersion.append(".");
}
fileName.replace(regex, newVersion);
fileAlreadyExists = QFile(fileName).exists();
if (fileAlreadyExists) {
if (!letter.isNull()) {
char letterCh = letter.at(0).toLatin1();
++letterCh;
letter = QString(QChar(letterCh));
} else {
letter = 'a';
}
}
} while (fileAlreadyExists && letter != "{"); // x, y, z, {...
if (letter == "{") {
QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental version"), i18n("Alternative names exhausted, try manually saving with a higher number"));
return;
}
document()->setFileBatchMode(true);
document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true);
document()->setFileBatchMode(false);
if (mainWindow()) {
mainWindow()->updateCaption();
}
}
void KisViewManager::slotSaveIncrementalBackup()
{
if (!document()) return;
bool workingOnBackup;
bool fileAlreadyExists;
QString version = "000";
QString newVersion;
QString letter;
QString fileName = document()->localFilePath();
// First, discover if working on a backup file, or a normal file
QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]");
regex.indexIn(fileName); // Perform the search
QStringList matches = regex.capturedTexts();
workingOnBackup = matches.at(0).isEmpty() ? false : true;
if (workingOnBackup) {
// Try to save incremental version (of backup), use letter for alt versions
version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches
if (version.contains(QRegExp("[a-z]"))) {
version.chop(1); // Trim "."
letter = version.right(1); // Save letter
version.chop(1); // Trim letter
} else {
version.chop(1); // Trim "."
}
version.remove(0, 1); // Trim "~"
// Prepare the base for new version filename
int intVersion = version.toInt(0);
++intVersion;
QString baseNewVersion = QString::number(intVersion);
QString backupFileName = document()->localFilePath();
while (baseNewVersion.length() < version.length()) {
baseNewVersion.prepend("0");
}
// Check if the file exists under the new name and search until options are exhausted (test appending a to z)
do {
newVersion = baseNewVersion;
newVersion.prepend("~");
if (!letter.isNull()) newVersion.append(letter);
newVersion.append(".");
backupFileName.replace(regex, newVersion);
fileAlreadyExists = QFile(backupFileName).exists();
if (fileAlreadyExists) {
if (!letter.isNull()) {
char letterCh = letter.at(0).toLatin1();
++letterCh;
letter = QString(QChar(letterCh));
} else {
letter = 'a';
}
}
} while (fileAlreadyExists && letter != "{"); // x, y, z, {...
if (letter == "{") {
QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number"));
return;
}
QFile::copy(fileName, backupFileName);
document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true);
if (mainWindow()) mainWindow()->updateCaption();
}
else { // if NOT working on a backup...
// Navigate directory searching for latest backup version, ignore letters
const quint8 HARDCODED_DIGIT_COUNT = 3;
QString baseNewVersion = "000";
QString backupFileName = document()->localFilePath();
QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension
regex2.indexIn(backupFileName);
QStringList matches2 = regex2.capturedTexts();
QString extensionPlusVersion = matches2.at(0);
extensionPlusVersion.prepend(baseNewVersion);
extensionPlusVersion.prepend("~");
backupFileName.replace(regex2, extensionPlusVersion);
// Save version with 1 number higher than the highest version found ignoring letters
do {
newVersion = baseNewVersion;
newVersion.prepend("~");
newVersion.append(".");
backupFileName.replace(regex, newVersion);
fileAlreadyExists = QFile(backupFileName).exists();
if (fileAlreadyExists) {
// Prepare the base for new version filename, increment by 1
int intVersion = baseNewVersion.toInt(0);
++intVersion;
baseNewVersion = QString::number(intVersion);
while (baseNewVersion.length() < HARDCODED_DIGIT_COUNT) {
baseNewVersion.prepend("0");
}
}
} while (fileAlreadyExists);
// Save both as backup and on current file for interapplication workflow
document()->setFileBatchMode(true);
QFile::copy(fileName, backupFileName);
document()->saveAs(QUrl::fromUserInput(fileName), document()->mimeType(), true);
document()->setFileBatchMode(false);
if (mainWindow()) mainWindow()->updateCaption();
}
}
void KisViewManager::disableControls()
{
// prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel
// this is for Bug 250944
// the solution blocks all wheel, mouse and key event, while dragging with the freehand tool
// see KisToolFreehand::initPaint() and endPaint()
d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter);
Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) {
child->installEventFilter(&d->blockingEventFilter);
}
}
void KisViewManager::enableControls()
{
d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter);
Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) {
child->removeEventFilter(&d->blockingEventFilter);
}
}
void KisViewManager::showStatusBar(bool toggled)
{
KisMainWindow *mw = mainWindow();
if(mw && mw->statusBar()) {
mw->statusBar()->setVisible(toggled);
KisConfig cfg;
cfg.setShowStatusBar(toggled);
}
}
void KisViewManager::switchCanvasOnly(bool toggled)
{
KisConfig cfg;
KisMainWindow* main = mainWindow();
if(!main) {
dbgUI << "Unable to switch to canvas-only mode, main window not found";
return;
}
if (toggled) {
d->canvasState = qtMainWindow()->saveState();
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
d->windowFlags = main->windowState();
#endif
}
if (cfg.hideStatusbarFullscreen()) {
if (main->statusBar()) {
if (!toggled) {
if (main->statusBar()->dynamicPropertyNames().contains("wasvisible")) {
if (main->statusBar()->property("wasvisible").toBool()) {
main->statusBar()->setVisible(true);
}
}
}
else {
main->statusBar()->setProperty("wasvisible", main->statusBar()->isVisible());
main->statusBar()->setVisible(false);
}
}
}
if (cfg.hideDockersFullscreen()) {
KisAction* action = qobject_cast<KisAction*>(main->actionCollection()->action("view_toggledockers"));
if (action) {
action->setCheckable(true);
if (toggled) {
if (action->isChecked()) {
cfg.setShowDockers(action->isChecked());
action->setChecked(false);
} else {
cfg.setShowDockers(false);
}
} else {
action->setChecked(cfg.showDockers());
}
}
}
// QT in windows does not return to maximized upon 4th tab in a row
// https://bugreports.qt.io/browse/QTBUG-57882, https://bugreports.qt.io/browse/QTBUG-52555, https://codereview.qt-project.org/#/c/185016/
if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) {
if(toggled) {
main->setWindowState( main->windowState() | Qt::WindowFullScreen);
} else {
main->setWindowState( main->windowState() & ~Qt::WindowFullScreen);
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
// If window was maximized prior to fullscreen, restore that
if (d->windowFlags & Qt::WindowMaximized) {
main->setWindowState( main->windowState() | Qt::WindowMaximized);
}
#endif
}
}
if (cfg.hideMenuFullscreen()) {
if (!toggled) {
if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) {
if (main->menuBar()->property("wasvisible").toBool()) {
main->menuBar()->setVisible(true);
}
}
}
else {
main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible());
main->menuBar()->setVisible(false);
}
}
if (cfg.hideToolbarFullscreen()) {
QList<QToolBar*> toolBars = main->findChildren<QToolBar*>();
Q_FOREACH (QToolBar* toolbar, toolBars) {
if (!toggled) {
if (toolbar->dynamicPropertyNames().contains("wasvisible")) {
if (toolbar->property("wasvisible").toBool()) {
toolbar->setVisible(true);
}
}
}
else {
toolbar->setProperty("wasvisible", toolbar->isVisible());
toolbar->setVisible(false);
}
}
}
showHideScrollbars();
if (toggled) {
// show a fading heads-up display about the shortcut to go back
showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.",
actionCollection()->action("view_show_canvas_only")->shortcut().toString()), QIcon());
}
else {
main->restoreState(d->canvasState);
}
}
void KisViewManager::toggleTabletLogger()
{
d->inputManager.toggleTabletLogger();
}
void KisViewManager::openResourcesDirectory()
{
QString dir = KoResourcePaths::locateLocal("data", "");
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
}
void KisViewManager::updateIcons()
{
if (mainWindow()) {
QList<QDockWidget*> dockers = mainWindow()->dockWidgets();
Q_FOREACH (QDockWidget* dock, dockers) {
dbgKrita << "name " << dock->objectName();
KoDockWidgetTitleBar* titlebar = dynamic_cast<KoDockWidgetTitleBar*>(dock->titleBarWidget());
if (titlebar) {
titlebar->updateIcons();
}
QObjectList objects;
objects.append(dock);
while (!objects.isEmpty()) {
QObject* object = objects.takeFirst();
objects.append(object->children());
KisIconUtils::updateIconCommon(object);
}
}
}
}
void KisViewManager::initializeStatusBarVisibility()
{
KisConfig cfg;
d->mainWindow->statusBar()->setVisible(cfg.showStatusBar());
}
void KisViewManager::guiUpdateTimeout()
{
d->nodeManager.updateGUI();
d->selectionManager.updateGUI();
d->filterManager.updateGUI();
if (zoomManager()) {
zoomManager()->updateGUI();
}
d->gridManager.updateGUI();
d->actionManager.updateGUI();
}
void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment)
{
if (!d->currentImageView) return;
d->currentImageView->showFloatingMessageImpl(message, icon, timeout, priority, alignment);
emit floatingMessageRequested(message, icon.name());
}
KisMainWindow *KisViewManager::mainWindow() const
{
return qobject_cast<KisMainWindow*>(d->mainWindow);
}
void KisViewManager::showHideScrollbars()
{
if (!d->currentImageView) return;
if (!d->currentImageView->canvasController()) return;
KisConfig cfg;
bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked();
if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) {
d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
} else {
d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
}
}
void KisViewManager::slotSaveShowRulersState(bool value)
{
KisConfig cfg;
cfg.setShowRulers(value);
}
void KisViewManager::slotSaveRulersTrackMouseState(bool value)
{
KisConfig cfg;
cfg.setRulersTrackMouse(value);
}
void KisViewManager::setShowFloatingMessage(bool show)
{
d->showFloatingMessage = show;
}
void KisViewManager::changeAuthorProfile(const QString &profileName)
{
KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author");
if (profileName.isEmpty()) {
appAuthorGroup.writeEntry("active-profile", "");
} else if (profileName == i18nc("choice for author profile", "Anonymous")) {
appAuthorGroup.writeEntry("active-profile", "anonymous");
} else {
appAuthorGroup.writeEntry("active-profile", profileName);
}
appAuthorGroup.sync();
Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) {
doc->documentInfo()->updateParameters();
}
}
void KisViewManager::slotUpdateAuthorProfileActions()
{
Q_ASSERT(d->actionAuthor);
if (!d->actionAuthor) {
return;
}
d->actionAuthor->clear();
d->actionAuthor->addAction(i18n("Default Author Profile"));
d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous"));
KConfigGroup authorGroup(KoGlobal::calligraConfig(), "Author");
QStringList profiles = authorGroup.readEntry("profile-names", QStringList());
Q_FOREACH (const QString &profile , profiles) {
d->actionAuthor->addAction(profile);
}
KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author");
QString profileName = appAuthorGroup.readEntry("active-profile", "");
if (profileName == "anonymous") {
d->actionAuthor->setCurrentItem(1);
} else if (profiles.contains(profileName)) {
d->actionAuthor->setCurrentAction(profileName);
} else {
d->actionAuthor->setCurrentItem(0);
}
}
diff --git a/libs/ui/KisViewManager.h b/libs/ui/KisViewManager.h
index 9da2f795e6..20b46b512e 100644
--- a/libs/ui/KisViewManager.h
+++ b/libs/ui/KisViewManager.h
@@ -1,270 +1,272 @@
/*
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_GUI_CLIENT_H
#define KIS_GUI_CLIENT_H
#include <QDockWidget>
#include <QQueue>
#include <QPointer>
#include <KisMainWindow.h>
#include <KoToolManager.h>
#include <kritaui_export.h>
#include <kis_types.h>
#include "kis_floating_message.h"
class QPoint;
class KisView;
class KisCanvas2;
class KisCanvasResourceProvider;
class KisDocument;
class KisFilterManager;
class KisGridManager;
class KisGuidesManager;
class KisImageManager;
class KisNodeManager;
class KisPaintingAssistantsManager;
class KisPaintopBox;
class KisSelectionManager;
class KisStatusBar;
class KisUndoAdapter;
class KisZoomManager;
class KisPaintopBox;
class KisActionManager;
class KisScriptManager;
class KisInputManager;
class KoUpdater;
class KoProgressUpdater;
/**
* KisViewManager manages the collection of views shown in a single mainwindow.
*/
class KRITAUI_EXPORT KisViewManager : public QObject
{
Q_OBJECT
public:
/**
* Construct a new view on the krita document.
* @param document the document we show.
* @param parent a parent widget we show ourselves in.
*/
KisViewManager(QWidget *parent, KActionCollection *actionCollection);
~KisViewManager() override;
/**
* Retrieves the entire action collection.
*/
virtual KActionCollection* actionCollection() const;
public: // Krita specific interfaces
void setCurrentView(KisView *view);
/// Return the image this view is displaying
KisImageWSP image() const;
KoZoomController *zoomController() const;
/// The resource provider contains all per-view settings, such as
/// current color, current paint op etc.
KisCanvasResourceProvider *resourceProvider();
/// Return the canvasbase class
KisCanvas2 *canvasBase() const;
/// Return the actual widget that is displaying the current image
QWidget* canvas() const;
/// Return the wrapper class around the statusbar
KisStatusBar *statusBar() const;
/**
* This adds a widget to the statusbar for this view.
* If you use this method instead of using statusBar() directly,
* KisView will take care of removing the items when the view GUI is deactivated
* and readding them when it is reactivated.
* The parameters are the same as QStatusBar::addWidget().
*/
void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false);
/**
* Remove a widget from the statusbar for this view.
*/
void removeStatusBarItem(QWidget * widget);
KisPaintopBox* paintOpBox() const;
/// create a new progress updater
QPointer<KoUpdater> createUnthreadedUpdater(const QString &name);
QPointer<KoUpdater> createThreadedUpdater(const QString &name);
/// The selection manager handles everything action related to
/// selections.
KisSelectionManager *selectionManager();
/// The node manager handles everything about nodes
KisNodeManager *nodeManager() const;
KisActionManager *actionManager() const;
/**
* Convenience method to get at the active node, which may be
* a layer or a mask or a selection
*/
KisNodeSP activeNode();
/// Convenience method to get at the active layer
KisLayerSP activeLayer();
/// Convenience method to get at the active paint device
KisPaintDeviceSP activeDevice();
/// The filtermanager handles everything action-related to filters
KisFilterManager *filterManager();
/// The image manager handles everything action-related to the
/// current image
KisImageManager *imageManager();
/// Filters events and sends them to canvas actions
KisInputManager *inputManager() const;
/// Convenience method to get at the active selection (the
/// selection of the current layer, or, if that does not exist,
/// the global selection.
KisSelectionSP selection();
/// Checks if the current global or local selection is editable
bool selectionEditable();
/// The undo adapter is used to add commands to the undo stack
KisUndoAdapter *undoAdapter();
KisDocument *document() const;
KisScriptManager *scriptManager() const;
int viewCount() const;
/**
* @brief blockUntilOperationsFinished blocks the GUI of the application until execution
* of actions on \p image is finished
* @param image the image which we should wait for
* @return true if the image has finished execution of the actions, false if
* the user cancelled operation
*/
bool blockUntilOperationsFinished(KisImageSP image);
/**
* @brief blockUntilOperationsFinished blocks the GUI of the application until execution
* of actions on \p image is finished. Does *not* provide a "Cancel" button. So the
* user is forced to wait.
* @param image the image which we should wait for
*/
void blockUntilOperationsFinishedForced(KisImageSP image);
public:
KisGridManager * gridManager() const;
KisGuidesManager * guidesManager() const;
/// disable and enable toolbar controls. used for disabling them during painting.
void enableControls();
void disableControls();
/// shows a floating message in the top right corner of the canvas
void showFloatingMessage(const QString &message, const QIcon& icon, int timeout = 4500,
KisFloatingMessage::Priority priority = KisFloatingMessage::Medium,
int alignment = Qt::AlignCenter | Qt::TextWordWrap);
/// @return the KoMaindow this view is in, or 0
KisMainWindow *mainWindow() const;
/// The QMainWindow associated with this view. This is most likely going to be shell(), but
/// when running as Gemini or Sketch, this will be set to the applications' own QMainWindow.
/// This can be checked by qobject_casting to KisMainWindow to check the difference.
QMainWindow* qtMainWindow() const;
/// The mainWindow function will return the shell() value, unless this function is called
/// with a non-null value. To make it return shell() again, simply pass null to this function.
void setQtMainWindow(QMainWindow* newMainWindow);
+ static void initializeResourceManager(KoCanvasResourceManager *resourceManager);
+
public Q_SLOTS:
void switchCanvasOnly(bool toggled);
void setShowFloatingMessage(bool show);
void showHideScrollbars();
/// Visit all managers to update gui elements, e.g. enable / disable actions.
/// This is heavy-duty call, so it uses a compressor.
void updateGUI();
/// Update the style of all the icons
void updateIcons();
void slotViewAdded(KisView *view);
void slotViewRemoved(KisView *view);
Q_SIGNALS:
void floatingMessageRequested(const QString &message, const QString &iconName);
/**
* @brief viewChanged
* sent out when the view has changed.
*/
void viewChanged();
private Q_SLOTS:
void slotBlacklistCleanup();
void slotCreateTemplate();
void slotCreateCopy();
void slotDocumentSaved();
void slotSaveIncremental();
void slotSaveIncrementalBackup();
void showStatusBar(bool toggled);
void toggleTabletLogger();
void openResourcesDirectory();
void initializeStatusBarVisibility();
void guiUpdateTimeout();
void changeAuthorProfile(const QString &profileName);
void slotUpdateAuthorProfileActions();
void slotSaveShowRulersState(bool value);
void slotSaveRulersTrackMouseState(bool value);
private:
void createActions();
void setupManagers();
/// The zoommanager handles everything action-related to zooming
KisZoomManager * zoomManager();
private:
class KisViewManagerPrivate;
KisViewManagerPrivate * const d;
};
#endif
diff --git a/libs/ui/canvas/kis_abstract_canvas_widget.h b/libs/ui/canvas/kis_abstract_canvas_widget.h
index 15ef84ed3b..46e2cb4401 100644
--- a/libs/ui/canvas/kis_abstract_canvas_widget.h
+++ b/libs/ui/canvas/kis_abstract_canvas_widget.h
@@ -1,91 +1,92 @@
/*
* Copyright (C) 2007 Boudewijn Rempt <boud@valdyas.org>, (C)
* Copyright (C) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_ABSTRACT_CANVAS_WIDGET_
#define _KIS_ABSTRACT_CANVAS_WIDGET_
class QWidget;
class QRect;
class QPainter;
class QRect;
class KoToolProxy;
#include <kis_canvas_decoration.h>
class KisDisplayFilter;
class KisDisplayColorConverter;
class QBitArray;
#include "kis_types.h"
#include "kis_ui_types.h"
class KisAbstractCanvasWidget
{
public:
KisAbstractCanvasWidget() {}
virtual ~KisAbstractCanvasWidget() {}
virtual QWidget * widget() = 0;
virtual KoToolProxy * toolProxy() const = 0;
/// Draw the specified decorations on the view.
virtual void drawDecorations(QPainter & gc, const QRect &updateWidgetRect) const = 0;
virtual void addDecoration(KisCanvasDecorationSP deco) = 0;
+ virtual void removeDecoration(const QString& id) = 0;
virtual KisCanvasDecorationSP decoration(const QString& id) const = 0;
virtual void setDecorations(const QList<KisCanvasDecorationSP> &) = 0;
virtual QList<KisCanvasDecorationSP> decorations() const = 0;
/// set the specified display filter on the canvas
virtual void setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter) = 0;
virtual void setWrapAroundViewingMode(bool value) = 0;
// Called from KisCanvas2::channelSelectionChanged
virtual void channelSelectionChanged(const QBitArray &channelFlags) = 0;
// Called from KisCanvas2::slotSetDisplayProfile
virtual void setDisplayProfile(KisDisplayColorConverter *colorConverter) = 0;
// Called from KisCanvas2::finishResizingImage
virtual void finishResizingImage(qint32 w, qint32 h) = 0;
// Called from KisCanvas2::startUpdateProjection
virtual KisUpdateInfoSP startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) = 0;
// Called from KisCanvas2::updateCanvasProjection
virtual QRect updateCanvasProjection(KisUpdateInfoSP info) = 0;
/**
* Returns true if the asynchromous engine of the canvas
* (e.g. openGL pipeline) is busy with processing of the previous
* update events. This will make KisCanvas2 to postpone and
* compress update events.
*/
virtual bool isBusy() const = 0;
};
#endif // _KIS_ABSTRACT_CANVAS_WIDGET_
diff --git a/libs/ui/canvas/kis_animation_player.cpp b/libs/ui/canvas/kis_animation_player.cpp
index ec03f40900..e23c59450f 100644
--- a/libs/ui/canvas/kis_animation_player.cpp
+++ b/libs/ui/canvas/kis_animation_player.cpp
@@ -1,552 +1,544 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen <joupent@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_animation_player.h"
#include <QElapsedTimer>
#include <QTimer>
#include <QtMath>
//#define PLAYER_DEBUG_FRAMERATE
#include "kis_global.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_image.h"
#include "kis_canvas2.h"
#include "kis_animation_frame_cache.h"
#include "kis_signal_auto_connection.h"
#include "kis_image_animation_interface.h"
#include "kis_time_range.h"
#include "kis_signal_compressor.h"
#include <KisDocument.h>
#include <QFileInfo>
-
-#include <boost/accumulators/accumulators.hpp>
-#include <boost/accumulators/statistics/stats.hpp>
-#include <boost/accumulators/statistics/rolling_mean.hpp>
-
#include "KisSyncedAudioPlayback.h"
#include "kis_signal_compressor_with_param.h"
#include "KisViewManager.h"
#include "kis_icon_utils.h"
#include "KisPart.h"
#include "dialogs/KisAsyncAnimationCacheRenderDialog.h"
-
-
-using namespace boost::accumulators;
-typedef accumulator_set<qreal, stats<tag::rolling_mean> > FpsAccumulator;
+#include "KisRollingMeanAccumulatorWrapper.h"
struct KisAnimationPlayer::Private
{
public:
Private(KisAnimationPlayer *_q)
: q(_q),
- realFpsAccumulator(tag::rolling_window::window_size = 24),
- droppedFpsAccumulator(tag::rolling_window::window_size = 24),
- droppedFramesPortion(tag::rolling_window::window_size = 24),
+ realFpsAccumulator(24),
+ droppedFpsAccumulator(24),
+ droppedFramesPortion(24),
dropFramesMode(true),
nextFrameExpectedTime(0),
expectedInterval(0),
expectedFrame(0),
lastTimerInterval(0),
lastPaintedFrame(0),
playbackStatisticsCompressor(1000, KisSignalCompressor::FIRST_INACTIVE),
stopAudioOnScrubbingCompressor(100, KisSignalCompressor::POSTPONE),
audioOffsetTolerance(-1)
{}
KisAnimationPlayer *q;
bool useFastFrameUpload;
bool playing;
QTimer *timer;
int initialFrame;
int firstFrame;
int lastFrame;
qreal playbackSpeed;
KisCanvas2 *canvas;
KisSignalAutoConnectionsStore cancelStrokeConnections;
QElapsedTimer realFpsTimer;
- FpsAccumulator realFpsAccumulator;
- FpsAccumulator droppedFpsAccumulator;
- FpsAccumulator droppedFramesPortion;
+ KisRollingMeanAccumulatorWrapper realFpsAccumulator;
+ KisRollingMeanAccumulatorWrapper droppedFpsAccumulator;
+ KisRollingMeanAccumulatorWrapper droppedFramesPortion;
bool dropFramesMode;
QElapsedTimer playbackTime;
int nextFrameExpectedTime;
int expectedInterval;
int expectedFrame;
int lastTimerInterval;
int lastPaintedFrame;
KisSignalCompressor playbackStatisticsCompressor;
QScopedPointer<KisSyncedAudioPlayback> syncedAudio;
QScopedPointer<KisSignalCompressorWithParam<int> > audioSyncScrubbingCompressor;
KisSignalCompressor stopAudioOnScrubbingCompressor;
int audioOffsetTolerance;
void stopImpl(bool doUpdates);
int incFrame(int frame, int inc) {
frame += inc;
if (frame > lastFrame) {
frame = firstFrame + frame - lastFrame - 1;
}
return frame;
}
qint64 frameToMSec(int value, int fps) {
return qreal(value) / fps * 1000.0;
}
int msecToFrame(qint64 value, int fps) {
return qreal(value) * fps / 1000.0;
}
};
KisAnimationPlayer::KisAnimationPlayer(KisCanvas2 *canvas)
: QObject(canvas)
, m_d(new Private(this))
{
m_d->useFastFrameUpload = false;
m_d->playing = false;
m_d->canvas = canvas;
m_d->playbackSpeed = 1.0;
m_d->timer = new QTimer(this);
connect(m_d->timer, SIGNAL(timeout()), this, SLOT(slotUpdate()));
m_d->timer->setSingleShot(true);
connect(KisConfigNotifier::instance(),
SIGNAL(dropFramesModeChanged()),
SLOT(slotUpdateDropFramesMode()));
slotUpdateDropFramesMode();
connect(&m_d->playbackStatisticsCompressor, SIGNAL(timeout()),
this, SIGNAL(sigPlaybackStatisticsUpdated()));
using namespace std::placeholders;
std::function<void (int)> callback(
std::bind(&KisAnimationPlayer::slotSyncScrubbingAudio, this, _1));
const int defaultScrubbingUdpatesDelay = 40; /* 40 ms == 25 fps */
m_d->audioSyncScrubbingCompressor.reset(
new KisSignalCompressorWithParam<int>(defaultScrubbingUdpatesDelay, callback, KisSignalCompressor::FIRST_ACTIVE));
m_d->stopAudioOnScrubbingCompressor.setDelay(defaultScrubbingUdpatesDelay);
connect(&m_d->stopAudioOnScrubbingCompressor, SIGNAL(timeout()), SLOT(slotTryStopScrubbingAudio()));
connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()), SLOT(slotUpdateAudioChunkLength()));
slotUpdateAudioChunkLength();
connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigAudioChannelChanged()), SLOT(slotAudioChannelChanged()));
connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SLOT(slotAudioVolumeChanged()));
slotAudioChannelChanged();
}
KisAnimationPlayer::~KisAnimationPlayer()
{}
void KisAnimationPlayer::slotUpdateDropFramesMode()
{
KisConfig cfg;
m_d->dropFramesMode = cfg.animationDropFrames();
}
void KisAnimationPlayer::slotSyncScrubbingAudio(int msecTime)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->syncedAudio);
if (!m_d->syncedAudio->isPlaying()) {
m_d->syncedAudio->play(msecTime);
} else {
m_d->syncedAudio->syncWithVideo(msecTime);
}
if (!isPlaying()) {
m_d->stopAudioOnScrubbingCompressor.start();
}
}
void KisAnimationPlayer::slotTryStopScrubbingAudio()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->syncedAudio);
if (m_d->syncedAudio && !isPlaying()) {
m_d->syncedAudio->stop();
}
}
void KisAnimationPlayer::slotAudioChannelChanged()
{
KisImageAnimationInterface *interface = m_d->canvas->image()->animationInterface();
QString fileName = interface->audioChannelFileName();
QFileInfo info(fileName);
if (info.exists() && !interface->isAudioMuted()) {
m_d->syncedAudio.reset(new KisSyncedAudioPlayback(info.absoluteFilePath()));
m_d->syncedAudio->setVolume(interface->audioVolume());
m_d->syncedAudio->setSoundOffsetTolerance(m_d->audioOffsetTolerance);
connect(m_d->syncedAudio.data(), SIGNAL(error(const QString &, const QString &)), SLOT(slotOnAudioError(const QString &, const QString &)));
} else {
m_d->syncedAudio.reset();
}
}
void KisAnimationPlayer::slotAudioVolumeChanged()
{
KisImageAnimationInterface *interface = m_d->canvas->image()->animationInterface();
if (m_d->syncedAudio) {
m_d->syncedAudio->setVolume(interface->audioVolume());
}
}
void KisAnimationPlayer::slotOnAudioError(const QString &fileName, const QString &message)
{
QString errorMessage(i18nc("floating on-canvas message", "Cannot open audio: \"%1\"\nError: %2", fileName, message));
m_d->canvas->viewManager()->showFloatingMessage(errorMessage, KisIconUtils::loadIcon("warning"));
}
void KisAnimationPlayer::connectCancelSignals()
{
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image().data(), SIGNAL(sigUndoDuringStrokeRequested()),
this, SLOT(slotCancelPlayback()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image().data(), SIGNAL(sigStrokeCancellationRequested()),
this, SLOT(slotCancelPlayback()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image().data(), SIGNAL(sigStrokeEndRequested()),
this, SLOT(slotCancelPlaybackSafe()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()),
this, SLOT(slotUpdatePlaybackTimer()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image()->animationInterface(), SIGNAL(sigFullClipRangeChanged()),
this, SLOT(slotUpdatePlaybackTimer()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image()->animationInterface(), SIGNAL(sigPlaybackRangeChanged()),
this, SLOT(slotUpdatePlaybackTimer()));
}
void KisAnimationPlayer::disconnectCancelSignals()
{
m_d->cancelStrokeConnections.clear();
}
void KisAnimationPlayer::slotUpdateAudioChunkLength()
{
const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface();
const int animationFramePeriod = qMax(1, 1000 / animation->framerate());
KisConfig cfg;
int scrubbingAudioUdpatesDelay = cfg.scrubbingAudioUpdatesDelay();
if (scrubbingAudioUdpatesDelay < 0) {
scrubbingAudioUdpatesDelay = qMax(1, animationFramePeriod);
}
m_d->audioSyncScrubbingCompressor->setDelay(scrubbingAudioUdpatesDelay);
m_d->stopAudioOnScrubbingCompressor.setDelay(scrubbingAudioUdpatesDelay);
m_d->audioOffsetTolerance = cfg.audioOffsetTolerance();
if (m_d->audioOffsetTolerance < 0) {
m_d->audioOffsetTolerance = animationFramePeriod;
}
if (m_d->syncedAudio) {
m_d->syncedAudio->setSoundOffsetTolerance(m_d->audioOffsetTolerance);
}
}
void KisAnimationPlayer::slotUpdatePlaybackTimer()
{
m_d->timer->stop();
const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface();
const KisTimeRange &range = animation->playbackRange();
if (!range.isValid()) return;
const int fps = animation->framerate();
m_d->initialFrame = animation->currentUITime();
m_d->firstFrame = range.start();
m_d->lastFrame = range.end();
m_d->expectedFrame = qBound(m_d->firstFrame, m_d->expectedFrame, m_d->lastFrame);
m_d->expectedInterval = qreal(1000) / fps / m_d->playbackSpeed;
m_d->lastTimerInterval = m_d->expectedInterval;
if (m_d->syncedAudio) {
m_d->syncedAudio->setSpeed(m_d->playbackSpeed);
}
m_d->timer->start(m_d->expectedInterval);
if (m_d->playbackTime.isValid()) {
m_d->playbackTime.restart();
} else {
m_d->playbackTime.start();
}
m_d->nextFrameExpectedTime = m_d->playbackTime.elapsed() + m_d->expectedInterval;
}
void KisAnimationPlayer::play()
{
{
const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface();
const KisTimeRange &range = animation->playbackRange();
if (!range.isValid()) return;
// when openGL is disabled, there is no animation cache
if (m_d->canvas->frameCache()) {
KisAsyncAnimationCacheRenderDialog dlg(m_d->canvas->frameCache(),
range,
200);
KisAsyncAnimationCacheRenderDialog::Result result =
dlg.regenerateRange(m_d->canvas->viewManager());
if (result != KisAsyncAnimationCacheRenderDialog::RenderComplete) {
return;
}
}
}
m_d->playing = true;
slotUpdatePlaybackTimer();
m_d->expectedFrame = m_d->firstFrame;
m_d->lastPaintedFrame = m_d->firstFrame;
connectCancelSignals();
if (m_d->syncedAudio) {
KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface();
m_d->syncedAudio->play(m_d->frameToMSec(m_d->firstFrame, animationInterface->framerate()));
}
}
void KisAnimationPlayer::Private::stopImpl(bool doUpdates)
{
if (syncedAudio) {
syncedAudio->stop();
}
q->disconnectCancelSignals();
timer->stop();
playing = false;
if (doUpdates) {
KisImageAnimationInterface *animation = canvas->image()->animationInterface();
if (animation->currentUITime() == initialFrame) {
canvas->refetchDataFromImage();
} else {
animation->switchCurrentTimeAsync(initialFrame);
}
}
emit q->sigPlaybackStopped();
}
void KisAnimationPlayer::stop()
{
m_d->stopImpl(true);
}
void KisAnimationPlayer::forcedStopOnExit()
{
m_d->stopImpl(false);
}
bool KisAnimationPlayer::isPlaying()
{
return m_d->playing;
}
int KisAnimationPlayer::currentTime()
{
return m_d->lastPaintedFrame;
}
void KisAnimationPlayer::displayFrame(int time)
{
uploadFrame(time);
}
void KisAnimationPlayer::slotUpdate()
{
uploadFrame(-1);
}
void KisAnimationPlayer::uploadFrame(int frame)
{
KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface();
if (frame < 0) {
const int currentTime = m_d->playbackTime.elapsed();
const int framesDiff = currentTime - m_d->nextFrameExpectedTime;
const qreal framesDiffNorm = qreal(framesDiff) / m_d->expectedInterval;
// qDebug() << ppVar(framesDiff)
// << ppVar(m_d->expectedFrame)
// << ppVar(framesDiffNorm)
// << ppVar(m_d->lastTimerInterval);
if (m_d->dropFramesMode) {
const int numDroppedFrames = qMax(0, qRound(framesDiffNorm));
frame = m_d->incFrame(m_d->expectedFrame, numDroppedFrames);
} else {
frame = m_d->expectedFrame;
}
m_d->nextFrameExpectedTime = currentTime + m_d->expectedInterval;
m_d->lastTimerInterval = qMax(0.0, m_d->lastTimerInterval - 0.5 * framesDiff);
m_d->expectedFrame = m_d->incFrame(frame, 1);
m_d->timer->start(m_d->lastTimerInterval);
m_d->playbackStatisticsCompressor.start();
}
if (m_d->syncedAudio) {
const int msecTime = m_d->frameToMSec(frame, animationInterface->framerate());
if (isPlaying()) {
slotSyncScrubbingAudio(msecTime);
} else {
m_d->audioSyncScrubbingCompressor->start(msecTime);
}
}
if (m_d->canvas->frameCache() && m_d->canvas->frameCache()->uploadFrame(frame)) {
m_d->canvas->updateCanvas();
m_d->useFastFrameUpload = true;
emit sigFrameChanged();
} else {
m_d->useFastFrameUpload = false;
m_d->canvas->image()->barrierLock(true);
m_d->canvas->image()->unlock();
// no OpenGL cache or the frame just not cached yet
animationInterface->switchCurrentTimeAsync(frame);
emit sigFrameChanged();
}
if (!m_d->realFpsTimer.isValid()) {
m_d->realFpsTimer.start();
} else {
const int elapsed = m_d->realFpsTimer.restart();
m_d->realFpsAccumulator(elapsed);
int numFrames = frame - m_d->lastPaintedFrame;
if (numFrames < 0) {
numFrames += m_d->lastFrame - m_d->firstFrame + 1;
}
m_d->droppedFramesPortion(qreal(int(numFrames != 1)));
if (numFrames > 0) {
m_d->droppedFpsAccumulator(qreal(elapsed) / numFrames);
}
#ifdef PLAYER_DEBUG_FRAMERATE
- qDebug() << " RFPS:" << 1000.0 / rolling_mean(m_d->realFpsAccumulator)
- << "DFPS:" << 1000.0 / rolling_mean(m_d->droppedFpsAccumulator) << ppVar(numFrames);
+ 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;
}
qreal KisAnimationPlayer::effectiveFps() const
{
- return 1000.0 / rolling_mean(m_d->droppedFpsAccumulator);
+ return 1000.0 / m_d->droppedFpsAccumulator.rollingMean();
}
qreal KisAnimationPlayer::realFps() const
{
- return 1000.0 / rolling_mean(m_d->realFpsAccumulator);
+ return 1000.0 / m_d->realFpsAccumulator.rollingMean();
}
qreal KisAnimationPlayer::framesDroppedPortion() const
{
- return rolling_mean(m_d->droppedFramesPortion);
+ 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) {
slotUpdatePlaybackTimer();
}
}
diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp
index d06f36791e..ed546469ad 100644
--- a/libs/ui/canvas/kis_canvas2.cpp
+++ b/libs/ui/canvas/kis_canvas2.cpp
@@ -1,1014 +1,1043 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) Lukáš Tvrdý <lukast.dev@gmail.com>, (C) 2010
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* 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_canvas2.h"
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QTime>
#include <QLabel>
#include <QMouseEvent>
#include <QDesktopWidget>
#include <kis_debug.h>
#include <KoUnit.h>
#include <KoShapeManager.h>
#include <KisSelectedShapesProxy.h>
#include <KoColorProfile.h>
#include <KoCanvasControllerWidget.h>
#include <KisDocument.h>
#include <KoSelection.h>
#include <KoShapeController.h>
#include <kis_lod_transform.h>
#include "kis_tool_proxy.h"
#include "kis_coordinates_converter.h"
#include "kis_prescaled_projection.h"
#include "kis_image.h"
#include "kis_image_barrier_locker.h"
#include "kis_undo_adapter.h"
#include "KisDocument.h"
#include "flake/kis_shape_layer.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_abstract_canvas_widget.h"
#include "kis_qpainter_canvas.h"
#include "kis_group_layer.h"
#include "flake/kis_shape_controller.h"
#include "kis_node_manager.h"
#include "kis_selection.h"
#include "kis_selection_component.h"
#include "flake/kis_shape_selection.h"
#include "kis_image_config.h"
#include "kis_infinity_manager.h"
#include "kis_signal_compressor.h"
#include "kis_display_color_converter.h"
#include "kis_exposure_gamma_correction_interface.h"
#include "KisView.h"
#include "kis_canvas_controller.h"
#include "kis_grid_config.h"
#include "kis_animation_player.h"
#include "kis_animation_frame_cache.h"
#include "opengl/kis_opengl_canvas2.h"
#include "opengl/kis_opengl.h"
#include "kis_fps_decoration.h"
#include "KoColorConversionTransformation.h"
#include "KisProofingConfiguration.h"
#include <kis_favorite_resource_manager.h>
#include <kis_popup_palette.h>
#include "input/kis_input_manager.h"
#include "kis_painting_assistants_decoration.h"
#include "kis_canvas_updates_compressor.h"
#include "KoZoomController.h"
+#include <KisStrokeSpeedMonitor.h>
+#include "opengl/kis_opengl_canvas_debugger.h"
class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private
{
public:
KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer<KisView> view, KoCanvasResourceManager* resourceManager)
: coordinatesConverter(coordConverter)
, view(view)
, shapeManager(parent)
, selectedShapesProxy(&shapeManager)
, toolProxy(parent)
, displayColorConverter(resourceManager, view)
{
}
KisCoordinatesConverter *coordinatesConverter;
QPointer<KisView>view;
KisAbstractCanvasWidget *canvasWidget = 0;
KoShapeManager shapeManager;
KisSelectedShapesProxy selectedShapesProxy;
bool currentCanvasIsOpenGL;
int openGLFilterMode;
KisToolProxy toolProxy;
KisPrescaledProjectionSP prescaledProjection;
bool vastScrolling;
KisSignalCompressor updateSignalCompressor;
QRect savedUpdateRect;
QBitArray channelFlags;
KisProofingConfigurationSP proofingConfig;
bool softProofing = false;
bool gamutCheck = false;
bool proofingConfigUpdated = false;
KisPopupPalette *popupPalette = 0;
KisDisplayColorConverter displayColorConverter;
KisCanvasUpdatesCompressor projectionUpdatesCompressor;
KisAnimationPlayer *animationPlayer;
KisAnimationFrameCacheSP frameCache;
bool lodAllowedInCanvas;
bool bootstrapLodBlocked;
QPointer<KoShapeManager> currentlyActiveShapeManager;
bool effectiveLodAllowedInCanvas() {
return lodAllowedInCanvas && !bootstrapLodBlocked;
}
};
namespace {
KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node)
{
KoShapeManager *shapeManager = 0;
KisLayer *layer = dynamic_cast<KisLayer*>(node.data());
if (layer) {
KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(layer);
if (shapeLayer) {
shapeManager = shapeLayer->shapeManager();
} else {
KisSelectionSP selection = layer->selection();
if (selection && selection->hasShapeSelection()) {
KisShapeSelection *shapeSelection = dynamic_cast<KisShapeSelection*>(selection->shapeSelection());
KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0);
shapeManager = shapeSelection->shapeManager();
}
}
}
return shapeManager;
}
}
KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc)
: KoCanvasBase(sc, resourceManager)
, m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager))
{
/**
* While loading LoD should be blocked. Only when GUI has finished
* loading and zoom level settled down, LoD is given a green
* light.
*/
m_d->bootstrapLodBlocked = true;
connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished()));
- m_d->updateSignalCompressor.setDelay(10);
+ KisImageConfig config;
+
+ m_d->updateSignalCompressor.setDelay(1000 / config.fpsLimit());
m_d->updateSignalCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE);
}
void KisCanvas2::setup()
{
// a bit of duplication from slotConfigChanged()
KisConfig cfg;
m_d->vastScrolling = cfg.vastScrolling();
m_d->lodAllowedInCanvas = cfg.levelOfDetailEnabled();
createCanvas(cfg.useOpenGL());
setLodAllowedInCanvas(m_d->lodAllowedInCanvas);
m_d->animationPlayer = new KisAnimationPlayer(this);
connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
/**
* We switch the shape manager every time vector layer or
* shape selection is activated. Flake does not expect this
* and connects all the signals of the global shape manager
* to the clients in the constructor. To workaround this we
* forward the signals of local shape managers stored in the
* vector layers to the signals of global shape manager. So the
* sequence of signal deliveries is the following:
*
* shapeLayer.m_d.canvas.m_shapeManager.selection() ->
* shapeLayer ->
* shapeController ->
* globalShapeManager.selection()
*/
KisShapeController *kritaShapeController = static_cast<KisShapeController*>(shapeController()->documentBase());
connect(kritaShapeController, SIGNAL(selectionChanged()),
this, SLOT(slotSelectionChanged()));
connect(kritaShapeController, SIGNAL(selectionContentChanged()),
globalShapeManager(), SIGNAL(selectionContentChanged()));
connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)),
globalShapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*)));
connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate()));
+
+ initializeFpsDecoration();
+}
+
+void KisCanvas2::initializeFpsDecoration()
+{
+ KisConfig cfg;
+
+ const bool shouldShowDebugOverlay =
+ (canvasIsOpenGL() && cfg.enableOpenGLFramerateLogging()) ||
+ cfg.enableBrushSpeedLogging();
+
+ if (shouldShowDebugOverlay && !decoration(KisFpsDecoration::idTag)) {
+ addDecoration(new KisFpsDecoration(imageView()));
+
+ if (cfg.enableBrushSpeedLogging()) {
+ connect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas()));
+ }
+ } else if (!shouldShowDebugOverlay && decoration(KisFpsDecoration::idTag)) {
+ m_d->canvasWidget->removeDecoration(KisFpsDecoration::idTag);
+ disconnect(KisStrokeSpeedMonitor::instance(), SIGNAL(sigStatsUpdated()), this, SLOT(updateCanvas()));
+ }
}
KisCanvas2::~KisCanvas2()
{
if (m_d->animationPlayer->isPlaying()) {
m_d->animationPlayer->forcedStopOnExit();
}
delete m_d;
}
void KisCanvas2::setCanvasWidget(QWidget * widget)
{
KisAbstractCanvasWidget *tmp = dynamic_cast<KisAbstractCanvasWidget*>(widget);
Q_ASSERT_X(tmp, "setCanvasWidget", "Cannot cast the widget to a KisAbstractCanvasWidget");
if (m_d->popupPalette) {
m_d->popupPalette->setParent(widget);
}
if(m_d->canvasWidget != 0)
{
tmp->setDecorations(m_d->canvasWidget->decorations());
// Redundant check for the constructor case, see below
if(viewManager() != 0)
viewManager()->inputManager()->removeTrackedCanvas(this);
}
m_d->canvasWidget = tmp;
// Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView
// constructor, so the view manager still doesn't exists.
if(m_d->canvasWidget != 0 && viewManager() != 0)
viewManager()->inputManager()->addTrackedCanvas(this);
if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) {
KisInfinityManager *manager = new KisInfinityManager(m_d->view, this);
manager->setVisible(true);
m_d->canvasWidget->addDecoration(manager);
}
widget->setAutoFillBackground(false);
widget->setAttribute(Qt::WA_OpaquePaintEvent);
widget->setMouseTracking(true);
widget->setAcceptDrops(true);
KoCanvasControllerWidget *controller = dynamic_cast<KoCanvasControllerWidget*>(canvasController());
if (controller) {
Q_ASSERT(controller->canvas() == this);
controller->changeCanvasWidget(widget);
}
}
bool KisCanvas2::canvasIsOpenGL() const
{
return m_d->currentCanvasIsOpenGL;
}
KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const
{
return KisOpenGL::FilterMode(m_d->openGLFilterMode);
}
void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const
{
QTransform transform = coordinatesConverter()->imageToDocumentTransform();
const QPoint intSpacing = m_d->view->document()->gridConfig().spacing();
const QPoint intOffset = m_d->view->document()->gridConfig().offset();
QPointF size = transform.map(QPointF(intSpacing));
spacing->rwidth() = size.x();
spacing->rheight() = size.y();
*offset = transform.map(QPointF(intOffset));
}
bool KisCanvas2::snapToGrid() const
{
return m_d->view->document()->gridConfig().snapToGrid();
}
qreal KisCanvas2::rotationAngle() const
{
return m_d->coordinatesConverter->rotationAngle();
}
bool KisCanvas2::xAxisMirrored() const
{
return m_d->coordinatesConverter->xAxisMirrored();
}
bool KisCanvas2::yAxisMirrored() const
{
return m_d->coordinatesConverter->yAxisMirrored();
}
void KisCanvas2::channelSelectionChanged()
{
KisImageSP image = this->image();
m_d->channelFlags = image->rootLayer()->channelFlags();
m_d->view->viewManager()->blockUntilOperationsFinishedForced(image);
image->barrierLock();
m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags);
startUpdateInPatches(image->bounds());
image->unlock();
}
void KisCanvas2::addCommand(KUndo2Command *command)
{
// This method exists to support flake-related operations
m_d->view->document()->addCommand(command);
}
KoShapeManager* KisCanvas2::shapeManager() const
{
KisNodeSP node = m_d->view->currentNode();
KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node);
// sanity check for consistency of the local shape manager
KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) {
localShapeManager = globalShapeManager();
}
return localShapeManager ? localShapeManager : globalShapeManager();
}
KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const
{
return &m_d->selectedShapesProxy;
}
KoShapeManager* KisCanvas2::globalShapeManager() const
{
return &m_d->shapeManager;
}
void KisCanvas2::updateInputMethodInfo()
{
// TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget...
}
const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const
{
return m_d->coordinatesConverter;
}
KoViewConverter* KisCanvas2::viewConverter() const
{
return m_d->coordinatesConverter;
}
KisInputManager* KisCanvas2::globalInputManager() const
{
return m_d->view->globalInputManager();
}
QWidget* KisCanvas2::canvasWidget()
{
return m_d->canvasWidget->widget();
}
const QWidget* KisCanvas2::canvasWidget() const
{
return m_d->canvasWidget->widget();
}
KoUnit KisCanvas2::unit() const
{
KoUnit unit(KoUnit::Pixel);
KisImageWSP image = m_d->view->image();
if (image) {
if (!qFuzzyCompare(image->xRes(), image->yRes())) {
warnKrita << "WARNING: resolution of the image is anisotropic"
<< ppVar(image->xRes())
<< ppVar(image->yRes());
}
const qreal resolution = image->xRes();
unit.setFactor(resolution);
}
return unit;
}
KoToolProxy * KisCanvas2::toolProxy() const
{
return &m_d->toolProxy;
}
void KisCanvas2::createQPainterCanvas()
{
m_d->currentCanvasIsOpenGL = false;
KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view);
m_d->prescaledProjection = new KisPrescaledProjection();
m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter);
m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(),
m_d->displayColorConverter.renderingIntent(),
m_d->displayColorConverter.conversionFlags());
m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter());
canvasWidget->setPrescaledProjection(m_d->prescaledProjection);
setCanvasWidget(canvasWidget);
}
void KisCanvas2::createOpenGLCanvas()
{
KisConfig cfg;
m_d->openGLFilterMode = cfg.openGLFilteringMode();
m_d->currentCanvasIsOpenGL = true;
KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter);
m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures());
setCanvasWidget(canvasWidget);
-
- if (canvasWidget->needsFpsDebugging() && !decoration(KisFpsDecoration::idTag)) {
- addDecoration(new KisFpsDecoration(imageView()));
- }
}
void KisCanvas2::createCanvas(bool useOpenGL)
{
// deinitialize previous canvas structures
m_d->prescaledProjection = 0;
m_d->frameCache = 0;
KisConfig cfg;
QDesktopWidget dw;
const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView()));
m_d->displayColorConverter.setMonitorProfile(profile);
if (useOpenGL) {
if (KisOpenGL::hasOpenGL()) {
createOpenGLCanvas();
if (cfg.canvasState() == "OPENGL_FAILED") {
// Creating the opengl canvas failed, fall back
warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter.";
createQPainterCanvas();
}
} else {
warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n";
createQPainterCanvas();
}
}
else {
createQPainterCanvas();
}
if (m_d->popupPalette) {
m_d->popupPalette->setParent(m_d->canvasWidget->widget());
}
}
void KisCanvas2::initializeImage()
{
KisImageSP image = m_d->view->image();
m_d->coordinatesConverter->setImage(image);
m_d->toolProxy.initializeImage(image);
connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection);
connect(this, SIGNAL(sigCanvasCacheUpdated()), SLOT(updateCanvasProjection()));
connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig()));
connect(image, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), SLOT(startResizingImage()), Qt::DirectConnection);
connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32)));
connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager()));
connectCurrentCanvas();
}
void KisCanvas2::connectCurrentCanvas()
{
KisImageWSP image = m_d->view->image();
if (!m_d->currentCanvasIsOpenGL) {
Q_ASSERT(m_d->prescaledProjection);
m_d->prescaledProjection->setImage(image);
}
startResizingImage();
emit imageChanged(image);
setLodAllowedInCanvas(m_d->lodAllowedInCanvas);
}
void KisCanvas2::resetCanvas(bool useOpenGL)
{
// we cannot reset the canvas before it's created, but this method might be called,
// for instance when setting the monitor profile.
if (!m_d->canvasWidget) {
return;
}
KisConfig cfg;
bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) ||
(m_d->currentCanvasIsOpenGL &&
m_d->openGLFilterMode != cfg.openGLFilteringMode());
if (needReset) {
createCanvas(useOpenGL);
connectCurrentCanvas();
notifyZoomChanged();
}
updateCanvasWidgetImpl();
}
void KisCanvas2::startUpdateInPatches(const QRect &imageRect)
{
if (m_d->currentCanvasIsOpenGL) {
startUpdateCanvasProjection(imageRect);
} else {
KisImageConfig imageConfig;
int patchWidth = imageConfig.updatePatchWidth();
int patchHeight = imageConfig.updatePatchHeight();
for (int y = 0; y < imageRect.height(); y += patchHeight) {
for (int x = 0; x < imageRect.width(); x += patchWidth) {
QRect patchRect(x, y, patchWidth, patchHeight);
startUpdateCanvasProjection(patchRect);
}
}
}
}
void KisCanvas2::setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)
{
m_d->displayColorConverter.setDisplayFilter(displayFilter);
KisImageSP image = this->image();
m_d->view->viewManager()->blockUntilOperationsFinishedForced(image);
image->barrierLock();
m_d->canvasWidget->setDisplayFilter(displayFilter);
image->unlock();
}
QSharedPointer<KisDisplayFilter> KisCanvas2::displayFilter() const
{
return m_d->displayColorConverter.displayFilter();
}
KisDisplayColorConverter* KisCanvas2::displayColorConverter() const
{
return &m_d->displayColorConverter;
}
KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const
{
QSharedPointer<KisDisplayFilter> displayFilter = m_d->displayColorConverter.displayFilter();
return displayFilter ?
displayFilter->correctionInterface() :
KisDumbExposureGammaCorrectionInterface::instance();
}
void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck)
{
m_d->proofingConfig = this->image()->proofingConfiguration();
if (!m_d->proofingConfig) {
qDebug()<<"Canvas: No proofing config found, generating one.";
KisImageConfig cfg;
m_d->proofingConfig = cfg.defaultProofingconfiguration();
}
KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags;
-
- if (softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) {
+#if QT_VERSION >= 0x07000
+ if (this->image()->colorSpace()->colorDepthId().id().contains("U")) {
+ conversionFlags.setFlag(KoColorConversionTransformation::SoftProofing, softProof);
+ if (softProof) {
+ conversionFlags.setFlag(KoColorConversionTransformation::GamutCheck, gamutCheck);
+ }
+ }
+#else
+ if (this->image()->colorSpace()->colorDepthId().id().contains("U")) {
conversionFlags |= KoColorConversionTransformation::SoftProofing;
} else {
- conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing;
+ conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing;
}
if (gamutCheck && softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) {
conversionFlags |= KoColorConversionTransformation::GamutCheck;
} else {
conversionFlags = conversionFlags & ~KoColorConversionTransformation::GamutCheck;
}
+#endif;
m_d->proofingConfig->conversionFlags = conversionFlags;
m_d->proofingConfigUpdated = true;
startUpdateInPatches(this->image()->bounds());
}
void KisCanvas2::slotSoftProofing(bool softProofing)
{
m_d->softProofing = softProofing;
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::slotGamutCheck(bool gamutCheck)
{
m_d->gamutCheck = gamutCheck;
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::slotChangeProofingConfig()
{
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::setProofingConfigUpdated(bool updated)
{
m_d->proofingConfigUpdated = updated;
}
bool KisCanvas2::proofingConfigUpdated()
{
return m_d->proofingConfigUpdated;
}
KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const
{
if (!m_d->proofingConfig) {
m_d->proofingConfig = this->image()->proofingConfiguration();
if (!m_d->proofingConfig) {
- qDebug()<<"Canvas: No proofing config found, generating one.";
- KisImageConfig cfg;
- m_d->proofingConfig = cfg.defaultProofingconfiguration();
+ m_d->proofingConfig = KisImageConfig().defaultProofingconfiguration();
}
}
return m_d->proofingConfig;
}
void KisCanvas2::startResizingImage()
{
KisImageWSP image = this->image();
qint32 w = image->width();
qint32 h = image->height();
emit sigContinueResizeImage(w, h);
QRect imageBounds(0, 0, w, h);
startUpdateInPatches(imageBounds);
}
void KisCanvas2::finishResizingImage(qint32 w, qint32 h)
{
m_d->canvasWidget->finishResizingImage(w, h);
}
void KisCanvas2::startUpdateCanvasProjection(const QRect & rc)
{
KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags);
if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) {
emit sigCanvasCacheUpdated();
}
}
void KisCanvas2::updateCanvasProjection()
{
while (KisUpdateInfoSP info = m_d->projectionUpdatesCompressor.takeUpdateInfo()) {
QRect vRect = m_d->canvasWidget->updateCanvasProjection(info);
if (!vRect.isEmpty()) {
updateCanvasWidgetImpl(m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect());
}
}
// TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas
if (m_d->currentCanvasIsOpenGL) {
updateCanvasWidgetImpl();
}
}
void KisCanvas2::slotDoCanvasUpdate()
{
if (m_d->canvasWidget->isBusy()) {
// just restarting the timer
updateCanvasWidgetImpl(m_d->savedUpdateRect);
return;
}
if (m_d->savedUpdateRect.isEmpty()) {
m_d->canvasWidget->widget()->update();
emit updateCanvasRequested(m_d->canvasWidget->widget()->rect());
} else {
emit updateCanvasRequested(m_d->savedUpdateRect);
m_d->canvasWidget->widget()->update(m_d->savedUpdateRect);
}
m_d->savedUpdateRect = QRect();
}
void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc)
{
if (!m_d->updateSignalCompressor.isActive() ||
!m_d->savedUpdateRect.isEmpty()) {
m_d->savedUpdateRect |= rc;
}
m_d->updateSignalCompressor.start();
}
void KisCanvas2::updateCanvas()
{
updateCanvasWidgetImpl();
}
void KisCanvas2::updateCanvas(const QRectF& documentRect)
{
if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) {
updateCanvasWidgetImpl();
}
else {
// updateCanvas is called from tools, never from the projection
// updates, so no need to prescale!
QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect();
widgetRect.adjust(-2, -2, 2, 2);
if (!widgetRect.isEmpty()) {
updateCanvasWidgetImpl(widgetRect);
}
}
}
void KisCanvas2::disconnectCanvasObserver(QObject *object)
{
KoCanvasBase::disconnectCanvasObserver(object);
m_d->view->disconnect(object);
}
void KisCanvas2::notifyZoomChanged()
{
if (!m_d->currentCanvasIsOpenGL) {
Q_ASSERT(m_d->prescaledProjection);
m_d->prescaledProjection->notifyZoomChanged();
}
notifyLevelOfDetailChange();
updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction
}
void KisCanvas2::slotTrySwitchShapeManager()
{
QPointer<KoShapeManager> oldManager = m_d->currentlyActiveShapeManager;
KisNodeSP node = m_d->view->currentNode();
QPointer<KoShapeManager> newManager;
newManager = fetchShapeManagerFromNode(node);
if (newManager != oldManager) {
m_d->currentlyActiveShapeManager = newManager;
m_d->selectedShapesProxy.setShapeManager(newManager);
}
}
void KisCanvas2::notifyLevelOfDetailChange()
{
if (!m_d->effectiveLodAllowedInCanvas()) return;
KisImageSP image = this->image();
const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom();
KisConfig cfg;
const int maxLod = cfg.numMipmapLevels();
int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod);
image->setDesiredLevelOfDetail(lod);
}
const KoColorProfile * KisCanvas2::monitorProfile()
{
return m_d->displayColorConverter.monitorProfile();
}
KisViewManager* KisCanvas2::viewManager() const
{
if (m_d->view) {
return m_d->view->viewManager();
}
return 0;
}
QPointer<KisView>KisCanvas2::imageView() const
{
return m_d->view;
}
KisImageWSP KisCanvas2::image() const
{
return m_d->view->image();
}
KisImageWSP KisCanvas2::currentImage() const
{
return m_d->view->image();
}
void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset)
{
QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
m_d->coordinatesConverter->setDocumentOffset(documentOffset);
QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
QPointF moveOffset = offsetAfter - offsetBefore;
if (!m_d->currentCanvasIsOpenGL)
m_d->prescaledProjection->viewportMoved(moveOffset);
emit documentOffsetUpdateFinished();
updateCanvas();
}
void KisCanvas2::slotConfigChanged()
{
KisConfig cfg;
m_d->vastScrolling = cfg.vastScrolling();
resetCanvas(cfg.useOpenGL());
slotSetDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget())));
+ initializeFpsDecoration();
}
void KisCanvas2::refetchDataFromImage()
{
KisImageSP image = this->image();
KisImageBarrierLocker l(image);
startUpdateInPatches(image->bounds());
}
void KisCanvas2::slotSetDisplayProfile(const KoColorProfile *monitorProfile)
{
if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return;
m_d->displayColorConverter.setMonitorProfile(monitorProfile);
{
KisImageSP image = this->image();
KisImageBarrierLocker l(image);
m_d->canvasWidget->setDisplayProfile(&m_d->displayColorConverter);
}
refetchDataFromImage();
}
void KisCanvas2::addDecoration(KisCanvasDecorationSP deco)
{
m_d->canvasWidget->addDecoration(deco);
}
KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const
{
return m_d->canvasWidget->decoration(id);
}
QPoint KisCanvas2::documentOrigin() const
{
/**
* In Krita we don't use document origin anymore.
* All the centering when needed (vastScrolling < 0.5) is done
* automatically by the KisCoordinatesConverter.
*/
return QPoint();
}
QPoint KisCanvas2::documentOffset() const
{
return m_d->coordinatesConverter->documentOffset();
}
void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager)
{
m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(),
m_d->view->resourceProvider(), m_d->canvasWidget->widget());
connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotZoomChanged(int)));
connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas()));
m_d->popupPalette->showPopupPalette(false);
}
void KisCanvas2::slotZoomChanged(int zoom ) {
m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom
notifyZoomChanged();
}
void KisCanvas2::setCursor(const QCursor &cursor)
{
canvasWidget()->setCursor(cursor);
}
KisAnimationFrameCacheSP KisCanvas2::frameCache() const
{
return m_d->frameCache;
}
KisAnimationPlayer *KisCanvas2::animationPlayer() const
{
return m_d->animationPlayer;
}
void KisCanvas2::slotSelectionChanged()
{
KisShapeLayer* shapeLayer = dynamic_cast<KisShapeLayer*>(viewManager()->activeLayer().data());
if (!shapeLayer) {
return;
}
m_d->shapeManager.selection()->deselectAll();
Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) {
m_d->shapeManager.selection()->select(shape);
}
}
bool KisCanvas2::isPopupPaletteVisible() const
{
if (!m_d->popupPalette) {
return false;
}
return m_d->popupPalette->isVisible();
}
void KisCanvas2::setWrapAroundViewingMode(bool value)
{
KisCanvasDecorationSP infinityDecoration =
m_d->canvasWidget->decoration(INFINITY_DECORATION_ID);
if (infinityDecoration) {
infinityDecoration->setVisible(!value);
}
m_d->canvasWidget->setWrapAroundViewingMode(value);
}
bool KisCanvas2::wrapAroundViewingMode() const
{
KisCanvasDecorationSP infinityDecoration =
m_d->canvasWidget->decoration(INFINITY_DECORATION_ID);
if (infinityDecoration) {
return !(infinityDecoration->visible());
}
return false;
}
void KisCanvas2::bootstrapFinished()
{
if (!m_d->bootstrapLodBlocked) return;
m_d->bootstrapLodBlocked = false;
setLodAllowedInCanvas(m_d->lodAllowedInCanvas);
}
void KisCanvas2::setLodAllowedInCanvas(bool value)
{
if (!KisOpenGL::supportsLoD()) {
qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support";
}
m_d->lodAllowedInCanvas =
value &&
m_d->currentCanvasIsOpenGL &&
KisOpenGL::supportsLoD() &&
(m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode ||
m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering);
KisImageSP image = this->image();
if (m_d->effectiveLodAllowedInCanvas() != !image->levelOfDetailBlocked()) {
image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInCanvas());
notifyLevelOfDetailChange();
}
KisConfig cfg;
cfg.setLevelOfDetailEnabled(m_d->lodAllowedInCanvas);
}
bool KisCanvas2::lodAllowedInCanvas() const
{
return m_d->lodAllowedInCanvas;
}
void KisCanvas2::slotShowPopupPalette(const QPoint &p)
{
if (!m_d->popupPalette) {
return;
}
m_d->popupPalette->showPopupPalette(p);
}
KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const
{
KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration");
return qobject_cast<KisPaintingAssistantsDecoration*>(deco.data());
}
diff --git a/libs/ui/canvas/kis_canvas2.h b/libs/ui/canvas/kis_canvas2.h
index 663c07c31e..ae5b270f27 100644
--- a/libs/ui/canvas/kis_canvas2.h
+++ b/libs/ui/canvas/kis_canvas2.h
@@ -1,303 +1,306 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* 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_H
#define KIS_CANVAS_H
#include <QObject>
#include <QWidget>
#include <QSize>
#include <QString>
#include <KoConfig.h>
#include <KoColorConversionTransformation.h>
#include <KoCanvasBase.h>
#include <kritaui_export.h>
#include <kis_types.h>
#include <KoPointerEvent.h>
#include "opengl/kis_opengl.h"
#include "kis_ui_types.h"
#include "kis_coordinates_converter.h"
#include "kis_canvas_decoration.h"
#include "kis_painting_assistants_decoration.h"
class KoToolProxy;
class KoColorProfile;
class KisViewManager;
class KisFavoriteResourceManager;
class KisDisplayFilter;
class KisDisplayColorConverter;
struct KisExposureGammaCorrectionInterface;
class KisView;
class KisInputManager;
class KisAnimationPlayer;
class KisShapeController;
class KisCoordinatesConverter;
class KoViewConverter;
/**
* KisCanvas2 is not an actual widget class, but rather an adapter for
* the widget it contains, which may be either a QPainter based
* canvas, or an OpenGL based canvas: that are the real widgets.
*/
class KRITAUI_EXPORT KisCanvas2 : public KoCanvasBase
{
Q_OBJECT
public:
/**
* Create a new canvas. The canvas manages a widget that will do
* the actual painting: the canvas itself is not a widget.
*
* @param viewConverter the viewconverter for converting between
* window and document coordinates.
*/
KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc);
~KisCanvas2() override;
void notifyZoomChanged();
void disconnectCanvasObserver(QObject *object) override;
public: // KoCanvasBase implementation
bool canvasIsOpenGL() const override;
KisOpenGL::FilterMode openGLFilterMode() const;
void gridSize(QPointF *offset, QSizeF *spacing) const override;
bool snapToGrid() const override;
// This method only exists to support flake-related operations
void addCommand(KUndo2Command *command) override;
QPoint documentOrigin() const override;
QPoint documentOffset() const;
/**
* Return the right shape manager for the current layer. That is
* to say, if the current layer is a vector layer, return the shape
* layer's canvas' shapemanager, else the shapemanager associated
* with the global krita canvas.
*/
KoShapeManager * shapeManager() const override;
/**
* Since shapeManager() may change, we need a persistent object where we can
* connect to and thack the selection. See more comments in KoCanvasBase.
*/
KoSelectedShapesProxy *selectedShapesProxy() const override;
/**
* Return the shape manager associated with this canvas
*/
KoShapeManager *globalShapeManager() const;
void updateCanvas(const QRectF& rc) override;
void updateInputMethodInfo() override;
const KisCoordinatesConverter* coordinatesConverter() const;
KoViewConverter *viewConverter() const override;
QWidget* canvasWidget() override;
const QWidget* canvasWidget() const override;
KoUnit unit() const override;
KoToolProxy* toolProxy() const override;
const KoColorProfile* monitorProfile();
// FIXME:
// Temporary! Either get the current layer and image from the
// resource provider, or use this, which gets them from the
// current shape selection.
KisImageWSP currentImage() const;
/**
* Filters events and sends them to canvas actions. Shared
* among all the views/canvases
*
* NOTE: May be null while initialization!
*/
KisInputManager* globalInputManager() const;
KisPaintingAssistantsDecorationSP paintingAssistantsDecoration() const;
public: // KisCanvas2 methods
KisImageWSP image() const;
KisViewManager* viewManager() const;
QPointer<KisView> imageView() const;
/// @return true if the canvas image should be displayed in vertically mirrored mode
void addDecoration(KisCanvasDecorationSP deco);
KisCanvasDecorationSP decoration(const QString& id) const;
void setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter);
QSharedPointer<KisDisplayFilter> displayFilter() const;
KisDisplayColorConverter *displayColorConverter() const;
KisExposureGammaCorrectionInterface* exposureGammaCorrectionInterface() const;
/**
* @brief setProofingOptions
* set the options for softproofing, without affecting the proofing options as stored inside the image.
*/
void setProofingOptions(bool softProof, bool gamutCheck);
KisProofingConfigurationSP proofingConfiguration() const;
/**
* @brief setProofingConfigUpdated This function is to set whether the proofing config is updated,
* this is needed for determining whether or not to generate a new proofing transform.
* @param updated whether it's updated. Just set it to false in normal usage.
*/
void setProofingConfigUpdated(bool updated);
/**
* @brief proofingConfigUpdated ask the canvas whether or not it updated the proofing config.
* @return whether or not the proofing config is updated, if so, a new proofing transform needs to be made
* in KisOpenGL canvas.
*/
bool proofingConfigUpdated();
void setCursor(const QCursor &cursor) override;
KisAnimationFrameCacheSP frameCache() const;
KisAnimationPlayer *animationPlayer() const;
void refetchDataFromImage();
Q_SIGNALS:
void imageChanged(KisImageWSP image);
void sigCanvasCacheUpdated();
void sigContinueResizeImage(qint32 w, qint32 h);
void documentOffsetUpdateFinished();
// emitted whenever the canvas widget thinks sketch should update
void updateCanvasRequested(const QRect &rc);
public Q_SLOTS:
/// Update the entire canvas area
void updateCanvas();
void startResizingImage();
void finishResizingImage(qint32 w, qint32 h);
/// canvas rotation in degrees
qreal rotationAngle() const;
/// Bools indicating canvasmirroring.
bool xAxisMirrored() const;
bool yAxisMirrored() const;
void slotSoftProofing(bool softProofing);
void slotGamutCheck(bool gamutCheck);
void slotChangeProofingConfig();
void slotZoomChanged(int zoom);
void channelSelectionChanged();
/**
* Called whenever the display monitor profile resource changes
*/
void slotSetDisplayProfile(const KoColorProfile *profile);
void startUpdateInPatches(const QRect &imageRect);
void slotTrySwitchShapeManager();
+ /**
+ * Called whenever the configuration settings change.
+ */
+ void slotConfigChanged();
+
+
private Q_SLOTS:
/// The image projection has changed, now start an update
/// of the canvas representation.
void startUpdateCanvasProjection(const QRect & rc);
void updateCanvasProjection();
/**
* Called whenever the view widget needs to show a different part of
* the document
*
* @param documentOffset the offset in widget pixels
*/
void documentOffsetMoved(const QPoint &documentOffset);
- /**
- * Called whenever the configuration settings change.
- */
- void slotConfigChanged();
-
void slotSelectionChanged();
void slotDoCanvasUpdate();
void bootstrapFinished();
public:
bool isPopupPaletteVisible() const;
void slotShowPopupPalette(const QPoint& = QPoint(0,0));
// interface for KisCanvasController only
void setWrapAroundViewingMode(bool value);
bool wrapAroundViewingMode() const;
void setLodAllowedInCanvas(bool value);
bool lodAllowedInCanvas() const;
void initializeImage();
void setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager);
private:
Q_DISABLE_COPY(KisCanvas2)
void connectCurrentCanvas();
void createCanvas(bool useOpenGL);
void createQPainterCanvas();
void createOpenGLCanvas();
void updateCanvasWidgetImpl(const QRect &rc = QRect());
void setCanvasWidget(QWidget *widget);
void resetCanvas(bool useOpenGL);
void notifyLevelOfDetailChange();
// Completes construction of canvas.
// To be called by KisView in its constructor, once it has been setup enough
// (to be defined what that means) for things KisCanvas2 expects from KisView
// TODO: see to avoid that
void setup();
+ void initializeFpsDecoration();
+
private:
friend class KisView; // calls setup()
class KisCanvas2Private;
KisCanvas2Private * const m_d;
};
#endif
diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp
index 635e445089..804ec76a5c 100644
--- a/libs/ui/canvas/kis_canvas_controller.cpp
+++ b/libs/ui/canvas/kis_canvas_controller.cpp
@@ -1,319 +1,337 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_canvas_controller.h"
#include <QMouseEvent>
#include <QTabletEvent>
#include <klocalizedstring.h>
#include "kis_canvas_decoration.h"
#include "kis_paintop_transformation_connector.h"
#include "kis_coordinates_converter.h"
#include "kis_canvas2.h"
+#include "opengl/kis_opengl_canvas2.h"
#include "kis_image.h"
#include "KisViewManager.h"
#include "KisView.h"
#include "krita_utils.h"
+#include "kis_config.h"
#include "kis_signal_compressor_with_param.h"
static const int gRulersUpdateDelay = 80 /* ms */;
struct KisCanvasController::Private {
Private(KisCanvasController *qq)
: q(qq),
paintOpTransformationConnector(0)
{
using namespace std::placeholders;
std::function<void (QPoint)> callback(
std::bind(&KisCanvasController::Private::emitPointerPositionChangedSignals, this, _1));
mousePositionCompressor.reset(
new KisSignalCompressorWithParam<QPoint>(
gRulersUpdateDelay,
callback,
KisSignalCompressor::FIRST_ACTIVE));
}
QPointer<KisView> view;
KisCoordinatesConverter *coordinatesConverter;
KisCanvasController *q;
KisPaintopTransformationConnector *paintOpTransformationConnector;
QScopedPointer<KisSignalCompressorWithParam<QPoint> > mousePositionCompressor;
void emitPointerPositionChangedSignals(QPoint pointerPos);
void updateDocumentSizeAfterTransform();
void showRotationValueOnCanvas();
void showMirrorStateOnCanvas();
};
void KisCanvasController::Private::emitPointerPositionChangedSignals(QPoint pointerPos)
{
if (!coordinatesConverter) return;
QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos);
q->proxyObject->emitDocumentMousePositionChanged(documentPos);
q->proxyObject->emitCanvasMousePositionChanged(pointerPos);
}
void KisCanvasController::Private::updateDocumentSizeAfterTransform()
{
// round the size of the area to the nearest integer instead of getting aligned rect
QSize widgetSize = coordinatesConverter->imageRectInWidgetPixels().toRect().size();
q->updateDocumentSize(widgetSize, true);
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(q->canvas());
Q_ASSERT(kritaCanvas);
kritaCanvas->notifyZoomChanged();
}
KisCanvasController::KisCanvasController(QPointer<KisView>parent, KActionCollection * actionCollection)
: KoCanvasControllerWidget(actionCollection, parent),
m_d(new Private(this))
{
m_d->view = parent;
}
KisCanvasController::~KisCanvasController()
{
delete m_d;
}
void KisCanvasController::setCanvas(KoCanvasBase *canvas)
{
if (canvas) {
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas);
KIS_SAFE_ASSERT_RECOVER_RETURN(kritaCanvas);
m_d->coordinatesConverter =
const_cast<KisCoordinatesConverter*>(kritaCanvas->coordinatesConverter());
m_d->paintOpTransformationConnector =
new KisPaintopTransformationConnector(kritaCanvas, this);
} else {
m_d->coordinatesConverter = 0;
delete m_d->paintOpTransformationConnector;
m_d->paintOpTransformationConnector = 0;
}
KoCanvasControllerWidget::setCanvas(canvas);
}
void KisCanvasController::changeCanvasWidget(QWidget *widget)
{
KoCanvasControllerWidget::changeCanvasWidget(widget);
}
void KisCanvasController::activate()
{
KoCanvasControllerWidget::activate();
}
QPointF KisCanvasController::currentCursorPosition() const
{
KoCanvasBase *canvas = m_d->view->canvasBase();
QWidget *canvasWidget = canvas->canvasWidget();
const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos());
return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget);
}
void KisCanvasController::keyPressEvent(QKeyEvent *event)
{
/**
* Dirty Hack Alert:
* Do not call the KoCanvasControllerWidget::keyPressEvent()
* to avoid activation of Pan and Default tool activation shortcuts
*/
Q_UNUSED(event);
}
void KisCanvasController::wheelEvent(QWheelEvent *event)
{
/**
* Dirty Hack Alert:
* Do not call the KoCanvasControllerWidget::wheelEvent()
* to disable the default behavior of KoCanvasControllerWidget and QAbstractScrollArea
*/
Q_UNUSED(event);
}
bool KisCanvasController::eventFilter(QObject *watched, QEvent *event)
{
KoCanvasBase *canvas = this->canvas();
if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false;
if (event->type() == QEvent::MouseMove) {
QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
m_d->mousePositionCompressor->start(mevent->pos());
} else if (event->type() == QEvent::TabletMove) {
QTabletEvent *tevent = static_cast<QTabletEvent*>(event);
m_d->mousePositionCompressor->start(tevent->pos());
} else if (event->type() == QEvent::FocusIn) {
m_d->view->syncLastActiveNodeToDocument();
}
return false;
}
void KisCanvasController::updateDocumentSize(const QSize &sz, bool recalculateCenter)
{
KoCanvasControllerWidget::updateDocumentSize(sz, recalculateCenter);
emit documentSizeChanged();
}
void KisCanvasController::Private::showMirrorStateOnCanvas()
{
bool isXMirrored = coordinatesConverter->xAxisMirrored();
view->viewManager()->
showFloatingMessage(
i18nc("floating message about mirroring",
"Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")),
QIcon(), 500, KisFloatingMessage::Low);
}
void KisCanvasController::mirrorCanvas(bool enable)
{
QPoint newOffset = m_d->coordinatesConverter->mirror(m_d->coordinatesConverter->widgetCenterPoint(), enable, false);
m_d->updateDocumentSizeAfterTransform();
setScrollBarValue(newOffset);
m_d->paintOpTransformationConnector->notifyTransformationChanged();
m_d->showMirrorStateOnCanvas();
}
void KisCanvasController::Private::showRotationValueOnCanvas()
{
qreal rotationAngle = coordinatesConverter->rotationAngle();
view->viewManager()->
showFloatingMessage(
i18nc("floating message about rotation", "Rotation: %1° ",
KritaUtils::prettyFormatReal(rotationAngle)),
QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter);
}
void KisCanvasController::rotateCanvas(qreal angle, const QPointF &center)
{
QPoint newOffset = m_d->coordinatesConverter->rotate(center, angle);
m_d->updateDocumentSizeAfterTransform();
setScrollBarValue(newOffset);
m_d->paintOpTransformationConnector->notifyTransformationChanged();
m_d->showRotationValueOnCanvas();
}
void KisCanvasController::rotateCanvas(qreal angle)
{
rotateCanvas(angle, m_d->coordinatesConverter->widgetCenterPoint());
}
void KisCanvasController::rotateCanvasRight15()
{
rotateCanvas(15.0);
}
void KisCanvasController::rotateCanvasLeft15()
{
rotateCanvas(-15.0);
}
qreal KisCanvasController::rotation() const
{
return m_d->coordinatesConverter->rotationAngle();
}
void KisCanvasController::resetCanvasRotation()
{
QPoint newOffset = m_d->coordinatesConverter->resetRotation(m_d->coordinatesConverter->widgetCenterPoint());
m_d->updateDocumentSizeAfterTransform();
setScrollBarValue(newOffset);
m_d->paintOpTransformationConnector->notifyTransformationChanged();
m_d->showRotationValueOnCanvas();
}
void KisCanvasController::slotToggleWrapAroundMode(bool value)
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
if (!canvas()->canvasIsOpenGL() && value) {
m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n"
"To visualize wrap-around mode, enable OpenGL."), QIcon());
}
kritaCanvas->setWrapAroundViewingMode(value);
kritaCanvas->image()->setWrapAroundModePermitted(value);
}
bool KisCanvasController::wrapAroundMode() const
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
return kritaCanvas->wrapAroundViewingMode();
}
+
+void KisCanvasController::slotTogglePixelGrid(bool value)
+{
+ KisConfig cfg;
+ cfg.enablePixelGrid(value);
+
+ KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
+
+ // pixel grid only works with openGL
+ if (kritaCanvas->canvasIsOpenGL() ) {
+ KisOpenGLCanvas2 *openGLWidget = dynamic_cast<KisOpenGLCanvas2*>(kritaCanvas->canvasWidget());
+ openGLWidget->slotConfigChanged();
+ }
+
+}
+
void KisCanvasController::slotToggleLevelOfDetailMode(bool value)
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
kritaCanvas->setLodAllowedInCanvas(value);
bool result = levelOfDetailMode();
if (!value || result) {
m_d->view->viewManager()->showFloatingMessage(
i18n("Instant Preview Mode: %1", result ?
i18n("ON") : i18n("OFF")),
QIcon(), 500, KisFloatingMessage::Low);
} else {
QString reason;
if (!kritaCanvas->canvasIsOpenGL()) {
reason = i18n("Instant Preview is only supported with OpenGL activated");
}
else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ||
kritaCanvas->openGLFilterMode() == KisOpenGL::NearestFilterMode) {
QString filteringMode =
kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ?
i18n("Bilinear") : i18n("Nearest Neighbour");
reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode);
}
m_d->view->viewManager()->showFloatingMessage(
i18n("Failed activating Instant Preview mode!\n\n%1", reason),
QIcon(), 5000, KisFloatingMessage::Low);
}
}
bool KisCanvasController::levelOfDetailMode() const
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
return kritaCanvas->lodAllowedInCanvas();
}
diff --git a/libs/ui/canvas/kis_canvas_controller.h b/libs/ui/canvas/kis_canvas_controller.h
index 98945cd06d..e35d45856c 100644
--- a/libs/ui/canvas/kis_canvas_controller.h
+++ b/libs/ui/canvas/kis_canvas_controller.h
@@ -1,71 +1,72 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CANVAS_CONTROLLER_H
#define KIS_CANVAS_CONTROLLER_H
#include <KoCanvasControllerWidget.h>
#include "kritaui_export.h"
#include "kis_types.h"
class KisView;
class KRITAUI_EXPORT KisCanvasController : public KoCanvasControllerWidget
{
Q_OBJECT
public:
KisCanvasController(QPointer<KisView>parent, KActionCollection * actionCollection);
~KisCanvasController() override;
void setCanvas(KoCanvasBase *canvas) override;
void changeCanvasWidget(QWidget *widget) override;
void keyPressEvent(QKeyEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
bool eventFilter(QObject *watched, QEvent *event) override;
void updateDocumentSize(const QSize &sz, bool recalculateCenter) override;
void activate() override;
QPointF currentCursorPosition() const override;
public:
using KoCanvasController::documentSize;
bool wrapAroundMode() const;
bool levelOfDetailMode() const;
public Q_SLOTS:
void mirrorCanvas(bool enable);
void rotateCanvas(qreal angle, const QPointF &center);
void rotateCanvas(qreal angle);
void rotateCanvasRight15();
void rotateCanvasLeft15();
qreal rotation() const;
void resetCanvasRotation();
void slotToggleWrapAroundMode(bool value);
+ void slotTogglePixelGrid(bool value);
void slotToggleLevelOfDetailMode(bool value);
Q_SIGNALS:
void documentSizeChanged();
private:
struct Private;
Private * const m_d;
};
#endif /* KIS_CANVAS_CONTROLLER_H */
diff --git a/libs/ui/canvas/kis_canvas_updates_compressor.cpp b/libs/ui/canvas/kis_canvas_updates_compressor.cpp
index 918924d358..c016e66d0e 100644
--- a/libs/ui/canvas/kis_canvas_updates_compressor.cpp
+++ b/libs/ui/canvas/kis_canvas_updates_compressor.cpp
@@ -1,61 +1,54 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_canvas_updates_compressor.h"
bool KisCanvasUpdatesCompressor::putUpdateInfo(KisUpdateInfoSP info)
{
const int levelOfDetail = info->levelOfDetail();
const QRect newUpdateRect = info->dirtyImageRect();
if (newUpdateRect.isEmpty()) return false;
QMutexLocker l(&m_mutex);
- bool updateOverridden = false;
-
UpdateInfoList::iterator it = m_updatesList.begin();
+
while (it != m_updatesList.end()) {
if (levelOfDetail == (*it)->levelOfDetail() &&
newUpdateRect.contains((*it)->dirtyImageRect())) {
- if (info) {
- *it = info;
- info = 0;
- ++it;
- } else {
- it = m_updatesList.erase(it);
- }
-
- updateOverridden = true;
+ /**
+ * We should always remove the overridden update and put 'info' to the end
+ * of the queue. Otherwise, the updates will become reordered and the canvas
+ * may have tiles artifacts with "outdated" data
+ */
+ it = m_updatesList.erase(it);
} else {
++it;
}
}
- if (!updateOverridden) {
- Q_ASSERT(info);
- m_updatesList.append(info);
- }
+ m_updatesList.append(info);
- return !updateOverridden;
+ return m_updatesList.size() <= 1;
}
KisUpdateInfoSP KisCanvasUpdatesCompressor::takeUpdateInfo()
{
QMutexLocker l(&m_mutex);
return !m_updatesList.isEmpty() ? m_updatesList.takeFirst() : 0;
}
diff --git a/libs/ui/canvas/kis_canvas_widget_base.cpp b/libs/ui/canvas/kis_canvas_widget_base.cpp
index 614dcb4467..482d63e034 100644
--- a/libs/ui/canvas/kis_canvas_widget_base.cpp
+++ b/libs/ui/canvas/kis_canvas_widget_base.cpp
@@ -1,248 +1,258 @@
/*
* Copyright (C) 2007, 2010 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_canvas_widget_base.h"
#include <QImage>
#include <QPainter>
#include <QTimer>
#include <QMenu>
#include <KoShapeManager.h>
#include <KoToolManager.h>
#include <KoViewConverter.h>
#include <KoToolProxy.h>
#include <KoCanvasController.h>
#include <KoShape.h>
#include <KoSelection.h>
#include <KoShapePaintingContext.h>
#include "kis_coordinates_converter.h"
#include "kis_canvas_decoration.h"
#include "kis_config.h"
#include "kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_selection_manager.h"
#include "KisDocument.h"
struct KisCanvasWidgetBase::Private
{
public:
Private(KisCanvas2 *newCanvas, KisCoordinatesConverter *newCoordinatesConverter)
: canvas(newCanvas)
, coordinatesConverter(newCoordinatesConverter)
, viewConverter(newCanvas->viewConverter())
, toolProxy(newCanvas->toolProxy())
, ignorenextMouseEventExceptRightMiddleClick(0)
, borderColor(Qt::gray)
{}
QList<KisCanvasDecorationSP> decorations;
KisCanvas2 * canvas;
KisCoordinatesConverter *coordinatesConverter;
const KoViewConverter * viewConverter;
KoToolProxy * toolProxy;
QTimer blockMouseEvent;
bool ignorenextMouseEventExceptRightMiddleClick; // HACK work around Qt bug not sending tablet right/dblclick http://bugreports.qt.nokia.com/browse/QTBUG-8598
QColor borderColor;
};
KisCanvasWidgetBase::KisCanvasWidgetBase(KisCanvas2 * canvas, KisCoordinatesConverter *coordinatesConverter)
: m_d(new Private(canvas, coordinatesConverter))
{
m_d->blockMouseEvent.setSingleShot(true);
}
KisCanvasWidgetBase::~KisCanvasWidgetBase()
{
/**
* Clear all the attached decoration. Oherwise they might decide
* to process some events or signals after the canvas has been
* destroyed
*/
//5qDeleteAll(m_d->decorations);
m_d->decorations.clear();
delete m_d;
}
void KisCanvasWidgetBase::drawDecorations(QPainter & gc, const QRect &updateWidgetRect) const
{
if (!m_d->canvas) {
dbgFile<<"canvas doesn't exist, in canvas widget base!";
return;
}
gc.save();
// Setup the painter to take care of the offset; all that the
// classes that do painting need to keep track of is resolution
gc.setRenderHint(QPainter::Antialiasing);
gc.setRenderHint(QPainter::TextAntialiasing);
// This option does not do anything anymore with Qt4.6, so don't reenable it since it seems to break display
// http://www.archivum.info/qt-interest@trolltech.com/2010-01/00481/Re:-(Qt-interest)-Is-QPainter::HighQualityAntialiasing-render-hint-broken-in-Qt-4.6.html
// gc.setRenderHint(QPainter::HighQualityAntialiasing);
gc.setRenderHint(QPainter::SmoothPixmapTransform);
gc.save();
gc.setClipRect(updateWidgetRect);
QTransform transform = m_d->coordinatesConverter->flakeToWidgetTransform();
gc.setTransform(transform);
// Paint the shapes (other than the layers)
m_d->canvas->globalShapeManager()->paint(gc, *m_d->viewConverter, false);
// draw green selection outlines around text shapes that are edited, so the user sees where they end
gc.save();
QTransform worldTransform = gc.worldTransform();
gc.setPen( Qt::green );
Q_FOREACH (KoShape *shape, canvas()->shapeManager()->selection()->selectedShapes()) {
if (shape->shapeId() == "ArtisticText" || shape->shapeId() == "TextShapeID") {
gc.setWorldTransform(shape->absoluteTransformation(m_d->viewConverter) * worldTransform);
KoShape::applyConversion(gc, *m_d->viewConverter);
gc.drawRect(QRectF(QPointF(), shape->size()));
}
}
gc.restore();
// Draw text shape over canvas while editing it, that's needs to show the text selection correctly
QString toolId = KoToolManager::instance()->activeToolId();
if (toolId == "ArtisticTextTool" || toolId == "TextTool") {
gc.save();
gc.setPen(Qt::NoPen);
gc.setBrush(Qt::NoBrush);
Q_FOREACH (KoShape *shape, canvas()->shapeManager()->selection()->selectedShapes()) {
if (shape->shapeId() == "ArtisticText" || shape->shapeId() == "TextShapeID") {
KoShapePaintingContext paintContext(canvas(), false);
gc.save();
gc.setTransform(shape->absoluteTransformation(m_d->viewConverter) * gc.transform());
canvas()->shapeManager()->paintShape(shape, gc, *m_d->viewConverter, paintContext);
gc.restore();
}
}
gc.restore();
}
// - some tools do not restore gc, but that is not important here
// - we need to disable clipping to draw handles properly
gc.setClipping(false);
toolProxy()->paint(gc, *m_d->viewConverter);
gc.restore();
// ask the decorations to paint themselves
Q_FOREACH (KisCanvasDecorationSP deco, m_d->decorations) {
deco->paint(gc, m_d->coordinatesConverter->widgetToDocument(updateWidgetRect), m_d->coordinatesConverter,m_d->canvas);
}
gc.restore();
}
void KisCanvasWidgetBase::addDecoration(KisCanvasDecorationSP deco)
{
m_d->decorations.push_back(deco);
}
+void KisCanvasWidgetBase::removeDecoration(const QString &id)
+{
+ for (auto it = m_d->decorations.begin(); it != m_d->decorations.end(); ++it) {
+ if ((*it)->id() == id) {
+ it = m_d->decorations.erase(it);
+ break;
+ }
+ }
+}
+
KisCanvasDecorationSP KisCanvasWidgetBase::decoration(const QString& id) const
{
Q_FOREACH (KisCanvasDecorationSP deco, m_d->decorations) {
if (deco->id() == id) {
return deco;
}
}
return 0;
}
void KisCanvasWidgetBase::setDecorations(const QList<KisCanvasDecorationSP > &decorations)
{
m_d->decorations=decorations;
}
QList<KisCanvasDecorationSP > KisCanvasWidgetBase::decorations() const
{
return m_d->decorations;
}
void KisCanvasWidgetBase::setWrapAroundViewingMode(bool value)
{
Q_UNUSED(value);
}
QImage KisCanvasWidgetBase::createCheckersImage(qint32 checkSize)
{
KisConfig cfg;
if(checkSize < 0)
checkSize = cfg.checkSize();
QColor checkColor1 = cfg.checkersColor1();
QColor checkColor2 = cfg.checkersColor2();
QImage tile(checkSize * 2, checkSize * 2, QImage::Format_RGB32);
QPainter pt(&tile);
pt.fillRect(tile.rect(), checkColor2);
pt.fillRect(0, 0, checkSize, checkSize, checkColor1);
pt.fillRect(checkSize, checkSize, checkSize, checkSize, checkColor1);
pt.end();
return tile;
}
void KisCanvasWidgetBase::notifyConfigChanged()
{
KisConfig cfg;
m_d->borderColor = cfg.canvasBorderColor();
}
QColor KisCanvasWidgetBase::borderColor() const
{
return m_d->borderColor;
}
KisCanvas2 *KisCanvasWidgetBase::canvas() const
{
return m_d->canvas;
}
KisCoordinatesConverter* KisCanvasWidgetBase::coordinatesConverter() const
{
return m_d->coordinatesConverter;
}
KoToolProxy *KisCanvasWidgetBase::toolProxy() const
{
return m_d->toolProxy;
}
QVariant KisCanvasWidgetBase::processInputMethodQuery(Qt::InputMethodQuery query) const
{
if (query == Qt::ImMicroFocus) {
QRectF rect = m_d->toolProxy->inputMethodQuery(query, *m_d->viewConverter).toRectF();
return m_d->coordinatesConverter->flakeToWidget(rect);
}
return m_d->toolProxy->inputMethodQuery(query, *m_d->viewConverter);
}
void KisCanvasWidgetBase::processInputMethodEvent(QInputMethodEvent *event)
{
m_d->toolProxy->inputMethodEvent(event);
}
diff --git a/libs/ui/canvas/kis_canvas_widget_base.h b/libs/ui/canvas/kis_canvas_widget_base.h
index a97be68e81..011e65e1be 100644
--- a/libs/ui/canvas/kis_canvas_widget_base.h
+++ b/libs/ui/canvas/kis_canvas_widget_base.h
@@ -1,102 +1,103 @@
/*
* Copyright (C) 2007 Boudewijn Rempt <boud@valdyas.org>, (C)
* Copyright (C) 2010 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_CANVAS_WIDGET_BASE_
#define _KIS_CANVAS_WIDGET_BASE_
#include <QList>
#include <Qt>
#include "kis_abstract_canvas_widget.h"
class QColor;
class QImage;
class QInputMethodEvent;
class QVariant;
class KisCoordinatesConverter;
class KisDisplayFilter;
class KisCanvas2;
#include "kritaui_export.h"
class KRITAUI_EXPORT KisCanvasWidgetBase : public KisAbstractCanvasWidget
{
public:
KisCanvasWidgetBase(KisCanvas2 * canvas, KisCoordinatesConverter *coordinatesConverter);
~KisCanvasWidgetBase() override;
public: // KisAbstractCanvasWidget
KoToolProxy *toolProxy() const override;
/// set the specified display filter on the canvas
void setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter) override = 0;
/**
* Draw the specified decorations on the view.
*/
void drawDecorations(QPainter & gc, const QRect &updateWidgetRect) const override;
void addDecoration(KisCanvasDecorationSP deco) override;
+ void removeDecoration(const QString& id) override;
KisCanvasDecorationSP decoration(const QString& id) const override;
void setDecorations(const QList<KisCanvasDecorationSP > &) override;
QList<KisCanvasDecorationSP > decorations() const override;
void setWrapAroundViewingMode(bool value) override;
/**
* Returns the color of the border, i.e. the part of the canvas
* outside the image contents.
*
*/
QColor borderColor() const;
/**
* Returns one check of the background checkerboard pattern.
*/
static QImage createCheckersImage(qint32 checkSize = -1);
KisCoordinatesConverter* coordinatesConverter() const;
protected:
KisCanvas2 *canvas() const;
/**
* Event handlers to be called by derived canvas event handlers.
* All common event processing is carried out by these
* functions.
*/
QVariant processInputMethodQuery(Qt::InputMethodQuery query) const;
void processInputMethodEvent(QInputMethodEvent *event);
void notifyConfigChanged();
/// To be implemented by the derived canvas
virtual bool callFocusNextPrevChild(bool next) = 0;
private:
struct Private;
Private * const m_d;
};
#endif // _KIS_CANVAS_WIDGET_BASE_
diff --git a/libs/ui/canvas/kis_infinity_manager.cpp b/libs/ui/canvas/kis_infinity_manager.cpp
index c180483b6a..ffc3e2b76a 100644
--- a/libs/ui/canvas/kis_infinity_manager.cpp
+++ b/libs/ui/canvas/kis_infinity_manager.cpp
@@ -1,305 +1,305 @@
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_infinity_manager.h"
#include <QPainter>
#include <klocalizedstring.h>
#include <KoCanvasController.h>
#include <kis_debug.h>
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <input/kis_input_manager.h>
#include <kis_config.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_canvas_controller.h>
#include <KisView.h>
#include <kis_algebra_2d.h>
KisInfinityManager::KisInfinityManager(QPointer<KisView>view, KisCanvas2 *canvas)
: KisCanvasDecoration(INFINITY_DECORATION_ID, view),
m_filteringEnabled(false),
m_cursorSwitched(false),
m_sideRects(NSides),
m_canvas(canvas)
{
connect(canvas, SIGNAL(documentOffsetUpdateFinished()), SLOT(imagePositionChanged()));
}
inline void KisInfinityManager::addDecoration(const QRect &areaRect, const QPointF &handlePoint, qreal angle, Side side)
{
QTransform t;
t.rotate(angle);
t = t * QTransform::fromTranslate(handlePoint.x(), handlePoint.y());
m_handleTransform << t;
m_decorationPath.addRect(areaRect);
m_sideRects[side] = areaRect;
}
void KisInfinityManager::imagePositionChanged()
{
const QRect imageRect = m_canvas->coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect();
const QRect widgetRect = m_canvas->canvasWidget()->rect();
KisConfig cfg;
qreal vastScrolling = cfg.vastScrolling();
int xReserve = vastScrolling * widgetRect.width();
int yReserve = vastScrolling * widgetRect.height();
int xThreshold = imageRect.width() - 0.4 * xReserve;
int yThreshold = imageRect.height() - 0.4 * yReserve;
const int stripeWidth = 48;
int xCut = widgetRect.width() - stripeWidth;
int yCut = widgetRect.height() - stripeWidth;
m_decorationPath = QPainterPath();
m_decorationPath.setFillRule(Qt::WindingFill);
m_handleTransform.clear();
m_sideRects.clear();
m_sideRects.resize(NSides);
bool visible = false;
if (imageRect.x() <= -xThreshold) {
QRect areaRect(widgetRect.adjusted(xCut, 0, 0, 0));
QPointF pt = areaRect.center() + QPointF(-0.1 * stripeWidth, 0);
addDecoration(areaRect, pt, 0, Right);
visible = true;
}
if (imageRect.y() <= -yThreshold) {
QRect areaRect(widgetRect.adjusted(0, yCut, 0, 0));
QPointF pt = areaRect.center() + QPointF(0, -0.1 * stripeWidth);
addDecoration(areaRect, pt, 90, Bottom);
visible = true;
}
if (imageRect.right() > widgetRect.width() + xThreshold) {
QRect areaRect(widgetRect.adjusted(0, 0, -xCut, 0));
QPointF pt = areaRect.center() + QPointF(0.1 * stripeWidth, 0);
addDecoration(areaRect, pt, 180, Left);
visible = true;
}
if (imageRect.bottom() > widgetRect.height() + yThreshold) {
QRect areaRect(widgetRect.adjusted(0, 0, 0, -yCut));
QPointF pt = areaRect.center() + QPointF(0, 0.1 * stripeWidth);
addDecoration(areaRect, pt, 270, Top);
visible = true;
}
if (!m_filteringEnabled && visible && this->visible()) {
KisInputManager *inputManager = m_canvas->globalInputManager();
if (inputManager) {
inputManager->attachPriorityEventFilter(this);
}
m_filteringEnabled = true;
}
if (m_filteringEnabled && (!visible || !this->visible())) {
KisInputManager *inputManager = m_canvas->globalInputManager();
if (inputManager) {
inputManager->detachPriorityEventFilter(this);
}
m_filteringEnabled = false;
}
}
void KisInfinityManager::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter *converter, KisCanvas2 *canvas)
{
Q_UNUSED(updateArea);
Q_UNUSED(converter);
Q_UNUSED(canvas);
if (!m_filteringEnabled) return;
gc.save();
gc.setTransform(QTransform(), false);
KisConfig cfg;
QColor color = cfg.canvasBorderColor();
gc.fillPath(m_decorationPath, color.darker(115));
QPainterPath p = KisAlgebra2D::smallArrow();
Q_FOREACH (const QTransform &t, m_handleTransform) {
gc.fillPath(t.map(p), color);
}
gc.restore();
}
inline int expandLeft(int x0, int x1, int maxExpand)
{
return qMax(x0 - maxExpand, qMin(x0, x1));
}
inline int expandRight(int x0, int x1, int maxExpand)
{
return qMin(x0 + maxExpand, qMax(x0, x1));
}
inline QPoint getPointFromEvent(QEvent *event)
{
QPoint result;
if (event->type() == QEvent::MouseMove ||
event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
result = mouseEvent->pos();
} else if (event->type() == QEvent::TabletMove ||
event->type() == QEvent::TabletPress ||
event->type() == QEvent::TabletRelease) {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
result = tabletEvent->pos();
}
return result;
}
inline Qt::MouseButton getButtonFromEvent(QEvent *event)
{
Qt::MouseButton button = Qt::NoButton;
if (event->type() == QEvent::MouseMove ||
event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
button = mouseEvent->button();
} else if (event->type() == QEvent::TabletMove ||
event->type() == QEvent::TabletPress ||
event->type() == QEvent::TabletRelease) {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
button = tabletEvent->button();
}
return button;
}
bool KisInfinityManager::eventFilter(QObject *obj, QEvent *event)
{
/**
* We connect our event filter to the global InputManager which is
* shared among all the canvases. Ideally we should disconnect our
* event filter whin this canvas is not active, but for now we can
* just check the destination of the event, if it is correct.
*/
- if (obj != m_canvas->canvasWidget()) return false;
+ if (m_canvas == NULL || obj != m_canvas->canvasWidget()) return false;
KIS_ASSERT_RECOVER_NOOP(m_filteringEnabled);
bool retval = false;
switch (event->type()) {
case QEvent::Enter:
case QEvent::Leave:
case QEvent::MouseMove:
case QEvent::TabletMove: {
QPoint pos = getPointFromEvent(event);
if (m_decorationPath.contains(pos)) {
if (!m_cursorSwitched) {
m_oldCursor = m_canvas->canvasWidget()->cursor();
m_cursorSwitched = true;
}
m_canvas->canvasWidget()->setCursor(Qt::PointingHandCursor);
retval = true;
} else if (m_cursorSwitched) {
m_canvas->canvasWidget()->setCursor(m_oldCursor);
m_cursorSwitched = false;
}
break;
}
case QEvent::MouseButtonPress:
case QEvent::TabletPress: {
Qt::MouseButton button = getButtonFromEvent(event);
retval = button == Qt::LeftButton && m_cursorSwitched;
if (button == Qt::RightButton) {
imagePositionChanged();
}
break;
}
case QEvent::MouseButtonRelease:
case QEvent::TabletRelease: {
Qt::MouseButton button = getButtonFromEvent(event);
retval = button == Qt::LeftButton && m_cursorSwitched;
if (retval) {
QPoint pos = getPointFromEvent(event);
const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
QRect widgetRect = converter->widgetToImage(m_canvas->canvasWidget()->rect()).toAlignedRect();
KisImageWSP image = view()->image();
QRect cropRect = image->bounds();
const int hLimit = cropRect.width();
const int vLimit = cropRect.height();
if (m_sideRects[Right].contains(pos)) {
cropRect.setRight(expandRight(cropRect.right(), widgetRect.right(), hLimit));
}
if (m_sideRects[Bottom].contains(pos)) {
cropRect.setBottom(expandRight(cropRect.bottom(), widgetRect.bottom(), vLimit));
}
if (m_sideRects[Left].contains(pos)) {
cropRect.setLeft(expandLeft(cropRect.left(), widgetRect.left(), hLimit));
}
if (m_sideRects[Top].contains(pos)) {
cropRect.setTop(expandLeft(cropRect.top(), widgetRect.top(), vLimit));
}
image->resizeImage(cropRect);
// since resizing the image can cause the cursor to end up on the canvas without a move event,
// it can get stuck in an overridden state until it is changed by another event,
// and we don't want that.
if (m_cursorSwitched) {
m_canvas->canvasWidget()->setCursor(m_oldCursor);
m_cursorSwitched = false;
}
}
break;
}
default:
break;
}
return !retval ? KisCanvasDecoration::eventFilter(obj, event) : true;
}
diff --git a/libs/ui/canvas/kis_tool_proxy.cpp b/libs/ui/canvas/kis_tool_proxy.cpp
index 8e6916ada6..5c054df964 100644
--- a/libs/ui/canvas/kis_tool_proxy.cpp
+++ b/libs/ui/canvas/kis_tool_proxy.cpp
@@ -1,243 +1,243 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_proxy.h"
#include "kis_canvas2.h"
#include "input/kis_tablet_debugger.h"
#include <KoToolProxy_p.h>
KisToolProxy::KisToolProxy(KoCanvasBase *canvas, QObject *parent)
: KoToolProxy(canvas, parent),
m_isActionActivated(false),
m_lastAction(KisTool::Primary)
{
}
void KisToolProxy::initializeImage(KisImageSP image)
{
connect(image, SIGNAL(sigUndoDuringStrokeRequested()), SLOT(requestUndoDuringStroke()), Qt::UniqueConnection);
connect(image, SIGNAL(sigStrokeCancellationRequested()), SLOT(requestStrokeCancellation()), Qt::UniqueConnection);
connect(image, SIGNAL(sigStrokeEndRequested()), SLOT(requestStrokeEnd()), Qt::UniqueConnection);
}
QPointF KisToolProxy::tabletToDocument(const QPointF &globalPos)
{
const QPointF pos = globalPos - QPointF(canvas()->canvasWidget()->mapToGlobal(QPoint(0, 0)));
return widgetToDocument(pos);
}
QPointF KisToolProxy::widgetToDocument(const QPointF &widgetPoint) const
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
return kritaCanvas->coordinatesConverter()->widgetToDocument(widgetPoint);
}
KoPointerEvent KisToolProxy::convertEventToPointerEvent(QEvent *event, const QPointF &docPoint, bool *result)
{
switch (event->type()) {
case QEvent::TabletPress:
case QEvent::TabletRelease:
case QEvent::TabletMove:
{
*result = true;
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
KoPointerEvent ev(tabletEvent, docPoint);
ev.setTabletButton(Qt::LeftButton);
return ev;
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove:
{
*result = true;
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
return KoPointerEvent(mouseEvent, docPoint);
}
default:
;
}
*result = false;
QMouseEvent fakeEvent(QEvent::MouseMove, QPoint(),
Qt::NoButton, Qt::NoButton,
Qt::NoModifier);
return KoPointerEvent(&fakeEvent, QPointF());
}
void KisToolProxy::forwardHoverEvent(QEvent *event)
{
switch (event->type()) {
case QEvent::TabletMove: {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
QPointF docPoint = widgetToDocument(tabletEvent->posF());
this->tabletEvent(tabletEvent, docPoint);
return;
}
case QEvent::MouseMove: {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
- QPointF docPoint = widgetToDocument(mouseEvent->posF());
+ QPointF docPoint = widgetToDocument(mouseEvent->localPos());
mouseMoveEvent(mouseEvent, docPoint);
return;
}
default: {
qWarning() << "forwardHoverEvent encountered unknown event type.";
return;
}
}
}
bool KisToolProxy::forwardEvent(ActionState state, KisTool::ToolAction action, QEvent *event, QEvent *originalEvent)
{
bool retval = true;
QTabletEvent *tabletEvent = dynamic_cast<QTabletEvent*>(event);
QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
if (tabletEvent) {
QPointF docPoint = widgetToDocument(tabletEvent->posF());
tabletEvent->accept();
this->tabletEvent(tabletEvent, docPoint);
forwardToTool(state, action, tabletEvent, docPoint);
retval = tabletEvent->isAccepted();
}
else if (mouseEvent) {
- QPointF docPoint = widgetToDocument(mouseEvent->posF());
+ QPointF docPoint = widgetToDocument(mouseEvent->localPos());
mouseEvent->accept();
if (mouseEvent->type() == QEvent::MouseButtonPress) {
mousePressEvent(mouseEvent, docPoint);
} else if (mouseEvent->type() == QEvent::MouseButtonDblClick) {
mouseDoubleClickEvent(mouseEvent, docPoint);
} else if (mouseEvent->type() == QEvent::MouseButtonRelease) {
mouseReleaseEvent(mouseEvent, docPoint);
} else if (mouseEvent->type() == QEvent::MouseMove) {
mouseMoveEvent(mouseEvent, docPoint);
}
forwardToTool(state, action, originalEvent, docPoint);
retval = mouseEvent->isAccepted();
}
else if (event && event->type() == QEvent::KeyPress) {
QKeyEvent* kevent = static_cast<QKeyEvent*>(event);
keyPressEvent(kevent);
}
else if (event && event->type() == QEvent::KeyRelease) {
QKeyEvent* kevent = static_cast<QKeyEvent*>(event);
keyReleaseEvent(kevent);
}
return retval;
}
void KisToolProxy::forwardToTool(ActionState state, KisTool::ToolAction action, QEvent *event, const QPointF &docPoint)
{
bool eventValid = false;
KoPointerEvent ev = convertEventToPointerEvent(event, docPoint, &eventValid);
KisTool *activeTool = dynamic_cast<KisTool*>(priv()->activeTool);
if (!eventValid || !activeTool) return;
switch (state) {
case BEGIN:
if (action == KisTool::Primary) {
if (event->type() == QEvent::MouseButtonDblClick) {
activeTool->beginPrimaryDoubleClickAction(&ev);
} else {
activeTool->beginPrimaryAction(&ev);
}
} else {
if (event->type() == QEvent::MouseButtonDblClick) {
activeTool->beginAlternateDoubleClickAction(&ev, KisTool::actionToAlternateAction(action));
} else {
activeTool->beginAlternateAction(&ev, KisTool::actionToAlternateAction(action));
}
}
break;
case CONTINUE:
if (action == KisTool::Primary) {
activeTool->continuePrimaryAction(&ev);
} else {
activeTool->continueAlternateAction(&ev, KisTool::actionToAlternateAction(action));
}
break;
case END:
if (action == KisTool::Primary) {
activeTool->endPrimaryAction(&ev);
} else {
activeTool->endAlternateAction(&ev, KisTool::actionToAlternateAction(action));
}
break;
}
}
bool KisToolProxy::primaryActionSupportsHiResEvents() const
{
KisTool *activeTool = dynamic_cast<KisTool*>(const_cast<KisToolProxy*>(this)->priv()->activeTool);
return activeTool && activeTool->primaryActionSupportsHiResEvents();
}
void KisToolProxy::setActiveTool(KoToolBase *tool)
{
if (!tool) return;
if (m_isActionActivated) {
deactivateToolAction(m_lastAction);
KoToolProxy::setActiveTool(tool);
activateToolAction(m_lastAction);
} else {
KoToolProxy::setActiveTool(tool);
}
}
void KisToolProxy::activateToolAction(KisTool::ToolAction action)
{
KisTool *activeTool = dynamic_cast<KisTool*>(const_cast<KisToolProxy*>(this)->priv()->activeTool);
if (activeTool) {
if (action == KisTool::Primary) {
activeTool->activatePrimaryAction();
} else {
activeTool->activateAlternateAction(KisTool::actionToAlternateAction(action));
}
}
m_isActionActivated = true;
m_lastAction = action;
}
void KisToolProxy::deactivateToolAction(KisTool::ToolAction action)
{
KisTool *activeTool = dynamic_cast<KisTool*>(const_cast<KisToolProxy*>(this)->priv()->activeTool);
if (activeTool) {
if (action == KisTool::Primary) {
activeTool->deactivatePrimaryAction();
} else {
activeTool->deactivateAlternateAction(KisTool::actionToAlternateAction(action));
}
}
m_isActionActivated = false;
m_lastAction = action;
}
diff --git a/libs/ui/dialogs/kis_dlg_file_layer.cpp b/libs/ui/dialogs/kis_dlg_file_layer.cpp
index 435d29caf1..0c3c2f82e2 100644
--- a/libs/ui/dialogs/kis_dlg_file_layer.cpp
+++ b/libs/ui/dialogs/kis_dlg_file_layer.cpp
@@ -1,98 +1,117 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2013
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_dlg_file_layer.h"
#include <QLineEdit>
#include <QCheckBox>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <klocalizedstring.h>
#include <KoFileDialog.h>
#include <KisApplication.h>
#include <KisImportExportManager.h>
#include <kis_file_name_requester.h>
#include <kis_config_widget.h>
#include <kis_paint_device.h>
#include <kis_transaction.h>
#include <kis_node.h>
#include <kis_file_layer.h>
KisDlgFileLayer::KisDlgFileLayer(const QString &basePath, const QString & name, QWidget * parent)
: KoDialog(parent)
, m_basePath(basePath)
{
setButtons(Ok | Cancel);
setDefaultButton(Ok);
QWidget * page = new QWidget(this);
dlgWidget.setupUi(page);
dlgWidget.wdgUrlRequester->setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import));
setMainWidget(page);
//dlgWidget.wdgUrlRequester->setBasePath(m_basePath);
dlgWidget.wdgUrlRequester->setStartDir(m_basePath);
dlgWidget.txtLayerName->setText(name);
connect(dlgWidget.wdgUrlRequester, SIGNAL(textChanged(const QString &)),
SLOT(slotNameChanged(const QString &)));
enableButtonOk(false);
}
void KisDlgFileLayer::slotNameChanged(const QString & text)
{
enableButtonOk(!text.isEmpty());
}
QString KisDlgFileLayer::layerName() const
{
return dlgWidget.txtLayerName->text();
}
KisFileLayer::ScalingMethod KisDlgFileLayer::scaleToImageResolution() const
{
if (dlgWidget.radioDontScale->isChecked()) {
return KisFileLayer::None;
}
else if (dlgWidget.radioScaleToImageSize->isChecked()) {
return KisFileLayer::ToImageSize;
}
else {
return KisFileLayer::ToImagePPI;
}
}
+void KisDlgFileLayer::setFileName(QString fileName)
+{
+ dlgWidget.wdgUrlRequester->setFileName(fileName);
+}
+
+void KisDlgFileLayer::setScalingMethod(KisFileLayer::ScalingMethod method)
+{
+ dlgWidget.radioDontScale->setChecked(false);
+ dlgWidget.radioScaleToImageSize->setChecked(false);
+ dlgWidget.radioScalePPI->setChecked(false);
+ if (method == KisFileLayer::None) {
+ dlgWidget.radioDontScale->setChecked(true);
+ } else if (method == KisFileLayer::ToImageSize) {
+ dlgWidget.radioScaleToImageSize->setChecked(true);
+ } else {
+ dlgWidget.radioScalePPI->setChecked(true);
+ }
+}
+
QString KisDlgFileLayer::fileName() const
{
QString path = dlgWidget.wdgUrlRequester->fileName();
QFileInfo fi(path);
if (fi.isSymLink()) {
path = fi.symLinkTarget();
fi = QFileInfo(path);
}
if (!m_basePath.isEmpty() && fi.isAbsolute()) {
QDir directory(m_basePath);
path = directory.relativeFilePath(path);
}
return path;
}
diff --git a/libs/ui/dialogs/kis_dlg_file_layer.h b/libs/ui/dialogs/kis_dlg_file_layer.h
index b7a66d1887..b7d83aa686 100644
--- a/libs/ui/dialogs/kis_dlg_file_layer.h
+++ b/libs/ui/dialogs/kis_dlg_file_layer.h
@@ -1,60 +1,63 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2013
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_DLG_FILE_LAYER_H
#define KIS_DLG_FILE_LAYER_H
#include <KoDialog.h>
#include <QString>
#include <kis_file_layer.h>
#include "ui_wdgdlgfilelayer.h"
/**
* Create a new file layer
*/
class KisDlgFileLayer : public KoDialog
{
public:
Q_OBJECT
public:
/**
* Create a new file layer
* @param name the proposed name for this layer
* @param parent the widget parent of this dialog
*/
KisDlgFileLayer(const QString &basePath, const QString &name, QWidget *parent = 0);
QString fileName() const;
QString layerName() const;
KisFileLayer::ScalingMethod scaleToImageResolution() const;
+ void setFileName(QString fileName);
+ void setScalingMethod(KisFileLayer::ScalingMethod method);
+
protected Q_SLOTS:
void slotNameChanged(const QString &);
private:
Ui_WdgDlgFileLayer dlgWidget;
QString m_basePath;
};
#endif
diff --git a/libs/ui/dialogs/kis_dlg_image_properties.cc b/libs/ui/dialogs/kis_dlg_image_properties.cc
index 7243891c01..16f8eb7d93 100644
--- a/libs/ui/dialogs/kis_dlg_image_properties.cc
+++ b/libs/ui/dialogs/kis_dlg_image_properties.cc
@@ -1,177 +1,203 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dlg_image_properties.h"
#include <QPushButton>
#include <QRadioButton>
#include <QLayout>
#include <QLabel>
#include <QSpinBox>
#include <QSlider>
#include <QCheckBox>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <klocalizedstring.h>
#include <KoColorSpace.h>
-#include "KoColorProfile.h"
-#include "KoColorSpaceRegistry.h"
-#include "KoColor.h"
-#include "KoColorConversionTransformation.h"
-#include "KoColorPopupAction.h"
-#include "kis_icon_utils.h"
-#include "KoID.h"
-#include "kis_image.h"
-#include "kis_annotation.h"
-#include "kis_config.h"
-#include "kis_signal_compressor.h"
+#include <KoColorProfile.h>
+#include <KoColorSpaceRegistry.h>
+#include <KoColor.h>
+#include <KoColorConversionTransformation.h>
+#include <KoColorPopupAction.h>
+#include <kis_icon_utils.h>
+#include <KoID.h>
+#include <kis_image.h>
+#include <kis_annotation.h>
+#include <kis_config.h>
+#include <kis_signal_compressor.h>
+#include <kis_image_config.h>
+
#include "widgets/kis_cmb_idlist.h"
#include "widgets/squeezedcombobox.h"
#include "kis_layer_utils.h"
+
KisDlgImageProperties::KisDlgImageProperties(KisImageWSP image, QWidget *parent, const char *name)
: KoDialog(parent)
{
setButtons(Ok | Cancel);
setDefaultButton(Ok);
setObjectName(name);
setCaption(i18n("Image Properties"));
m_page = new WdgImageProperties(this);
m_image = image;
setMainWidget(m_page);
resize(m_page->sizeHint());
KisConfig cfg;
m_page->lblWidthValue->setText(QString::number(image->width()));
m_page->lblHeightValue->setText(QString::number(image->height()));
m_page->lblResolutionValue->setText(QLocale().toString(image->xRes()*72, 2)); // XXX: separate values for x & y?
//Set the canvas projection color: backgroundColor
KoColor background = m_image->defaultProjectionColor();
background.setOpacity(1.0);
m_page->bnBackgroundColor->setColor(background);
m_page->sldBackgroundColor->setRange(0.0,1.0,2);
m_page->sldBackgroundColor->setSingleStep(0.05);
m_page->sldBackgroundColor->setValue(m_image->defaultProjectionColor().opacityF());
KisSignalCompressor *compressor = new KisSignalCompressor(500 /* ms */, KisSignalCompressor::POSTPONE, this);
connect(m_page->bnBackgroundColor, SIGNAL(changed(KoColor)), compressor, SLOT(start()));
connect(m_page->sldBackgroundColor, SIGNAL(valueChanged(qreal)), compressor, SLOT(start()));
connect(compressor, SIGNAL(timeout()), this, SLOT(setCurrentColor()));
//Set the color space
m_page->colorSpaceSelector->setCurrentColorSpace(image->colorSpace());
//set the proofing space
m_proofingConfig = m_image->proofingConfiguration();
- m_page->proofSpaceSelector->setCurrentColorSpace(KoColorSpaceRegistry::instance()->colorSpace(m_proofingConfig->proofingModel, m_proofingConfig->proofingDepth, m_proofingConfig->proofingProfile));
+ if (!m_proofingConfig) {
+ m_page->chkSaveProofing->setChecked(false);
+ m_proofingConfig = KisImageConfig().defaultProofingconfiguration();
+ }
+ else {
+ m_page->chkSaveProofing->setChecked(true);
+ }
+ m_page->proofSpaceSelector->setCurrentColorSpace(KoColorSpaceRegistry::instance()->colorSpace(m_proofingConfig->proofingModel, m_proofingConfig->proofingDepth, m_proofingConfig->proofingProfile));
m_page->cmbIntent->setCurrentIndex((int)m_proofingConfig->intent);
-
m_page->ckbBlackPointComp->setChecked(m_proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation));
m_page->gamutAlarm->setColor(m_proofingConfig->warningColor);
m_page->gamutAlarm->setToolTip(i18n("Set color used for warning"));
m_page->sldAdaptationState->setMaximum(20);
m_page->sldAdaptationState->setMinimum(0);
m_page->sldAdaptationState->setValue((int)m_proofingConfig->adaptationState*20);
KisSignalCompressor *softProofConfigCompressor = new KisSignalCompressor(500, KisSignalCompressor::POSTPONE,this);
connect(m_page->gamutAlarm, SIGNAL(changed(KoColor)), softProofConfigCompressor, SLOT(start()));
connect(m_page->proofSpaceSelector, SIGNAL(colorSpaceChanged(const KoColorSpace*)), softProofConfigCompressor, SLOT(start()));
connect(m_page->cmbIntent, SIGNAL(currentIndexChanged(int)), softProofConfigCompressor, SLOT(start()));
connect(m_page->ckbBlackPointComp, SIGNAL(stateChanged(int)), softProofConfigCompressor, SLOT(start()));
connect(m_page->sldAdaptationState, SIGNAL(valueChanged(int)), softProofConfigCompressor, SLOT(start()));
connect(softProofConfigCompressor, SIGNAL(timeout()), this, SLOT(setProofingConfig()));
//annotations
vKisAnnotationSP_it beginIt = image->beginAnnotations();
vKisAnnotationSP_it endIt = image->endAnnotations();
vKisAnnotationSP_it it = beginIt;
while (it != endIt) {
if (!(*it) || (*it)->type().isEmpty()) {
dbgFile << "Warning: empty annotation";
it++;
continue;
}
m_page->cmbAnnotations->addItem((*it) -> type());
it++;
}
connect(m_page->cmbAnnotations, SIGNAL(activated(QString)), SLOT(setAnnotation(QString)));
setAnnotation(m_page->cmbAnnotations->currentText());
}
KisDlgImageProperties::~KisDlgImageProperties()
{
delete m_page;
}
const KoColorSpace * KisDlgImageProperties::colorSpace()
{
return m_page->colorSpaceSelector->currentColorSpace();
}
void KisDlgImageProperties::setCurrentColor()
{
KoColor background = m_page->bnBackgroundColor->color();
background.setOpacity(m_page->sldBackgroundColor->value());
KisLayerUtils::changeImageDefaultProjectionColor(m_image, background);
}
void KisDlgImageProperties::setProofingConfig()
{
- m_proofingConfig->conversionFlags = KoColorConversionTransformation::HighQuality;
- if (m_page->ckbBlackPointComp) m_proofingConfig->conversionFlags |= KoColorConversionTransformation::BlackpointCompensation;
- m_proofingConfig->intent = (KoColorConversionTransformation::Intent)m_page->cmbIntent->currentIndex();
- m_proofingConfig->proofingProfile = m_page->proofSpaceSelector->currentColorSpace()->profile()->name();
- m_proofingConfig->proofingModel = m_page->proofSpaceSelector->currentColorSpace()->colorModelId().id();
- m_proofingConfig->proofingDepth = "U8";//default to this
- m_proofingConfig->warningColor = m_page->gamutAlarm->color();
- m_proofingConfig->adaptationState = (double)m_page->sldAdaptationState->value()/20.0;
- qDebug()<<"set proofing config in properties: "<<m_proofingConfig->proofingProfile;
- m_image->setProofingConfiguration(m_proofingConfig);
+ if (m_firstProofingConfigChange) {
+ m_page->chkSaveProofing->setChecked(true);
+ m_firstProofingConfigChange = false;
+ }
+ if (m_page->chkSaveProofing->isChecked()) {
+
+ m_proofingConfig->conversionFlags = KoColorConversionTransformation::HighQuality;
+#if QT_VERSION >= 0x07000
+ m_proofingConfig->conversionFlags.setFlag(KoColorConversionTransformation::BlackpointCompensation, m_page->ckbBlackPointComp->isChecked());
+#else
+ m_page->chkBlackPointComp->isChecked() ?
+ m_proofingConfig->conversionFlags |= KoColorConversionTransformation::BlackpointCompensation
+ : m_proofingConfig->conversionFlags = m_proofingConfig->conversionFlags & ~KoColorConversionTransformation::BlackpointCompensation;
+
+#endif
+ m_proofingConfig->intent = (KoColorConversionTransformation::Intent)m_page->cmbIntent->currentIndex();
+ m_proofingConfig->proofingProfile = m_page->proofSpaceSelector->currentColorSpace()->profile()->name();
+ m_proofingConfig->proofingModel = m_page->proofSpaceSelector->currentColorSpace()->colorModelId().id();
+ m_proofingConfig->proofingDepth = "U8";//default to this
+ m_proofingConfig->warningColor = m_page->gamutAlarm->color();
+ m_proofingConfig->adaptationState = (double)m_page->sldAdaptationState->value()/20.0;
+
+ m_image->setProofingConfiguration(m_proofingConfig);
+ }
+ else {
+ m_image->setProofingConfiguration(0);
+ }
}
void KisDlgImageProperties::setAnnotation(const QString &type)
{
KisAnnotationSP annotation = m_image->annotation(type);
if (annotation) {
m_page->lblDescription->clear();
m_page->txtAnnotation->clear();
m_page->lblDescription->setText(annotation->description());
m_page->txtAnnotation->appendPlainText(annotation->displayText());
}
else {
m_page->lblDescription->clear();
m_page->txtAnnotation->clear();
}
}
diff --git a/libs/ui/dialogs/kis_dlg_image_properties.h b/libs/ui/dialogs/kis_dlg_image_properties.h
index 1c9c50e26f..5084e61ccd 100644
--- a/libs/ui/dialogs/kis_dlg_image_properties.h
+++ b/libs/ui/dialogs/kis_dlg_image_properties.h
@@ -1,65 +1,66 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_DLG_IMAGE_PROPERTIES_H_
#define KIS_DLG_IMAGE_PROPERTIES_H_
#include <KoDialog.h>
#include "KisProofingConfiguration.h"
#include <kis_types.h>
#include "ui_wdgimageproperties.h"
class KoColorSpace;
class WdgImageProperties : public QWidget, public Ui::WdgImageProperties
{
Q_OBJECT
public:
WdgImageProperties(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class KisDlgImageProperties : public KoDialog
{
Q_OBJECT
public:
KisDlgImageProperties(KisImageWSP image,
QWidget *parent = 0,
const char *name = 0);
~KisDlgImageProperties() override;
const KoColorSpace * colorSpace();
private Q_SLOTS:
void setAnnotation(const QString& type);
void setCurrentColor();
void setProofingConfig();
private:
- WdgImageProperties * m_page;
+ WdgImageProperties *m_page;
KisImageWSP m_image;
KisProofingConfigurationSP m_proofingConfig;
+ bool m_firstProofingConfigChange {true};
};
#endif // KIS_DLG_IMAGE_PROPERTIES_H_
diff --git a/libs/ui/dialogs/kis_dlg_import_image_sequence.cpp b/libs/ui/dialogs/kis_dlg_import_image_sequence.cpp
index d518cfbc09..5fa8ae5db9 100644
--- a/libs/ui/dialogs/kis_dlg_import_image_sequence.cpp
+++ b/libs/ui/dialogs/kis_dlg_import_image_sequence.cpp
@@ -1,163 +1,163 @@
/*
* Copyright (c) 2016 Jouni Pentikäinen <joupent@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dlg_import_image_sequence.h"
#include "KisDocument.h"
#include "KisMainWindow.h"
#include "kis_image.h"
#include "kis_image_animation_interface.h"
#include "KisImportExportManager.h"
#include "KoFileDialog.h"
-#include <QDesktopServices>
+#include <QStandardPaths>
class KisDlgImportImageSequence::ListItem : QListWidgetItem {
public:
ListItem(const QString &text, QListWidget *view, QCollator *collator)
: QListWidgetItem(text, view),
collator(collator)
{}
bool operator <(const QListWidgetItem &other) const override
{
int cmp = collator->compare(this->text(), other.text());
return cmp < 0;
}
private:
QCollator *collator;
};
KisDlgImportImageSequence::KisDlgImportImageSequence(KisMainWindow *mainWindow, KisDocument *document) :
KoDialog(mainWindow),
m_mainWindow(mainWindow),
m_document(document)
{
setButtons(Ok | Cancel);
setDefaultButton(Ok);
QWidget * page = new QWidget(this);
m_ui.setupUi(page);
setMainWidget(page);
enableButtonOk(false);
m_ui.cmbOrder->addItem(i18n("Ascending"), Ascending);
m_ui.cmbOrder->addItem(i18n("Descending"), Descending);
m_ui.cmbOrder->setCurrentIndex(0);
m_ui.cmbSortMode->addItem(i18n("Alphabetical"), Natural);
m_ui.cmbSortMode->addItem(i18n("Numerical"), Numerical);
m_ui.cmbSortMode->setCurrentIndex(1);
m_ui.lstFiles->setSelectionMode(QAbstractItemView::ExtendedSelection);
connect(m_ui.btnAddImages, &QAbstractButton::clicked, this, &KisDlgImportImageSequence::slotAddFiles);
connect(m_ui.btnRemove, &QAbstractButton::clicked, this, &KisDlgImportImageSequence::slotRemoveFiles);
connect(m_ui.spinStep, SIGNAL(valueChanged(int)), this, SLOT(slotSkipChanged(int)));
connect(m_ui.cmbOrder, SIGNAL(currentIndexChanged(int)), this, SLOT(slotOrderOptionsChanged(int)));
connect(m_ui.cmbSortMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotOrderOptionsChanged(int)));
// cold initialization of the controls
slotSkipChanged(m_ui.spinStep->value());
slotOrderOptionsChanged(m_ui.cmbOrder->currentIndex());
slotOrderOptionsChanged(m_ui.cmbSortMode->currentIndex());
}
QStringList KisDlgImportImageSequence::files()
{
QStringList list;
for (int i=0; i < m_ui.lstFiles->count(); i++) {
list.append(m_ui.lstFiles->item(i)->text());
}
return list;
}
int KisDlgImportImageSequence::firstFrame()
{
return m_ui.spinFirstFrame->value();
}
int KisDlgImportImageSequence::step()
{
return m_ui.spinStep->value();
}
void KisDlgImportImageSequence::slotAddFiles()
{
QStringList urls = showOpenFileDialog();
if (!urls.isEmpty()) {
Q_FOREACH(QString url, urls) {
new ListItem(url, m_ui.lstFiles, &m_collator);
}
sortFileList();
}
enableButtonOk(m_ui.lstFiles->count() > 0);
}
QStringList KisDlgImportImageSequence::showOpenFileDialog()
{
KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument");
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import));
dialog.setCaption(i18n("Import Images"));
return dialog.filenames();
}
void KisDlgImportImageSequence::slotRemoveFiles()
{
QList<QListWidgetItem*> selected = m_ui.lstFiles->selectedItems();
Q_FOREACH(QListWidgetItem *item, selected) {
delete item;
}
enableButtonOk(m_ui.lstFiles->count() > 0);
}
void KisDlgImportImageSequence::slotSkipChanged(int)
{
int documentFps = m_document->image()->animationInterface()->framerate();
float sourceFps = 1.0f * documentFps / m_ui.spinStep->value();
m_ui.lblFramerate->setText(i18n("Source fps: %1", sourceFps));
}
void KisDlgImportImageSequence::slotOrderOptionsChanged(int)
{
sortFileList();
}
void KisDlgImportImageSequence::sortFileList()
{
int order = m_ui.cmbOrder->currentData().toInt();
bool numeric = m_ui.cmbSortMode->currentData().toInt() == Numerical;
m_collator.setNumericMode(numeric);
m_ui.lstFiles->sortItems((order == Ascending) ? Qt::AscendingOrder : Qt::DescendingOrder);
}
diff --git a/libs/ui/dialogs/kis_dlg_layer_properties.cc b/libs/ui/dialogs/kis_dlg_layer_properties.cc
index 32d387c2ff..dc517694c9 100644
--- a/libs/ui/dialogs/kis_dlg_layer_properties.cc
+++ b/libs/ui/dialogs/kis_dlg_layer_properties.cc
@@ -1,296 +1,296 @@
/*
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dlg_layer_properties.h"
#include <limits.h>
#include <QLabel>
#include <QLayout>
#include <QString>
#include <QGroupBox>
#include <QVBoxLayout>
#include <klocalizedstring.h>
#include <KoColorSpace.h>
#include "KisViewManager.h"
#include <kis_debug.h>
#include <kis_global.h>
#include "widgets/kis_cmb_composite.h"
#include "KoColorProfile.h"
#include "kis_multinode_property.h"
#include "kis_layer_utils.h"
#include "kis_image.h"
#include "kis_layer_properties_icons.h"
#include "kis_signal_compressor.h"
#include "commands_new/kis_saved_commands.h"
#include "kis_post_execution_undo_adapter.h"
struct KisDlgLayerProperties::Private
{
Private() : updatesCompressor(500, KisSignalCompressor::POSTPONE) {}
KisNodeList nodes;
const KoColorSpace *colorSpace;
KisViewManager *view;
WdgLayerProperties *page;
QSharedPointer<KisMultinodeCompositeOpProperty> compositeOpProperty;
QSharedPointer<KisMultinodeOpacityProperty> opacityProperty;
QSharedPointer<KisMultinodeNameProperty> nameProperty;
QSharedPointer<KisMultinodeColorLabelProperty> colorLabelProperty;
QList<KisMultinodePropertyInterfaceSP> layerProperties;
QList<QPointer<QCheckBox> > layerPropCheckboxes;
QList<KisMultinodePropertyInterfaceSP> channelFlagsProps;
QList<QPointer<QCheckBox> > channelFlagsCheckboxes;
KisSignalCompressor updatesCompressor;
QList<KisMultinodePropertyInterfaceSP> allProperties() const {
QList<KisMultinodePropertyInterfaceSP> props;
props << compositeOpProperty;
props << opacityProperty;
props << nameProperty;
props << layerProperties;
props << channelFlagsProps;
props << colorLabelProperty;
return props;
}
};
-KisDlgLayerProperties::KisDlgLayerProperties(KisNodeList nodes, KisViewManager *view, QWidget *parent, const char *name, Qt::WFlags f)
+KisDlgLayerProperties::KisDlgLayerProperties(KisNodeList nodes, KisViewManager *view, QWidget *parent, const char *name, Qt::WindowFlags f)
: KoDialog(parent)
, d(new Private())
{
nodes = KisLayerUtils::sortMergableNodes(view->image()->root(), nodes);
d->nodes = nodes;
Q_UNUSED(f);
setCaption(i18n("Layer Properties"));
setButtons(Ok | Cancel);
setDefaultButton(Ok);
setModal(false);
setObjectName(name);
d->page = new WdgLayerProperties(this);
setMainWidget(d->page);
d->view = view;
d->colorSpace = d->nodes.first()->colorSpace();
d->page->editName->setFocus();
d->nameProperty.reset(new KisMultinodeNameProperty(nodes));
d->nameProperty->connectIgnoreCheckBox(d->page->chkName);
d->nameProperty->connectAutoEnableWidget(d->page->editName);
d->nameProperty->connectValueChangedSignal(this, SLOT(slotNameValueChangedInternally()));
connect(d->page->editName, SIGNAL(textChanged(const QString &)), SLOT(slotNameValueChangedExternally()));
d->page->intOpacity->setRange(0, 100);
d->page->intOpacity->setSuffix("%");
d->opacityProperty.reset(new KisMultinodeOpacityProperty(nodes));
d->opacityProperty->connectIgnoreCheckBox(d->page->chkOpacity);
d->opacityProperty->connectAutoEnableWidget(d->page->intOpacity);
d->opacityProperty->connectValueChangedSignal(this, SLOT(slotOpacityValueChangedInternally()));
d->opacityProperty->connectValueChangedSignal(&d->updatesCompressor, SLOT(start()));
connect(d->page->intOpacity, SIGNAL(valueChanged(int)), SLOT(slotOpacityValueChangedExternally()));
d->compositeOpProperty.reset(new KisMultinodeCompositeOpProperty(nodes));
d->compositeOpProperty->connectIgnoreCheckBox(d->page->chkCompositeOp);
d->compositeOpProperty->connectAutoEnableWidget(d->page->cmbComposite);
d->compositeOpProperty->connectValueChangedSignal(this, SLOT(slotCompositeOpValueChangedInternally()));
d->compositeOpProperty->connectValueChangedSignal(&d->updatesCompressor, SLOT(start()));
connect(d->page->cmbComposite, SIGNAL(currentIndexChanged(int)), SLOT(slotCompositeOpValueChangedExternally()));
d->page->colorLabelSelector->setFocusPolicy(Qt::StrongFocus);
d->colorLabelProperty.reset(new KisMultinodeColorLabelProperty(nodes));
d->colorLabelProperty->connectIgnoreCheckBox(d->page->chkColorLabel);
d->colorLabelProperty->connectAutoEnableWidget(d->page->colorLabelSelector);
d->colorLabelProperty->connectValueChangedSignal(this, SLOT(slotColorLabelValueChangedInternally()));
d->colorLabelProperty->connectValueChangedSignal(&d->updatesCompressor, SLOT(start()));
connect(d->page->colorLabelSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelValueChangedExternally()));
if (!KisLayerUtils::checkNodesDiffer<const KoColorSpace*>(d->nodes, [](KisNodeSP node) { return node->colorSpace(); })) {
d->page->lblColorSpace->setText(d->colorSpace->name());
if (const KoColorProfile* profile = d->colorSpace->profile()) {
d->page->lblProfile->setText(profile->name());
}
ChannelFlagAdapter::PropertyList props = ChannelFlagAdapter::adaptersList(nodes);
if (!props.isEmpty()) {
QVBoxLayout *vbox = new QVBoxLayout;
Q_FOREACH (const ChannelFlagAdapter::Property &prop, props) {
QCheckBox *chk = new QCheckBox(prop.name, this);
vbox->addWidget(chk);
KisMultinodePropertyInterface *multiprop =
new KisMultinodeProperty<ChannelFlagAdapter>(
nodes,
ChannelFlagAdapter(prop));
multiprop->connectIgnoreCheckBox(chk);
multiprop->connectValueChangedSignal(this, SLOT(slotFlagsValueChangedInternally()));
multiprop->connectValueChangedSignal(&d->updatesCompressor, SLOT(start()));
d->channelFlagsCheckboxes << chk;
d->channelFlagsProps << toQShared(multiprop);
}
d->page->grpActiveChannels->setLayout(vbox);
} else {
d->page->grpActiveChannels->setVisible(false);
d->page->lineActiveChannels->setVisible(false);
}
} else {
d->page->grpActiveChannels->setVisible(false);
d->page->lineActiveChannels->setVisible(false);
d->page->cmbComposite->setEnabled(false);
d->page->chkCompositeOp->setEnabled(false);
d->page->lblColorSpace->setText(i18n("*varies*"));
d->page->lblProfile->setText(i18n("*varies*"));
}
{
QVBoxLayout *vbox = new QVBoxLayout;
KisBaseNode::PropertyList props = LayerPropertyAdapter::adaptersList(nodes);
Q_FOREACH (const KisBaseNode::Property &prop, props) {
QCheckBox *chk = new QCheckBox(prop.name, this);
chk->setIcon(prop.onIcon);
vbox->addWidget(chk);
KisMultinodePropertyInterface *multiprop =
new KisMultinodeProperty<LayerPropertyAdapter>(
nodes,
LayerPropertyAdapter(prop.name));
multiprop->connectIgnoreCheckBox(chk);
multiprop->connectValueChangedSignal(this, SLOT(slotPropertyValueChangedInternally()));
multiprop->connectValueChangedSignal(&d->updatesCompressor, SLOT(start()));
d->layerPropCheckboxes << chk;
d->layerProperties << toQShared(multiprop);
}
d->page->grpProperties->setLayout(vbox);
}
connect(&d->updatesCompressor, SIGNAL(timeout()), SLOT(updatePreview()));
}
KisDlgLayerProperties::~KisDlgLayerProperties()
{
if (result() == QDialog::Accepted) {
if (d->updatesCompressor.isActive()) {
d->updatesCompressor.stop();
updatePreview();
}
KisPostExecutionUndoAdapter *adapter =
d->view->image()->postExecutionUndoAdapter();
KisSavedMacroCommand *macro = adapter->createMacro(kundo2_i18n("Change Layer Properties"));
macro->addCommand(toQShared(new KisLayerUtils::KisSimpleUpdateCommand(d->nodes, false)));
Q_FOREACH(auto prop, d->allProperties()) {
if (!prop->isIgnored()) {
macro->addCommand(toQShared(prop->createPostExecutionUndoCommand()));
}
}
macro->addCommand(toQShared(new KisLayerUtils::KisSimpleUpdateCommand(d->nodes, true)));
adapter->addMacro(macro);
}
else /* if (result() == QDialog::Rejected) */ {
Q_FOREACH(auto prop, d->allProperties()) {
prop->setIgnored(true);
}
updatePreview();
}
}
void KisDlgLayerProperties::slotCompositeOpValueChangedInternally()
{
d->page->cmbComposite->validate(d->colorSpace);
d->page->cmbComposite->selectCompositeOp(KoID(d->compositeOpProperty->value()));
d->page->cmbComposite->setEnabled(!d->compositeOpProperty->isIgnored());
}
void KisDlgLayerProperties::slotCompositeOpValueChangedExternally()
{
if (d->compositeOpProperty->isIgnored()) return;
d->compositeOpProperty->setValue(d->page->cmbComposite->selectedCompositeOp().id());
}
void KisDlgLayerProperties::slotColorLabelValueChangedInternally()
{
d->page->colorLabelSelector->setCurrentIndex(d->colorLabelProperty->value());
d->page->colorLabelSelector->setEnabled(!d->colorLabelProperty->isIgnored());
}
void KisDlgLayerProperties::slotColorLabelValueChangedExternally()
{
if (d->colorLabelProperty->isIgnored()) return;
d->colorLabelProperty->setValue(d->page->colorLabelSelector->currentIndex());
}
void KisDlgLayerProperties::slotOpacityValueChangedInternally()
{
d->page->intOpacity->setValue(d->opacityProperty->value());
d->page->intOpacity->setEnabled(!d->opacityProperty->isIgnored());
}
void KisDlgLayerProperties::slotOpacityValueChangedExternally()
{
if (d->opacityProperty->isIgnored()) return;
d->opacityProperty->setValue(d->page->intOpacity->value());
}
void KisDlgLayerProperties::slotNameValueChangedInternally()
{
d->page->editName->setText(d->nameProperty->value());
d->page->editName->setEnabled(!d->nameProperty->isIgnored());
}
void KisDlgLayerProperties::slotNameValueChangedExternally()
{
if (d->nameProperty->isIgnored()) return;
d->nameProperty->setValue(d->page->editName->text());
}
void KisDlgLayerProperties::slotPropertyValueChangedInternally()
{
Q_FOREACH (KisMultinodePropertyInterfaceSP prop, d->channelFlagsProps) {
prop->rereadCurrentValue();
}
}
void KisDlgLayerProperties::slotFlagsValueChangedInternally()
{
Q_FOREACH (KisMultinodePropertyInterfaceSP prop, d->layerProperties) {
prop->rereadCurrentValue();
}
}
void KisDlgLayerProperties::updatePreview()
{
KisLayerUtils::KisSimpleUpdateCommand::updateNodes(d->nodes);
}
diff --git a/libs/ui/dialogs/kis_dlg_layer_properties.h b/libs/ui/dialogs/kis_dlg_layer_properties.h
index 2ba053f73c..4ed5bd1aeb 100644
--- a/libs/ui/dialogs/kis_dlg_layer_properties.h
+++ b/libs/ui/dialogs/kis_dlg_layer_properties.h
@@ -1,86 +1,86 @@
/*
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_DLG_LAYER_PROPERTIES_H_
#define KIS_DLG_LAYER_PROPERTIES_H_
#include <QList>
#include <QCheckBox>
#include <QScopedPointer>
#include "kis_types.h"
#include <KoDialog.h>
#include "ui_wdglayerproperties.h"
class QWidget;
class QBitArray;
class KisViewManager;
class KisDocument;
class WdgLayerProperties : public QWidget, public Ui::WdgLayerProperties
{
Q_OBJECT
public:
WdgLayerProperties(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
/**
* KisDlgLayerProperties is a dialogue for displaying and modifying information on a KisLayer.
* The dialog is non modal by default and uses a timer to check for user changes to the
* configuration, showing a preview of them.
*/
class KisDlgLayerProperties : public KoDialog
{
Q_OBJECT
public:
- KisDlgLayerProperties(KisNodeList nodes, KisViewManager *view, QWidget *parent = 0, const char *name = 0, Qt::WFlags f = 0);
+ KisDlgLayerProperties(KisNodeList nodes, KisViewManager *view, QWidget *parent = 0, const char *name = 0, Qt::WindowFlags f = 0);
~KisDlgLayerProperties() override;
protected Q_SLOTS:
void updatePreview();
void slotCompositeOpValueChangedInternally();
void slotCompositeOpValueChangedExternally();
void slotColorLabelValueChangedInternally();
void slotColorLabelValueChangedExternally();
void slotOpacityValueChangedInternally();
void slotOpacityValueChangedExternally();
void slotNameValueChangedInternally();
void slotNameValueChangedExternally();
void slotPropertyValueChangedInternally();
void slotFlagsValueChangedInternally();
private:
struct Private;
const QScopedPointer<Private> d;
};
#endif // KIS_DLG_LAYER_PROPERTIES_H_
diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc
index 7d4f811811..90f0693638 100644
--- a/libs/ui/dialogs/kis_dlg_preferences.cc
+++ b/libs/ui/dialogs/kis_dlg_preferences.cc
@@ -1,1260 +1,1271 @@
/*
* preferencesdlg.cc - part of KImageShop
*
* Copyright (c) 1999 Michael Koch <koch@kde.org>
* Copyright (c) 2003-2011 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dlg_preferences.h"
#include <opengl/kis_opengl.h>
#include <QBitmap>
#include <QCheckBox>
#include <QCursor>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QSlider>
#include <QToolButton>
#include <QThread>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QGridLayout>
#include <QRadioButton>
#include <QGroupBox>
#include <QMdiArea>
#include <QMessageBox>
#include <QDesktopWidget>
#include <QFileDialog>
#include <QFormLayout>
#include <QSettings>
#include <KisDocument.h>
#include <KoColorProfile.h>
#include <KisApplication.h>
#include <KoFileDialog.h>
#include <KisPart.h>
#include <KoColorSpaceEngine.h>
#include <kis_icon.h>
#include <KoConfig.h>
#include "KoID.h"
#include <KoConfigAuthorPage.h>
#include <KoVBox.h>
#include <klocalizedstring.h>
#include <kundo2stack.h>
#include <KoResourcePaths.h>
#include "kis_action_registry.h"
#include "widgets/squeezedcombobox.h"
#include "kis_clipboard.h"
#include "widgets/kis_cmb_idlist.h"
#include "KoColorSpace.h"
#include "KoColorSpaceRegistry.h"
#include "KoColorConversionTransformation.h"
#include "kis_cursor.h"
#include "kis_config.h"
#include "kis_canvas_resource_provider.h"
#include "kis_preference_set_registry.h"
#include "kis_color_manager.h"
#include "KisProofingConfiguration.h"
#include "kis_image_config.h"
#include "slider_and_spin_box_sync.h"
// for the performance update
#include <kis_cubic_curve.h>
#include <kis_signals_blocker.h>
#include "input/config/kis_input_configuration_page.h"
#ifdef Q_OS_WIN
# include <kis_tablet_support_win8.h>
#endif
GeneralTab::GeneralTab(QWidget *_parent, const char *_name)
: WdgGeneralSettings(_parent, _name)
{
KisConfig cfg;
m_cmbCursorShape->addItem(i18n("No Cursor"));
m_cmbCursorShape->addItem(i18n("Tool Icon"));
m_cmbCursorShape->addItem(i18n("Arrow"));
m_cmbCursorShape->addItem(i18n("Small Circle"));
m_cmbCursorShape->addItem(i18n("Crosshair"));
m_cmbCursorShape->addItem(i18n("Triangle Righthanded"));
m_cmbCursorShape->addItem(i18n("Triangle Lefthanded"));
m_cmbCursorShape->addItem(i18n("Black Pixel"));
m_cmbCursorShape->addItem(i18n("White Pixel"));
m_cmbOutlineShape->addItem(i18n("No Outline"));
m_cmbOutlineShape->addItem(i18n("Circle Outline"));
m_cmbOutlineShape->addItem(i18n("Preview Outline"));
m_cmbOutlineShape->addItem(i18n("Tilt Outline"));
m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle());
m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle());
chkShowRootLayer->setChecked(cfg.showRootLayer());
int autosaveInterval = cfg.autoSaveInterval();
//convert to minutes
m_autosaveSpinBox->setValue(autosaveInterval / 60);
m_autosaveCheckBox->setChecked(autosaveInterval > 0);
m_undoStackSize->setValue(cfg.undoStackLimit());
m_backupFileCheckBox->setChecked(cfg.backupFile());
m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting());
m_hideSplashScreen->setChecked(cfg.hideSplashScreen());
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", true));
intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000));
m_cmbMDIType->setCurrentIndex(cfg.readEntry<int>("mdi_viewmode", (int)QMdiArea::TabbedView));
m_chkRubberBand->setChecked(cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
m_favoritePresetsSpinBox->setValue(cfg.favoritePresets());
KoColor mdiColor;
mdiColor.fromQColor(cfg.getMDIBackgroundColor());
m_mdiColor->setColor(mdiColor);
m_backgroundimage->setText(cfg.getMDIBackgroundImage());
m_chkCanvasMessages->setChecked(cfg.showCanvasMessages());
m_chkCompressKra->setChecked(cfg.compressKra());
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", false).toBool());
m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool());
m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker());
m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt());
chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas());
m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport());
m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground());
KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8());
cursorColor.fromQColor(cfg.getCursorMainColor());
cursorColorBtutton->setColor(cursorColor);
connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage()));
connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage()));
}
void GeneralTab::setDefault()
{
KisConfig cfg;
m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true));
m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true));
chkShowRootLayer->setChecked(cfg.showRootLayer(true));
m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0);
//convert to minutes
m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60);
m_undoStackSize->setValue(cfg.undoStackLimit(true));
m_backupFileCheckBox->setChecked(cfg.backupFile(true));
m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true));
m_hideSplashScreen->setChecked(cfg.hideSplashScreen(true));
m_chkNativeFileDialog->setChecked(false);
intMaxBrushSize->setValue(1000);
m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView);
m_chkRubberBand->setChecked(cfg.useOpenGL(true));
m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true));
KoColor mdiColor;
mdiColor.fromQColor(cfg.getMDIBackgroundColor(true));
m_mdiColor->setColor(mdiColor);
m_backgroundimage->setText(cfg.getMDIBackgroundImage(true));
m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true));
m_chkCompressKra->setChecked(cfg.compressKra(true));
m_chkHiDPI->setChecked(false);
m_chkSingleApplication->setChecked(true);
m_chkHiDPI->setChecked(true);
m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true));
m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true));
chkEnableTouch->setChecked(!cfg.disableTouchOnCanvas(true));
m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true));
m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground(true));
KoColor cursorColor(KoColorSpaceRegistry::instance()->rgb8());
cursorColor.fromQColor(cfg.getCursorMainColor(true));
cursorColorBtutton->setColor(cursorColor);
}
CursorStyle GeneralTab::cursorStyle()
{
return (CursorStyle)m_cmbCursorShape->currentIndex();
}
OutlineStyle GeneralTab::outlineStyle()
{
return (OutlineStyle)m_cmbOutlineShape->currentIndex();
}
bool GeneralTab::showRootLayer()
{
return chkShowRootLayer->isChecked();
}
int GeneralTab::autoSaveInterval()
{
//convert to seconds
return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0;
}
int GeneralTab::undoStackSize()
{
return m_undoStackSize->value();
}
bool GeneralTab::showOutlineWhilePainting()
{
return m_showOutlinePainting->isChecked();
}
bool GeneralTab::hideSplashScreen()
{
return m_hideSplashScreen->isChecked();
}
int GeneralTab::mdiMode()
{
return m_cmbMDIType->currentIndex();
}
int GeneralTab::favoritePresets()
{
return m_favoritePresetsSpinBox->value();
}
bool GeneralTab::showCanvasMessages()
{
return m_chkCanvasMessages->isChecked();
}
bool GeneralTab::compressKra()
{
return m_chkCompressKra->isChecked();
}
bool GeneralTab::toolOptionsInDocker()
{
return m_radioToolOptionsInDocker->isChecked();
}
bool GeneralTab::switchSelectionCtrlAlt()
{
return m_chkSwitchSelectionCtrlAlt->isChecked();
}
bool GeneralTab::convertToImageColorspaceOnImport()
{
return m_chkConvertOnImport->isChecked();
}
bool GeneralTab::calculateAnimationCacheInBackground()
{
return m_chkCacheAnimatioInBackground->isChecked();
}
void GeneralTab::getBackgroundImage()
{
KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages");
dialog.setCaption(i18n("Select a Background Image"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setImageFilters();
QString fn = dialog.filename();
// dialog box was canceled or somehow no file was selected
if (fn.isEmpty()) {
return;
}
QImage image(fn);
if (image.isNull()) {
QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn));
}
else {
m_backgroundimage->setText(fn);
}
}
void GeneralTab::clearBackgroundImage()
{
// clearing the background image text will implicitly make the background color be used
m_backgroundimage->setText("");
}
#include "kactioncollection.h"
#include "KisActionsSnapshot.h"
ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name)
: QWidget(parent)
{
setObjectName(name);
QGridLayout * l = new QGridLayout(this);
l->setMargin(0);
m_page = new WdgShortcutSettings(this);
l->addWidget(m_page, 0, 0);
m_snapshot.reset(new KisActionsSnapshot);
KActionCollection *collection =
KisPart::instance()->currentMainwindow()->actionCollection();
Q_FOREACH (QAction *action, collection->actions()) {
m_snapshot->addAction(action->objectName(), action);
}
QMap<QString, KActionCollection*> sortedCollections =
m_snapshot->actionCollections();
for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) {
m_page->addCollection(it.value(), it.key());
}
}
ShortcutSettingsTab::~ShortcutSettingsTab()
{
}
void ShortcutSettingsTab::setDefault()
{
m_page->allDefault();
}
void ShortcutSettingsTab::saveChanges()
{
m_page->save();
KisActionRegistry::instance()->settingsPageSaved();
}
void ShortcutSettingsTab::cancelChanges()
{
m_page->undo();
}
ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name)
: QWidget(parent)
{
setObjectName(name);
// XXX: Make sure only profiles that fit the specified color model
// are shown in the profile combos
QGridLayout * l = new QGridLayout(this);
l->setMargin(0);
m_page = new WdgColorSettings(this);
l->addWidget(m_page, 0, 0);
KisConfig cfg;
m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile());
connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool)));
m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys());
m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace());
m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open"));
m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") );
connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile()));
QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder);
for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1));
m_monitorProfileLabels << lbl;
SqueezedComboBox *cmb = new SqueezedComboBox();
cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
monitorProfileGrid->addRow(lbl, cmb);
m_monitorProfileWidgets << cmb;
}
refillMonitorProfiles(KoID("RGBA", ""));
for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) {
m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i));
}
}
m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation());
m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization());
KisImageConfig cfgImage;
KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration();
m_page->sldAdaptationState->setMaximum(20);
m_page->sldAdaptationState->setMinimum(0);
m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20);
//probably this should become the screenprofile?
KoColor ga(KoColorSpaceRegistry::instance()->rgb8());
ga.fromKoColor(proofingConfig->warningColor);
m_page->gamutAlarm->setColor(ga);
const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,
proofingConfig->proofingDepth,
proofingConfig->proofingProfile);
if (proofingSpace) {
m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace);
}
m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent);
m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation));
m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB);
m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR);
m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK);
QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour());
Q_ASSERT(button);
if (button) {
button->setChecked(true);
}
m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent());
toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile());
}
void ColorSettingsTab::installProfile()
{
KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC");
dialog.setCaption(i18n("Install Color Profiles"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile");
QStringList profileNames = dialog.filenames();
KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
Q_ASSERT(iccEngine);
QString saveLocation = KoResourcePaths::saveLocation("icc_profiles");
Q_FOREACH (const QString &profileName, profileNames) {
if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) {
qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName();
continue;
}
iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName());
}
KisConfig cfg;
refillMonitorProfiles(KoID("RGBA", ""));
for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) {
m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i));
}
}
}
void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile)
{
if (useSystemProfile) {
KisConfig cfg;
QStringList devices = KisColorManager::instance()->devices();
if (devices.size() == QApplication::desktop()->screenCount()) {
for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
m_monitorProfileWidgets[i]->clear();
QString monitorForScreen = cfg.monitorForScreen(i, devices[i]);
Q_FOREACH (const QString &device, devices) {
m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1));
m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device);
if (devices[i] == monitorForScreen) {
m_monitorProfileWidgets[i]->setCurrentIndex(i);
}
}
}
}
}
else {
KisConfig cfg;
refillMonitorProfiles(KoID("RGBA", ""));
for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) {
m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i));
}
}
}
}
void ColorSettingsTab::setDefault()
{
m_page->cmbWorkingColorSpace->setCurrent("RGBA");
refillMonitorProfiles(KoID("RGBA", ""));
KisConfig cfg;
KisImageConfig cfgImage;
KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration();
const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile);
if (proofingSpace) {
m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace);
}
m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent);
m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation));
m_page->sldAdaptationState->setValue(0);
//probably this should become the screenprofile?
KoColor ga(KoColorSpaceRegistry::instance()->rgb8());
ga.fromKoColor(proofingConfig->warningColor);
m_page->gamutAlarm->setColor(ga);
m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true));
m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true));
m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true));
m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true));
QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true));
Q_ASSERT(button);
if (button) {
button->setChecked(true);
}
}
void ColorSettingsTab::refillMonitorProfiles(const KoID & colorSpaceId)
{
for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
m_monitorProfileWidgets[i]->clear();
}
QMap<QString, const KoColorProfile *> profileList;
Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId.id())) {
profileList[profile->name()] = profile;
}
Q_FOREACH (const KoColorProfile *profile, profileList.values()) {
//qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile();
if (profile->isSuitableForDisplay()) {
for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
m_monitorProfileWidgets[i]->addSqueezedItem(profile->name());
}
}
}
for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1));
m_monitorProfileWidgets[i]->setCurrent(KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId.id()));
}
}
//---------------------------------------------------------------------------------------------------
void TabletSettingsTab::setDefault()
{
KisCubicCurve curve;
curve.fromString(DEFAULT_CURVE_STRING);
m_page->pressureCurve->setCurve(curve);
#ifdef Q_OS_WIN
if (KisTabletSupportWin8::isAvailable()) {
KisConfig cfg;
m_page->radioWintab->setChecked(!cfg.useWin8PointerInput(true));
m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput(true));
} else {
m_page->radioWintab->setChecked(true);
m_page->radioWin8PointerInput->setChecked(false);
}
#endif
}
TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent)
{
setObjectName(name);
QGridLayout * l = new QGridLayout(this);
l->setMargin(0);
m_page = new WdgTabletSettings(this);
l->addWidget(m_page, 0, 0);
KisConfig cfg;
KisCubicCurve curve;
curve.fromString( cfg.pressureTabletCurve() );
m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
m_page->pressureCurve->setCurve(curve);
#ifdef Q_OS_WIN
if (KisTabletSupportWin8::isAvailable()) {
m_page->radioWintab->setChecked(!cfg.useWin8PointerInput());
m_page->radioWin8PointerInput->setChecked(cfg.useWin8PointerInput());
} else {
m_page->radioWintab->setChecked(true);
m_page->radioWin8PointerInput->setChecked(false);
m_page->grpTabletApi->setVisible(false);
}
#else
m_page->grpTabletApi->setVisible(false);
#endif
}
//---------------------------------------------------------------------------------------------------
#include "kis_acyclic_signal_connector.h"
int getTotalRAM() {
KisImageConfig cfg;
return cfg.totalRAM();
}
int PerformanceTab::realTilesRAM()
{
return intMemoryLimit->value() - intPoolLimit->value();
}
PerformanceTab::PerformanceTab(QWidget *parent, const char *name)
: WdgPerformanceSettings(parent, name)
{
KisImageConfig cfg;
const int totalRAM = cfg.totalRAM();
lblTotalMemory->setText(i18n("%1 MiB", totalRAM));
sliderMemoryLimit->setSuffix(i18n(" %"));
sliderMemoryLimit->setRange(1, 100, 2);
sliderMemoryLimit->setSingleStep(0.01);
sliderPoolLimit->setSuffix(i18n(" %"));
sliderPoolLimit->setRange(0, 20, 2);
sliderMemoryLimit->setSingleStep(0.01);
sliderUndoLimit->setSuffix(i18n(" %"));
sliderUndoLimit->setRange(0, 50, 2);
sliderMemoryLimit->setSingleStep(0.01);
intMemoryLimit->setMinimumWidth(80);
intPoolLimit->setMinimumWidth(80);
intUndoLimit->setMinimumWidth(80);
SliderAndSpinBoxSync *sync1 =
new SliderAndSpinBoxSync(sliderMemoryLimit,
intMemoryLimit,
getTotalRAM);
sync1->slotParentValueChanged();
m_syncs << sync1;
SliderAndSpinBoxSync *sync2 =
new SliderAndSpinBoxSync(sliderPoolLimit,
intPoolLimit,
std::bind(&KisIntParseSpinBox::value,
intMemoryLimit));
connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged()));
sync2->slotParentValueChanged();
m_syncs << sync2;
SliderAndSpinBoxSync *sync3 =
new SliderAndSpinBoxSync(sliderUndoLimit,
intUndoLimit,
std::bind(&PerformanceTab::realTilesRAM,
this));
connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged()));
sync3->slotParentValueChanged();
m_syncs << sync3;
sliderSwapSize->setSuffix(i18n(" GiB"));
sliderSwapSize->setRange(1, 64);
intSwapSize->setRange(1, 64);
KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this);
swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)),
intSwapSize, SLOT(setValue(int)));
swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)),
sliderSwapSize, SLOT(setValue(int)));
lblSwapFileLocation->setText(cfg.swapDir());
connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir()));
sliderThreadsLimit->setRange(1, QThread::idealThreadCount());
sliderFrameClonesLimit->setRange(1, QThread::idealThreadCount());
+ sliderFpsLimit->setRange(20, 100);
+ sliderFpsLimit->setSuffix(i18n(" fps"));
connect(sliderThreadsLimit, SIGNAL(valueChanged(int)), SLOT(slotThreadsLimitChanged(int)));
connect(sliderFrameClonesLimit, SIGNAL(valueChanged(int)), SLOT(slotFrameClonesLimitChanged(int)));
+ connect(sliderFpsLimit, SIGNAL(valueChanged(int)), SLOT(slotFpsLimitChanged(int)));
load(false);
}
PerformanceTab::~PerformanceTab()
{
qDeleteAll(m_syncs);
}
void PerformanceTab::load(bool requestDefault)
{
KisImageConfig cfg;
sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault));
sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault));
sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault));
chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault));
chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault));
sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024);
lblSwapFileLocation->setText(cfg.swapDir(requestDefault));
m_lastUsedThreadsLimit = cfg.maxNumberOfThreads(requestDefault);
m_lastUsedClonesLimit = cfg.frameRenderingClones(requestDefault);
+ m_lastUsedFpsLimit = cfg.fpsLimit(requestDefault);
sliderThreadsLimit->setValue(m_lastUsedThreadsLimit);
sliderFrameClonesLimit->setValue(m_lastUsedClonesLimit);
+ sliderFpsLimit->setValue(m_lastUsedFpsLimit);
{
KisConfig cfg2;
chkOpenGLFramerateLogging->setChecked(cfg2.enableOpenGLFramerateLogging(requestDefault));
+ chkBrushSpeedLogging->setChecked(cfg2.enableBrushSpeedLogging(requestDefault));
chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault));
}
}
void PerformanceTab::save()
{
KisImageConfig cfg;
cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value());
cfg.setMemorySoftLimitPercent(sliderUndoLimit->value());
cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value());
cfg.setEnablePerfLog(chkPerformanceLogging->isChecked());
cfg.setEnableProgressReporting(chkProgressReporting->isChecked());
cfg.setMaxSwapSize(sliderSwapSize->value() * 1024);
cfg.setSwapDir(lblSwapFileLocation->text());
cfg.setMaxNumberOfThreads(sliderThreadsLimit->value());
cfg.setFrameRenderingClones(sliderFrameClonesLimit->value());
+ cfg.setFpsLimit(sliderFpsLimit->value());
{
KisConfig cfg2;
cfg2.setEnableOpenGLFramerateLogging(chkOpenGLFramerateLogging->isChecked());
+ cfg2.setEnableBrushSpeedLogging(chkBrushSpeedLogging->isChecked());
cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked());
}
}
void PerformanceTab::selectSwapDir()
{
KisImageConfig cfg;
QString swapDir = cfg.swapDir();
swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir);
if (swapDir.isEmpty()) {
return;
}
lblSwapFileLocation->setText(swapDir);
}
void PerformanceTab::slotThreadsLimitChanged(int value)
{
KisSignalsBlocker b(sliderFrameClonesLimit);
sliderFrameClonesLimit->setValue(qMin(m_lastUsedClonesLimit, value));
m_lastUsedThreadsLimit = value;
}
void PerformanceTab::slotFrameClonesLimitChanged(int value)
{
KisSignalsBlocker b(sliderThreadsLimit);
sliderThreadsLimit->setValue(qMax(m_lastUsedThreadsLimit, value));
m_lastUsedClonesLimit = value;
}
+void PerformanceTab::slotFpsLimitChanged(int value)
+{
+ KisSignalsBlocker b(sliderFrameClonesLimit);
+ sliderFrameClonesLimit->setValue(qMax(m_lastUsedFpsLimit, value));
+ m_lastUsedFpsLimit = value;
+}
+
//---------------------------------------------------------------------------------------------------
#include "KoColor.h"
DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name)
: WdgDisplaySettings(parent, name)
{
KisConfig cfg;
const QString rendererOpenGLText = i18nc("canvas renderer", "OpenGL");
const QString rendererAngleText = i18nc("canvas renderer", "Direct3D 11 via ANGLE");
#ifdef Q_OS_WIN
cmbRenderer->clear();
QString qtPreferredRendererText;
if (KisOpenGL::getQtPreferredOpenGLRenderer() == KisOpenGL::RendererAngle) {
qtPreferredRendererText = rendererAngleText;
} else {
qtPreferredRendererText = rendererOpenGLText;
}
cmbRenderer->addItem(i18nc("canvas renderer", "Auto (%1)", qtPreferredRendererText), KisOpenGL::RendererAuto);
cmbRenderer->setCurrentIndex(0);
if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererDesktopGL) {
cmbRenderer->addItem(rendererOpenGLText, KisOpenGL::RendererDesktopGL);
if (KisOpenGL::getNextUserOpenGLRendererConfig() == KisOpenGL::RendererDesktopGL) {
cmbRenderer->setCurrentIndex(cmbRenderer->count() - 1);
}
}
if (KisOpenGL::getSupportedOpenGLRenderers() & KisOpenGL::RendererAngle) {
cmbRenderer->addItem(rendererAngleText, KisOpenGL::RendererAngle);
if (KisOpenGL::getNextUserOpenGLRendererConfig() == KisOpenGL::RendererAngle) {
cmbRenderer->setCurrentIndex(cmbRenderer->count() - 1);
}
}
#else
lblRenderer->setEnabled(false);
cmbRenderer->setEnabled(false);
cmbRenderer->clear();
cmbRenderer->addItem(rendererOpenGLText);
cmbRenderer->setCurrentIndex(0);
#endif
#ifdef Q_OS_WIN
if (!(KisOpenGL::getSupportedOpenGLRenderers() &
(KisOpenGL::RendererDesktopGL | KisOpenGL::RendererAngle))) {
#else
if (!KisOpenGL::hasOpenGL()) {
#endif
grpOpenGL->setEnabled(false);
grpOpenGL->setChecked(false);
chkUseTextureBuffer->setEnabled(false);
chkDisableVsync->setEnabled(false);
cmbFilterMode->setEnabled(false);
} else {
grpOpenGL->setEnabled(true);
grpOpenGL->setChecked(cfg.useOpenGL());
chkUseTextureBuffer->setEnabled(cfg.useOpenGL());
chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer());
chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings());
chkDisableVsync->setEnabled(cfg.useOpenGL());
chkDisableVsync->setChecked(cfg.disableVSync());
cmbFilterMode->setEnabled(cfg.useOpenGL());
cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode());
// Don't show the high quality filtering mode if it's not available
if (!KisOpenGL::supportsLoD()) {
cmbFilterMode->removeItem(3);
}
}
if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") {
grpOpenGL->setVisible(false);
grpOpenGL->setMaximumHeight(0);
}
KoColor c;
c.fromQColor(cfg.selectionOverlayMaskColor());
c.setOpacity(1.0);
btnSelectionOverlayColor->setColor(c);
sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2);
sldSelectionOverlayOpacity->setSingleStep(0.05);
sldSelectionOverlayOpacity->setValue(cfg.selectionOverlayMaskColor().alphaF());
intCheckSize->setValue(cfg.checkSize());
chkMoving->setChecked(cfg.scrollCheckers());
KoColor ck1(KoColorSpaceRegistry::instance()->rgb8());
ck1.fromQColor(cfg.checkersColor1());
colorChecks1->setColor(ck1);
KoColor ck2(KoColorSpaceRegistry::instance()->rgb8());
ck2.fromQColor(cfg.checkersColor2());
colorChecks2->setColor(ck2);
KoColor cb(KoColorSpaceRegistry::instance()->rgb8());
cb.fromQColor(cfg.canvasBorderColor());
canvasBorder->setColor(cb);
hideScrollbars->setChecked(cfg.hideScrollbars());
chkCurveAntialiasing->setChecked(cfg.antialiasCurves());
chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline());
chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor());
chkHidePopups->setChecked(cfg.hidePopups());
connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool)));
KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8());
gridColor.fromQColor(cfg.getPixelGridColor());
pixelGridColorButton->setColor(gridColor);
pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold() * 100);
- grpPixelGrid->setEnabled(true);
- grpPixelGrid->setChecked(cfg.pixelGridEnabled());
}
void DisplaySettingsTab::setDefault()
{
KisConfig cfg;
cmbRenderer->setCurrentIndex(0);
#ifdef Q_OS_WIN
if (!(KisOpenGL::getSupportedOpenGLRenderers() &
(KisOpenGL::RendererDesktopGL | KisOpenGL::RendererAngle))) {
#else
if (!KisOpenGL::hasOpenGL()) {
#endif
grpOpenGL->setEnabled(false);
grpOpenGL->setChecked(false);
chkUseTextureBuffer->setEnabled(false);
chkDisableVsync->setEnabled(false);
cmbFilterMode->setEnabled(false);
}
else {
grpOpenGL->setEnabled(true);
grpOpenGL->setChecked(cfg.useOpenGL(true));
chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true));
chkUseTextureBuffer->setEnabled(true);
chkDisableVsync->setEnabled(true);
chkDisableVsync->setChecked(cfg.disableVSync(true));
cmbFilterMode->setEnabled(true);
cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true));
}
chkMoving->setChecked(cfg.scrollCheckers(true));
intCheckSize->setValue(cfg.checkSize(true));
KoColor ck1(KoColorSpaceRegistry::instance()->rgb8());
ck1.fromQColor(cfg.checkersColor1(true));
colorChecks1->setColor(ck1);
KoColor ck2(KoColorSpaceRegistry::instance()->rgb8());
ck2.fromQColor(cfg.checkersColor2(true));
colorChecks2->setColor(ck2);
KoColor cvb(KoColorSpaceRegistry::instance()->rgb8());
cvb.fromQColor(cfg.canvasBorderColor(true));
canvasBorder->setColor(cvb);
hideScrollbars->setChecked(cfg.hideScrollbars(true));
chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true));
chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true));
chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true));
chkHidePopups->setChecked(cfg.hidePopups(true));
KoColor gridColor(KoColorSpaceRegistry::instance()->rgb8());
gridColor.fromQColor(cfg.getPixelGridColor(true));
pixelGridColorButton->setColor(gridColor);
pixelGridDrawingThresholdBox->setValue(cfg.getPixelGridDrawingThreshold(true) * 100);
- grpPixelGrid->setEnabled(true);
- grpPixelGrid->setChecked(cfg.pixelGridEnabled(true));
}
void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked)
{
chkUseTextureBuffer->setEnabled(isChecked);
chkDisableVsync->setEnabled(isChecked);
cmbFilterMode->setEnabled(isChecked);
}
//---------------------------------------------------------------------------------------------------
FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent)
{
KisConfig cfg;
chkDockers->setChecked(cfg.hideDockersFullscreen());
chkMenu->setChecked(cfg.hideMenuFullscreen());
chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen());
chkStatusbar->setChecked(cfg.hideStatusbarFullscreen());
chkTitlebar->setChecked(cfg.hideTitlebarFullscreen());
chkToolbar->setChecked(cfg.hideToolbarFullscreen());
}
void FullscreenSettingsTab::setDefault()
{
KisConfig cfg;
chkDockers->setChecked(cfg.hideDockersFullscreen(true));
chkMenu->setChecked(cfg.hideMenuFullscreen(true));
chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true));
chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true));
chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true));
chkToolbar->setChecked(cfg.hideToolbarFullscreen(true));
}
//---------------------------------------------------------------------------------------------------
KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name)
: KPageDialog(parent)
{
Q_UNUSED(name);
setWindowTitle(i18n("Configure Krita"));
setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
button(QDialogButtonBox::Ok)->setDefault(true);
- setFaceType(KPageDialog::List);
+ setFaceType(KPageDialog::Tree);
// General
KoVBox *vbox = new KoVBox();
KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General"));
page->setObjectName("general");
page->setHeader(i18n("General"));
page->setIcon(KisIconUtils::loadIcon("go-home"));
addPage(page);
m_general = new GeneralTab(vbox);
// Shortcuts
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts"));
page->setObjectName("shortcuts");
page->setHeader(i18n("Shortcuts"));
page->setIcon(KisIconUtils::loadIcon("document-export"));
addPage(page);
m_shortcutSettings = new ShortcutSettingsTab(vbox);
connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges()));
connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges()));
// Canvas input settings
m_inputConfiguration = new KisInputConfigurationPage();
page = addPage(m_inputConfiguration, i18n("Canvas Input Settings"));
page->setHeader(i18n("Canvas Input"));
page->setObjectName("canvasinput");
page->setIcon(KisIconUtils::loadIcon("configure"));
// Display
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Display"));
page->setObjectName("display");
page->setHeader(i18n("Display"));
page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display"));
addPage(page);
m_displaySettings = new DisplaySettingsTab(vbox);
// Color
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Color Management"));
page->setObjectName("colormanagement");
page->setHeader(i18n("Color"));
page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color"));
addPage(page);
m_colorSettings = new ColorSettingsTab(vbox);
// Performance
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Performance"));
page->setObjectName("performance");
page->setHeader(i18n("Performance"));
page->setIcon(KisIconUtils::loadIcon("applications-system"));
addPage(page);
m_performanceSettings = new PerformanceTab(vbox);
// Tablet
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Tablet settings"));
page->setObjectName("tablet");
page->setHeader(i18n("Tablet"));
page->setIcon(KisIconUtils::loadIcon("document-edit"));
addPage(page);
m_tabletSettings = new TabletSettingsTab(vbox);
// full-screen mode
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Canvas-only settings"));
page->setObjectName("canvasonly");
page->setHeader(i18n("Canvas-only"));
page->setIcon(KisIconUtils::loadIcon("folder-pictures"));
addPage(page);
m_fullscreenSettings = new FullscreenSettingsTab(vbox);
// Author profiles
m_authorPage = new KoConfigAuthorPage();
page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" ));
page->setObjectName("author");
page->setHeader(i18n("Author"));
page->setIcon(KisIconUtils::loadIcon("im-user"));
QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults);
+ restoreDefaultsButton->setText("Restore Defaults");
connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges()));
connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges()));
KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
Q_FOREACH (KisAbstractPreferenceSetFactory *preferenceSetFactory, preferenceSetRegistry->values()) {
KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet();
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, preferenceSet->name());
page->setHeader(preferenceSet->header());
page->setIcon(preferenceSet->icon());
addPage(page);
preferenceSet->setParent(vbox);
preferenceSet->loadPreferences();
connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection);
connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection);
}
connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault()));
}
KisDlgPreferences::~KisDlgPreferences()
{
}
void KisDlgPreferences::slotDefault()
{
if (currentPage()->objectName() == "general") {
m_general->setDefault();
}
else if (currentPage()->objectName() == "shortcuts") {
m_shortcutSettings->setDefault();
}
else if (currentPage()->objectName() == "display") {
m_displaySettings->setDefault();
}
else if (currentPage()->objectName() == "colormanagement") {
m_colorSettings->setDefault();
}
else if (currentPage()->objectName() == "performance") {
m_performanceSettings->load(true);
}
else if (currentPage()->objectName() == "tablet") {
m_tabletSettings->setDefault();
}
else if (currentPage()->objectName() == "canvasonly") {
m_fullscreenSettings->setDefault();
}
else if (currentPage()->objectName() == "canvasinput") {
m_inputConfiguration->setDefaults();
}
}
bool KisDlgPreferences::editPreferences()
{
KisDlgPreferences* dialog;
dialog = new KisDlgPreferences();
bool baccept = (dialog->exec() == Accepted);
if (baccept) {
// General settings
KisConfig cfg;
cfg.setNewCursorStyle(dialog->m_general->cursorStyle());
cfg.setNewOutlineStyle(dialog->m_general->outlineStyle());
cfg.setShowRootLayer(dialog->m_general->showRootLayer());
cfg.setShowOutlineWhilePainting(dialog->m_general->showOutlineWhilePainting());
cfg.setHideSplashScreen(dialog->m_general->hideSplashScreen());
cfg.setCalculateAnimationCacheInBackground(dialog->m_general->calculateAnimationCacheInBackground());
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
group.writeEntry("DontUseNativeFileDialog", !dialog->m_general->m_chkNativeFileDialog->isChecked());
cfg.writeEntry<int>("maximumBrushSize", dialog->m_general->intMaxBrushSize->value());
cfg.writeEntry<int>("mdi_viewmode", dialog->m_general->mdiMode());
cfg.setMDIBackgroundColor(dialog->m_general->m_mdiColor->color().toQColor());
cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text());
cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval());
cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked());
cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages());
cfg.setCompressKra(dialog->m_general->compressKra());
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked());
kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked());
cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker());
cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt());
cfg.setDisableTouchOnCanvas(!dialog->m_general->chkEnableTouch->isChecked());
cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport());
cfg.setUndoStackLimit(dialog->m_general->undoStackSize());
cfg.setFavoritePresets(dialog->m_general->favoritePresets());
// Color settings
cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked());
for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) {
int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex();
QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString();
cfg.setMonitorForScreen(i, monitorid);
}
else {
cfg.setMonitorProfile(i,
dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(),
dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked());
}
}
cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id());
KisImageConfig cfgImage;
cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(),
dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(),
dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(),
dialog->m_colorSettings->m_page->gamutAlarm->color(),
(double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20);
cfg.setUseBlackPointCompensation(dialog->m_colorSettings->m_page->chkBlackpoint->isChecked());
cfg.setAllowLCMSOptimization(dialog->m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked());
cfg.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId());
cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex());
// Tablet settings
cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() );
#ifdef Q_OS_WIN
if (KisTabletSupportWin8::isAvailable()) {
cfg.setUseWin8PointerInput(dialog->m_tabletSettings->m_page->radioWin8PointerInput->isChecked());
}
#endif
dialog->m_performanceSettings->save();
#ifdef Q_OS_WIN
{
KisOpenGL::OpenGLRenderer renderer = static_cast<KisOpenGL::OpenGLRenderer>(
dialog->m_displaySettings->cmbRenderer->itemData(
dialog->m_displaySettings->cmbRenderer->currentIndex()).toInt());
KisOpenGL::setNextUserOpenGLRendererConfig(renderer);
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer));
}
#endif
if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked())
cfg.setCanvasState("TRY_OPENGL");
cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked());
cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked());
cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex());
cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked());
cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value());
cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked());
cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor());
cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor());
cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor());
cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked());
KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color();
c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value());
cfg.setSelectionOverlayMaskColor(c.toQColor());
cfg.setAntialiasCurves(dialog->m_displaySettings->chkCurveAntialiasing->isChecked());
cfg.setAntialiasSelectionOutline(dialog->m_displaySettings->chkSelectionOutlineAntialiasing->isChecked());
cfg.setShowSingleChannelAsColor(dialog->m_displaySettings->chkChannelsAsColor->isChecked());
cfg.setHidePopups(dialog->m_displaySettings->chkHidePopups->isChecked());
cfg.setHideDockersFullscreen(dialog->m_fullscreenSettings->chkDockers->checkState());
cfg.setHideMenuFullscreen(dialog->m_fullscreenSettings->chkMenu->checkState());
cfg.setHideScrollbarsFullscreen(dialog->m_fullscreenSettings->chkScrollbars->checkState());
cfg.setHideStatusbarFullscreen(dialog->m_fullscreenSettings->chkStatusbar->checkState());
cfg.setHideTitlebarFullscreen(dialog->m_fullscreenSettings->chkTitlebar->checkState());
cfg.setHideToolbarFullscreen(dialog->m_fullscreenSettings->chkToolbar->checkState());
cfg.setCursorMainColor(dialog->m_general->cursorColorBtutton->color().toQColor());
cfg.setPixelGridColor(dialog->m_displaySettings->pixelGridColorButton->color().toQColor());
cfg.setPixelGridDrawingThreshold(dialog->m_displaySettings->pixelGridDrawingThresholdBox->value() / 100);
- cfg.enablePixelGrid(dialog->m_displaySettings->grpPixelGrid->isChecked());
dialog->m_authorPage->apply();
}
delete dialog;
return baccept;
}
diff --git a/libs/ui/dialogs/kis_dlg_preferences.h b/libs/ui/dialogs/kis_dlg_preferences.h
index 9000b8c02a..30cfa3a6b5 100644
--- a/libs/ui/dialogs/kis_dlg_preferences.h
+++ b/libs/ui/dialogs/kis_dlg_preferences.h
@@ -1,338 +1,340 @@
/*
* preferencesdlg.h - part of KImageShop^WKrita
*
* Copyright (c) 1999 Michael Koch <koch@kde.org>
* Copyright (c) 2003-2011 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DLG_PREFERENCES_H_
#define _KIS_DLG_PREFERENCES_H_
#include <QWidget>
#include <kpagedialog.h>
#include "kis_global.h"
#include "widgets/squeezedcombobox.h"
#include "ui_wdggeneralsettings.h"
#include "ui_wdgdisplaysettings.h"
#include "ui_wdgcolorsettings.h"
#include "ui_wdgtabletsettings.h"
#include "ui_wdgperformancesettings.h"
#include "ui_wdgfullscreensettings.h"
#include "KisShortcutsDialog.h"
class KoID;
class KisInputConfigurationPage;
class KoConfigAuthorPage;
/**
* "General"-tab for preferences dialog
*/
class WdgGeneralSettings : public QWidget, public Ui::WdgGeneralSettings
{
Q_OBJECT
public:
WdgGeneralSettings(QWidget *parent, const char *name) : QWidget(parent) {
setObjectName(name);
setupUi(this);
chkShowRootLayer->setVisible(false);
}
};
class GeneralTab : public WdgGeneralSettings
{
Q_OBJECT
public:
GeneralTab(QWidget *parent = 0, const char *name = 0);
CursorStyle cursorStyle();
OutlineStyle outlineStyle();
bool showRootLayer();
int autoSaveInterval();
void setDefault();
int undoStackSize();
bool showOutlineWhilePainting();
bool hideSplashScreen();
int mdiMode();
int favoritePresets();
bool showCanvasMessages();
bool compressKra();
bool toolOptionsInDocker();
bool switchSelectionCtrlAlt();
bool convertToImageColorspaceOnImport();
bool calculateAnimationCacheInBackground();
private Q_SLOTS:
void getBackgroundImage();
void clearBackgroundImage();
};
/**
* "Shortcuts" tab for preferences dialog
*/
class WdgShortcutSettings : public KisShortcutsDialog
{
Q_OBJECT
public:
WdgShortcutSettings(QWidget *parent)
: KisShortcutsDialog(KisShortcutsEditor::AllActions,
KisShortcutsEditor::LetterShortcutsAllowed,
parent)
{ }
};
class KisActionsSnapshot;
class ShortcutSettingsTab : public QWidget
{
Q_OBJECT
public:
ShortcutSettingsTab(QWidget *parent = 0, const char *name = 0);
~ShortcutSettingsTab() override;
public:
void setDefault();
WdgShortcutSettings *m_page;
QScopedPointer<KisActionsSnapshot> m_snapshot;
public Q_SLOTS:
void saveChanges();
void cancelChanges();
};
/**
* "Color" tab for preferences dialog
*/
class WdgColorSettings : public QWidget, public Ui::WdgColorSettings
{
Q_OBJECT
public:
WdgColorSettings(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class ColorSettingsTab : public QWidget
{
Q_OBJECT
public:
ColorSettingsTab(QWidget *parent = 0, const char *name = 0);
private Q_SLOTS:
void refillMonitorProfiles(const KoID & s);
void installProfile();
void toggleAllowMonitorProfileSelection(bool useSystemProfile);
public:
void setDefault();
WdgColorSettings *m_page;
QButtonGroup m_pasteBehaviourGroup;
QList<QLabel*> m_monitorProfileLabels;
QList<SqueezedComboBox*> m_monitorProfileWidgets;
};
//=======================
class WdgTabletSettings : public QWidget, public Ui::WdgTabletSettings {
Q_OBJECT
public:
WdgTabletSettings(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class TabletSettingsTab : public QWidget {
Q_OBJECT
public:
TabletSettingsTab(QWidget *parent = 0, const char *name = 0);
public:
void setDefault();
WdgTabletSettings *m_page;
};
//=======================
/**
* "Performance"-tab for preferences dialog
*/
class SliderAndSpinBoxSync;
class WdgPerformanceSettings : public QWidget, public Ui::WdgPerformanceSettings
{
Q_OBJECT
public:
WdgPerformanceSettings(QWidget *parent, const char *name) : QWidget(parent) {
setObjectName(name); setupUi(this);
}
};
class PerformanceTab : public WdgPerformanceSettings
{
Q_OBJECT
public:
PerformanceTab(QWidget *parent = 0, const char *name = 0);
~PerformanceTab() override;
void load(bool requestDefault);
void save();
private Q_SLOTS:
void selectSwapDir();
void slotThreadsLimitChanged(int value);
void slotFrameClonesLimitChanged(int value);
+ void slotFpsLimitChanged(int value);
private:
int realTilesRAM();
private:
QVector<SliderAndSpinBoxSync*> m_syncs;
int m_lastUsedThreadsLimit;
int m_lastUsedClonesLimit;
+ int m_lastUsedFpsLimit;
};
//=======================
class WdgDisplaySettings : public QWidget, public Ui::WdgDisplaySettings
{
Q_OBJECT
public:
WdgDisplaySettings(QWidget *parent, const char *name) : QWidget(parent) {
setObjectName(name); setupUi(this);
}
};
/**
* Display settings tab for preferences dialog
*/
class DisplaySettingsTab : public WdgDisplaySettings
{
Q_OBJECT
public:
DisplaySettingsTab(QWidget *parent = 0, const char *name = 0);
public:
void setDefault();
protected Q_SLOTS:
void slotUseOpenGLToggled(bool isChecked);
public:
};
//=======================
/**
* Full screen settings tab for preferences dialog
*/
class WdgFullscreenSettingsBase : public QWidget, public Ui::WdgFullscreenSettings
{
Q_OBJECT
public:
WdgFullscreenSettingsBase(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class FullscreenSettingsTab : public WdgFullscreenSettingsBase
{
Q_OBJECT
public:
FullscreenSettingsTab(QWidget *parent);
public:
void setDefault();
};
//=======================
/**
* Preferences dialog of KImageShop^WKrayon^WKrita
*/
class KisDlgPreferences : public KPageDialog
{
Q_OBJECT
public:
static bool editPreferences();
protected:
KisDlgPreferences(QWidget *parent = 0, const char *name = 0);
~KisDlgPreferences() override;
protected:
GeneralTab *m_general;
ShortcutSettingsTab *m_shortcutSettings;
ColorSettingsTab *m_colorSettings;
PerformanceTab *m_performanceSettings;
DisplaySettingsTab *m_displaySettings;
TabletSettingsTab *m_tabletSettings;
FullscreenSettingsTab *m_fullscreenSettings;
KisInputConfigurationPage *m_inputConfiguration;
KoConfigAuthorPage *m_authorPage;
protected Q_SLOTS:
void slotDefault();
};
#endif
diff --git a/libs/ui/forms/wdgcolorsettings.ui b/libs/ui/forms/wdgcolorsettings.ui
index c51dc04f02..a9e41265c2 100644
--- a/libs/ui/forms/wdgcolorsettings.ui
+++ b/libs/ui/forms/wdgcolorsettings.ui
@@ -1,389 +1,423 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgColorSettings</class>
<widget class="QWidget" name="WdgColorSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>586</width>
- <height>613</height>
+ <width>568</width>
+ <height>335</height>
</rect>
</property>
<property name="windowTitle">
<string>Color Settings</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <item>
- <layout class="QHBoxLayout">
- <property name="leftMargin">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
<number>0</number>
</property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="textLabel1_2">
- <property name="text">
- <string>Default color model for new images:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="KisCmbIDList" name="cmbWorkingColorSpace">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>20</height>
- </size>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QGroupBox" name="grpDisplay">
- <property name="title">
- <string>Display</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <item>
- <widget class="QCheckBox" name="chkUseSystemMonitorProfile">
- <property name="text">
- <string>Use system monitor profile</string>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QWidget" name="monitorprofileholder" native="true"/>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="lblRenderingIntent">
- <property name="toolTip">
- <string>The icm profile for your calibrated monitor</string>
- </property>
- <property name="text">
- <string>&amp;Rendering intent:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>cmbMonitorIntent</cstring>
- </property>
- </widget>
- </item>
- <item>
- <widget class="KComboBox" name="cmbMonitorIntent">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <item>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>General</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="textLabel1_2">
<property name="text">
- <string>Perceptual</string>
+ <string>Default color model for new images:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="KisCmbIDList" name="cmbWorkingColorSpace">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
</property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="grpPasteBehaviour">
+ <property name="title">
+ <string>When Pasting Into Krita From Other Applications</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QRadioButton" name="radioPasteWeb">
+ <property name="text">
+ <string>Assume sRGB (like images from the web are supposed to be seen)</string>
+ </property>
+ </widget>
</item>
<item>
- <property name="text">
- <string>Relative Colorimetric</string>
- </property>
+ <widget class="QRadioButton" name="radioPasteMonitor">
+ <property name="text">
+ <string>Assume &amp;monitor profile (like you see it in the other application)</string>
+ </property>
+ </widget>
</item>
<item>
- <property name="text">
- <string>Saturation</string>
- </property>
+ <widget class="QRadioButton" name="radioPasteAsk">
+ <property name="text">
+ <string>As&amp;k each time</string>
+ </property>
+ </widget>
</item>
<item>
- <property name="text">
- <string>Absolute Colorimetric</string>
- </property>
+ <widget class="QLabel" name="textLabel1_2_2">
+ <property name="text">
+ <string>Note: When copying/pasting inside Krita color info is always preserved.</string>
+ </property>
+ </widget>
</item>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Add new color profile:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="bnAddColorProfile">
- <property name="maximumSize">
- <size>
- <width>24</width>
- <height>24</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="grpProofingOptions">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="title">
- <string>Soft Proof Options</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="3" column="0">
- <widget class="QLabel" name="lblProofingIntent">
- <property name="text">
- <string>Proofing Rendering Intent:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0" colspan="4">
- <widget class="KisColorSpaceSelector" name="proofingSpaceSelector" native="true"/>
- </item>
- <item row="5" column="2" colspan="2">
- <widget class="QCheckBox" name="ckbProofBlackPoint">
- <property name="text">
- <string>Black Point Compensation</string>
- </property>
- </widget>
- </item>
- <item row="5" column="0">
- <widget class="QLabel" name="lblGamutWarning">
- <property name="text">
- <string>Gamut Warning:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="3" column="1" colspan="3">
- <widget class="QComboBox" name="cmbProofingIntent">
- <item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chkBlackpoint">
<property name="text">
- <string>Perceptual</string>
+ <string>Use Blackpoint Compensation</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
</property>
- </item>
- <item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chkAllowLCMSOptimization">
<property name="text">
- <string>Relative Colorimetric</string>
+ <string>Allow Little CMS optimizations (uncheck when using linear light RGB or XYZ)</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
</property>
- </item>
- <item>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Display</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="chkUseSystemMonitorProfile">
<property name="text">
- <string>Saturation</string>
+ <string>Use system monitor profile</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="monitorprofileholder" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>50</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- </item>
- <item>
+ <item>
+ <widget class="QLabel" name="lblRenderingIntent">
+ <property name="toolTip">
+ <string>The icm profile for your calibrated monitor</string>
+ </property>
+ <property name="text">
+ <string>&amp;Rendering intent:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>cmbMonitorIntent</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="KComboBox" name="cmbMonitorIntent">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>Perceptual</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Relative Colorimetric</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Saturation</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Absolute Colorimetric</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Add new color profile:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="bnAddColorProfile">
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_3">
+ <attribute name="title">
+ <string>Soft Proofing</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="0">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cmbProofingIntent">
+ <item>
+ <property name="text">
+ <string>Perceptual</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Relative Colorimetric</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Saturation</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Absolute Colorimetric</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblAdaptationState">
+ <property name="text">
+ <string>Adaptation State:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblProofingIntent">
+ <property name="text">
+ <string>Proofing Rendering Intent:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="lblGamutWarning">
+ <property name="text">
+ <string>Gamut Warning:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSlider" name="sldAdaptationState">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="KisColorButton" name="gamutAlarm">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QCheckBox" name="ckbProofBlackPoint">
+ <property name="text">
+ <string>Black Point Compensation</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <spacer name="verticalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0">
+ <widget class="KisColorSpaceSelector" name="proofingSpaceSelector" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>100</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
<property name="text">
- <string>Absolute Colorimetric</string>
+ <string>Note: these are the default proofing settings for new images.</string>
</property>
- </item>
- </widget>
- </item>
- <item row="4" column="1" colspan="3">
- <widget class="QSlider" name="sldAdaptationState">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item row="4" column="0">
- <widget class="QLabel" name="lblAdaptationState">
- <property name="text">
- <string>Adaptation State:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="KisColorButton" name="gamutAlarm">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="grpPasteBehaviour">
- <property name="title">
- <string>When Pasting Into Krita From Other Applications</string>
- </property>
- <layout class="QVBoxLayout">
- <item>
- <widget class="QRadioButton" name="radioPasteWeb">
- <property name="text">
- <string>Assume sRGB (like images from the web are supposed to be seen)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="radioPasteMonitor">
- <property name="text">
- <string>Assume monitor profile (like you see it in the other application)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="radioPasteAsk">
- <property name="text">
- <string>Ask each time</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="textLabel1_2_2">
- <property name="text">
- <string>Note: When copying/pasting inside Krita color info is always preserved.</string>
- </property>
- </widget>
- </item>
- </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</widget>
</item>
- <item>
- <layout class="QVBoxLayout">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QCheckBox" name="chkBlackpoint">
- <property name="text">
- <string>Use Blackpoint Compensation</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="chkAllowLCMSOptimization">
- <property name="text">
- <string>Allow Little CMS optimizations (uncheck when using linear light RGB or XYZ)</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <spacer>
+ <item row="1" column="0">
+ <spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
- <property name="sizeType">
- <enum>QSizePolicy::Expanding</enum>
- </property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
- <height>16</height>
+ <height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
+ <customwidget>
+ <class>KComboBox</class>
+ <extends>QComboBox</extends>
+ <header>kcombobox.h</header>
+ </customwidget>
<customwidget>
<class>KisColorButton</class>
<extends>QPushButton</extends>
<header>kis_color_button.h</header>
</customwidget>
<customwidget>
<class>KisColorSpaceSelector</class>
<extends>QWidget</extends>
<header>widgets/kis_color_space_selector.h</header>
<container>1</container>
</customwidget>
- <customwidget>
- <class>KComboBox</class>
- <extends>QComboBox</extends>
- <header>kcombobox.h</header>
- </customwidget>
<customwidget>
<class>KisCmbIDList</class>
<extends>QComboBox</extends>
<header>widgets/kis_cmb_idlist.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/libs/ui/forms/wdgdisplaysettings.ui b/libs/ui/forms/wdgdisplaysettings.ui
index fa7ae0f971..2415b26ce1 100644
--- a/libs/ui/forms/wdgdisplaysettings.ui
+++ b/libs/ui/forms/wdgdisplaysettings.ui
@@ -1,441 +1,470 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgDisplaySettings</class>
<widget class="QWidget" name="WdgDisplaySettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>619</width>
- <height>685</height>
+ <width>556</width>
+ <height>546</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Display</string>
</property>
- <layout class="QGridLayout">
- <item row="4" column="0">
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>Miscellaneous</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QCheckBox" name="chkChannelsAsColor">
- <property name="text">
- <string>Color channels in color</string>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="chkCurveAntialiasing">
- <property name="text">
- <string>Enable curve anti-aliasing</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="chkSelectionOutlineAntialiasing">
- <property name="text">
- <string>Enable selection outline anti-aliasing</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="chkHidePopups">
- <property name="text">
- <string>Hide layer thumbnail popup</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QGroupBox" name="grpCheckers">
- <property name="title">
- <string>Transparency Checkerboard Pattern</string>
- </property>
- <layout class="QGridLayout">
- <item row="0" column="0">
- <widget class="QLabel" name="lblCheckSize">
- <property name="text">
- <string>S&amp;ize:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>intCheckSize</cstring>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="KisIntParseSpinBox" name="intCheckSize">
- <property name="suffix">
- <string> px</string>
- </property>
- <property name="maximum">
- <number>256</number>
- </property>
- <property name="value">
- <number>32</number>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="3">
- <widget class="QLabel" name="lblColor">
- <property name="text">
- <string>Colors:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="4">
- <widget class="KisColorButton" name="colorChecks1"/>
- </item>
- <item row="0" column="5">
- <widget class="KisColorButton" name="colorChecks2"/>
- </item>
- <item row="0" column="6">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="0" colspan="3">
- <widget class="QCheckBox" name="chkMoving">
- <property name="toolTip">
- <string>If checked, the checkers will move when scrolling the canvas.</string>
- </property>
- <property name="whatsThis">
- <string>Determines whether the checks will stay put or whether they will scroll together with the canvas</string>
- </property>
- <property name="text">
- <string>&amp;Move checkers when scrolling</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="6" column="0">
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="0">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>15</number>
+ </property>
+ <item>
<widget class="QGroupBox" name="grpOpenGL">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Canvas &amp;Graphics Acceleration</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QGridLayout">
<item row="1" column="1">
<widget class="QComboBox" name="cmbFilterMode">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Nearest Neighbour</string>
</property>
</item>
<item>
<property name="text">
<string>Bilinear Filtering</string>
</property>
</item>
<item>
<property name="text">
<string>Trilinear Filtering</string>
</property>
</item>
<item>
<property name="text">
<string>High Quality Filtering</string>
</property>
</item>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="chkDisableVsync">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Try to disable vsync for Krita. This makes painting more responsive. Uncheck only when experiencing crashes with some GPU/driver combinations.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Disable vsync (needs restart)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="chkUseTextureBuffer">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use Texture Buffering. This can be faster on some GPU/Driver combinations (like Intel) or broken on some others (like AMD/Radeon).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use texture buffer</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Scaling Mode:</string>
</property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="lblRenderer">
- <property name="text">
- <string>Renderer (needs restart):</string>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="cmbRenderer"/>
- </item>
- </layout>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QGroupBox" name="grpCanvasBorder">
- <property name="title">
- <string>Canvas border</string>
- </property>
- <layout class="QGridLayout">
- <item row="1" column="0">
- <widget class="QLabel" name="lblCanvasBorderColor">
<property name="text">
- <string>Color:</string>
+ <string>Renderer (needs restart):</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
- <item row="1" column="1">
- <widget class="KisColorButton" name="canvasBorder"/>
- </item>
- <item row="1" column="2">
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="2" column="0" colspan="3">
- <widget class="QCheckBox" name="hideScrollbars">
- <property name="text">
- <string>Hide Scrollbars</string>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>Selection Overlay</string>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Color:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="KisColorButton" name="btnSelectionOverlayColor">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item>
- <widget class="KisDoubleSliderSpinBox" name="sldSelectionOverlayOpacity" native="true">
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cmbRenderer">
<property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
- <item row="5" column="0">
- <widget class="QGroupBox" name="grpPixelGrid">
- <property name="title">
- <string>Pixel Grid</string>
+ <item>
+ <layout class="QGridLayout" name="options2GridLayout">
+ <property name="topMargin">
+ <number>15</number>
</property>
- <property name="checkable">
- <bool>true</bool>
+ <property name="verticalSpacing">
+ <number>7</number>
</property>
- <layout class="QGridLayout" name="gridLayout4">
- <item row="0" column="5">
- <widget class="QLabel" name="chooseGridLabel">
- <property name="text">
- <string>Color:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="3">
- <widget class="QDoubleSpinBox" name="pixelGridDrawingThresholdBox">
- <property name="suffix">
- <string>%</string>
- </property>
- <property name="maximum">
- <double>6400.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="0" column="6">
- <widget class="KisColorButton" name="pixelGridColorButton">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="7">
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="gridThresholdLabel">
- <property name="text">
- <string>Start showing at:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="4">
- <spacer name="horizontalSpacer_6">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
+ <item row="0" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="lblCheckSize">
+ <property name="text">
+ <string>Si&amp;ze:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>intCheckSize</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="KisIntParseSpinBox" name="intCheckSize">
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="maximum">
+ <number>256</number>
+ </property>
+ <property name="value">
+ <number>32</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="4" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="gridThresholdLabel">
+ <property name="text">
+ <string>Start showing at:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDoubleSpinBox" name="pixelGridDrawingThresholdBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string>%</string>
+ </property>
+ <property name="maximum">
+ <double>6400.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Opacity:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="KisDoubleSliderSpinBox" name="sldSelectionOverlayOpacity" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>20</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="1">
+ <widget class="KisColorButton" name="btnSelectionOverlayColor">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="lblCanvasBorderColor_2">
+ <property name="text">
+ <string>Pixel Grid:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Selection Overlay:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Transparency Checkerboard:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="KisColorButton" name="pixelGridColorButton">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="KisColorButton" name="colorChecks2"/>
+ </item>
+ <item>
+ <widget class="KisColorButton" name="colorChecks1"/>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblCanvasBorderColor">
+ <property name="text">
+ <string>Canvas Border Color:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="KisColorButton" name="canvasBorder">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="checkboxesLayout">
+ <property name="topMargin">
+ <number>20</number>
+ </property>
+ <property name="horizontalSpacing">
+ <number>0</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>10</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="hideScrollbars">
+ <property name="text">
+ <string>Hide Window Scrollbars</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="chkHidePopups">
+ <property name="text">
+ <string>Hide layer thumbnail popup</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="chkCurveAntialiasing">
+ <property name="text">
+ <string>Enable curve anti-aliasing</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QCheckBox" name="chkChannelsAsColor">
+ <property name="text">
+ <string>Color channels in color</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="chkSelectionOutlineAntialiasing">
+ <property name="text">
+ <string>Enable selection outline anti-aliasing</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="chkMoving">
+ <property name="toolTip">
+ <string>If checked, the checkers will move when scrolling the canvas.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Determines whether the checks will stay put or whether they will scroll together with the canvas</string>
+ </property>
+ <property name="text">
+ <string>&amp;Move checkers when scrolling</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>5</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
</item>
</layout>
+ <zorder>grpOpenGL</zorder>
+ <zorder>verticalSpacer</zorder>
</widget>
<customwidgets>
- <customwidget>
- <class>KisIntParseSpinBox</class>
- <extends>QSpinBox</extends>
- <header>kis_int_parse_spin_box.h</header>
- </customwidget>
<customwidget>
<class>KisColorButton</class>
<extends>QPushButton</extends>
<header>kis_color_button.h</header>
</customwidget>
+ <customwidget>
+ <class>KisIntParseSpinBox</class>
+ <extends>QSpinBox</extends>
+ <header>kis_int_parse_spin_box.h</header>
+ </customwidget>
<customwidget>
<class>KisDoubleSliderSpinBox</class>
<extends>QWidget</extends>
<header>kis_slider_spin_box.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/libs/ui/forms/wdgimageproperties.ui b/libs/ui/forms/wdgimageproperties.ui
index 6253bc15ab..b2a67d7b18 100644
--- a/libs/ui/forms/wdgimageproperties.ui
+++ b/libs/ui/forms/wdgimageproperties.ui
@@ -1,373 +1,400 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgImageProperties</class>
<widget class="QWidget" name="WdgImageProperties">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>449</width>
<height>322</height>
</rect>
</property>
<property name="windowTitle">
<string>New Image</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTabWidget" name="grpColorM">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Dimensions</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="horizontalSpacing">
<number>12</number>
</property>
<property name="verticalSpacing">
<number>12</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="lblWidth">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Width:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblWidthValue">
<property name="text">
<string comment="KDE::DoNotExtract">TextLabel</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lblHeight">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Height:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="lblHeightValue">
<property name="text">
<string comment="KDE::DoNotExtract">TextLabel</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lblResolution">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Resolution:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="lblResolutionValue">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string comment="KDE::DoNotExtract">TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="toolTip">
<string>pixels-per-inch</string>
</property>
<property name="text">
<string>ppi</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="lblBgColor">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string comment="The background color of the image's projection">Background Color:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="KisColorButton" name="bnBackgroundColor">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="KisDoubleSliderSpinBox" name="sldBackgroundColor" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Background Opacity:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="grpMode">
<attribute name="title">
<string>Image Color Space</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="KisColorSpaceSelector" name="colorSpaceSelector" native="true">
- </widget>
+ <widget class="KisColorSpaceSelector" name="colorSpaceSelector" native="true"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Note:&lt;/span&gt; This changes only the colorspace of the rendered image. To convert the colorspace of the layers, use Convert Image Colorspace.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="grpSoftProof">
<attribute name="title">
<string>Softproofing</string>
</attribute>
- <layout class="QGridLayout" name="gridLayout">
- <item row="3" column="0">
- <widget class="QLabel" name="lblGamutWarning">
- <property name="text">
- <string>Gamut Warning:</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="lblAdaptationState">
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="text">
- <string>Adaptation State:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="2" column="1" colspan="3">
- <widget class="QSlider" name="sldAdaptationState">
- <property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set how much you wish to correct the adaptation state. This will affect how &lt;span style=&quot; font-style:italic;&quot;&gt;Absolute Colorimetric&lt;/span&gt; changes the whites of your image. In Layman's terms: how much do you wish to have the color management correct the paper-color to screen white while using &lt;span style=&quot; font-style:italic;&quot;&gt;Absolute Colorimetric&lt;/span&gt;?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item row="3" column="2" colspan="2">
- <widget class="QCheckBox" name="ckbBlackPointComp">
- <property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Black Point compensation matches the darkest color of the source device to the darkest color of the destination device. Relative Colorimetric without Black Point Compensation will show the difference between the darkest values. With blackpoint compensation, black is black.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QCheckBox" name="chkSaveProofing">
<property name="text">
- <string>Black Point Compensation</string>
+ <string>Store Softproofing configuration in the image</string>
</property>
</widget>
</item>
- <item row="0" column="0" colspan="4">
+ <item>
<widget class="KisColorSpaceSelector" name="proofSpaceSelector" native="true"/>
</item>
- <item row="1" column="0" colspan="4">
+ <item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Rendering Intent</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QComboBox" name="cmbIntent">
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Perceptual</string>
</property>
</item>
<item>
<property name="text">
<string>Relative Colorimetric</string>
</property>
</item>
<item>
<property name="text">
<string>Saturation</string>
</property>
</item>
<item>
<property name="text">
<string>Absolute Colorimetric</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
- <item row="3" column="1">
- <widget class="KisColorButton" name="gamutAlarm">
- <property name="text">
- <string/>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="lblAdaptationState">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>Adaptation State:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="sldAdaptationState">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set how much you wish to correct the adaptation state. This will affect how &lt;span style=&quot; font-style:italic;&quot;&gt;Absolute Colorimetric&lt;/span&gt; changes the whites of your image. In Layman's terms: how much do you wish to have the color management correct the paper-color to screen white while using &lt;span style=&quot; font-style:italic;&quot;&gt;Absolute Colorimetric&lt;/span&gt;?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="lblGamutWarning">
+ <property name="text">
+ <string>Gamut Warning:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="KisColorButton" name="gamutAlarm">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="ckbBlackPointComp">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Black Point compensation matches the darkest color of the source device to the darkest color of the destination device. Relative Colorimetric without Black Point Compensation will show the difference between the darkest values. With blackpoint compensation, black is black.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Black Point Compensation</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
</property>
- </widget>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Annotations</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QComboBox" name="cmbAnnotations"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblDescription">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="txtAnnotation">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisDoubleSliderSpinBox</class>
<extends>QWidget</extends>
<header>kis_slider_spin_box.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KisColorButton</class>
<extends>QPushButton</extends>
<header>kis_color_button.h</header>
</customwidget>
<customwidget>
<class>KisColorSpaceSelector</class>
<extends>QWidget</extends>
<header>widgets/kis_color_space_selector.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/libs/ui/forms/wdgpaintopsettings.ui b/libs/ui/forms/wdgpaintopsettings.ui
index f16a3a8657..f59fc1c611 100644
--- a/libs/ui/forms/wdgpaintopsettings.ui
+++ b/libs/ui/forms/wdgpaintopsettings.ui
@@ -1,927 +1,926 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgPaintOpSettings</class>
<widget class="QWidget" name="WdgPaintOpSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>1441</width>
- <height>495</height>
+ <width>1134</width>
+ <height>431</height>
</rect>
</property>
<property name="windowTitle">
<string>Brush Editor</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="3">
<layout class="QHBoxLayout" name="brushEditorDashboard">
<property name="spacing">
- <number>5</number>
+ <number>9</number>
</property>
<property name="leftMargin">
<number>1</number>
</property>
<item>
<widget class="QGraphicsView" name="presetThumbnailicon">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>55</width>
<height>55</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<height>55</height>
</size>
</property>
<property name="font">
<font>
<kerning>false</kerning>
</font>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="interactive">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
<property name="renderHints">
<set>QPainter::Antialiasing</set>
</property>
<property name="transformationAnchor">
<enum>QGraphicsView::AnchorViewCenter</enum>
</property>
</widget>
</item>
- <item>
- <layout class="QVBoxLayout" name="verticalLayout_3">
- <property name="rightMargin">
- <number>5</number>
- </property>
- <item>
- <widget class="QPushButton" name="zoomOutGraphicsViewButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>32</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>32</width>
- <height>32</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="resetGraphicsViewButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>32</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>32</width>
- <height>32</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
<item>
<widget class="KisPresetLivePreviewView" name="liveBrushPreviewView">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>60</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>320</width>
<height>60</height>
</size>
</property>
<property name="font">
<font>
<kerning>false</kerning>
</font>
</property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::DefaultContextMenu</enum>
+ </property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="midLineWidth">
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="interactive">
<bool>true</bool>
</property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
<property name="renderHints">
<set>QPainter::Antialiasing|QPainter::HighQualityAntialiasing|QPainter::SmoothPixmapTransform</set>
</property>
<property name="resizeAnchor">
<enum>QGraphicsView::AnchorViewCenter</enum>
</property>
</widget>
</item>
<item>
- <layout class="QVBoxLayout" name="brushNameLayout">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
- <property name="topMargin">
- <number>5</number>
- </property>
<item>
- <widget class="QLabel" name="currentBrushNameLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>150</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>20</height>
- </size>
- </property>
- <property name="font">
- <font>
- <pointsize>10</pointsize>
- <weight>75</weight>
- <bold>true</bold>
- </font>
- </property>
- <property name="lineWidth">
- <number>0</number>
- </property>
- <property name="text">
- <string>Current Brush Name</string>
- </property>
- <property name="scaledContents">
- <bool>false</bool>
- </property>
- <property name="alignment">
- <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
- </property>
- <property name="margin">
- <number>0</number>
- </property>
- <property name="indent">
- <number>5</number>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="currentBrushEngineLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>20</height>
- </size>
- </property>
- <property name="text">
- <string>Current Brush Engine</string>
- </property>
- <property name="scaledContents">
- <bool>false</bool>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
- </property>
- <property name="indent">
- <number>5</number>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <item>
- <widget class="QPushButton" name="dirtyPresetIndicatorButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>32</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>32</width>
- <height>32</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="flat">
- <bool>false</bool>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <property name="spacing">
+ <number>10</number>
</property>
- </widget>
+ <item>
+ <widget class="QLabel" name="currentBrushNameLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>10</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>current brush</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="renameBrushPresetButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="renameBrushNameTextField">
+ <property name="minimumSize">
+ <size>
+ <width>180</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="updateBrushNameButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>28</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Save</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="cancelBrushNameUpdateButton">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>28</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Cancel</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
</item>
<item>
- <widget class="QPushButton" name="reloadPresetButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>32</width>
- <height>32</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>32</width>
- <height>32</height>
- </size>
- </property>
- <property name="text">
- <string/>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="spacing">
+ <number>6</number>
</property>
- </widget>
+ <item>
+ <widget class="QLabel" name="currentBrushEngineLabel">
+ <property name="text">
+ <string>Current Brush Engine</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="reloadPresetButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="dirtyPresetIndicatorButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
</item>
</layout>
</item>
<item>
- <spacer name="horizontalSpacer_3">
+ <spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
+ <property name="sizeType">
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
- <height>20</height>
+ <width>0</width>
+ <height>0</height>
</size>
</property>
</spacer>
</item>
- <item>
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Show:</string>
- </property>
- </widget>
- </item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>0</number>
+ </property>
<item>
- <widget class="KisHighlightedToolButton" name="showPresetsButton" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>35</width>
- <height>20</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>20</height>
- </size>
- </property>
- <property name="text" stdset="0">
- <string>Presets</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="KisHighlightedToolButton" name="showEditorButton" native="true">
+ <widget class="QPushButton" name="saveNewBrushPresetButton">
<property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="minimumSize">
- <size>
- <width>35</width>
- <height>20</height>
- </size>
- </property>
<property name="maximumSize">
<size>
<width>16777215</width>
- <height>20</height>
+ <height>28</height>
</size>
</property>
- <property name="text" stdset="0">
- <string>Settings</string>
+ <property name="text">
+ <string>Save New Brush Preset...</string>
</property>
</widget>
</item>
<item>
- <widget class="KisHighlightedToolButton" name="showScratchpadButton" native="true">
+ <widget class="QPushButton" name="saveBrushPresetButton">
<property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="minimumSize">
- <size>
- <width>35</width>
- <height>20</height>
- </size>
- </property>
<property name="maximumSize">
<size>
<width>16777215</width>
- <height>20</height>
+ <height>28</height>
</size>
</property>
- <property name="text" stdset="0">
- <string>Scratchpad</string>
- </property>
- <property name="checkable" stdset="0">
- <bool>true</bool>
+ <property name="text">
+ <string>Overwrite Brush</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="presetsContainer">
<property name="title">
<string/>
</property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
<layout class="QVBoxLayout" name="paintEngineOpContainer">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
- <number>10</number>
+ <number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <widget class="QPushButton" name="showPresetsButton">
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>12</width>
+ <height>12</height>
+ </size>
+ </property>
+ <property name="shortcut">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="presetsSidebarLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Presets</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
- <widget class="QLabel" name="label_2">
+ <widget class="QLabel" name="engineFilterLabel">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Engine:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="brushEgineComboBox">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="presetChangeViewToolButton">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="KisPresetSelectorStrip" name="presetWidget" native="true">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
- <height>350</height>
+ <height>120</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>1677215</width>
<height>1677215</height>
</size>
</property>
</widget>
</item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>5</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="bnDefaultPreset">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Engine Default Preset</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="presetsSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="brushEditorSettingsControls">
<property name="title">
<string/>
</property>
<property name="flat">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="brushEditorLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>1</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QFrame" name="frmOptionWidgetContainer">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>4</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
- <width>120</width>
+ <width>100</width>
<height>0</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,0,0,0,0">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QCheckBox" name="eraserBrushSizeCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Erase mode will use a separate brush size</string>
</property>
<property name="text">
<string>Eraser switch size</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="dirtyPresetCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Temporarily Save Tweaks To Presets</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="eraserBrushOpacityCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Erase mode will use a separate brush opacity</string>
</property>
<property name="text">
<string>Eraser switch opacity</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="KisLodAvailabilityWidget" name="wdgLodAvailability" native="true">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_5">
- <item>
- <widget class="QPushButton" name="bnDefaultPreset">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Load Engine Defaults</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="renameBrushPresetButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Rename</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="renameBrushNameTextField">
- <property name="minimumSize">
- <size>
- <width>250</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="updateBrushNameButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Save</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="cancelBrushNameUpdateButton">
- <property name="text">
- <string>Cancel</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="saveBrushPresetButton">
- <property name="text">
- <string>Overwrite Brush</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="saveNewBrushPresetButton">
- <property name="text">
- <string>Save New Brush Preset...</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
</layout>
</widget>
</item>
<item row="1" column="2">
<widget class="QGroupBox" name="scratchpadControls">
<property name="title">
<string/>
</property>
<property name="flat">
<bool>false</bool>
</property>
- <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <item>
+ <widget class="QPushButton" name="showScratchpadButton">
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>12</width>
+ <height>12</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="scratchpadSidebarLabel">
+ <property name="text">
+ <string>Scratchpad</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="KisScratchPad" name="scratchPad" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
- <height>300</height>
+ <height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0,0,0,0">
<item>
<widget class="QPushButton" name="paintPresetIcon">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="fillLayer">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="fillGradient">
<property name="toolTip">
<string>Fill area with gradient</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="fillSolid">
<property name="toolTip">
<string>Fill area with background color</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="eraseScratchPad">
<property name="toolTip">
<string>Reset area to white</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisScratchPad</class>
<extends>QWidget</extends>
<header>kis_scratch_pad.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KisPresetLivePreviewView</class>
<extends>QGraphicsView</extends>
<header>kis_preset_live_preview_view.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KisPresetSelectorStrip</class>
<extends>QWidget</extends>
<header>kis_preset_selector_strip.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KisLodAvailabilityWidget</class>
<extends>QWidget</extends>
<header>kis_lod_availability_widget.h</header>
<container>1</container>
</customwidget>
- <customwidget>
- <class>KisHighlightedToolButton</class>
- <extends>QWidget</extends>
- <header>kis_highlighted_button.h</header>
- <container>1</container>
- </customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/libs/ui/forms/wdgperformancesettings.ui b/libs/ui/forms/wdgperformancesettings.ui
index d8012e7772..73544802cf 100644
--- a/libs/ui/forms/wdgperformancesettings.ui
+++ b/libs/ui/forms/wdgperformancesettings.ui
@@ -1,354 +1,442 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgPerformanceSettings</class>
<widget class="QWidget" name="WdgPerformanceSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>753</width>
- <height>554</height>
+ <width>505</width>
+ <height>446</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
<item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>RAM (needs restarting Krita)</string>
+ <widget class="QLabel" name="label_10">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
</property>
- <layout class="QFormLayout" name="formLayout">
- <item row="0" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Memory available:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="lblTotalMemory">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>XXX MiB</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label">
- <property name="toolTip">
- <string>Krita will not use more memory than this limit.</string>
- </property>
- <property name="text">
- <string>Memory Limit:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="KisDoubleSliderSpinBox" name="sliderMemoryLimit" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="toolTip">
- <string>Krita will not use more memory than this limit.</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="KisIntParseSpinBox" name="intMemoryLimit">
- <property name="suffix">
- <string> MiB</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>Internal Pool:</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <widget class="KisDoubleSliderSpinBox" name="sliderPoolLimit" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item>
- <widget class="KisIntParseSpinBox" name="intPoolLimit">
- <property name="suffix">
- <string> MiB</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_4">
- <property name="toolTip">
- <string>When undo information reaches this limit, it will be stored in a temporary file and memory will be freed. Undo will be slower.</string>
- </property>
- <property name="text">
- <string>Swap Undo After:</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="KisDoubleSliderSpinBox" name="sliderUndoLimit" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="toolTip">
- <string>When undo information reaches this limit, it will be stored in a temporary file and memory will be freed. Undo will be slower.</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="KisIntParseSpinBox" name="intUndoLimit">
- <property name="suffix">
- <string> MiB</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>Swap File Size (needs restarting Krita)</string>
+ <property name="text">
+ <string>Note: Krita will need to be restarted for changes to take affect</string>
</property>
- <layout class="QFormLayout" name="formLayout_2">
- <item row="0" column="0">
- <widget class="QLabel" name="label_6">
- <property name="toolTip">
- <string>The swap file will not be bigger than this limit.</string>
- </property>
- <property name="text">
- <string>File Size Limit:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <item>
- <widget class="KisSliderSpinBox" name="sliderSwapSize" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="toolTip">
- <string>The swap file will not be bigger than this limit.</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="KisIntParseSpinBox" name="intSwapSize">
- <property name="suffix">
- <string> GiB</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Swap File Location:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <layout class="QHBoxLayout" name="horizontalLayout_5">
- <item>
- <widget class="QLabel" name="lblSwapFileLocation">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>TextLabel</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QToolButton" name="bnSwapFile">
- <property name="toolTip">
- <string>Select the location where Krita writes its swap files.</string>
- </property>
- <property name="text">
- <string>...</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_4">
- <property name="title">
- <string>Multithreading</string>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
</property>
- <layout class="QFormLayout" name="formLayout_3">
- <item row="0" column="0">
- <widget class="QLabel" name="label_8">
- <property name="text">
- <string>CPU Limit:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="KisSliderSpinBox" name="sliderThreadsLimit" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Krita will not use more CPU cores than selected by this limit&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_9">
- <property name="text">
- <string>Frame Rendering Clones Limit</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="KisSliderSpinBox" name="sliderFrameClonesLimit" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When rendering animation frames (into files or during animation cache regeneration), Krita will make the specified number of copies of your image and will work on them in parallel. Each copy will demand more RAM for its storage (about 20% of the size of you image), so raise this limit only if you have enough RAM installed.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recommended value:&lt;/span&gt; set Clones Limit to the number of &lt;span style=&quot; text-decoration: underline;&quot;&gt;physical&lt;/span&gt; (non-hyperthreaded) cores your CPU has&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox_3">
- <property name="title">
- <string>Advanced (needs restarting Krita)</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QCheckBox" name="chkProgressReporting">
- <property name="text">
- <string>Enable progress reporting (might affect performance)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="chkPerformanceLogging">
- <property name="text">
- <string>Enable performance logging</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_7">
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When performance logging is enabled Krita saves timing information into the '&amp;lt;working_dir&amp;gt;/log' folder. If you experience performance problems and want to help us, enable this option and add the contents of the directory to a bug report.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="chkOpenGLFramerateLogging">
- <property name="text">
- <string>Enable debug logging of OpenGL framerate</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="chkDisableVectorOptimizations">
- <property name="text">
- <string>Disable vector optimizations (for AMD CPUs)</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>General</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>RAM</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Memory available:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="lblTotalMemory">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>XXX MiB</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="toolTip">
+ <string>Krita will not use more memory than this limit.</string>
+ </property>
+ <property name="text">
+ <string>Memory Limit:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="KisDoubleSliderSpinBox" name="sliderMemoryLimit" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Krita will not use more memory than this limit.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="KisIntParseSpinBox" name="intMemoryLimit">
+ <property name="suffix">
+ <string> MiB</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Internal Pool:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="KisDoubleSliderSpinBox" name="sliderPoolLimit" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="KisIntParseSpinBox" name="intPoolLimit">
+ <property name="suffix">
+ <string> MiB</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="toolTip">
+ <string>When undo information reaches this limit, it will be stored in a temporary file and memory will be freed. Undo will be slower.</string>
+ </property>
+ <property name="text">
+ <string>Swap Undo After:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="KisDoubleSliderSpinBox" name="sliderUndoLimit" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>When undo information reaches this limit, it will be stored in a temporary file and memory will be freed. Undo will be slower.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="KisIntParseSpinBox" name="intUndoLimit">
+ <property name="suffix">
+ <string> MiB</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Swap File Size</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout_2">
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="toolTip">
+ <string>The swap file will not be bigger than this limit.</string>
+ </property>
+ <property name="text">
+ <string>File Size Limit:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="KisSliderSpinBox" name="sliderSwapSize" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>The swap file will not be bigger than this limit.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="KisIntParseSpinBox" name="intSwapSize">
+ <property name="suffix">
+ <string> GiB</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Swap File Location:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QLabel" name="lblSwapFileLocation">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Box</enum>
+ </property>
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="bnSwapFile">
+ <property name="toolTip">
+ <string>Select the location where Krita writes its swap files.</string>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Advanced</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="title">
+ <string>Multithreading</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout_3">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>CPU Limit:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="KisSliderSpinBox" name="sliderThreadsLimit" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Krita will not use more CPU cores than selected by this limit&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Frame Rendering Clones Limit</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="KisSliderSpinBox" name="sliderFrameClonesLimit" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When rendering animation frames (into files or during animation cache regeneration), Krita will make the specified number of copies of your image and will work on them in parallel. Each copy will demand more RAM for its storage (about 20% of the size of you image), so raise this limit only if you have enough RAM installed.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recommended value:&lt;/span&gt; set Clones Limit to the number of &lt;span style=&quot; text-decoration: underline;&quot;&gt;physical&lt;/span&gt; (non-hyperthreaded) cores your CPU has&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QFormLayout" name="formLayout_limitFps">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_fps">
+ <property name="text">
+ <string>Limit frames per second while painting:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="KisSliderSpinBox" name="sliderFpsLimit" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Krita will try to limit the number of screen updates per second to the given number. A lower number will decrease visual responsiveness but increase stylus precision on some systems like macOS.&lt;p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chkOpenGLFramerateLogging">
+ <property name="text">
+ <string>Debug logging of OpenGL framerate</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chkBrushSpeedLogging">
+ <property name="text">
+ <string>Debug logging for brush rendering speed</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chkDisableVectorOptimizations">
+ <property name="text">
+ <string>Disable vector optimizations (for AMD CPUs)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chkProgressReporting">
+ <property name="text">
+ <string>Progress reporting (might affect performance)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chkPerformanceLogging">
+ <property name="text">
+ <string>Performance logging</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_7">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When performance logging is enabled Krita saves timing information into the '&amp;lt;working_dir&amp;gt;/log' folder. If you experience performance problems and want to help us, enable this option and add the contents of the directory to a bug report.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
- <height>40</height>
+ <height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
- <customwidget>
- <class>KisSliderSpinBox</class>
- <extends>QWidget</extends>
- <header>kis_slider_spin_box.h</header>
- <container>1</container>
- </customwidget>
<customwidget>
<class>KisIntParseSpinBox</class>
<extends>QSpinBox</extends>
<header>kis_int_parse_spin_box.h</header>
</customwidget>
<customwidget>
<class>KisDoubleSliderSpinBox</class>
<extends>QWidget</extends>
<header>kis_slider_spin_box.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>KisSliderSpinBox</class>
+ <extends>QWidget</extends>
+ <header>kis_slider_spin_box.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/libs/ui/input/KisQtWidgetsTweaker.cpp b/libs/ui/input/KisQtWidgetsTweaker.cpp
index ed323d3b7f..5e1153504e 100644
--- a/libs/ui/input/KisQtWidgetsTweaker.cpp
+++ b/libs/ui/input/KisQtWidgetsTweaker.cpp
@@ -1,333 +1,339 @@
/* This file is part of the KDE project
Copyright (C) 2017 Nikita Vertikov <kitmouse.nikita@gmail.com>
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 "KisQtWidgetsTweaker.h"
#include <QBitArray>
#include <QComboBox>
#include <QDockWidget>
#include <QDoubleSpinBox>
#include <QEvent>
#include <QKeyEvent>
#include <QKeySequence>
#include <QLineEdit>
#include <QSpinBox>
#include "opengl/kis_opengl_canvas2.h"
#include "canvas/kis_qpainter_canvas.h"
#include "KisMainWindow.h"
Q_GLOBAL_STATIC(KisQtWidgetsTweaker, kqwt_instance)
namespace {
class ShortcutOverriderBase
{
public:
enum class DecisionOnShortcutOverride {
overrideShortcut,
askNext,
dontOverrideShortcut
};
constexpr ShortcutOverriderBase() = default;
virtual ~ShortcutOverriderBase()
{}
virtual bool interestedInEvent(QKeyEvent *event) = 0;
virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event) = 0;
virtual DecisionOnShortcutOverride finishedPhysicalKeyPressHandling()
{
return DecisionOnShortcutOverride::askNext;
}
};
class LineTextEditingShortcutOverrider : public ShortcutOverriderBase
{
public:
constexpr LineTextEditingShortcutOverrider() = default;
virtual bool interestedInEvent(QKeyEvent *event) override
{
static constexpr QKeySequence::StandardKey actionsForQLineEdit[]{
QKeySequence::MoveToNextChar
,QKeySequence::MoveToPreviousChar
,QKeySequence::MoveToStartOfLine
,QKeySequence::MoveToEndOfLine
,QKeySequence::MoveToPreviousWord
,QKeySequence::MoveToNextWord
,QKeySequence::SelectPreviousChar
,QKeySequence::SelectNextChar
,QKeySequence::SelectNextWord
,QKeySequence::SelectPreviousWord
,QKeySequence::SelectStartOfLine
,QKeySequence::SelectEndOfLine
,QKeySequence::SelectAll
,QKeySequence::Deselect
,QKeySequence::Backspace
,QKeySequence::DeleteStartOfWord
,QKeySequence::Delete
,QKeySequence::DeleteEndOfWord
,QKeySequence::DeleteEndOfLine
,QKeySequence::Copy
,QKeySequence::Paste
,QKeySequence::Cut
,QKeySequence::Undo
,QKeySequence::Redo
};
for (QKeySequence::StandardKey sk : actionsForQLineEdit) {
if (event->matches(sk)) {
event->accept();
return true;
}
}
return false;
}
virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event) override
{
+ Q_UNUSED(event);
+
if ((qobject_cast<QLineEdit*> (receiver) != nullptr)||
(qobject_cast<QSpinBox*> (receiver) != nullptr)||
(qobject_cast<QDoubleSpinBox*>(receiver) != nullptr)) {
return DecisionOnShortcutOverride::overrideShortcut;
} else {
return DecisionOnShortcutOverride::askNext;
}
}
};
class SpingboxShortcutOverrider : public ShortcutOverriderBase
{
public:
constexpr SpingboxShortcutOverrider() = default;
virtual bool interestedInEvent(QKeyEvent *event) override
{
if (event->modifiers() != Qt::NoModifier) {
return false;
}
switch (event->key()) {
case Qt::Key_Down:
case Qt::Key_Up:
case Qt::Key_PageDown:
case Qt::Key_PageUp:
event->accept();
return true;
default:
return false;
}
}
virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event) override
{
+ Q_UNUSED(event);
+
if (qobject_cast<QSpinBox*> (receiver) != nullptr||
qobject_cast<QDoubleSpinBox*>(receiver) != nullptr) {
return DecisionOnShortcutOverride::overrideShortcut;
} else {
return DecisionOnShortcutOverride::askNext;
}
}
};
class TabShortcutOverrider : public ShortcutOverriderBase
{
public:
constexpr TabShortcutOverrider() = default;
virtual bool interestedInEvent(QKeyEvent *event) override
{
bool tab = event->modifiers() == Qt::NoModifier &&
( event->key() == Qt::Key_Tab ||
event->key() == Qt::Key_Backtab);
bool shiftTab = event->modifiers() == Qt::ShiftModifier &&
event->key() == Qt::Key_Backtab;
if (tab || shiftTab) {
return true;
}else{
return false;
}
}
virtual DecisionOnShortcutOverride handleEvent(QObject *receiver, QKeyEvent *event) override
{
+ Q_UNUSED(event);
+
if (qobject_cast<KisQPainterCanvas*>(receiver) != nullptr||
qobject_cast<KisOpenGLCanvas2*> (receiver) != nullptr) {
return DecisionOnShortcutOverride::dontOverrideShortcut;
} else {
m_nooverride = true;
return DecisionOnShortcutOverride::askNext;
}
}
virtual DecisionOnShortcutOverride finishedPhysicalKeyPressHandling() override
{
if (m_nooverride){
m_nooverride = false;
return DecisionOnShortcutOverride::overrideShortcut;
}
return DecisionOnShortcutOverride::askNext;
}
private:
bool m_nooverride = false;
};
//for some reason I can't just populate constexpr
//pointer array using "new"
LineTextEditingShortcutOverrider overrider0;
SpingboxShortcutOverrider overrider1;
TabShortcutOverrider overrider2;
constexpr ShortcutOverriderBase *allShortcutOverriders[] = {
&overrider0, &overrider1, &overrider2
};
-constexpr size_t numOfShortcutOverriders =
+constexpr int numOfShortcutOverriders =
sizeof(allShortcutOverriders)/
sizeof(allShortcutOverriders[0]);
} //namespace
struct KisQtWidgetsTweaker::Private
{
public:
Private(KisQtWidgetsTweaker *parent)
: q(parent)
+ , interestedHandlers(numOfShortcutOverriders)
, lastKeyPressProcessingComplete(true)
, decision(ShortcutOverriderBase::DecisionOnShortcutOverride::askNext)
- , interestedHandlers(numOfShortcutOverriders)
{
}
const KisQtWidgetsTweaker *q;
QBitArray interestedHandlers = QBitArray(numOfShortcutOverriders);
ShortcutOverriderBase::DecisionOnShortcutOverride decision =
ShortcutOverriderBase::DecisionOnShortcutOverride::askNext;
//unsigned long lastEventTimestamp=0;
bool lastKeyPressProcessingComplete = true;
void newPhysicalKeyPressed(QKeyEvent *event)
{
for (int i=0; i < numOfShortcutOverriders; ++i) {
if (allShortcutOverriders[i]->interestedInEvent(event)) {
interestedHandlers.setBit(i);
}else{
interestedHandlers.clearBit(i);
}
}
decision = ShortcutOverriderBase::DecisionOnShortcutOverride::askNext;
lastKeyPressProcessingComplete = false;
}
};
KisQtWidgetsTweaker::KisQtWidgetsTweaker(QObject *parent)
:QObject(parent)
, d(new KisQtWidgetsTweaker::Private(this))
{
}
KisQtWidgetsTweaker::~KisQtWidgetsTweaker()
{
delete d;
}
bool KisQtWidgetsTweaker::eventFilter(QObject *receiver, QEvent *event)
{
switch(event->type()) {
case QEvent::ShortcutOverride:{
//QLineEdit and other widgets lets qt's shortcut system take away it's keyboard events
//even is it knows them, such as ctrl+backspace
//if there is application-wide shortcut, assigned to it.
//The following code fixes it
//by accepting ShortcutOverride events.
//if you press key 'a' and then 'b', qt at first call
//all handlers for 'a' key press event, and only after that
//for 'b'
QKeyEvent *key = static_cast<QKeyEvent*>(event);
if (d->lastKeyPressProcessingComplete) {
d->newPhysicalKeyPressed(key);
}
for(int i = 0; i < numOfShortcutOverriders; ++i) {
if (d->decision != ShortcutOverriderBase::DecisionOnShortcutOverride::askNext) {
break;
}
if (d->interestedHandlers.at(i)) {
d->decision = allShortcutOverriders[i]->handleEvent(receiver, key);
}
}
//if nothing said wether shortcutoverride to be accepted
//last widget that qt will ask will be kismainwindow or docker
if (qobject_cast<KisMainWindow*>(receiver)!=nullptr||
receiver->inherits(QDockWidget::staticMetaObject.className())) {
for (int i = 0; i < numOfShortcutOverriders; ++i) {
if (d->decision != ShortcutOverriderBase::DecisionOnShortcutOverride::askNext) {
break;
}
if (d->interestedHandlers.at(i)) {
d->decision = allShortcutOverriders[i]->finishedPhysicalKeyPressHandling();
}
}
d->lastKeyPressProcessingComplete = true;
}
bool retval = false;
switch (d->decision) {
case ShortcutOverriderBase::DecisionOnShortcutOverride::askNext:
event->ignore();
retval = false;
break;
case ShortcutOverriderBase::DecisionOnShortcutOverride::dontOverrideShortcut:
event->ignore();
retval = true;
break;
case ShortcutOverriderBase::DecisionOnShortcutOverride::overrideShortcut:
event->accept();
//once shortcutoverride acepted, qt stop asking everyone
//about it and proceed to handling next event
d->lastKeyPressProcessingComplete = true;
retval = true;
break;
}
return retval || QObject::eventFilter(receiver, event);
}break;
//other event types
default:
break;
}
//code for tweaking the behavior of other qt elements will go here
return QObject::eventFilter(receiver, event);
}
KisQtWidgetsTweaker *KisQtWidgetsTweaker::instance()
{
return kqwt_instance;
}
diff --git a/libs/ui/input/config/kis_input_configuration_page_item.cpp b/libs/ui/input/config/kis_input_configuration_page_item.cpp
index fc7a28c60f..9e5a10bb95 100644
--- a/libs/ui/input/config/kis_input_configuration_page_item.cpp
+++ b/libs/ui/input/config/kis_input_configuration_page_item.cpp
@@ -1,90 +1,90 @@
/*
* This file is part of the KDE project
* Copyright (C) 2013 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_input_configuration_page_item.h"
#include "kis_icon_utils.h"
#include "input/kis_abstract_input_action.h"
#include "input/kis_input_profile_manager.h"
#include "kis_action_shortcuts_model.h"
#include "kis_input_type_delegate.h"
#include "kis_input_mode_delegate.h"
#include "kis_input_editor_delegate.h"
#include "ui_kis_input_configuration_page_item.h"
KisInputConfigurationPageItem::KisInputConfigurationPageItem(QWidget *parent, Qt::WindowFlags f)
: QWidget(parent, f)
{
ui = new Ui::KisInputConfigurationPageItem;
ui->setupUi(this);
m_shortcutsModel = new KisActionShortcutsModel(this);
ui->shortcutsView->setModel(m_shortcutsModel);
ui->shortcutsView->setItemDelegateForColumn(0, new KisInputTypeDelegate(ui->shortcutsView));
ui->shortcutsView->setItemDelegateForColumn(1, new KisInputEditorDelegate(ui->shortcutsView));
ui->shortcutsView->setItemDelegateForColumn(2, new KisInputModeDelegate(ui->shortcutsView));
- ui->shortcutsView->header()->setResizeMode(QHeaderView::Stretch);
+ ui->shortcutsView->header()->setSectionResizeMode(QHeaderView::Stretch);
setExpanded(false);
QAction *deleteAction = new QAction(KisIconUtils::loadIcon("edit-delete"), i18n("Delete Shortcut"), ui->shortcutsView);
connect(deleteAction, SIGNAL(triggered(bool)), SLOT(deleteShortcut()));
ui->shortcutsView->addAction(deleteAction);
ui->shortcutsView->setContextMenuPolicy(Qt::ActionsContextMenu);
connect(ui->collapseButton, SIGNAL(clicked(bool)), SLOT(setExpanded(bool)));
}
KisInputConfigurationPageItem::~KisInputConfigurationPageItem()
{
delete ui;
}
void KisInputConfigurationPageItem::setAction(KisAbstractInputAction *action)
{
m_action = action;
ui->collapseButton->setText(action->name());
ui->descriptionLabel->setText(action->description());
m_shortcutsModel->setProfile(KisInputProfileManager::instance()->currentProfile());
m_shortcutsModel->setAction(action);
qobject_cast<KisInputModeDelegate *>(ui->shortcutsView->itemDelegateForColumn(2))->setAction(action);
}
void KisInputConfigurationPageItem::setExpanded(bool expand)
{
if (expand) {
ui->descriptionLabel->setVisible(true);
ui->shortcutsView->setVisible(true);
ui->collapseButton->setArrowType(Qt::DownArrow);
}
else {
ui->descriptionLabel->setVisible(false);
ui->shortcutsView->setVisible(false);
ui->collapseButton->setArrowType(Qt::RightArrow);
}
}
void KisInputConfigurationPageItem::deleteShortcut()
{
int row = ui->shortcutsView->selectionModel()->currentIndex().row();
if (m_shortcutsModel->canRemoveRow(row)) {
m_shortcutsModel->removeRow(row, QModelIndex());
}
}
diff --git a/libs/ui/input/kis_abstract_input_action.cpp b/libs/ui/input/kis_abstract_input_action.cpp
index 18e205c22b..3e525fec93 100644
--- a/libs/ui/input/kis_abstract_input_action.cpp
+++ b/libs/ui/input/kis_abstract_input_action.cpp
@@ -1,217 +1,217 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* 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_abstract_input_action.h"
#include <QPointF>
#include <QMouseEvent>
#include <klocalizedstring.h>
#include <kis_debug.h>
class Q_DECL_HIDDEN KisAbstractInputAction::Private
{
public:
QString id;
QString name;
QString description;
QHash<QString, int> indexes;
QPointF lastCursorPosition;
static KisInputManager* inputManager;
};
KisInputManager *KisAbstractInputAction::Private::inputManager = 0;
KisAbstractInputAction::KisAbstractInputAction(const QString & id)
: d(new Private)
{
d->id = id;
d->indexes.insert(i18n("Activate"), 0);
}
KisAbstractInputAction::~KisAbstractInputAction()
{
delete d;
}
void KisAbstractInputAction::activate(int shortcut)
{
Q_UNUSED(shortcut);
}
void KisAbstractInputAction::deactivate(int shortcut)
{
Q_UNUSED(shortcut);
}
void KisAbstractInputAction::begin(int shortcut, QEvent *event)
{
Q_UNUSED(shortcut);
if (event) {
d->lastCursorPosition = eventPosF(event);
}
}
void KisAbstractInputAction::inputEvent(QEvent* event)
{
if (event) {
QPointF newPosition = eventPosF(event);
cursorMoved(d->lastCursorPosition, newPosition);
d->lastCursorPosition = newPosition;
}
}
void KisAbstractInputAction::end(QEvent *event)
{
Q_UNUSED(event);
}
void KisAbstractInputAction::cursorMoved(const QPointF &lastPos, const QPointF &pos)
{
Q_UNUSED(lastPos);
Q_UNUSED(pos);
}
bool KisAbstractInputAction::supportsHiResInputEvents() const
{
return false;
}
KisInputManager* KisAbstractInputAction::inputManager() const
{
return Private::inputManager;
}
QString KisAbstractInputAction::name() const
{
return d->name;
}
QString KisAbstractInputAction::description() const
{
return d->description;
}
int KisAbstractInputAction::priority() const
{
return 0;
}
bool KisAbstractInputAction::canIgnoreModifiers() const
{
return false;
}
QHash< QString, int > KisAbstractInputAction::shortcutIndexes() const
{
return d->indexes;
}
QString KisAbstractInputAction::id() const
{
return d->id;
}
void KisAbstractInputAction::setName(const QString& name)
{
d->name = name;
}
void KisAbstractInputAction::setDescription(const QString& description)
{
d->description = description;
}
void KisAbstractInputAction::setShortcutIndexes(const QHash< QString, int >& indexes)
{
d->indexes = indexes;
}
void KisAbstractInputAction::setInputManager(KisInputManager *manager)
{
Private::inputManager = manager;
}
bool KisAbstractInputAction::isShortcutRequired(int shortcut) const
{
Q_UNUSED(shortcut);
return false;
}
QPoint KisAbstractInputAction::eventPos(const QEvent *event) {
if(!event) {
return QPoint();
}
switch (event->type()) {
case QEvent::MouseMove:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
return static_cast<const QMouseEvent*>(event)->pos();
case QEvent::TabletMove:
case QEvent::TabletPress:
case QEvent::TabletRelease:
return static_cast<const QTabletEvent*>(event)->pos();
case QEvent::Wheel:
return static_cast<const QWheelEvent*>(event)->pos();
case QEvent::NativeGesture:
return static_cast<const QNativeGestureEvent*>(event)->pos();
default:
warnInput << "KisAbstractInputAction" << d->name << "tried to process event data from an unhandled event type" << event->type();
return QPoint();
}
}
QPointF KisAbstractInputAction::eventPosF(const QEvent *event) {
switch (event->type()) {
case QEvent::MouseMove:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
- return static_cast<const QMouseEvent*>(event)->posF();
+ return static_cast<const QMouseEvent*>(event)->localPos();
case QEvent::TabletMove:
case QEvent::TabletPress:
case QEvent::TabletRelease:
return static_cast<const QTabletEvent*>(event)->posF();
case QEvent::Wheel:
return static_cast<const QWheelEvent*>(event)->posF();
case QEvent::NativeGesture:
return QPointF(static_cast<const QNativeGestureEvent*>(event)->pos());
default:
warnInput << "KisAbstractInputAction" << d->name << "tried to process event data from an unhandled event type" << event->type();
return QPointF();
}
}
bool KisAbstractInputAction::isAvailable() const
{
return true;
}
diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp
index 989b476b10..40818f2625 100644
--- a/libs/ui/input/kis_input_manager.cpp
+++ b/libs/ui/input/kis_input_manager.cpp
@@ -1,678 +1,681 @@
/* This file is part of the KDE project
*
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
* Copyright (C) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_input_manager.h"
#include <kis_debug.h>
#include <QQueue>
#include <klocalizedstring.h>
#include <QApplication>
#include <QTouchEvent>
-#include <QTouchEvent>
+#include <QElapsedTimer>
#include <KoToolManager.h>
#include "kis_tool_proxy.h"
#include <kis_config.h>
#include <kis_canvas2.h>
#include <KisViewManager.h>
#include <kis_image.h>
#include <kis_canvas_resource_provider.h>
#include <kis_favorite_resource_manager.h>
#include "kis_abstract_input_action.h"
#include "kis_tool_invocation_action.h"
#include "kis_pan_action.h"
#include "kis_alternate_invocation_action.h"
#include "kis_rotate_canvas_action.h"
#include "kis_zoom_action.h"
#include "kis_show_palette_action.h"
#include "kis_change_primary_setting_action.h"
#include "kis_shortcut_matcher.h"
#include "kis_stroke_shortcut.h"
#include "kis_single_action_shortcut.h"
#include "kis_touch_shortcut.h"
#include "kis_input_profile.h"
#include "kis_input_profile_manager.h"
#include "kis_shortcut_configuration.h"
#include <input/kis_tablet_debugger.h>
#include <kis_signal_compressor.h>
#include "kis_extended_modifiers_mapper.h"
#include "kis_input_manager_p.h"
template <typename T>
uint qHash(QPointer<T> value) {
return reinterpret_cast<quintptr>(value.data());
}
KisInputManager::KisInputManager(QObject *parent)
: QObject(parent), d(new Private(this))
{
d->setupActions();
connect(KoToolManager::instance(), SIGNAL(aboutToChangeTool(KoCanvasController*)), SLOT(slotAboutToChangeTool()));
connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(slotToolChanged()));
connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent()));
QApplication::instance()->
installEventFilter(new Private::ProximityNotifier(d, this));
}
KisInputManager::~KisInputManager()
{
delete d;
}
void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas)
{
d->canvasSwitcher.addCanvas(canvas);
}
void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas)
{
d->canvasSwitcher.removeCanvas(canvas);
}
void KisInputManager::toggleTabletLogger()
{
KisTabletDebugger::instance()->toggleDebugging();
}
void KisInputManager::attachPriorityEventFilter(QObject *filter, int priority)
{
Private::PriorityList::iterator begin = d->priorityEventFilter.begin();
Private::PriorityList::iterator it = begin;
Private::PriorityList::iterator end = d->priorityEventFilter.end();
it = std::find_if(begin, end,
[filter] (const Private::PriorityPair &a) { return a.second == filter; });
if (it != end) return;
it = std::find_if(begin, end,
[priority] (const Private::PriorityPair &a) { return a.first > priority; });
d->priorityEventFilter.insert(it, qMakePair(priority, filter));
d->priorityEventFilterSeqNo++;
}
void KisInputManager::detachPriorityEventFilter(QObject *filter)
{
Private::PriorityList::iterator it = d->priorityEventFilter.begin();
Private::PriorityList::iterator end = d->priorityEventFilter.end();
it = std::find_if(it, end,
[filter] (const Private::PriorityPair &a) { return a.second == filter; });
if (it != end) {
d->priorityEventFilter.erase(it);
}
}
void KisInputManager::setupAsEventFilter(QObject *receiver)
{
if (d->eventsReceiver) {
d->eventsReceiver->removeEventFilter(this);
}
d->eventsReceiver = receiver;
if (d->eventsReceiver) {
d->eventsReceiver->installEventFilter(this);
}
}
void KisInputManager::stopIgnoringEvents()
{
d->allowMouseEvents();
}
#if defined (__clang__)
#pragma GCC diagnostic ignored "-Wswitch"
#endif
bool KisInputManager::eventFilter(QObject* object, QEvent* event)
{
if (object != d->eventsReceiver) return false;
if (d->eventEater.eventFilter(object, event)) return false;
if (!d->matcher.hasRunningShortcut()) {
int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo;
for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) {
const QPointer<QObject> &filter = it->second;
if (filter.isNull()) {
it = d->priorityEventFilter.erase(it);
d->priorityEventFilterSeqNo++;
savedPriorityEventFilterSeqNo++;
continue;
}
if (filter->eventFilter(object, event)) return true;
/**
* If the filter removed itself from the filters list or
* added something there, just exit the loop
*/
if (d->priorityEventFilterSeqNo != savedPriorityEventFilterSeqNo) {
return true;
}
++it;
}
// KoToolProxy needs to pre-process some events to ensure the
// global shortcuts (not the input manager's ones) are not
// executed, in particular, this line will accept events when the
// tool is in text editing, preventing shortcut triggering
if (d->toolProxy) {
d->toolProxy->processEvent(event);
}
}
// Continue with the actual switch statement...
return eventFilterImpl(event);
}
template <class Event>
bool KisInputManager::compressMoveEventCommon(Event *event)
{
/**
* We construct a copy of this event object, so we must ensure it
* has a correct type.
*/
static_assert(std::is_same<Event, QMouseEvent>::value ||
std::is_same<Event, QTabletEvent>::value,
"event should be a mouse or a tablet event");
bool retval = false;
/**
* Compress the events if the tool doesn't need high resolution input
*/
if ((event->type() == QEvent::MouseMove ||
event->type() == QEvent::TabletMove) &&
(!d->matcher.supportsHiResInputEvents() ||
d->testingCompressBrushEvents)) {
d->compressedMoveEvent.reset(new Event(*event));
d->moveEventCompressor.start();
/**
* On Linux Qt eats the rest of unneeded events if we
* ignore the first of the chunk of tablet events. So
* generally we should never activate this feature. Only
* for testing purposes!
*/
if (d->testingAcceptCompressedTabletEvents) {
event->setAccepted(true);
}
retval = true;
} else {
slotCompressedMoveEvent();
retval = d->handleCompressedTabletEvent(event);
}
return retval;
}
bool KisInputManager::eventFilterImpl(QEvent * event)
{
bool retval = false;
if (event->type() != QEvent::Wheel) {
d->accumulatedScrollDelta = 0;
}
switch (event->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick: {
d->debugEvent<QMouseEvent, true>(event);
//Block mouse press events on Genius tablets
if (d->tabletActive) break;
if (d->ignoringQtCursorEvents()) break;
if (d->touchHasBlockedPressEvents) break;
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (d->tryHidePopupPalette()) {
retval = true;
} else {
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent);
}
//Reset signal compressor to prevent processing events before press late
d->resetCompressor();
event->setAccepted(retval);
break;
}
case QEvent::MouseButtonRelease: {
d->debugEvent<QMouseEvent, true>(event);
if (d->ignoringQtCursorEvents()) break;
if (d->touchHasBlockedPressEvents) break;
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent);
event->setAccepted(retval);
break;
}
case QEvent::ShortcutOverride: {
d->debugEvent<QKeyEvent, false>(event);
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent);
if (!keyEvent->isAutoRepeat()) {
retval = d->matcher.keyPressed(key);
} else {
retval = d->matcher.autoRepeatedKeyPressed(key);
}
/**
* Workaround for temporary switching of tools by
* KoCanvasControllerWidget. We don't need this switch because
* we handle it ourselves.
*/
retval |= !d->forwardAllEventsToTool &&
(keyEvent->key() == Qt::Key_Space ||
keyEvent->key() == Qt::Key_Escape);
break;
}
case QEvent::KeyRelease: {
d->debugEvent<QKeyEvent, false>(event);
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if (!keyEvent->isAutoRepeat()) {
Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent);
retval = d->matcher.keyReleased(key);
}
break;
}
case QEvent::MouseMove: {
d->debugEvent<QMouseEvent, true>(event);
if (d->ignoringQtCursorEvents()) break;
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
retval = compressMoveEventCommon(mouseEvent);
break;
}
case QEvent::Wheel: {
d->debugEvent<QWheelEvent, false>(event);
QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);
#ifdef Q_OS_OSX
// Some QT wheel events are actually touch pad pan events. From the QT docs:
// "Wheel events are generated for both mouse wheels and trackpad scroll gestures."
// We differentiate between touchpad events and real mouse wheels by inspecting the
// event source.
if (wheelEvent->source() == Qt::MouseEventSource::MouseEventSynthesizedBySystem) {
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.wheelEvent(KisSingleActionShortcut::WheelTrackpad, wheelEvent);
break;
}
#endif
d->accumulatedScrollDelta += wheelEvent->delta();
KisSingleActionShortcut::WheelAction action;
/**
* Ignore delta 0 events on OSX, since they are triggered by tablet
* proximity when using Wacom devices.
*/
#ifdef Q_OS_OSX
if(wheelEvent->delta() == 0) {
retval = true;
break;
}
#endif
if (wheelEvent->orientation() == Qt::Horizontal) {
if(wheelEvent->delta() < 0) {
action = KisSingleActionShortcut::WheelRight;
}
else {
action = KisSingleActionShortcut::WheelLeft;
}
}
else {
if(wheelEvent->delta() > 0) {
action = KisSingleActionShortcut::WheelUp;
}
else {
action = KisSingleActionShortcut::WheelDown;
}
}
if (qAbs(d->accumulatedScrollDelta) >= QWheelEvent::DefaultDeltasPerStep) {
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.wheelEvent(action, wheelEvent);
d->accumulatedScrollDelta = 0;
}
else {
retval = true;
}
break;
}
case QEvent::Enter:
d->debugEvent<QEvent, false>(event);
d->containsPointer = true;
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
d->allowMouseEvents();
d->touchHasBlockedPressEvents = false;
d->matcher.enterEvent();
break;
case QEvent::Leave:
d->debugEvent<QEvent, false>(event);
d->containsPointer = false;
/**
* We won't get a TabletProximityLeave event when the tablet
* is hovering above some other widget, so restore cursor
* events processing right now.
*/
d->allowMouseEvents();
d->touchHasBlockedPressEvents = false;
d->matcher.leaveEvent();
break;
case QEvent::FocusIn:
d->debugEvent<QEvent, false>(event);
KisAbstractInputAction::setInputManager(this);
//Clear all state so we don't have half-matched shortcuts dangling around.
d->matcher.reinitialize();
{ // Emulate pressing of the key that are already pressed
KisExtendedModifiersMapper mapper;
Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers();
Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) {
QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers);
eventFilterImpl(&kevent);
}
}
d->allowMouseEvents();
break;
case QEvent::FocusOut: {
d->debugEvent<QEvent, false>(event);
KisAbstractInputAction::setInputManager(this);
QPointF currentLocalPos =
canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
d->matcher.lostFocusEvent(currentLocalPos);
break;
}
case QEvent::TabletPress: {
d->debugEvent<QTabletEvent, false>(event);
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
if (d->tryHidePopupPalette()) {
retval = true;
} else {
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent);
}
event->setAccepted(true);
retval = true;
d->blockMouseEvents();
//Reset signal compressor to prevent processing events before press late
d->resetCompressor();
d->eatOneMousePress();
break;
}
case QEvent::TabletMove: {
d->debugEvent<QTabletEvent, false>(event);
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
retval = compressMoveEventCommon(tabletEvent);
+ if (d->tabletLatencyTracker) {
+ d->tabletLatencyTracker->push(tabletEvent->timestamp());
+ }
+
/**
* The flow of tablet events means the tablet is in the
* proximity area, so activate it even when the
* TabletEnterProximity event was missed (may happen when
* changing focus of the window with tablet in the proximity
* area)
*/
d->blockMouseEvents();
break;
}
case QEvent::TabletRelease: {
#ifdef Q_OS_MAC
d->allowMouseEvents();
#endif
- if (d->touchHasBlockedPressEvents) break;
d->debugEvent<QTabletEvent, false>(event);
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent);
retval = true;
event->setAccepted(true);
break;
}
case QEvent::TouchBegin:
{
if (startTouch(retval)) {
QTouchEvent *tevent = static_cast<QTouchEvent*>(event);
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.touchBeginEvent(tevent);
event->accept();
}
break;
}
case QEvent::TouchUpdate:
{
QTouchEvent *tevent = static_cast<QTouchEvent*>(event);
#ifdef Q_OS_MAC
int count = 0;
Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) {
if (point.state() != Qt::TouchPointReleased) {
count++;
}
}
if (count < 2 && tevent->touchPoints().length() > count) {
d->touchHasBlockedPressEvents = false;
retval = d->matcher.touchEndEvent(tevent);
} else {
#endif
d->touchHasBlockedPressEvents = KisConfig().disableTouchOnCanvas();
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.touchUpdateEvent(tevent);
#ifdef Q_OS_OSX
}
#endif
event->accept();
break;
}
case QEvent::TouchEnd:
{
endTouch();
QTouchEvent *tevent = static_cast<QTouchEvent*>(event);
retval = d->matcher.touchEndEvent(tevent);
event->accept();
break;
}
case QEvent::NativeGesture:
{
QNativeGestureEvent *gevent = static_cast<QNativeGestureEvent*>(event);
switch (gevent->gestureType()) {
case Qt::BeginNativeGesture:
{
if (startTouch(retval)) {
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.nativeGestureBeginEvent(gevent);
event->accept();
}
break;
}
case Qt::EndNativeGesture:
{
endTouch();
retval = d->matcher.nativeGestureEndEvent(gevent);
event->accept();
break;
}
default:
{
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.nativeGestureEvent(gevent);
event->accept();
break;
}
}
break;
}
default:
break;
}
return !retval ? d->processUnhandledEvent(event) : true;
}
bool KisInputManager::startTouch(bool &retval)
{
d->touchHasBlockedPressEvents = KisConfig().disableTouchOnCanvas();
// Touch rejection: if touch is disabled on canvas, no need to block mouse press events
if (KisConfig().disableTouchOnCanvas()) {
d->eatOneMousePress();
}
if (d->tryHidePopupPalette()) {
retval = true;
return false;
} else {
return true;
}
}
void KisInputManager::endTouch()
{
d->touchHasBlockedPressEvents = false;
}
void KisInputManager::slotCompressedMoveEvent()
{
if (d->compressedMoveEvent) {
// d->touchHasBlockedPressEvents = false;
(void) d->handleCompressedTabletEvent(d->compressedMoveEvent.data());
d->compressedMoveEvent.reset();
dbgKrita << "Compressed move event received.";
} else {
dbgKrita << "Unexpected empty move event";
}
}
KisCanvas2* KisInputManager::canvas() const
{
return d->canvas;
}
QPointer<KisToolProxy> KisInputManager::toolProxy() const
{
return d->toolProxy;
}
void KisInputManager::slotAboutToChangeTool()
{
QPointF currentLocalPos;
if (canvas() && canvas()->canvasWidget()) {
currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
}
d->matcher.lostFocusEvent(currentLocalPos);
}
void KisInputManager::slotToolChanged()
{
KoToolManager *toolManager = KoToolManager::instance();
KoToolBase *tool = toolManager->toolById(canvas(), toolManager->activeToolId());
if (tool) {
d->setMaskSyntheticEvents(tool->maskSyntheticEvents());
if (tool->isInTextMode()) {
d->forwardAllEventsToTool = true;
d->matcher.suppressAllActions(true);
} else {
d->forwardAllEventsToTool = false;
d->matcher.suppressAllActions(false);
}
}
}
void KisInputManager::profileChanged()
{
d->matcher.clearShortcuts();
KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile();
if (profile) {
const QList<KisShortcutConfiguration*> shortcuts = profile->allShortcuts();
for (KisShortcutConfiguration * const shortcut : shortcuts) {
dbgUI << "Adding shortcut" << shortcut->keys() << "for action" << shortcut->action()->name();
switch(shortcut->type()) {
case KisShortcutConfiguration::KeyCombinationType:
d->addKeyShortcut(shortcut->action(), shortcut->mode(), shortcut->keys());
break;
case KisShortcutConfiguration::MouseButtonType:
d->addStrokeShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->buttons());
break;
case KisShortcutConfiguration::MouseWheelType:
d->addWheelShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->wheel());
break;
case KisShortcutConfiguration::GestureType:
if (!d->addNativeGestureShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture())) {
d->addTouchShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture());
}
break;
default:
break;
}
}
}
else {
dbgKrita << "No Input Profile Found: canvas interaction will be impossible";
}
}
diff --git a/libs/ui/input/kis_input_manager_p.cpp b/libs/ui/input/kis_input_manager_p.cpp
index d7f25b0cfb..35c476c3f8 100644
--- a/libs/ui/input/kis_input_manager_p.cpp
+++ b/libs/ui/input/kis_input_manager_p.cpp
@@ -1,570 +1,590 @@
/*
* Copyright (C) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_input_manager_p.h"
#include <QMap>
#include <QApplication>
#include <QScopedPointer>
#include <QtGlobal>
#include "kis_input_manager.h"
#include "kis_config.h"
#include "kis_abstract_input_action.h"
#include "kis_tool_invocation_action.h"
#include "kis_stroke_shortcut.h"
#include "kis_touch_shortcut.h"
#include "kis_native_gesture_shortcut.h"
#include "kis_input_profile_manager.h"
/**
* This hungry class EventEater encapsulates event masking logic.
*
* Its basic role is to kill synthetic mouseMove events sent by Xorg or Qt after
* tablet events. Those events are sent in order to allow widgets that haven't
* implemented tablet specific functionality to seamlessly behave as if one were
* using a mouse. These synthetic events are *supposed* to be optional, or at
* least come with a flag saying "This is a fake event!!" but neither of those
* methods is trustworthy. (This is correct as of Qt 5.4 + Xorg.)
*
* Qt 5.4 provides no reliable way to see if a user's tablet is being hovered
* over the pad, since it converts all tablethover events into mousemove, with
* no option to turn this off. Moreover, sometimes the MouseButtonPress event
* from the tapping their tablet happens BEFORE the TabletPress event. This
* means we have to resort to a somewhat complicated logic. What makes this
* truly a joke is that we are not guaranteed to observe TabletProximityEnter
* events when we're using a tablet, either, you may only see an Enter event.
*
* Once we see tablet events heading our way, we can say pretty confidently that
* every mouse event is fake. There are two painful cases to consider - a
* mousePress event could arrive before the tabletPress event, or it could
* arrive much later, e.g. after tabletRelease. The first was only seen on Linux
* with Qt's XInput2 code, the solution was to hold onto mousePress events
* temporarily and wait for tabletPress later, this is contained in git history
* but is now removed. The second case is currently handled by the
* eatOneMousePress function, which waits as long as necessary to detect and
* block a single mouse press event.
*/
static bool isMouseEventType(QEvent::Type t)
{
return (t == QEvent::MouseMove ||
t == QEvent::MouseButtonPress ||
t == QEvent::MouseButtonRelease ||
t == QEvent::MouseButtonDblClick);
}
bool KisInputManager::Private::EventEater::eventFilter(QObject* target, QEvent* event )
{
Q_UNUSED(target)
auto debugEvent = [&](int i) {
if (KisTabletDebugger::instance()->debugEnabled()) {
QString pre = QString("[BLOCKED %1:]").arg(i);
QMouseEvent *ev = static_cast<QMouseEvent*>(event);
dbgTablet << KisTabletDebugger::instance()->eventToString(*ev, pre);
}
};
if (peckish && event->type() == QEvent::MouseButtonPress
// Drop one mouse press following tabletPress or touchBegin
&& (static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton)) {
peckish = false;
debugEvent(1);
return true;
}
else if (isMouseEventType(event->type()) &&
(hungry
// On Mac, we need mouse events when the tablet is in proximity, but not pressed down
// since tablet move events are not generated until after tablet press.
#ifndef Q_OS_MAC
|| (eatSyntheticEvents && static_cast<QMouseEvent*>(event)->source() != Qt::MouseEventNotSynthesized)
#endif
)) {
// Drop mouse events if enabled or event was synthetic & synthetic events are disabled
debugEvent(2);
return true;
}
return false; // All clear - let this one through!
}
void KisInputManager::Private::EventEater::activate()
{
if (!hungry && (KisTabletDebugger::instance()->debugEnabled())) {
dbgTablet << "Start blocking mouse events";
}
hungry = true;
}
void KisInputManager::Private::EventEater::deactivate()
{
if (hungry && (KisTabletDebugger::instance()->debugEnabled())) {
dbgTablet << "Stop blocking mouse events";
}
hungry = false;
}
void KisInputManager::Private::EventEater::eatOneMousePress()
{
// Enable on other platforms if getting full-pressure splotches
peckish = true;
}
bool KisInputManager::Private::ignoringQtCursorEvents()
{
return eventEater.hungry;
}
void KisInputManager::Private::setMaskSyntheticEvents(bool value)
{
eventEater.eatSyntheticEvents = value;
}
void KisInputManager::Private::setTabletActive(bool value)
{
tabletActive = value;
}
KisInputManager::Private::Private(KisInputManager *qq)
: q(qq)
, moveEventCompressor(10 /* ms */, KisSignalCompressor::FIRST_ACTIVE)
, priorityEventFilterSeqNo(0)
, canvasSwitcher(this, qq)
{
KisConfig cfg;
moveEventCompressor.setDelay(cfg.tabletEventsDelay());
testingAcceptCompressedTabletEvents = cfg.testingAcceptCompressedTabletEvents();
testingCompressBrushEvents = cfg.testingCompressBrushEvents();
+
+ if (cfg.trackTabletEventLatency()) {
+ tabletLatencyTracker = new TabletLatencyTracker();
+ }
}
static const int InputWidgetsThreshold = 2000;
static const int OtherWidgetsThreshold = 400;
KisInputManager::Private::CanvasSwitcher::CanvasSwitcher(Private *_d, QObject *p)
: QObject(p),
d(_d),
eatOneMouseStroke(false),
focusSwitchThreshold(InputWidgetsThreshold)
{
}
void KisInputManager::Private::CanvasSwitcher::setupFocusThreshold(QObject* object)
{
QWidget *widget = qobject_cast<QWidget*>(object);
KIS_SAFE_ASSERT_RECOVER_RETURN(widget);
thresholdConnections.clear();
thresholdConnections.addConnection(&focusSwitchThreshold, SIGNAL(timeout()), widget, SLOT(setFocus()));
}
void KisInputManager::Private::CanvasSwitcher::addCanvas(KisCanvas2 *canvas)
{
if (!canvas) return;
QObject *canvasWidget = canvas->canvasWidget();
if (!canvasResolver.contains(canvasWidget)) {
canvasResolver.insert(canvasWidget, canvas);
d->q->setupAsEventFilter(canvasWidget);
canvasWidget->installEventFilter(this);
setupFocusThreshold(canvasWidget);
focusSwitchThreshold.setEnabled(false);
d->canvas = canvas;
d->toolProxy = qobject_cast<KisToolProxy*>(canvas->toolProxy());
} else {
KIS_ASSERT_RECOVER_RETURN(d->canvas == canvas);
}
}
void KisInputManager::Private::CanvasSwitcher::removeCanvas(KisCanvas2 *canvas)
{
QObject *widget = canvas->canvasWidget();
canvasResolver.remove(widget);
if (d->eventsReceiver == widget) {
d->q->setupAsEventFilter(0);
}
widget->removeEventFilter(this);
}
bool isInputWidget(QWidget *w)
{
if (!w) return false;
QList<QLatin1String> types;
types << QLatin1String("QAbstractSlider");
types << QLatin1String("QAbstractSpinBox");
types << QLatin1String("QLineEdit");
types << QLatin1String("QTextEdit");
types << QLatin1String("QPlainTextEdit");
types << QLatin1String("QComboBox");
types << QLatin1String("QKeySequenceEdit");
Q_FOREACH (const QLatin1String &type, types) {
if (w->inherits(type.data())) {
return true;
}
}
return false;
}
bool KisInputManager::Private::CanvasSwitcher::eventFilter(QObject* object, QEvent* event )
{
if (canvasResolver.contains(object)) {
switch (event->type()) {
case QEvent::FocusIn: {
QFocusEvent *fevent = static_cast<QFocusEvent*>(event);
KisCanvas2 *canvas = canvasResolver.value(object);
// only relevant canvases from the same main window should be
// registered in the switcher
KIS_SAFE_ASSERT_RECOVER_BREAK(canvas);
if (canvas != d->canvas) {
eatOneMouseStroke = 2 * (fevent->reason() == Qt::MouseFocusReason);
}
d->canvas = canvas;
d->toolProxy = qobject_cast<KisToolProxy*>(canvas->toolProxy());
d->q->setupAsEventFilter(object);
object->removeEventFilter(this);
object->installEventFilter(this);
setupFocusThreshold(object);
focusSwitchThreshold.setEnabled(false);
QEvent event(QEvent::Enter);
d->q->eventFilter(object, &event);
break;
}
case QEvent::FocusOut: {
focusSwitchThreshold.setEnabled(true);
break;
}
case QEvent::Enter: {
break;
}
case QEvent::Leave: {
focusSwitchThreshold.stop();
break;
}
case QEvent::Wheel: {
QWidget *widget = static_cast<QWidget*>(object);
widget->setFocus();
break;
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::TabletPress:
case QEvent::TabletRelease:
focusSwitchThreshold.forceDone();
if (eatOneMouseStroke) {
eatOneMouseStroke--;
return true;
}
break;
case QEvent::MouseButtonDblClick:
focusSwitchThreshold.forceDone();
if (eatOneMouseStroke) {
return true;
}
break;
case QEvent::MouseMove:
case QEvent::TabletMove: {
QWidget *widget = static_cast<QWidget*>(object);
if (!widget->hasFocus()) {
const int delay =
isInputWidget(QApplication::focusWidget()) ?
InputWidgetsThreshold : OtherWidgetsThreshold;
focusSwitchThreshold.setDelayThreshold(delay);
focusSwitchThreshold.start();
}
}
break;
default:
break;
}
}
return QObject::eventFilter(object, event);
}
KisInputManager::Private::ProximityNotifier::ProximityNotifier(KisInputManager::Private *_d, QObject *p)
: QObject(p), d(_d)
{}
bool KisInputManager::Private::ProximityNotifier::eventFilter(QObject* object, QEvent* event )
{
switch (event->type()) {
case QEvent::TabletEnterProximity:
d->debugEvent<QEvent, false>(event);
// Tablet proximity events are unreliable AND fake mouse events do not
// necessarily come after tablet events, so this is insufficient.
// d->eventEater.eatOneMousePress();
// Qt sends fake mouse events instead of hover events, so not very useful.
// Don't block mouse events on tablet since tablet move events are not generated until
// after tablet press.
#ifndef Q_OS_OSX
d->blockMouseEvents();
#else
// Notify input manager that tablet proximity is entered for Genius tablets.
d->setTabletActive(true);
#endif
break;
case QEvent::TabletLeaveProximity:
d->debugEvent<QEvent, false>(event);
d->allowMouseEvents();
#ifdef Q_OS_OSX
d->setTabletActive(false);
#endif
break;
default:
break;
}
return QObject::eventFilter(object, event);
}
void KisInputManager::Private::addStrokeShortcut(KisAbstractInputAction* action, int index,
const QList<Qt::Key> &modifiers,
Qt::MouseButtons buttons)
{
KisStrokeShortcut *strokeShortcut =
new KisStrokeShortcut(action, index);
QList<Qt::MouseButton> buttonList;
if(buttons & Qt::LeftButton) {
buttonList << Qt::LeftButton;
}
if(buttons & Qt::RightButton) {
buttonList << Qt::RightButton;
}
if(buttons & Qt::MidButton) {
buttonList << Qt::MidButton;
}
if(buttons & Qt::XButton1) {
buttonList << Qt::XButton1;
}
if(buttons & Qt::XButton2) {
buttonList << Qt::XButton2;
}
if (buttonList.size() > 0) {
strokeShortcut->setButtons(QSet<Qt::Key>::fromList(modifiers), QSet<Qt::MouseButton>::fromList(buttonList));
matcher.addShortcut(strokeShortcut);
}
else {
delete strokeShortcut;
}
}
void KisInputManager::Private::addKeyShortcut(KisAbstractInputAction* action, int index,
const QList<Qt::Key> &keys)
{
if (keys.size() == 0) return;
KisSingleActionShortcut *keyShortcut =
new KisSingleActionShortcut(action, index);
//Note: Ordering is important here, Shift + V is different from V + Shift,
//which is the reason we use the last key here since most users will enter
//shortcuts as "Shift + V". Ideally this should not happen, but this is
//the way the shortcut matcher is currently implemented.
QList<Qt::Key> allKeys = keys;
Qt::Key key = allKeys.takeLast();
QSet<Qt::Key> modifiers = QSet<Qt::Key>::fromList(allKeys);
keyShortcut->setKey(modifiers, key);
matcher.addShortcut(keyShortcut);
}
void KisInputManager::Private::addWheelShortcut(KisAbstractInputAction* action, int index,
const QList<Qt::Key> &modifiers,
KisShortcutConfiguration::MouseWheelMovement wheelAction)
{
KisSingleActionShortcut *keyShortcut =
new KisSingleActionShortcut(action, index);
KisSingleActionShortcut::WheelAction a;
switch(wheelAction) {
case KisShortcutConfiguration::WheelUp:
a = KisSingleActionShortcut::WheelUp;
break;
case KisShortcutConfiguration::WheelDown:
a = KisSingleActionShortcut::WheelDown;
break;
case KisShortcutConfiguration::WheelLeft:
a = KisSingleActionShortcut::WheelLeft;
break;
case KisShortcutConfiguration::WheelRight:
a = KisSingleActionShortcut::WheelRight;
break;
case KisShortcutConfiguration::WheelTrackpad:
a = KisSingleActionShortcut::WheelTrackpad;
break;
default:
return;
}
keyShortcut->setWheel(QSet<Qt::Key>::fromList(modifiers), a);
matcher.addShortcut(keyShortcut);
}
void KisInputManager::Private::addTouchShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture)
{
KisTouchShortcut *shortcut = new KisTouchShortcut(action, index);
switch(gesture) {
case KisShortcutConfiguration::PinchGesture:
shortcut->setMinimumTouchPoints(2);
shortcut->setMaximumTouchPoints(2);
break;
case KisShortcutConfiguration::PanGesture:
shortcut->setMinimumTouchPoints(3);
shortcut->setMaximumTouchPoints(10);
break;
default:
break;
}
matcher.addShortcut(shortcut);
}
bool KisInputManager::Private::addNativeGestureShortcut(KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture)
{
// each platform should decide here which gestures are handled via QtNativeGestureEvent.
Qt::NativeGestureType type;
switch (gesture) {
#ifdef Q_OS_OSX
case KisShortcutConfiguration::PinchGesture:
type = Qt::ZoomNativeGesture;
break;
case KisShortcutConfiguration::PanGesture:
type = Qt::PanNativeGesture;
break;
case KisShortcutConfiguration::RotateGesture:
type = Qt::RotateNativeGesture;
break;
case KisShortcutConfiguration::SmartZoomGesture:
type = Qt::SmartZoomNativeGesture;
break;
#endif
default:
return false;
}
KisNativeGestureShortcut *shortcut = new KisNativeGestureShortcut(action, index, type);
matcher.addShortcut(shortcut);
return true;
}
void KisInputManager::Private::setupActions()
{
QList<KisAbstractInputAction*> actions = KisInputProfileManager::instance()->actions();
Q_FOREACH (KisAbstractInputAction *action, actions) {
KisToolInvocationAction *toolAction =
dynamic_cast<KisToolInvocationAction*>(action);
if(toolAction) {
defaultInputAction = toolAction;
}
}
connect(KisInputProfileManager::instance(), SIGNAL(currentProfileChanged()), q, SLOT(profileChanged()));
if(KisInputProfileManager::instance()->currentProfile()) {
q->profileChanged();
}
}
bool KisInputManager::Private::processUnhandledEvent(QEvent *event)
{
bool retval = false;
if (forwardAllEventsToTool ||
event->type() == QEvent::KeyPress ||
event->type() == QEvent::KeyRelease) {
defaultInputAction->processUnhandledEvent(event);
retval = true;
}
return retval && !forwardAllEventsToTool;
}
bool KisInputManager::Private::tryHidePopupPalette()
{
if (canvas->isPopupPaletteVisible()) {
canvas->slotShowPopupPalette();
return true;
}
return false;
}
#ifdef HAVE_X11
inline QPointF dividePoints(const QPointF &pt1, const QPointF &pt2) {
return QPointF(pt1.x() / pt2.x(), pt1.y() / pt2.y());
}
inline QPointF multiplyPoints(const QPointF &pt1, const QPointF &pt2) {
return QPointF(pt1.x() * pt2.x(), pt1.y() * pt2.y());
}
#endif
void KisInputManager::Private::blockMouseEvents()
{
eventEater.activate();
}
void KisInputManager::Private::allowMouseEvents()
{
eventEater.deactivate();
}
void KisInputManager::Private::eatOneMousePress()
{
eventEater.eatOneMousePress();
}
void KisInputManager::Private::resetCompressor() {
compressedMoveEvent.reset();
moveEventCompressor.stop();
}
bool KisInputManager::Private::handleCompressedTabletEvent(QEvent *event)
{
bool retval = false;
if (!matcher.pointerMoved(event) && toolProxy) {
toolProxy->forwardHoverEvent(event);
}
retval = true;
event->setAccepted(true);
return retval;
}
+
+qint64 KisInputManager::Private::TabletLatencyTracker::currentTimestamp() const
+{
+ // on OS X, we need to compute the timestamp that compares correctly against the native event timestamp,
+ // which seems to be the msecs since system startup. On Linux with WinTab, we produce the timestamp that
+ // we compare against ourselves in QWindowSystemInterface.
+
+ QElapsedTimer elapsed;
+ elapsed.start();
+ return elapsed.msecsSinceReference();
+}
+
+void KisInputManager::Private::TabletLatencyTracker::print(const QString &message)
+{
+ dbgTablet << qUtf8Printable(message);
+}
diff --git a/libs/ui/input/kis_input_manager_p.h b/libs/ui/input/kis_input_manager_p.h
index ce93448b89..1d61af9a24 100644
--- a/libs/ui/input/kis_input_manager_p.h
+++ b/libs/ui/input/kis_input_manager_p.h
@@ -1,149 +1,159 @@
/*
* Copyright (C) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <QList>
#include <QPointer>
#include <QSet>
#include <QEvent>
#include <QTouchEvent>
#include <QScopedPointer>
#include <QPointer>
+#include <QQueue>
+#include <QElapsedTimer>
#include "kis_input_manager.h"
#include "kis_shortcut_matcher.h"
#include "kis_shortcut_configuration.h"
#include "kis_canvas2.h"
#include "kis_tool_proxy.h"
#include "kis_signal_compressor.h"
#include "input/kis_tablet_debugger.h"
#include "kis_timed_signal_threshold.h"
#include "kis_signal_auto_connection.h"
-
+#include "kis_latency_tracker.h"
class KisToolInvocationAction;
class KisInputManager::Private
{
public:
Private(KisInputManager *qq);
bool tryHidePopupPalette();
void addStrokeShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, Qt::MouseButtons buttons);
void addKeyShortcut(KisAbstractInputAction* action, int index,const QList<Qt::Key> &keys);
void addTouchShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture );
bool addNativeGestureShortcut( KisAbstractInputAction* action, int index, KisShortcutConfiguration::GestureAction gesture );
void addWheelShortcut(KisAbstractInputAction* action, int index, const QList< Qt::Key >& modifiers, KisShortcutConfiguration::MouseWheelMovement wheelAction);
bool processUnhandledEvent(QEvent *event);
void setupActions();
bool handleCompressedTabletEvent(QEvent *event);
KisInputManager *q;
QPointer<KisCanvas2> canvas;
QPointer<KisToolProxy> toolProxy;
bool forwardAllEventsToTool = false;
bool ignoringQtCursorEvents();
bool touchHasBlockedPressEvents = false;
KisShortcutMatcher matcher;
KisToolInvocationAction *defaultInputAction = 0;
QObject *eventsReceiver = 0;
KisSignalCompressor moveEventCompressor;
QScopedPointer<QEvent> compressedMoveEvent;
bool testingAcceptCompressedTabletEvents = false;
bool testingCompressBrushEvents = false;
bool tabletActive = false; // Indicates whether or not tablet is in proximity
typedef QPair<int, QPointer<QObject> > PriorityPair;
typedef QList<PriorityPair> PriorityList;
PriorityList priorityEventFilter;
int priorityEventFilterSeqNo;
void blockMouseEvents();
void allowMouseEvents();
void eatOneMousePress();
void setMaskSyntheticEvents(bool value);
void setTabletActive(bool value);
void resetCompressor();
template <class Event, bool useBlocking>
void debugEvent(QEvent *event)
{
if (!KisTabletDebugger::instance()->debugEnabled()) return;
QString msg1 = useBlocking && ignoringQtCursorEvents() ? "[BLOCKED] " : "[ ]";
Event *specificEvent = static_cast<Event*>(event);
dbgTablet << KisTabletDebugger::instance()->eventToString(*specificEvent, msg1);
}
class ProximityNotifier : public QObject
{
public:
ProximityNotifier(Private *_d, QObject *p);
bool eventFilter(QObject* object, QEvent* event ) override;
private:
KisInputManager::Private *d;
};
class CanvasSwitcher : public QObject
{
public:
CanvasSwitcher(Private *_d, QObject *p);
void addCanvas(KisCanvas2 *canvas);
void removeCanvas(KisCanvas2 *canvas);
bool eventFilter(QObject* object, QEvent* event ) override;
private:
void setupFocusThreshold(QObject *object);
private:
KisInputManager::Private *d;
QMap<QObject*, QPointer<KisCanvas2>> canvasResolver;
int eatOneMouseStroke;
KisTimedSignalThreshold focusSwitchThreshold;
KisSignalAutoConnectionsStore thresholdConnections;
};
CanvasSwitcher canvasSwitcher;
struct EventEater
{
bool eventFilter(QObject* target, QEvent* event);
// This should be called after we're certain a tablet stroke has started.
void activate();
// This should be called after a tablet stroke has ended.
void deactivate();
// On Windows, we sometimes receive mouse events very late, so watch & wait.
void eatOneMousePress();
bool hungry{false}; // Continue eating mouse strokes
bool peckish{false}; // Eat a single mouse press event
bool eatSyntheticEvents{false}; // Mask all synthetic events
};
EventEater eventEater;
bool containsPointer = true;
int accumulatedScrollDelta = 0;
+
+ class TabletLatencyTracker : public KisLatencyTracker {
+ protected:
+ virtual qint64 currentTimestamp() const;
+ virtual void print(const QString &message);
+ };
+
+ KisSharedPtr<TabletLatencyTracker> tabletLatencyTracker;
};
diff --git a/libs/ui/input/wintab/kis_tablet_support_win8.cpp b/libs/ui/input/wintab/kis_tablet_support_win8.cpp
index 8649c3bc27..53bb88a43d 100644
--- a/libs/ui/input/wintab/kis_tablet_support_win8.cpp
+++ b/libs/ui/input/wintab/kis_tablet_support_win8.cpp
@@ -1,994 +1,994 @@
/*
* Copyright (c) 2017 Alvin Wong <alvinhochun@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// Get Windows 8 API prototypes and types
#ifdef WINVER
# undef WINVER
#endif
#ifdef _WIN32_WINNT
# undef _WIN32_WINNT
#endif
#define WINVER 0x0602
#define _WIN32_WINNT 0x0602
#include "kis_tablet_support_win8.h"
#include <QApplication>
#include <QDebug>
#include <QHash>
#include <QLibrary>
#include <QPointer>
#include <QTabletEvent>
#include <QVector>
#include <QWidget>
#include <QWindow>
#include <utility>
#include <kis_debug.h>
#include <windows.h>
#include <tpcshrd.h>
#ifndef Q_OS_WIN
# error This file must not be compiled for non-Windows systems
#endif
namespace
{
class Win8PointerInputApi
{
#define WIN8_POINTER_INPUT_API_LIST(FUNC) \
/* Pointer Input Functions */ \
FUNC(GetPointerPenInfo) \
FUNC(GetPointerPenInfoHistory) \
FUNC(GetPointerType) \
/* Pointer Device Functions */ \
/*FUNC(GetPointerDevices)*/ \
/*FUNC(GetPointerDeviceProperties)*/ \
FUNC(GetPointerDevice) \
FUNC(GetPointerDeviceRects) \
/*FUNC(RegisterPointerDeviceNotifications)*/ \
/* end */
bool m_loaded;
public:
#define DEFINE_FP_FROM_WINAPI(func) \
public: using p ## func ## _t = std::add_pointer<decltype(func)>::type; \
private: p ## func ## _t m_p ## func = nullptr; \
public: const p ## func ## _t &func = m_p ## func; // const fp ref to member
WIN8_POINTER_INPUT_API_LIST(DEFINE_FP_FROM_WINAPI)
#undef DEFINE_FP_FROM_WINAPI
public:
Win8PointerInputApi()
: m_loaded(false)
{
}
bool init() {
if (m_loaded) {
return true;
}
QLibrary user32Lib("user32");
if (!user32Lib.load()) {
qWarning() << "Failed to load user32.dll! This really should not happen.";
return false;
}
#define LOAD_AND_CHECK_FP_FROM_WINAPI(func) \
m_p ## func = reinterpret_cast<p ## func ## _t>(user32Lib.resolve(#func)); \
if (!m_p ## func) { \
dbgTablet << "Failed to load function " #func " from user32.dll"; \
return false; \
}
WIN8_POINTER_INPUT_API_LIST(LOAD_AND_CHECK_FP_FROM_WINAPI)
#undef LOAD_AND_CHECK_FP_FROM_WINAPI
dbgTablet << "Loaded Windows 8 Pointer Input API functions";
m_loaded = true;
return true;
}
bool isLoaded() {
return m_loaded;
}
#undef WIN8_POINTER_INPUT_API_LIST
}; // class Win8PointerInputApi
Win8PointerInputApi api;
class PointerFlagsWrapper
{
const POINTER_FLAGS f;
public:
PointerFlagsWrapper(POINTER_FLAGS flags)
: f(flags)
{}
static PointerFlagsWrapper fromPointerInfo(const POINTER_INFO &pointerInfo) {
return PointerFlagsWrapper(pointerInfo.pointerFlags);
}
static PointerFlagsWrapper fromPenInfo(const POINTER_PEN_INFO &penInfo) {
return fromPointerInfo(penInfo.pointerInfo);
}
bool isNew() const {
return f & POINTER_FLAG_NEW;
}
bool isInRange() const {
return f & POINTER_FLAG_INRANGE;
}
bool isInContact() const {
return f & POINTER_FLAG_INCONTACT;
}
bool isFirstButtonDown() const {
return f & POINTER_FLAG_FIRSTBUTTON;
}
bool isSecondButtonDown() const {
return f & POINTER_FLAG_SECONDBUTTON;
}
bool isThirdButtonDown() const {
return f & POINTER_FLAG_THIRDBUTTON;
}
bool isForthButtonDown() const {
return f & POINTER_FLAG_FOURTHBUTTON;
}
bool isFifthButtonDown() const {
return f & POINTER_FLAG_FIFTHBUTTON;
}
bool isPrimary() const {
return f & POINTER_FLAG_PRIMARY;
}
bool isConfidence() const {
return f & POINTER_FLAG_CONFIDENCE;
}
bool isCancelled() const {
return f & POINTER_FLAG_CANCELED;
}
bool isDown() const {
return f & POINTER_FLAG_DOWN;
}
bool isUpdate() const {
return f & POINTER_FLAG_UPDATE;
}
bool isUp() const {
return f & POINTER_FLAG_UP;
}
bool isWheel() const {
return f & POINTER_FLAG_WHEEL;
}
bool isHWheel() const {
return f & POINTER_FLAG_HWHEEL;
}
bool isCaptureChanged() const {
return f & POINTER_FLAG_CAPTURECHANGED;
}
bool hasTransform() const {
// mingw-w64 headers is missing this flag
// return f & POINTER_FLAG_HASTRANSFORM;
return f & 0x00400000;
}
}; // class PointerFlagsWrapper
class PenFlagsWrapper
{
const PEN_FLAGS f;
public:
PenFlagsWrapper(PEN_FLAGS flags)
: f(flags)
{}
static PenFlagsWrapper fromPenInfo(const POINTER_PEN_INFO &penInfo) {
return PenFlagsWrapper(penInfo.penFlags);
}
bool isBarrelPressed() const {
return f & PEN_FLAG_BARREL;
}
bool isInverted() const {
return f & PEN_FLAG_INVERTED;
}
bool isEraserPressed() const {
return f & PEN_FLAG_ERASER;
}
}; // class PenFlagsWrapper
class PenMaskWrapper
{
const PEN_MASK f;
public:
PenMaskWrapper(PEN_MASK mask)
:f(mask)
{}
static PenMaskWrapper fromPenInfo(const POINTER_PEN_INFO &penInfo) {
return PenMaskWrapper(penInfo.penMask);
}
bool pressureValid() const {
return f & PEN_MASK_PRESSURE;
}
bool rotationValid() const {
return f & PEN_MASK_ROTATION;
}
bool tiltXValid() const {
return f & PEN_MASK_TILT_X;
}
bool tiltYValid() const {
return f & PEN_MASK_TILT_Y;
}
}; // class PenMaskWrapper
struct PointerDeviceItem
{
// HANDLE handle;
// RECT pointerDeviceRect;
// RECT displayRect;
qreal himetricToPixelX;
qreal himetricToPixelY;
qreal pixelOffsetX;
qreal pixelOffsetY;
DISPLAYCONFIG_ROTATION deviceOrientation; // This is needed to fix tilt
};
QHash<HANDLE, PointerDeviceItem> penDevices;
struct PenPointerItem
{
// int pointerId;
// POINTER_PEN_INFO penInfo;
HWND hwnd;
HANDLE deviceHandle;
QPointer<QWidget> activeWidget; // Current widget receiving events
qreal oneOverDpr; // 1 / devicePixelRatio of activeWidget
bool widgetIsCaptured; // Current widget is capturing a pen cown event
bool widgetIsIgnored; // Pen events should be ignored until pen up
bool isCaptured() const {
return widgetIsCaptured;
}
};
QHash<int, PenPointerItem> penPointers;
// int primaryPenPointerId;
bool handlePointerMsg(const MSG &msg);
// extern "C" {
//
// LRESULT CALLBACK pointerDeviceNotificationsWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
// {
// switch (uMsg) {
// case WM_POINTERDEVICECHANGE:
// dbgTablet << "I would want to handle this WM_POINTERDEVICECHANGE event, but ms just doesn't want me to use it";
// dbgTablet << " wParam:" << wParam;
// dbgTablet << " lParam:" << lParam;
// return 0;
// case WM_POINTERDEVICEINRANGE:
// dbgTablet << "I would want to handle this WM_POINTERDEVICEINRANGE event, but ms just doesn't want me to use it";
// dbgTablet << " wParam:" << wParam;
// dbgTablet << " lParam:" << lParam;
// return 0;
// case WM_POINTERDEVICEOUTOFRANGE:
// dbgTablet << "I would want to handle this WM_POINTERDEVICEOUTOFRANGE event, but ms just doesn't want me to use it";
// dbgTablet << " wParam:" << wParam;
// dbgTablet << " lParam:" << lParam;
// return 0;
// }
// return DefWindowProcW(hwnd, uMsg, wParam, lParam);
// }
//
// } // extern "C"
} // namespace
bool KisTabletSupportWin8::isAvailable()
{
// Just try loading the APIs
return api.init();
}
bool KisTabletSupportWin8::init()
{
return api.init();
}
// void KisTabletSupportWin8::registerPointerDeviceNotifications()
// {
// const wchar_t *className = L"w8PointerMsgWindow";
// HINSTANCE hInst = static_cast<HINSTANCE>(GetModuleHandleW(nullptr));
// WNDCLASSEXW wc;
// wc.cbSize = sizeof(WNDCLASSEXW);
// wc.style = 0;
// wc.lpfnWndProc = pointerDeviceNotificationsWndProc;
// wc.cbClsExtra = 0;
// wc.cbWndExtra = 0;
// wc.hInstance = hInst;
// wc.hCursor = 0;
// wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
// wc.hIcon = 0;
// wc.hIconSm = 0;
// wc.lpszMenuName = 0;
// wc.lpszClassName = className;
//
// if (RegisterClassEx(&wc)) {
// HWND hwnd = CreateWindowEx(0, className, nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, hInst, nullptr);
// api.RegisterPointerDeviceNotifications(hwnd, TRUE);
// } else {
// dbgTablet << "Cannot register dummy window";
// }
// }
bool KisTabletSupportWin8::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
if (!result) {
// I don't know why this even happens, but it actually does
// And the same event is sent in again with result != nullptr
return false;
}
// This is only installed on Windows so there is no reason to check eventType
MSG &msg = *static_cast<MSG *>(message);
switch (msg.message) {
case WM_POINTERDOWN:
case WM_POINTERUP:
case WM_POINTERENTER:
case WM_POINTERLEAVE:
case WM_POINTERUPDATE:
case WM_POINTERCAPTURECHANGED:
{
bool handled = handlePointerMsg(msg);
if (handled) {
*result = 0;
return true;
}
break;
}
case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
*result = 0;
return true;
}
Q_UNUSED(eventType)
return false;
}
namespace {
QDebug operator<<(QDebug debug, const POINT &pt)
{
QDebugStateSaver saver(debug);
debug.nospace() << '(' << pt.x << ", " << pt.y << ')';
return debug;
}
QDebug operator<<(QDebug debug, const RECT &rect)
{
QDebugStateSaver saver(debug);
debug.nospace() << '(' << rect.left << ", " << rect.top << ", " << rect.right << ", " << rect.bottom << ')';
return debug;
}
bool registerOrUpdateDevice(HANDLE deviceHandle, const RECT &pointerDeviceRect, const RECT &displayRect, const DISPLAYCONFIG_ROTATION deviceOrientation)
{
bool isPreviouslyRegistered = penDevices.contains(deviceHandle);
PointerDeviceItem &deviceItem = penDevices[deviceHandle];
PointerDeviceItem oldDeviceItem = deviceItem;
// deviceItem.handle = deviceHandle;
deviceItem.himetricToPixelX =
static_cast<qreal>(displayRect.right - displayRect.left)
/ (pointerDeviceRect.right - pointerDeviceRect.left);
deviceItem.himetricToPixelY =
static_cast<qreal>(displayRect.bottom - displayRect.top)
/ (pointerDeviceRect.bottom - pointerDeviceRect.top);
deviceItem.pixelOffsetX = static_cast<qreal>(displayRect.left)
- deviceItem.himetricToPixelX * pointerDeviceRect.left;
deviceItem.pixelOffsetY = static_cast<qreal>(displayRect.top)
- deviceItem.himetricToPixelY * pointerDeviceRect.top;
deviceItem.deviceOrientation = deviceOrientation;
if (!isPreviouslyRegistered) {
dbgTablet << "Registered pen device" << deviceHandle
<< "with displayRect" << displayRect
<< "and deviceRect" << pointerDeviceRect
<< "scale" << deviceItem.himetricToPixelX << deviceItem.himetricToPixelY
<< "offset" << deviceItem.pixelOffsetX << deviceItem.pixelOffsetY
<< "orientation" << deviceItem.deviceOrientation;
} else if (deviceItem.himetricToPixelX != oldDeviceItem.himetricToPixelX
|| deviceItem.himetricToPixelY != oldDeviceItem.himetricToPixelY
|| deviceItem.pixelOffsetX != oldDeviceItem.pixelOffsetX
|| deviceItem.pixelOffsetY != oldDeviceItem.pixelOffsetY
|| deviceItem.deviceOrientation != oldDeviceItem.deviceOrientation) {
dbgTablet << "Updated pen device" << deviceHandle
<< "with displayRect" << displayRect
<< "and deviceRect" << pointerDeviceRect
<< "scale" << deviceItem.himetricToPixelX << deviceItem.himetricToPixelY
<< "offset" << deviceItem.pixelOffsetX << deviceItem.pixelOffsetY
<< "orientation" << deviceItem.deviceOrientation;
}
return true;
}
bool registerOrUpdateDevice(HANDLE deviceHandle)
{
RECT pointerDeviceRect, displayRect;
if (!api.GetPointerDeviceRects(deviceHandle, &pointerDeviceRect, &displayRect)) {
dbgTablet << "GetPointerDeviceRects failed";
return false;
}
POINTER_DEVICE_INFO pointerDeviceInfo;
if (!api.GetPointerDevice(deviceHandle, &pointerDeviceInfo)) {
dbgTablet << "GetPointerDevice failed";
return false;
}
return registerOrUpdateDevice(deviceHandle, pointerDeviceRect, displayRect,
static_cast<DISPLAYCONFIG_ROTATION>(pointerDeviceInfo.displayOrientation));
}
QTabletEvent makeProximityTabletEvent(const QEvent::Type eventType, const POINTER_PEN_INFO &penInfo)
{
PenFlagsWrapper penFlags = PenFlagsWrapper::fromPenInfo(penInfo);
QTabletEvent::PointerType pointerType = penFlags.isInverted() ? QTabletEvent::Eraser : QTabletEvent::Pen;
const QPointF emptyPoint;
return QTabletEvent(
eventType, // type
emptyPoint, // pos
emptyPoint, // globalPos
QTabletEvent::Stylus, // device
pointerType, // pointerType
0, // pressure
0, // xTilt
0, // yTilt
0, // tangentialPressure
0, // rotation
0, // z
Qt::NoModifier, // keyState
reinterpret_cast<qint64>(penInfo.pointerInfo.sourceDevice), // uniqueID
Qt::NoButton, // button
(Qt::MouseButtons)0 // buttons
);
}
-void rotateTiltAngles(int &tiltX, int &tiltY, const DISPLAYCONFIG_ROTATION orientation) {
- int newTiltX, newTiltY;
- switch (orientation) {
- case DISPLAYCONFIG_ROTATION_ROTATE90:
- newTiltX = -tiltY;
- newTiltY = tiltX;
- break;
- case DISPLAYCONFIG_ROTATION_ROTATE180:
- newTiltX = -tiltX;
- newTiltY = -tiltY;
- break;
- case DISPLAYCONFIG_ROTATION_ROTATE270:
- newTiltX = tiltY;
- newTiltY = -tiltX;
- break;
- case DISPLAYCONFIG_ROTATION_IDENTITY:
- default:
- newTiltX = tiltX;
- newTiltY = tiltY;
- break;
- }
- tiltX = newTiltX;
- tiltY = newTiltY;
-}
+// void rotateTiltAngles(int &tiltX, int &tiltY, const DISPLAYCONFIG_ROTATION orientation) {
+// int newTiltX, newTiltY;
+// switch (orientation) {
+// case DISPLAYCONFIG_ROTATION_ROTATE90:
+// newTiltX = -tiltY;
+// newTiltY = tiltX;
+// break;
+// case DISPLAYCONFIG_ROTATION_ROTATE180:
+// newTiltX = -tiltX;
+// newTiltY = -tiltY;
+// break;
+// case DISPLAYCONFIG_ROTATION_ROTATE270:
+// newTiltX = tiltY;
+// newTiltY = -tiltX;
+// break;
+// case DISPLAYCONFIG_ROTATION_IDENTITY:
+// default:
+// newTiltX = tiltX;
+// newTiltY = tiltY;
+// break;
+// }
+// tiltX = newTiltX;
+// tiltY = newTiltY;
+// }
QTabletEvent makePositionalTabletEvent(const QWidget *targetWidget, const QEvent::Type eventType, const POINTER_PEN_INFO &penInfo, const PointerDeviceItem &deviceItem, const PenPointerItem &penPointerItem)
{
PenFlagsWrapper penFlags = PenFlagsWrapper::fromPenInfo(penInfo);
PointerFlagsWrapper pointerFlags = PointerFlagsWrapper::fromPenInfo(penInfo);
PenMaskWrapper penMask = PenMaskWrapper::fromPenInfo(penInfo);
const QPointF globalPosF(
(deviceItem.himetricToPixelX * penInfo.pointerInfo.ptHimetricLocationRaw.x + deviceItem.pixelOffsetX) * penPointerItem.oneOverDpr,
(deviceItem.himetricToPixelY * penInfo.pointerInfo.ptHimetricLocationRaw.y + deviceItem.pixelOffsetY) * penPointerItem.oneOverDpr
);
const QPoint globalPos = globalPosF.toPoint();
const QPoint localPos = targetWidget->mapFromGlobal(globalPos);
const QPointF delta = globalPosF - globalPos;
const QPointF localPosF = localPos + delta;
const QTabletEvent::PointerType pointerType = penFlags.isInverted() ? QTabletEvent::Eraser : QTabletEvent::Pen;
Qt::MouseButton mouseButton;
if (eventType == QEvent::TabletPress) {
if (penInfo.pointerInfo.ButtonChangeType == POINTER_CHANGE_SECONDBUTTON_DOWN) {
mouseButton = Qt::RightButton;
} else {
KIS_SAFE_ASSERT_RECOVER(penInfo.pointerInfo.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_DOWN) {
qWarning() << "WM_POINTER* sent unknown ButtonChangeType" << penInfo.pointerInfo.ButtonChangeType;
}
mouseButton = Qt::LeftButton;
}
} else if (eventType == QEvent::TabletRelease) {
if (penInfo.pointerInfo.ButtonChangeType == POINTER_CHANGE_SECONDBUTTON_UP) {
mouseButton = Qt::RightButton;
} else {
KIS_SAFE_ASSERT_RECOVER(penInfo.pointerInfo.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_UP) {
qWarning() << "WM_POINTER* sent unknown ButtonChangeType" << penInfo.pointerInfo.ButtonChangeType;
}
mouseButton = Qt::LeftButton;
}
} else {
mouseButton = Qt::NoButton;
}
Qt::MouseButtons mouseButtons;
if (pointerFlags.isFirstButtonDown()) {
mouseButtons |= Qt::LeftButton;
}
if (pointerFlags.isSecondButtonDown()) {
mouseButtons |= Qt::RightButton;
}
int tiltX = 0, tiltY = 0;
if (penMask.tiltXValid()) {
tiltX = qBound(-60, penInfo.tiltX, 60);
}
if (penMask.tiltYValid()) {
tiltY = qBound(-60, penInfo.tiltY, 60);
}
- rotateTiltAngles(tiltX, tiltY, deviceItem.deviceOrientation);
+ // rotateTiltAngles(tiltX, tiltY, deviceItem.deviceOrientation);
int rotation = 0;
if (penMask.rotationValid()) {
rotation = 360 - penInfo.rotation; // Flip direction and convert to signed int
if (rotation > 180) {
rotation -= 360;
}
}
return QTabletEvent(
eventType, // type
localPosF, // pos
globalPosF, // globalPos
QTabletEvent::Stylus, // device
pointerType, // pointerType
penMask.pressureValid() ? static_cast<qreal>(penInfo.pressure) / 1024 : 0, // pressure
tiltX, // xTilt
tiltY, // yTilt
0, // tangentialPressure
rotation, // rotation
0, // z
QApplication::queryKeyboardModifiers(), // keyState
reinterpret_cast<qint64>(penInfo.pointerInfo.sourceDevice), // uniqueID
mouseButton, // button
mouseButtons // buttons
);
}
bool sendProximityTabletEvent(const QEvent::Type eventType, const POINTER_PEN_INFO &penInfo)
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(
eventType == QEvent::TabletEnterProximity || eventType == QEvent::TabletLeaveProximity,
false
);
QTabletEvent ev = makeProximityTabletEvent(eventType, penInfo);
ev.setAccepted(false);
ev.setTimestamp(penInfo.pointerInfo.dwTime);
QCoreApplication::sendEvent(qApp, &ev);
return ev.isAccepted();
}
bool sendPositionalTabletEvent(QWidget *target, const QEvent::Type eventType, const POINTER_PEN_INFO &penInfo, const PointerDeviceItem &device, const PenPointerItem &penPointerItem)
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(
eventType == QEvent::TabletMove || eventType == QEvent::TabletPress || eventType == QEvent::TabletRelease,
false
);
QTabletEvent ev = makePositionalTabletEvent(target, eventType, penInfo, device, penPointerItem);
ev.setAccepted(false);
ev.setTimestamp(penInfo.pointerInfo.dwTime);
QCoreApplication::sendEvent(target, &ev);
return ev.isAccepted();
}
bool handlePenEnterMsg(const POINTER_PEN_INFO &penInfo)
{
PointerFlagsWrapper pointerFlags = PointerFlagsWrapper::fromPenInfo(penInfo);
if (!pointerFlags.isPrimary()) {
// Don't handle non-primary pointer messages for now
dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId
<< "of device" << penInfo.pointerInfo.sourceDevice
<< "is not flagged PRIMARY";
return false;
}
// Update the device scaling factors here
// It doesn't cost much to recalculate anyway
// This ensures that the screen resolution changes are reflected
// WM_POINTERDEVICECHANGE might be useful for this, but its docs are too unclear to use
registerOrUpdateDevice(penInfo.pointerInfo.sourceDevice);
// TODO: Need a way to remove from device registration when devices are changed
// We now only handle one pointer at a time, so just clear the pointer registration
penPointers.clear();
int pointerId = penInfo.pointerInfo.pointerId;
PenPointerItem penPointerItem;
penPointerItem.hwnd = penInfo.pointerInfo.hwndTarget;
penPointerItem.deviceHandle = penInfo.pointerInfo.sourceDevice;
penPointerItem.activeWidget = nullptr;
penPointerItem.oneOverDpr = 1.0;
penPointerItem.widgetIsCaptured = false;
penPointerItem.widgetIsIgnored = false;
// penPointerItem.pointerId = pointerId;
penPointers.insert(pointerId, penPointerItem);
// primaryPenPointerId = pointerId;
// penEnter
sendProximityTabletEvent(QEvent::TabletEnterProximity, penInfo);
return false;
}
bool handlePenLeaveMsg(const POINTER_PEN_INFO &penInfo)
{
if (!penPointers.contains(penInfo.pointerInfo.pointerId)) {
dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "wasn't being handled";
return false;
}
if (!penDevices.contains(penInfo.pointerInfo.sourceDevice)) {
dbgTablet << "Device is gone from the registration???";
// TODO: re-register device?
penPointers.remove(penInfo.pointerInfo.pointerId);
return false;
}
// penLeave
sendProximityTabletEvent(QEvent::TabletLeaveProximity, penInfo);
penPointers.remove(penInfo.pointerInfo.pointerId);
return false;
}
bool handleSinglePenUpdate(PenPointerItem &penPointerItem, const POINTER_PEN_INFO &penInfo, const PointerDeviceItem &device)
{
QWidget *targetWidget;
if (penPointerItem.isCaptured()) {
if (penPointerItem.widgetIsIgnored) {
return false;
}
targetWidget = penPointerItem.activeWidget;
if (!targetWidget) {
return false;
}
} else {
QWidget *hwndWidget = QWidget::find(reinterpret_cast<WId>(penInfo.pointerInfo.hwndTarget));
if (!hwndWidget) {
dbgTablet << "HWND cannot be mapped to QWidget (what?)";
return false;
}
{
// Check popup / modal widget
QWidget *modalWidget = QApplication::activePopupWidget();
if (!modalWidget) {
modalWidget = QApplication::activeModalWidget();
}
if (modalWidget && modalWidget != hwndWidget && !modalWidget->isAncestorOf(hwndWidget)) {
return false;
}
}
{
QWindow *topLevelWindow = hwndWidget->windowHandle();
if (topLevelWindow) {
penPointerItem.oneOverDpr = 1.0 / topLevelWindow->devicePixelRatio();
} else {
penPointerItem.oneOverDpr = 1.0 / qApp->devicePixelRatio();
}
}
QPoint posInHwndWidget = hwndWidget->mapFromGlobal(QPoint(
static_cast<int>(penInfo.pointerInfo.ptPixelLocationRaw.x * penPointerItem.oneOverDpr),
static_cast<int>(penInfo.pointerInfo.ptPixelLocationRaw.y * penPointerItem.oneOverDpr)
));
targetWidget = hwndWidget->childAt(posInHwndWidget);
if (!targetWidget) {
// dbgTablet << "No childQWidget at cursor position";
targetWidget = hwndWidget;
}
// penPointerItem.activeWidget = targetWidget;
}
bool handled = sendPositionalTabletEvent(targetWidget, QEvent::TabletMove, penInfo, device, penPointerItem);
if (!handled) {
// dbgTablet << "Target widget doesn't want pen events";
}
return handled;
}
bool handlePenUpdateMsg(const POINTER_PEN_INFO &penInfo)
{
auto currentPointerIt = penPointers.find(penInfo.pointerInfo.pointerId);
if (currentPointerIt == penPointers.end()) {
- dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "wasn't being handled";
+ // dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "wasn't being handled";
return false;
}
const auto devIt = penDevices.find(penInfo.pointerInfo.sourceDevice);
if (devIt == penDevices.end()) {
dbgTablet << "Device not registered???";
return false;
}
// UINT32 entriesCount = 0;
// if (!api.GetPointerPenInfoHistory(penInfo.pointerInfo.pointerId, &entriesCount, nullptr)) {
// dbgTablet << "GetPointerPenInfoHistory (getting count) failed";
// return false;
// }
UINT32 entriesCount = penInfo.pointerInfo.historyCount;
// dbgTablet << "entriesCount:" << entriesCount;
if (entriesCount != 1) {
QVector<POINTER_PEN_INFO> penInfoArray(entriesCount);
if (!api.GetPointerPenInfoHistory(penInfo.pointerInfo.pointerId, &entriesCount, penInfoArray.data())) {
dbgTablet << "GetPointerPenInfoHistory failed";
return false;
}
bool handled = false;
// The returned array is in reverse chronological order
const auto rbegin = penInfoArray.rbegin();
const auto rend = penInfoArray.rend();
for (auto it = rbegin; it != rend; ++it) {
handled |= handleSinglePenUpdate(*currentPointerIt, *it, *devIt); // Bitwise OR doesn't short circuit
}
return handled;
} else {
return handleSinglePenUpdate(*currentPointerIt, penInfo, *devIt);
}
}
bool handlePenDownMsg(const POINTER_PEN_INFO &penInfo)
{
// PointerFlagsWrapper pointerFlags = PointerFlagsWrapper::fromPenInfo(penInfo);
// if (!pointerFlags.isPrimary()) {
// // Don't handle non-primary pointer messages for now
// return false;
// }
auto currentPointerIt = penPointers.find(penInfo.pointerInfo.pointerId);
if (currentPointerIt == penPointers.end()) {
dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "wasn't being handled";
return false;
}
currentPointerIt->hwnd = penInfo.pointerInfo.hwndTarget; // They *should* be the same, but just in case
QWidget *hwndWidget = QWidget::find(reinterpret_cast<WId>(penInfo.pointerInfo.hwndTarget));
if (!hwndWidget) {
dbgTablet << "HWND cannot be mapped to QWidget (what?)";
return false;
}
{
QWindow *topLevelWindow = hwndWidget->windowHandle();
if (topLevelWindow) {
currentPointerIt->oneOverDpr = 1.0 / topLevelWindow->devicePixelRatio();
} else {
currentPointerIt->oneOverDpr = 1.0 / qApp->devicePixelRatio();
}
}
QPoint posInHwndWidget = hwndWidget->mapFromGlobal(QPoint(
static_cast<int>(penInfo.pointerInfo.ptPixelLocationRaw.x * currentPointerIt->oneOverDpr),
static_cast<int>(penInfo.pointerInfo.ptPixelLocationRaw.y * currentPointerIt->oneOverDpr)
));
QWidget *targetWidget = hwndWidget->childAt(posInHwndWidget);
if (!targetWidget) {
dbgTablet << "No childQWidget at cursor position";
targetWidget = hwndWidget;
}
currentPointerIt->activeWidget = targetWidget;
currentPointerIt->widgetIsCaptured = true;
// dbgTablet << "QWidget" << targetWidget->windowTitle() << "is capturing pointer" << penInfo.pointerInfo.pointerId;
{
// Check popup / modal widget
QWidget *modalWidget = QApplication::activePopupWidget();
if (!modalWidget) {
modalWidget = QApplication::activeModalWidget();
}
if (modalWidget && modalWidget != hwndWidget && !modalWidget->isAncestorOf(hwndWidget)) {
currentPointerIt->widgetIsIgnored = true;
dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "is being captured but will be ignored";
return false;
}
}
// penDown
const auto devIt = penDevices.find(penInfo.pointerInfo.sourceDevice);
if (devIt == penDevices.end()) {
dbgTablet << "Device not registered???";
return false;
}
bool handled = sendPositionalTabletEvent(targetWidget, QEvent::TabletPress, penInfo, *devIt, *currentPointerIt);
if (!handled) {
// dbgTablet << "QWidget did not handle tablet down event";
}
return handled;
}
bool handlePenUpMsg(const POINTER_PEN_INFO &penInfo)
{
auto currentPointerIt = penPointers.find(penInfo.pointerInfo.pointerId);
if (currentPointerIt == penPointers.end()) {
dbgTablet << "Pointer" << penInfo.pointerInfo.pointerId << "wasn't being handled";
return false;
}
PenPointerItem &penPointerItem = *currentPointerIt;
if (!penPointerItem.isCaptured()) {
dbgTablet << "Pointer wasn't captured";
return false;
}
if (penPointerItem.widgetIsIgnored) {
penPointerItem.widgetIsCaptured = false;
penPointerItem.widgetIsIgnored = false;
return false;
}
// penUp
QWidget *targetWidget = penPointerItem.activeWidget;
if (!targetWidget) {
dbgTablet << "Previously captured target has been deleted";
penPointerItem.widgetIsCaptured = false;
return false;
}
const auto devIt = penDevices.find(penInfo.pointerInfo.sourceDevice);
if (devIt == penDevices.end()) {
dbgTablet << "Device not registered???";
return false;
}
bool handled = sendPositionalTabletEvent(targetWidget, QEvent::TabletRelease, penInfo, *devIt, penPointerItem);
// dbgTablet << "QWidget" << currentPointerIt->activeWidget->windowTitle() << "is releasing capture to pointer" << penInfo.pointerInfo.pointerId;
penPointerItem.widgetIsCaptured = false;
return handled;
}
bool handlePointerMsg(const MSG &msg)
{
if (!api.isLoaded()) {
qWarning() << "Windows 8 Pointer Input API functions not loaded";
return false;
}
int pointerId = GET_POINTERID_WPARAM(msg.wParam);
POINTER_INPUT_TYPE pointerType;
if (!api.GetPointerType(pointerId, &pointerType)) {
dbgTablet << "GetPointerType failed";
return false;
}
if (pointerType != PT_PEN) {
- dbgTablet << "pointerType" << pointerType << "is not PT_PEN";
+ // dbgTablet << "pointerType" << pointerType << "is not PT_PEN";
return false;
}
POINTER_PEN_INFO penInfo;
if (!api.GetPointerPenInfo(pointerId, &penInfo)) {
dbgTablet << "GetPointerPenInfo failed";
return false;
}
switch (msg.message) {
case WM_POINTERDOWN:
// dbgTablet << "WM_POINTERDOWN";
break;
case WM_POINTERUP:
// dbgTablet << "WM_POINTERUP";
break;
case WM_POINTERENTER:
// dbgTablet << "WM_POINTERENTER";
break;
case WM_POINTERLEAVE:
// dbgTablet << "WM_POINTERLEAVE";
break;
case WM_POINTERUPDATE:
// dbgTablet << "WM_POINTERUPDATE";
break;
case WM_POINTERCAPTURECHANGED:
// dbgTablet << "WM_POINTERCAPTURECHANGED";
break;
default:
dbgTablet << "I missed this message: " << msg.message;
break;
}
// dbgTablet << " hwnd: " << penInfo.pointerInfo.hwndTarget;
// dbgTablet << " msg hwnd: " << msg.hwnd;
// dbgTablet << " pointerId: " << pointerId;
// dbgTablet << " sourceDevice:" << penInfo.pointerInfo.sourceDevice;
// dbgTablet << " pointerFlags:" << penInfo.pointerInfo.pointerFlags;
// dbgTablet << " btnChgType: " << penInfo.pointerInfo.ButtonChangeType;
// dbgTablet << " penFlags: " << penInfo.penFlags;
// dbgTablet << " penMask: " << penInfo.penMask;
// dbgTablet << " pressure: " << penInfo.pressure;
// dbgTablet << " rotation: " << penInfo.rotation;
// dbgTablet << " tiltX: " << penInfo.tiltX;
// dbgTablet << " tiltY: " << penInfo.tiltY;
// dbgTablet << " ptPixelLocationRaw: " << penInfo.pointerInfo.ptPixelLocationRaw;
// dbgTablet << " ptHimetricLocationRaw:" << penInfo.pointerInfo.ptHimetricLocationRaw;
// RECT pointerDeviceRect, displayRect;
// if (!api.GetPointerDeviceRects(penInfo.pointerInfo.sourceDevice, &pointerDeviceRect, &displayRect)) {
// dbgTablet << "GetPointerDeviceRects failed";
// return false;
// }
// dbgTablet << " pointerDeviceRect:" << pointerDeviceRect;
// dbgTablet << " displayRect:" << displayRect;
// dbgTablet << " scaled X:" << static_cast<qreal>(penInfo.pointerInfo.ptHimetricLocationRaw.x) / (pointerDeviceRect.right - pointerDeviceRect.left) * (displayRect.right - displayRect.left);
// dbgTablet << " scaled Y:" << static_cast<qreal>(penInfo.pointerInfo.ptHimetricLocationRaw.y) / (pointerDeviceRect.bottom - pointerDeviceRect.top) * (displayRect.bottom - displayRect.top);
switch (msg.message) {
case WM_POINTERDOWN:
return handlePenDownMsg(penInfo);
case WM_POINTERUP:
return handlePenUpMsg(penInfo);
case WM_POINTERENTER:
return handlePenEnterMsg(penInfo);
case WM_POINTERLEAVE:
return handlePenLeaveMsg(penInfo);
case WM_POINTERUPDATE:
// HACK: Force further processing to force Windows to generate mouse move events
handlePenUpdateMsg(penInfo);
return false;
case WM_POINTERCAPTURECHANGED:
// TODO: Should this event be handled?
dbgTablet << "FIXME: WM_POINTERCAPTURECHANGED isn't handled";
break;
}
return false;
}
} // namespace
diff --git a/libs/ui/input/wintab/qxcbconnection.cpp b/libs/ui/input/wintab/qxcbconnection.cpp
index d597e62247..395d0adda8 100644
--- a/libs/ui/input/wintab/qxcbconnection.cpp
+++ b/libs/ui/input/wintab/qxcbconnection.cpp
@@ -1,790 +1,790 @@
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qxcbconnection_xi2.h"
#include <QX11Info>
#include <QWidget>
#include <QPointer>
#include <QElapsedTimer>
#include <QGuiApplication>
#include <QApplication>
#include <X11/extensions/XI2proto.h>
#include <xcb/xproto.h>
Q_LOGGING_CATEGORY(lcQpaXInput, "qt.qpa.input")
Q_LOGGING_CATEGORY(lcQpaXInputDevices, "qt.qpa.input.devices")
Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen")
QXcbConnection::QXcbConnection(bool canGrabServer, const char *displayName)
: m_connection(0)
, m_canGrabServer(canGrabServer)
, m_displayName(displayName ? QByteArray(displayName) : qgetenv("DISPLAY"))
#ifdef XCB_USE_XLIB
, m_xlib_display(0)
#endif
{
m_connection = QX11Info::connection();
m_xlib_display = QX11Info::display();
if (!m_connection || xcb_connection_has_error(m_connection)) {
qFatal("QXcbConnection: Could not connect to display %s", m_displayName.constData());
}
initializeAllAtoms();
#if defined(XCB_USE_XINPUT2)
initializeXInput2();
#endif
}
QXcbConnection::~QXcbConnection()
{
#if defined(XCB_USE_XINPUT2)
finalizeXInput2();
#endif
}
QXcbAtom::Atom QXcbConnection::qatom(xcb_atom_t xatom) const
{
return static_cast<QXcbAtom::Atom>(std::find(m_allAtoms, m_allAtoms + QXcbAtom::NAtoms, xatom) - m_allAtoms);
}
void *QXcbConnection::xlib_display() const
{
return m_xlib_display;
}
QByteArray QXcbConnection::atomName(xcb_atom_t atom)
{
if (!atom)
return QByteArray();
xcb_generic_error_t *error = 0;
xcb_get_atom_name_cookie_t cookie = Q_XCB_CALL(xcb_get_atom_name(xcb_connection(), atom));
xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(xcb_connection(), cookie, &error);
if (error) {
qWarning() << "QXcbConnection::atomName: bad Atom" << atom;
free(error);
}
if (reply) {
QByteArray result(xcb_get_atom_name_name(reply), xcb_get_atom_name_name_length(reply));
free(reply);
return result;
}
return QByteArray();
}
static const char * xcb_atomnames = {
// window-manager <-> client protocols
"WM_PROTOCOLS\0"
"WM_DELETE_WINDOW\0"
"WM_TAKE_FOCUS\0"
"_NET_WM_PING\0"
"_NET_WM_CONTEXT_HELP\0"
"_NET_WM_SYNC_REQUEST\0"
"_NET_WM_SYNC_REQUEST_COUNTER\0"
"MANAGER\0"
"_NET_SYSTEM_TRAY_OPCODE\0"
// ICCCM window state
"WM_STATE\0"
"WM_CHANGE_STATE\0"
"WM_CLASS\0"
"WM_NAME\0"
// Session management
"WM_CLIENT_LEADER\0"
"WM_WINDOW_ROLE\0"
"SM_CLIENT_ID\0"
// Clipboard
"CLIPBOARD\0"
"INCR\0"
"TARGETS\0"
"MULTIPLE\0"
"TIMESTAMP\0"
"SAVE_TARGETS\0"
"CLIP_TEMPORARY\0"
"_QT_SELECTION\0"
"_QT_CLIPBOARD_SENTINEL\0"
"_QT_SELECTION_SENTINEL\0"
"CLIPBOARD_MANAGER\0"
"RESOURCE_MANAGER\0"
"_XSETROOT_ID\0"
"_QT_SCROLL_DONE\0"
"_QT_INPUT_ENCODING\0"
"_QT_CLOSE_CONNECTION\0"
"_MOTIF_WM_HINTS\0"
"DTWM_IS_RUNNING\0"
"ENLIGHTENMENT_DESKTOP\0"
"_DT_SAVE_MODE\0"
"_SGI_DESKS_MANAGER\0"
// EWMH (aka NETWM)
"_NET_SUPPORTED\0"
"_NET_VIRTUAL_ROOTS\0"
"_NET_WORKAREA\0"
"_NET_MOVERESIZE_WINDOW\0"
"_NET_WM_MOVERESIZE\0"
"_NET_WM_NAME\0"
"_NET_WM_ICON_NAME\0"
"_NET_WM_ICON\0"
"_NET_WM_PID\0"
"_NET_WM_WINDOW_OPACITY\0"
"_NET_WM_STATE\0"
"_NET_WM_STATE_ABOVE\0"
"_NET_WM_STATE_BELOW\0"
"_NET_WM_STATE_FULLSCREEN\0"
"_NET_WM_STATE_MAXIMIZED_HORZ\0"
"_NET_WM_STATE_MAXIMIZED_VERT\0"
"_NET_WM_STATE_MODAL\0"
"_NET_WM_STATE_STAYS_ON_TOP\0"
"_NET_WM_STATE_DEMANDS_ATTENTION\0"
"_NET_WM_USER_TIME\0"
"_NET_WM_USER_TIME_WINDOW\0"
"_NET_WM_FULL_PLACEMENT\0"
"_NET_WM_WINDOW_TYPE\0"
"_NET_WM_WINDOW_TYPE_DESKTOP\0"
"_NET_WM_WINDOW_TYPE_DOCK\0"
"_NET_WM_WINDOW_TYPE_TOOLBAR\0"
"_NET_WM_WINDOW_TYPE_MENU\0"
"_NET_WM_WINDOW_TYPE_UTILITY\0"
"_NET_WM_WINDOW_TYPE_SPLASH\0"
"_NET_WM_WINDOW_TYPE_DIALOG\0"
"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0"
"_NET_WM_WINDOW_TYPE_POPUP_MENU\0"
"_NET_WM_WINDOW_TYPE_TOOLTIP\0"
"_NET_WM_WINDOW_TYPE_NOTIFICATION\0"
"_NET_WM_WINDOW_TYPE_COMBO\0"
"_NET_WM_WINDOW_TYPE_DND\0"
"_NET_WM_WINDOW_TYPE_NORMAL\0"
"_KDE_NET_WM_WINDOW_TYPE_OVERRIDE\0"
"_KDE_NET_WM_FRAME_STRUT\0"
"_NET_FRAME_EXTENTS\0"
"_NET_STARTUP_INFO\0"
"_NET_STARTUP_INFO_BEGIN\0"
"_NET_SUPPORTING_WM_CHECK\0"
"_NET_WM_CM_S0\0"
"_NET_SYSTEM_TRAY_VISUAL\0"
"_NET_ACTIVE_WINDOW\0"
// Property formats
"TEXT\0"
"UTF8_STRING\0"
"CARDINAL\0"
// xdnd
"XdndEnter\0"
"XdndPosition\0"
"XdndStatus\0"
"XdndLeave\0"
"XdndDrop\0"
"XdndFinished\0"
"XdndTypeList\0"
"XdndActionList\0"
"XdndSelection\0"
"XdndAware\0"
"XdndProxy\0"
"XdndActionCopy\0"
"XdndActionLink\0"
"XdndActionMove\0"
"XdndActionPrivate\0"
// Motif DND
"_MOTIF_DRAG_AND_DROP_MESSAGE\0"
"_MOTIF_DRAG_INITIATOR_INFO\0"
"_MOTIF_DRAG_RECEIVER_INFO\0"
"_MOTIF_DRAG_WINDOW\0"
"_MOTIF_DRAG_TARGETS\0"
"XmTRANSFER_SUCCESS\0"
"XmTRANSFER_FAILURE\0"
// Xkb
"_XKB_RULES_NAMES\0"
// XEMBED
"_XEMBED\0"
"_XEMBED_INFO\0"
// XInput2
"Button Left\0"
"Button Middle\0"
"Button Right\0"
"Button Wheel Up\0"
"Button Wheel Down\0"
"Button Horiz Wheel Left\0"
"Button Horiz Wheel Right\0"
"Abs MT Position X\0"
"Abs MT Position Y\0"
"Abs MT Touch Major\0"
"Abs MT Touch Minor\0"
"Abs MT Pressure\0"
"Abs MT Tracking ID\0"
"Max Contacts\0"
"Rel X\0"
"Rel Y\0"
// XInput2 tablet
"Abs X\0"
"Abs Y\0"
"Abs Pressure\0"
"Abs Tilt X\0"
"Abs Tilt Y\0"
"Abs Wheel\0"
"Abs Distance\0"
"Wacom Serial IDs\0"
"INTEGER\0"
"Rel Horiz Wheel\0"
"Rel Vert Wheel\0"
"Rel Horiz Scroll\0"
"Rel Vert Scroll\0"
"_XSETTINGS_SETTINGS\0"
"_COMPIZ_DECOR_PENDING\0"
"_COMPIZ_DECOR_REQUEST\0"
"_COMPIZ_DECOR_DELETE_PIXMAP\0" // \0\0 terminates loop.
};
void QXcbConnection::initializeAllAtoms() {
const char *names[QXcbAtom::NAtoms];
const char *ptr = xcb_atomnames;
int i = 0;
while (*ptr) {
names[i++] = ptr;
while (*ptr)
++ptr;
++ptr;
}
Q_ASSERT(i == QXcbAtom::NPredefinedAtoms);
QByteArray settings_atom_name("_QT_SETTINGS_TIMESTAMP_");
settings_atom_name += m_displayName;
names[i++] = settings_atom_name;
xcb_intern_atom_cookie_t cookies[QXcbAtom::NAtoms];
Q_ASSERT(i == QXcbAtom::NAtoms);
for (i = 0; i < QXcbAtom::NAtoms; ++i)
cookies[i] = xcb_intern_atom(xcb_connection(), false, strlen(names[i]), names[i]);
for (i = 0; i < QXcbAtom::NAtoms; ++i) {
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(xcb_connection(), cookies[i], 0);
m_allAtoms[i] = reply->atom;
free(reply);
}
}
bool QXcbConnection::xi2MouseEvents() const
{
static bool mouseViaXI2 = !qEnvironmentVariableIsSet("QT_XCB_NO_XI2_MOUSE");
return mouseViaXI2;
}
void QXcbConnection::notifyEnterEvent(xcb_enter_notify_event_t *event)
{
xcb_window_t window;
// first cleaning up deleted windows: assuming 0 is not a valid window id
while ((window = m_windowMapper.key(0,0)) != 0) {
m_windowMapper.remove(window);
}
addWindowFromXi2Id(event->event);
}
void QXcbConnection::addWindowFromXi2Id(xcb_window_t id)
{
if (!m_windowMapper.contains(id)) {
QWidget *widget = QWidget::find(id);
if (widget) {
QWindow *windowHandle = widget->windowHandle();
m_windowMapper.insert(id, windowHandle);
}
}
}
QWindow* QXcbConnection::windowFromId(xcb_window_t id)
{
QWindow *window = m_windowMapper.value(id, 0);
// Try to fetch the window Id lazily. It is needed when the cursor gets under
// a popup window or a popup dialog, which doesn't produce any enter event on
// some systems
if (!window) {
addWindowFromXi2Id(id);
window = m_windowMapper.value(id, 0);
}
return window;
}
static int xi2ValuatorOffset(unsigned char *maskPtr, int maskLen, int number)
{
int offset = 0;
for (int i = 0; i < maskLen; i++) {
if (number < 8) {
if ((maskPtr[i] & (1 << number)) == 0)
return -1;
}
for (int j = 0; j < 8; j++) {
if (j == number)
return offset;
if (maskPtr[i] & (1 << j))
offset++;
}
number -= 8;
}
return -1;
}
bool QXcbConnection::xi2GetValuatorValueIfSet(void *event, int valuatorNum, double *value)
{
xXIDeviceEvent *xideviceevent = static_cast<xXIDeviceEvent *>(event);
unsigned char *buttonsMaskAddr = (unsigned char*)&xideviceevent[1];
unsigned char *valuatorsMaskAddr = buttonsMaskAddr + xideviceevent->buttons_len * 4;
FP3232 *valuatorsValuesAddr = (FP3232*)(valuatorsMaskAddr + xideviceevent->valuators_len * 4);
int valuatorOffset = xi2ValuatorOffset(valuatorsMaskAddr, xideviceevent->valuators_len, valuatorNum);
if (valuatorOffset < 0)
return false;
*value = valuatorsValuesAddr[valuatorOffset].integral;
*value += ((double)valuatorsValuesAddr[valuatorOffset].frac / (1 << 16) / (1 << 16));
return true;
}
// Starting from the xcb version 1.9.3 struct xcb_ge_event_t has changed:
// - "pad0" became "extension"
// - "pad1" and "pad" became "pad0"
// New and old version of this struct share the following fields:
// NOTE: API might change again in the next release of xcb in which case this comment will
// need to be updated to reflect the reality.
typedef struct qt_xcb_ge_event_t {
uint8_t response_type;
uint8_t extension;
uint16_t sequence;
uint32_t length;
uint16_t event_type;
} qt_xcb_ge_event_t;
bool QXcbConnection::xi2PrepareXIGenericDeviceEvent(xcb_ge_event_t *ev, int opCode)
{
qt_xcb_ge_event_t *event = (qt_xcb_ge_event_t *)ev;
// xGenericEvent has "extension" on the second byte, the same is true for xcb_ge_event_t starting from
// the xcb version 1.9.3, prior to that it was called "pad0".
if (event->extension == opCode) {
// xcb event structs contain stuff that wasn't on the wire, the full_sequence field
// adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes.
// Move this data back to have the same layout in memory as it was on the wire
// and allow casting, overwriting the full_sequence field.
memmove((char*) event + 32, (char*) event + 36, event->length * 4);
return true;
}
return false;
}
class Q_GUI_EXPORT QWindowSystemInterfacePrivate {
public:
enum EventType {
UserInputEvent = 0x100,
Close = UserInputEvent | 0x01,
GeometryChange = 0x02,
Enter = UserInputEvent | 0x03,
Leave = UserInputEvent | 0x04,
ActivatedWindow = 0x05,
WindowStateChanged = 0x06,
Mouse = UserInputEvent | 0x07,
FrameStrutMouse = UserInputEvent | 0x08,
Wheel = UserInputEvent | 0x09,
Key = UserInputEvent | 0x0a,
Touch = UserInputEvent | 0x0b,
ScreenOrientation = 0x0c,
ScreenGeometry = 0x0d,
ScreenAvailableGeometry = 0x0e,
ScreenLogicalDotsPerInch = 0x0f,
ScreenRefreshRate = 0x10,
ThemeChange = 0x11,
Expose_KRITA_XXX = 0x12,
FileOpen = UserInputEvent | 0x13,
Tablet = UserInputEvent | 0x14,
TabletEnterProximity = UserInputEvent | 0x15,
TabletLeaveProximity = UserInputEvent | 0x16,
PlatformPanel = UserInputEvent | 0x17,
ContextMenu = UserInputEvent | 0x18,
EnterWhatsThisMode = UserInputEvent | 0x19,
#ifndef QT_NO_GESTURES
Gesture = UserInputEvent | 0x1a,
#endif
ApplicationStateChanged = 0x19,
FlushEvents = 0x20,
WindowScreenChanged = 0x21
};
class WindowSystemEvent {
public:
enum {
Synthetic = 0x1,
NullWindow = 0x2
};
explicit WindowSystemEvent(EventType t)
: type(t), flags(0) { }
virtual ~WindowSystemEvent() { }
bool synthetic() const { return flags & Synthetic; }
bool nullWindow() const { return flags & NullWindow; }
EventType type;
int flags;
};
class UserEvent : public WindowSystemEvent {
public:
UserEvent(QWindow * w, ulong time, EventType t)
: WindowSystemEvent(t), window(w), timestamp(time)
{
if (!w)
flags |= NullWindow;
}
QPointer<QWindow> window;
unsigned long timestamp;
};
class InputEvent: public UserEvent {
public:
InputEvent(QWindow * w, ulong time, EventType t, Qt::KeyboardModifiers mods)
: UserEvent(w, time, t), modifiers(mods) {}
Qt::KeyboardModifiers modifiers;
};
class TabletEvent : public InputEvent {
public:
static void handleTabletEvent(QWindow *w, const QPointF &local, const QPointF &global,
int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt,
qreal tangentialPressure, qreal rotation, int z, qint64 uid,
Qt::KeyboardModifiers modifiers = Qt::NoModifier);
TabletEvent(QWindow *w, ulong time, const QPointF &local, const QPointF &global,
int device, int pointerType, Qt::MouseButtons b, qreal pressure, int xTilt, int yTilt, qreal tpressure,
qreal rotation, int z, qint64 uid, Qt::KeyboardModifiers mods)
: InputEvent(w, time, Tablet, mods),
buttons(b), local(local), global(global), device(device), pointerType(pointerType),
pressure(pressure), xTilt(xTilt), yTilt(yTilt), tangentialPressure(tpressure),
rotation(rotation), z(z), uid(uid) { }
Qt::MouseButtons buttons;
QPointF local;
QPointF global;
int device;
int pointerType;
qreal pressure;
int xTilt;
int yTilt;
qreal tangentialPressure;
qreal rotation;
int z;
qint64 uid;
};
class WheelEvent : public InputEvent {
public:
#if QT_VERSION >= 0x050700
WheelEvent(QWindow *w, ulong time, const QPointF & local, const QPointF & global, QPoint pixelD, QPoint angleD, int qt4D, Qt::Orientation qt4O,
Qt::KeyboardModifiers mods, Qt::ScrollPhase phase = Qt::NoScrollPhase, Qt::MouseEventSource src = Qt::MouseEventNotSynthesized)
#else
WheelEvent(QWindow *w, ulong time, const QPointF & local, const QPointF & global, QPoint pixelD, QPoint angleD, int qt4D, Qt::Orientation qt4O,
Qt::KeyboardModifiers mods, Qt::ScrollPhase phase = Qt::ScrollUpdate, Qt::MouseEventSource src = Qt::MouseEventNotSynthesized)
#endif
: InputEvent(w, time, Wheel, mods), pixelDelta(pixelD), angleDelta(angleD), qt4Delta(qt4D), qt4Orientation(qt4O), localPos(local), globalPos(global), phase(phase), source(src) { }
QPoint pixelDelta;
QPoint angleDelta;
int qt4Delta;
Qt::Orientation qt4Orientation;
QPointF localPos;
QPointF globalPos;
Qt::ScrollPhase phase;
Qt::MouseEventSource source;
};
};
void processTabletEvent(QWindowSystemInterfacePrivate::TabletEvent *e);
static QElapsedTimer g_eventTimer;
struct EventTimerStaticInitializer
{
EventTimerStaticInitializer() {
g_eventTimer.start();
}
};
EventTimerStaticInitializer __timerStaticInitializer;
Qt::MouseButtons tabletState = Qt::NoButton;
QPointer<QWidget> tabletPressWidget = 0;
void QWindowSystemInterface::handleTabletEvent(QWindow *w, const QPointF &local, const QPointF &global,
int device, int pointerType, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt,
qreal tangentialPressure, qreal rotation, int z, qint64 uid,
Qt::KeyboardModifiers modifiers)
{
- qint64 timestamp = g_eventTimer.elapsed();
+ qint64 timestamp = g_eventTimer.msecsSinceReference() + g_eventTimer.elapsed();
QWindowSystemInterfacePrivate::TabletEvent *e =
new QWindowSystemInterfacePrivate::TabletEvent(w, timestamp, local, global, device, pointerType, buttons, pressure,
xTilt, yTilt, tangentialPressure, rotation, z, uid, modifiers);
processTabletEvent(e);
}
void processTabletEvent(QWindowSystemInterfacePrivate::TabletEvent *e)
{
#ifndef QT_NO_TABLETEVENT
QEvent::Type type = QEvent::TabletMove;
if (e->buttons != tabletState)
type = (e->buttons > tabletState) ? QEvent::TabletPress : QEvent::TabletRelease;
bool localValid = true;
// It can happen that we got no tablet release event. Just catch it here
// and clean up the state.
if (type == QEvent::TabletMove && e->buttons == Qt::NoButton) {
tabletPressWidget = 0;
}
QWidget *targetWidget = 0;
if (tabletPressWidget) {
targetWidget = tabletPressWidget;
localValid = false;
} else if (e->window) {
/**
* Here we use a weird way of converting QWindow into a
* QWidget. The problem is that the Qt itself does it by just
* converting QWindow into QWidgetWindow. But the latter one
* is private, so we cannot use it.
*
* We also cannot use QApplication::widegtAt(). We *MUST NOT*!
* There is some but in XCB: if we call
* QApplication::topLevelAt() during the event processing, the
* Enter/Leave events stop arriving. Or, more precisely, they
* start to errive at random points in time. Which makes
* KisShortcutMatcher go crazy of course.
*
* So instead of just fetching the toplevel window we decrypt
* the pointer using WinId mapping.
*/
targetWidget = QWidget::find(e->window->winId());
if (targetWidget) {
QWidget *childWidget = targetWidget->childAt(e->local.toPoint());
if (childWidget) {
targetWidget = childWidget;
localValid = false;
}
}
}
if (!targetWidget) {
targetWidget = QApplication::widgetAt(e->global.toPoint());
localValid = false;
if (!targetWidget) return;
}
if (type == QEvent::TabletPress) {
tabletPressWidget = targetWidget;
} else if (type == QEvent::TabletRelease) {
tabletPressWidget = 0;
}
QPointF local = e->local;
if (!localValid) {
QPointF delta = e->global - e->global.toPoint();
local = targetWidget->mapFromGlobal(e->global.toPoint()) + delta;
}
Qt::MouseButtons stateChange = e->buttons ^ tabletState;
Qt::MouseButton button = Qt::NoButton;
for (int check = Qt::LeftButton; check <= int(Qt::MaxMouseButton); check = check << 1) {
if (check & stateChange) {
button = Qt::MouseButton(check);
break;
}
}
QTabletEvent ev(type, local, e->global,
e->device, e->pointerType, e->pressure, e->xTilt, e->yTilt,
e->tangentialPressure, e->rotation, e->z,
e->modifiers, e->uid, button, e->buttons);
ev.setTimestamp(e->timestamp);
QGuiApplication::sendEvent(targetWidget, &ev);
tabletState = e->buttons;
#else
Q_UNUSED(e)
#endif
}
void QWindowSystemInterface::handleTabletEnterProximityEvent(int device, int pointerType, qint64 uid)
{
- qint64 timestamp = g_eventTimer.elapsed();
+ qint64 timestamp = g_eventTimer.msecsSinceReference() + g_eventTimer.elapsed();
QTabletEvent ev(QEvent::TabletEnterProximity, QPointF(), QPointF(),
device, pointerType, 0, 0, 0,
0, 0, 0,
Qt::NoModifier, uid, Qt::NoButton, tabletState);
ev.setTimestamp(timestamp);
QGuiApplication::sendEvent(qGuiApp, &ev);
}
void QWindowSystemInterface::handleTabletLeaveProximityEvent(int device, int pointerType, qint64 uid)
{
- qint64 timestamp = g_eventTimer.elapsed();
+ qint64 timestamp = g_eventTimer.msecsSinceReference() + g_eventTimer.elapsed();
QTabletEvent ev(QEvent::TabletLeaveProximity, QPointF(), QPointF(),
device, pointerType, 0, 0, 0,
0, 0, 0,
Qt::NoModifier, uid, Qt::NoButton, tabletState);
ev.setTimestamp(timestamp);
QGuiApplication::sendEvent(qGuiApp, &ev);
}
void processWheelEvent(QWindowSystemInterfacePrivate::WheelEvent *e);
void QWindowSystemInterface::handleWheelEvent(QWindow *tlw, ulong timestamp, const QPointF & local, const QPointF & global, QPoint pixelDelta, QPoint angleDelta, Qt::KeyboardModifiers mods, Qt::ScrollPhase phase, Qt::MouseEventSource source)
{
// Qt 4 sends two separate wheel events for horizontal and vertical
// deltas. For Qt 5 we want to send the deltas in one event, but at the
// same time preserve source and behavior compatibility with Qt 4.
//
// In addition high-resolution pixel-based deltas are also supported.
// Platforms that does not support these may pass a null point here.
// Angle deltas must always be sent in addition to pixel deltas.
QScopedPointer<QWindowSystemInterfacePrivate::WheelEvent> e;
// Pass Qt::ScrollBegin and Qt::ScrollEnd through
// even if the wheel delta is null.
if (angleDelta.isNull() && phase == Qt::ScrollUpdate)
return;
// Simple case: vertical deltas only:
if (angleDelta.y() != 0 && angleDelta.x() == 0) {
e.reset(new QWindowSystemInterfacePrivate::WheelEvent(tlw, timestamp, local, global, pixelDelta, angleDelta, angleDelta.y(), Qt::Vertical, mods, phase, source));
processWheelEvent(e.data());
return;
}
// Simple case: horizontal deltas only:
if (angleDelta.y() == 0 && angleDelta.x() != 0) {
e.reset(new QWindowSystemInterfacePrivate::WheelEvent(tlw, timestamp, local, global, pixelDelta, angleDelta, angleDelta.x(), Qt::Horizontal, mods, phase, source));
processWheelEvent(e.data());
return;
}
// Both horizontal and vertical deltas: Send two wheel events.
// The first event contains the Qt 5 pixel and angle delta as points,
// and in addition the Qt 4 compatibility vertical angle delta.
e.reset(new QWindowSystemInterfacePrivate::WheelEvent(tlw, timestamp, local, global, pixelDelta, angleDelta, angleDelta.y(), Qt::Vertical, mods, phase, source));
processWheelEvent(e.data());
// The second event contains null pixel and angle points and the
// Qt 4 compatibility horizontal angle delta.
e.reset(new QWindowSystemInterfacePrivate::WheelEvent(tlw, timestamp, local, global, QPoint(), QPoint(), angleDelta.x(), Qt::Horizontal, mods, phase, source));
processWheelEvent(e.data());
}
void processWheelEvent(QWindowSystemInterfacePrivate::WheelEvent *e)
{
#ifndef QT_NO_WHEELEVENT
QWindow *window = e->window.data();
QPointF globalPoint = e->globalPos;
QPointF localPoint = e->localPos;
if (e->nullWindow()) {
window = QGuiApplication::topLevelAt(globalPoint.toPoint());
if (window) {
QPointF delta = globalPoint - globalPoint.toPoint();
localPoint = window->mapFromGlobal(globalPoint.toPoint()) + delta;
}
}
if (!window)
return;
// Cut off in Krita...
//
// QGuiApplicationPrivate::lastCursorPosition = globalPoint;
// modifier_buttons = e->modifiers;
//if (window->d_func()->blockedByModalWindow) {
if (QGuiApplication::modalWindow() &&
QGuiApplication::modalWindow() != window) {
// a modal window is blocking this window, don't allow wheel events through
return;
}
#if QT_VERSION >= 0x050500
QWheelEvent ev(localPoint, globalPoint, e->pixelDelta, e->angleDelta, e->qt4Delta, e->qt4Orientation, QGuiApplication::mouseButtons(), e->modifiers, e->phase, e->source);
#else
QWheelEvent ev(localPoint, globalPoint, e->pixelDelta, e->angleDelta, e->qt4Delta, e->qt4Orientation, QGuiApplication::mouseButtons(), e->modifiers, e->phase);
#endif
ev.setTimestamp(e->timestamp);
QGuiApplication::sendEvent(window, &ev);
#endif /* ifndef QT_NO_WHEELEVENT */
}
diff --git a/libs/ui/kis_change_file_layer_command.h b/libs/ui/kis_change_file_layer_command.h
new file mode 100644
index 0000000000..823c03190d
--- /dev/null
+++ b/libs/ui/kis_change_file_layer_command.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2017 Wolthera van Hövell tot Westerflier<griffinvalley@mail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifndef KIS_CHANGE_FILE_LAYER_COMMAND_H
+#define KIS_CHANGE_FILE_LAYER_COMMAND_H
+#include <kundo2command.h>
+#include "kis_types.h"
+#include "kis_file_layer.h"
+class KisChangeFileLayerCmd : public KUndo2Command
+{
+
+public:
+ KisChangeFileLayerCmd(KisFileLayerSP fileLayer,
+ const QString &oldPath,
+ const QString &oldFileName,
+ const KisFileLayer::ScalingMethod &oldMethod,
+ const QString &newPath,
+ const QString &newFileName,
+ const KisFileLayer::ScalingMethod &newMethod)
+ : KUndo2Command(kundo2_i18n("Change File Layer")) {
+ m_node = fileLayer;
+
+ m_oldPath = oldPath;
+ m_newPath = newPath;
+ m_oldFileName = oldFileName;
+ m_newFileName = newFileName;
+ m_oldMethod = oldMethod;
+ m_newMethod = newMethod;
+ }
+public:
+ void redo() override {
+ // setFileName() automatically issues a setDirty call
+ m_node->setScalingMethod(m_newMethod);
+ m_node->setFileName(m_newPath, m_newFileName);
+ }
+
+ void undo() override {
+ // setFileName() automatically issues a setDirty call
+ m_node->setScalingMethod(m_oldMethod);
+ m_node->setFileName(m_oldPath, m_oldFileName);
+ }
+private:
+ KisFileLayerSP m_node;
+
+ QString m_oldPath;
+ QString m_newPath;
+ QString m_oldFileName;
+ QString m_newFileName;
+ KisFileLayer::ScalingMethod m_oldMethod;
+ KisFileLayer::ScalingMethod m_newMethod;
+};
+#endif // KIS_CHANGE_FILE_LAYER_COMMAND_H
diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc
index 5427599ad5..3231fd3cd4 100644
--- a/libs/ui/kis_config.cc
+++ b/libs/ui/kis_config.cc
@@ -1,1923 +1,1943 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_config.h"
#include <limits.h>
#include <QtGlobal>
#include <QApplication>
#include <QDesktopWidget>
#include <QMutex>
#include <QFont>
#include <QThread>
#include <QStringList>
#include <QSettings>
#include <QStandardPaths>
#include <kconfig.h>
#include <KisDocument.h>
#include <KoColor.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KoColorProfile.h>
#include <kis_debug.h>
#include <kis_types.h>
#include "kis_canvas_resource_provider.h"
#include "kis_config_notifier.h"
#include "kis_snap_config.h"
#include <config-ocio.h>
#include <kis_color_manager.h>
KisConfig::KisConfig()
: m_cfg( KSharedConfig::openConfig()->group(""))
{
}
KisConfig::~KisConfig()
{
if (qApp->thread() != QThread::currentThread()) {
//dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Skipping...";
return;
}
m_cfg.sync();
}
bool KisConfig::disableTouchOnCanvas(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false));
}
void KisConfig::setDisableTouchOnCanvas(bool value) const
{
m_cfg.writeEntry("disableTouchOnCanvas", value);
}
bool KisConfig::useProjections(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("useProjections", true));
}
void KisConfig::setUseProjections(bool useProj) const
{
m_cfg.writeEntry("useProjections", useProj);
}
bool KisConfig::undoEnabled(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true));
}
void KisConfig::setUndoEnabled(bool undo) const
{
m_cfg.writeEntry("undoEnabled", undo);
}
int KisConfig::undoStackLimit(bool defaultValue) const
{
return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30));
}
void KisConfig::setUndoStackLimit(int limit) const
{
m_cfg.writeEntry("undoStackLimit", limit);
}
bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false));
}
void KisConfig::setCumulativeUndoRedo(bool value)
{
m_cfg.writeEntry("useCumulativeUndoRedo", value);
}
qreal KisConfig::stackT1(bool defaultValue) const
{
return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5));
}
void KisConfig::setStackT1(int T1)
{
m_cfg.writeEntry("stackT1", T1);
}
qreal KisConfig::stackT2(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1));
}
void KisConfig::setStackT2(int T2)
{
m_cfg.writeEntry("stackT2", T2);
}
int KisConfig::stackN(bool defaultValue) const
{
return (defaultValue ? 5 : m_cfg.readEntry("stackN",5));
}
void KisConfig::setStackN(int N)
{
m_cfg.writeEntry("stackN", N);
}
qint32 KisConfig::defImageWidth(bool defaultValue) const
{
return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600));
}
qint32 KisConfig::defImageHeight(bool defaultValue) const
{
return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200));
}
qreal KisConfig::defImageResolution(bool defaultValue) const
{
return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0;
}
QString KisConfig::defColorModel(bool defaultValue) const
{
return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id()
: m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id()));
}
void KisConfig::defColorModel(const QString & model) const
{
m_cfg.writeEntry("colorModelDef", model);
}
QString KisConfig::defaultColorDepth(bool defaultValue) const
{
return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id()
: m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id()));
}
void KisConfig::setDefaultColorDepth(const QString & depth) const
{
m_cfg.writeEntry("colorDepthDef", depth);
}
QString KisConfig::defColorProfile(bool defaultValue) const
{
return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() :
m_cfg.readEntry("colorProfileDef",
KoColorSpaceRegistry::instance()->rgb8()->profile()->name()));
}
void KisConfig::defColorProfile(const QString & profile) const
{
m_cfg.writeEntry("colorProfileDef", profile);
}
void KisConfig::defImageWidth(qint32 width) const
{
m_cfg.writeEntry("imageWidthDef", width);
}
void KisConfig::defImageHeight(qint32 height) const
{
m_cfg.writeEntry("imageHeightDef", height);
}
void KisConfig::defImageResolution(qreal res) const
{
m_cfg.writeEntry("imageResolutionDef", res*72.0);
}
void cleanOldCursorStyleKeys(KConfigGroup &cfg)
{
if (cfg.hasKey("newCursorStyle") &&
cfg.hasKey("newOutlineStyle")) {
cfg.deleteEntry("cursorStyleDef");
}
}
CursorStyle KisConfig::newCursorStyle(bool defaultValue) const
{
if (defaultValue) {
return CURSOR_STYLE_NO_CURSOR;
}
int style = m_cfg.readEntry("newCursorStyle", int(-1));
if (style < 0) {
// old style format
style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE));
switch (style) {
case OLD_CURSOR_STYLE_TOOLICON:
style = CURSOR_STYLE_TOOLICON;
break;
case OLD_CURSOR_STYLE_CROSSHAIR:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS:
style = CURSOR_STYLE_CROSSHAIR;
break;
case OLD_CURSOR_STYLE_POINTER:
style = CURSOR_STYLE_POINTER;
break;
case OLD_CURSOR_STYLE_OUTLINE:
case OLD_CURSOR_STYLE_NO_CURSOR:
style = CURSOR_STYLE_NO_CURSOR;
break;
case OLD_CURSOR_STYLE_SMALL_ROUND:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT:
style = CURSOR_STYLE_SMALL_ROUND;
break;
case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED:
style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED;
break;
case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED:
style = CURSOR_STYLE_TRIANGLE_LEFTHANDED;
break;
default:
style = -1;
}
}
cleanOldCursorStyleKeys(m_cfg);
// compatibility with future versions
if (style < 0 || style >= N_CURSOR_STYLE_SIZE) {
style = CURSOR_STYLE_NO_CURSOR;
}
return (CursorStyle) style;
}
void KisConfig::setNewCursorStyle(CursorStyle style)
{
m_cfg.writeEntry("newCursorStyle", (int)style);
}
QColor KisConfig::getCursorMainColor(bool defaultValue) const
{
QColor col;
col.setRgbF(0.501961, 1.0, 0.501961);
return (defaultValue ? col : m_cfg.readEntry("cursorMaincColor", col));
}
void KisConfig::setCursorMainColor(const QColor &v) const
{
m_cfg.writeEntry("cursorMaincColor", v);
}
OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const
{
if (defaultValue) {
return OUTLINE_FULL;
}
int style = m_cfg.readEntry("newOutlineStyle", int(-1));
if (style < 0) {
// old style format
style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE));
switch (style) {
case OLD_CURSOR_STYLE_TOOLICON:
case OLD_CURSOR_STYLE_CROSSHAIR:
case OLD_CURSOR_STYLE_POINTER:
case OLD_CURSOR_STYLE_NO_CURSOR:
case OLD_CURSOR_STYLE_SMALL_ROUND:
case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED:
case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED:
style = OUTLINE_NONE;
break;
case OLD_CURSOR_STYLE_OUTLINE:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED:
style = OUTLINE_FULL;
break;
default:
style = -1;
}
}
cleanOldCursorStyleKeys(m_cfg);
// compatibility with future versions
if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) {
style = OUTLINE_FULL;
}
return (OutlineStyle) style;
}
void KisConfig::setNewOutlineStyle(OutlineStyle style)
{
m_cfg.writeEntry("newOutlineStyle", (int)style);
}
QRect KisConfig::colorPreviewRect() const
{
return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect();
}
void KisConfig::setColorPreviewRect(const QRect &rect)
{
m_cfg.writeEntry("colorPreviewRect", QVariant(rect));
}
bool KisConfig::useDirtyPresets(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",false));
}
void KisConfig::setUseDirtyPresets(bool value)
{
m_cfg.writeEntry("useDirtyPresets",value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::useEraserBrushSize(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize",false));
}
void KisConfig::setUseEraserBrushSize(bool value)
{
m_cfg.writeEntry("useEraserBrushSize",value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::useEraserBrushOpacity(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false));
}
void KisConfig::setUseEraserBrushOpacity(bool value)
{
m_cfg.writeEntry("useEraserBrushOpacity",value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
QColor KisConfig::getMDIBackgroundColor(bool defaultValue) const
{
QColor col(77, 77, 77);
return (defaultValue ? col : m_cfg.readEntry("mdiBackgroundColor", col));
}
void KisConfig::setMDIBackgroundColor(const QColor &v) const
{
m_cfg.writeEntry("mdiBackgroundColor", v);
}
QString KisConfig::getMDIBackgroundImage(bool defaultValue) const
{
return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", ""));
}
void KisConfig::setMDIBackgroundImage(const QString &filename) const
{
m_cfg.writeEntry("mdiBackgroundImage", filename);
}
QString KisConfig::monitorProfile(int screen) const
{
// Note: keep this in sync with the default profile for the RGB colorspaces!
QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc");
//dbgKrita << "KisConfig::monitorProfile()" << profile;
return profile;
}
QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const
{
return (defaultValue ? defaultMonitor
: m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor));
}
void KisConfig::setMonitorForScreen(int screen, const QString& monitor)
{
m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor);
}
void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const
{
m_cfg.writeEntry("monitorProfile/OverrideX11", override);
m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile);
}
const KoColorProfile *KisConfig::getScreenProfile(int screen)
{
if (screen < 0) return 0;
KisConfig cfg;
QString monitorId;
if (KisColorManager::instance()->devices().size() > screen) {
monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]);
}
//dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId;
if (monitorId.isEmpty()) {
return 0;
}
QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId);
//dbgKrita << "\tgetScreenProfile()" << bytes.size();
if (bytes.length() > 0) {
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes);
//dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name();
return profile;
}
else {
//dbgKrita << "\tCould not get a system monitor profile";
return 0;
}
}
const KoColorProfile *KisConfig::displayProfile(int screen) const
{
if (screen < 0) return 0;
// if the user plays with the settings, they can override the display profile, in which case
// we don't want the system setting.
bool override = useSystemMonitorProfile();
//dbgKrita << "KisConfig::displayProfile(). Override X11:" << override;
const KoColorProfile *profile = 0;
if (override) {
//dbgKrita << "\tGoing to get the screen profile";
profile = KisConfig::getScreenProfile(screen);
}
// if it fails. check the configuration
if (!profile || !profile->isSuitableForDisplay()) {
//dbgKrita << "\tGoing to get the monitor profile";
QString monitorProfileName = monitorProfile(screen);
//dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName;
if (!monitorProfileName.isEmpty()) {
profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName);
}
if (profile) {
//dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay();
}
else {
//dbgKrita << "\t\tstill no profile";
}
}
// if we still don't have a profile, or the profile isn't suitable for display,
// we need to get a last-resort profile. the built-in sRGB is a good choice then.
if (!profile || !profile->isSuitableForDisplay()) {
//dbgKrita << "\tnothing worked, going to get sRGB built-in";
profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in");
}
if (profile) {
//dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name();
}
else {
//dbgKrita << "\tCouldn't get a display profile at all";
}
return profile;
}
QString KisConfig::workingColorSpace(bool defaultValue) const
{
return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA"));
}
void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const
{
m_cfg.writeEntry("workingColorSpace", workingColorSpace);
}
QString KisConfig::printerColorSpace(bool /*defaultValue*/) const
{
//TODO currently only rgb8 is supported
//return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA"));
return QString("RGBA");
}
void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const
{
m_cfg.writeEntry("printerColorSpace", printerColorSpace);
}
QString KisConfig::printerProfile(bool defaultValue) const
{
return (defaultValue ? "" : m_cfg.readEntry("printerProfile", ""));
}
void KisConfig::setPrinterProfile(const QString & printerProfile) const
{
m_cfg.writeEntry("printerProfile", printerProfile);
}
bool KisConfig::useBlackPointCompensation(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true));
}
void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const
{
m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation);
}
bool KisConfig::allowLCMSOptimization(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true));
}
void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization)
{
m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization);
}
bool KisConfig::showRulers(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showrulers", false));
}
void KisConfig::setShowRulers(bool rulers) const
{
m_cfg.writeEntry("showrulers", rulers);
}
bool KisConfig::forceShowSaveMessages(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("forceShowSaveMessages", false));
}
void KisConfig::setForceShowSaveMessages(bool value) const
{
m_cfg.writeEntry("forceShowSaveMessages", value);
}
bool KisConfig::forceShowAutosaveMessages(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("forceShowAutosaveMessages", false));
}
void KisConfig::setForceShowAutosaveMessages(bool value) const
{
m_cfg.writeEntry("forceShowAutosaveMessages", value);
}
bool KisConfig::rulersTrackMouse(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true));
}
void KisConfig::setRulersTrackMouse(bool value) const
{
m_cfg.writeEntry("rulersTrackMouse", value);
}
qint32 KisConfig::pasteBehaviour(bool defaultValue) const
{
return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2));
}
void KisConfig::setPasteBehaviour(qint32 renderIntent) const
{
m_cfg.writeEntry("pasteBehaviour", renderIntent);
}
qint32 KisConfig::monitorRenderIntent(bool defaultValue) const
{
qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL);
if (intent > 3) intent = 3;
if (intent < 0) intent = 0;
return (defaultValue ? INTENT_PERCEPTUAL : intent);
}
void KisConfig::setRenderIntent(qint32 renderIntent) const
{
if (renderIntent > 3) renderIntent = 3;
if (renderIntent < 0) renderIntent = 0;
m_cfg.writeEntry("renderIntent", renderIntent);
}
bool KisConfig::useOpenGL(bool defaultValue) const
{
if (defaultValue) {
return true;
}
//dbgKrita << "use opengl" << m_cfg.readEntry("useOpenGL", true) << "success" << m_cfg.readEntry("canvasState", "OPENGL_SUCCESS");
QString cs = canvasState();
#ifdef Q_OS_WIN
return (m_cfg.readEntry("useOpenGLWindows", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL"));
#else
return (m_cfg.readEntry("useOpenGL", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL"));
#endif
}
void KisConfig::setUseOpenGL(bool useOpenGL) const
{
#ifdef Q_OS_WIN
m_cfg.writeEntry("useOpenGLWindows", useOpenGL);
#else
m_cfg.writeEntry("useOpenGL", useOpenGL);
#endif
}
int KisConfig::openGLFilteringMode(bool defaultValue) const
{
return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3));
}
void KisConfig::setOpenGLFilteringMode(int filteringMode)
{
m_cfg.writeEntry("OpenGLFilterMode", filteringMode);
}
bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true));
}
void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer)
{
m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer);
}
int KisConfig::openGLTextureSize(bool defaultValue) const
{
return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256));
}
bool KisConfig::disableVSync(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("disableVSync", true));
}
void KisConfig::setDisableVSync(bool disableVSync)
{
m_cfg.writeEntry("disableVSync", disableVSync);
}
bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false));
}
bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false));
}
int KisConfig::numMipmapLevels(bool defaultValue) const
{
return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4));
}
int KisConfig::textureOverlapBorder() const
{
return 1 << qMax(0, numMipmapLevels());
}
quint32 KisConfig::getGridMainStyle(bool defaultValue) const
{
int v = m_cfg.readEntry("gridmainstyle", 0);
v = qBound(0, v, 2);
return (defaultValue ? 0 : v);
}
void KisConfig::setGridMainStyle(quint32 v) const
{
m_cfg.writeEntry("gridmainstyle", v);
}
quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const
{
quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1);
if (v > 2) v = 2;
return (defaultValue ? 1 : v);
}
void KisConfig::setGridSubdivisionStyle(quint32 v) const
{
m_cfg.writeEntry("gridsubdivisionstyle", v);
}
QColor KisConfig::getGridMainColor(bool defaultValue) const
{
QColor col(99, 99, 99);
return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col));
}
void KisConfig::setGridMainColor(const QColor & v) const
{
m_cfg.writeEntry("gridmaincolor", v);
}
QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const
{
QColor col(150, 150, 150);
return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col));
}
void KisConfig::setGridSubdivisionColor(const QColor & v) const
{
m_cfg.writeEntry("gridsubdivisioncolor", v);
}
QColor KisConfig::getPixelGridColor(bool defaultValue) const
{
QColor col(255, 255, 255);
return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col));
}
void KisConfig::setPixelGridColor(const QColor & v) const
{
m_cfg.writeEntry("pixelGridColor", v);
}
qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const
{
qreal border = 8.0f;
return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border));
}
void KisConfig::setPixelGridDrawingThreshold(qreal v) const
{
m_cfg.writeEntry("pixelGridDrawingThreshold", v);
}
bool KisConfig::pixelGridEnabled(bool defaultValue) const
{
bool enabled = true;
return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled));
}
void KisConfig::enablePixelGrid(bool v) const
{
m_cfg.writeEntry("pixelGridEnabled", v);
}
quint32 KisConfig::guidesLineStyle(bool defaultValue) const
{
int v = m_cfg.readEntry("guidesLineStyle", 0);
v = qBound(0, v, 2);
return (defaultValue ? 0 : v);
}
void KisConfig::setGuidesLineStyle(quint32 v) const
{
m_cfg.writeEntry("guidesLineStyle", v);
}
QColor KisConfig::guidesColor(bool defaultValue) const
{
QColor col(99, 99, 99);
return (defaultValue ? col : m_cfg.readEntry("guidesColor", col));
}
void KisConfig::setGuidesColor(const QColor & v) const
{
m_cfg.writeEntry("guidesColor", v);
}
void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const
{
KisSnapConfig defaultConfig(false);
if (defaultValue) {
*config = defaultConfig;
return;
}
config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal()));
config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node()));
config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension()));
config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection()));
config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox()));
config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds()));
config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter()));
}
void KisConfig::saveSnapConfig(const KisSnapConfig &config)
{
m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal());
m_cfg.writeEntry("globalSnapNode", config.node());
m_cfg.writeEntry("globalSnapExtension", config.extension());
m_cfg.writeEntry("globalSnapIntersection", config.intersection());
m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox());
m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds());
m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter());
}
qint32 KisConfig::checkSize(bool defaultValue) const
{
return (defaultValue ? 32 : m_cfg.readEntry("checksize", 32));
}
void KisConfig::setCheckSize(qint32 checksize) const
{
m_cfg.writeEntry("checksize", checksize);
}
bool KisConfig::scrollCheckers(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false));
}
void KisConfig::setScrollingCheckers(bool sc) const
{
m_cfg.writeEntry("scrollingcheckers", sc);
}
QColor KisConfig::canvasBorderColor(bool defaultValue) const
{
QColor color(QColor(128,128,128));
return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color));
}
void KisConfig::setCanvasBorderColor(const QColor& color) const
{
m_cfg.writeEntry("canvasBorderColor", color);
}
bool KisConfig::hideScrollbars(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false));
}
void KisConfig::setHideScrollbars(bool value) const
{
m_cfg.writeEntry("hideScrollbars", value);
}
QColor KisConfig::checkersColor1(bool defaultValue) const
{
QColor col(220, 220, 220);
return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col));
}
void KisConfig::setCheckersColor1(const QColor & v) const
{
m_cfg.writeEntry("checkerscolor", v);
}
QColor KisConfig::checkersColor2(bool defaultValue) const
{
return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white)));
}
void KisConfig::setCheckersColor2(const QColor & v) const
{
m_cfg.writeEntry("checkerscolor2", v);
}
bool KisConfig::antialiasCurves(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true));
}
void KisConfig::setAntialiasCurves(bool v) const
{
m_cfg.writeEntry("antialiascurves", v);
}
QColor KisConfig::selectionOverlayMaskColor(bool defaultValue) const
{
QColor def(255, 0, 0, 220);
return (defaultValue ? def : m_cfg.readEntry("selectionOverlayMaskColor", def));
}
void KisConfig::setSelectionOverlayMaskColor(const QColor &color)
{
m_cfg.writeEntry("selectionOverlayMaskColor", color);
}
bool KisConfig::antialiasSelectionOutline(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false));
}
void KisConfig::setAntialiasSelectionOutline(bool v) const
{
m_cfg.writeEntry("AntialiasSelectionOutline", v);
}
bool KisConfig::showRootLayer(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false));
}
void KisConfig::setShowRootLayer(bool showRootLayer) const
{
m_cfg.writeEntry("ShowRootLayer", showRootLayer);
}
bool KisConfig::showGlobalSelection(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false));
}
void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const
{
m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection);
}
bool KisConfig::showOutlineWhilePainting(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true));
}
void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const
{
m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting);
}
bool KisConfig::hideSplashScreen(bool defaultValue) const
{
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
return (defaultValue ? true : cfg.readEntry("HideSplashAfterStartup", true));
}
void KisConfig::setHideSplashScreen(bool hideSplashScreen) const
{
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
cfg.writeEntry("HideSplashAfterStartup", hideSplashScreen);
}
qreal KisConfig::outlineSizeMinimum(bool defaultValue) const
{
return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0));
}
void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const
{
m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum);
}
qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const
{
return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0));
}
void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const
{
m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum);
}
int KisConfig::autoSaveInterval(bool defaultValue) const
{
return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60));
}
void KisConfig::setAutoSaveInterval(int seconds) const
{
return m_cfg.writeEntry("AutoSaveInterval", seconds);
}
bool KisConfig::backupFile(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true));
}
void KisConfig::setBackupFile(bool backupFile) const
{
m_cfg.writeEntry("CreateBackupFile", backupFile);
}
bool KisConfig::showFilterGallery(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false));
}
void KisConfig::setShowFilterGallery(bool showFilterGallery) const
{
m_cfg.writeEntry("showFilterGallery", showFilterGallery);
}
bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true));
}
void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const
{
m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery);
}
QString KisConfig::canvasState(bool defaultValue) const
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString());
}
void KisConfig::setCanvasState(const QString& state) const
{
static QStringList acceptableStates;
if (acceptableStates.isEmpty()) {
acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED";
}
if (acceptableStates.contains(state)) {
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("canvasState", state);
}
}
bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false));
}
void KisConfig::setToolOptionsPopupDetached(bool detached) const
{
m_cfg.writeEntry("ToolOptionsPopupDetached", detached);
}
bool KisConfig::paintopPopupDetached(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false));
}
void KisConfig::setPaintopPopupDetached(bool detached) const
{
m_cfg.writeEntry("PaintopPopupDetached", detached);
}
QString KisConfig::pressureTabletCurve(bool defaultValue) const
{
return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;"));
}
void KisConfig::setPressureTabletCurve(const QString& curveString) const
{
m_cfg.writeEntry("tabletPressureCurve", curveString);
}
bool KisConfig::useWin8PointerInput(bool defaultValue) const
{
#ifdef Q_OS_WIN
return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false));
#else
Q_UNUSED(defaultValue);
return false;
#endif
}
void KisConfig::setUseWin8PointerInput(bool value) const
{
#ifdef Q_OS_WIN
// Special handling: Only set value if changed
// I don't want it to be set if the user hasn't touched it
if (useWin8PointerInput() != value) {
m_cfg.writeEntry("useWin8PointerInput", value);
}
#else
Q_UNUSED(value)
#endif
}
qreal KisConfig::vastScrolling(bool defaultValue) const
{
return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9));
}
void KisConfig::setVastScrolling(const qreal factor) const
{
m_cfg.writeEntry("vastScrolling", factor);
}
int KisConfig::presetChooserViewMode(bool defaultValue) const
{
return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0));
}
void KisConfig::setPresetChooserViewMode(const int mode) const
{
m_cfg.writeEntry("presetChooserViewMode", mode);
}
int KisConfig::presetIconSize(bool defaultValue) const
{
return (defaultValue ? 30 : m_cfg.readEntry("presetIconSize", 30));
}
void KisConfig::setPresetIconSize(const int value) const
{
m_cfg.writeEntry("presetIconSize", value);
}
bool KisConfig::firstRun(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("firstRun", true));
}
void KisConfig::setFirstRun(const bool first) const
{
m_cfg.writeEntry("firstRun", first);
}
int KisConfig::horizontalSplitLines(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1));
}
void KisConfig::setHorizontalSplitLines(const int numberLines) const
{
m_cfg.writeEntry("horizontalSplitLines", numberLines);
}
int KisConfig::verticalSplitLines(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1));
}
void KisConfig::setVerticalSplitLines(const int numberLines) const
{
m_cfg.writeEntry("verticalSplitLines", numberLines);
}
bool KisConfig::clicklessSpacePan(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true));
}
void KisConfig::setClicklessSpacePan(const bool toggle) const
{
m_cfg.writeEntry("clicklessSpacePan", toggle);
}
bool KisConfig::hideDockersFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true));
}
void KisConfig::setHideDockersFullscreen(const bool value) const
{
m_cfg.writeEntry("hideDockersFullScreen", value);
}
bool KisConfig::showDockerTitleBars(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showDockerTitleBars", true));
}
void KisConfig::setShowDockerTitleBars(const bool value) const
{
m_cfg.writeEntry("showDockerTitleBars", value);
}
bool KisConfig::showDockers(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showDockers", true));
}
void KisConfig::setShowDockers(const bool value) const
{
m_cfg.writeEntry("showDockers", value);
}
bool KisConfig::showStatusBar(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true));
}
void KisConfig::setShowStatusBar(const bool value) const
{
m_cfg.writeEntry("showStatusBar", value);
}
bool KisConfig::hideMenuFullscreen(bool defaultValue) const
{
return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true));
}
void KisConfig::setHideMenuFullscreen(const bool value) const
{
m_cfg.writeEntry("hideMenuFullScreen", value);
}
bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true));
}
void KisConfig::setHideScrollbarsFullscreen(const bool value) const
{
m_cfg.writeEntry("hideScrollbarsFullScreen", value);
}
bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const
{
return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true));
}
void KisConfig::setHideStatusbarFullscreen(const bool value) const
{
m_cfg.writeEntry("hideStatusbarFullScreen", value);
}
bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true));
}
void KisConfig::setHideTitlebarFullscreen(const bool value) const
{
m_cfg.writeEntry("hideTitleBarFullscreen", value);
}
bool KisConfig::hideToolbarFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true));
}
void KisConfig::setHideToolbarFullscreen(const bool value) const
{
m_cfg.writeEntry("hideToolbarFullscreen", value);
}
bool KisConfig::fullscreenMode(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true));
}
void KisConfig::setFullscreenMode(const bool value) const
{
m_cfg.writeEntry("fullscreenMode", value);
}
QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const
{
return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QStringList()));
}
void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const
{
m_cfg.writeEntry("favoriteCompositeOps", compositeOps);
}
QString KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString()));
}
void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const
{
QString exportConfig = properties->toXML();
m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig);
}
QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString()));
}
void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const
{
QString importConfig = properties->toXML();
m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig);
}
bool KisConfig::useOcio(bool defaultValue) const
{
#ifdef HAVE_OCIO
return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false));
#else
Q_UNUSED(defaultValue);
return false;
#endif
}
void KisConfig::setUseOcio(bool useOCIO) const
{
m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO);
}
int KisConfig::favoritePresets(bool defaultValue) const
{
return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10));
}
void KisConfig::setFavoritePresets(const int value)
{
m_cfg.writeEntry("numFavoritePresets", value);
}
bool KisConfig::levelOfDetailEnabled(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false));
}
void KisConfig::setLevelOfDetailEnabled(bool value)
{
m_cfg.writeEntry("levelOfDetailEnabled", value);
}
KisConfig::OcioColorManagementMode
KisConfig::ocioColorManagementMode(bool defaultValue) const
{
return (OcioColorManagementMode)(defaultValue ? INTERNAL
: m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL));
}
void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const
{
m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode);
}
QString KisConfig::ocioConfigurationPath(bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString()));
}
void KisConfig::setOcioConfigurationPath(const QString &path) const
{
m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", path);
}
QString KisConfig::ocioLutPath(bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString()));
}
void KisConfig::setOcioLutPath(const QString &path) const
{
m_cfg.writeEntry("Krita/Ocio/OcioLutPath", path);
}
int KisConfig::ocioLutEdgeSize(bool defaultValue) const
{
return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64));
}
void KisConfig::setOcioLutEdgeSize(int value)
{
m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value);
}
bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false));
}
void KisConfig::setOcioLockColorVisualRepresentation(bool value)
{
m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value);
}
QString KisConfig::defaultPalette(bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", QString()));
}
void KisConfig::setDefaultPalette(const QString& name) const
{
m_cfg.writeEntry("defaultPalette", name);
}
QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const
{
QString def = "flow";
if (sliderNumber == 1) {
def = "opacity";
}
if (sliderNumber == 2) {
def = "size";
}
return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def));
}
void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider)
{
m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider);
}
bool KisConfig::sliderLabels(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true));
}
void KisConfig::setSliderLabels(bool enabled)
{
m_cfg.writeEntry("sliderLabels", enabled);
}
QString KisConfig::currentInputProfile(bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString()));
}
void KisConfig::setCurrentInputProfile(const QString& name)
{
m_cfg.writeEntry("currentInputProfile", name);
}
bool KisConfig::useSystemMonitorProfile(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false));
}
void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const
{
m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile);
}
bool KisConfig::presetStripVisible(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true));
}
void KisConfig::setPresetStripVisible(bool visible)
{
m_cfg.writeEntry("presetStripVisible", visible);
}
bool KisConfig::scratchpadVisible(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true));
}
void KisConfig::setScratchpadVisible(bool visible)
{
m_cfg.writeEntry("scratchpadVisible", visible);
}
bool KisConfig::showSingleChannelAsColor(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false));
}
void KisConfig::setShowSingleChannelAsColor(bool asColor)
{
m_cfg.writeEntry("showSingleChannelAsColor", asColor);
}
bool KisConfig::hidePopups(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("hidePopups", false));
}
void KisConfig::setHidePopups(bool hidepopups)
{
m_cfg.writeEntry("hidePopups", hidepopups);
}
int KisConfig::numDefaultLayers(bool defaultValue) const
{
return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2));
}
void KisConfig::setNumDefaultLayers(int num)
{
m_cfg.writeEntry("NumberOfLayersForNewImage", num);
}
quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const
{
return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8));
}
void KisConfig::setDefaultBackgroundOpacity(quint8 value)
{
m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value);
}
QColor KisConfig::defaultBackgroundColor(bool defaultValue) const
{
return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white)));
}
void KisConfig::setDefaultBackgroundColor(QColor value)
{
m_cfg.writeEntry("BackgroundColorForNewImage", value);
}
KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const
{
return (KisConfig::BackgroundStyle)(defaultValue ? LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)LAYER));
}
void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value)
{
m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value);
}
int KisConfig::lineSmoothingType(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1));
}
void KisConfig::setLineSmoothingType(int value)
{
m_cfg.writeEntry("LineSmoothingType", value);
}
qreal KisConfig::lineSmoothingDistance(bool defaultValue) const
{
return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0));
}
void KisConfig::setLineSmoothingDistance(qreal value)
{
m_cfg.writeEntry("LineSmoothingDistance", value);
}
qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const
{
return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15));
}
void KisConfig::setLineSmoothingTailAggressiveness(qreal value)
{
m_cfg.writeEntry("LineSmoothingTailAggressiveness", value);
}
bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false));
}
void KisConfig::setLineSmoothingSmoothPressure(bool value)
{
m_cfg.writeEntry("LineSmoothingSmoothPressure", value);
}
bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true));
}
void KisConfig::setLineSmoothingScalableDistance(bool value)
{
m_cfg.writeEntry("LineSmoothingScalableDistance", value);
}
qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const
{
return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0));
}
void KisConfig::setLineSmoothingDelayDistance(qreal value)
{
m_cfg.writeEntry("LineSmoothingDelayDistance", value);
}
bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true));
}
void KisConfig::setLineSmoothingUseDelayDistance(bool value)
{
m_cfg.writeEntry("LineSmoothingUseDelayDistance", value);
}
bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true));
}
void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value)
{
m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value);
}
bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true));
}
void KisConfig::setLineSmoothingStabilizeSensors(bool value)
{
m_cfg.writeEntry("LineSmoothingStabilizeSensors", value);
}
int KisConfig::paletteDockerPaletteViewSectionSize(bool defaultValue) const
{
return (defaultValue ? 12 : m_cfg.readEntry("paletteDockerPaletteViewSectionSize", 12));
}
void KisConfig::setPaletteDockerPaletteViewSectionSize(int value) const
{
m_cfg.writeEntry("paletteDockerPaletteViewSectionSize", value);
}
int KisConfig::tabletEventsDelay(bool defaultValue) const
{
return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10));
}
void KisConfig::setTabletEventsDelay(int value)
{
m_cfg.writeEntry("tabletEventsDelay", value);
}
+bool KisConfig::trackTabletEventLatency(bool defaultValue) const
+{
+ return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false));
+}
+
+void KisConfig::setTrackTabletEventLatency(bool value)
+{
+ m_cfg.writeEntry("trackTabletEventLatency", value);
+}
+
bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false));
}
void KisConfig::setTestingAcceptCompressedTabletEvents(bool value)
{
m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value);
}
bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false));
}
bool KisConfig::testingCompressBrushEvents(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false));
}
void KisConfig::setTestingCompressBrushEvents(bool value)
{
m_cfg.writeEntry("testingCompressBrushEvents", value);
}
int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const
{
return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0));
}
bool KisConfig::showCanvasMessages(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true));
}
void KisConfig::setShowCanvasMessages(bool show)
{
m_cfg.writeEntry("showOnCanvasMessages", show);
}
bool KisConfig::compressKra(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false));
}
void KisConfig::setCompressKra(bool compress)
{
m_cfg.writeEntry("compressLayersInKra", compress);
}
bool KisConfig::toolOptionsInDocker(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true));
}
void KisConfig::setToolOptionsInDocker(bool inDocker)
{
m_cfg.writeEntry("ToolOptionsInDocker", inDocker);
}
const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const
{
const KoColorSpace *cs = 0;
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) {
KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance();
QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA");
QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8");
QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)");
if (profile == "default") {
// qDebug() << "Falling back to default color profile.";
profile = "sRGB built-in - (lcms internal)";
}
cs = csr->colorSpace(modelID, depthID, profile);
}
return cs;
}
void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs)
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
cfg.writeEntry("useCustomColorSpace", bool(cs));
if(cs) {
cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id());
cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id());
cfg.writeEntry("customColorSpaceProfile", cs->profile()->name());
}
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::enableOpenGLFramerateLogging(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("enableOpenGLFramerateLogging", false));
}
void KisConfig::setEnableOpenGLFramerateLogging(bool value) const
{
m_cfg.writeEntry("enableOpenGLFramerateLogging", value);
}
+bool KisConfig::enableBrushSpeedLogging(bool defaultValue) const
+{
+ return (defaultValue ? false : m_cfg.readEntry("enableBrushSpeedLogging", false));
+}
+
+void KisConfig::setEnableBrushSpeedLogging(bool value) const
+{
+ m_cfg.writeEntry("enableBrushSpeedLogging", value);
+}
+
void KisConfig::setEnableAmdVectorizationWorkaround(bool value)
{
m_cfg.writeEntry("amdDisableVectorWorkaround", value);
}
bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false));
}
void KisConfig::setAnimationDropFrames(bool value)
{
bool oldValue = animationDropFrames();
if (value == oldValue) return;
m_cfg.writeEntry("animationDropFrames", value);
KisConfigNotifier::instance()->notifyDropFramesModeChanged();
}
bool KisConfig::animationDropFrames(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true));
}
int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const
{
return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30));
}
void KisConfig::setScrubbingUpdatesDelay(int value)
{
m_cfg.writeEntry("scrubbingUpdatesDelay", value);
}
int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const
{
return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1));
}
void KisConfig::setScrubbingAudioUpdatesDelay(int value)
{
m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value);
}
int KisConfig::audioOffsetTolerance(bool defaultValue) const
{
return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1));
}
void KisConfig::setAudioOffsetTolerance(int value)
{
m_cfg.writeEntry("audioOffsetTolerance", value);
}
bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false);
}
void KisConfig::setSwitchSelectionCtrlAlt(bool value)
{
m_cfg.writeEntry("switchSelectionCtrlAlt", value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false);
}
void KisConfig::setConvertToImageColorspaceOnImport(bool value)
{
m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value);
}
int KisConfig::stabilizerSampleSize(bool defaultValue) const
{
#ifdef Q_OS_WIN
const int defaultSampleSize = 50;
#else
const int defaultSampleSize = 15;
#endif
return defaultValue ?
defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize);
}
void KisConfig::setStabilizerSampleSize(int value)
{
m_cfg.writeEntry("stabilizerSampleSize", value);
}
bool KisConfig::stabilizerDelayedPaint(bool defaultValue) const
{
const bool defaultEnabled = true;
return defaultValue ?
defaultEnabled : m_cfg.readEntry("stabilizerDelayedPaint", defaultEnabled);
}
void KisConfig::setStabilizerDelayedPaint(bool value)
{
m_cfg.writeEntry("stabilizerDelayedPaint", value);
}
QString KisConfig::customFFMpegPath(bool defaultValue) const
{
return defaultValue ? QString() : m_cfg.readEntry("ffmpegExecutablePath", QString());
}
void KisConfig::setCustomFFMpegPath(const QString &value) const
{
m_cfg.writeEntry("ffmpegExecutablePath", value);
}
bool KisConfig::showBrushHud(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("showBrushHud", false);
}
void KisConfig::setShowBrushHud(bool value)
{
m_cfg.writeEntry("showBrushHud", value);
}
QString KisConfig::brushHudSetting(bool defaultValue) const
{
QString defaultDoc = "<!DOCTYPE hud_properties>\n<hud_properties>\n <version value=\"1\" type=\"value\"/>\n <paintbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"angle\" type=\"value\"/>\n </properties_list>\n </paintbrush>\n <colorsmudge>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"smudge_mode\" type=\"value\"/>\n <item_3 value=\"smudge_length\" type=\"value\"/>\n <item_4 value=\"smudge_color_rate\" type=\"value\"/>\n </properties_list>\n </colorsmudge>\n <sketchbrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"size\" type=\"value\"/>\n </properties_list>\n </sketchbrush>\n <hairybrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </hairybrush>\n <experimentbrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"shape_windingfill\" type=\"value\"/>\n </properties_list>\n </experimentbrush>\n <spraybrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"spray_particlecount\" type=\"value\"/>\n <item_3 value=\"spray_density\" type=\"value\"/>\n </properties_list>\n </spraybrush>\n <hatchingbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"hatching_angle\" type=\"value\"/>\n <item_3 value=\"hatching_thickness\" type=\"value\"/>\n <item_4 value=\"hatching_separation\" type=\"value\"/>\n </properties_list>\n </hatchingbrush>\n <gridbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"grid_divisionlevel\" type=\"value\"/>\n </properties_list>\n </gridbrush>\n <curvebrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"curve_historysize\" type=\"value\"/>\n <item_2 value=\"curve_linewidth\" type=\"value\"/>\n <item_3 value=\"curve_lineopacity\" type=\"value\"/>\n <item_4 value=\"curve_connectionline\" type=\"value\"/>\n </properties_list>\n </curvebrush>\n <dynabrush>\n <properties_list type=\"array\">\n <item_0 value=\"dyna_diameter\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"dyna_mass\" type=\"value\"/>\n <item_3 value=\"dyna_drag\" type=\"value\"/>\n </properties_list>\n </dynabrush>\n <particlebrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"particle_particles\" type=\"value\"/>\n <item_2 value=\"particle_opecityweight\" type=\"value\"/>\n <item_3 value=\"particle_iterations\" type=\"value\"/>\n </properties_list>\n </particlebrush>\n <duplicate>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"clone_healing\" type=\"value\"/>\n <item_3 value=\"clone_movesource\" type=\"value\"/>\n </properties_list>\n </duplicate>\n <deformbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"deform_amount\" type=\"value\"/>\n <item_3 value=\"deform_mode\" type=\"value\"/>\n </properties_list>\n </deformbrush>\n <tangentnormal>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </tangentnormal>\n <filter>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </filter>\n <chalkbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </chalkbrush>\n <roundmarker>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"size\" type=\"value\"/>\n </properties_list>\n </roundmarker>\n</hud_properties>\n";
return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc);
}
void KisConfig::setBrushHudSetting(const QString &value) const
{
m_cfg.writeEntry("brushHudSettings", value);
}
bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const
{
return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true);
}
void KisConfig::setCalculateAnimationCacheInBackground(bool value)
{
m_cfg.writeEntry("calculateAnimationCacheInBackground", value);
}
#include <QDomDocument>
#include <QDomElement>
void KisConfig::writeKoColor(const QString& name, const KoColor& color) const
{
QDomDocument doc = QDomDocument(name);
QDomElement el = doc.createElement(name);
doc.appendChild(el);
color.toXML(doc, el);
m_cfg.writeEntry(name, doc.toString());
}
//ported from kispropertiesconfig.
KoColor KisConfig::readKoColor(const QString& name, const KoColor& color) const
{
QDomDocument doc;
if (!m_cfg.readEntry(name).isNull()) {
doc.setContent(m_cfg.readEntry(name));
QDomElement e = doc.documentElement().firstChild().toElement();
return KoColor::fromXML(e, Integer16BitsColorDepthID.id());
} else {
QString blackColor = "<!DOCTYPE Color>\n<Color>\n <RGB r=\"0\" space=\"sRGB-elle-V2-srgbtrc.icc\" b=\"0\" g=\"0\"/>\n</Color>\n";
doc.setContent(blackColor);
QDomElement e = doc.documentElement().firstChild().toElement();
return KoColor::fromXML(e, Integer16BitsColorDepthID.id());
}
return color;
}
diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h
index 831e6d079e..557c2119f3 100644
--- a/libs/ui/kis_config.h
+++ b/libs/ui/kis_config.h
@@ -1,568 +1,574 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CONFIG_H_
#define KIS_CONFIG_H_
#include <QString>
#include <QStringList>
#include <QList>
#include <QColor>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include "kis_global.h"
#include "kis_properties_configuration.h"
#include "kritaui_export.h"
class KoColorProfile;
class KoColorSpace;
class KisSnapConfig;
class KRITAUI_EXPORT KisConfig
{
public:
KisConfig();
~KisConfig();
bool disableTouchOnCanvas(bool defaultValue = false) const;
void setDisableTouchOnCanvas(bool value) const;
bool useProjections(bool defaultValue = false) const;
void setUseProjections(bool useProj) const;
bool undoEnabled(bool defaultValue = false) const;
void setUndoEnabled(bool undo) const;
int undoStackLimit(bool defaultValue = false) const;
void setUndoStackLimit(int limit) const;
bool useCumulativeUndoRedo(bool defaultValue = false) const;
void setCumulativeUndoRedo(bool value);
double stackT1(bool defaultValue = false) const;
void setStackT1(int T1);
double stackT2(bool defaultValue = false) const;
void setStackT2(int T2);
int stackN(bool defaultValue = false) const;
void setStackN(int N);
qint32 defImageWidth(bool defaultValue = false) const;
void defImageWidth(qint32 width) const;
qint32 defImageHeight(bool defaultValue = false) const;
void defImageHeight(qint32 height) const;
qreal defImageResolution(bool defaultValue = false) const;
void defImageResolution(qreal res) const;
/**
* @return the id of the default color model used for creating new images.
*/
QString defColorModel(bool defaultValue = false) const;
/**
* set the id of the default color model used for creating new images.
*/
void defColorModel(const QString & model) const;
/**
* @return the id of the default color depth used for creating new images.
*/
QString defaultColorDepth(bool defaultValue = false) const;
/**
* set the id of the default color depth used for creating new images.
*/
void setDefaultColorDepth(const QString & depth) const;
/**
* @return the id of the default color profile used for creating new images.
*/
QString defColorProfile(bool defaultValue = false) const;
/**
* set the id of the default color profile used for creating new images.
*/
void defColorProfile(const QString & depth) const;
CursorStyle newCursorStyle(bool defaultValue = false) const;
void setNewCursorStyle(CursorStyle style);
QColor getCursorMainColor(bool defaultValue = false) const;
void setCursorMainColor(const QColor& v) const;
OutlineStyle newOutlineStyle(bool defaultValue = false) const;
void setNewOutlineStyle(OutlineStyle style);
QRect colorPreviewRect() const;
void setColorPreviewRect(const QRect &rect);
/// get the profile the user has selected for the given screen
QString monitorProfile(int screen) const;
void setMonitorProfile(int screen, const QString & monitorProfile, bool override) const;
QString monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue = true) const;
void setMonitorForScreen(int screen, const QString& monitor);
/// Get the actual profile to be used for the given screen, which is
/// either the screen profile set by the color management system or
/// the custom monitor profile set by the user, depending on the configuration
const KoColorProfile *displayProfile(int screen) const;
QString workingColorSpace(bool defaultValue = false) const;
void setWorkingColorSpace(const QString & workingColorSpace) const;
QString importProfile(bool defaultValue = false) const;
void setImportProfile(const QString & importProfile) const;
QString printerColorSpace(bool defaultValue = false) const;
void setPrinterColorSpace(const QString & printerColorSpace) const;
QString printerProfile(bool defaultValue = false) const;
void setPrinterProfile(const QString & printerProfile) const;
bool useBlackPointCompensation(bool defaultValue = false) const;
void setUseBlackPointCompensation(bool useBlackPointCompensation) const;
bool allowLCMSOptimization(bool defaultValue = false) const;
void setAllowLCMSOptimization(bool allowLCMSOptimization);
void writeKoColor(const QString& name, const KoColor& color) const;
KoColor readKoColor(const QString& name, const KoColor& color = KoColor()) const;
bool showRulers(bool defaultValue = false) const;
void setShowRulers(bool rulers) const;
bool forceShowSaveMessages(bool defaultValue = true) const;
void setForceShowSaveMessages(bool value) const;
bool forceShowAutosaveMessages(bool defaultValue = true) const;
void setForceShowAutosaveMessages(bool ShowAutosaveMessages) const;
bool rulersTrackMouse(bool defaultValue = false) const;
void setRulersTrackMouse(bool value) const;
qint32 pasteBehaviour(bool defaultValue = false) const;
void setPasteBehaviour(qint32 behaviour) const;
qint32 monitorRenderIntent(bool defaultValue = false) const;
void setRenderIntent(qint32 monitorRenderIntent) const;
bool useOpenGL(bool defaultValue = false) const;
void setUseOpenGL(bool useOpenGL) const;
int openGLFilteringMode(bool defaultValue = false) const;
void setOpenGLFilteringMode(int filteringMode);
bool useOpenGLTextureBuffer(bool defaultValue = false) const;
void setUseOpenGLTextureBuffer(bool useBuffer);
bool disableVSync(bool defaultValue = false) const;
void setDisableVSync(bool disableVSync);
bool showAdvancedOpenGLSettings(bool defaultValue = false) const;
bool forceOpenGLFenceWorkaround(bool defaultValue = false) const;
int numMipmapLevels(bool defaultValue = false) const;
int openGLTextureSize(bool defaultValue = false) const;
int textureOverlapBorder() const;
quint32 getGridMainStyle(bool defaultValue = false) const;
void setGridMainStyle(quint32 v) const;
quint32 getGridSubdivisionStyle(bool defaultValue = false) const;
void setGridSubdivisionStyle(quint32 v) const;
QColor getGridMainColor(bool defaultValue = false) const;
void setGridMainColor(const QColor & v) const;
QColor getGridSubdivisionColor(bool defaultValue = false) const;
void setGridSubdivisionColor(const QColor & v) const;
QColor getPixelGridColor(bool defaultValue = false) const;
void setPixelGridColor(const QColor & v) const;
qreal getPixelGridDrawingThreshold(bool defaultValue = false) const;
void setPixelGridDrawingThreshold(qreal v) const;
bool pixelGridEnabled(bool defaultValue = false) const;
void enablePixelGrid(bool v) const;
quint32 guidesLineStyle(bool defaultValue = false) const;
void setGuidesLineStyle(quint32 v) const;
QColor guidesColor(bool defaultValue = false) const;
void setGuidesColor(const QColor & v) const;
void loadSnapConfig(KisSnapConfig *config, bool defaultValue = false) const;
void saveSnapConfig(const KisSnapConfig &config);
qint32 checkSize(bool defaultValue = false) const;
void setCheckSize(qint32 checkSize) const;
bool scrollCheckers(bool defaultValue = false) const;
void setScrollingCheckers(bool scollCheckers) const;
QColor checkersColor1(bool defaultValue = false) const;
void setCheckersColor1(const QColor & v) const;
QColor checkersColor2(bool defaultValue = false) const;
void setCheckersColor2(const QColor & v) const;
QColor canvasBorderColor(bool defaultValue = false) const;
void setCanvasBorderColor(const QColor &color) const;
bool hideScrollbars(bool defaultValue = false) const;
void setHideScrollbars(bool value) const;
bool antialiasCurves(bool defaultValue = false) const;
void setAntialiasCurves(bool v) const;
QColor selectionOverlayMaskColor(bool defaultValue = false) const;
void setSelectionOverlayMaskColor(const QColor &color);
bool antialiasSelectionOutline(bool defaultValue = false) const;
void setAntialiasSelectionOutline(bool v) const;
bool showRootLayer(bool defaultValue = false) const;
void setShowRootLayer(bool showRootLayer) const;
bool showGlobalSelection(bool defaultValue = false) const;
void setShowGlobalSelection(bool showGlobalSelection) const;
bool showOutlineWhilePainting(bool defaultValue = false) const;
void setShowOutlineWhilePainting(bool showOutlineWhilePainting) const;
bool hideSplashScreen(bool defaultValue = false) const;
void setHideSplashScreen(bool hideSplashScreen) const;
qreal outlineSizeMinimum(bool defaultValue = false) const;
void setOutlineSizeMinimum(qreal outlineSizeMinimum) const;
qreal selectionViewSizeMinimum(bool defaultValue = false) const;
void setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const;
int autoSaveInterval(bool defaultValue = false) const;
void setAutoSaveInterval(int seconds) const;
bool backupFile(bool defaultValue = false) const;
void setBackupFile(bool backupFile) const;
bool showFilterGallery(bool defaultValue = false) const;
void setShowFilterGallery(bool showFilterGallery) const;
bool showFilterGalleryLayerMaskDialog(bool defaultValue = false) const;
void setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const;
// OPENGL_SUCCESS, TRY_OPENGL, OPENGL_NOT_TRIED, OPENGL_FAILED
QString canvasState(bool defaultValue = false) const;
void setCanvasState(const QString& state) const;
bool toolOptionsPopupDetached(bool defaultValue = false) const;
void setToolOptionsPopupDetached(bool detached) const;
bool paintopPopupDetached(bool defaultValue = false) const;
void setPaintopPopupDetached(bool detached) const;
QString pressureTabletCurve(bool defaultValue = false) const;
void setPressureTabletCurve(const QString& curveString) const;
bool useWin8PointerInput(bool defaultValue = false) const;
void setUseWin8PointerInput(bool value) const;
qreal vastScrolling(bool defaultValue = false) const;
void setVastScrolling(const qreal factor) const;
int presetChooserViewMode(bool defaultValue = false) const;
void setPresetChooserViewMode(const int mode) const;
int presetIconSize(bool defaultValue = false) const;
void setPresetIconSize(const int value) const;
bool firstRun(bool defaultValue = false) const;
void setFirstRun(const bool firstRun) const;
bool clicklessSpacePan(bool defaultValue = false) const;
void setClicklessSpacePan(const bool toggle) const;
int horizontalSplitLines(bool defaultValue = false) const;
void setHorizontalSplitLines(const int numberLines) const;
int verticalSplitLines(bool defaultValue = false) const;
void setVerticalSplitLines(const int numberLines) const;
bool hideDockersFullscreen(bool defaultValue = false) const;
void setHideDockersFullscreen(const bool value) const;
bool showDockerTitleBars(bool defaultValue = false) const;
void setShowDockerTitleBars(const bool value) const;
bool showDockers(bool defaultValue = false) const;
void setShowDockers(const bool value) const;
bool showStatusBar(bool defaultValue = false) const;
void setShowStatusBar(const bool value) const;
bool hideMenuFullscreen(bool defaultValue = false) const;
void setHideMenuFullscreen(const bool value) const;
bool hideScrollbarsFullscreen(bool defaultValue = false) const;
void setHideScrollbarsFullscreen(const bool value) const;
bool hideStatusbarFullscreen(bool defaultValue = false) const;
void setHideStatusbarFullscreen(const bool value) const;
bool hideTitlebarFullscreen(bool defaultValue = false) const;
void setHideTitlebarFullscreen(const bool value) const;
bool hideToolbarFullscreen(bool defaultValue = false) const;
void setHideToolbarFullscreen(const bool value) const;
bool fullscreenMode(bool defaultValue = false) const;
void setFullscreenMode(const bool value) const;
QStringList favoriteCompositeOps(bool defaultValue = false) const;
void setFavoriteCompositeOps(const QStringList& compositeOps) const;
QString exportConfiguration(const QString &filterId, bool defaultValue = false) const;
void setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const;
QString importConfiguration(const QString &filterId, bool defaultValue = false) const;
void setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const;
bool useOcio(bool defaultValue = false) const;
void setUseOcio(bool useOCIO) const;
int favoritePresets(bool defaultValue = false) const;
void setFavoritePresets(const int value);
bool levelOfDetailEnabled(bool defaultValue = false) const;
void setLevelOfDetailEnabled(bool value);
enum OcioColorManagementMode {
INTERNAL = 0,
OCIO_CONFIG,
OCIO_ENVIRONMENT
};
OcioColorManagementMode ocioColorManagementMode(bool defaultValue = false) const;
void setOcioColorManagementMode(OcioColorManagementMode mode) const;
QString ocioConfigurationPath(bool defaultValue = false) const;
void setOcioConfigurationPath(const QString &path) const;
QString ocioLutPath(bool defaultValue = false) const;
void setOcioLutPath(const QString &path) const;
int ocioLutEdgeSize(bool defaultValue = false) const;
void setOcioLutEdgeSize(int value);
bool ocioLockColorVisualRepresentation(bool defaultValue = false) const;
void setOcioLockColorVisualRepresentation(bool value);
bool useSystemMonitorProfile(bool defaultValue = false) const;
void setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const;
QString defaultPalette(bool defaultValue = false) const;
void setDefaultPalette(const QString& name) const;
QString toolbarSlider(int sliderNumber, bool defaultValue = false) const;
void setToolbarSlider(int sliderNumber, const QString &slider);
bool sliderLabels(bool defaultValue = false) const;
void setSliderLabels(bool enabled);
QString currentInputProfile(bool defaultValue = false) const;
void setCurrentInputProfile(const QString& name);
bool presetStripVisible(bool defaultValue = false) const;
void setPresetStripVisible(bool visible);
bool scratchpadVisible(bool defaultValue = false) const;
void setScratchpadVisible(bool visible);
bool showSingleChannelAsColor(bool defaultValue = false) const;
void setShowSingleChannelAsColor(bool asColor);
bool hidePopups(bool defaultValue = false) const;
void setHidePopups(bool hidepopups);
int numDefaultLayers(bool defaultValue = false) const;
void setNumDefaultLayers(int num);
quint8 defaultBackgroundOpacity(bool defaultValue = false) const;
void setDefaultBackgroundOpacity(quint8 value);
QColor defaultBackgroundColor(bool defaultValue = false) const;
void setDefaultBackgroundColor(QColor value);
enum BackgroundStyle {
LAYER = 0,
PROJECTION = 1
};
BackgroundStyle defaultBackgroundStyle(bool defaultValue = false) const;
void setDefaultBackgroundStyle(BackgroundStyle value);
int lineSmoothingType(bool defaultValue = false) const;
void setLineSmoothingType(int value);
qreal lineSmoothingDistance(bool defaultValue = false) const;
void setLineSmoothingDistance(qreal value);
qreal lineSmoothingTailAggressiveness(bool defaultValue = false) const;
void setLineSmoothingTailAggressiveness(qreal value);
bool lineSmoothingSmoothPressure(bool defaultValue = false) const;
void setLineSmoothingSmoothPressure(bool value);
bool lineSmoothingScalableDistance(bool defaultValue = false) const;
void setLineSmoothingScalableDistance(bool value);
qreal lineSmoothingDelayDistance(bool defaultValue = false) const;
void setLineSmoothingDelayDistance(qreal value);
bool lineSmoothingUseDelayDistance(bool defaultValue = false) const;
void setLineSmoothingUseDelayDistance(bool value);
bool lineSmoothingFinishStabilizedCurve(bool defaultValue = false) const;
void setLineSmoothingFinishStabilizedCurve(bool value);
bool lineSmoothingStabilizeSensors(bool defaultValue = false) const;
void setLineSmoothingStabilizeSensors(bool value);
int paletteDockerPaletteViewSectionSize(bool defaultValue = false) const;
void setPaletteDockerPaletteViewSectionSize(int value) const;
int tabletEventsDelay(bool defaultValue = false) const;
void setTabletEventsDelay(int value);
+ bool trackTabletEventLatency(bool defaultValue = false) const;
+ void setTrackTabletEventLatency(bool value);
+
bool testingAcceptCompressedTabletEvents(bool defaultValue = false) const;
void setTestingAcceptCompressedTabletEvents(bool value);
bool shouldEatDriverShortcuts(bool defaultValue = false) const;
bool testingCompressBrushEvents(bool defaultValue = false) const;
void setTestingCompressBrushEvents(bool value);
const KoColorSpace* customColorSelectorColorSpace(bool defaultValue = false) const;
void setCustomColorSelectorColorSpace(const KoColorSpace *cs);
bool useDirtyPresets(bool defaultValue = false) const;
void setUseDirtyPresets(bool value);
bool useEraserBrushSize(bool defaultValue = false) const;
void setUseEraserBrushSize(bool value);
bool useEraserBrushOpacity(bool defaultValue = false) const;
void setUseEraserBrushOpacity(bool value);
QColor getMDIBackgroundColor(bool defaultValue = false) const;
void setMDIBackgroundColor(const QColor & v) const;
QString getMDIBackgroundImage(bool defaultValue = false) const;
void setMDIBackgroundImage(const QString & fileName) const;
int workaroundX11SmoothPressureSteps(bool defaultValue = false) const;
bool showCanvasMessages(bool defaultValue = false) const;
void setShowCanvasMessages(bool show);
bool compressKra(bool defaultValue = false) const;
void setCompressKra(bool compress);
bool toolOptionsInDocker(bool defaultValue = false) const;
void setToolOptionsInDocker(bool inDocker);
void setEnableOpenGLFramerateLogging(bool value) const;
bool enableOpenGLFramerateLogging(bool defaultValue = false) const;
+ void setEnableBrushSpeedLogging(bool value) const;
+ bool enableBrushSpeedLogging(bool defaultValue = false) const;
+
void setEnableAmdVectorizationWorkaround(bool value);
bool enableAmdVectorizationWorkaround(bool defaultValue = false) const;
bool animationDropFrames(bool defaultValue = false) const;
void setAnimationDropFrames(bool value);
int scrubbingUpdatesDelay(bool defaultValue = false) const;
void setScrubbingUpdatesDelay(int value);
int scrubbingAudioUpdatesDelay(bool defaultValue = false) const;
void setScrubbingAudioUpdatesDelay(int value);
int audioOffsetTolerance(bool defaultValue = false) const;
void setAudioOffsetTolerance(int value);
bool switchSelectionCtrlAlt(bool defaultValue = false) const;
void setSwitchSelectionCtrlAlt(bool value);
bool convertToImageColorspaceOnImport(bool defaultValue = false) const;
void setConvertToImageColorspaceOnImport(bool value);
int stabilizerSampleSize(bool defaultValue = false) const;
void setStabilizerSampleSize(int value);
bool stabilizerDelayedPaint(bool defaultValue = false) const;
void setStabilizerDelayedPaint(bool value);
QString customFFMpegPath(bool defaultValue = false) const;
void setCustomFFMpegPath(const QString &value) const;
bool showBrushHud(bool defaultValue = false) const;
void setShowBrushHud(bool value);
QString brushHudSetting(bool defaultValue = false) const;
void setBrushHudSetting(const QString &value) const;
bool calculateAnimationCacheInBackground(bool defaultValue = false) const;
void setCalculateAnimationCacheInBackground(bool value);
template<class T>
void writeEntry(const QString& name, const T& value) {
m_cfg.writeEntry(name, value);
}
template<class T>
void writeList(const QString& name, const QList<T>& value) {
m_cfg.writeEntry(name, value);
}
template<class T>
T readEntry(const QString& name, const T& defaultValue=T()) {
return m_cfg.readEntry(name, defaultValue);
}
template<class T>
QList<T> readList(const QString& name, const QList<T>& defaultValue=QList<T>()) {
return m_cfg.readEntry(name, defaultValue);
}
/// get the profile the color managment system has stored for the given screen
static const KoColorProfile* getScreenProfile(int screen);
private:
KisConfig(const KisConfig&);
KisConfig& operator=(const KisConfig&) const;
private:
mutable KConfigGroup m_cfg;
};
#endif // KIS_CONFIG_H_
diff --git a/libs/ui/kis_file_layer.cpp b/libs/ui/kis_file_layer.cpp
index e0420ddac6..b812b2a094 100644
--- a/libs/ui/kis_file_layer.cpp
+++ b/libs/ui/kis_file_layer.cpp
@@ -1,239 +1,245 @@
/*
* Copyright (c) 2013 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_file_layer.h"
#include <QFile>
#include <QFileInfo>
#include "kis_transform_worker.h"
#include "kis_filter_strategy.h"
#include "kis_node_progress_proxy.h"
#include "kis_node_visitor.h"
#include "kis_image.h"
#include "kis_types.h"
#include "commands_new/kis_node_move_command2.h"
#include "kis_default_bounds.h"
#include "kis_layer_properties_icons.h"
#include <KisPart.h>
#include <KisDocument.h>
#include <QDir>
KisFileLayer::KisFileLayer(KisImageWSP image, const QString &name, quint8 opacity)
: KisExternalLayer(image, name, opacity)
{
connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int)));
}
KisFileLayer::KisFileLayer(KisImageWSP image, const QString &basePath, const QString &filename, ScalingMethod scaleToImageResolution, const QString &name, quint8 opacity)
: KisExternalLayer(image, name, opacity)
, m_basePath(basePath)
, m_filename(filename)
, m_scalingMethod(scaleToImageResolution)
{
/**
* Set default paint device for a layer. It will be used is case
* the file does not exist anymore. Or course, this can happen only
* in the failing execution path.
*/
m_paintDevice = new KisPaintDevice(image->colorSpace());
connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int)));
QFileInfo fi(path());
if (fi.exists()) {
m_loader.setPath(path());
m_loader.reloadImage();
}
}
KisFileLayer::~KisFileLayer()
{
}
KisFileLayer::KisFileLayer(const KisFileLayer &rhs)
: KisExternalLayer(rhs)
{
m_basePath = rhs.m_basePath;
m_filename = rhs.m_filename;
KIS_SAFE_ASSERT_RECOVER_NOOP(QFile::exists(path()));
m_scalingMethod = rhs.m_scalingMethod;
m_paintDevice = new KisPaintDevice(*rhs.m_paintDevice);
connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int)));
m_loader.setPath(path());
}
QIcon KisFileLayer::icon() const
{
return KisIconUtils::loadIcon("fileLayer");
}
void KisFileLayer::resetCache()
{
m_loader.reloadImage();
}
const KoColorSpace *KisFileLayer::colorSpace() const
{
return m_paintDevice->colorSpace();
}
KisPaintDeviceSP KisFileLayer::original() const
{
return m_paintDevice;
}
KisPaintDeviceSP KisFileLayer::paintDevice() const
{
return 0;
}
void KisFileLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
{
KisBaseNode::setSectionModelProperties(properties);
Q_FOREACH (const KisBaseNode::Property &property, properties) {
if (property.id== KisLayerPropertiesIcons::openFileLayerFile.id()) {
if (property.state.toBool() == false) {
openFile();
}
}
}
}
KisBaseNode::PropertyList KisFileLayer::sectionModelProperties() const
{
KisBaseNode::PropertyList l = KisLayer::sectionModelProperties();
l << KisBaseNode::Property(KoID("sourcefile", i18n("File")), m_filename);
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::openFileLayerFile, true);
return l;
}
void KisFileLayer::setFileName(const QString &basePath, const QString &filename)
{
m_basePath = basePath;
m_filename = filename;
m_loader.setPath(path());
m_loader.reloadImage();
}
QString KisFileLayer::fileName() const
{
return m_filename;
}
QString KisFileLayer::path() const
{
if (m_basePath.isEmpty()) {
return m_filename;
}
else {
return QDir(m_basePath).filePath(QDir::cleanPath(m_filename));;
}
}
void KisFileLayer::openFile() const
{
bool fileAlreadyOpen = false;
Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) {
if (doc->url().toLocalFile()==path()){
fileAlreadyOpen = true;
}
}
if (!fileAlreadyOpen) {
KisPart::instance()->openExistingFile(QUrl::fromLocalFile(QFileInfo(path()).absoluteFilePath()));
}
}
KisFileLayer::ScalingMethod KisFileLayer::scalingMethod() const
{
return m_scalingMethod;
}
+void KisFileLayer::setScalingMethod(ScalingMethod method)
+{
+ m_scalingMethod = method;
+}
+
void KisFileLayer::slotLoadingFinished(KisPaintDeviceSP projection, int xRes, int yRes)
{
qint32 oldX = x();
qint32 oldY = y();
+ const QRect oldLayerExtent = m_paintDevice->extent();
m_paintDevice->makeCloneFrom(projection, projection->extent());
m_paintDevice->setDefaultBounds(new KisDefaultBounds(image()));
QSize size = projection->exactBounds().size();
if (m_scalingMethod == ToImagePPI && (image()->xRes() != xRes
|| image()->yRes() != yRes)) {
qreal xscale = image()->xRes() / xRes;
qreal yscale = image()->yRes() / yRes;
KisTransformWorker worker(m_paintDevice, xscale, yscale, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, KisFilterStrategyRegistry::instance()->get("Bicubic"));
worker.run();
}
else if (m_scalingMethod == ToImageSize) {
QSize sz = size;
sz.scale(image()->size(), Qt::KeepAspectRatio);
qreal xscale = (qreal)sz.width() / (qreal)size.width();
qreal yscale = (qreal)sz.height() / (qreal)size.height();
KisTransformWorker worker(m_paintDevice, xscale, yscale, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, KisFilterStrategyRegistry::instance()->get("Bicubic"));
worker.run();
}
m_paintDevice->setX(oldX);
m_paintDevice->setY(oldY);
- setDirty();
+ setDirty(m_paintDevice->extent() | oldLayerExtent);
}
KisNodeSP KisFileLayer::clone() const
{
qDebug() << "Cloning KisFileLayer" << m_filename;
return KisNodeSP(new KisFileLayer(*this));
}
bool KisFileLayer::allowAsChild(KisNodeSP node) const
{
return node->inherits("KisMask");
}
bool KisFileLayer::accept(KisNodeVisitor& visitor)
{
return visitor.visit(this);
}
void KisFileLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
KUndo2Command* KisFileLayer::crop(const QRect & rect)
{
QPoint oldPos(x(), y());
QPoint newPos = oldPos - rect.topLeft();
return new KisNodeMoveCommand2(this, oldPos, newPos);
}
KUndo2Command* KisFileLayer::transform(const QTransform &/*transform*/)
{
warnKrita << "WARNING: File Layer does not support transformations!" << name();
return 0;
}
diff --git a/libs/ui/kis_file_layer.h b/libs/ui/kis_file_layer.h
index ff707c5122..82faf5c656 100644
--- a/libs/ui/kis_file_layer.h
+++ b/libs/ui/kis_file_layer.h
@@ -1,86 +1,87 @@
/*
* Copyright (c) 2013 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_FILE_LAYER_H
#define KIS_FILE_LAYER_H
#include "kritaui_export.h"
#include "kis_external_layer_iface.h"
#include "kis_safe_document_loader.h"
/**
* @brief The KisFileLayer class loads a particular file as a layer into the layer stack.
*/
class KRITAUI_EXPORT KisFileLayer : public KisExternalLayer
{
Q_OBJECT
public:
enum ScalingMethod {
None,
ToImageSize,
ToImagePPI
};
KisFileLayer(KisImageWSP image, const QString &name, quint8 opacity);
KisFileLayer(KisImageWSP image, const QString& basePath, const QString &filename, ScalingMethod scalingMethod, const QString &name, quint8 opacity);
~KisFileLayer() override;
KisFileLayer(const KisFileLayer& rhs);
QIcon icon() const override;
void resetCache() override;
const KoColorSpace *colorSpace() const override;
KisPaintDeviceSP original() const override;
KisPaintDeviceSP paintDevice() const override;
void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override;
KisBaseNode::PropertyList sectionModelProperties() const override;
void setFileName(const QString &basePath, const QString &filename);
QString fileName() const;
QString path() const;
void openFile() const;
ScalingMethod scalingMethod() const;
+ void setScalingMethod(ScalingMethod method);
KisNodeSP clone() const override;
bool allowAsChild(KisNodeSP) const override;
bool accept(KisNodeVisitor&) override;
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override;
KUndo2Command* crop(const QRect & rect) override;
KUndo2Command* transform(const QTransform &transform) override;
public Q_SLOTS:
void slotLoadingFinished(KisPaintDeviceSP projection, int xRes, int yRes);
private:
QString m_basePath;
QString m_filename;
ScalingMethod m_scalingMethod;
KisPaintDeviceSP m_paintDevice;
KisSafeDocumentLoader m_loader;
};
#endif // KIS_FILE_LAYER_H
diff --git a/libs/ui/kis_fps_decoration.cpp b/libs/ui/kis_fps_decoration.cpp
index e5a79baf39..8e6f465a68 100644
--- a/libs/ui/kis_fps_decoration.cpp
+++ b/libs/ui/kis_fps_decoration.cpp
@@ -1,64 +1,99 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_fps_decoration.h"
#include <QPainter>
#include "kis_canvas2.h"
#include "kis_coordinates_converter.h"
#include "opengl/kis_opengl_canvas_debugger.h"
+#include <KisStrokeSpeedMonitor.h>
const QString KisFpsDecoration::idTag = "fps_decoration";
KisFpsDecoration::KisFpsDecoration(QPointer<KisView> view)
: KisCanvasDecoration(idTag, view)
{
setVisible(true);
}
KisFpsDecoration::~KisFpsDecoration()
{
}
void KisFpsDecoration::drawDecoration(QPainter& gc, const QRectF& /*updateRect*/, const KisCoordinatesConverter */*converter*/, KisCanvas2* /*canvas*/)
{
#ifdef Q_OS_OSX
- QPixmap pixmap(256, 64);
+ QPixmap pixmap(320, 128);
pixmap.fill(Qt::transparent);
{
QPainter painter(&pixmap);
draw(painter);
}
gc.drawPixmap(0, 0, pixmap);
#else
draw(gc);
#endif
}
+
+
void KisFpsDecoration::draw(QPainter& gc)
{
- const qreal value = KisOpenglCanvasDebugger::instance()->accumulatedFps();
- const QString text = QString("FPS: %1").arg(QString::number(value, 'f', 1));
+ QStringList lines;
+
+ if (KisOpenglCanvasDebugger::instance()->showFpsOnCanvas()) {
+ const qreal value = KisOpenglCanvasDebugger::instance()->accumulatedFps();
+ lines << QString("Canvas FPS: %1").arg(QString::number(value, 'f', 1));
+ }
+
+ KisStrokeSpeedMonitor *monitor = KisStrokeSpeedMonitor::instance();
+
+ if (monitor->haveStrokeSpeedMeasurement()) {
+ lines << QString("Last cursor/brush speed (px/ms): %1/%2%3")
+ .arg(monitor->lastCursorSpeed(), 0, 'f', 1)
+ .arg(monitor->lastRenderingSpeed(), 0, 'f', 1)
+ .arg(monitor->lastStrokeSaturated() ? " (!)" : "");
+ lines << QString("Last brush framerate: %1 fps")
+ .arg(monitor->lastFps(), 0, 'f', 1);
+
+ lines << QString("Average cursor/brush speed (px/ms): %1/%2")
+ .arg(monitor->avgCursorSpeed(), 0, 'f', 1)
+ .arg(monitor->avgRenderingSpeed(), 0, 'f', 1);
+ lines << QString("Average brush framerate: %1 fps")
+ .arg(monitor->avgFps(), 0, 'f', 1);
+ }
+
+
+ QPoint startPoint(20,30);
+ const int lineSpacing = QFontMetrics(gc.font()).lineSpacing();
+
gc.save();
- gc.setPen(QPen(Qt::white));
- gc.drawText(QPoint(21, 31), text);
- gc.setPen(QPen(Qt::black));
- gc.drawText(QPoint(20, 30), text);
+
+ Q_FOREACH (const QString &line, lines) {
+ gc.setPen(QPen(Qt::white));
+ gc.drawText(startPoint + QPoint(1,1), line);
+ gc.setPen(QPen(Qt::black));
+ gc.drawText(startPoint, line);
+
+ startPoint += QPoint(0, lineSpacing);
+ }
+
gc.restore();
}
diff --git a/libs/ui/kis_histogram_view.cc b/libs/ui/kis_histogram_view.cc
index 9a9b478b5c..8cd3b0c712 100644
--- a/libs/ui/kis_histogram_view.cc
+++ b/libs/ui/kis_histogram_view.cc
@@ -1,239 +1,239 @@
/*
* Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
*
* 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_histogram_view.h"
#include <math.h>
#include <QPainter>
#include <QPixmap>
#include <QLabel>
#include <QComboBox>
#include <QPushButton>
#include <QScrollBar>
#include <QMouseEvent>
#include <QFrame>
#include <QBrush>
#include <QLinearGradient>
#include <QImage>
#include <QPaintEvent>
#include <kis_debug.h>
#include "KoChannelInfo.h"
#include "KoBasicHistogramProducers.h"
#include "KoColorSpace.h"
#include "kis_global.h"
#include "kis_layer.h"
#include <kis_signal_compressor.h>
#include "kis_paint_device.h"
-KisHistogramView::KisHistogramView(QWidget *parent, const char *name, Qt::WFlags f)
+KisHistogramView::KisHistogramView(QWidget *parent, const char *name, Qt::WindowFlags f)
: QLabel(parent, f),
m_currentDev(nullptr), m_currentProducer(nullptr),
m_smoothHistogram(false),m_histogram_type(LINEAR)
{
setObjectName(name);
}
KisHistogramView::~KisHistogramView()
{
}
KoHistogramProducer *KisHistogramView::currentProducer()
{
return m_currentProducer;
}
void KisHistogramView::startUpdateCanvasProjection()
{
updateHistogramCalculation();
}
void KisHistogramView::setChannels(QList<KoChannelInfo*> & channels)
{
m_channels = channels;
updateHistogramCalculation();
}
void KisHistogramView::setProducer(KoHistogramProducer* producer)
{
m_currentProducer = producer;
m_channels = m_currentProducer->channels();
if( !m_histogram.isNull() ){
m_histogram->setProducer( m_currentProducer );
}
updateHistogramCalculation();
}
void KisHistogramView::setPaintDevice(KisPaintDeviceSP dev, KoHistogramProducer* producer, const QRect &bounds)
{
m_currentProducer = producer;
m_channels = m_currentProducer->channels();
m_currentDev = dev;
m_currentBounds = bounds;
m_histogram = new KisHistogram(m_currentDev, m_currentBounds, m_currentProducer, m_histogram_type);
updateHistogramCalculation();
}
void KisHistogramView::setView(double from, double size)
{
double m_from = from;
double m_width = size;
if (m_from + m_width > 1.0)
m_from = 1.0 - m_width;
m_histogram->producer()->setView(m_from, m_width);
updateHistogramCalculation();
}
bool KisHistogramView::hasColor()
{
return m_color;
}
void KisHistogramView::setColor(bool set)
{
if (set != m_color) {
m_color = set;
}
update();
}
void KisHistogramView::setHistogramType(enumHistogramType type)
{
m_histogram_type = type;
updateHistogramCalculation();
}
void KisHistogramView::updateHistogramCalculation()
{
if (!m_currentProducer || m_currentDev.isNull() || m_histogram.isNull() ) { // Something's very wrong: not initialized
return;
}
m_histogram->updateHistogram();
update();
}
void KisHistogramView::mousePressEvent(QMouseEvent * e)
{
if (e->button() == Qt::RightButton)
emit rightClicked(e->globalPos());
else
QLabel::mousePressEvent(e);
}
void KisHistogramView::setSmoothHistogram(bool smoothHistogram)
{
m_smoothHistogram = smoothHistogram;
}
void KisHistogramView::paintEvent(QPaintEvent *event)
{
QLabel::paintEvent(event);
if( this->height() > 0 && this->width()>0 && !m_histogram.isNull() ){
qint32 height = this->height();
int selFrom, selTo; // from - to in bins
qint32 bins = m_histogram->producer()->numberOfBins();
QPainter painter(this);
painter.setPen(this->palette().light().color());
const int NGRID = 4;
for(int i=0; i<=NGRID; ++i){
painter.drawLine(this->width()*i/NGRID,0.,this->width()*i/NGRID,this->height());
painter.drawLine(0.,this->height()*i/NGRID,this->width(),this->height()*i/NGRID);
}
// Draw the box of the selection, if any
if (m_histogram->hasSelection()) {
double width = m_histogram->selectionTo() - m_histogram->selectionFrom();
double factor = static_cast<double>(bins) / m_histogram->producer()->viewWidth();
selFrom = static_cast<int>(m_histogram->selectionFrom() * factor);
selTo = selFrom + static_cast<int>(width * factor);
painter.drawRect(selFrom, 0, selTo - selFrom, height);
} else {
// We don't want the drawing to think we're in a selected area
selFrom = -1;
selTo = 2;
}
float highest = 0;
if(m_channels.count()==0){
m_channels=m_currentProducer->channels();
}
int nChannels = m_channels.size();
// First we iterate once, so that we have the overall maximum. This is a bit inefficient,
// but not too much since the histogram caches the calculations
for (int chan = 0; chan < m_channels.size(); chan++) {
if( m_channels.at(chan)->channelType() != KoChannelInfo::ALPHA ){
m_histogram->setChannel(chan);
if ((float)m_histogram->calculations().getHighest() > highest)
highest = (float)m_histogram->calculations().getHighest();
}
}
highest = (m_histogram_type==LINEAR)? highest: std::log2(highest);
painter.setWindow(QRect(-1,0,bins+1,highest));
painter.setCompositionMode(QPainter::CompositionMode_Plus);
for (int chan = 0; chan < nChannels; chan++) {
if( m_channels.at(chan)->channelType() != KoChannelInfo::ALPHA ){
auto color = m_channels.at(chan)->color();
auto fill_color = color;
fill_color.setAlphaF(.25);
painter.setBrush(fill_color);
auto pen = QPen(color);
pen.setWidth(0);
painter.setPen(pen);
if (m_smoothHistogram){
QPainterPath path;
m_histogram->setChannel(chan);
path.moveTo(QPointF(-1,highest));
for (qint32 i = 0; i < bins; ++i) {
float v = (m_histogram_type==LINEAR)? highest-m_histogram->getValue(i): highest-std::log2(m_histogram->getValue(i));
path.lineTo(QPointF(i,v));
}
path.lineTo(QPointF(bins+1,highest));
path.closeSubpath();
painter.drawPath(path);
}
else {
pen.setWidth(1);
painter.setPen(pen);
m_histogram->setChannel(chan);
for (qint32 i = 0; i < bins; ++i) {
float v = (m_histogram_type==LINEAR)? highest-m_histogram->getValue(i): highest-std::log2(m_histogram->getValue(i));
painter.drawLine(QPointF(i,highest),QPointF(i,v));
}
}
}
}
}
}
diff --git a/libs/ui/kis_histogram_view.h b/libs/ui/kis_histogram_view.h
index a710c9a85d..1c57c15c10 100644
--- a/libs/ui/kis_histogram_view.h
+++ b/libs/ui/kis_histogram_view.h
@@ -1,106 +1,106 @@
/*
* Copyright (c) 2005 Bart Coppens <kde@bartcoppens.be>
*
* 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_HISTOGRAM_VIEW_
#define _KIS_HISTOGRAM_VIEW_
#include <QLabel>
#include <QPixmap>
#include <QVector>
#include <QStringList>
#include <QMouseEvent>
#include <QSharedPointer>
#include "kis_types.h"
#include "kis_histogram.h"
#include <kritaui_export.h>
class KoChannelInfo;
/**
* This class displays a histogram. It has a list of channels it can
* select. The easy way is to display channelStrings() to the user,
* and then use a setActiveChannel with the integer the same as the
* one the selected string in that stringlist has. If the selected one
* is a producer, the histogram will automatically display all its
* channels, and color them if that is possible.
*
* You can also set the channels manually, just don't forget that the
* displayed channels all need to belong to the same producer! If you
* set them manually, don't forget to set the (non)usage of color as
* well.
*
* You can either set this to use a specific layer, or use a specific
* histogram. With the latter, some functionality will disappear, like
* listProducers(). Setting a histogram will discard info on the
* layer, and setting a layer will discard info on the histogram.
*
**/
class KRITAUI_EXPORT KisHistogramView : public QLabel
{
Q_OBJECT
public:
- KisHistogramView(QWidget *parent = 0, const char *name = 0, Qt::WFlags f = 0);
+ KisHistogramView(QWidget *parent = 0, const char *name = 0, Qt::WindowFlags f = 0);
~KisHistogramView() override;
void setPaintDevice(KisPaintDeviceSP dev, KoHistogramProducer *producer, const QRect &bounds);
void setView(double from, double size);
KoHistogramProducer *currentProducer();
bool hasColor();
void setColor(bool set);
void setProducer(KoHistogramProducer* producer);
void setChannels(QList<KoChannelInfo*> & channels);
void paintEvent(QPaintEvent* event) override;
virtual void updateHistogramCalculation();
void setSmoothHistogram(bool smoothHistogram);
public Q_SLOTS:
virtual void setHistogramType(enumHistogramType type);
virtual void startUpdateCanvasProjection();
Q_SIGNALS:
void rightClicked(const QPoint& pos);
protected:
void mousePressEvent(QMouseEvent * e) override;
private:
void setChannels(void);
void addProducerChannels(KoHistogramProducer *producer);
KisHistogramSP m_histogram;
KisPaintDeviceSP m_currentDev;
QRect m_currentBounds;
KoHistogramProducer *m_currentProducer;
QList<KoChannelInfo *> m_channels;
bool m_color;
bool m_smoothHistogram;
enumHistogramType m_histogram_type;
};
#endif // _KIS_HISTOGRAM_VIEW_
diff --git a/libs/ui/kis_image_manager.cc b/libs/ui/kis_image_manager.cc
index 2f46118b2b..7ae7c670a0 100644
--- a/libs/ui/kis_image_manager.cc
+++ b/libs/ui/kis_image_manager.cc
@@ -1,211 +1,211 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2006
*
* 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_manager.h"
#include <QString>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QAction>
#include <QUrl>
#include <QColorDialog>
#include <klocalizedstring.h>
#include <KoColor.h>
#include <KoFileDialog.h>
#include <kis_types.h>
#include <kis_image.h>
#include <kis_icon.h>
#include <KisImportExportManager.h>
#include "kis_import_catcher.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "dialogs/kis_dlg_image_properties.h"
#include "commands/kis_image_commands.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "kis_layer_utils.h"
#include "kis_signal_compressor_with_param.h"
KisImageManager::KisImageManager(KisViewManager * view)
: m_view(view)
{
}
void KisImageManager::setView(QPointer<KisView>imageView)
{
Q_UNUSED(imageView);
}
void KisImageManager::setup(KisActionManager *actionManager)
{
KisAction *action = actionManager->createAction("import_layer_from_file");
connect(action, SIGNAL(triggered()), this, SLOT(slotImportLayerFromFile()));
action = actionManager->createAction("image_properties");
connect(action, SIGNAL(triggered()), this, SLOT(slotImageProperties()));
action = actionManager->createAction("import_layer_as_paint_layer");
connect(action, SIGNAL(triggered()), this, SLOT(slotImportLayerFromFile()));
action = actionManager->createAction("import_layer_as_transparency_mask");
connect(action, SIGNAL(triggered()), this, SLOT(slotImportLayerAsTransparencyMask()));
action = actionManager->createAction("import_layer_as_filter_mask");
connect(action, SIGNAL(triggered()), this, SLOT(slotImportLayerAsFilterMask()));
action = actionManager->createAction("import_layer_as_selection_mask");
connect(action, SIGNAL(triggered()), this, SLOT(slotImportLayerAsSelectionMask()));
action = actionManager->createAction("image_color");
connect(action, SIGNAL(triggered()), this, SLOT(slotImageColor()));
}
void KisImageManager::slotImportLayerFromFile()
{
importImage(QUrl(), "KisPaintLayer");
}
void KisImageManager::slotImportLayerAsTransparencyMask()
{
importImage(QUrl(), "KisTransparencyMask");
}
void KisImageManager::slotImportLayerAsFilterMask()
{
importImage(QUrl(), "KisFilterMask");
}
void KisImageManager::slotImportLayerAsSelectionMask()
{
importImage(QUrl(), "KisSelectionMask");
}
qint32 KisImageManager::importImage(const QUrl &urlArg, const QString &layerType)
{
KisImageWSP currentImage = m_view->image();
if (!currentImage) {
return 0;
}
QList<QUrl> urls;
qint32 rc = 0;
if (urlArg.isEmpty()) {
KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::OpenFiles, "OpenDocument");
dialog.setCaption(i18n("Import Image"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import));
QStringList fileNames = dialog.filenames();
Q_FOREACH (const QString &fileName, fileNames) {
urls << QUrl::fromLocalFile(fileName);
}
} else {
urls.push_back(urlArg);
}
if (urls.empty())
return 0;
for (QList<QUrl>::iterator it = urls.begin(); it != urls.end(); ++it) {
new KisImportCatcher(*it, m_view, layerType);
}
m_view->canvas()->update();
return rc;
}
void KisImageManager::resizeCurrentImage(qint32 w, qint32 h, qint32 xOffset, qint32 yOffset)
{
if (!m_view->image()) return;
m_view->image()->resizeImage(QRect(-xOffset, -yOffset, w, h));
}
void KisImageManager::scaleCurrentImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
{
if (!m_view->image()) return;
m_view->image()->scaleImage(size, xres, yres, filterStrategy);
}
void KisImageManager::rotateCurrentImage(double radians)
{
if (!m_view->image()) return;
m_view->image()->rotateImage(radians);
}
void KisImageManager::shearCurrentImage(double angleX, double angleY)
{
if (!m_view->image()) return;
m_view->image()->shear(angleX, angleY);
}
void KisImageManager::slotImageProperties()
{
KisImageWSP image = m_view->image();
if (!image) return;
QPointer<KisDlgImageProperties> dlg = new KisDlgImageProperties(image, m_view->mainWindow());
if (dlg->exec() == QDialog::Accepted) {
image->convertProjectionColorSpace(dlg->colorSpace());
}
delete dlg;
}
void updateImageBackgroundColor(KisImageSP image, const QColorDialog *dlg)
{
QColor newColor = dlg->currentColor();
KoColor bg = image->defaultProjectionColor();
bg.fromQColor(newColor);
KisLayerUtils::changeImageDefaultProjectionColor(image, bg);
}
void KisImageManager::slotImageColor()
{
KisImageWSP image = m_view->image();
if (!image) return;
QColorDialog dlg;
dlg.setOption(QColorDialog::ShowAlphaChannel, true);
KoColor bg = image->defaultProjectionColor();
dlg.setCurrentColor(bg.toQColor());
KisSignalCompressor compressor(200, KisSignalCompressor::FIRST_INACTIVE);
std::function<void ()> updateCall(std::bind(updateImageBackgroundColor, image, &dlg));
SignalToFunctionProxy proxy(updateCall);
connect(&dlg, SIGNAL(currentColorChanged(QColor)), &compressor, SLOT(start()));
connect(&compressor, SIGNAL(timeout()), &proxy, SLOT(start()));
dlg.exec();
}
diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc
index 9056afa0e1..323330fc82 100644
--- a/libs/ui/kis_layer_manager.cc
+++ b/libs/ui/kis_layer_manager.cc
@@ -1,843 +1,956 @@
/*
* Copyright (C) 2006 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_layer_manager.h"
#include <QRect>
#include <QApplication>
#include <QCursor>
#include <QString>
#include <QDialog>
#include <QVBoxLayout>
#include <QFileInfo>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <kactioncollection.h>
#include <klocalizedstring.h>
#include <QMessageBox>
#include <QUrl>
#include <kis_file_name_requester.h>
#include <kis_icon.h>
#include <KisImportExportManager.h>
#include <KisDocument.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include <KoPointerEvent.h>
#include <KoColorProfile.h>
#include <KoSelection.h>
#include <KisPart.h>
#include <KisMainWindow.h>
#include <filter/kis_filter_configuration.h>
#include <filter/kis_filter.h>
#include <kis_filter_strategy.h>
#include <generator/kis_generator_layer.h>
#include <kis_file_layer.h>
#include <kis_adjustment_layer.h>
#include <kis_mask.h>
#include <kis_clone_layer.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_paint_device.h>
#include <kis_selection.h>
#include <flake/kis_shape_layer.h>
#include <kis_undo_adapter.h>
#include <kis_painter.h>
#include <metadata/kis_meta_data_store.h>
#include <metadata/kis_meta_data_merge_strategy_registry.h>
#include <kis_psd_layer_style.h>
#include <KisMimeDatabase.h>
#include "KisImportExportManager.h"
#include "kis_config.h"
#include "kis_cursor.h"
#include "dialogs/kis_dlg_adj_layer_props.h"
#include "dialogs/kis_dlg_adjustment_layer.h"
#include "dialogs/kis_dlg_layer_properties.h"
#include "dialogs/kis_dlg_generator_layer.h"
#include "dialogs/kis_dlg_file_layer.h"
#include "dialogs/kis_dlg_layer_style.h"
#include "KisDocument.h"
#include "kis_filter_manager.h"
#include "kis_node_visitor.h"
#include "kis_paint_layer.h"
#include "commands/kis_image_commands.h"
#include "commands/kis_layer_command.h"
#include "commands/kis_node_commands.h"
+#include "kis_change_file_layer_command.h"
#include "kis_canvas_resource_provider.h"
#include "kis_selection_manager.h"
#include "kis_statusbar.h"
#include "KisViewManager.h"
#include "kis_zoom_manager.h"
#include "canvas/kis_canvas2.h"
#include "widgets/kis_meta_data_merge_strategy_chooser_widget.h"
#include "widgets/kis_wdg_generator.h"
#include "kis_progress_widget.h"
#include "kis_node_commands_adapter.h"
#include "kis_node_manager.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "KisPart.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_signal_compressor_with_param.h"
#include "kis_abstract_projection_plane.h"
#include "commands_new/kis_set_layer_style_command.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_selection_mask.h"
#include "kis_layer_utils.h"
#include "lazybrush/kis_colorize_mask.h"
#include "KisSaveGroupVisitor.h"
KisLayerManager::KisLayerManager(KisViewManager * view)
: m_view(view)
, m_imageView(0)
, m_imageFlatten(0)
, m_imageMergeLayer(0)
, m_groupLayersSave(0)
, m_imageResizeToLayer(0)
, m_flattenLayer(0)
, m_rasterizeLayer(0)
, m_commandsAdapter(new KisNodeCommandsAdapter(m_view))
, m_layerStyle(0)
{
}
KisLayerManager::~KisLayerManager()
{
delete m_commandsAdapter;
}
void KisLayerManager::setView(QPointer<KisView>view)
{
m_imageView = view;
}
KisLayerSP KisLayerManager::activeLayer()
{
if (m_imageView) {
return m_imageView->currentLayer();
}
return 0;
}
KisPaintDeviceSP KisLayerManager::activeDevice()
{
if (activeLayer()) {
return activeLayer()->paintDevice();
}
return 0;
}
void KisLayerManager::activateLayer(KisLayerSP layer)
{
if (m_imageView) {
emit sigLayerActivated(layer);
layersUpdated();
if (layer) {
m_view->resourceProvider()->slotNodeActivated(layer.data());
}
}
}
void KisLayerManager::setup(KisActionManager* actionManager)
{
m_imageFlatten = actionManager->createAction("flatten_image");
connect(m_imageFlatten, SIGNAL(triggered()), this, SLOT(flattenImage()));
m_imageMergeLayer = actionManager->createAction("merge_layer");
connect(m_imageMergeLayer, SIGNAL(triggered()), this, SLOT(mergeLayer()));
m_flattenLayer = actionManager->createAction("flatten_layer");
connect(m_flattenLayer, SIGNAL(triggered()), this, SLOT(flattenLayer()));
m_rasterizeLayer = actionManager->createAction("rasterize_layer");
connect(m_rasterizeLayer, SIGNAL(triggered()), this, SLOT(rasterizeLayer()));
m_groupLayersSave = actionManager->createAction("save_groups_as_images");
connect(m_groupLayersSave, SIGNAL(triggered()), this, SLOT(saveGroupLayers()));
m_convertGroupAnimated = actionManager->createAction("convert_group_to_animated");
connect(m_convertGroupAnimated, SIGNAL(triggered()), this, SLOT(convertGroupToAnimated()));
m_imageResizeToLayer = actionManager->createAction("resizeimagetolayer");
connect(m_imageResizeToLayer, SIGNAL(triggered()), this, SLOT(imageResizeToActiveLayer()));
KisAction *action = actionManager->createAction("trim_to_image");
connect(action, SIGNAL(triggered()), this, SLOT(trimToImage()));
m_layerStyle = actionManager->createAction("layer_style");
connect(m_layerStyle, SIGNAL(triggered()), this, SLOT(layerStyle()));
}
void KisLayerManager::updateGUI()
{
KisImageSP image = m_view->image();
KisLayerSP layer = activeLayer();
const bool isGroupLayer = layer && layer->inherits("KisGroupLayer");
m_imageMergeLayer->setText(
isGroupLayer ?
i18nc("@action:inmenu", "Merge Group") :
i18nc("@action:inmenu", "Merge with Layer Below"));
m_flattenLayer->setVisible(!isGroupLayer);
if (m_view->statusBar())
m_view->statusBar()->setProfile(image);
}
void KisLayerManager::imageResizeToActiveLayer()
{
KisLayerSP layer;
KisImageWSP image = m_view->image();
if (image && (layer = activeLayer())) {
QRect cropRect = layer->projection()->nonDefaultPixelArea();
if (!cropRect.isEmpty()) {
image->cropImage(cropRect);
} else {
m_view->showFloatingMessage(
i18nc("floating message in layer manager",
"Layer is empty "),
QIcon(), 2000, KisFloatingMessage::Low);
}
}
}
void KisLayerManager::trimToImage()
{
KisImageWSP image = m_view->image();
if (image) {
image->cropImage(image->bounds());
}
}
void KisLayerManager::layerProperties()
{
if (!m_view) return;
if (!m_view->document()) return;
KisLayerSP layer = activeLayer();
QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
const bool multipleLayersSelected = selectedNodes.size() > 1;
if (!layer) return;
KisAdjustmentLayerSP alayer = KisAdjustmentLayerSP(dynamic_cast<KisAdjustmentLayer*>(layer.data()));
KisGeneratorLayerSP glayer = KisGeneratorLayerSP(dynamic_cast<KisGeneratorLayer*>(layer.data()));
+ KisFileLayerSP flayer = KisFileLayerSP(dynamic_cast<KisFileLayer*>(layer.data()));
if (alayer && !multipleLayersSelected) {
KisPaintDeviceSP dev = alayer->projection();
KisDlgAdjLayerProps dlg(alayer, alayer.data(), dev, m_view, alayer->filter().data(), alayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops");
dlg.resize(dlg.minimumSizeHint());
KisFilterConfigurationSP configBefore(alayer->filter());
KIS_ASSERT_RECOVER_RETURN(configBefore);
QString xmlBefore = configBefore->toXML();
if (dlg.exec() == QDialog::Accepted) {
alayer->setName(dlg.layerName());
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
if(xmlBefore != xmlAfter) {
KisChangeFilterCmd *cmd
= new KisChangeFilterCmd(alayer,
configBefore->name(),
xmlBefore,
configAfter->name(),
xmlAfter,
false);
// FIXME: check whether is needed
cmd->redo();
m_view->undoAdapter()->addCommand(cmd);
m_view->document()->setModified(true);
}
}
else {
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
if(xmlBefore != xmlAfter) {
alayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data()));
alayer->setDirty();
}
}
}
else if (glayer && !multipleLayersSelected) {
KisDlgGeneratorLayer dlg(glayer->name(), m_view, m_view->mainWindow());
dlg.setCaption(i18n("Fill Layer Properties"));
KisFilterConfigurationSP configBefore(glayer->filter());
Q_ASSERT(configBefore);
QString xmlBefore = configBefore->toXML();
dlg.setConfiguration(configBefore.data());
dlg.resize(dlg.minimumSizeHint());
if (dlg.exec() == QDialog::Accepted) {
glayer->setName(dlg.layerName());
KisFilterConfigurationSP configAfter(dlg.configuration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
if(xmlBefore != xmlAfter) {
KisChangeFilterCmd *cmd
= new KisChangeFilterCmd(glayer,
configBefore->name(),
xmlBefore,
configAfter->name(),
xmlAfter,
true);
// FIXME: check whether is needed
cmd->redo();
m_view->undoAdapter()->addCommand(cmd);
m_view->document()->setModified(true);
}
}
+ } else if (flayer && !multipleLayersSelected){
+ QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath();
+ QString fileNameOld = flayer->fileName();
+ KisFileLayer::ScalingMethod scalingMethodOld = flayer->scalingMethod();
+ KisDlgFileLayer dlg(basePath, flayer->name(), m_view->mainWindow());
+ dlg.setCaption(i18n("File Layer Properties"));
+ dlg.setFileName(fileNameOld);
+ dlg.setScalingMethod(scalingMethodOld);
+
+ if (dlg.exec() == QDialog::Accepted) {
+ const QString fileNameNew = dlg.fileName();
+ KisFileLayer::ScalingMethod scalingMethodNew = dlg.scaleToImageResolution();
+
+ if(fileNameNew.isEmpty()){
+ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified"));
+ return;
+ }
+ flayer->setName(dlg.layerName());
+
+ if (fileNameOld!= fileNameNew || scalingMethodOld != scalingMethodNew) {
+ KisChangeFileLayerCmd *cmd
+ = new KisChangeFileLayerCmd(flayer,
+ basePath,
+ fileNameOld,
+ scalingMethodOld,
+ basePath,
+ fileNameNew,
+ scalingMethodNew);
+ m_view->undoAdapter()->addCommand(cmd);
+ }
+ }
} else { // If layer == normal painting layer, vector layer, or group layer
QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
KisDlgLayerProperties *dialog = new KisDlgLayerProperties(selectedNodes, m_view);
dialog->resize(dialog->minimumSizeHint());
dialog->setAttribute(Qt::WA_DeleteOnClose);
Qt::WindowFlags flags = dialog->windowFlags();
dialog->setWindowFlags(flags | Qt::WindowStaysOnTopHint | Qt::Dialog);
dialog->show();
}
}
void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source)
{
KisImageWSP image = m_view->image();
if (!image) return;
KisLayer *srcLayer = qobject_cast<KisLayer*>(source.data());
if (srcLayer && (srcLayer->inherits("KisGroupLayer") || srcLayer->layerStyle() || srcLayer->childCount() > 0)) {
image->flattenLayer(srcLayer);
return;
}
KisPaintDeviceSP srcDevice =
source->paintDevice() ? source->projection() : source->original();
bool putBehind = false;
QString newCompositeOp = source->compositeOpId();
KisColorizeMask *colorizeMask = dynamic_cast<KisColorizeMask*>(source.data());
if (colorizeMask) {
srcDevice = colorizeMask->coloringProjection();
putBehind = colorizeMask->compositeOpId() == COMPOSITE_BEHIND;
if (putBehind) {
newCompositeOp = COMPOSITE_OVER;
}
}
if (!srcDevice) return;
KisPaintDeviceSP clone;
if (*srcDevice->colorSpace() !=
*srcDevice->compositionSourceColorSpace()) {
clone = new KisPaintDevice(srcDevice->compositionSourceColorSpace());
QRect rc(srcDevice->extent());
KisPainter::copyAreaOptimized(rc.topLeft(), srcDevice, clone, rc);
} else {
clone = new KisPaintDevice(*srcDevice);
}
KisLayerSP layer = new KisPaintLayer(image,
source->name(),
source->opacity(),
clone);
layer->setCompositeOpId(newCompositeOp);
KisNodeSP parent = source->parent();
KisNodeSP above = source;
while (parent && !parent->allowAsChild(layer)) {
above = above->parent();
parent = above ? above->parent() : 0;
}
if (putBehind && above == source->parent()) {
above = above->prevSibling();
}
m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a Paint Layer"));
m_commandsAdapter->addNode(layer, parent, above);
m_commandsAdapter->removeNode(source);
m_commandsAdapter->endMacro();
}
void KisLayerManager::convertGroupToAnimated()
{
KisGroupLayerSP group = dynamic_cast<KisGroupLayer*>(activeLayer().data());
if (group.isNull()) return;
KisPaintLayerSP animatedLayer = new KisPaintLayer(m_view->image(), group->name(), OPACITY_OPAQUE_U8);
animatedLayer->enableAnimation();
KisRasterKeyframeChannel *contentChannel = dynamic_cast<KisRasterKeyframeChannel*>(
animatedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true));
KIS_ASSERT_RECOVER_RETURN(contentChannel);
KisNodeSP child = group->firstChild();
int time = 0;
while (child) {
contentChannel->importFrame(time, child->projection(), NULL);
time++;
child = child->nextSibling();
}
m_commandsAdapter->beginMacro(kundo2_i18n("Convert to an animated layer"));
m_commandsAdapter->addNode(animatedLayer, group->parent(), group);
m_commandsAdapter->removeNode(group);
m_commandsAdapter->endMacro();
}
+void KisLayerManager::convertLayerToFileLayer(KisNodeSP source)
+{
+ KisImageSP image = m_view->image();
+ if (!image) return;
+
+ QStringList listMimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export);
+
+ KoDialog dlg;
+ QWidget *page = new QWidget(&dlg);
+ dlg.setMainWidget(page);
+ QBoxLayout *layout = new QVBoxLayout(page);
+ dlg.setWindowTitle(i18n("Save layers to..."));
+ QLabel *lbl = new QLabel(i18n("Choose the location where the layer will be saved to. The new file layer will then reference this location."));
+ lbl->setWordWrap(true);
+ layout->addWidget(lbl);
+ KisFileNameRequester *urlRequester = new KisFileNameRequester(page);
+ urlRequester->setMode(KoFileDialog::SaveFile);
+ urlRequester->setMimeTypeFilters(listMimeFilter);
+ urlRequester->setFileName(m_view->document()->url().toLocalFile());
+ if (m_view->document()->url().isLocalFile()) {
+ QFileInfo location = QFileInfo(m_view->document()->url().toLocalFile()).baseName();
+ location.setFile(location.dir(), location.baseName()+"_"+ source->name()+".kra");
+ urlRequester->setFileName(location.absoluteFilePath());
+ } else {
+ const QFileInfo location = QFileInfo(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
+ const QString proposedFileName = QDir(location.absoluteFilePath()).absoluteFilePath(source->name() + ".kra");
+ urlRequester->setFileName(proposedFileName);
+ }
+
+ layout->addWidget(urlRequester);
+ if (!dlg.exec()) return;
+
+ QString path = urlRequester->fileName();
+
+ if (path.isEmpty()) return;
+
+ QFileInfo f(path);
+
+ QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName());
+ if (mimeType.isEmpty()) {
+ mimeType = "image/png";
+ }
+ QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
+
+ QRect bounds = source->exactBounds();
+
+ KisImageSP dst = new KisImage(doc->createUndoStore(),
+ image->width(),
+ image->height(),
+ image->projection()->compositionSourceColorSpace(),
+ source->name());
+ dst->setResolution(image->xRes(), image->yRes());
+ doc->setFileBatchMode(false);
+ doc->setCurrentImage(dst);
+ KisNodeSP node = source->clone();
+ dst->addNode(node);
+ dst->initialRefreshGraph();
+ dst->cropImage(bounds);
+ dst->waitForDone();
+
+ bool r = doc->exportDocumentSync(QUrl::fromLocalFile(path), mimeType.toLatin1());
+ if (!r) {
+ qDebug()<< "Path:"<<path;
+ qWarning() << doc->errorMessage();
+ } else {
+ QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath();
+ QString relativePath = QDir(basePath).relativeFilePath(path);
+ KisFileLayer *fileLayer = new KisFileLayer(image, basePath, relativePath, KisFileLayer::None, source->name(), OPACITY_OPAQUE_U8);
+ fileLayer->setX(bounds.x());
+ fileLayer->setY(bounds.y());
+ KisNodeSP dstParent = source->parent();
+ KisNodeSP dstAboveThis = source->prevSibling();
+ m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a file layer"));
+ m_commandsAdapter->removeNode(source);
+ m_commandsAdapter->addNode(fileLayer, dstParent, dstAboveThis);
+ m_commandsAdapter->endMacro();
+ }
+ doc->closeUrl(false);
+}
+
void KisLayerManager::adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above)
{
Q_ASSERT(activeNode);
parent = activeNode;
above = parent->lastChild();
while (parent &&
(!parent->allowAsChild(node) || parent->userLocked())) {
above = parent;
parent = parent->parent();
}
if (!parent) {
warnKrita << "KisLayerManager::adjustLayerPosition:"
<< "No node accepted newly created node";
parent = m_view->image()->root();
above = parent->lastChild();
}
}
void KisLayerManager::addLayerCommon(KisNodeSP activeNode, KisLayerSP layer, bool updateImage)
{
KisNodeSP parent;
KisNodeSP above;
adjustLayerPosition(layer, activeNode, parent, above);
KisGroupLayer *group = dynamic_cast<KisGroupLayer*>(parent.data());
const bool parentForceUpdate = group && !group->projectionIsValid();
updateImage |= parentForceUpdate;
m_commandsAdapter->addNode(layer, parent, above, updateImage, updateImage);
}
KisLayerSP KisLayerManager::addLayer(KisNodeSP activeNode)
{
KisLayerSP layer = KisLayerUtils::constructDefaultLayer(m_view->image());
addLayerCommon(activeNode, layer, false);
return layer;
}
void KisLayerManager::addGroupLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
addLayerCommon(activeNode,
new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8), false);
}
void KisLayerManager::addCloneLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
addLayerCommon(activeNode,
new KisCloneLayer(activeLayer(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8));
}
void KisLayerManager::addShapeLayer(KisNodeSP activeNode)
{
if (!m_view) return;
if (!m_view->document()) return;
KisImageWSP image = m_view->image();
KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, layer, false);
}
void KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisSelectionSP selection = m_view->selection();
KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection);
image->refreshGraph();
KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original());
KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view);
dlg.resize(dlg.minimumSizeHint());
// ensure that the device may be free'd by the dialog
// when it is not needed anymore
previewDevice = 0;
if (dlg.exec() != QDialog::Accepted || adjl->filter().isNull()) {
// XXX: add messagebox warning if there's no filter set!
m_commandsAdapter->undoLastCommand();
} else {
adjl->setName(dlg.layerName());
}
}
KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name,
KisFilterConfigurationSP filter, KisSelectionSP selection)
{
KisImageWSP image = m_view->image();
KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection);
addLayerCommon(activeNode, layer);
return layer;
}
void KisLayerManager::addGeneratorLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow());
dlg.resize(dlg.minimumSizeHint());
if (dlg.exec() == QDialog::Accepted) {
KisSelectionSP selection = m_view->selection();
KisFilterConfigurationSP generator = dlg.configuration();
QString name = dlg.layerName();
addLayerCommon(activeNode,
new KisGeneratorLayer(image, name, generator, selection));
}
}
void KisLayerManager::flattenImage()
{
KisImageSP image = m_view->image();
if (!m_view->blockUntilOperationsFinished(image)) return;
if (image) {
bool doIt = true;
if (image->nHiddenLayers() > 0) {
int answer = QMessageBox::warning(m_view->mainWindow(),
i18nc("@title:window", "Flatten Image"),
i18n("The image contains hidden layers that will be lost. Do you want to flatten the image?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (answer != QMessageBox::Yes) {
doIt = false;
}
}
if (doIt) {
image->flatten();
}
}
}
inline bool isSelectionMask(KisNodeSP node) {
return dynamic_cast<KisSelectionMask*>(node.data());
}
bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image)
{
bool result = false;
KisNodeSP prevNode = currentNode->prevSibling();
if (isSelectionMask(currentNode) &&
prevNode && isSelectionMask(prevNode)) {
QList<KisNodeSP> mergedNodes;
mergedNodes.append(currentNode);
mergedNodes.append(prevNode);
image->mergeMultipleLayers(mergedNodes, currentNode);
result = true;
}
return result;
}
bool tryFlattenGroupLayer(KisNodeSP currentNode, KisImageSP image)
{
bool result = false;
if (currentNode->inherits("KisGroupLayer")) {
KisGroupLayer *layer = qobject_cast<KisGroupLayer*>(currentNode.data());
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(layer, false);
image->flattenLayer(layer);
result = true;
}
return result;
}
void KisLayerManager::mergeLayer()
{
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
if (selectedNodes.size() > 1) {
image->mergeMultipleLayers(selectedNodes, m_view->activeNode());
}
else if (tryMergeSelectionMasks(m_view->activeNode(), image)) {
// already done!
} else if (tryFlattenGroupLayer(m_view->activeNode(), image)) {
// already done!
} else {
if (!layer->prevSibling()) return;
KisLayer *prevLayer = qobject_cast<KisLayer*>(layer->prevSibling().data());
if (!prevLayer) return;
if (prevLayer->userLocked()) {
m_view->showFloatingMessage(
i18nc("floating message in layer manager",
"Layer is locked "),
QIcon(), 2000, KisFloatingMessage::Low);
}
else if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) {
image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop"));
}
else {
const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow());
if (!strategy) return;
image->mergeDown(layer, strategy);
}
}
m_view->updateGUI();
}
void KisLayerManager::flattenLayer()
{
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
convertNodeToPaintLayer(layer);
m_view->updateGUI();
}
void KisLayerManager::rasterizeLayer()
{
KisImageSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity());
KisPainter gc(paintLayer->paintDevice());
QRect rc = layer->projection()->exactBounds();
gc.bitBlt(rc.topLeft(), layer->projection(), rc);
m_commandsAdapter->beginMacro(kundo2_i18n("Rasterize Layer"));
m_commandsAdapter->addNode(paintLayer.data(), layer->parent().data(), layer.data());
int childCount = layer->childCount();
for (int i = 0; i < childCount; i++) {
m_commandsAdapter->moveNode(layer->firstChild(), paintLayer, paintLayer->lastChild());
}
m_commandsAdapter->removeNode(layer);
m_commandsAdapter->endMacro();
updateGUI();
}
void KisLayerManager::layersUpdated()
{
KisLayerSP layer = activeLayer();
if (!layer) return;
m_view->updateGUI();
}
void KisLayerManager::saveGroupLayers()
{
QStringList listMimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export);
KoDialog dlg;
QWidget *page = new QWidget(&dlg);
dlg.setMainWidget(page);
QBoxLayout *layout = new QVBoxLayout(page);
KisFileNameRequester *urlRequester = new KisFileNameRequester(page);
urlRequester->setMode(KoFileDialog::SaveFile);
if (m_view->document()->url().isLocalFile()) {
urlRequester->setStartDir(QFileInfo(m_view->document()->url().toLocalFile()).absolutePath());
}
urlRequester->setMimeTypeFilters(listMimeFilter);
urlRequester->setFileName(m_view->document()->url().toLocalFile());
layout->addWidget(urlRequester);
QCheckBox *chkInvisible = new QCheckBox(i18n("Convert Invisible Groups"), page);
chkInvisible->setChecked(false);
layout->addWidget(chkInvisible);
QCheckBox *chkDepth = new QCheckBox(i18n("Export Only Toplevel Groups"), page);
chkDepth->setChecked(true);
layout->addWidget(chkDepth);
if (!dlg.exec()) return;
QString path = urlRequester->fileName();
if (path.isEmpty()) return;
QFileInfo f(path);
QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName());
if (mimeType.isEmpty()) {
mimeType = "image/png";
}
QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first();
QString basename = f.baseName();
KisImageSP image = m_view->image();
if (!image) return;
KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType);
image->rootLayer()->accept(v);
}
bool KisLayerManager::activeLayerHasSelection()
{
return (activeLayer()->selection() != 0);
}
void KisLayerManager::addFileLayer(KisNodeSP activeNode)
{
QString basePath;
QUrl url = m_view->document()->url();
if (url.isLocalFile()) {
basePath = QFileInfo(url.toLocalFile()).absolutePath();
}
KisImageWSP image = m_view->image();
KisDlgFileLayer dlg(basePath, image->nextLayerName(), m_view->mainWindow());
dlg.resize(dlg.minimumSizeHint());
if (dlg.exec() == QDialog::Accepted) {
QString name = dlg.layerName();
QString fileName = dlg.fileName();
if(fileName.isEmpty()){
QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified"));
return;
}
KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution();
addLayerCommon(activeNode,
new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8));
}
}
void updateLayerStyles(KisLayerSP layer, KisDlgLayerStyle *dlg)
{
KisSetLayerStyleCommand::updateLayerStyle(layer, dlg->style()->clone());
}
void KisLayerManager::layerStyle()
{
KisImageWSP image = m_view->image();
if (!image) return;
KisLayerSP layer = activeLayer();
if (!layer) return;
if (!m_view->blockUntilOperationsFinished(image)) return;
KisPSDLayerStyleSP oldStyle;
if (layer->layerStyle()) {
oldStyle = layer->layerStyle()->clone();
}
else {
oldStyle = toQShared(new KisPSDLayerStyle());
}
KisDlgLayerStyle dlg(oldStyle->clone(), m_view->resourceProvider());
std::function<void ()> updateCall(std::bind(updateLayerStyles, layer, &dlg));
SignalToFunctionProxy proxy(updateCall);
connect(&dlg, SIGNAL(configChanged()), &proxy, SLOT(start()));
if (dlg.exec() == QDialog::Accepted) {
KisPSDLayerStyleSP newStyle = dlg.style();
KUndo2CommandSP command = toQShared(
new KisSetLayerStyleCommand(layer, oldStyle, newStyle));
image->postExecutionUndoAdapter()->addCommand(command);
}
}
diff --git a/libs/ui/kis_layer_manager.h b/libs/ui/kis_layer_manager.h
index 15b8b3ef37..17f9d9cdaf 100644
--- a/libs/ui/kis_layer_manager.h
+++ b/libs/ui/kis_layer_manager.h
@@ -1,131 +1,133 @@
/*
* Copyright (C) 2006 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_LAYER_MANAGER
#define KIS_LAYER_MANAGER
#include <QObject>
#include <QPointer>
#include <QList>
#include "kis_adjustment_layer.h"
#include "kis_types.h"
#include "KisView.h"
#include <filter/kis_filter_configuration.h>
class KisViewManager;
class KisNodeCommandsAdapter;
class KisAction;
class KisActionManager;
/**
* KisLayerManager takes care of the gui around working with layers:
* adding, removing, editing. It also keeps track of the active layer
* for this view.
*/
class KisLayerManager : public QObject
{
Q_OBJECT
public:
KisLayerManager(KisViewManager * view);
~KisLayerManager() override;
void setView(QPointer<KisView>view);
Q_SIGNALS:
void sigLayerActivated(KisLayerSP layer);
private:
friend class KisNodeManager;
/**
* Activate the specified layer. The layer may be 0.
*/
void activateLayer(KisLayerSP layer);
KisLayerSP activeLayer();
KisPaintDeviceSP activeDevice();
void setup(KisActionManager *actionManager);
void updateGUI();
private Q_SLOTS:
void mergeLayer();
void imageResizeToActiveLayer();
void trimToImage();
void layerProperties();
void flattenImage();
void flattenLayer();
void rasterizeLayer();
void layersUpdated();
void saveGroupLayers();
bool activeLayerHasSelection();
void convertNodeToPaintLayer(KisNodeSP source);
void convertGroupToAnimated();
+ void convertLayerToFileLayer(KisNodeSP source);
+
KisLayerSP addLayer(KisNodeSP activeNode);
void addGroupLayer(KisNodeSP activeNode);
void addCloneLayer(KisNodeSP activeNode);
void addShapeLayer(KisNodeSP activeNode);
void addAdjustmentLayer(KisNodeSP activeNode);
KisAdjustmentLayerSP addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection);
void addGeneratorLayer(KisNodeSP activeNode);
void addFileLayer(KisNodeSP activeNode);
void layerStyle();
private:
void adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above);
void addLayerCommon(KisNodeSP activeNode, KisLayerSP layer, bool updateImage = true);
private:
KisViewManager * m_view;
QPointer<KisView>m_imageView;
KisAction *m_imageFlatten;
KisAction *m_imageMergeLayer;
KisAction *m_groupLayersSave;
KisAction *m_convertGroupAnimated;
KisAction *m_imageResizeToLayer;
KisAction *m_flattenLayer;
KisAction *m_rasterizeLayer;
KisNodeCommandsAdapter* m_commandsAdapter;
KisAction *m_layerStyle;
};
#endif
diff --git a/libs/ui/kis_mimedata.cpp b/libs/ui/kis_mimedata.cpp
index ddbdef890f..323488ab8f 100644
--- a/libs/ui/kis_mimedata.cpp
+++ b/libs/ui/kis_mimedata.cpp
@@ -1,453 +1,467 @@
/*
* Copyright (c) 2011 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_mimedata.h"
#include "kis_config.h"
#include "kis_node.h"
#include "kis_paint_device.h"
#include "kis_shared_ptr.h"
#include "kis_image.h"
#include "kis_layer.h"
#include "kis_shape_layer.h"
#include "kis_paint_layer.h"
#include "KisDocument.h"
#include "kis_shape_controller.h"
#include "KisPart.h"
#include "kis_layer_utils.h"
#include "kis_node_insertion_adapter.h"
#include "kis_dummies_facade_base.h"
#include "kis_node_dummies_graph.h"
#include "KisImportExportManager.h"
#include "KisImageBarrierLockerWithFeedback.h"
#include <KoProperties.h>
#include <KoStore.h>
#include <KoColorProfile.h>
#include <KoColorSpaceRegistry.h>
#include <QApplication>
#include <QImage>
#include <QByteArray>
#include <QBuffer>
#include <QDomDocument>
#include <QDomElement>
#include <QTemporaryFile>
#include <QDesktopWidget>
#include <QDir>
KisMimeData::KisMimeData(QList<KisNodeSP> nodes, KisImageSP image, bool forceCopy)
: QMimeData()
, m_nodes(nodes)
, m_forceCopy(forceCopy)
, m_image(image)
{
Q_ASSERT(m_nodes.size() > 0);
}
void KisMimeData::deepCopyNodes()
{
KisNodeList newNodes;
{
KisImageBarrierLockerWithFeedbackAllowNull locker(m_image);
Q_FOREACH (KisNodeSP node, m_nodes) {
newNodes << node->clone();
}
}
m_nodes = newNodes;
m_image = 0;
}
QList<KisNodeSP> KisMimeData::nodes() const
{
return m_nodes;
}
QStringList KisMimeData::formats () const
{
QStringList f = QMimeData::formats();
if (m_nodes.size() > 0) {
f << "application/x-krita-node"
<< "application/x-krita-node-url"
<< "application/x-qt-image"
<< "application/zip"
<< "application/x-krita-node-internal-pointer";
}
return f;
}
KisDocument *createDocument(QList<KisNodeSP> nodes, KisImageSP srcImage)
{
KisDocument *doc = KisPart::instance()->createDocument();
QRect rc;
Q_FOREACH (KisNodeSP node, nodes) {
rc |= node->exactBounds();
}
KisImageSP image = new KisImage(0, rc.width(), rc.height(), nodes.first()->colorSpace(), nodes.first()->name());
{
KisImageBarrierLockerWithFeedbackAllowNull locker(srcImage);
Q_FOREACH (KisNodeSP node, nodes) {
image->addNode(node->clone());
}
}
doc->setCurrentImage(image);
return doc;
}
QByteArray serializeToByteArray(QList<KisNodeSP> nodes, KisImageSP srcImage)
{
QScopedPointer<KisDocument> doc(createDocument(nodes, srcImage));
return doc->serializeToNativeByteArray();
}
QVariant KisMimeData::retrieveData(const QString &mimetype, QVariant::Type preferredType) const
{
/**
* HACK ALERT:
*
* Sometimes Qt requests the data *after* destruction of Krita,
* we cannot load the nodes in that case, because we need signals
* and timers. So we just skip serializing.
*/
if (!QApplication::instance()) return QVariant();
Q_ASSERT(m_nodes.size() > 0);
if (mimetype == "application/x-qt-image") {
KisConfig cfg;
KisDocument *doc = createDocument(m_nodes, m_image);
return doc->image()->projection()->convertToQImage(cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow())),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
}
else if (mimetype == "application/x-krita-node" ||
mimetype == "application/zip") {
QByteArray ba = serializeToByteArray(m_nodes, m_image);
return ba;
}
else if (mimetype == "application/x-krita-node-url") {
QByteArray ba = serializeToByteArray(m_nodes, m_image);
QString temporaryPath =
QDir::tempPath() + QDir::separator() +
QString("krita_tmp_dnd_layer_%1_%2.kra")
.arg(QApplication::applicationPid())
.arg(qrand());
QFile file(temporaryPath);
file.open(QFile::WriteOnly);
file.write(ba);
file.flush();
file.close();
return QUrl::fromLocalFile(temporaryPath).toEncoded();
}
else if (mimetype == "application/x-krita-node-internal-pointer") {
QDomDocument doc("krita_internal_node_pointer");
QDomElement root = doc.createElement("pointer");
root.setAttribute("application_pid", (qint64)QApplication::applicationPid());
root.setAttribute("force_copy", m_forceCopy);
root.setAttribute("image_pointer_value", (qint64)m_image.data());
doc.appendChild(root);
Q_FOREACH (KisNodeSP node, m_nodes) {
QDomElement element = doc.createElement("node");
element.setAttribute("pointer_value", (qint64)node.data());
root.appendChild(element);
}
return doc.toByteArray();
}
else {
return QMimeData::retrieveData(mimetype, preferredType);
}
}
void KisMimeData::initializeExternalNode(KisNodeSP *node,
KisImageWSP image,
KisShapeController *shapeController)
{
// layers store a link to the image, so update it
KisLayer *layer = qobject_cast<KisLayer*>(node->data());
if (layer) {
layer->setImage(image);
}
KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(node->data());
if (shapeLayer) {
// attach the layer to a new shape controller
KisShapeLayer *shapeLayer2 = new KisShapeLayer(*shapeLayer, shapeController);
*node = shapeLayer2;
}
}
QList<KisNodeSP> KisMimeData::tryLoadInternalNodes(const QMimeData *data,
KisImageSP image,
KisShapeController *shapeController,
bool /* IN-OUT */ &copyNode)
{
QList<KisNodeSP> nodes;
bool forceCopy = false;
KisImageSP sourceImage;
// Qt 4.7 and Qt 5.5 way
const KisMimeData *mimedata = qobject_cast<const KisMimeData*>(data);
if (mimedata) {
nodes = mimedata->nodes();
forceCopy = mimedata->m_forceCopy;
sourceImage = mimedata->m_image;
}
// Qt 4.8 way
if (nodes.isEmpty() && data->hasFormat("application/x-krita-node-internal-pointer")) {
QByteArray nodeXml = data->data("application/x-krita-node-internal-pointer");
QDomDocument doc;
doc.setContent(nodeXml);
QDomElement element = doc.documentElement();
qint64 pid = element.attribute("application_pid").toLongLong();
forceCopy = element.attribute("force_copy").toInt();
qint64 imagePointerValue = element.attribute("image_pointer_value").toLongLong();
sourceImage = reinterpret_cast<KisImage*>(imagePointerValue);
if (pid == QApplication::applicationPid()) {
QDomNode n = element.firstChild();
while (!n.isNull()) {
QDomElement e = n.toElement();
if (!e.isNull()) {
qint64 pointerValue = e.attribute("pointer_value").toLongLong();
if (pointerValue) {
nodes << reinterpret_cast<KisNode*>(pointerValue);
}
}
n = n.nextSibling();
}
}
}
if (!nodes.isEmpty() && (forceCopy || copyNode || sourceImage != image)) {
KisImageBarrierLockerWithFeedbackAllowNull locker(sourceImage);
QList<KisNodeSP> clones;
Q_FOREACH (KisNodeSP node, nodes) {
node = node->clone();
if ((forceCopy || copyNode) && sourceImage == image) {
KisLayerUtils::addCopyOfNameTag(node);
}
initializeExternalNode(&node, image, shapeController);
clones << node;
}
nodes = clones;
copyNode = true;
}
return nodes;
}
QList<KisNodeSP> KisMimeData::loadNodes(const QMimeData *data,
const QRect &imageBounds,
const QPoint &preferredCenter,
bool forceRecenter,
KisImageWSP image,
KisShapeController *shapeController)
{
bool alwaysRecenter = false;
QList<KisNodeSP> nodes;
if (data->hasFormat("application/x-krita-node")) {
KisDocument *tempDoc = KisPart::instance()->createDocument();
QByteArray ba = data->data("application/x-krita-node");
QBuffer buf(&ba);
KisImportExportFilter *filter = tempDoc->importExportManager()->filterForMimeType(tempDoc->nativeFormatMimeType(), KisImportExportManager::Import);
filter->setBatchMode(true);
bool result = (filter->convert(tempDoc, &buf) == KisImportExportFilter::OK);
if (result) {
KisImageWSP tempImage = tempDoc->image();
Q_FOREACH (KisNodeSP node, tempImage->root()->childNodes(QStringList(), KoProperties())) {
tempImage->removeNode(node);
initializeExternalNode(&node, image, shapeController);
nodes << node;
}
}
delete filter;
delete tempDoc;
}
if (nodes.isEmpty() && data->hasFormat("application/x-krita-node-url")) {
QByteArray ba = data->data("application/x-krita-node-url");
KisDocument *tempDoc = KisPart::instance()->createDocument();
Q_ASSERT(QUrl::fromEncoded(ba).isLocalFile());
bool result = tempDoc->openUrl(QUrl::fromEncoded(ba));
if (result) {
KisImageWSP tempImage = tempDoc->image();
Q_FOREACH (KisNodeSP node, tempImage->root()->childNodes(QStringList(), KoProperties())) {
tempImage->removeNode(node);
initializeExternalNode(&node, image, shapeController);
nodes << node;
}
}
delete tempDoc;
QFile::remove(QUrl::fromEncoded(ba).toLocalFile());
}
if (nodes.isEmpty() && data->hasImage()) {
QImage qimage = qvariant_cast<QImage>(data->imageData());
KisPaintDeviceSP device = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
device->convertFromQImage(qimage, 0);
- nodes << new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, device);
+
+ if (image) {
+ nodes << new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, device);
+ }
alwaysRecenter = true;
}
if (!nodes.isEmpty()) {
Q_FOREACH (KisNodeSP node, nodes) {
QRect bounds = node->projection()->exactBounds();
if (alwaysRecenter || forceRecenter ||
(!imageBounds.contains(bounds) &&
!imageBounds.intersects(bounds))) {
QPoint pt = preferredCenter - bounds.center();
node->setX(pt.x());
node->setY(pt.y());
}
}
}
return nodes;
}
QMimeData* KisMimeData::mimeForLayers(const KisNodeList &nodes, KisImageSP image, bool forceCopy)
{
KisNodeList inputNodes = nodes;
KisNodeList sortedNodes;
KisLayerUtils::sortMergableNodes(image->root(), inputNodes, sortedNodes);
if (sortedNodes.isEmpty()) return 0;
KisMimeData* data = new KisMimeData(sortedNodes, image, forceCopy);
return data;
}
QMimeData* KisMimeData::mimeForLayersDeepCopy(const KisNodeList &nodes, KisImageSP image, bool forceCopy)
{
KisNodeList inputNodes = nodes;
KisNodeList sortedNodes;
KisLayerUtils::sortMergableNodes(image->root(), inputNodes, sortedNodes);
if (sortedNodes.isEmpty()) return 0;
KisMimeData* data = new KisMimeData(sortedNodes, image, forceCopy);
data->deepCopyNodes();
return data;
}
bool nodeAllowsAsChild(KisNodeSP parent, KisNodeList nodes)
{
bool result = true;
Q_FOREACH (KisNodeSP node, nodes) {
if (!parent->allowAsChild(node)) {
result = false;
break;
}
}
return result;
}
bool correctNewNodeLocation(KisNodeList nodes,
KisNodeDummy* &parentDummy,
KisNodeDummy* &aboveThisDummy)
{
KisNodeSP parentNode = parentDummy->node();
bool result = true;
if(!nodeAllowsAsChild(parentDummy->node(), nodes)) {
aboveThisDummy = parentDummy;
parentDummy = parentDummy->parent();
result = (!parentDummy) ? false :
correctNewNodeLocation(nodes, parentDummy, aboveThisDummy);
}
return result;
}
-bool KisMimeData::insertMimeLayers(const QMimeData *data,
- KisImageSP image,
- KisShapeController *shapeController,
- KisNodeDummy *parentDummy,
- KisNodeDummy *aboveThisDummy,
- bool copyNode,
- KisNodeInsertionAdapter *nodeInsertionAdapter)
+KisNodeList KisMimeData::loadNodesFast(
+ const QMimeData *data,
+ KisImageSP image,
+ KisShapeController *shapeController,
+ bool &copyNode)
{
QList<KisNodeSP> nodes =
KisMimeData::tryLoadInternalNodes(data,
image,
shapeController,
copyNode /* IN-OUT */);
if (nodes.isEmpty()) {
QRect imageBounds = image->bounds();
nodes = KisMimeData::loadNodes(data,
imageBounds, imageBounds.center(),
false,
image, shapeController);
/**
* Don't try to move a node originating from another image,
* just copy it.
*/
copyNode = true;
}
+ return nodes;
+}
+
+bool KisMimeData::insertMimeLayers(const QMimeData *data,
+ KisImageSP image,
+ KisShapeController *shapeController,
+ KisNodeDummy *parentDummy,
+ KisNodeDummy *aboveThisDummy,
+ bool copyNode,
+ KisNodeInsertionAdapter *nodeInsertionAdapter)
+{
+ QList<KisNodeSP> nodes = loadNodesFast(data, image, shapeController, copyNode /* IN-OUT */);
+
if (nodes.isEmpty()) return false;
bool result = true;
if (!correctNewNodeLocation(nodes, parentDummy, aboveThisDummy)) {
return false;
}
KIS_ASSERT_RECOVER(nodeInsertionAdapter) { return false; }
Q_ASSERT(parentDummy);
KisNodeSP aboveThisNode = aboveThisDummy ? aboveThisDummy->node() : 0;
if (copyNode) {
nodeInsertionAdapter->addNodes(nodes, parentDummy->node(), aboveThisNode);
}
else {
Q_ASSERT(nodes.first()->graphListener() == image.data());
nodeInsertionAdapter->moveNodes(nodes, parentDummy->node(), aboveThisNode);
}
return result;
}
diff --git a/libs/ui/kis_mimedata.h b/libs/ui/kis_mimedata.h
index 32a7ab1452..cf175fbdb6 100644
--- a/libs/ui/kis_mimedata.h
+++ b/libs/ui/kis_mimedata.h
@@ -1,118 +1,124 @@
/*
* Copyright (c) 2011 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_MIMEDATA_H
#define KIS_MIMEDATA_H
#include <QMimeData>
#include <kis_types.h>
#include <kritaui_export.h>
class KisShapeController;
class KisNodeDummy;
class KisNodeInsertionAdapter;
class KisNodeGraphListener;
/**
* KisMimeData implements delayed retrieval of nodes for d&d and copy/paste.
*
* TODO: implement support for the ora format.
*/
class KRITAUI_EXPORT KisMimeData : public QMimeData
{
Q_OBJECT
public:
KisMimeData(QList<KisNodeSP> nodes, KisImageSP image, bool forceCopy = false);
/// return the node set on this mimedata object -- for internal use
QList<KisNodeSP> nodes() const;
/**
* For Cut/Copy/Paste operations we should detach the contents of
* the mime data from the actual image because the user can modify
* our image between the Copy/Cut and Paste calls. So we just copy
* all our nodes into the internal array.
*
* It also fixes the problem of Cutting group layers. If we don't copy
* the node and all its children, it'll be deleted by the Cut operation
* and we will not be able to paste it correctly later.
*/
void deepCopyNodes();
/**
* KisMimeData provides the following formats if a node has been set:
* <ul>
* <li>application/x-krita-node: requests a whole serialized node. For d&d between instances of Krita.
* <li>application/x-qt-image: fallback for other applications, returns a QImage of the
* current node's paintdevice
* <li>application/zip: allows drop targets that can handle zip files to open the data
* </ul>
*/
QStringList formats() const override;
/**
* Loads a node from a mime container
* Supports application/x-krita-node and image types.
*/
static KisNodeList loadNodes(const QMimeData *data,
const QRect &imageBounds,
const QPoint &preferredCenter,
bool forceRecenter,
KisImageWSP image,
KisShapeController *shapeController);
+ static KisNodeList loadNodesFast(
+ const QMimeData *data,
+ KisImageSP image,
+ KisShapeController *shapeController,
+ bool &copyNode);
+
private:
/**
* Try load the node, which belongs to the same Krita instance,
* that is can be fetched without serialization
*/
static KisNodeList tryLoadInternalNodes(const QMimeData *data,
KisImageSP image,
KisShapeController *shapeController,
bool /* IN-OUT */ &copyNode);
public:
static QMimeData* mimeForLayers(const KisNodeList &nodes, KisImageSP image, bool forceCopy = false);
static QMimeData* mimeForLayersDeepCopy(const KisNodeList &nodes, KisImageSP image, bool forceCopy);
static bool insertMimeLayers(const QMimeData *data,
KisImageSP image,
KisShapeController *shapeController,
KisNodeDummy *parentDummy,
KisNodeDummy *aboveThisDummy,
bool copyNode,
KisNodeInsertionAdapter *nodeInsertionAdapter);
protected:
QVariant retrieveData(const QString &mimetype, QVariant::Type preferredType) const override;
private:
static void initializeExternalNode(KisNodeSP *nodes,
KisImageWSP image,
KisShapeController *shapeController);
private:
QList<KisNodeSP> m_nodes;
bool m_forceCopy;
KisImageSP m_image;
};
#endif // KIS_MIMEDATA_H
diff --git a/libs/ui/kis_node_filter_proxy_model.cpp b/libs/ui/kis_node_filter_proxy_model.cpp
index f01b6f6485..fc24ba62e0 100644
--- a/libs/ui/kis_node_filter_proxy_model.cpp
+++ b/libs/ui/kis_node_filter_proxy_model.cpp
@@ -1,165 +1,167 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_filter_proxy_model.h"
#include <QSet>
#include "kis_node.h"
#include "kis_node_model.h"
#include "kis_node_manager.h"
#include "kis_signal_compressor.h"
#include "kis_image.h"
struct KisNodeFilterProxyModel::Private
{
Private()
: nodeModel(0),
activeNodeCompressor(1000, KisSignalCompressor::FIRST_INACTIVE)
{}
KisNodeModel *nodeModel;
KisNodeSP pendingActiveNode;
KisNodeSP activeNode;
QSet<int> acceptedLabels;
KisSignalCompressor activeNodeCompressor;
bool isUpdatingFilter = false;
bool checkIndexAllowedRecursively(QModelIndex srcIndex);
};
KisNodeFilterProxyModel::KisNodeFilterProxyModel(QObject *parent)
: QSortFilterProxyModel(parent),
m_d(new Private)
{
connect(&m_d->activeNodeCompressor, SIGNAL(timeout()), SLOT(slotUpdateCurrentNodeFilter()));
}
KisNodeFilterProxyModel::~KisNodeFilterProxyModel()
{
}
void KisNodeFilterProxyModel::setNodeModel(KisNodeModel *model)
{
m_d->nodeModel = model;
setSourceModel(model);
}
bool KisNodeFilterProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (m_d->isUpdatingFilter && role == KisNodeModel::ActiveRole) {
return false;
}
return QSortFilterProxyModel::setData(index, value, role);
}
bool KisNodeFilterProxyModel::Private::checkIndexAllowedRecursively(QModelIndex srcIndex)
{
+ if (!srcIndex.isValid()) return false;
+
KisNodeSP node = nodeModel->nodeFromIndex(srcIndex);
if (node == activeNode ||
acceptedLabels.contains(node->colorLabelIndex())) {
return true;
}
bool result = false;
const int numChildren = srcIndex.model()->rowCount(srcIndex);
for (int i = 0; i < numChildren; i++) {
- QModelIndex child = srcIndex.child(i, 0);
+ QModelIndex child = nodeModel->index(i, 0, srcIndex);
if (checkIndexAllowedRecursively(child)) {
result = true;
break;
}
}
return result;
}
bool KisNodeFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
KIS_ASSERT_RECOVER(m_d->nodeModel) { return true; }
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
KisNodeSP node = m_d->nodeModel->nodeFromIndex(index);
return !node ||
m_d->acceptedLabels.isEmpty() ||
m_d->checkIndexAllowedRecursively(index);
}
KisNodeSP KisNodeFilterProxyModel::nodeFromIndex(const QModelIndex &index) const
{
KIS_ASSERT_RECOVER(m_d->nodeModel) { return 0; }
QModelIndex srcIndex = mapToSource(index);
return m_d->nodeModel->nodeFromIndex(srcIndex);
}
QModelIndex KisNodeFilterProxyModel::indexFromNode(KisNodeSP node) const
{
KIS_ASSERT_RECOVER(m_d->nodeModel) { return QModelIndex(); }
QModelIndex srcIndex = m_d->nodeModel->indexFromNode(node);
return mapFromSource(srcIndex);
}
void KisNodeFilterProxyModel::setAcceptedLabels(const QList<int> &value)
{
m_d->acceptedLabels = QSet<int>::fromList(value);
invalidateFilter();
}
void KisNodeFilterProxyModel::setActiveNode(KisNodeSP node)
{
// the new node may temporary become null when the last layer
// of the document in removed
m_d->pendingActiveNode = node;
if (node && indexFromNode(node).isValid()) {
m_d->activeNodeCompressor.start();
} else {
slotUpdateCurrentNodeFilter();
}
}
void KisNodeFilterProxyModel::slotUpdateCurrentNodeFilter()
{
m_d->activeNode = m_d->pendingActiveNode;
/**
* During the filter update the model might emit "current changed" signals,
* which (in their turn) will issue setData(..., KisNodeModel::ActiveRole)
* call, leading to a double recursion. Which, obviously, crashes Krita.
*
* Right now, just blocking the KisNodeModel::ActiveRole call is the
* most obvious solution for the problem.
*/
m_d->isUpdatingFilter = true;
invalidateFilter();
m_d->isUpdatingFilter = false;
}
void KisNodeFilterProxyModel::unsetDummiesFacade()
{
m_d->nodeModel->setDummiesFacade(0, 0, 0, 0, 0);
m_d->pendingActiveNode = 0;
m_d->activeNode = 0;
}
diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp
index 78bf22aa67..0f70ccc11c 100644
--- a/libs/ui/kis_node_manager.cpp
+++ b/libs/ui/kis_node_manager.cpp
@@ -1,1438 +1,1441 @@
/*
* Copyright (C) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_manager.h"
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QMessageBox>
#include <QSignalMapper>
#include <kactioncollection.h>
#include <QKeySequence>
#include <kis_icon.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoShape.h>
#include <KoShapeLayer.h>
#include <KisImportExportManager.h>
#include <KoFileDialog.h>
#include <KoToolManager.h>
#include <KoProperties.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <kis_types.h>
#include <kis_node.h>
#include <kis_selection.h>
#include <kis_selection_mask.h>
#include <kis_layer.h>
#include <kis_mask.h>
#include <kis_image.h>
#include <kis_painter.h>
#include <kis_paint_layer.h>
#include <KisMimeDatabase.h>
#include "KisPart.h"
#include "canvas/kis_canvas2.h"
#include "kis_shape_controller.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "kis_mask_manager.h"
#include "kis_group_layer.h"
#include "kis_layer_manager.h"
#include "kis_selection_manager.h"
#include "kis_node_commands_adapter.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "kis_processing_applicator.h"
#include "kis_sequential_iterator.h"
#include "kis_transaction.h"
#include "kis_node_selection_adapter.h"
#include "kis_node_insertion_adapter.h"
#include "kis_node_juggler_compressed.h"
#include "kis_clipboard.h"
#include "kis_node_dummies_graph.h"
#include "kis_mimedata.h"
#include "kis_layer_utils.h"
#include "krita_utils.h"
#include "processing/kis_mirror_processing_visitor.h"
#include "KisView.h"
#include <kis_signals_blocker.h>
struct KisNodeManager::Private {
Private(KisNodeManager *_q, KisViewManager *v)
: q(_q)
, view(v)
, imageView(0)
, layerManager(v)
, maskManager(v)
, commandsAdapter(v)
, nodeSelectionAdapter(new KisNodeSelectionAdapter(q))
, nodeInsertionAdapter(new KisNodeInsertionAdapter(q))
{
}
KisNodeManager * q;
KisViewManager * view;
QPointer<KisView>imageView;
KisLayerManager layerManager;
KisMaskManager maskManager;
KisNodeCommandsAdapter commandsAdapter;
QScopedPointer<KisNodeSelectionAdapter> nodeSelectionAdapter;
QScopedPointer<KisNodeInsertionAdapter> nodeInsertionAdapter;
KisAction *showInTimeline;
KisNodeList selectedNodes;
QPointer<KisNodeJugglerCompressed> nodeJuggler;
KisNodeWSP previouslyActiveNode;
bool activateNodeImpl(KisNodeSP node);
QSignalMapper nodeCreationSignalMapper;
QSignalMapper nodeConversionSignalMapper;
void saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity);
void mergeTransparencyMaskAsAlpha(bool writeToLayers);
KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName);
};
bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node)
{
Q_ASSERT(view);
Q_ASSERT(view->canvasBase());
Q_ASSERT(view->canvasBase()->globalShapeManager());
Q_ASSERT(imageView);
if (node && node == q->activeNode()) {
return false;
}
// Set the selection on the shape manager to the active layer
// and set call KoSelection::setActiveLayer( KoShapeLayer* layer )
// with the parent of the active layer.
KoSelection *selection = view->canvasBase()->globalShapeManager()->selection();
Q_ASSERT(selection);
selection->deselectAll();
if (!node) {
selection->setActiveLayer(0);
imageView->setCurrentNode(0);
maskManager.activateMask(0);
layerManager.activateLayer(0);
previouslyActiveNode = q->activeNode();
} else {
previouslyActiveNode = q->activeNode();
KoShape * shape = view->document()->shapeForNode(node);
//if (!shape) return false;
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, false);
selection->select(shape);
KoShapeLayer * shapeLayer = dynamic_cast<KoShapeLayer*>(shape);
//if (!shapeLayer) return false;
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false);
// shapeLayer->setGeometryProtected(node->userLocked());
// shapeLayer->setVisible(node->visible());
selection->setActiveLayer(shapeLayer);
imageView->setCurrentNode(node);
if (KisLayerSP layer = qobject_cast<KisLayer*>(node.data())) {
maskManager.activateMask(0);
layerManager.activateLayer(layer);
} else if (KisMaskSP mask = dynamic_cast<KisMask*>(node.data())) {
maskManager.activateMask(mask);
// XXX_NODE: for now, masks cannot be nested.
layerManager.activateLayer(static_cast<KisLayer*>(node->parent().data()));
}
}
return true;
}
KisNodeManager::KisNodeManager(KisViewManager *view)
: m_d(new Private(this, view))
{
connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP)));
}
KisNodeManager::~KisNodeManager()
{
delete m_d;
}
void KisNodeManager::setView(QPointer<KisView>imageView)
{
m_d->maskManager.setView(imageView);
m_d->layerManager.setView(imageView);
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast<KisShapeController*>(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this);
m_d->imageView->image()->disconnect(this);
}
m_d->imageView = imageView;
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast<KisShapeController*>(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP)));
connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeAction()));
connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP, const KisNodeList&)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP, const KisNodeList&)));
m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode());
}
}
#define NEW_LAYER_ACTION(id, layerType) \
{ \
action = actionManager->createAction(id); \
m_d->nodeCreationSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeCreationSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION_2(id, layerType, exclude) \
{ \
action = actionManager->createAction(id); \
action->setExcludedNodeTypes(QStringList(exclude)); \
actionManager->addAction(id, action); \
m_d->nodeConversionSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeConversionSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION(id, layerType) \
CONVERT_NODE_ACTION_2(id, layerType, layerType)
void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager)
{
m_d->layerManager.setup(actionManager);
m_d->maskManager.setup(actionCollection, actionManager);
KisAction * action = actionManager->createAction("mirrorNodeX");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX()));
action = actionManager->createAction("mirrorNodeY");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY()));
action = actionManager->createAction("activateNextLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode()));
action = actionManager->createAction("activatePreviousLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode()));
action = actionManager->createAction("switchToPreviouslyActiveNode");
connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode()));
action = actionManager->createAction("save_node_as_image");
connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage()));
action = actionManager->createAction("duplicatelayer");
connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode()));
action = actionManager->createAction("copy_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard()));
action = actionManager->createAction("cut_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard()));
action = actionManager->createAction("paste_layer_from_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard()));
action = actionManager->createAction("create_quick_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup()));
action = actionManager->createAction("create_quick_clipping_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup()));
action = actionManager->createAction("quick_ungroup");
connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup()));
action = actionManager->createAction("select_all_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes()));
action = actionManager->createAction("select_visible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes()));
action = actionManager->createAction("select_locked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes()));
action = actionManager->createAction("select_invisible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes()));
action = actionManager->createAction("select_unlocked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes()));
action = actionManager->createAction("new_from_visible");
connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible()));
action = actionManager->createAction("show_in_timeline");
action->setCheckable(true);
connect(action, SIGNAL(toggled(bool)), this, SLOT(slotShowHideTimeline(bool)));
m_d->showInTimeline = action;
NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer");
NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer");
NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer");
NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer");
NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer");
NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer");
NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer");
NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask");
NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask");
NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask");
NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask");
NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask");
connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(createNode(const QString &)));
CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer");
CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask");
CONVERT_NODE_ACTION("convert_to_animated", "animated");
+ CONVERT_NODE_ACTION_2("convert_layer_to_file_layer", "KisFileLayer", QStringList()<< "KisFileLayer" << "KisCloneLayer");
+
connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(convertNode(const QString &)));
action = actionManager->createAction("isolate_layer");
connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool)));
action = actionManager->createAction("toggle_layer_visibility");
connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility()));
action = actionManager->createAction("toggle_layer_lock");
connect(action, SIGNAL(triggered()), this, SLOT(toggleLock()));
action = actionManager->createAction("toggle_layer_inherit_alpha");
connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha()));
action = actionManager->createAction("toggle_layer_alpha_lock");
connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock()));
action = actionManager->createAction("split_alpha_into_mask");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask()));
action = actionManager->createAction("split_alpha_write");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite()));
// HINT: we can save even when the nodes are not editable
action = actionManager->createAction("split_alpha_save_merged");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryFinishIsolatedMode()));
}
void KisNodeManager::updateGUI()
{
// enable/disable all relevant actions
m_d->layerManager.updateGUI();
m_d->maskManager.updateGUI();
}
KisNodeSP KisNodeManager::activeNode()
{
if (m_d->imageView) {
return m_d->imageView->currentNode();
}
return 0;
}
KisLayerSP KisNodeManager::activeLayer()
{
return m_d->layerManager.activeLayer();
}
const KoColorSpace* KisNodeManager::activeColorSpace()
{
if (m_d->maskManager.activeDevice()) {
return m_d->maskManager.activeDevice()->colorSpace();
} else {
Q_ASSERT(m_d->layerManager.activeLayer());
if (m_d->layerManager.activeLayer()->parentLayer())
return m_d->layerManager.activeLayer()->parentLayer()->colorSpace();
else
return m_d->view->image()->colorSpace();
}
}
void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index)
{
if (parent->allowAsChild(node)) {
if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) {
KisSelectionMask *m = dynamic_cast<KisSelectionMask*>(node.data());
KisLayer *l = qobject_cast<KisLayer*>(parent.data());
KisSelectionMaskSP selMask = l->selectionMask();
if (m && m->active() && l && l->selectionMask())
selMask->setActive(false);
}
m_d->commandsAdapter.moveNode(node, parent, index);
}
}
void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Move Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, aboveThis);
}
void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Copy Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->copyNode(nodes, parent, aboveThis);
}
void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Add Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->addNode(nodes, parent, aboveThis);
}
void KisNodeManager::toggleIsolateActiveNode()
{
KisImageWSP image = m_d->view->image();
KisNodeSP activeNode = this->activeNode();
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (activeNode == image->isolatedModeRoot()) {
toggleIsolateMode(false);
} else {
toggleIsolateMode(true);
}
}
void KisNodeManager::toggleIsolateMode(bool checked)
{
KisImageWSP image = m_d->view->image();
if (checked) {
KisNodeSP activeNode = this->activeNode();
// Transform and colorize masks don't have pixel data...
if (activeNode->inherits("KisTransformMask") ||
activeNode->inherits("KisColorizeMask")) return;
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (!image->startIsolatedMode(activeNode)) {
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
action->setChecked(false);
}
} else {
image->stopIsolatedMode();
}
}
void KisNodeManager::slotUpdateIsolateModeAction()
{
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
Q_ASSERT(action);
KisNodeSP activeNode = this->activeNode();
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
action->setChecked(isolatedRootNode && isolatedRootNode == activeNode);
}
void KisNodeManager::slotTryFinishIsolatedMode()
{
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
if (!isolatedRootNode) return;
this->toggleIsolateMode(true);
}
void KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom)
{
if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) {
return;
}
KisNodeSP activeNode = this->activeNode();
if (!activeNode) {
activeNode = m_d->view->image()->root();
}
KIS_ASSERT_RECOVER_RETURN(activeNode);
// XXX: make factories for this kind of stuff,
// with a registry
if (nodeType == "KisPaintLayer") {
m_d->layerManager.addLayer(activeNode);
} else if (nodeType == "KisGroupLayer") {
m_d->layerManager.addGroupLayer(activeNode);
} else if (nodeType == "KisAdjustmentLayer") {
m_d->layerManager.addAdjustmentLayer(activeNode);
} else if (nodeType == "KisGeneratorLayer") {
m_d->layerManager.addGeneratorLayer(activeNode);
} else if (nodeType == "KisShapeLayer") {
m_d->layerManager.addShapeLayer(activeNode);
} else if (nodeType == "KisCloneLayer") {
m_d->layerManager.addCloneLayer(activeNode);
} else if (nodeType == "KisTransparencyMask") {
m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFilterMask") {
m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false);
} else if (nodeType == "KisColorizeMask") {
m_d->maskManager.createColorizeMask(activeNode);
} else if (nodeType == "KisTransformMask") {
m_d->maskManager.createTransformMask(activeNode);
} else if (nodeType == "KisSelectionMask") {
m_d->maskManager.createSelectionMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFileLayer") {
m_d->layerManager.addFileLayer(activeNode);
}
}
void KisNodeManager::createFromVisible()
{
KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild());
}
void KisNodeManager::slotShowHideTimeline(bool value)
{
Q_FOREACH (KisNodeSP node, selectedNodes()) {
node->setUseInTimeline(value);
}
}
KisLayerSP KisNodeManager::createPaintLayer()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) {
activeNode = m_d->view->image()->root();
}
return m_d->layerManager.addLayer(activeNode);
}
void KisNodeManager::convertNode(const QString &nodeType)
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
if (nodeType == "KisPaintLayer") {
m_d->layerManager.convertNodeToPaintLayer(activeNode);
} else if (nodeType == "KisSelectionMask" ||
nodeType == "KisFilterMask" ||
nodeType == "KisTransparencyMask") {
KisPaintDeviceSP copyFrom = activeNode->paintDevice() ?
activeNode->paintDevice() : activeNode->projection();
m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask"));
if (nodeType == "KisSelectionMask") {
m_d->maskManager.createSelectionMask(activeNode, copyFrom, true);
} else if (nodeType == "KisFilterMask") {
m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true);
} else if (nodeType == "KisTransparencyMask") {
m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true);
}
m_d->commandsAdapter.removeNode(activeNode);
m_d->commandsAdapter.endMacro();
-
+ } else if (nodeType == "KisFileLayer") {
+ m_d->layerManager.convertLayerToFileLayer(activeNode);
} else {
warnKrita << "Unsupported node conversion type:" << nodeType;
}
}
void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node)
{
KIS_ASSERT_RECOVER_RETURN(node != activeNode());
if (m_d->activateNodeImpl(node)) {
emit sigUiNeedChangeActiveNode(node);
emit sigNodeActivated(node);
nodesUpdated();
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
}
void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node)
{
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
void KisNodeManager::slotUiActivatedNode(KisNodeSP node)
{
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
if (node) {
QStringList vectorTools = QStringList()
<< "InteractionTool"
<< "KarbonPatternTool"
<< "KarbonGradientTool"
<< "KarbonCalligraphyTool"
<< "CreateShapesTool"
<< "PathTool";
QStringList pixelTools = QStringList()
<< "KritaShape/KisToolBrush"
<< "KritaShape/KisToolDyna"
<< "KritaShape/KisToolMultiBrush"
<< "KritaFill/KisToolFill"
<< "KritaFill/KisToolGradient";
if (node->inherits("KisShapeLayer")) {
if (pixelTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("InteractionTool");
}
}
else {
if (vectorTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
}
}
}
}
void KisNodeManager::nodesUpdated()
{
KisNodeSP node = activeNode();
if (!node) return;
m_d->layerManager.layersUpdated();
m_d->maskManager.masksUpdated();
m_d->view->updateGUI();
m_d->view->selectionManager()->selectionChanged();
{
KisSignalsBlocker b(m_d->showInTimeline);
m_d->showInTimeline->setChecked(node->useInTimeline());
}
}
KisPaintDeviceSP KisNodeManager::activePaintDevice()
{
return m_d->maskManager.activeMask() ?
m_d->maskManager.activeDevice() :
m_d->layerManager.activeDevice();
}
void KisNodeManager::nodeProperties(KisNodeSP node)
{
if (selectedNodes().size() > 1 || node->inherits("KisLayer")) {
m_d->layerManager.layerProperties();
} else if (node->inherits("KisMask")) {
m_d->maskManager.maskProperties();
}
}
qint32 KisNodeManager::convertOpacityToInt(qreal opacity)
{
/**
* Scales opacity from the range 0...100
* to the integer range 0...255
*/
return qMin(255, int(opacity * 2.55 + 0.5));
}
void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity,
bool finalChange)
{
if (!node) return;
if (node->opacity() == opacity) return;
if (!finalChange) {
node->setOpacity(opacity);
node->setDirty();
} else {
m_d->commandsAdapter.setOpacity(node, opacity);
}
}
void KisNodeManager::setNodeCompositeOp(KisNodeSP node,
const KoCompositeOp* compositeOp)
{
if (!node) return;
if (node->compositeOp() == compositeOp) return;
m_d->commandsAdapter.setCompositeOp(node, compositeOp);
}
void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes)
{
if (activeNode) {
slotNonUiActivatedNode(activeNode);
}
if (!selectedNodes.isEmpty()) {
slotSetSelectedNodes(selectedNodes);
}
}
void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes)
{
m_d->selectedNodes = nodes;
emit sigUiNeedChangeSelectedNodes(nodes);
}
KisNodeList KisNodeManager::selectedNodes()
{
return m_d->selectedNodes;
}
KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const
{
return m_d->nodeSelectionAdapter.data();
}
KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const
{
return m_d->nodeInsertionAdapter.data();
}
void KisNodeManager::nodeOpacityChanged(qreal opacity, bool finalChange)
{
KisNodeSP node = activeNode();
setNodeOpacity(node, convertOpacityToInt(opacity), finalChange);
}
void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op)
{
KisNodeSP node = activeNode();
setNodeCompositeOp(node, op);
}
void KisNodeManager::duplicateActiveNode()
{
KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->duplicateNode(selectedNodes());
}
KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName)
{
KisImageWSP image = view->image();
if (!nodeJuggler ||
(nodeJuggler &&
!nodeJuggler->canMergeAction(actionName))) {
nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750);
nodeJuggler->setAutoDelete(true);
}
return nodeJuggler;
}
void KisNodeManager::raiseNode()
{
KUndo2MagicString actionName = kundo2_i18n("Raise Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->raiseNode(selectedNodes());
}
void KisNodeManager::lowerNode()
{
KUndo2MagicString actionName = kundo2_i18n("Lower Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->lowerNode(selectedNodes());
}
void KisNodeManager::removeSingleNode(KisNodeSP node)
{
if (!node || !node->parent()) {
return;
}
KisNodeList nodes;
nodes << node;
removeSelectedNodes(nodes);
}
void KisNodeManager::removeSelectedNodes(KisNodeList nodes)
{
KUndo2MagicString actionName = kundo2_i18n("Remove Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::removeNode()
{
removeSelectedNodes(selectedNodes());
}
void KisNodeManager::mirrorNodeX()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer X");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask X");
}
mirrorNode(node, commandName, Qt::Horizontal);
}
void KisNodeManager::mirrorNodeY()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer Y");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask Y");
}
mirrorNode(node, commandName, Qt::Vertical);
}
inline bool checkForGlobalSelection(KisNodeSP node) {
return dynamic_cast<KisSelectionMask*>(node.data()) && node->parent() && !node->parent()->parent();
}
void KisNodeManager::activateNextNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node = activeNode->nextSibling();
while (node && node->childCount() > 0) {
node = node->firstChild();
}
if (!node && activeNode->parent() && activeNode->parent()->parent()) {
node = activeNode->parent();
}
while(node && checkForGlobalSelection(node)) {
node = node->nextSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::activatePreviousNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node;
if (activeNode->childCount() > 0) {
node = activeNode->lastChild();
}
else {
node = activeNode->prevSibling();
}
while (!node && activeNode->parent()) {
node = activeNode->parent()->prevSibling();
activeNode = activeNode->parent();
}
while(node && checkForGlobalSelection(node)) {
node = node->prevSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::switchToPreviouslyActiveNode()
{
if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) {
slotNonUiActivatedNode(m_d->previouslyActiveNode);
}
}
void KisNodeManager::rotate(double radians)
{
if(!m_d->view->image()) return;
KisNodeSP node = activeNode();
if (!node) return;
if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return;
m_d->view->image()->rotateNode(node, radians);
}
void KisNodeManager::rotate180()
{
rotate(M_PI);
}
void KisNodeManager::rotateLeft90()
{
rotate(-M_PI / 2);
}
void KisNodeManager::rotateRight90()
{
rotate(M_PI / 2);
}
void KisNodeManager::shear(double angleX, double angleY)
{
if (!m_d->view->image()) return;
KisNodeSP node = activeNode();
if (!node) return;
if(!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return;
m_d->view->image()->shearNode(node, angleX, angleY);
}
void KisNodeManager::scale(double sx, double sy, KisFilterStrategy *filterStrategy)
{
KisNodeSP node = activeNode();
KIS_ASSERT_RECOVER_RETURN(node);
m_d->view->image()->scaleNode(node, sx, sy, filterStrategy);
nodesUpdated();
}
void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation)
{
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(m_d->view->image(), node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisProcessingVisitorSP visitor =
new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation);
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
nodesUpdated();
}
void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity)
{
KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage");
dialog.setCaption(i18n("Export \"%1\"", defaultName));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Export));
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QUrl url = QUrl::fromLocalFile(filename);
if (url.isEmpty()) return;
QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);;
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
KisImageSP dst = new KisImage(doc->createUndoStore(),
bounds.width(),
bounds.height(),
device->compositionSourceColorSpace(),
defaultName);
dst->setResolution(xRes, yRes);
doc->setCurrentImage(dst);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity);
paintLayer->paintDevice()->makeCloneFrom(device, bounds);
dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
dst->initialRefreshGraph();
doc->exportDocumentSync(url, mimefilter.toLatin1());
}
void KisNodeManager::saveNodeAsImage()
{
KisNodeSP node = activeNode();
if (!node) {
warnKrita << "BUG: Save Node As Image was called without any node selected";
return;
}
KisImageWSP image = m_d->view->image();
QRect saveRect = image->bounds() | node->exactBounds();
KisPaintDeviceSP device = node->paintDevice();
if (!device) {
device = node->projection();
}
m_d->saveDeviceAsImage(device, node->name(),
saveRect,
image->xRes(), image->yRes(),
node->opacity());
}
void KisNodeManager::slotSplitAlphaIntoMask()
{
KisNodeSP node = activeNode();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice());
KisPaintDeviceSP srcDevice = node->paintDevice();
const KoColorSpace *srcCS = srcDevice->colorSpace();
const QRect processRect =
srcDevice->exactBounds() |
srcDevice->defaultBounds()->bounds();
KisPaintDeviceSP selectionDevice =
new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask"));
KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice);
KisSequentialIterator srcIt(srcDevice, processRect);
KisSequentialIterator dstIt(selectionDevice, processRect);
do {
quint8 *srcPtr = srcIt.rawData();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->opacityU8(srcPtr);
srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1);
} while (srcIt.nextPixel() && dstIt.nextPixel());
m_d->commandsAdapter.addExtraCommand(transaction.endAndTake());
createNode("KisTransparencyMask", false, selectionDevice);
m_d->commandsAdapter.endMacro();
}
void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers)
{
KisNodeSP node = q->activeNode();
KisNodeSP parentNode = node->parent();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask"));
if (writeToLayers && !parentNode->hasEditablePaintDevice()) {
QMessageBox::information(view->mainWindow(),
i18nc("@title:window", "Layer %1 is not editable").arg(parentNode->name()),
i18n("Cannot write alpha channel of "
"the parent layer \"%1\".\n"
"The operation will be cancelled.").arg(parentNode->name()));
return;
}
KisPaintDeviceSP dstDevice;
if (writeToLayers) {
KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice());
dstDevice = parentNode->paintDevice();
} else {
KisPaintDeviceSP copyDevice = parentNode->paintDevice();
if (!copyDevice) {
copyDevice = parentNode->original();
}
dstDevice = new KisPaintDevice(*copyDevice);
}
const KoColorSpace *dstCS = dstDevice->colorSpace();
KisPaintDeviceSP selectionDevice = node->paintDevice();
KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1);
const QRect processRect =
selectionDevice->exactBounds() |
dstDevice->exactBounds() |
selectionDevice->defaultBounds()->bounds();
QScopedPointer<KisTransaction> transaction;
if (writeToLayers) {
commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer"));
transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice));
}
KisSequentialIterator srcIt(selectionDevice, processRect);
KisSequentialIterator dstIt(dstDevice, processRect);
do {
quint8 *alpha8Ptr = srcIt.rawData();
quint8 *dstPtr = dstIt.rawData();
dstCS->setOpacity(dstPtr, *alpha8Ptr, 1);
} while (srcIt.nextPixel() && dstIt.nextPixel());
if (writeToLayers) {
commandsAdapter.addExtraCommand(transaction->endAndTake());
commandsAdapter.removeNode(node);
commandsAdapter.endMacro();
} else {
KisImageWSP image = view->image();
QRect saveRect = image->bounds();
saveDeviceAsImage(dstDevice, parentNode->name(),
saveRect,
image->xRes(), image->yRes(),
OPACITY_OPAQUE_U8);
}
}
void KisNodeManager::slotSplitAlphaWrite()
{
m_d->mergeTransparencyMaskAsAlpha(true);
}
void KisNodeManager::slotSplitAlphaSaveMerged()
{
m_d->mergeTransparencyMaskAsAlpha(false);
}
void KisNodeManager::toggleLock()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
bool isLocked = active->userLocked();
for (auto &node : nodes) {
node->setUserLocked(!isLocked);
}
}
void KisNodeManager::toggleVisibility()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
bool isVisible = active->visible();
for (auto &node : nodes) {
node->setVisible(!isVisible);
node->setDirty();
}
}
void KisNodeManager::toggleAlphaLock()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
auto layer = qobject_cast<KisPaintLayer*>(active.data());
if (!layer) {
return;
}
bool isAlphaLocked = layer->alphaLocked();
for (auto &node : nodes) {
auto layer = qobject_cast<KisPaintLayer*>(node.data());
if (layer) {
layer->setAlphaLocked(!isAlphaLocked);
}
}
}
void KisNodeManager::toggleInheritAlpha()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP active = activeNode();
if (nodes.isEmpty() || !active) return;
auto layer = qobject_cast<KisLayer*>(active.data());
if (!layer) {
return;
}
bool isAlphaDisabled = layer->alphaChannelDisabled();
for (auto &node : nodes) {
auto layer = qobject_cast<KisLayer*>(node.data());
if (layer) {
layer->disableAlphaChannel(!isAlphaDisabled);
node->setDirty();
}
}
}
void KisNodeManager::cutLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
if (nodes.isEmpty()) return;
KisClipboard::instance()->setLayers(nodes, m_d->view->image(), false);
KUndo2MagicString actionName = kundo2_i18n("Cut Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::copyLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
KisClipboard::instance()->setLayers(nodes, m_d->view->image(), true);
}
void KisNodeManager::pasteLayersFromClipboard()
{
const QMimeData *data = KisClipboard::instance()->layersMimeData();
if (!data) return;
KisNodeSP activeNode = this->activeNode();
KisShapeController *shapeController = dynamic_cast<KisShapeController*>(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
KisDummiesFacadeBase *dummiesFacade = dynamic_cast<KisDummiesFacadeBase*>(m_d->imageView->document()->shapeController());
Q_ASSERT(dummiesFacade);
const bool copyNode = false;
KisImageSP image = m_d->view->image();
KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode);
KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0;
KisMimeData::insertMimeLayers(data,
image,
shapeController,
parentDummy,
aboveThisDummy,
copyNode,
nodeInsertionAdapter());
}
void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler,
const QString &overrideGroupName,
KisNodeSP *newGroup,
KisNodeSP *newLastChild)
{
KisNodeSP active = activeNode();
if (!active) return;
KisImageSP image = m_d->view->image();
QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName();
KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8);
KisNodeList nodes1;
nodes1 << group;
KisNodeList nodes2;
nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes());
KisLayerUtils::filterMergableNodes(nodes2);
if (KisLayerUtils::checkIsChildOf(active, nodes2)) {
active = nodes2.first();
}
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
juggler->addNode(nodes1, parent, aboveThis);
juggler->moveNode(nodes2, group, 0);
*newGroup = group;
*newLastChild = nodes2.last();
}
void KisNodeManager::createQuickGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
createQuickGroupImpl(juggler, "", &parent, &above);
}
void KisNodeManager::createQuickClippingGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
KisImageSP image = m_d->view->image();
createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above);
KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace());
maskLayer->disableAlphaChannel(true);
juggler->addNode(KisNodeList() << maskLayer, parent, above);
}
void KisNodeManager::quickUngroup()
{
KisNodeSP active = activeNode();
if (!active) return;
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup");
if (parent && dynamic_cast<KisGroupLayer*>(active.data())) {
KisNodeList nodes = active->childNodes(QStringList(), KoProperties());
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, active);
juggler->removeNode(KisNodeList() << active);
} else if (parent && parent->parent()) {
KisNodeSP grandParent = parent->parent();
KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties());
KisNodeList allSelectedNodes = selectedNodes();
const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes);
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(allSelectedNodes, grandParent, parent);
if (removeParent) {
juggler->removeNode(KisNodeList() << parent);
}
}
}
void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps)
{
KisImageSP image = m_d->view->image();
KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true);
KisNodeList selectedNodes = this->selectedNodes();
if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) {
nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true);
}
if (!nodes.isEmpty()) {
slotImageRequestNodeReselection(nodes.last(), nodes);
}
}
void KisNodeManager::selectAllNodes()
{
KoProperties props;
selectLayersImpl(props, props);
}
void KisNodeManager::selectVisibleNodes()
{
KoProperties props;
props.setProperty("visible", true);
KoProperties invertedProps;
invertedProps.setProperty("visible", false);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectLockedNodes()
{
KoProperties props;
props.setProperty("locked", true);
KoProperties invertedProps;
invertedProps.setProperty("locked", false);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectInvisibleNodes()
{
KoProperties props;
props.setProperty("visible", false);
KoProperties invertedProps;
invertedProps.setProperty("visible", true);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectUnlockedNodes()
{
KoProperties props;
props.setProperty("locked", false);
KoProperties invertedProps;
invertedProps.setProperty("locked", true);
selectLayersImpl(props, invertedProps);
}
diff --git a/libs/ui/kis_node_model.cpp b/libs/ui/kis_node_model.cpp
index 275b2262b2..c5d9d29db8 100644
--- a/libs/ui/kis_node_model.cpp
+++ b/libs/ui/kis_node_model.cpp
@@ -1,646 +1,706 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_model.h"
#include <iostream>
#include <QMimeData>
#include <QBuffer>
#include <QPointer>
#include <KoColorSpaceConstants.h>
#include <klocalizedstring.h>
#include "kis_mimedata.h"
#include <kis_debug.h>
#include <kis_node.h>
#include <kis_node_progress_proxy.h>
#include <kis_image.h>
#include <kis_selection.h>
#include <kis_selection_mask.h>
#include <kis_undo_adapter.h>
#include <commands/kis_node_property_list_command.h>
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
#include <kis_projection_leaf.h>
#include <kis_shape_controller.h>
#include "kis_dummies_facade_base.h"
#include "kis_node_dummies_graph.h"
#include "kis_model_index_converter.h"
#include "kis_model_index_converter_show_all.h"
#include "kis_node_selection_adapter.h"
#include "kis_node_insertion_adapter.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include <QTimer>
struct KisNodeModel::Private
{
KisImageWSP image;
KisShapeController *shapeController = 0;
KisNodeSelectionAdapter *nodeSelectionAdapter = 0;
KisNodeInsertionAdapter *nodeInsertionAdapter = 0;
QList<KisNodeDummy*> updateQueue;
QTimer updateTimer;
KisModelIndexConverterBase *indexConverter = 0;
QPointer<KisDummiesFacadeBase> dummiesFacade = 0;
bool needFinishRemoveRows = false;
bool needFinishInsertRows = false;
bool showRootLayer = false;
bool showGlobalSelection = false;
QPersistentModelIndex activeNodeIndex;
QPointer<KisNodeDummy> parentOfRemovedNode = 0;
+
+ QSet<quintptr> dropEnabled;
};
KisNodeModel::KisNodeModel(QObject * parent)
: QAbstractItemModel(parent)
, m_d(new Private)
{
updateSettings();
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(updateSettings()));
m_d->updateTimer.setSingleShot(true);
connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue()));
}
KisNodeModel::~KisNodeModel()
{
delete m_d->indexConverter;
delete m_d;
}
KisNodeSP KisNodeModel::nodeFromIndex(const QModelIndex &index) const
{
Q_ASSERT(index.isValid());
KisNodeDummy *dummy = m_d->indexConverter->dummyFromIndex(index);
if (dummy) {
return dummy->node();
}
return 0;
}
QModelIndex KisNodeModel::indexFromNode(KisNodeSP node) const
{
KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node);
if(dummy)
return m_d->indexConverter->indexFromDummy(dummy);
return QModelIndex();
}
bool KisNodeModel::belongsToIsolatedGroup(KisImageSP image, KisNodeSP node, KisDummiesFacadeBase *dummiesFacade)
{
KisNodeSP isolatedRoot = image->isolatedModeRoot();
if (!isolatedRoot) return true;
KisNodeDummy *isolatedRootDummy =
dummiesFacade->dummyForNode(isolatedRoot);
KisNodeDummy *dummy =
dummiesFacade->dummyForNode(node);
while (dummy) {
if (dummy == isolatedRootDummy) {
return true;
}
dummy = dummy->parent();
}
return false;
}
bool KisNodeModel::belongsToIsolatedGroup(KisNodeSP node) const
{
return belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade);
}
void KisNodeModel::resetIndexConverter()
{
delete m_d->indexConverter;
m_d->indexConverter = 0;
if(m_d->dummiesFacade) {
m_d->indexConverter = createIndexConverter();
}
}
KisModelIndexConverterBase *KisNodeModel::createIndexConverter()
{
if(m_d->showRootLayer) {
return new KisModelIndexConverterShowAll(m_d->dummiesFacade, this);
} else {
return new KisModelIndexConverter(m_d->dummiesFacade, this, m_d->showGlobalSelection);
}
}
void KisNodeModel::regenerateItems(KisNodeDummy *dummy)
{
const QModelIndex &index = m_d->indexConverter->indexFromDummy(dummy);
emit dataChanged(index, index);
dummy = dummy->firstChild();
while (dummy) {
regenerateItems(dummy);
dummy = dummy->nextSibling();
}
}
void KisNodeModel::slotIsolatedModeChanged()
{
regenerateItems(m_d->dummiesFacade->rootDummy());
}
bool KisNodeModel::showGlobalSelection() const
{
KisConfig cfg;
return cfg.showGlobalSelection();
}
void KisNodeModel::setShowGlobalSelection(bool value)
{
KisConfig cfg;
cfg.setShowGlobalSelection(value);
updateSettings();
}
void KisNodeModel::updateSettings()
{
KisConfig cfg;
bool oldShowRootLayer = m_d->showRootLayer;
bool oldShowGlobalSelection = m_d->showGlobalSelection;
m_d->showRootLayer = cfg.showRootLayer();
m_d->showGlobalSelection = cfg.showGlobalSelection();
if (m_d->showRootLayer != oldShowRootLayer || m_d->showGlobalSelection != oldShowGlobalSelection) {
resetIndexConverter();
- reset();
+ beginResetModel();
+ endResetModel();
}
}
void KisNodeModel::progressPercentageChanged(int, const KisNodeSP node)
{
if(!m_d->dummiesFacade) return;
// Need to check here as the node might already be removed, but there might
// still be some signals arriving from another thread
if (m_d->dummiesFacade->hasDummyForNode(node)) {
QModelIndex index = indexFromNode(node);
emit dataChanged(index, index);
}
}
KisModelIndexConverterBase * KisNodeModel::indexConverter() const
{
return m_d->indexConverter;
}
KisDummiesFacadeBase *KisNodeModel::dummiesFacade() const
{
return m_d->dummiesFacade;
}
void KisNodeModel::connectDummy(KisNodeDummy *dummy, bool needConnect)
{
KisNodeSP node = dummy->node();
if (!node) {
qWarning() << "Dummy node has no node!" << dummy << dummy->node();
return;
}
KisNodeProgressProxy *progressProxy = node->nodeProgressProxy();
if(progressProxy) {
if(needConnect) {
connect(progressProxy, SIGNAL(percentageChanged(int,KisNodeSP)),
SLOT(progressPercentageChanged(int,KisNodeSP)));
} else {
progressProxy->disconnect(this);
}
}
}
void KisNodeModel::connectDummies(KisNodeDummy *dummy, bool needConnect)
{
connectDummy(dummy, needConnect);
dummy = dummy->firstChild();
while(dummy) {
connectDummies(dummy, needConnect);
dummy = dummy->nextSibling();
}
}
void KisNodeModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageWSP image, KisShapeController *shapeController, KisNodeSelectionAdapter *nodeSelectionAdapter, KisNodeInsertionAdapter *nodeInsertionAdapter)
{
QPointer<KisDummiesFacadeBase> oldDummiesFacade(m_d->dummiesFacade);
KisShapeController *oldShapeController = m_d->shapeController;
m_d->shapeController = shapeController;
m_d->nodeSelectionAdapter = nodeSelectionAdapter;
m_d->nodeInsertionAdapter = nodeInsertionAdapter;
if (oldDummiesFacade && m_d->image) {
m_d->image->disconnect(this);
oldDummiesFacade->disconnect(this);
connectDummies(m_d->dummiesFacade->rootDummy(), false);
}
m_d->image = image;
m_d->dummiesFacade = dummiesFacade;
m_d->parentOfRemovedNode = 0;
resetIndexConverter();
if (m_d->dummiesFacade) {
KisNodeDummy *rootDummy = m_d->dummiesFacade->rootDummy();
if (rootDummy) {
connectDummies(rootDummy, true);
}
connect(m_d->dummiesFacade, SIGNAL(sigBeginInsertDummy(KisNodeDummy*,int,QString)),
SLOT(slotBeginInsertDummy(KisNodeDummy*,int,QString)));
connect(m_d->dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)),
SLOT(slotEndInsertDummy(KisNodeDummy*)));
connect(m_d->dummiesFacade, SIGNAL(sigBeginRemoveDummy(KisNodeDummy*)),
SLOT(slotBeginRemoveDummy(KisNodeDummy*)));
connect(m_d->dummiesFacade, SIGNAL(sigEndRemoveDummy()),
SLOT(slotEndRemoveDummy()));
connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)),
SLOT(slotDummyChanged(KisNodeDummy*)));
if (m_d->image.isValid()) {
connect(m_d->image, SIGNAL(sigIsolatedModeChanged()), SLOT(slotIsolatedModeChanged()));
}
}
if (m_d->dummiesFacade != oldDummiesFacade || m_d->shapeController != oldShapeController) {
- reset();
+ beginResetModel();
+ endResetModel();
}
}
void KisNodeModel::slotBeginInsertDummy(KisNodeDummy *parent, int index, const QString &metaObjectType)
{
int row = 0;
QModelIndex parentIndex;
bool willAdd =
m_d->indexConverter->indexFromAddedDummy(parent, index,
metaObjectType,
parentIndex, row);
if(willAdd) {
beginInsertRows(parentIndex, row, row);
m_d->needFinishInsertRows = true;
}
}
void KisNodeModel::slotEndInsertDummy(KisNodeDummy *dummy)
{
if(m_d->needFinishInsertRows) {
connectDummy(dummy, true);
endInsertRows();
m_d->needFinishInsertRows = false;
}
}
void KisNodeModel::slotBeginRemoveDummy(KisNodeDummy *dummy)
{
if (!dummy) return;
// FIXME: is it really what we want?
m_d->updateTimer.stop();
m_d->updateQueue.clear();
m_d->parentOfRemovedNode = dummy->parent();
QModelIndex parentIndex;
if (m_d->parentOfRemovedNode) {
parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode);
}
QModelIndex itemIndex = m_d->indexConverter->indexFromDummy(dummy);
if (itemIndex.isValid()) {
connectDummy(dummy, false);
beginRemoveRows(parentIndex, itemIndex.row(), itemIndex.row());
m_d->needFinishRemoveRows = true;
}
}
void KisNodeModel::slotEndRemoveDummy()
{
if(m_d->needFinishRemoveRows) {
endRemoveRows();
m_d->needFinishRemoveRows = false;
}
}
void KisNodeModel::slotDummyChanged(KisNodeDummy *dummy)
{
if (!m_d->updateQueue.contains(dummy)) {
m_d->updateQueue.append(dummy);
}
m_d->updateTimer.start(1000);
}
-void addChangedIndex(const QModelIndex &index, QSet<QModelIndex> *indexes)
+void addChangedIndex(const QModelIndex &idx, QSet<QModelIndex> *indexes)
{
- if (!index.isValid() || indexes->contains(index)) return;
+ if (!idx.isValid() || indexes->contains(idx)) return;
- indexes->insert(index);
+ indexes->insert(idx);
- const int rowCount = index.model()->rowCount(index);
+ const int rowCount = idx.model()->rowCount(idx);
for (int i = 0; i < rowCount; i++) {
- addChangedIndex(index.child(i, 0), indexes);
+ addChangedIndex(idx.model()->index(i, 0, idx), indexes);
}
}
void KisNodeModel::processUpdateQueue()
{
QSet<QModelIndex> indexes;
Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) {
QModelIndex index = m_d->indexConverter->indexFromDummy(dummy);
addChangedIndex(index, &indexes);
}
Q_FOREACH (const QModelIndex &index, indexes) {
emit dataChanged(index, index);
}
m_d->updateQueue.clear();
}
QModelIndex KisNodeModel::index(int row, int col, const QModelIndex &parent) const
{
if(!m_d->dummiesFacade || !hasIndex(row, col, parent)) return QModelIndex();
QModelIndex itemIndex;
KisNodeDummy *dummy = m_d->indexConverter->dummyFromRow(row, parent);
if(dummy) {
itemIndex = m_d->indexConverter->indexFromDummy(dummy);
}
return itemIndex;
}
int KisNodeModel::rowCount(const QModelIndex &parent) const
{
if(!m_d->dummiesFacade) return 0;
return m_d->indexConverter->rowCount(parent);
}
int KisNodeModel::columnCount(const QModelIndex&) const
{
return 1;
}
QModelIndex KisNodeModel::parent(const QModelIndex &index) const
{
if(!m_d->dummiesFacade || !index.isValid()) return QModelIndex();
KisNodeDummy *dummy = m_d->indexConverter->dummyFromIndex(index);
KisNodeDummy *parentDummy = dummy->parent();
QModelIndex parentIndex;
if(parentDummy) {
parentIndex = m_d->indexConverter->indexFromDummy(parentDummy);
}
return parentIndex;
}
QVariant KisNodeModel::data(const QModelIndex &index, int role) const
{
if (!m_d->dummiesFacade || !index.isValid() || !m_d->image.isValid()) return QVariant();
KisNodeSP node = nodeFromIndex(index);
switch (role) {
case Qt::DisplayRole: return node->name();
case Qt::DecorationRole: return node->icon();
case Qt::EditRole: return node->name();
case Qt::SizeHintRole: return m_d->image->size(); // FIXME
case Qt::TextColorRole:
return belongsToIsolatedGroup(node) &&
!node->projectionLeaf()->isDroppedMask() ? QVariant() : QVariant(QColor(Qt::gray));
case Qt::FontRole: {
QFont baseFont;
if (node->projectionLeaf()->isDroppedMask()) {
baseFont.setStrikeOut(true);
}
if (m_d->activeNodeIndex == index) {
baseFont.setBold(true);
}
return baseFont;
}
case KisNodeModel::PropertiesRole: return QVariant::fromValue(node->sectionModelProperties());
case KisNodeModel::AspectRatioRole: return double(m_d->image->width()) / m_d->image->height();
case KisNodeModel::ProgressRole: {
KisNodeProgressProxy *proxy = node->nodeProgressProxy();
return proxy ? proxy->percentage() : -1;
}
case KisNodeModel::ActiveRole: {
return m_d->activeNodeIndex == index;
}
case KisNodeModel::ShouldGrayOutRole: {
return !node->visible(true);
}
case KisNodeModel::ColorLabelIndexRole: {
return node->colorLabelIndex();
}
default:
if (role >= int(KisNodeModel::BeginThumbnailRole) && belongsToIsolatedGroup(node)) {
const int maxSize = role - int(KisNodeModel::BeginThumbnailRole);
QSize size = node->extent().size();
size.scale(maxSize, maxSize, Qt::KeepAspectRatio);
if (size.width() == 0 || size.height() == 0) {
// No thumbnail can be shown if there isn't width or height...
return QVariant();
}
return node->createThumbnail(size.width(), size.height());
} else {
return QVariant();
}
}
return QVariant();
}
Qt::ItemFlags KisNodeModel::flags(const QModelIndex &index) const
{
if(!m_d->dummiesFacade || !index.isValid()) return Qt::ItemIsDropEnabled;
- Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
+ Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable;
+ if (m_d->dropEnabled.contains(index.internalId())) {
+ flags |= Qt::ItemIsDropEnabled;
+ }
return flags;
}
bool KisNodeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
+ if (role == KisNodeModel::DropEnabled) {
+ const QMimeData *mimeData = static_cast<const QMimeData*>(value.value<void*>());
+ setDropEnabled(mimeData);
+ return true;
+ }
if (role == KisNodeModel::ActiveRole || role == KisNodeModel::AlternateActiveRole) {
QModelIndex parentIndex;
if (!index.isValid() && m_d->parentOfRemovedNode && m_d->dummiesFacade && m_d->indexConverter) {
parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode);
m_d->parentOfRemovedNode = 0;
}
KisNodeSP activatedNode;
if (index.isValid() && value.toBool()) {
activatedNode = nodeFromIndex(index);
}
else if (parentIndex.isValid() && value.toBool()) {
activatedNode = nodeFromIndex(parentIndex);
}
else {
activatedNode = 0;
}
QModelIndex newActiveNode = activatedNode ? indexFromNode(activatedNode) : QModelIndex();
if (role == KisNodeModel::ActiveRole && value.toBool() &&
m_d->activeNodeIndex == newActiveNode) {
return true;
}
m_d->activeNodeIndex = newActiveNode;
if (m_d->nodeSelectionAdapter) {
m_d->nodeSelectionAdapter->setActiveNode(activatedNode);
}
if (role == KisNodeModel::AlternateActiveRole) {
emit toggleIsolateActiveNode();
}
emit dataChanged(index, index);
return true;
}
if(!m_d->dummiesFacade || !index.isValid()) return false;
bool result = true;
bool shouldUpdateRecursively = false;
KisNodeSP node = nodeFromIndex(index);
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
node->setName(value.toString());
break;
case KisNodeModel::PropertiesRole:
{
// don't record undo/redo for visibility, locked or alpha locked changes
KisBaseNode::PropertyList proplist = value.value<KisBaseNode::PropertyList>();
KisNodePropertyListCommand::setNodePropertiesNoUndo(node, m_d->image, proplist);
shouldUpdateRecursively = true;
break;
}
default:
result = false;
}
if(result) {
if (shouldUpdateRecursively) {
QSet<QModelIndex> indexes;
addChangedIndex(index, &indexes);
Q_FOREACH (const QModelIndex &index, indexes) {
emit dataChanged(index, index);
}
} else {
emit dataChanged(index, index);
}
}
return result;
}
Qt::DropActions KisNodeModel::supportedDragActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
Qt::DropActions KisNodeModel::supportedDropActions() const
{
return Qt::MoveAction | Qt::CopyAction;
}
bool KisNodeModel::hasDummiesFacade()
{
return m_d->dummiesFacade != 0;
}
QStringList KisNodeModel::mimeTypes() const
{
QStringList types;
types << QLatin1String("application/x-krita-node");
types << QLatin1String("application/x-qt-image");
return types;
}
QMimeData * KisNodeModel::mimeData(const QModelIndexList &indexes) const
{
KisNodeList nodes;
Q_FOREACH (const QModelIndex &idx, indexes) {
nodes << nodeFromIndex(idx);
}
return KisMimeData::mimeForLayers(nodes, m_d->image);
}
bool KisNodeModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
{
Q_UNUSED(column);
bool copyNode = (action == Qt::CopyAction);
KisNodeDummy *parentDummy = 0;
KisNodeDummy *aboveThisDummy = 0;
parentDummy = parent.isValid() ?
m_d->indexConverter->dummyFromIndex(parent) :
m_d->dummiesFacade->rootDummy();
if (row == -1) {
aboveThisDummy = parent.isValid() ? parentDummy->lastChild() : 0;
}
else {
aboveThisDummy = row < m_d->indexConverter->rowCount(parent) ? m_d->indexConverter->dummyFromRow(row, parent) : 0;
}
return KisMimeData::insertMimeLayers(data,
m_d->image,
m_d->shapeController,
parentDummy,
aboveThisDummy,
copyNode,
m_d->nodeInsertionAdapter);
}
+bool KisNodeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const {
+ if (parent.isValid()) {
+ // drop occured on an item. always return true as returning false will mess up
+ // QT5's drag handling (see KisNodeModel::setDropEnabled).
+ return true;
+ } else {
+ return QAbstractItemModel::canDropMimeData(data, action, row, column, parent);
+ }
+}
+
+void KisNodeModel::setDropEnabled(const QMimeData *data) {
+ // what happens here should really happen in KisNodeModel::canDropMimeData(), but QT5
+ // will mess up if an item's Qt::ItemIsDropEnabled does not match what is returned by
+ // canDropMimeData; specifically, if we set the flag, but decide in canDropMimeData()
+ // later on that an "onto" drag is not allowed, QT will display an drop indicator for
+ // insertion, but not perform any drop when the mouse is released.
+
+ // the only robust implementation seems to set all flags correctly, which is done here.
+
+ bool copyNode = false;
+ KisNodeList nodes = KisMimeData::loadNodesFast(data, m_d->image, m_d->shapeController, copyNode);
+ m_d->dropEnabled.clear();
+ updateDropEnabled(nodes);
+}
+
+void KisNodeModel::updateDropEnabled(const QList<KisNodeSP> &nodes, QModelIndex parent) {
+ for (int r = 0; r < rowCount(parent); r++) {
+ QModelIndex idx = index(r, 0, parent);
+
+ KisNodeSP target = nodeFromIndex(idx);
+
+ bool dropEnabled = true;
+ Q_FOREACH (const KisNodeSP &node, nodes) {
+ if (!target->allowAsChild(node)) {
+ dropEnabled = false;
+ break;
+ }
+ }
+ if (dropEnabled) {
+ m_d->dropEnabled.insert(idx.internalId());
+ }
+ emit dataChanged(idx, idx); // indicate to QT that flags() have changed
+
+ if (hasChildren(idx)) {
+ updateDropEnabled(nodes, idx);
+ }
+ }
+}
diff --git a/libs/ui/kis_node_model.h b/libs/ui/kis_node_model.h
index 68ee5e21b2..7d737af260 100644
--- a/libs/ui/kis_node_model.h
+++ b/libs/ui/kis_node_model.h
@@ -1,168 +1,176 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_NODE_MODEL
#define KIS_NODE_MODEL
#include "kritaui_export.h"
#include <kis_types.h>
#include <QAbstractItemModel>
#include <QIcon>
#include <QList>
#include <QString>
#include <QVariant>
class KisDummiesFacadeBase;
class KisNodeDummy;
class KisShapeController;
class KisModelIndexConverterBase;
class KisNodeSelectionAdapter;
class KisNodeInsertionAdapter;
/**
* KisNodeModel offers a Qt model-view compatible view of the node
* hierarchy. The KisNodeView displays a thumbnail and a row of
* icon properties for every document section.
*
* Note that there's a discrepancy between the krita node tree model
* and the model Qt wants to see: we hide the root node from Qt.
*
* The node model also shows an inverse view of the layer tree: we want
* the first layer to show up at the bottom.
*
* See also the Qt documentation for QAbstractItemModel.
* This class extends that interface to provide a name and set of toggle
* properties (like visible, locked, selected.)
*
*/
class KRITAUI_EXPORT KisNodeModel : public QAbstractItemModel
{
Q_OBJECT
public:
/// Extensions to Qt::ItemDataRole.
enum ItemDataRole
{
/// Whether the section is the active one
ActiveRole = Qt::UserRole + 1,
/// A list of properties the part has.
PropertiesRole,
/// The aspect ratio of the section as a floating point value: width divided by height.
AspectRatioRole,
/// Use to communicate a progress report to the section delegate on an action (a value of -1 or a QVariant() disable the progress bar
ProgressRole,
/// Speacial activation role which is emitted when the user Atl-clicks on a section
/// The item is first activated with ActiveRole, then a separate AlternateActiveRole comes
AlternateActiveRole,
// When a layer is not (recursively) visible, then it should be gayed out
ShouldGrayOutRole,
// An index of a color label associated with the node
ColorLabelIndexRole,
+ // Instruct this model to update all its items' Qt::ItemIsDropEnabled flags in order to
+ // reflect if the item allows an "onto" drop of the given QMimeData*.
+ DropEnabled,
+
/// This is to ensure that we can extend the data role in the future, since it's not possible to add a role after BeginThumbnailRole (due to "Hack")
ReservedRole = 99,
/**
* For values of BeginThumbnailRole or higher, a thumbnail of the layer of which neither dimension
* is larger than (int) value - (int) BeginThumbnailRole.
* This is a hack to work around the fact that Interview doesn't have a nice way to
* request thumbnails of arbitrary size.
*/
BeginThumbnailRole
};
public: // from QAbstractItemModel
KisNodeModel(QObject * parent);
~KisNodeModel() override;
void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageWSP image, KisShapeController *shapeController, KisNodeSelectionAdapter *nodeSelectionAdapter, KisNodeInsertionAdapter *nodeInsertionAdapter);
KisNodeSP nodeFromIndex(const QModelIndex &index) const;
QModelIndex indexFromNode(KisNodeSP node) const;
bool showGlobalSelection() const;
public Q_SLOTS:
void setShowGlobalSelection(bool value);
public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
QStringList mimeTypes() const override;
QMimeData* mimeData(const QModelIndexList & indexes) const override;
bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
+ bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override;
Qt::DropActions supportedDragActions() const override;
Qt::DropActions supportedDropActions() const override;
bool hasDummiesFacade();
static bool belongsToIsolatedGroup(KisImageSP image, KisNodeSP node, KisDummiesFacadeBase *dummiesFacade);
Q_SIGNALS:
void toggleIsolateActiveNode();
protected Q_SLOTS:
void slotBeginInsertDummy(KisNodeDummy *parent, int index, const QString &metaObjectType);
void slotEndInsertDummy(KisNodeDummy *dummy);
void slotBeginRemoveDummy(KisNodeDummy *dummy);
void slotEndRemoveDummy();
void slotDummyChanged(KisNodeDummy *dummy);
void slotIsolatedModeChanged();
void updateSettings();
void processUpdateQueue();
void progressPercentageChanged(int, const KisNodeSP);
protected:
virtual KisModelIndexConverterBase *createIndexConverter();
KisModelIndexConverterBase *indexConverter() const;
KisDummiesFacadeBase *dummiesFacade() const;
private:
friend class KisModelIndexConverter;
friend class KisModelIndexConverterShowAll;
void connectDummy(KisNodeDummy *dummy, bool needConnect);
void connectDummies(KisNodeDummy *dummy, bool needConnect);
void resetIndexConverter();
void regenerateItems(KisNodeDummy *dummy);
bool belongsToIsolatedGroup(KisNodeSP node) const;
+
+ void setDropEnabled(const QMimeData *data);
+ void updateDropEnabled(const QList<KisNodeSP> &nodes, QModelIndex parent = QModelIndex());
private:
struct Private;
Private * const m_d;
};
#endif
diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc
index cc1945ca1f..5e6991d150 100644
--- a/libs/ui/kis_paintop_box.cc
+++ b/libs/ui/kis_paintop_box.cc
@@ -1,1293 +1,1297 @@
/*
* kis_paintop_box.cc - part of KImageShop/Krayon/Krita
*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
* Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com)
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
* Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
* Copyright (c) 2014 Mohit Goyal <mohit.bits2011@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_paintop_box.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QToolButton>
#include <QPixmap>
#include <QWidgetAction>
#include <QApplication>
#include <QMenu>
#include <QTime>
#include <kis_debug.h>
#include <kactioncollection.h>
#include <kacceleratormanager.h>
#include <QKeySequence>
#include <kis_icon.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include <KoResourceSelector.h>
#include <KoResourceServerAdapter.h>
#include <KoToolManager.h>
#include <QTemporaryFile>
#include <KoColorSpaceRegistry.h>
#include <kis_paint_device.h>
#include <brushengine/kis_paintop_registry.h>
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paintop_settings.h>
#include <brushengine/kis_paintop_settings_update_proxy.h>
#include <kis_config_widget.h>
#include <kis_image.h>
#include <kis_node.h>
#include <brushengine/kis_paintop_config_widget.h>
#include <kis_action.h>
#include "kis_canvas2.h"
#include "kis_node_manager.h"
#include "KisViewManager.h"
#include "kis_canvas_resource_provider.h"
#include "kis_resource_server_provider.h"
#include "kis_favorite_resource_manager.h"
#include "kis_config.h"
#include "widgets/kis_popup_button.h"
#include "widgets/kis_tool_options_popup.h"
#include "widgets/kis_paintop_presets_popup.h"
#include "widgets/kis_tool_options_popup.h"
#include "widgets/kis_paintop_presets_chooser_popup.h"
#include "widgets/kis_workspace_chooser.h"
#include "widgets/kis_paintop_list_widget.h"
#include "widgets/kis_slider_spin_box.h"
#include "widgets/kis_cmb_composite.h"
#include "widgets/kis_widget_chooser.h"
#include "tool/kis_tool.h"
#include "kis_signals_blocker.h"
#include "kis_action_manager.h"
#include "kis_highlighted_button.h"
typedef KoResourceServerSimpleConstruction<KisPaintOpPreset, SharedPointerStoragePolicy<KisPaintOpPresetSP> > KisPaintOpPresetResourceServer;
typedef KoResourceServerAdapter<KisPaintOpPreset, SharedPointerStoragePolicy<KisPaintOpPresetSP> > KisPaintOpPresetResourceServerAdapter;
KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name)
: QWidget(parent)
, m_resourceProvider(view->resourceProvider())
, m_optionWidget(0)
, m_toolOptionsPopupButton(0)
, m_brushEditorPopupButton(0)
, m_presetSelectorPopupButton(0)
, m_toolOptionsPopup(0)
, m_viewManager(view)
, m_previousNode(0)
, m_currTabletToolID(KoInputDevice::invalid())
, m_presetsEnabled(true)
, m_blockUpdate(false)
, m_dirtyPresetsEnabled(false)
, m_eraserBrushSizeEnabled(false)
, m_eraserBrushOpacityEnabled(false)
{
Q_ASSERT(view != 0);
setObjectName(name);
KisConfig cfg;
m_dirtyPresetsEnabled = cfg.useDirtyPresets();
m_eraserBrushSizeEnabled = cfg.useEraserBrushSize();
m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity();
KAcceleratorManager::setNoAccel(this);
setWindowTitle(i18n("Painter's Toolchest"));
m_favoriteResourceManager = new KisFavoriteResourceManager(this);
KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff");
int iconsize = grp.readEntry("IconSize", 32);
if (!cfg.toolOptionsInDocker()) {
m_toolOptionsPopupButton = new KisPopupButton(this);
m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure"));
m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings"));
m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize);
}
m_brushEditorPopupButton = new KisPopupButton(this);
m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02"));
m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings"));
m_brushEditorPopupButton->setFixedSize(iconsize, iconsize);
m_presetSelectorPopupButton = new KisPopupButton(this);
m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01"));
m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset"));
m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize);
m_eraseModeButton = new KisHighlightedToolButton(this);
m_eraseModeButton->setFixedSize(iconsize, iconsize);
m_eraseModeButton->setCheckable(true);
m_eraseAction = m_viewManager->actionManager()->createAction("erase_action");
m_eraseModeButton->setDefaultAction(m_eraseAction);
m_reloadButton = new QToolButton(this);
m_reloadButton->setFixedSize(iconsize, iconsize);
m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action");
m_reloadButton->setDefaultAction(m_reloadAction);
m_alphaLockButton = new KisHighlightedToolButton(this);
m_alphaLockButton->setFixedSize(iconsize, iconsize);
m_alphaLockButton->setCheckable(true);
KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha");
m_alphaLockButton->setDefaultAction(alphaLockAction);
// pen pressure
m_disablePressureButton = new KisHighlightedToolButton(this);
m_disablePressureButton->setFixedSize(iconsize, iconsize);
m_disablePressureButton->setCheckable(true);
m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure");
m_disablePressureButton->setDefaultAction(m_disablePressureAction);
// horizontal and vertical mirror toolbar buttons
// mirror tool options for the X Mirror
QMenu *toolbarMenuXMirror = new QMenu();
hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations");
toolbarMenuXMirror->addAction(hideCanvasDecorationsX);
lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock");
toolbarMenuXMirror->addAction(lockActionX);
moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter");
toolbarMenuXMirror->addAction(moveToCenterActionX);
// mirror tool options for the Y Mirror
QMenu *toolbarMenuYMirror = new QMenu();
hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations");
toolbarMenuYMirror->addAction(hideCanvasDecorationsY);
lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock");
toolbarMenuYMirror->addAction(lockActionY);
moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter");
toolbarMenuYMirror->addAction(moveToCenterActionY);
// create horizontal and vertical mirror buttons
m_hMirrorButton = new KisHighlightedToolButton(this);
int menuPadding = 10;
m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize);
m_hMirrorButton->setCheckable(true);
m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action");
m_hMirrorButton->setDefaultAction(m_hMirrorAction);
m_hMirrorButton->setMenu(toolbarMenuXMirror);
m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup);
m_vMirrorButton = new KisHighlightedToolButton(this);
m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize);
m_vMirrorButton->setCheckable(true);
m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action");
m_vMirrorButton->setDefaultAction(m_vMirrorAction);
m_vMirrorButton->setMenu(toolbarMenuYMirror);
m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup);
// add connections for horizontal and mirrror buttons
connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool)));
connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool)));
connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX()));
connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY()));
connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool)));
connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool)));
const bool sliderLabels = cfg.sliderLabels();
int sliderWidth;
if (sliderLabels) {
sliderWidth = 150 * logicalDpiX() / 96;
}
else {
sliderWidth = 120 * logicalDpiX() / 96;
}
for (int i = 0; i < 3; ++i) {
m_sliderChooser[i] = new KisWidgetChooser(i + 1);
KisDoubleSliderSpinBox* slOpacity;
KisDoubleSliderSpinBox* slFlow;
KisDoubleSliderSpinBox* slSize;
if (sliderLabels) {
slOpacity = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("opacity");
slFlow = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("flow");
slSize = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("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<KisDoubleSliderSpinBox>("opacity", i18n("Opacity:"));
slFlow = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("flow", i18n("Flow:"));
slSize = m_sliderChooser[i]->addWidget<KisDoubleSliderSpinBox>("size", i18n("Size:"));
}
slOpacity->setRange(0.0, 1.0, 2);
slOpacity->setValue(1.0);
slOpacity->setSingleStep(0.05);
slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width()));
slOpacity->setFixedHeight(iconsize);
slOpacity->setBlockUpdateSignalOnDrag(true);
slFlow->setRange(0.0, 1.0, 2);
slFlow->setValue(1.0);
slFlow->setSingleStep(0.05);
slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width()));
slFlow->setFixedHeight(iconsize);
slFlow->setBlockUpdateSignalOnDrag(true);
slSize->setRange(0, cfg.readEntry("maximumBrushSize", 1000), 2);
slSize->setValue(100);
slSize->setSingleStep(1);
slSize->setExponentRatio(3.0);
slSize->setSuffix(i18n(" px"));
slSize->setMinimumWidth(qMax(sliderWidth, slSize->sizeHint().width()));
slSize->setFixedHeight(iconsize);
slSize->setBlockUpdateSignalOnDrag(true);
m_sliderChooser[i]->chooseWidget(cfg.toolbarSlider(i + 1));
}
m_cmbCompositeOp = new KisCompositeOpComboBox();
m_cmbCompositeOp->setFixedHeight(iconsize);
Q_FOREACH (KisAction * a, m_cmbCompositeOp->blendmodeActions()) {
m_viewManager->actionManager()->addAction(a->text(), a);
}
m_workspaceWidget = new KisPopupButton(this);
m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose"));
m_workspaceWidget->setToolTip(i18n("Choose workspace"));
m_workspaceWidget->setFixedSize(iconsize, iconsize);
m_workspaceWidget->setPopupWidget(new KisWorkspaceChooser(view));
QHBoxLayout* baseLayout = new QHBoxLayout(this);
m_paintopWidget = new QWidget(this);
baseLayout->addWidget(m_paintopWidget);
baseLayout->setSpacing(4);
baseLayout->setContentsMargins(0, 0, 0, 0);
m_layout = new QHBoxLayout(m_paintopWidget);
if (!cfg.toolOptionsInDocker()) {
m_layout->addWidget(m_toolOptionsPopupButton);
}
m_layout->addWidget(m_brushEditorPopupButton);
m_layout->addWidget(m_presetSelectorPopupButton);
m_layout->setSpacing(4);
m_layout->setContentsMargins(0, 0, 0, 0);
QWidget* compositeActions = new QWidget(this);
QHBoxLayout* compositeLayout = new QHBoxLayout(compositeActions);
compositeLayout->addWidget(m_cmbCompositeOp);
compositeLayout->addWidget(m_eraseModeButton);
compositeLayout->addWidget(m_alphaLockButton);
compositeLayout->setSpacing(4);
compositeLayout->setContentsMargins(0, 0, 0, 0);
compositeLayout->addWidget(m_reloadButton);
QWidgetAction * action;
action = new QWidgetAction(this);
view->actionCollection()->addAction("composite_actions", action);
action->setText(i18n("Brush composite"));
action->setDefaultWidget(compositeActions);
QWidget* compositePressure = new QWidget(this);
QHBoxLayout* pressureLayout = new QHBoxLayout(compositePressure);
pressureLayout->addWidget(m_disablePressureButton);
pressureLayout->setSpacing(4);
pressureLayout->setContentsMargins(0, 0, 0, 0);
action = new QWidgetAction(this);
view->actionCollection()->addAction("pressure_action", action);
action->setText(i18n("Pressure usage (small button)"));
action->setDefaultWidget(compositePressure);
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("brushslider1", action);
view->actionCollection()->addAction("brushslider1", action);
action->setDefaultWidget(m_sliderChooser[0]);
connect(action, SIGNAL(triggered()), m_sliderChooser[0], SLOT(showPopupWidget()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[0], SLOT(updateThemedIcons()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("brushslider2", action);
view->actionCollection()->addAction("brushslider2", action);
action->setDefaultWidget(m_sliderChooser[1]);
connect(action, SIGNAL(triggered()), m_sliderChooser[1], SLOT(showPopupWidget()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[1], SLOT(updateThemedIcons()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("brushslider3", action);
view->actionCollection()->addAction("brushslider3", action);
action->setDefaultWidget(m_sliderChooser[2]);
connect(action, SIGNAL(triggered()), m_sliderChooser[2], SLOT(showPopupWidget()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[2], SLOT(updateThemedIcons()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("next_favorite_preset", action);
view->actionCollection()->addAction("next_favorite_preset", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotNextFavoritePreset()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("previous_favorite_preset", action);
view->actionCollection()->addAction("previous_favorite_preset", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotPreviousFavoritePreset()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("previous_preset", action);
view->actionCollection()->addAction("previous_preset", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotSwitchToPreviousPreset()));
if (!cfg.toolOptionsInDocker()) {
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("show_tool_options", action);
view->actionCollection()->addAction("show_tool_options", action);
connect(action, SIGNAL(triggered()), m_toolOptionsPopupButton, SLOT(showPopupWidget()));
}
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("show_brush_editor", action);
view->actionCollection()->addAction("show_brush_editor", action);
connect(action, SIGNAL(triggered()), m_brushEditorPopupButton, SLOT(showPopupWidget()));
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("show_brush_presets", action);
view->actionCollection()->addAction("show_brush_presets", action);
connect(action, SIGNAL(triggered()), m_presetSelectorPopupButton, SLOT(showPopupWidget()));
QWidget* mirrorActions = new QWidget(this);
QHBoxLayout* mirrorLayout = new QHBoxLayout(mirrorActions);
mirrorLayout->addWidget(m_hMirrorButton);
mirrorLayout->addWidget(m_vMirrorButton);
mirrorLayout->setSpacing(4);
mirrorLayout->setContentsMargins(0, 0, 0, 0);
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("mirror_actions", action);
action->setDefaultWidget(mirrorActions);
view->actionCollection()->addAction("mirror_actions", action);
action = new QWidgetAction(this);
KisActionRegistry::instance()->propertizeAction("workspaces", action);
view->actionCollection()->addAction("workspaces", action);
action->setDefaultWidget(m_workspaceWidget);
if (!cfg.toolOptionsInDocker()) {
m_toolOptionsPopup = new KisToolOptionsPopup();
m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup);
m_toolOptionsPopup->switchDetached(false);
}
m_savePresetWidget = new KisPresetSaveWidget(this);
m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider, m_favoriteResourceManager, m_savePresetWidget);
m_brushEditorPopupButton->setPopupWidget(m_presetsPopup);
m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor"));
connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup()));
connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons()));
m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup();
m_presetsChooserPopup->setMinimumHeight(550);
m_presetsChooserPopup->setMinimumWidth(450);
m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup);
m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id();
slotNodeChanged(view->activeNode());
// Get all the paintops
QList<QString> keys = KisPaintOpRegistry::instance()->keys();
QList<KisPaintOpFactory*> factoryList;
Q_FOREACH (const QString & paintopId, keys) {
factoryList.append(KisPaintOpRegistry::instance()->get(paintopId));
}
m_presetsPopup->setPaintOpList(factoryList);
connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString)));
connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset()));
connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResource*)), SLOT(resourceSelected(KoResource*)));
connect(m_presetsPopup , SIGNAL(reloadPresetClicked()) , SLOT(slotReloadPreset()));
connect(m_presetsPopup , SIGNAL(dirtyPresetToggled(bool)) , SLOT(slotDirtyPresetToggled(bool)));
connect(m_presetsPopup , SIGNAL(eraserBrushSizeToggled(bool)) , SLOT(slotEraserBrushSizeToggled(bool)));
connect(m_presetsPopup , SIGNAL(eraserBrushOpacityToggled(bool)) , SLOT(slotEraserBrushOpacityToggled(bool)));
connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResource*)) , SLOT(resourceSelected(KoResource*)));
connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResource*)) , SLOT(resourceSelected(KoResource*)));
connect(m_resourceProvider , SIGNAL(sigNodeChanged(const KisNodeSP)) , SLOT(slotNodeChanged(const KisNodeSP)));
connect(m_cmbCompositeOp , SIGNAL(currentIndexChanged(int)) , SLOT(slotSetCompositeMode(int)));
connect(m_eraseAction , SIGNAL(toggled(bool)) , SLOT(slotToggleEraseMode(bool)));
connect(alphaLockAction , SIGNAL(toggled(bool)) , SLOT(slotToggleAlphaLockMode(bool)));
connect(m_disablePressureAction , SIGNAL(toggled(bool)) , SLOT(slotDisablePressureMode(bool)));
m_disablePressureAction->setChecked(true);
connect(m_hMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotHorizontalMirrorChanged(bool)));
connect(m_vMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotVerticalMirrorChanged(bool)));
connect(m_reloadAction , SIGNAL(triggered()) , SLOT(slotReloadPreset()));
connect(m_sliderChooser[0]->getWidget<KisDoubleSliderSpinBox>("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed()));
connect(m_sliderChooser[0]->getWidget<KisDoubleSliderSpinBox>("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed()));
connect(m_sliderChooser[0]->getWidget<KisDoubleSliderSpinBox>("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed()));
connect(m_sliderChooser[1]->getWidget<KisDoubleSliderSpinBox>("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed()));
connect(m_sliderChooser[1]->getWidget<KisDoubleSliderSpinBox>("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed()));
connect(m_sliderChooser[1]->getWidget<KisDoubleSliderSpinBox>("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed()));
connect(m_sliderChooser[2]->getWidget<KisDoubleSliderSpinBox>("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed()));
connect(m_sliderChooser[2]->getWidget<KisDoubleSliderSpinBox>("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed()));
connect(m_sliderChooser[2]->getWidget<KisDoubleSliderSpinBox>("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed()));
//Needed to connect canvas to favorite resource manager
connect(m_viewManager->resourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), SLOT(slotUnsetEraseMode()));
connect(m_resourceProvider, SIGNAL(sigFGColorUsed(KoColor)), m_favoriteResourceManager, SLOT(slotAddRecentColor(KoColor)));
connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotChangeFGColorSelector(KoColor)));
connect(m_resourceProvider, SIGNAL(sigBGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotSetBGColor(KoColor)));
// cold initialization
m_favoriteResourceManager->slotChangeFGColorSelector(m_resourceProvider->fgColor());
m_favoriteResourceManager->slotSetBGColor(m_resourceProvider->bgColor());
connect(m_favoriteResourceManager, SIGNAL(sigSetFGColor(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor)));
connect(m_favoriteResourceManager, SIGNAL(sigSetBGColor(KoColor)), m_resourceProvider, SLOT(slotSetBGColor(KoColor)));
connect(m_favoriteResourceManager, SIGNAL(sigEnableChangeColor(bool)), m_resourceProvider, SLOT(slotResetEnableFGChange(bool)));
connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon()));
slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice());
}
KisPaintopBox::~KisPaintopBox()
{
KisConfig cfg;
QMapIterator<TabletToolID, TabletToolData> iter(m_tabletToolMap);
while (iter.hasNext()) {
iter.next();
//qDebug() << "Writing last used preset for" << iter.key().pointer << iter.key().uniqueID << iter.value().preset->name();
if ((iter.key().pointer) == QTabletEvent::Eraser) {
cfg.writeEntry(QString("LastEraser_%1").arg(iter.key().uniqueID) , iter.value().preset->name());
}
else {
cfg.writeEntry(QString("LastPreset_%1").arg(iter.key().uniqueID) , iter.value().preset->name());
}
}
// Do not delete the widget, since it it is global to the application, not owned by the view
m_presetsPopup->setPaintOpSettingsWidget(0);
qDeleteAll(m_paintopOptionWidgets);
delete m_favoriteResourceManager;
for (int i = 0; i < 3; ++i) {
delete m_sliderChooser[i];
}
}
void KisPaintopBox::restoreResource(KoResource* resource)
{
KisPaintOpPreset* preset = dynamic_cast<KisPaintOpPreset*>(resource);
//qDebug() << "restoreResource" << resource << preset;
if (preset) {
setCurrentPaintop(preset);
m_presetsPopup->setPresetImage(preset->image());
m_presetsPopup->resourceSelected(resource);
}
}
void KisPaintopBox::newOptionWidgets(const QList<QPointer<QWidget> > &optionWidgetList)
{
if (m_toolOptionsPopup) {
m_toolOptionsPopup->newOptionWidgets(optionWidgetList);
}
}
void KisPaintopBox::resourceSelected(KoResource* resource)
{
KisPaintOpPreset* preset = dynamic_cast<KisPaintOpPreset*>(resource);
if (preset && preset != m_resourceProvider->currentPreset()) {
if (!preset->settings()->isLoadable())
return;
if (!m_dirtyPresetsEnabled) {
KisSignalsBlocker blocker(m_optionWidget);
if (!preset->load()) {
warnKrita << "failed to load the preset.";
}
}
//qDebug() << "resourceSelected" << resource->name();
setCurrentPaintop(preset);
m_presetsPopup->setPresetImage(preset->image());
m_presetsPopup->resourceSelected(resource);
}
}
void KisPaintopBox::setCurrentPaintop(const KoID& paintop)
{
KisPaintOpPresetSP preset = activePreset(paintop);
Q_ASSERT(preset && preset->settings());
//qDebug() << "setCurrentPaintop();" << paintop << preset;
setCurrentPaintop(preset);
}
void KisPaintopBox::setCurrentPaintop(KisPaintOpPresetSP preset)
{
//qDebug() << "setCurrentPaintop(); " << preset->name();
if (preset == m_resourceProvider->currentPreset()) {
if (preset == m_tabletToolMap[m_currTabletToolID].preset) {
return;
}
}
Q_ASSERT(preset);
const KoID& paintop = preset->paintOp();
m_presetConnections.clear();
if (m_resourceProvider->currentPreset()) {
m_resourceProvider->setPreviousPaintOpPreset(m_resourceProvider->currentPreset());
if (m_optionWidget) {
m_optionWidget->hide();
}
}
if (!m_paintopOptionWidgets.contains(paintop))
m_paintopOptionWidgets[paintop] = KisPaintOpRegistry::instance()->get(paintop.id())->createConfigWidget(this);
m_optionWidget = m_paintopOptionWidgets[paintop];
KisSignalsBlocker b(m_optionWidget);
preset->setOptionsWidget(m_optionWidget);
m_optionWidget->setImage(m_viewManager->image());
m_optionWidget->setNode(m_viewManager->activeNode());
m_presetsPopup->setPaintOpSettingsWidget(m_optionWidget);
m_resourceProvider->setPaintOpPreset(preset);
Q_ASSERT(m_optionWidget && m_presetSelectorPopupButton);
m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotGuiChangedCurrentPreset()));
m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigSaveLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP)));
m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigDropLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotDropLockedOption(KisPropertiesConfigurationSP)));
// load the current brush engine icon for the brush editor toolbar button
KisPaintOpFactory* paintOp = KisPaintOpRegistry::instance()->get(paintop.id());
QString pixFilename = KoResourcePaths::findResource("kis_images", paintOp->pixmap());
m_brushEditorPopupButton->setIcon(QIcon(pixFilename));
m_presetsPopup->setCurrentPaintOpId(paintop.id());
////qDebug() << "\tsetting the new preset for" << m_currTabletToolID.uniqueID << "to" << preset->name();
m_paintOpPresetMap[m_resourceProvider->currentPreset()->paintOp()] = preset;
m_tabletToolMap[m_currTabletToolID].preset = preset;
m_tabletToolMap[m_currTabletToolID].paintOpID = preset->paintOp();
if (m_presetsPopup->currentPaintOpId() != paintop.id()) {
// Must change the paintop as the current one is not supported
// by the new colorspace.
dbgKrita << "current paintop " << paintop.name() << " was not set, not supported by colorspace";
}
}
void KisPaintopBox::slotUpdateOptionsWidgetPopup()
{
KisPaintOpPresetSP preset = m_resourceProvider->currentPreset();
KIS_SAFE_ASSERT_RECOVER_RETURN(preset);
KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget);
m_optionWidget->setConfigurationSafe(preset->settings());
m_presetsPopup->resourceSelected(preset.data());
m_presetsPopup->updateViewSettings();
// the m_viewManager->image() is set earlier, but the reference will be missing when the stamp button is pressed
// need to later do some research on how and when we should be using weak shared pointers (WSP) that creates this situation
m_optionWidget->setImage(m_viewManager->image());
}
KisPaintOpPresetSP KisPaintopBox::defaultPreset(const KoID& paintOp)
{
QString defaultName = paintOp.id() + ".kpp";
QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName);
KisPaintOpPresetSP preset = new KisPaintOpPreset(path);
if (!preset->load()) {
preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp);
}
Q_ASSERT(preset);
Q_ASSERT(preset->valid());
return preset;
}
KisPaintOpPresetSP KisPaintopBox::activePreset(const KoID& paintOp)
{
if (m_paintOpPresetMap[paintOp] == 0) {
m_paintOpPresetMap[paintOp] = defaultPreset(paintOp);
}
return m_paintOpPresetMap[paintOp];
}
void KisPaintopBox::updateCompositeOp(QString compositeOpID)
{
if (!m_optionWidget) return;
KisSignalsBlocker blocker(m_optionWidget);
KisNodeSP node = m_resourceProvider->currentNode();
if (node && node->paintDevice()) {
if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID))
compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id();
{
KisSignalsBlocker b1(m_cmbCompositeOp);
m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID));
}
if (compositeOpID != m_currCompositeOpID) {
m_currCompositeOpID = compositeOpID;
}
if (compositeOpID == COMPOSITE_ERASE) {
m_eraseModeButton->setChecked(true);
}
else {
m_eraseModeButton->setChecked(false);
}
}
}
void KisPaintopBox::setWidgetState(int flags)
{
if (flags & (ENABLE_COMPOSITEOP | DISABLE_COMPOSITEOP)) {
m_cmbCompositeOp->setEnabled(flags & ENABLE_COMPOSITEOP);
m_eraseModeButton->setEnabled(flags & ENABLE_COMPOSITEOP);
}
if (flags & (ENABLE_PRESETS | DISABLE_PRESETS)) {
m_presetSelectorPopupButton->setEnabled(flags & ENABLE_PRESETS);
m_brushEditorPopupButton->setEnabled(flags & ENABLE_PRESETS);
}
for (int i = 0; i < 3; ++i) {
if (flags & (ENABLE_OPACITY | DISABLE_OPACITY))
m_sliderChooser[i]->getWidget("opacity")->setEnabled(flags & ENABLE_OPACITY);
if (flags & (ENABLE_FLOW | DISABLE_FLOW))
m_sliderChooser[i]->getWidget("flow")->setEnabled(flags & ENABLE_FLOW);
if (flags & (ENABLE_SIZE | DISABLE_SIZE))
m_sliderChooser[i]->getWidget("size")->setEnabled(flags & ENABLE_SIZE);
}
}
void KisPaintopBox::setSliderValue(const QString& sliderID, qreal value)
{
for (int i = 0; i < 3; ++i) {
KisDoubleSliderSpinBox* slider = m_sliderChooser[i]->getWidget<KisDoubleSliderSpinBox>(sliderID);
KisSignalsBlocker b(slider);
slider->setValue(value);
}
}
void KisPaintopBox::slotSetPaintop(const QString& paintOpId)
{
if (KisPaintOpRegistry::instance()->get(paintOpId) != 0) {
KoID id(paintOpId, KisPaintOpRegistry::instance()->get(paintOpId)->name());
//qDebug() << "slotsetpaintop" << id;
setCurrentPaintop(id);
}
}
void KisPaintopBox::slotInputDeviceChanged(const KoInputDevice& inputDevice)
{
TabletToolMap::iterator toolData = m_tabletToolMap.find(inputDevice);
//qDebug() << "slotInputDeviceChanged()" << inputDevice.device() << inputDevice.uniqueTabletId();
m_currTabletToolID = TabletToolID(inputDevice);
if (toolData == m_tabletToolMap.end()) {
KisConfig cfg;
KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false);
KisPaintOpPresetSP preset;
if (inputDevice.pointer() == QTabletEvent::Eraser) {
preset = rserver->resourceByName(cfg.readEntry<QString>(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), "Eraser_circle"));
}
else {
preset = rserver->resourceByName(cfg.readEntry<QString>(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), "Basic_tip_default"));
//if (preset)
//qDebug() << "found stored preset " << preset->name() << "for" << inputDevice.uniqueTabletId();
//else
//qDebug() << "no preset fcound for" << inputDevice.uniqueTabletId();
}
if (!preset) {
preset = rserver->resourceByName("Basic_tip_default");
}
if (preset) {
//qDebug() << "inputdevicechanged 1" << preset;
setCurrentPaintop(preset);
}
}
else {
if (toolData->preset) {
//qDebug() << "inputdevicechanged 2" << toolData->preset;
setCurrentPaintop(toolData->preset);
}
else {
//qDebug() << "inputdevicechanged 3" << toolData->paintOpID;
setCurrentPaintop(toolData->paintOpID);
}
}
}
void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value)
{
if (m_viewManager) {
sender()->blockSignals(true);
KisPaintOpPresetSP preset = m_viewManager->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) {
QString compositeOp = preset->settings()->getString("CompositeOp");
updateCompositeOp(compositeOp);
resourceSelected(preset.data());
}
/**
* Update currently selected preset in both the popup widgets
*/
m_presetsChooserPopup->canvasResourceChanged(preset);
m_presetsPopup->currentPresetChanged(preset);
if (key == KisCanvasResourceProvider::CurrentCompositeOp) {
if (m_resourceProvider->currentCompositeOp() != m_currCompositeOpID) {
updateCompositeOp(m_resourceProvider->currentCompositeOp());
}
}
if (key == KisCanvasResourceProvider::Size) {
setSliderValue("size", m_resourceProvider->size());
}
if (key == KisCanvasResourceProvider::Opacity) {
setSliderValue("opacity", m_resourceProvider->opacity());
}
if (key == KisCanvasResourceProvider::Flow) {
setSliderValue("flow", m_resourceProvider->flow());
}
if (key == KisCanvasResourceProvider::EraserMode) {
m_eraseAction->setChecked(value.toBool());
}
if (key == KisCanvasResourceProvider::DisablePressure) {
m_disablePressureAction->setChecked(value.toBool());
}
sender()->blockSignals(false);
}
}
void KisPaintopBox::slotUpdatePreset()
{
if (!m_resourceProvider->currentPreset()) return;
// block updates of avoid some over updating of the option widget
m_blockUpdate = true;
setSliderValue("size", m_resourceProvider->size());
{
qreal opacity = m_resourceProvider->currentPreset()->settings()->paintOpOpacity();
m_resourceProvider->setOpacity(opacity);
setSliderValue("opacity", opacity);
setWidgetState(ENABLE_OPACITY);
}
{
setSliderValue("flow", m_resourceProvider->currentPreset()->settings()->paintOpFlow());
setWidgetState(ENABLE_FLOW);
}
{
updateCompositeOp(m_resourceProvider->currentPreset()->settings()->paintOpCompositeOp());
setWidgetState(ENABLE_COMPOSITEOP);
}
m_blockUpdate = false;
}
void KisPaintopBox::slotSetupDefaultPreset()
{
KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp());
preset->setOptionsWidget(m_optionWidget);
m_resourceProvider->setPaintOpPreset(preset);
+
+ // tell the brush editor that the resource has changed
+ // so it can update everything
+ m_presetsPopup->resourceSelected(preset.data());
}
void KisPaintopBox::slotNodeChanged(const KisNodeSP node)
{
if (m_previousNode.isValid() && m_previousNode->paintDevice())
disconnect(m_previousNode->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*)));
// Reconnect colorspace change of node
if (node && node->paintDevice()) {
connect(node->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*)));
m_resourceProvider->setCurrentCompositeOp(m_currCompositeOpID);
m_previousNode = node;
slotColorSpaceChanged(node->colorSpace());
}
if (m_optionWidget) {
m_optionWidget->setNode(node);
}
}
void KisPaintopBox::slotColorSpaceChanged(const KoColorSpace* colorSpace)
{
m_cmbCompositeOp->validate(colorSpace);
}
void KisPaintopBox::slotToggleEraseMode(bool checked)
{
const bool oldEraserMode = m_resourceProvider->eraserMode();
m_resourceProvider->setEraserMode(checked);
if (oldEraserMode != checked && m_eraserBrushSizeEnabled) {
const qreal currentSize = m_resourceProvider->size();
KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings();
// remember brush size. set the eraser size to the normal brush size if not set
if (checked) {
settings->setSavedBrushSize(currentSize);
if (qFuzzyIsNull(settings->savedEraserSize())) {
settings->setSavedEraserSize(currentSize);
}
} else {
settings->setSavedEraserSize(currentSize);
if (qFuzzyIsNull(settings->savedBrushSize())) {
settings->setSavedBrushSize(currentSize);
}
}
//update value in UI (this is the main place the value is 'stored' in memory)
qreal newSize = checked ? settings->savedEraserSize() : settings->savedBrushSize();
m_resourceProvider->setSize(newSize);
}
if (oldEraserMode != checked && m_eraserBrushOpacityEnabled) {
const qreal currentOpacity = m_resourceProvider->opacity();
KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings();
// remember brush opacity. set the eraser opacity to the normal brush opacity if not set
if (checked) {
settings->setSavedBrushOpacity(currentOpacity);
if (qFuzzyIsNull(settings->savedEraserOpacity())) {
settings->setSavedEraserOpacity(currentOpacity);
}
} else {
settings->setSavedEraserOpacity(currentOpacity);
if (qFuzzyIsNull(settings->savedBrushOpacity())) {
settings->setSavedBrushOpacity(currentOpacity);
}
}
//update value in UI (this is the main place the value is 'stored' in memory)
qreal newOpacity = checked ? settings->savedEraserOpacity() : settings->savedBrushOpacity();
m_resourceProvider->setOpacity(newOpacity);
}
}
void KisPaintopBox::slotSetCompositeMode(int index)
{
Q_UNUSED(index);
QString compositeOp = m_cmbCompositeOp->selectedCompositeOp().id();
m_resourceProvider->setCurrentCompositeOp(compositeOp);
}
void KisPaintopBox::slotHorizontalMirrorChanged(bool value)
{
m_resourceProvider->setMirrorHorizontal(value);
}
void KisPaintopBox::slotVerticalMirrorChanged(bool value)
{
m_resourceProvider->setMirrorVertical(value);
}
void KisPaintopBox::sliderChanged(int n)
{
if (!m_optionWidget) // widget will not exist if the are no documents open
return;
KisSignalsBlocker blocker(m_optionWidget);
qreal opacity = m_sliderChooser[n]->getWidget<KisDoubleSliderSpinBox>("opacity")->value();
qreal flow = m_sliderChooser[n]->getWidget<KisDoubleSliderSpinBox>("flow")->value();
qreal size = m_sliderChooser[n]->getWidget<KisDoubleSliderSpinBox>("size")->value();
setSliderValue("opacity", opacity);
setSliderValue("flow" , flow);
setSliderValue("size" , size);
if (m_presetsEnabled) {
// IMPORTANT: set the PaintOp size before setting the other properties
// it wont work the other way
// TODO: why?!
m_resourceProvider->setSize(size);
m_resourceProvider->setOpacity(opacity);
m_resourceProvider->setFlow(flow);
KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(m_resourceProvider->currentPreset()->settings());
propertiesProxy->setProperty("OpacityValue", opacity);
propertiesProxy->setProperty("FlowValue", flow);
m_optionWidget->setConfigurationSafe(m_resourceProvider->currentPreset()->settings().data());
} else {
m_resourceProvider->setOpacity(opacity);
}
m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data());
}
void KisPaintopBox::slotSlider1Changed()
{
sliderChanged(0);
}
void KisPaintopBox::slotSlider2Changed()
{
sliderChanged(1);
}
void KisPaintopBox::slotSlider3Changed()
{
sliderChanged(2);
}
void KisPaintopBox::slotToolChanged(KoCanvasController* canvas, int toolId)
{
Q_UNUSED(canvas);
Q_UNUSED(toolId);
if (!m_viewManager->canvasBase()) return;
QString id = KoToolManager::instance()->activeToolId();
KisTool* tool = dynamic_cast<KisTool*>(KoToolManager::instance()->toolById(m_viewManager->canvasBase(), id));
if (tool) {
int flags = tool->flags();
if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) {
setWidgetState(ENABLE_COMPOSITEOP | ENABLE_OPACITY);
} else {
setWidgetState(DISABLE_COMPOSITEOP | DISABLE_OPACITY);
}
if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) {
setWidgetState(ENABLE_PRESETS);
slotUpdatePreset();
m_presetsEnabled = true;
} else {
setWidgetState(DISABLE_PRESETS);
m_presetsEnabled = false;
}
if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) {
setWidgetState(ENABLE_SIZE | ENABLE_FLOW);
} else {
setWidgetState(DISABLE_SIZE | DISABLE_FLOW);
}
} else setWidgetState(DISABLE_ALL);
}
void KisPaintopBox::slotPreviousFavoritePreset()
{
if (!m_favoriteResourceManager) return;
QVector<KisPaintOpPresetSP> presets = m_favoriteResourceManager->favoritePresetList();
for (int i=0; i < presets.size(); ++i) {
if (m_resourceProvider->currentPreset() &&
m_resourceProvider->currentPreset()->name() == presets[i]->name()) {
if (i > 0) {
m_favoriteResourceManager->slotChangeActivePaintop(i - 1);
} else {
m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1);
}
//floating message should have least 2 lines, otherwise
//preset thumbnail will be too small to distinguish
//(because size of image on floating message depends on amount of lines in msg)
m_viewManager->showFloatingMessage(
i18n("%1\nselected",
m_resourceProvider->currentPreset()->name()),
QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image())));
return;
}
}
}
void KisPaintopBox::slotNextFavoritePreset()
{
if (!m_favoriteResourceManager) return;
QVector<KisPaintOpPresetSP> presets = m_favoriteResourceManager->favoritePresetList();
for(int i = 0; i < presets.size(); ++i) {
if (m_resourceProvider->currentPreset()->name() == presets[i]->name()) {
if (i < m_favoriteResourceManager->numFavoritePresets() - 1) {
m_favoriteResourceManager->slotChangeActivePaintop(i + 1);
} else {
m_favoriteResourceManager->slotChangeActivePaintop(0);
}
m_viewManager->showFloatingMessage(
i18n("%1\nselected",
m_resourceProvider->currentPreset()->name()),
QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image())));
return;
}
}
}
void KisPaintopBox::slotSwitchToPreviousPreset()
{
if (m_resourceProvider->previousPreset()) {
//qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset();
setCurrentPaintop(m_resourceProvider->previousPreset());
m_viewManager->showFloatingMessage(
i18n("%1\nselected",
m_resourceProvider->currentPreset()->name()),
QIcon(QPixmap::fromImage(m_resourceProvider->currentPreset()->image())));
}
}
void KisPaintopBox::slotUnsetEraseMode()
{
m_eraseAction->setChecked(false);
}
void KisPaintopBox::slotToggleAlphaLockMode(bool checked)
{
if (checked) {
m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked"));
} else {
m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked"));
}
m_resourceProvider->setGlobalAlphaLock(checked);
}
void KisPaintopBox::slotDisablePressureMode(bool checked)
{
if (checked) {
m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure"));
} else {
m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked"));
}
m_resourceProvider->setDisablePressure(checked);
}
void KisPaintopBox::slotReloadPreset()
{
KisSignalsBlocker blocker(m_optionWidget);
//Here using the name and fetching the preset from the server was the only way the load was working. Otherwise it was not loading.
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
KisPaintOpPresetSP preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name());
if (preset) {
preset->load();
}
}
void KisPaintopBox::slotGuiChangedCurrentPreset() // Called only when UI is changed and not when preset is changed
{
KisPaintOpPresetSP preset = m_resourceProvider->currentPreset();
{
/**
* Here we postpone all the settings updates events until thye entire writing
* operation will be finished. As soon as it is finished, the updates will be
* emitted happily (if there were any).
*/
KisPaintOpPreset::UpdatedPostponer postponer(preset.data());
m_optionWidget->writeConfigurationSafe(const_cast<KisPaintOpSettings*>(preset->settings().data()));
}
// we should also update the preset strip to update the status of the "dirty" mark
m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data());
// TODO!!!!!!!!
//m_presetsPopup->updateViewSettings();
}
void KisPaintopBox::slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p)
{
QMapIterator<QString, QVariant> i(p->getProperties());
while (i.hasNext()) {
i.next();
m_resourceProvider->currentPreset()->settings()->setProperty(i.key(), QVariant(i.value()));
if (m_resourceProvider->currentPreset()->settings()->hasProperty(i.key() + "_previous")) {
m_resourceProvider->currentPreset()->settings()->removeProperty(i.key() + "_previous");
}
}
slotGuiChangedCurrentPreset();
}
void KisPaintopBox::slotDropLockedOption(KisPropertiesConfigurationSP p)
{
KisSignalsBlocker blocker(m_optionWidget);
KisPaintOpPresetSP preset = m_resourceProvider->currentPreset();
{
KisPaintOpPreset::DirtyStateSaver dirtySaver(preset.data());
QMapIterator<QString, QVariant> i(p->getProperties());
while (i.hasNext()) {
i.next();
if (preset->settings()->hasProperty(i.key() + "_previous")) {
preset->settings()->setProperty(i.key(), preset->settings()->getProperty(i.key() + "_previous"));
preset->settings()->removeProperty(i.key() + "_previous");
}
}
}
//slotUpdatePreset();
}
void KisPaintopBox::slotDirtyPresetToggled(bool value)
{
if (!value) {
slotReloadPreset();
m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data());
m_presetsPopup->updateViewSettings();
}
m_dirtyPresetsEnabled = value;
KisConfig cfg;
cfg.setUseDirtyPresets(m_dirtyPresetsEnabled);
}
void KisPaintopBox::slotEraserBrushSizeToggled(bool value)
{
m_eraserBrushSizeEnabled = value;
KisConfig cfg;
cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled);
}
void KisPaintopBox::slotEraserBrushOpacityToggled(bool value)
{
m_eraserBrushOpacityEnabled = value;
KisConfig cfg;
cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled);
}
void KisPaintopBox::slotUpdateSelectionIcon()
{
m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal"));
m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical"));
KisConfig cfg;
if (!cfg.toolOptionsInDocker() && m_toolOptionsPopupButton) {
m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure"));
}
m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01"));
m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02"));
m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose"));
m_eraseAction->setIcon(KisIconUtils::loadIcon("draw-eraser"));
m_reloadAction->setIcon(KisIconUtils::loadIcon("view-refresh"));
if (m_disablePressureAction->isChecked()) {
m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure"));
} else {
m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked"));
}
}
void KisPaintopBox::slotLockXMirrorToggle(bool toggleLock) {
m_resourceProvider->setMirrorHorizontalLock(toggleLock);
}
void KisPaintopBox::slotLockYMirrorToggle(bool toggleLock) {
m_resourceProvider->setMirrorVerticalLock(toggleLock);
}
void KisPaintopBox::slotHideDecorationMirrorX(bool toggled) {
m_resourceProvider->setMirrorHorizontalHideDecorations(toggled);
}
void KisPaintopBox::slotHideDecorationMirrorY(bool toggled) {
m_resourceProvider->setMirrorVerticalHideDecorations(toggled);
}
void KisPaintopBox::slotMoveToCenterMirrorX() {
m_resourceProvider->mirrorHorizontalMoveCanvasToCenter();
}
void KisPaintopBox::slotMoveToCenterMirrorY() {
m_resourceProvider->mirrorVerticalMoveCanvasToCenter();
}
diff --git a/libs/ui/kis_paintop_settings_widget.cpp b/libs/ui/kis_paintop_settings_widget.cpp
index cf0e4147dc..40d69302a5 100644
--- a/libs/ui/kis_paintop_settings_widget.cpp
+++ b/libs/ui/kis_paintop_settings_widget.cpp
@@ -1,241 +1,241 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2008
* Copyright (C) Silvio Heinrich <plassy@web.de> , (C) 2011
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_paintop_settings_widget.h"
#include "kis_paintop_option.h"
#include "kis_paintop_options_model.h"
#include <QHBoxLayout>
#include <QList>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QPainter>
#include <QStyleOptionViewItem>
#include <QMenu>
#include <QAction>
#include <QShowEvent>
#include <brushengine/kis_paintop_preset.h>
#include <kis_cmb_composite.h>
#include <kis_categorized_item_delegate.h>
#include <brushengine/kis_locked_properties_server.h>
#include <brushengine/kis_locked_properties_proxy.h>
#include <brushengine/kis_locked_properties.h>
#include <brushengine/kis_paintop_lod_limitations.h>
struct KisPaintOpSettingsWidget::Private
{
QList<KisPaintOpOption*> paintOpOptions;
KisCategorizedListView* optionsList;
KisPaintOpOptionListModel* model;
QStackedWidget* optionsStack;
};
KisPaintOpSettingsWidget::KisPaintOpSettingsWidget(QWidget * parent)
: KisPaintOpConfigWidget(parent)
, m_d(new Private())
{
setObjectName("KisPaintOpPresetsWidget");
m_d->model = new KisPaintOpOptionListModel(this);
m_d->optionsList = new KisCategorizedListView(false, this);
m_d->optionsList->setModel(m_d->model);
m_d->optionsList->setItemDelegate(new KisCategorizedItemDelegate(m_d->optionsList));
m_d->optionsList->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
m_d->optionsList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QSizePolicy policy = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_d->optionsList->setSizePolicy(policy);
- m_d->optionsList->setMinimumWidth(130); // this should be just big enough to show all of the setting names
+ m_d->optionsList->setMinimumWidth(140); // this should be just big enough to show all of the setting names
m_d->optionsStack = new QStackedWidget(this);
policy = QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_d->optionsStack->setSizePolicy(policy);
QHBoxLayout* layout = new QHBoxLayout(this);
layout->addWidget(m_d->optionsList);
layout->addWidget(m_d->optionsStack);
layout->setStretch(0, 0);
layout->setStretch(1, 1);
m_saveLockedOption = false;
connect(m_d->optionsList, SIGNAL(activated(const QModelIndex&)), this, SLOT(changePage(const QModelIndex&)));
connect(m_d->optionsList, SIGNAL(clicked(QModelIndex)), this, SLOT(changePage(const QModelIndex&)));
connect(m_d->optionsList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(lockProperties(const QModelIndex&)));
connect(m_d->optionsList, SIGNAL(rightClickedMenuDropSettingsTriggered()), this, SLOT(slotLockPropertiesDrop()));
connect(m_d->optionsList, SIGNAL(rightClickedMenuSaveSettingsTriggered()), this, SLOT(slotLockPropertiesSave()));
connect(m_d->optionsList, SIGNAL(sigEntryChecked(QModelIndex)), this, SLOT(slotEntryChecked(QModelIndex)));
}
KisPaintOpSettingsWidget::~KisPaintOpSettingsWidget()
{
qDeleteAll(m_d->paintOpOptions);
delete m_d;
}
void KisPaintOpSettingsWidget::addPaintOpOption(KisPaintOpOption *option, const QString &label)
{
if (!option->configurationPage()) return;
m_d->model->addPaintOpOption(option, m_d->optionsStack->count(), label);
connect(option, SIGNAL(sigSettingChanged()), SIGNAL(sigConfigurationItemChanged()));
m_d->optionsStack->addWidget(option->configurationPage());
m_d->paintOpOptions << option;
}
void KisPaintOpSettingsWidget::setConfiguration(const KisPropertiesConfigurationSP config)
{
Q_ASSERT(!config->getString("paintop").isEmpty());
KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(config);
int indexcount = 0;
Q_FOREACH (KisPaintOpOption* option, m_d->paintOpOptions) {
option->startReadOptionSetting(propertiesProxy);
if (KisLockedPropertiesServer::instance()->propertiesFromLocked()) {
option->setLocked(true);
}
else {
option->setLocked(false);
}
KisLockedPropertiesServer::instance()->setPropertiesFromLocked(false);
KisOptionInfo info;
info.option = option;
info.index = indexcount;
m_d->model->categoriesMapper()->itemFromRow(m_d->model->indexOf(info).row())->setLocked(option->isLocked());
m_d->model->categoriesMapper()->itemFromRow(m_d->model->indexOf(info).row())->setLockable(true);
m_d->model->signalDataChanged(m_d->model->indexOf(info));
indexcount++;
}
}
void KisPaintOpSettingsWidget::writeConfiguration(KisPropertiesConfigurationSP config) const
{
KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(config);
Q_FOREACH (const KisPaintOpOption* option, m_d->paintOpOptions) {
option->startWriteOptionSetting(propertiesProxy);
}
}
KisPaintopLodLimitations KisPaintOpSettingsWidget::lodLimitations() const
{
KisPaintopLodLimitations l;
Q_FOREACH (const KisPaintOpOption* option, m_d->paintOpOptions) {
if (option->isCheckable() && !option->isChecked()) continue;
option->lodLimitations(&l);
}
return l;
}
void KisPaintOpSettingsWidget::setImage(KisImageWSP image)
{
Q_FOREACH (KisPaintOpOption* option, m_d->paintOpOptions) {
option->setImage(image);
}
}
void KisPaintOpSettingsWidget::setNode(KisNodeWSP node)
{
Q_FOREACH (KisPaintOpOption* option, m_d->paintOpOptions) {
option->setNode(node);
}
}
void KisPaintOpSettingsWidget::changePage(const QModelIndex& index)
{
KisOptionInfo info;
QPalette palette;
palette.setColor(QPalette::Base, QColor(255,200,200));
palette.setColor(QPalette::Text, Qt::black);
if(m_d->model->entryAt(info, index)) {
m_d->optionsStack->setCurrentIndex(info.index);
// disable the widget if a setting area is not active and not being used
if (info.option->isCheckable() ) {
m_d->optionsStack->setEnabled(info.option->isChecked());
} else {
m_d->optionsStack->setEnabled(true); // option is not checkable, so always enable
}
}
notifyPageChanged();
}
void KisPaintOpSettingsWidget::notifyPageChanged()
{
}
void KisPaintOpSettingsWidget::lockProperties(const QModelIndex& index)
{
KisOptionInfo info;
if (m_d->model->entryAt(info, index)) {
m_d->optionsList->setCurrentIndex(index);
KisPropertiesConfigurationSP p = new KisPropertiesConfiguration();
info.option->startWriteOptionSetting(p);
if (!info.option->isLocked()){
KisLockedPropertiesServer::instance()->addToLockedProperties(p);
info.option->setLocked(true);
m_d->model->categoriesMapper()->itemFromRow(index.row())->setLocked(true);
}
else {
KisLockedPropertiesServer::instance()->removeFromLockedProperties(p);
info.option->setLocked(false);
m_d->model->categoriesMapper()->itemFromRow(index.row())->setLocked(false);
if (m_saveLockedOption){
emit sigSaveLockedConfig(p);
}
else {
emit sigDropLockedConfig(p);
}
m_saveLockedOption = false;
}
m_d->model->signalDataChanged(index);
}
}
void KisPaintOpSettingsWidget::slotLockPropertiesDrop()
{
m_saveLockedOption = false;
lockProperties(m_d->optionsList->currentIndex());
}
void KisPaintOpSettingsWidget::slotLockPropertiesSave()
{
m_saveLockedOption = true;
lockProperties(m_d->optionsList->currentIndex());
}
void KisPaintOpSettingsWidget::slotEntryChecked(const QModelIndex &index)
{
Q_UNUSED(index);
emit sigConfigurationItemChanged();
}
diff --git a/libs/ui/kis_palette_delegate.cpp b/libs/ui/kis_palette_delegate.cpp
index 689afb6a3e..72ee7e4bf9 100644
--- a/libs/ui/kis_palette_delegate.cpp
+++ b/libs/ui/kis_palette_delegate.cpp
@@ -1,89 +1,89 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_palette_delegate.h"
#include <QPen>
#include <QPainter>
#include <kis_global.h>
#include "kis_debug.h"
#include <KisPaletteModel.h>
KisPaletteDelegate::KisPaletteDelegate(QObject *parent)
: QAbstractItemDelegate(parent)
{
}
KisPaletteDelegate::~KisPaletteDelegate()
{
}
void KisPaletteDelegate::setCrossedKeyword(const QString &value)
{
m_crossedKeyword = value;
}
void KisPaletteDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
painter->save();
if (! index.isValid())
return;
const bool isSelected = option.state & QStyle::State_Selected;
const int minSize = qMin(option.rect.width(), option.rect.height());
const int maxWidth = qBound(2, minSize / 6, 4);
const int width = isSelected ? maxWidth : 1;
- if (qVariantValue<bool>(index.data(KisPaletteModel::IsHeaderRole))) {
- QString name = qVariantValue<QString>(index.data(Qt::DisplayRole));
+ if (qvariant_cast<bool>(index.data(KisPaletteModel::IsHeaderRole))) {
+ QString name = qvariant_cast<QString>(index.data(Qt::DisplayRole));
if (isSelected) {
painter->fillRect(option.rect, option.palette.highlight());
}
QRect paintRect = kisGrowRect(option.rect, -width);
painter->drawText(paintRect, name);
} else {
if (isSelected) {
painter->fillRect(option.rect, option.palette.highlight());
}
QRect paintRect = kisGrowRect(option.rect, -width);
- QBrush brush = qVariantValue<QBrush>(index.data(Qt::BackgroundRole));
+ QBrush brush = qvariant_cast<QBrush>(index.data(Qt::BackgroundRole));
painter->fillRect(paintRect, brush);
}
painter->restore();
- QString name = qVariantValue<QString>(index.data(Qt::DisplayRole));
+ QString name = qvariant_cast<QString>(index.data(Qt::DisplayRole));
if (!m_crossedKeyword.isNull() && name.toLower().contains(m_crossedKeyword)) {
QRect crossRect = kisGrowRect(option.rect, -maxWidth);
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setPen(QPen(Qt::white, 2.5));
painter->drawLine(crossRect.topLeft(), crossRect.bottomRight());
painter->setPen(QPen(Qt::red, 1.0));
painter->drawLine(crossRect.topLeft(), crossRect.bottomRight());
painter->restore();
}
}
QSize KisPaletteDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const
{
return option.decorationSize;
}
diff --git a/libs/ui/kis_palette_view.cpp b/libs/ui/kis_palette_view.cpp
index d206ba4347..24c86b4d33 100644
--- a/libs/ui/kis_palette_view.cpp
+++ b/libs/ui/kis_palette_view.cpp
@@ -1,338 +1,340 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_palette_view.h"
#include <QWheelEvent>
#include <QHeaderView>
#include "kis_palette_delegate.h"
#include "KisPaletteModel.h"
#include "kis_config.h"
#include <KLocalizedString>
#include <KoDialog.h>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include <kis_color_button.h>
#include <QCheckBox>
#include <QComboBox>
struct KisPaletteView::Private
{
KisPaletteModel *model = 0;
bool allowPaletteModification = true;
};
KisPaletteView::KisPaletteView(QWidget *parent)
: KoTableView(parent),
m_d(new Private)
{
setShowGrid(false);
horizontalHeader()->setVisible(false);
verticalHeader()->setVisible(false);
setItemDelegate(new KisPaletteDelegate());
setDragEnabled(true);
setDragDropMode(QAbstractItemView::InternalMove);
setDropIndicatorShown(true);
KisConfig cfg;
//QPalette pal(palette());
//pal.setColor(QPalette::Base, cfg.getMDIBackgroundColor());
//setAutoFillBackground(true);
//setPalette(pal);
int defaultSectionSize = cfg.paletteDockerPaletteViewSectionSize();
horizontalHeader()->setDefaultSectionSize(defaultSectionSize);
verticalHeader()->setDefaultSectionSize(defaultSectionSize);
connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(modifyEntry(QModelIndex)));
}
KisPaletteView::~KisPaletteView()
{
}
void KisPaletteView::setCrossedKeyword(const QString &value)
{
KisPaletteDelegate *delegate =
dynamic_cast<KisPaletteDelegate*>(itemDelegate());
KIS_ASSERT_RECOVER_RETURN(delegate);
delegate->setCrossedKeyword(value);
}
bool KisPaletteView::addEntryWithDialog(KoColor color)
{
KoDialog *window = new KoDialog();
window->setWindowTitle(i18nc("@title:window", "Add a new Colorset Entry"));
QFormLayout *editableItems = new QFormLayout();
window->mainWidget()->setLayout(editableItems);
QComboBox *cmbGroups = new QComboBox();
QString defaultGroupName = i18nc("Name for default group", "Default");
cmbGroups->addItem(defaultGroupName);
cmbGroups->addItems(m_d->model->colorSet()->getGroupNames());
QLineEdit *lnIDName = new QLineEdit();
QLineEdit *lnName = new QLineEdit();
KisColorButton *bnColor = new KisColorButton();
QCheckBox *chkSpot = new QCheckBox();
chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color."));
editableItems->addRow(i18n("Group"), cmbGroups);
editableItems->addRow(i18n("ID"), lnIDName);
editableItems->addRow(i18n("Name"), lnName);
editableItems->addRow(i18n("Color"), bnColor);
editableItems->addRow(i18n("Spot"), chkSpot);
cmbGroups->setCurrentIndex(0);
lnName->setText(i18nc("Part of a default name for a color","Color")+" "+QString::number(m_d->model->colorSet()->nColors()+1));
lnIDName->setText(QString::number(m_d->model->colorSet()->nColors()+1));
bnColor->setColor(color);
chkSpot->setChecked(false);
//
if (window->exec() == KoDialog::Accepted) {
QString groupName = cmbGroups->currentText();
if (groupName == defaultGroupName) {
groupName = QString();
}
KoColorSetEntry newEntry;
newEntry.color = bnColor->color();
newEntry.name = lnName->text();
newEntry.id = lnIDName->text();
newEntry.spotColor = chkSpot->isChecked();
m_d->model->addColorSetEntry(newEntry, groupName);
m_d->model->colorSet()->save();
return true;
}
return false;
}
bool KisPaletteView::addGroupWithDialog()
{
KoDialog *window = new KoDialog();
window->setWindowTitle(i18nc("@title:window","Add a new group"));
QFormLayout *editableItems = new QFormLayout();
window->mainWidget()->setLayout(editableItems);
QLineEdit *lnName = new QLineEdit();
editableItems->addRow(i18nc("Name for a group", "Name"), lnName);
lnName->setText(i18nc("Part of default name for a new group", "Color Group")+""+QString::number(m_d->model->colorSet()->getGroupNames().size()+1));
if (window->exec() == KoDialog::Accepted) {
QString groupName = lnName->text();
m_d->model->addGroup(groupName);
m_d->model->colorSet()->save();
return true;
}
return false;
}
bool KisPaletteView::removeEntryWithDialog(QModelIndex index)
{
bool keepColors = true;
- if (qVariantValue<bool>(index.data(KisPaletteModel::IsHeaderRole))) {
+ if (qvariant_cast<bool>(index.data(KisPaletteModel::IsHeaderRole))) {
KoDialog *window = new KoDialog();
window->setWindowTitle(i18nc("@title:window","Removing Group"));
QFormLayout *editableItems = new QFormLayout();
QCheckBox *chkKeep = new QCheckBox();
window->mainWidget()->setLayout(editableItems);
editableItems->addRow(i18nc("Shows up when deleting a group","Keep the Colors"), chkKeep);
chkKeep->setChecked(keepColors);
if (window->exec() == KoDialog::Accepted) {
keepColors = chkKeep->isChecked();
m_d->model->removeEntry(index, keepColors);
m_d->model->colorSet()->save();
}
} else {
m_d->model->removeEntry(index, keepColors);
m_d->model->colorSet()->save();
}
return true;
}
void KisPaletteView::trySelectClosestColor(KoColor color)
{
KoColorSet* color_set = m_d->model->colorSet();
if (!color_set)
return;
//also don't select if the color is the same as the current selection
if (selectedIndexes().size()>0) {
QModelIndex currentI = currentIndex();
if (!currentI.isValid()) {
currentI = selectedIndexes().last();
}
if (!currentI.isValid()) {
currentI = selectedIndexes().first();
}
if (currentI.isValid()) {
if (m_d->model->colorSetEntryFromIndex(currentI).color==color) {
return;
}
}
}
quint32 i = color_set->getIndexClosestColor(color);
QModelIndex index = m_d->model->indexFromId(i);
this->selectionModel()->clearSelection();
this->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select);
}
void KisPaletteView::mouseReleaseEvent(QMouseEvent *event)
{
bool foreground = false;
if (event->button()== Qt::LeftButton) {
foreground = true;
}
entrySelection(foreground);
}
void KisPaletteView::paletteModelChanged()
{
updateView();
updateRows();
}
void KisPaletteView::setPaletteModel(KisPaletteModel *model)
{
if (m_d->model) {
disconnect(m_d->model, 0, this, 0);
}
m_d->model = model;
setModel(model);
paletteModelChanged();
connect(m_d->model, SIGNAL(layoutChanged(QList<QPersistentModelIndex>,QAbstractItemModel::LayoutChangeHint)), this, SLOT(paletteModelChanged()));
connect(m_d->model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(paletteModelChanged()));
connect(m_d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(paletteModelChanged()));
connect(m_d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(paletteModelChanged()));
connect(m_d->model, SIGNAL(modelReset()), this, SLOT(paletteModelChanged()));
}
KisPaletteModel* KisPaletteView::paletteModel() const
{
return m_d->model;
}
void KisPaletteView::updateRows()
{
this->clearSpans();
if (m_d->model) {
for (int r=0; r<=m_d->model->rowCount(); r++) {
QModelIndex index = m_d->model->index(r, 0);
- if (qVariantValue<bool>(index.data(KisPaletteModel::IsHeaderRole))) {
+ if (qvariant_cast<bool>(index.data(KisPaletteModel::IsHeaderRole))) {
setSpan(r, 0, 1, m_d->model->columnCount());
setRowHeight(r, this->fontMetrics().lineSpacing()+6);
} else {
this->setRowHeight(r, this->columnWidth(0));
}
}
}
}
void KisPaletteView::setAllowModification(bool allow)
{
m_d->allowPaletteModification = allow;
}
void KisPaletteView::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::ControlModifier) {
int numDegrees = event->delta() / 8;
int numSteps = numDegrees / 7;
int curSize = horizontalHeader()->sectionSize(0);
int setSize = numSteps + curSize;
- if ( setSize >= 12 ) {
- horizontalHeader()->setDefaultSectionSize(setSize);
- verticalHeader()->setDefaultSectionSize(setSize);
- KisConfig cfg;
- cfg.setPaletteDockerPaletteViewSectionSize(setSize);
- }
+ if ( (event->delta() <= 0) && (setSize <= 8) ) {
+ // Ignore scroll-zooming down below a certain size
+ } else {
+ horizontalHeader()->setDefaultSectionSize(setSize);
+ verticalHeader()->setDefaultSectionSize(setSize);
+ KisConfig cfg;
+ cfg.setPaletteDockerPaletteViewSectionSize(setSize);
+ }
event->accept();
} else {
KoTableView::wheelEvent(event);
}
}
void KisPaletteView::entrySelection(bool foreground) {
QModelIndex index;
if (selectedIndexes().size()<=0) {
return;
}
if (selectedIndexes().last().isValid()) {
index = selectedIndexes().last();
} else if (selectedIndexes().first().isValid()) {
index = selectedIndexes().first();
} else {
return;
}
- if (qVariantValue<bool>(index.data(KisPaletteModel::IsHeaderRole))==false) {
+ if (qvariant_cast<bool>(index.data(KisPaletteModel::IsHeaderRole))==false) {
KoColorSetEntry entry = m_d->model->colorSetEntryFromIndex(index);
if (foreground) {
emit(entrySelected(entry));
emit(indexEntrySelected(index));
} else {
emit(entrySelectedBackGround(entry));
emit(indexEntrySelected(index));
}
}
}
void KisPaletteView::modifyEntry(QModelIndex index) {
if (m_d->allowPaletteModification) {
KoDialog *group = new KoDialog();
QFormLayout *editableItems = new QFormLayout();
group->mainWidget()->setLayout(editableItems);
QLineEdit *lnIDName = new QLineEdit();
QLineEdit *lnGroupName = new QLineEdit();
KisColorButton *bnColor = new KisColorButton();
QCheckBox *chkSpot = new QCheckBox();
- if (qVariantValue<bool>(index.data(KisPaletteModel::IsHeaderRole))) {
- QString groupName = qVariantValue<QString>(index.data(Qt::DisplayRole));
+ if (qvariant_cast<bool>(index.data(KisPaletteModel::IsHeaderRole))) {
+ QString groupName = qvariant_cast<QString>(index.data(Qt::DisplayRole));
editableItems->addRow(i18nc("Name for a colorgroup","Name"), lnGroupName);
lnGroupName->setText(groupName);
if (group->exec() == KoDialog::Accepted) {
m_d->model->colorSet()->changeGroupName(groupName, lnGroupName->text());
m_d->model->colorSet()->save();
updateRows();
}
//rename the group.
} else {
KoColorSetEntry entry = m_d->model->colorSetEntryFromIndex(index);
- QStringList entryList = qVariantValue<QStringList>(index.data(KisPaletteModel::RetrieveEntryRole));
+ QStringList entryList = qvariant_cast<QStringList>(index.data(KisPaletteModel::RetrieveEntryRole));
chkSpot->setToolTip(i18nc("@info:tooltip", "A spot color is a color that the printer is able to print without mixing the paints it has available to it. The opposite is called a process color."));
editableItems->addRow(i18n("ID"), lnIDName);
editableItems->addRow(i18n("Name"), lnGroupName);
editableItems->addRow(i18n("Color"), bnColor);
editableItems->addRow(i18n("Spot"), chkSpot);
lnGroupName->setText(entry.name);
lnIDName->setText(entry.id);
bnColor->setColor(entry.color);
chkSpot->setChecked(entry.spotColor);
if (group->exec() == KoDialog::Accepted) {
entry.name = lnGroupName->text();
entry.id = lnIDName->text();
entry.color = bnColor->color();
entry.spotColor = chkSpot->isChecked();
m_d->model->colorSet()->changeColorSetEntry(entry, entryList.at(0), entryList.at(1).toUInt());
m_d->model->colorSet()->save();
}
}
}
}
diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp
index a17f76859f..a920ed6f8e 100644
--- a/libs/ui/kis_popup_palette.cpp
+++ b/libs/ui/kis_popup_palette.cpp
@@ -1,921 +1,921 @@
/* This file is part of the KDE project
Copyright 2009 Vera Lukman <shicmap@gmail.com>
Copyright 2011 Sven Langkamp <sven.langkamp@gmail.com>
Copyright 2016 Scott Petrovic <scottpetrovic@gmail.com>
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 "kis_config.h"
#include "kis_popup_palette.h"
#include "kis_paintop_box.h"
#include "kis_favorite_resource_manager.h"
#include "kis_icon_utils.h"
#include <brushengine/kis_paintop_preset.h>
#include "kis_resource_server_provider.h"
#include <kis_canvas_resource_provider.h>
#include <KoTriangleColorSelector.h>
#include <KisVisualColorSelector.h>
#include <kis_config_notifier.h>
#include "KoColorSpaceRegistry.h"
#include <kis_types.h>
#include <QtGui>
#include <kis_debug.h>
#include <QQueue>
#include <QMenu>
#include <QWhatsThis>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QSpacerItem>
#include <QDebug>
#include <math.h>
#include "kis_signal_compressor.h"
#include <QApplication>
#include "brushhud/kis_brush_hud.h"
#include "brushhud/kis_round_hud_button.h"
#include <kis_action.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;
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_hoveredPreset(0)
, m_hoveredColor(0)
, m_selectedColor(0)
, m_coordinatesConverter(coordinatesConverter)
, m_actionManager(viewManager->actionManager())
, m_resourceManager(manager)
, m_triangleColorSelector(0)
, m_displayRenderer(displayRenderer)
, m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE))
, m_actionCollection(viewManager->actionCollection())
, m_brushHud(0)
, m_popupPaletteSize(385.0)
, m_colorHistoryInnerRadius(72.0)
, m_colorHistoryOuterRadius(92.0)
, m_isOverCanvasRotationIndicator(false)
, m_isRotatingCanvasIndicator(false)
{
// some UI controls are defined and created based off these variables
const int borderWidth = 3;
if (KisConfig().readEntry<bool>("popuppalette/usevisualcolorselector", false)) {
m_triangleColorSelector = new KisVisualColorSelector(this);
}
else {
m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this);
}
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_colorHistoryInnerRadius*2-borderWidth*2, m_colorHistoryInnerRadius*2-borderWidth*2);
m_triangleColorSelector->setVisible(true);
KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8());
if (m_resourceManager) {
fgcolor = provider->fgColor();
}
m_triangleColorSelector->slotSetColor(fgcolor);
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(const 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_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)),
SLOT(slotExternalFgColorChanged(KoColor)));
connect(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->setMaximumHeight(m_popupPaletteSize);
m_brushHud->setVisible(false);
const int auxButtonSize = 35;
m_settingsButton = new KisRoundHudButton(this);
m_settingsButton->setIcon(KisIconUtils::loadIcon("configure"));
m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize,
auxButtonSize, auxButtonSize);
connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup()));
KisConfig cfg;
m_brushHudButton = new KisRoundHudButton(this);
m_brushHudButton->setCheckable(true);
m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right"));
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->setCheckable(true);
mirrorMode->setFixedSize(35, 35);
mirrorMode->setIcon(KisIconUtils::loadIcon("symmetry-horizontal"));
mirrorMode->setToolTip(i18n("Mirror Canvas"));
connect(mirrorMode, SIGNAL(clicked(bool)), this, SLOT(slotmirroModeClicked()));
canvasOnlyButton = new KisHighlightedToolButton(this);
canvasOnlyButton->setCheckable(true);
canvasOnlyButton->setFixedSize(35, 35);
canvasOnlyButton->setIcon(KisIconUtils::loadIcon("document-new"));
canvasOnlyButton->setToolTip(i18n("Canvas Only"));
connect(canvasOnlyButton, SIGNAL(clicked(bool)), this, SLOT(slotCanvasonlyModeClicked()));
zoomToOneHundredPercentButton = new QPushButton(this);
zoomToOneHundredPercentButton->setText(i18n("100%"));
zoomToOneHundredPercentButton->setFixedHeight(35);
zoomToOneHundredPercentButton->setIcon(KisIconUtils::loadIcon("zoom-original"));
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)));
hLayout->addWidget(mirrorMode);
hLayout->addWidget(canvasOnlyButton);
hLayout->addWidget(zoomToOneHundredPercentButton);
hLayout->addWidget(zoomCanvasSlider);
setVisible(true);
setVisible(false);
// Prevent tablet events from being captured by the canvas
setAttribute(Qt::WA_NoMousePropagation, true);
}
void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color)
{
//hack to get around cmyk for now.
if (color.colorSpace()->colorChannelCount()>3) {
KoColor c(KoColorSpaceRegistry::instance()->rgb8());
c.fromKoColor(color);
m_triangleColorSelector->slotSetColor(c);
} else {
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::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::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;
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 ){
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<QImage> 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);
}
}
}
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;
float 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->posF();
+ 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
m_coordinatesConverter->rotate(m_coordinatesConverter->widgetCenterPoint(), 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->favoritePresetList().at(pos).data()->name());
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->posF();
+ 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, so find it ou
m_coordinatesConverter->rotate(m_coordinatesConverter->widgetCenterPoint(), angleDifference);
emit sigUpdateCanvas();
}
}
}
void KisPopupPalette::slotShowTagsPopup()
{
KisPaintOpPresetResourceServer* rServer = KisResourceServerProvider::instance()->paintOpPresetServer();
QStringList tags = rServer->tagNamesList();
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) {
m_resourceManager->setCurrentTag(action->text());
}
} 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::slotmirroModeClicked() {
QAction* action = m_actionCollection->action("mirror_canvas");
if (action) {
action->trigger();
}
}
void KisPopupPalette::slotCanvasonlyModeClicked() {
QAction* action = m_actionCollection->action("view_show_canvas_only");
if (action) {
action->trigger();
}
}
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::mouseReleaseEvent(QMouseEvent * event)
{
- QPointF point = event->posF();
+ QPointF point = event->localPos();
event->accept();
m_isOverCanvasRotationIndicator = false;
m_isRotatingCanvasIndicator = false;
if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) {
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 presetRadius = m_colorHistoryOuterRadius * qSin(qDegreesToRadians(angleSlice/2)) / (1-qSin(qDegreesToRadians(angleSlice/2)));
QPainterPath path;
float pathX = (m_colorHistoryOuterRadius + presetRadius) * qCos(qDegreesToRadians(startingAngle)) - presetRadius;
float pathY = -(m_colorHistoryOuterRadius + presetRadius) * qSin(qDegreesToRadians(startingAngle)) - presetRadius;
float 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;
return qMax(config.favoritePresets(), 10);
}
diff --git a/libs/ui/kis_splash_screen.cpp b/libs/ui/kis_splash_screen.cpp
index fbdc19283f..c54b51c9a9 100644
--- a/libs/ui/kis_splash_screen.cpp
+++ b/libs/ui/kis_splash_screen.cpp
@@ -1,204 +1,203 @@
/*
* Copyright (c) 2014 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_splash_screen.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QPixmap>
#include <QCheckBox>
#include <kis_debug.h>
#include <QFile>
#include <KisPart.h>
#include <KisApplication.h>
#include <kis_icon.h>
#include <klocalizedstring.h>
#include <kconfig.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <QIcon>
KisSplashScreen::KisSplashScreen(const QString &version, const QPixmap &pixmap, bool themed, QWidget *parent, Qt::WindowFlags f)
: QWidget(parent, Qt::SplashScreen | Qt::FramelessWindowHint
#ifdef Q_OS_LINUX
| Qt::WindowStaysOnTopHint
#endif
| f),
m_themed(themed)
{
setupUi(this);
setWindowIcon(KisIconUtils::loadIcon("calligrakrita"));
// Maintain the aspect ratio on high DPI screens when scaling
lblSplash->setPixmap(pixmap);
setFixedWidth(pixmap.width());
QString color = colorString();
lblVersion->setText(i18n("Version: %1", version));
lblVersion->setStyleSheet("color:" + color);
bnClose->hide();
connect(bnClose, SIGNAL(clicked()), this, SLOT(close()));
chkShowAtStartup->hide();
connect(chkShowAtStartup, SIGNAL(toggled(bool)), this, SLOT(toggleShowAtStartup(bool)));
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false);
chkShowAtStartup->setChecked(hideSplash);
connect(lblRecent, SIGNAL(linkActivated(QString)), SLOT(linkClicked(QString)));
connect(&m_timer, SIGNAL(timeout()), SLOT(raise()));
// hide these labels by default
lblLinks->setVisible(false);
lblRecent->setVisible(false);
line->setVisible(false);
m_timer.setSingleShot(true);
m_timer.start(10);
}
void KisSplashScreen::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
updateText();
}
void KisSplashScreen::updateText()
{
QString color = colorString();
KConfigGroup cfg2( KSharedConfig::openConfig(), "RecentFiles");
int i = 1;
QString recent = i18n("<html>"
"<head/>"
"<body>"
"<p><b><span style=\" color:%1;\">Recent Files</span></b></p>", color);
QString path;
QStringList recentfiles;
QFontMetrics metrics(lblRecent->font());
do {
path = cfg2.readPathEntry(QString("File%1").arg(i), QString());
if (!path.isEmpty()) {
QString name = cfg2.readPathEntry(QString("Name%1").arg(i), QString());
QUrl url(path);
if (name.isEmpty()) {
name = url.fileName();
}
name = metrics.elidedText(name, Qt::ElideMiddle, lblRecent->width());
if (!url.isLocalFile() || QFile::exists(url.toLocalFile())) {
recentfiles.insert(0, QString("<p><a href=\"%1\"><span style=\"color:%3;\">%2</span></a></p>").arg(path).arg(name).arg(color));
}
}
i++;
} while (!path.isEmpty() || i <= 8);
recent += recentfiles.join("\n");
recent += "</body>"
"</html>";
lblRecent->setText(recent);
}
void KisSplashScreen::displayLinks() {
QString color = colorString();
lblLinks->setTextFormat(Qt::RichText);
lblLinks->setText(i18n("<html>"
"<head/>"
"<body>"
"<p><span style=\" color:%1;\"><b>Links</b></span></p>"
"<p><a href=\"https://krita.org/support-us/\"><span style=\" text-decoration: underline; color:%1;\">Support Krita</span></a></p>"
"<p><a href=\"https://docs.krita.org/Category:Getting_Started\"><span style=\" text-decoration: underline; color:%1;\">Getting Started</span></a></p>"
"<p><a href=\"https://docs.krita.org/\"><span style=\" text-decoration: underline; color:%1;\">Manual</span></a></p>"
"<p><a href=\"https://krita.org/\"><span style=\" text-decoration: underline; color:%1;\">Krita Website</span></a></p>"
"<p><a href=\"https://forum.kde.org/viewforum.php?f=136\"><span style=\" text-decoration: underline; color:%1;\">User Community</span></a></p>"
- "<p><a href=\"https://quickgit.kde.org/?p=krita.git\"><span style=\" text-decoration: underline; color:%1;\">Source Code</span></a></p>"
+ "<p><a href=\"https://phabricator.kde.org/source/krita/\"><span style=\" text-decoration: underline; color:%1;\">Source Code</span></a></p>"
- "<p><a href=\"https://store.steampowered.com/app/280680/\"><span style=\" text-decoration: underline; color:%1;\">Krita on Steam</span></a></p>"
"</body>"
"</html>", color));
lblLinks->setVisible(true);
updateText();
}
void KisSplashScreen::displayRecentFiles() {
lblRecent->setVisible(true);
line->setVisible(true);
}
QString KisSplashScreen::colorString() const
{
QString color = "#FFFFFF";
if (m_themed && qApp->palette().background().color().value() > 100) {
color = "#000000";
}
return color;
}
void KisSplashScreen::repaint()
{
QWidget::repaint();
- QApplication::flush();
+ qApp->sendPostedEvents();
}
void KisSplashScreen::show()
{
QRect r(QPoint(), sizeHint());
resize(r.size());
move(QApplication::desktop()->availableGeometry().center() - r.center());
if (isVisible()) {
repaint();
}
m_timer.setSingleShot(true);
m_timer.start(1);
QWidget::show();
}
void KisSplashScreen::toggleShowAtStartup(bool toggle)
{
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
cfg.writeEntry("HideSplashAfterStartup", toggle);
}
void KisSplashScreen::linkClicked(const QString &link)
{
KisPart::instance()->openExistingFile(QUrl::fromLocalFile(link));
if (isTopLevel()) {
close();
}
}
diff --git a/libs/ui/kis_statusbar.cc b/libs/ui/kis_statusbar.cc
index 3153a22682..f2745c2d8d 100644
--- a/libs/ui/kis_statusbar.cc
+++ b/libs/ui/kis_statusbar.cc
@@ -1,404 +1,406 @@
/* This file is part of KimageShop^WKrayon^WKrita
*
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_statusbar.h"
#include <QLabel>
#include <QFontMetrics>
#include <QToolButton>
#include <QPushButton>
#include <QAction>
#include <QToolTip>
#include <QStatusBar>
#include <ksqueezedtextlabel.h>
#include <klocalizedstring.h>
#include <KoColorProfile.h>
#include <KoColorSpace.h>
#include "kis_icon_utils.h"
#include <kis_types.h>
#include <kis_image.h>
#include <kis_selection.h>
#include <kis_paint_device.h>
#include <kis_selection_manager.h>
#include "kis_memory_statistics_server.h"
#include <KisView.h>
#include "KisViewManager.h"
#include "canvas/kis_canvas2.h"
#include "kis_progress_widget.h"
#include "kis_zoom_manager.h"
#include <KoToolManager.h>
#include <KoViewConverter.h>
#include <KisMainWindow.h>
enum {
IMAGE_SIZE_ID,
POINTER_POSITION_ID
};
KisStatusBar::KisStatusBar(KisViewManager *view)
: m_view(view),
m_imageView(0),
m_statusBar(0)
{
}
void KisStatusBar::setup()
{
m_selectionStatus = new QToolButton();
m_selectionStatus->setIconSize(QSize(16,16));
m_selectionStatus->setAutoRaise(true);
m_selectionStatus->setEnabled(false);
updateSelectionIcon();
m_statusBar = m_view->mainWindow()->statusBar();
connect(m_selectionStatus, SIGNAL(clicked()), m_view->selectionManager(), SLOT(slotToggleSelectionDecoration()));
connect(m_view->selectionManager(), SIGNAL(displaySelectionChanged()), SLOT(updateSelectionToolTip()));
connect(m_view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(updateSelectionIcon()));
addStatusBarItem(m_selectionStatus);
m_selectionStatus->setVisible(false);
m_statusBarStatusLabel = new KSqueezedTextLabel();
m_statusBarStatusLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
m_statusBarStatusLabel->setContentsMargins(5, 5, 5, 5);
connect(KoToolManager::instance(), SIGNAL(changedStatusText(const QString &)),
m_statusBarStatusLabel, SLOT(setText(const QString &)));
addStatusBarItem(m_statusBarStatusLabel, 2);
m_statusBarStatusLabel->setVisible(false);
m_statusBarProfileLabel = new KSqueezedTextLabel();
m_statusBarProfileLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
m_statusBarProfileLabel->setContentsMargins(5, 5, 5, 5);
addStatusBarItem(m_statusBarProfileLabel, 3);
m_statusBarProfileLabel->setVisible(false);
m_progress = new KisProgressWidget();
addStatusBarItem(m_progress);
m_progress->setVisible(false);
connect(m_progress, SIGNAL(sigCancellationRequested()), this, SIGNAL(sigCancellationRequested()));
m_progressUpdater.reset(new KisProgressUpdater(m_progress, m_progress->progressProxy()));
m_progressUpdater->setAutoNestNames(true);
m_memoryReportBox = new QPushButton();
m_memoryReportBox->setFlat(true);
m_memoryReportBox->setContentsMargins(5, 5, 5, 5);
m_memoryReportBox->setMinimumWidth(120);
addStatusBarItem(m_memoryReportBox);
m_memoryReportBox->setVisible(false);
connect(m_memoryReportBox, SIGNAL(clicked()), SLOT(showMemoryInfoToolTip()));
m_pointerPositionLabel = new QLabel(QString());
m_pointerPositionLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
m_pointerPositionLabel->setMinimumWidth(100);
m_pointerPositionLabel->setContentsMargins(5,5, 5, 5);
addStatusBarItem(m_pointerPositionLabel);
m_pointerPositionLabel->setVisible(false);
connect(KisMemoryStatisticsServer::instance(),
SIGNAL(sigUpdateMemoryStatistics()),
SLOT(imageSizeChanged()));
}
KisStatusBar::~KisStatusBar()
{
}
void KisStatusBar::setView(QPointer<KisView> imageView)
{
if (m_imageView == imageView) {
return;
}
if (m_imageView) {
m_imageView->disconnect(this);
removeStatusBarItem(m_imageView->zoomManager()->zoomActionWidget());
m_imageView = 0;
}
if (imageView) {
m_imageView = imageView;
connect(m_imageView, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)),
this, SLOT(updateStatusBarProfileLabel()));
connect(m_imageView, SIGNAL(sigProfileChanged(const KoColorProfile*)),
this, SLOT(updateStatusBarProfileLabel()));
connect(m_imageView, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)),
this, SLOT(imageSizeChanged()));
updateStatusBarProfileLabel();
addStatusBarItem(m_imageView->zoomManager()->zoomActionWidget());
}
imageSizeChanged();
}
void KisStatusBar::addStatusBarItem(QWidget *widget, int stretch, bool permanent)
{
StatusBarItem sbItem(widget);
if (permanent) {
m_statusBar->addPermanentWidget(widget, stretch);
}
else {
m_statusBar->addWidget(widget, stretch);
}
sbItem.show();
m_statusBarItems.append(sbItem);
}
void KisStatusBar::removeStatusBarItem(QWidget *widget)
{
int i = 0;
Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) {
if (sbItem.widget() == widget) {
break;
}
i++;
}
if (i < m_statusBarItems.count()) {
m_statusBar->removeWidget(m_statusBarItems[i].widget());
m_statusBarItems.remove(i);
}
}
void KisStatusBar::hideAllStatusBarItems()
{
Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) {
sbItem.hide();
}
}
void KisStatusBar::showAllStatusBarItems()
{
Q_FOREACH(const StatusBarItem& sbItem, m_statusBarItems) {
sbItem.show();
}
}
void KisStatusBar::documentMousePositionChanged(const QPointF &pos)
{
if (!m_imageView) return;
QPoint pixelPos = m_imageView->image()->documentToIntPixel(pos);
pixelPos.setX(qBound(0, pixelPos.x(), m_view->image()->width() - 1));
pixelPos.setY(qBound(0, pixelPos.y(), m_view->image()->height() - 1));
m_pointerPositionLabel->setText(QString("%1, %2").arg(pixelPos.x()).arg(pixelPos.y()));
}
void KisStatusBar::imageSizeChanged()
{
updateMemoryStatus();
QString sizeText;
KisImageWSP image = m_imageView ? m_imageView->image() : 0;
if (image) {
qint32 w = image->width();
qint32 h = image->height();
sizeText = QString("%1 x %2 (%3)").arg(w).arg(h).arg(m_shortMemoryTag);
} else {
sizeText = m_shortMemoryTag;
}
m_memoryReportBox->setIcon(m_memoryStatusIcon);
m_memoryReportBox->setText(sizeText);
m_memoryReportBox->setToolTip(m_longMemoryTag);
}
void KisStatusBar::updateSelectionIcon()
{
QIcon icon;
if (!m_view->selectionManager()->displaySelection()) {
icon = KisIconUtils::loadIcon("selection-mode_invisible");
} else if (m_view->selectionManager()->showSelectionAsMask()) {
icon = KisIconUtils::loadIcon("selection-mode_mask");
} else /* if (!m_view->selectionManager()->showSelectionAsMask()) */ {
icon = KisIconUtils::loadIcon("selection-mode_ants");
}
m_selectionStatus->setIcon(icon);
}
-inline QString formatSize(qint64 size)
+QString KisStatusBar::formatSize(qint64 size)
{
qint64 K = 1024;
QString suffix = i18nc("very shortened \'byte\' suffix (for statusbar)", "b");
qreal realSize = size;
if (realSize > K) {
realSize /= K;
suffix = i18nc("very shortened KiB suffix (for statusbar)", "K");
}
if (realSize > K) {
realSize /= K;
suffix = i18nc("very shortened MiB suffix (for statusbar)", "M");
}
if (realSize > K) {
realSize /= K;
suffix = i18nc("very shortened GiB suffix (for statusbar)", "G");
}
if (realSize > K) {
realSize /= K;
suffix = i18nc("very shortened TiB suffix (for statusbar)", "T");
}
return QString("%2%3").arg(QString::number(realSize, 'f', 1)).arg(suffix);
}
void KisStatusBar::updateMemoryStatus()
{
KisMemoryStatisticsServer::Statistics stats =
KisMemoryStatisticsServer::instance()
->fetchMemoryStatistics(m_imageView ? m_imageView->image() : 0);
const QString imageStatsMsg =
i18nc("tooltip on statusbar memory reporting button (image stats)",
"Image size:\t %1\n"
" - layers:\t\t %2\n"
" - projections:\t %3\n"
" - instant preview:\t %4\n",
formatSize(stats.imageSize),
formatSize(stats.layersSize),
formatSize(stats.projectionsSize),
formatSize(stats.lodSize));
const QString memoryStatsMsg =
i18nc("tooltip on statusbar memory reporting button (total stats)",
"Memory used:\t %1 / %2\n"
" image data:\t %3 / %4\n"
" pool:\t\t %5 / %6\n"
" undo data:\t %7\n"
"\n"
"Swap used:\t %8",
formatSize(stats.totalMemorySize),
formatSize(stats.totalMemoryLimit),
formatSize(stats.realMemorySize),
formatSize(stats.tilesHardLimit),
formatSize(stats.poolSize),
formatSize(stats.tilesPoolLimit),
formatSize(stats.historicalMemorySize),
formatSize(stats.swapSize));
QString longStats = imageStatsMsg + "\n" + memoryStatsMsg;
QString shortStats = formatSize(stats.imageSize);
QIcon icon;
const qint64 warnLevel = stats.tilesHardLimit - stats.tilesHardLimit / 8;
if (stats.imageSize > warnLevel ||
stats.realMemorySize > warnLevel) {
icon = KisIconUtils::loadIcon("dialog-warning");
QString suffix =
i18nc("tooltip on statusbar memory reporting button",
"\n\nWARNING:\tOut of memory! Swapping has been started.\n"
"\t\tPlease configure more RAM for Krita in Settings dialog");
longStats += suffix;
}
m_shortMemoryTag = shortStats;
m_longMemoryTag = longStats;
m_memoryStatusIcon = icon;
+
+ emit memoryStatusUpdated();
}
void KisStatusBar::showMemoryInfoToolTip()
{
QToolTip::showText(QCursor::pos(), m_memoryReportBox->toolTip(), m_memoryReportBox);
}
void KisStatusBar::updateSelectionToolTip()
{
updateSelectionIcon();
KisSelectionSP selection = m_view->selection();
if (selection) {
m_selectionStatus->setEnabled(true);
QRect r = selection->selectedExactRect();
QString displayMode =
!m_view->selectionManager()->displaySelection() ?
i18n("Hidden") :
(m_view->selectionManager()->showSelectionAsMask() ?
i18n("Mask") : i18n("Ants"));
m_selectionStatus->setToolTip(
i18n("Selection: x = %1 y = %2 width = %3 height = %4\n"
"Display Mode: %5",
r.x(), r.y(), r.width(), r.height(), displayMode));
} else {
m_selectionStatus->setEnabled(false);
m_selectionStatus->setToolTip(i18n("No Selection"));
}
}
void KisStatusBar::setSelection(KisImageWSP image)
{
Q_UNUSED(image);
updateSelectionToolTip();
}
void KisStatusBar::setProfile(KisImageWSP image)
{
if (m_statusBarProfileLabel == 0) {
return;
}
if (!image) return;
if (image->profile() == 0) {
m_statusBarProfileLabel->setText(i18n("No profile"));
} else {
m_statusBarProfileLabel->setText(image->colorSpace()->name() + " " + image->profile()->name());
}
}
void KisStatusBar::setHelp(const QString &t)
{
Q_UNUSED(t);
}
void KisStatusBar::updateStatusBarProfileLabel()
{
if (!m_imageView) return;
setProfile(m_imageView->image());
}
KoProgressUpdater *KisStatusBar::progressUpdater()
{
return m_progressUpdater.data();
}
diff --git a/libs/ui/kis_statusbar.h b/libs/ui/kis_statusbar.h
index 8363873da6..a6c51833ec 100644
--- a/libs/ui/kis_statusbar.h
+++ b/libs/ui/kis_statusbar.h
@@ -1,134 +1,140 @@
/* This file is part of KimageShop^WKrayon^WKrita
*
* Copyright (c) 2003-200^ Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_STATUSBAR_H
#define KIS_STATUSBAR_H
#include <QObject>
#include <QPointer>
#include <QIcon>
#include <QStatusBar>
#include <kis_types.h>
#include "KisView.h"
class QLabel;
class QToolButton;
class QPushButton;
class KSqueezedTextLabel;
class KisViewManager;
class KisProgressWidget;
class KoProgressUpdater;
#include "kritaui_export.h"
class KRITAUI_EXPORT KisStatusBar : public QObject
{
class StatusBarItem
{
public:
StatusBarItem() // for QValueList
: m_widget(0) {}
StatusBarItem(QWidget * widget)
: m_widget(widget) {}
bool operator==(const StatusBarItem& rhs) {
return m_widget == rhs.m_widget;
}
bool operator!=(const StatusBarItem& rhs) {
return m_widget != rhs.m_widget;
}
QWidget * widget() const {
return m_widget;
}
void show() const {
m_widget->show();
}
void hide() const {
m_widget->hide();
}
private:
QPointer<QWidget> m_widget;
};
Q_OBJECT
public:
KisStatusBar(KisViewManager *view);
~KisStatusBar() override;
void setup();
void setView(QPointer<KisView> imageView);
void addStatusBarItem(QWidget *widget, int stretch = 0, bool permanent = false);
void removeStatusBarItem(QWidget *widget);
void hideAllStatusBarItems();
void showAllStatusBarItems();
+ static QString formatSize(qint64 size);
+
KoProgressUpdater *progressUpdater();
public Q_SLOTS:
void documentMousePositionChanged(const QPointF &p);
void imageSizeChanged();
void setSelection(KisImageWSP image);
void setProfile(KisImageWSP image);
void setHelp(const QString &t);
void updateStatusBarProfileLabel();
void updateSelectionToolTip();
private Q_SLOTS:
void updateSelectionIcon();
void showMemoryInfoToolTip();
Q_SIGNALS:
void sigCancellationRequested();
+ /// tell the listener that the memory usage has changed
+ /// and it needs to update its stats
+ void memoryStatusUpdated();
+
private:
void updateMemoryStatus();
private:
QPointer<KisViewManager> m_view;
QPointer<KisView> m_imageView;
QPointer<QStatusBar> m_statusBar;
KisProgressWidget *m_progress;
QScopedPointer<KoProgressUpdater> m_progressUpdater;
QToolButton *m_selectionStatus;
QPushButton *m_memoryReportBox;
QLabel *m_pointerPositionLabel;
KSqueezedTextLabel *m_statusBarStatusLabel;
KSqueezedTextLabel *m_statusBarProfileLabel;
QString m_shortMemoryTag;
QString m_longMemoryTag;
QIcon m_memoryStatusIcon;
QVector<StatusBarItem> m_statusBarItems;
};
#endif
diff --git a/libs/ui/kisexiv2/kis_exiv2.cpp b/libs/ui/kisexiv2/kis_exiv2.cpp
index 98bb847326..b0cfab91c9 100644
--- a/libs/ui/kisexiv2/kis_exiv2.cpp
+++ b/libs/ui/kisexiv2/kis_exiv2.cpp
@@ -1,290 +1,290 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-
+
#include "kis_exiv2.h"
#include <QDateTime>
#include "kis_iptc_io.h"
#include "kis_exif_io.h"
#include "kis_xmp_io.h"
#include <metadata/kis_meta_data_value.h>
#include <kis_debug.h>
// ---- Generic convertion functions ---- //
// Convert an exiv value to a KisMetaData value
KisMetaData::Value exivValueToKMDValue(const Exiv2::Value::AutoPtr value, bool forceSeq, KisMetaData::Value::ValueType arrayType)
{
switch (value->typeId()) {
case Exiv2::signedByte:
case Exiv2::invalidTypeId:
case Exiv2::lastTypeId:
case Exiv2::directory:
dbgFile << "Invalid value :" << value->typeId() << " value =" << value->toString().c_str();
return KisMetaData::Value();
case Exiv2::undefined: {
dbgFile << "Undefined value :" << value->typeId() << " value =" << value->toString().c_str();
QByteArray array(value->count() , 0);
value->copy((Exiv2::byte*)array.data(), Exiv2::invalidByteOrder);
return KisMetaData::Value(QString(array.toBase64()));
}
case Exiv2::unsignedByte:
case Exiv2::unsignedShort:
case Exiv2::unsignedLong:
case Exiv2::signedShort:
case Exiv2::signedLong: {
if (value->count() == 1 && !forceSeq) {
return KisMetaData::Value((int)value->toLong());
} else {
QList<KisMetaData::Value> array;
for (int i = 0; i < value->count(); i++)
array.push_back(KisMetaData::Value((int)value->toLong(i)));
return KisMetaData::Value(array, arrayType);
}
}
case Exiv2::asciiString:
case Exiv2::string:
case Exiv2::comment: // look at kexiv2 for the problem about decoding correctly that tag
return KisMetaData::Value(value->toString().c_str());
case Exiv2::unsignedRational:
if(value->size() < 2)
{
dbgFile << "Invalid size :" << value->size() << " value =" << value->toString().c_str();
return KisMetaData::Value();
}
return KisMetaData::Value(KisMetaData::Rational(value->toRational().first , value->toRational().second));
case Exiv2::signedRational:
if(value->size() < 2)
{
dbgFile << "Invalid size :" << value->size() << " value =" << value->toString().c_str();
return KisMetaData::Value();
}
return KisMetaData::Value(KisMetaData::Rational(value->toRational().first , value->toRational().second));
case Exiv2::date:
case Exiv2::time:
return KisMetaData::Value(QDateTime::fromString(value->toString().c_str(), Qt::ISODate));
case Exiv2::xmpText:
case Exiv2::xmpAlt:
case Exiv2::xmpBag:
case Exiv2::xmpSeq:
case Exiv2::langAlt:
default: {
dbgFile << "Unknown type id :" << value->typeId() << " value =" << value->toString().c_str();
//Q_ASSERT(false); // This point must never be reached !
return KisMetaData::Value();
}
}
dbgFile << "Unknown type id :" << value->typeId() << " value =" << value->toString().c_str();
//Q_ASSERT(false); // This point must never be reached !
return KisMetaData::Value();
}
// Convert a QtVariant to an Exiv value
Exiv2::Value* variantToExivValue(const QVariant& variant, Exiv2::TypeId type)
{
switch (type) {
case Exiv2::undefined: {
QByteArray arr = QByteArray::fromBase64(variant.toString().toLatin1());
return new Exiv2::DataValue((Exiv2::byte*)arr.data(), arr.size());
}
case Exiv2::unsignedByte:
return new Exiv2::ValueType<uint16_t>(variant.toUInt(0));
case Exiv2::unsignedShort:
return new Exiv2::ValueType<uint16_t>(variant.toUInt(0));
case Exiv2::unsignedLong:
return new Exiv2::ValueType<uint32_t>(variant.toUInt(0));
case Exiv2::signedShort:
return new Exiv2::ValueType<int16_t>(variant.toInt(0));
case Exiv2::signedLong:
return new Exiv2::ValueType<int32_t>(variant.toInt(0));
case Exiv2::date: {
QDate date = variant.toDate();
return new Exiv2::DateValue(date.year(), date.month(), date.day());
}
case Exiv2::asciiString:
if (variant.type() == QVariant::DateTime) {
return new Exiv2::AsciiValue(qPrintable(variant.toDateTime().toString("yyyy:MM:dd hh:mm:ss")));
} else
return new Exiv2::AsciiValue(qPrintable(variant.toString()));
case Exiv2::string: {
if (variant.type() == QVariant::DateTime) {
return new Exiv2::StringValue(qPrintable(variant.toDateTime().toString("yyyy:MM:dd hh:mm:ss")));
} else
return new Exiv2::StringValue(qPrintable(variant.toString()));
}
case Exiv2::comment:
return new Exiv2::CommentValue(qPrintable(variant.toString()));
default:
dbgFile << "Unhandled type:" << type;
//Q_ASSERT(false);
return 0;
}
}
template<typename _TYPE_>
Exiv2::Value* arrayToExivValue(const KisMetaData::Value& value)
{
Exiv2::ValueType<_TYPE_>* ev = new Exiv2::ValueType<_TYPE_>();
for (int i = 0; i < value.asArray().size(); ++i) {
- ev->value_.push_back(qVariantValue<_TYPE_>(value.asArray()[i].asVariant()));
+ ev->value_.push_back(qvariant_cast<_TYPE_>(value.asArray()[i].asVariant()));
}
return ev;
}
// Convert a KisMetaData to an Exiv value
Exiv2::Value* kmdValueToExivValue(const KisMetaData::Value& value, Exiv2::TypeId type)
{
switch (value.type()) {
case KisMetaData::Value::Invalid:
return &*Exiv2::Value::create(Exiv2::invalidTypeId);
case KisMetaData::Value::Variant: {
return variantToExivValue(value.asVariant(), type);
}
case KisMetaData::Value::Rational:
//Q_ASSERT(type == Exiv2::signedRational || type == Exiv2::unsignedRational);
if (type == Exiv2::signedRational) {
return new Exiv2::ValueType<Exiv2::Rational>(Exiv2::Rational(value.asRational().numerator, value.asRational().denominator));
} else {
return new Exiv2::ValueType<Exiv2::URational>(Exiv2::URational(value.asRational().numerator, value.asRational().denominator));
}
case KisMetaData::Value::OrderedArray:
case KisMetaData::Value::UnorderedArray:
case KisMetaData::Value::AlternativeArray: {
switch (type) {
case Exiv2::unsignedByte:
return arrayToExivValue<uint16_t>(value);
case Exiv2::unsignedShort:
return arrayToExivValue<uint16_t>(value);
case Exiv2::unsignedLong:
return arrayToExivValue<uint32_t>(value);
case Exiv2::signedShort:
return arrayToExivValue<int16_t>(value);
case Exiv2::signedLong:
return arrayToExivValue<int32_t>(value);
case Exiv2::string: {
Exiv2::StringValue* ev = new Exiv2::StringValue();
for (int i = 0; i < value.asArray().size(); ++i) {
- ev->value_ += qVariantValue<QString>(value.asArray()[i].asVariant()).toLatin1().constData();
+ ev->value_ += qvariant_cast<QString>(value.asArray()[i].asVariant()).toLatin1().constData();
if (i != value.asArray().size() - 1) ev->value_ += ',';
}
return ev;
}
default:
dbgFile << type << " " << value;
//Q_ASSERT(false);
}
}
default:
dbgFile << type << " " << value;
//Q_ASSERT(false);
}
return 0;
}
Exiv2::Value* kmdValueToExivXmpValue(const KisMetaData::Value& value)
{
//Q_ASSERT(value.type() != KisMetaData::Value::Structure);
switch (value.type()) {
case KisMetaData::Value::Invalid:
return new Exiv2::DataValue(Exiv2::invalidTypeId);
case KisMetaData::Value::Variant: {
QVariant var = value.asVariant();
if (var.type() == QVariant::Bool) {
if (var.toBool()) {
return new Exiv2::XmpTextValue("True");
} else {
return new Exiv2::XmpTextValue("False");
}
} else {
//Q_ASSERT(var.canConvert(QVariant::String));
return new Exiv2::XmpTextValue(var.toString().toLatin1().constData());
}
}
case KisMetaData::Value::Rational: {
QString rat = "%1 / %2";
rat = rat.arg(value.asRational().numerator);
rat = rat.arg(value.asRational().denominator);
return new Exiv2::XmpTextValue(rat.toLatin1().constData());
}
case KisMetaData::Value::AlternativeArray:
case KisMetaData::Value::OrderedArray:
case KisMetaData::Value::UnorderedArray: {
Exiv2::XmpArrayValue* arrV = new Exiv2::XmpArrayValue;
switch (value.type()) {
case KisMetaData::Value::OrderedArray:
arrV->setXmpArrayType(Exiv2::XmpValue::xaSeq);
break;
case KisMetaData::Value::UnorderedArray:
arrV->setXmpArrayType(Exiv2::XmpValue::xaBag);
break;
case KisMetaData::Value::AlternativeArray:
arrV->setXmpArrayType(Exiv2::XmpValue::xaAlt);
break;
default:
// Cannot happen
;
}
Q_FOREACH (const KisMetaData::Value& v, value.asArray()) {
Exiv2::Value* ev = kmdValueToExivXmpValue(v);
if (ev) {
arrV->read(ev->toString());
delete ev;
}
}
return arrV;
}
case KisMetaData::Value::LangArray: {
Exiv2::Value* arrV = new Exiv2::LangAltValue;
QMap<QString, KisMetaData::Value> langArray = value.asLangArray();
for (QMap<QString, KisMetaData::Value>::iterator it = langArray.begin();
it != langArray.end(); ++it) {
QString exivVal;
if (it.key() != "x-default") {
exivVal = "lang=" + it.key() + ' ';
}
//Q_ASSERT(it.value().type() == KisMetaData::Value::Variant);
QVariant var = it.value().asVariant();
//Q_ASSERT(var.type() == QVariant::String);
exivVal += var.toString();
arrV->read(exivVal.toLatin1().constData());
}
return arrV;
}
case KisMetaData::Value::Structure:
default: {
warnKrita << "KisExiv2: Unhandled value type";
return 0;
}
}
warnKrita << "KisExiv2: Unhandled value type";
return 0;
}
void KisExiv2::initialize()
{
// XXX_EXIV: make the exiv io backends real plugins
KisMetaData::IOBackendRegistry* ioreg = KisMetaData::IOBackendRegistry::instance();
ioreg->add(new KisIptcIO);
ioreg->add(new KisExifIO);
ioreg->add(new KisXMPIO);
}
diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp
index 7e3218371e..41aa35db36 100644
--- a/libs/ui/opengl/kis_opengl.cpp
+++ b/libs/ui/opengl/kis_opengl.cpp
@@ -1,602 +1,600 @@
/*
* Copyright (c) 2007 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "opengl/kis_opengl.h"
#include <QOpenGLContext>
#include <QOpenGLDebugLogger>
#include <QOpenGLFunctions>
#include <QApplication>
#include <QDesktopWidget>
#include <QPixmapCache>
#include <QDir>
#include <QFile>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QMessageBox>
#include <QRegularExpression>
#include <QStringList>
#include <QWindow>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kis_config.h>
#include <KisLoggingManager.h>
#include <boost/optional.hpp>
#ifndef GL_RENDERER
# define GL_RENDERER 0x1F01
#endif
namespace
{
bool defaultFormatIsSet = false;
bool isDebugEnabled = false;
bool isDebugSynchronous = false;
struct OpenGLCheckResult {
// bool contextValid = false;
int glMajorVersion = 0;
int glMinorVersion = 0;
bool supportsDeprecatedFunctions = false;
bool isOpenGLES = false;
QString rendererString;
QString driverVersionString;
OpenGLCheckResult(QOpenGLContext &context) {
if (!context.isValid()) {
return;
}
QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used
rendererString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_RENDERER)));
driverVersionString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_VERSION)));
glMajorVersion = context.format().majorVersion();
glMinorVersion = context.format().minorVersion();
supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions);
isOpenGLES = context.isOpenGLES();
}
bool isSupportedVersion() const {
return
#ifdef Q_OS_OSX
- ((glMajorVersion * 100 + glMinorVersion) >= 302
- && !rendererString.toLower().contains("amd")
- && !rendererString.toLower().contains("radeon"))
+ ((glMajorVersion * 100 + glMinorVersion) >= 302)
#else
(glMajorVersion >= 3 && (supportsDeprecatedFunctions || isOpenGLES)) ||
((glMajorVersion * 100 + glMinorVersion) == 201)
#endif
;
}
bool supportsLoD() const {
return (glMajorVersion * 100 + glMinorVersion) >= 300;
}
bool hasOpenGL3() const {
return (glMajorVersion * 100 + glMinorVersion) >= 302;
}
bool supportsFenceSync() const {
return glMajorVersion >= 3;
}
#ifdef Q_OS_WIN
// This is only for detecting whether ANGLE is being used.
// For detecting generic OpenGL ES please check isOpenGLES
bool isUsingAngle() const {
return rendererString.startsWith("ANGLE", Qt::CaseInsensitive);
}
#endif
};
boost::optional<OpenGLCheckResult> openGLCheckResult;
bool NeedsFenceWorkaround = false;
bool NeedsPixmapCacheWorkaround = false;
QString debugText("OpenGL Info\n **OpenGL not initialized**");
void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) {
qDebug() << "OpenGL:" << debugMessage;
}
}
#ifdef Q_OS_WIN
namespace
{
struct WindowsOpenGLStatus {
bool supportsDesktopGL = false;
bool supportsAngleD3D11 = false;
bool isQtPreferAngle = false;
bool overridePreferAngle = false; // override Qt to force ANGLE to be preferred
};
WindowsOpenGLStatus windowsOpenGLStatus = {};
KisOpenGL::OpenGLRenderer userRendererConfig;
KisOpenGL::OpenGLRenderer nextUserRendererConfig;
KisOpenGL::OpenGLRenderer currentRenderer;
QStringList qpaDetectionLog;
boost::optional<OpenGLCheckResult> checkQpaOpenGLStatus() {
QWindow surface;
surface.setSurfaceType(QSurface::OpenGLSurface);
surface.create();
QOpenGLContext context;
if (!context.create()) {
qDebug() << "OpenGL context cannot be created";
return boost::none;
}
if (!context.isValid()) {
qDebug() << "OpenGL context is not valid while checking Qt's OpenGL status";
return boost::none;
}
if (!context.makeCurrent(&surface)) {
qDebug() << "OpenGL context cannot be made current";
return boost::none;
}
return OpenGLCheckResult(context);
}
bool checkIsSupportedDesktopGL(const OpenGLCheckResult &checkResult) {
if (checkResult.isUsingAngle()) {
qWarning() << "ANGLE was being used when desktop OpenGL was wanted, assuming no desktop OpenGL support";
return false;
}
if (checkResult.isOpenGLES) {
qWarning() << "Got OpenGL ES instead of desktop OpenGL, this shouldn't happen!";
return false;
}
return checkResult.isSupportedVersion();
}
bool checkIsSupportedAngleD3D11(const OpenGLCheckResult &checkResult) {
if (!checkResult.isUsingAngle()) {
qWarning() << "Desktop OpenGL was being used when ANGLE was wanted, assuming no ANGLE support";
return false;
}
if (!checkResult.isOpenGLES) {
qWarning() << "Got desktop OpenGL instead of OpenGL ES, this shouldn't happen!";
return false;
}
// HACK: Block ANGLE with Direct3D9
// Direct3D9 does not give OpenGL ES 3.0
// Some versions of ANGLE returns OpenGL version 3.0 incorrectly
if (checkResult.rendererString.contains("Direct3D9", Qt::CaseInsensitive)) {
qWarning() << "ANGLE tried to use Direct3D9, Krita won't work with it";
return false;
}
return checkResult.isSupportedVersion();
}
void specialOpenGLVendorFilter(WindowsOpenGLStatus &status, const OpenGLCheckResult &checkResult) {
if (!status.supportsAngleD3D11) {
return;
}
// HACK: 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)
if (checkResult.rendererString.startsWith("Intel")) {
QRegularExpression regex("\\b\\d{2}\\.\\d{2}\\.\\d{2}\\.(\\d{4})\\b");
QRegularExpressionMatch match = regex.match(checkResult.driverVersionString);
if (match.hasMatch()) {
int driverBuild = match.captured(1).toInt();
if (driverBuild > 4636 && driverBuild < 4729) {
qDebug() << "Detected Intel driver build between 4636 and 4729, making ANGLE the preferred renderer";
status.overridePreferAngle = true;
}
}
}
}
} // namespace
/**
* This function probes the Qt Platform Abstraction (QPA) for OpenGL diagnostics
* information. The code works under the assumption that the bundled Qt is built
* with `-opengl dynamic` and includes support for ANGLE.
*
* This function is written for Qt 5.9.1. On other versions it might not work
* as well.
*/
void KisOpenGL::probeWindowsQpaOpenGL(int argc, char **argv, QString userRendererConfigString)
{
KIS_SAFE_ASSERT_RECOVER(defaultFormatIsSet) {
qWarning() << "Default OpenGL format was not set before calling KisOpenGL::probeWindowsQpaOpenGL. This might be a BUG!";
setDefaultFormat();
}
// Clear env var to prevent affecting tests
qunsetenv("QT_OPENGL");
boost::optional<OpenGLCheckResult> qpaDetectionResult;
qDebug() << "Probing Qt OpenGL detection:";
{
KisLoggingManager::ScopedLogCapturer logCapturer(
"qt.qpa.gl",
[](QtMsgType type, const QMessageLogContext &context, const QString &msg) {
Q_UNUSED(type)
Q_UNUSED(context)
qpaDetectionLog.append(msg);
}
);
{
QGuiApplication app(argc, argv);
qpaDetectionResult = checkQpaOpenGLStatus();
}
}
if (!qpaDetectionResult) {
qWarning() << "Could not initialize OpenGL context!";
return;
}
qDebug() << "Done probing Qt OpenGL detection";
windowsOpenGLStatus.isQtPreferAngle = qpaDetectionResult->isUsingAngle();
boost::optional<OpenGLCheckResult> checkResultAngle, checkResultDesktopGL;
if (qpaDetectionResult->isUsingAngle()) {
checkResultAngle = qpaDetectionResult;
// We already checked ANGLE, now check desktop OpenGL
qputenv("QT_OPENGL", "desktop");
qDebug() << "Checking desktop OpenGL...";
{
QGuiApplication app(argc, argv);
checkResultDesktopGL = checkQpaOpenGLStatus();
}
if (!checkResultDesktopGL) {
qWarning() << "Could not initialize OpenGL context!";
}
qDebug() << "Done checking desktop OpenGL";
qunsetenv("QT_OPENGL");
} else {
checkResultDesktopGL = qpaDetectionResult;
// We already checked desktop OpenGL, now check ANGLE
qputenv("QT_OPENGL", "angle");
qDebug() << "Checking ANGLE...";
{
QGuiApplication app(argc, argv);
checkResultAngle = checkQpaOpenGLStatus();
}
if (!checkResultAngle) {
qWarning() << "Could not initialize OpenGL context!";
}
qDebug() << "Done checking ANGLE";
qunsetenv("QT_OPENGL");
}
windowsOpenGLStatus.supportsDesktopGL =
checkResultDesktopGL && checkIsSupportedDesktopGL(*checkResultDesktopGL);
windowsOpenGLStatus.supportsAngleD3D11 =
checkResultAngle && checkIsSupportedAngleD3D11(*checkResultAngle);
// HACK: Filter specific buggy drivers not handled by Qt OpenGL buglist
if (checkResultDesktopGL) {
specialOpenGLVendorFilter(windowsOpenGLStatus, *checkResultDesktopGL);
}
userRendererConfig = convertConfigToOpenGLRenderer(userRendererConfigString);
if ((userRendererConfig == RendererDesktopGL && !windowsOpenGLStatus.supportsDesktopGL) ||
(userRendererConfig == RendererAngle && !windowsOpenGLStatus.supportsAngleD3D11)) {
// Set it to auto so we won't get stuck
userRendererConfig = RendererAuto;
}
nextUserRendererConfig = userRendererConfig;
switch (userRendererConfig) {
case RendererDesktopGL:
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true);
currentRenderer = RendererDesktopGL;
break;
case RendererAngle:
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true);
currentRenderer = RendererAngle;
break;
default:
if (windowsOpenGLStatus.isQtPreferAngle && windowsOpenGLStatus.supportsAngleD3D11) {
currentRenderer = RendererAngle;
} else if (windowsOpenGLStatus.overridePreferAngle && windowsOpenGLStatus.supportsAngleD3D11) {
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true);
currentRenderer = RendererAngle;
} else if (!windowsOpenGLStatus.isQtPreferAngle && windowsOpenGLStatus.supportsDesktopGL) {
currentRenderer = RendererDesktopGL;
} else {
currentRenderer = RendererNone;
}
break;
}
}
KisOpenGL::OpenGLRenderer KisOpenGL::getCurrentOpenGLRenderer()
{
return currentRenderer;
}
KisOpenGL::OpenGLRenderer KisOpenGL::getQtPreferredOpenGLRenderer()
{
return (windowsOpenGLStatus.isQtPreferAngle || windowsOpenGLStatus.overridePreferAngle)
? RendererAngle : RendererDesktopGL;
}
KisOpenGL::OpenGLRenderers KisOpenGL::getSupportedOpenGLRenderers()
{
return RendererAuto |
(windowsOpenGLStatus.supportsDesktopGL ? RendererDesktopGL : static_cast<OpenGLRenderer>(0)) |
(windowsOpenGLStatus.supportsAngleD3D11 ? RendererAngle : static_cast<OpenGLRenderers>(0));
}
KisOpenGL::OpenGLRenderer KisOpenGL::getUserOpenGLRendererConfig()
{
return userRendererConfig;
}
KisOpenGL::OpenGLRenderer KisOpenGL::getNextUserOpenGLRendererConfig()
{
return nextUserRendererConfig;
}
void KisOpenGL::setNextUserOpenGLRendererConfig(KisOpenGL::OpenGLRenderer renderer)
{
nextUserRendererConfig = renderer;
}
QString KisOpenGL::convertOpenGLRendererToConfig(KisOpenGL::OpenGLRenderer renderer)
{
switch (renderer) {
case RendererDesktopGL:
return QStringLiteral("desktop");
case RendererAngle:
return QStringLiteral("angle");
default:
return QStringLiteral("auto");
}
}
KisOpenGL::OpenGLRenderer KisOpenGL::convertConfigToOpenGLRenderer(QString renderer)
{
if (renderer == "desktop") {
return RendererDesktopGL;
} else if (renderer == "angle") {
return RendererAngle;
} else {
return RendererAuto;
}
}
#endif
void KisOpenGL::initialize()
{
if (openGLCheckResult) return;
KIS_SAFE_ASSERT_RECOVER(defaultFormatIsSet) {
qWarning() << "Default OpenGL format was not set before calling KisOpenGL::initialize. This might be a BUG!";
setDefaultFormat();
}
// we need a QSurface active to get our GL functions from the context
QWindow surface;
surface.setSurfaceType( QSurface::OpenGLSurface );
surface.create();
QOpenGLContext context;
if (!context.create()) {
qDebug() << "OpenGL context cannot be created";
return;
}
if (!context.isValid()) {
qDebug() << "OpenGL context is not valid";
return;
}
if (!context.makeCurrent(&surface)) {
qDebug() << "OpenGL context cannot be made current";
return;
}
QOpenGLFunctions *funcs = context.functions();
openGLCheckResult = OpenGLCheckResult(context);
debugText.clear();
QDebug debugOut(&debugText);
debugOut << "OpenGL Info";
debugOut << "\n Vendor: " << reinterpret_cast<const char *>(funcs->glGetString(GL_VENDOR));
debugOut << "\n Renderer: " << openGLCheckResult->rendererString;
debugOut << "\n Version: " << openGLCheckResult->driverVersionString;
debugOut << "\n Shading language: " << reinterpret_cast<const char *>(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION));
debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat();
debugOut << "\n Current format: " << context.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;
#ifdef Q_OS_WIN
debugOut << "\n\nQPA OpenGL Detection Info";
debugOut << "\n supportsDesktopGL:" << windowsOpenGLStatus.supportsDesktopGL;
debugOut << "\n supportsAngleD3D11:" << windowsOpenGLStatus.supportsAngleD3D11;
debugOut << "\n isQtPreferAngle:" << windowsOpenGLStatus.isQtPreferAngle;
debugOut << "\n overridePreferAngle:" << windowsOpenGLStatus.overridePreferAngle;
debugOut << "\n== log ==\n";
debugOut.noquote();
debugOut << qpaDetectionLog.join('\n');
debugOut.resetFormat();
debugOut << "\n== end log ==";
#endif
qDebug().noquote() << debugText;
}
void KisOpenGL::initializeContext(QOpenGLContext *ctx)
{
KisConfig cfg;
initialize();
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(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(QDesktopServices::storageLocation(QDesktopServices::TempLocation) + "/krita-opengl.txt");
+ 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();
// Check if we have a bugged driver that needs fence workaround
bool isOnX11 = false;
#ifdef HAVE_X11
isOnX11 = true;
#endif
if ((isOnX11 && openGLCheckResult->rendererString.startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) {
NeedsFenceWorkaround = true;
}
/**
* NVidia + Qt's openGL don't play well together and one cannot
* draw a pixmap on a widget more than once in one rendering cycle.
*
* It can be workarounded by drawing strictly via QPixmapCache and
* only when the pixmap size in bigger than doubled size of the
* display framebuffer. That is for 8-bit HD display, you should have
* a cache bigger than 16 MiB. Don't ask me why. (DK)
*
* See bug: https://bugs.kde.org/show_bug.cgi?id=361709
*
* TODO: check if this workaround is still needed after merging
* Qt5+openGL3 branch.
*/
if (vendor.toUpper().contains("NVIDIA")) {
NeedsPixmapCacheWorkaround = true;
const QRect screenSize = QApplication::desktop()->screenGeometry();
const int minCacheSize = 20 * 1024;
const int cacheSize = 2048 + 2 * 4 * screenSize.width() * screenSize.height() / 1024; //KiB
QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize));
}
}
const QString &KisOpenGL::getDebugText()
{
initialize();
return debugText;
}
// 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->supportsLoD();
}
bool KisOpenGL::hasOpenGL3()
{
initialize();
return openGLCheckResult->hasOpenGL3();
}
bool KisOpenGL::hasOpenGLES()
{
initialize();
return openGLCheckResult->isOpenGLES;
}
bool KisOpenGL::supportsFenceSync()
{
initialize();
return openGLCheckResult->supportsFenceSync();
}
bool KisOpenGL::needsFenceWorkaround()
{
initialize();
return NeedsFenceWorkaround;
}
bool KisOpenGL::needsPixmapCacheWorkaround()
{
initialize();
return NeedsPixmapCacheWorkaround;
}
void KisOpenGL::setDefaultFormat(bool enableDebug, bool debugSynchronous)
{
if (defaultFormatIsSet) {
return;
}
defaultFormatIsSet = true;
QSurfaceFormat format;
#ifdef Q_OS_OSX
format.setVersion(3, 2);
format.setProfile(QSurfaceFormat::CoreProfile);
#else
// XXX This can be removed once we move to Qt5.7
format.setVersion(3, 0);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setOptions(QSurfaceFormat::DeprecatedFunctions);
#endif
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
format.setSwapInterval(0); // Disable vertical refresh syncing
isDebugEnabled = enableDebug;
if (enableDebug) {
format.setOption(QSurfaceFormat::DebugContext, true);
isDebugSynchronous = debugSynchronous;
qDebug() << "QOpenGLDebugLogger will be enabled, synchronous:" << debugSynchronous;
}
QSurfaceFormat::setDefaultFormat(format);
}
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 0d72cbeb85..6174bfecfe 100644
--- a/libs/ui/opengl/kis_opengl_canvas2.cpp
+++ b/libs/ui/opengl/kis_opengl_canvas2.cpp
@@ -1,909 +1,904 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2006-2013
* Copyright (C) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#define GL_GLEXT_PROTOTYPES
#include "opengl/kis_opengl_canvas2.h"
#include "opengl/kis_opengl_canvas2_p.h"
#include "opengl/kis_opengl_shader_loader.h"
#include "opengl/kis_opengl_canvas_debugger.h"
#include "canvas/kis_canvas2.h"
#include "canvas/kis_coordinates_converter.h"
#include "canvas/kis_display_filter.h"
#include "canvas/kis_display_color_converter.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_debug.h"
#include <QPainter>
#include <QPainterPath>
#include <QPointF>
#include <QMatrix>
#include <QTransform>
#include <QThread>
#include <QFile>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QMessageBox>
#ifndef Q_OS_OSX
#include <QOpenGLFunctions_2_1>
#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;
Sync::deleteSync(glSyncObject);
}
bool canvasInitialized{false};
KisOpenGLImageTexturesSP openGLImageTextures;
KisOpenGLShaderLoader shaderLoader;
KisShaderProgram *displayShader{0};
KisShaderProgram *checkerShader{0};
KisShaderProgram *solidColorShader{0};
bool displayShaderCompiledWithDisplayFilterSupport{false};
GLfloat checkSizeScale;
bool scrollCheckers;
QSharedPointer<KisDisplayFilter> displayFilter;
KisOpenGL::FilterMode filterMode;
bool proofingConfigIsUpdated=false;
GLsync glSyncObject{0};
bool wrapAroundMode{false};
// Stores a quad for drawing the canvas
QOpenGLVertexArrayObject quadVAO;
QOpenGLBuffer quadBuffers[2];
// Stores data for drawing tool outlines
QOpenGLVertexArrayObject outlineVAO;
QOpenGLBuffer lineBuffer;
QVector3D vertices[6];
QVector2D texCoords[6];
#ifndef Q_OS_OSX
QOpenGLFunctions_2_1 *glFn201;
#endif
qreal pixelGridDrawingThreshold;
bool pixelGridEnabled;
QColor gridColor;
int xToColWithWrapCompensation(int x, const QRect &imageRect) {
int firstImageColumn = openGLImageTextures->xToCol(imageRect.left());
int lastImageColumn = openGLImageTextures->xToCol(imageRect.right());
int colsPerImage = lastImageColumn - firstImageColumn + 1;
int numWraps = floor(qreal(x) / imageRect.width());
int remainder = x - imageRect.width() * numWraps;
return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder);
}
int yToRowWithWrapCompensation(int y, const QRect &imageRect) {
int firstImageRow = openGLImageTextures->yToRow(imageRect.top());
int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom());
int rowsPerImage = lastImageRow - firstImageRow + 1;
int numWraps = floor(qreal(y) / imageRect.height());
int remainder = y - imageRect.height() * numWraps;
return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder);
}
};
KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas,
KisCoordinatesConverter *coordinatesConverter,
QWidget *parent,
KisImageWSP image,
KisDisplayColorConverter *colorConverter)
: QOpenGLWidget(parent)
, KisCanvasWidgetBase(canvas, coordinatesConverter)
, d(new Private())
{
KisConfig cfg;
cfg.setCanvasState("OPENGL_STARTED");
d->openGLImageTextures =
KisOpenGLImageTextures::getImageTextures(image,
colorConverter->monitorProfile(),
colorConverter->renderingIntent(),
colorConverter->conversionFlags());
setAcceptDrops(true);
setAutoFillBackground(false);
setFocusPolicy(Qt::StrongFocus);
setAttribute(Qt::WA_NoSystemBackground, true);
#ifdef Q_OS_OSX
setAttribute(Qt::WA_AcceptTouchEvents, false);
#else
setAttribute(Qt::WA_AcceptTouchEvents, true);
#endif
setAttribute(Qt::WA_InputMethodEnabled, true);
setAttribute(Qt::WA_DontCreateNativeAncestors, true);
setDisplayFilterImpl(colorConverter->displayFilter(), true);
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
slotConfigChanged();
cfg.writeEntry("canvasState", "OPENGL_SUCCESS");
}
KisOpenGLCanvas2::~KisOpenGLCanvas2()
{
delete d;
}
-bool KisOpenGLCanvas2::needsFpsDebugging() const
-{
- return KisOpenglCanvasDebugger::instance()->showFpsOnCanvas();
-}
-
void KisOpenGLCanvas2::setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)
{
setDisplayFilterImpl(displayFilter, false);
}
void KisOpenGLCanvas2::setDisplayFilterImpl(QSharedPointer<KisDisplayFilter> 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::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();
#ifndef Q_OS_OSX
if (!KisOpenGL::hasOpenGLES()) {
d->glFn201 = context()->versionFunctions<QOpenGLFunctions_2_1>();
if (!d->glFn201) {
warnUI << "Cannot obtain QOpenGLFunctions_2_1, glLogicOp cannot be used";
}
} else {
d->glFn201 = nullptr;
}
#endif
KisConfig cfg;
d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration());
d->openGLImageTextures->initGL(context()->functions());
d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize()));
initializeShaders();
// If we support OpenGL 3.2, then prepare our VAOs and VBOs for drawing
if (KisOpenGL::hasOpenGL3()) {
d->quadVAO.create();
d->quadVAO.bind();
glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE);
glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE);
// Create the vertex buffer object, it has 6 vertices with 3 components
d->quadBuffers[0].create();
d->quadBuffers[0].setUsagePattern(QOpenGLBuffer::StaticDraw);
d->quadBuffers[0].bind();
d->quadBuffers[0].allocate(d->vertices, 6 * 3 * sizeof(float));
glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0);
// Create the texture buffer object, it has 6 texture coordinates with 2 components
d->quadBuffers[1].create();
d->quadBuffers[1].setUsagePattern(QOpenGLBuffer::StaticDraw);
d->quadBuffers[1].bind();
d->quadBuffers[1].allocate(d->texCoords, 6 * 2 * sizeof(float));
glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
// Create the outline buffer, this buffer will store the outlines of
// tools and will frequently change data
d->outlineVAO.create();
d->outlineVAO.bind();
glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE);
// The outline buffer has a StreamDraw usage pattern, because it changes constantly
d->lineBuffer.create();
d->lineBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
d->lineBuffer.bind();
glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0);
}
Sync::init(context());
d->canvasInitialized = true;
}
/**
* Loads all shaders and reports compilation problems
*/
void KisOpenGLCanvas2::initializeShaders()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized);
delete d->checkerShader;
delete d->solidColorShader;
d->checkerShader = 0;
d->solidColorShader = 0;
try {
d->checkerShader = d->shaderLoader.loadCheckerShader();
d->solidColorShader = d->shaderLoader.loadSolidColorShader();
} 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;
qDebug() << "Shader Compilation Failure: " << context;
QMessageBox::critical(this, i18nc("@title:window", "Krita"),
QString(i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.")).arg(context),
QMessageBox::Close);
cfg.setUseOpenGL(false);
cfg.setCanvasState("OPENGL_FAILED");
}
void KisOpenGLCanvas2::resizeGL(int width, int height)
{
coordinatesConverter()->setCanvasWidgetSize(QSize(width, height));
paintGL();
}
void KisOpenGLCanvas2::paintGL()
{
if (!OPENGL_SUCCESS) {
KisConfig cfg;
cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED");
}
KisOpenglCanvasDebugger::instance()->nofityPaintRequested();
renderCanvasGL();
if (d->glSyncObject) {
Sync::deleteSync(d->glSyncObject);
}
d->glSyncObject = Sync::getSync();
QPainter gc(this);
renderDecorations(&gc);
gc.end();
if (!OPENGL_SUCCESS) {
KisConfig cfg;
cfg.writeEntry("canvasState", "OPENGL_SUCCESS");
OPENGL_SUCCESS = true;
}
}
void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path)
{
if (!d->solidColorShader->bind()) {
return;
}
// setup the mvp transformation
QMatrix4x4 projectionMatrix;
projectionMatrix.setToIdentity();
projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL);
// Set view/projection matrices
QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform());
modelMatrix.optimize();
modelMatrix = projectionMatrix * modelMatrix;
d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix);
if (!KisOpenGL::hasOpenGLES()) {
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
glEnable(GL_COLOR_LOGIC_OP);
#ifndef Q_OS_OSX
if (d->glFn201) {
d->glFn201->glLogicOp(GL_XOR);
}
#else
glLogicOp(GL_XOR);
#endif
} else {
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_ONE_MINUS_DST_COLOR, GL_ZERO, GL_ONE, GL_ONE);
}
KisConfig cfg;
QColor cursorColor = cfg.getCursorMainColor();
d->solidColorShader->setUniformValue(
d->solidColorShader->location(Uniform::FragmentColor),
QVector4D(cursorColor.redF(), cursorColor.greenF(), cursorColor.blueF(), 1.0f));
// Paint the tool outline
if (KisOpenGL::hasOpenGL3()) {
d->outlineVAO.bind();
d->lineBuffer.bind();
}
// Convert every disjointed subpath to a polygon and draw that polygon
QList<QPolygonF> subPathPolygons = path.toSubpathPolygons();
for (int i = 0; i < subPathPolygons.size(); i++) {
const QPolygonF& polygon = subPathPolygons.at(i);
QVector<QVector3D> vertices;
vertices.resize(polygon.count());
for (int j = 0; j < polygon.count(); j++) {
QPointF p = polygon.at(j);
vertices[j].setX(p.x());
vertices[j].setY(p.y());
}
if (KisOpenGL::hasOpenGL3()) {
d->lineBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float));
}
else {
d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData());
}
glDrawArrays(GL_LINE_STRIP, 0, vertices.size());
}
if (KisOpenGL::hasOpenGL3()) {
d->lineBuffer.release();
d->outlineVAO.release();
}
if (!KisOpenGL::hasOpenGLES()) {
glDisable(GL_COLOR_LOGIC_OP);
} else {
glDisable(GL_BLEND);
}
d->solidColorShader->release();
}
bool KisOpenGLCanvas2::isBusy() const
{
const bool isBusyStatus = Sync::syncStatus(d->glSyncObject) == Sync::Unsignaled;
KisOpenglCanvasDebugger::instance()->nofitySyncStatus(isBusyStatus);
return isBusyStatus;
}
void KisOpenGLCanvas2::drawCheckers()
{
if (!d->checkerShader) {
return;
}
KisCoordinatesConverter *converter = coordinatesConverter();
QTransform textureTransform;
QTransform modelTransform;
QRectF textureRect;
QRectF modelRect;
QRectF viewportRect = !d->wrapAroundMode ?
converter->imageRectInViewportPixels() :
converter->widgetToViewport(this->rect());
converter->getOpenGLCheckersInfo(viewportRect,
&textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers);
textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE,
d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE);
if (!d->checkerShader->bind()) {
qWarning() << "Could not bind checker shader";
return;
}
QMatrix4x4 projectionMatrix;
projectionMatrix.setToIdentity();
projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL);
// Set view/projection matrices
QMatrix4x4 modelMatrix(modelTransform);
modelMatrix.optimize();
modelMatrix = projectionMatrix * modelMatrix;
d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix);
QMatrix4x4 textureMatrix(textureTransform);
d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix);
//Setup the geometry for rendering
if (KisOpenGL::hasOpenGL3()) {
rectToVertices(d->vertices, modelRect);
d->quadBuffers[0].bind();
d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float));
rectToTexCoords(d->texCoords, textureRect);
d->quadBuffers[1].bind();
d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float));
}
else {
rectToVertices(d->vertices, modelRect);
d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices);
rectToTexCoords(d->texCoords, textureRect);
d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords);
}
// render checkers
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture());
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindTexture(GL_TEXTURE_2D, 0);
d->checkerShader->release();
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void KisOpenGLCanvas2::drawGrid()
{
if (!d->solidColorShader->bind()) {
return;
}
QMatrix4x4 projectionMatrix;
projectionMatrix.setToIdentity();
projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL);
// Set view/projection matrices
QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform());
modelMatrix.optimize();
modelMatrix = projectionMatrix * modelMatrix;
d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
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->lineBuffer.bind();
}
QRectF widgetRect(0,0, width(), height());
QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect));
QRect wr = widgetRectInImagePixels.toAlignedRect();
if (!d->wrapAroundMode) {
wr &= d->openGLImageTextures->storedImageBounds();
}
QPoint topLeftCorner = wr.topLeft();
QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1);
QVector<QVector3D> grid;
for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) {
grid.append(QVector3D(i, topLeftCorner.y(), 0));
grid.append(QVector3D(i, bottomRightCorner.y(), 0));
}
for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) {
grid.append(QVector3D(topLeftCorner.x(), i, 0));
grid.append(QVector3D(bottomRightCorner.x(), i, 0));
}
if (KisOpenGL::hasOpenGL3()) {
d->lineBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float));
}
else {
d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData());
}
glDrawArrays(GL_LINES, 0, grid.size());
if (KisOpenGL::hasOpenGL3()) {
d->lineBuffer.release();
d->outlineVAO.release();
}
d->solidColorShader->release();
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();
QMatrix4x4 projectionMatrix;
projectionMatrix.setToIdentity();
projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL);
// Set view/projection matrices
QMatrix4x4 modelMatrix(converter->imageToWidgetTransform());
modelMatrix.optimize();
modelMatrix = projectionMatrix * modelMatrix;
d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix);
QMatrix4x4 textureMatrix;
textureMatrix.setToIdentity();
d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix);
QRectF widgetRect(0,0, width(), height());
QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect));
qreal scaleX, scaleY;
converter->imageScale(&scaleX, &scaleY);
d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX);
d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize());
QRect ir = d->openGLImageTextures->storedImageBounds();
QRect wr = widgetRectInImagePixels.toAlignedRect();
if (!d->wrapAroundMode) {
// if we don't want to paint wrapping images, just limit the
// processing area, and the code will handle all the rest
wr &= ir;
}
int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir);
int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir);
int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir);
int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir);
int minColumn = d->openGLImageTextures->xToCol(ir.left());
int maxColumn = d->openGLImageTextures->xToCol(ir.right());
int minRow = d->openGLImageTextures->yToRow(ir.top());
int maxRow = d->openGLImageTextures->yToRow(ir.bottom());
int imageColumns = maxColumn - minColumn + 1;
int imageRows = maxRow - minRow + 1;
for (int col = firstColumn; col <= lastColumn; col++) {
for (int row = firstRow; row <= lastRow; row++) {
int effectiveCol = col;
int effectiveRow = row;
QPointF tileWrappingTranslation;
if (effectiveCol > maxColumn || effectiveCol < minColumn) {
int translationStep = floor(qreal(col) / imageColumns);
int originCol = translationStep * imageColumns;
effectiveCol = col - originCol;
tileWrappingTranslation.rx() = translationStep * ir.width();
}
if (effectiveRow > maxRow || effectiveRow < minRow) {
int translationStep = floor(qreal(row) / imageRows);
int originRow = translationStep * imageRows;
effectiveRow = row - originRow;
tileWrappingTranslation.ry() = translationStep * ir.height();
}
KisTextureTile *tile =
d->openGLImageTextures->getTextureTileCR(effectiveCol, effectiveRow);
if (!tile) {
warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet.";
continue;
}
/*
* We create a float rect here to workaround Qt's
* "history reasons" in calculation of right()
* and bottom() coordinates of integer rects.
*/
QRectF textureRect(tile->tileRectInTexturePixels());
QRectF modelRect(tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y()));
//Setup the geometry for rendering
if (KisOpenGL::hasOpenGL3()) {
rectToVertices(d->vertices, modelRect);
d->quadBuffers[0].bind();
d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float));
rectToTexCoords(d->texCoords, textureRect);
d->quadBuffers[1].bind();
d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float));
}
else {
rectToVertices(d->vertices, modelRect);
d->displayShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE);
d->displayShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices);
rectToTexCoords(d->texCoords, textureRect);
d->displayShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE);
d->displayShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords);
}
if (d->displayFilter) {
glActiveTexture(GL_TEXTURE0 + 1);
glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture());
d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1);
}
int currentLodPlane = tile->currentLodPlane();
if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) {
d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel),
(GLfloat) currentLodPlane);
}
glActiveTexture(GL_TEXTURE0);
tile->bindToActiveTexture();
if (currentLodPlane > 0) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
} else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
switch(d->filterMode) {
case KisOpenGL::NearestFilterMode:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
break;
case KisOpenGL::BilinearFilterMode:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
break;
case KisOpenGL::TrilinearFilterMode:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
break;
case KisOpenGL::HighQualityFiltering:
if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
break;
}
}
glDrawArrays(GL_TRIANGLES, 0, 6);
}
}
glBindTexture(GL_TEXTURE_2D, 0);
d->displayShader->release();
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisable(GL_BLEND);
}
void KisOpenGLCanvas2::slotConfigChanged()
{
KisConfig cfg;
d->checkSizeScale = KisOpenGLImageTextures::BACKGROUND_TEXTURE_CHECK_SIZE / static_cast<GLfloat>(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->pixelGridDrawingThreshold = cfg.getPixelGridDrawingThreshold();
d->pixelGridEnabled = cfg.pixelGridEnabled();
d->gridColor = cfg.getPixelGridColor();
notifyConfigChanged();
}
QVariant KisOpenGLCanvas2::inputMethodQuery(Qt::InputMethodQuery query) const
{
return processInputMethodQuery(query);
}
void KisOpenGLCanvas2::inputMethodEvent(QInputMethodEvent *event)
{
processInputMethodEvent(event);
}
void KisOpenGLCanvas2::renderCanvasGL()
{
// Draw the border (that is, clear the whole widget to the border color)
QColor widgetBackgroundColor = borderColor();
glClearColor(widgetBackgroundColor.redF(), widgetBackgroundColor.greenF(), widgetBackgroundColor.blueF(), 1.0);
glClear(GL_COLOR_BUFFER_BIT);
if ((d->displayFilter && d->displayFilter->updateShader()) ||
(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::setDisplayProfile(KisDisplayColorConverter *colorConverter)
{
d->openGLImageTextures->setMonitorProfile(colorConverter->monitorProfile(),
colorConverter->renderingIntent(),
colorConverter->conversionFlags());
}
void KisOpenGLCanvas2::channelSelectionChanged(const QBitArray &channelFlags)
{
d->openGLImageTextures->setChannelFlags(channelFlags);
}
void KisOpenGLCanvas2::finishResizingImage(qint32 w, qint32 h)
{
if (d->canvasInitialized) {
d->openGLImageTextures->slotImageSizeChanged(w, h);
}
}
KisUpdateInfoSP KisOpenGLCanvas2::startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags)
{
d->openGLImageTextures->setChannelFlags(channelFlags);
if (canvas()->proofingConfigUpdated()) {
d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration());
canvas()->setProofingConfigUpdated(false);
}
return d->openGLImageTextures->updateCache(rc, d->openGLImageTextures->image());
}
QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info)
{
// See KisQPainterCanvas::updateCanvasProjection for more info
bool isOpenGLUpdateInfo = dynamic_cast<KisOpenGLUpdateInfo*>(info.data());
if (isOpenGLUpdateInfo) {
d->openGLImageTextures->recalculateCache(info);
}
#ifdef Q_OS_OSX
/**
* There is a bug on OSX: if we issue frame redraw before the tiles finished
* uploading, the tiles will become corrupted. Depending on the GPU/driver
* version either the tile itself, or its mipmaps will become totally
* transparent.
*/
glFinish();
#endif
return QRect(); // FIXME: Implement dirty rect for OpenGL
}
bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next)
{
return focusNextPrevChild(next);
}
KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const
{
return d->openGLImageTextures;
}
diff --git a/libs/ui/opengl/kis_opengl_canvas2.h b/libs/ui/opengl/kis_opengl_canvas2.h
index 4cb092c0db..a9eec1bbf3 100644
--- a/libs/ui/opengl/kis_opengl_canvas2.h
+++ b/libs/ui/opengl/kis_opengl_canvas2.h
@@ -1,127 +1,125 @@
/*
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2006
* Copyright (C) Michael Abrahams <miabraha@gmail.com>, (C) 2015
*
* 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_OPENGL_CANVAS_2_H
#define KIS_OPENGL_CANVAS_2_H
#include <QOpenGLWidget>
#ifndef Q_OS_OSX
#include <QOpenGLFunctions>
#else
#include <QOpenGLFunctions_3_2_Core>
#endif
#include "canvas/kis_canvas_widget_base.h"
#include "opengl/kis_opengl_image_textures.h"
#include "kritaui_export.h"
#include "kis_ui_types.h"
class KisCanvas2;
class KisDisplayColorConverter;
class QOpenGLShaderProgram;
class QPainterPath;
#ifndef Q_MOC_RUN
#ifndef Q_OS_OSX
#define GLFunctions QOpenGLFunctions
#else
#define GLFunctions QOpenGLFunctions_3_2_Core
#endif
#endif
/**
* KisOpenGLCanvas is the widget that shows the actual image using OpenGL
*
* NOTE: if you change something in the event handling here, also change it
* in the qpainter canvas.
*
*/
class KRITAUI_EXPORT KisOpenGLCanvas2
: public QOpenGLWidget
#ifndef Q_MOC_RUN
, protected GLFunctions
#endif
, public KisCanvasWidgetBase
{
Q_OBJECT
public:
KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter);
~KisOpenGLCanvas2() override;
public: // QOpenGLWidget
void resizeGL(int width, int height) override;
void initializeGL() override;
void paintGL() override;
QVariant inputMethodQuery(Qt::InputMethodQuery query) const override;
void inputMethodEvent(QInputMethodEvent *event) override;
public:
void renderCanvasGL();
void renderDecorations(QPainter *painter);
void paintToolOutline(const QPainterPath &path);
- bool needsFpsDebugging() const;
-
public: // Implement kis_abstract_canvas_widget interface
void setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter) override;
void setWrapAroundViewingMode(bool value) override;
void channelSelectionChanged(const QBitArray &channelFlags) override;
void setDisplayProfile(KisDisplayColorConverter *colorConverter) override;
void finishResizingImage(qint32 w, qint32 h) override;
KisUpdateInfoSP startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) override;
QRect updateCanvasProjection(KisUpdateInfoSP info) override;
QWidget *widget() override {
return this;
}
bool isBusy() const override;
void setDisplayFilterImpl(QSharedPointer<KisDisplayFilter> displayFilter, bool initializing);
KisOpenGLImageTexturesSP openGLImageTextures() const;
-private Q_SLOTS:
+public Q_SLOTS:
void slotConfigChanged();
protected: // KisCanvasWidgetBase
bool callFocusNextPrevChild(bool next) override;
private:
void initializeShaders();
void initializeDisplayShader();
void reportFailedShaderCompilation(const QString &context);
void drawImage();
void drawCheckers();
void drawGrid();
private:
struct Private;
Private * const d;
};
#endif // KIS_OPENGL_CANVAS_2_H
diff --git a/libs/ui/opengl/kis_opengl_canvas_debugger.cpp b/libs/ui/opengl/kis_opengl_canvas_debugger.cpp
index 3e13bb3842..86df5a0bf9 100644
--- a/libs/ui/opengl/kis_opengl_canvas_debugger.cpp
+++ b/libs/ui/opengl/kis_opengl_canvas_debugger.cpp
@@ -1,115 +1,121 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_opengl_canvas_debugger.h"
#include <QGlobalStatic>
#include <QElapsedTimer>
#include <QDebug>
#include "kis_config.h"
-
+#include <kis_config_notifier.h>
struct KisOpenglCanvasDebugger::Private
{
Private()
: fpsCounter(0),
fpsSum(0),
syncFlaggedCounter(0),
syncFlaggedSum(0),
isEnabled(true) {}
QElapsedTimer time;
int fpsCounter;
int fpsSum;
int syncFlaggedCounter;
int syncFlaggedSum;
bool isEnabled;
};
Q_GLOBAL_STATIC(KisOpenglCanvasDebugger, s_instance)
KisOpenglCanvasDebugger::KisOpenglCanvasDebugger()
: m_d(new Private)
{
- KisConfig cfg;
- m_d->isEnabled = cfg.enableOpenGLFramerateLogging();
-
- if (m_d->isEnabled) {
- m_d->time.start();
- }
+ connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
+ slotConfigChanged();
}
KisOpenglCanvasDebugger::~KisOpenglCanvasDebugger()
{
}
KisOpenglCanvasDebugger*
KisOpenglCanvasDebugger::instance()
{
return s_instance;
}
bool KisOpenglCanvasDebugger::showFpsOnCanvas() const
{
return m_d->isEnabled;
}
qreal KisOpenglCanvasDebugger::accumulatedFps()
{
qreal value = 0;
if (m_d->fpsSum > 0) {
value = qreal(m_d->fpsCounter) / m_d->fpsSum * 1000.0;
}
return value;
}
+void KisOpenglCanvasDebugger::slotConfigChanged()
+{
+ KisConfig cfg;
+ m_d->isEnabled = cfg.enableOpenGLFramerateLogging();
+
+ if (m_d->isEnabled) {
+ m_d->time.start();
+ }
+}
+
void KisOpenglCanvasDebugger::nofityPaintRequested()
{
if (!m_d->isEnabled) return;
m_d->fpsSum += m_d->time.restart();
m_d->fpsCounter++;
if (m_d->fpsCounter > 100 && m_d->fpsSum > 0) {
qDebug() << "Requested FPS:" << qreal(m_d->fpsCounter) / m_d->fpsSum * 1000.0;
m_d->fpsSum = 0;
m_d->fpsCounter = 0;
}
}
void KisOpenglCanvasDebugger::nofitySyncStatus(bool isBusy)
{
if (!m_d->isEnabled) return;
m_d->syncFlaggedSum += isBusy;
m_d->syncFlaggedCounter++;
if (m_d->syncFlaggedCounter > 500 && m_d->syncFlaggedSum > 0) {
qDebug() << "glSync effectiveness:" << qreal(m_d->syncFlaggedSum) / m_d->syncFlaggedCounter;
m_d->syncFlaggedSum = 0;
m_d->syncFlaggedCounter = 0;
}
}
diff --git a/libs/ui/opengl/kis_opengl_canvas_debugger.h b/libs/ui/opengl/kis_opengl_canvas_debugger.h
index 8d7e605e96..293128e860 100644
--- a/libs/ui/opengl/kis_opengl_canvas_debugger.h
+++ b/libs/ui/opengl/kis_opengl_canvas_debugger.h
@@ -1,45 +1,49 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_OPENGL_CANVAS_DEBUGGER_H
#define __KIS_OPENGL_CANVAS_DEBUGGER_H
#include <QScopedPointer>
+#include <QObject>
-
-class KisOpenglCanvasDebugger
+class KisOpenglCanvasDebugger : public QObject
{
+ Q_OBJECT
public:
KisOpenglCanvasDebugger();
~KisOpenglCanvasDebugger();
static KisOpenglCanvasDebugger* instance();
bool showFpsOnCanvas() const;
void nofityPaintRequested();
void nofitySyncStatus(bool value);
qreal accumulatedFps();
+private Q_SLOTS:
+ void slotConfigChanged();
+
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_OPENGL_CANVAS_DEBUGGER_H */
diff --git a/libs/image/kis_projection_updates_filter.cpp b/libs/ui/opengl/kis_texture_tile_info_pool.cpp
similarity index 51%
copy from libs/image/kis_projection_updates_filter.cpp
copy to libs/ui/opengl/kis_texture_tile_info_pool.cpp
index e1493ec367..5c980aa19d 100644
--- a/libs/image/kis_projection_updates_filter.cpp
+++ b/libs/ui/opengl/kis_texture_tile_info_pool.cpp
@@ -1,36 +1,41 @@
/*
- * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Bernhard Liebl <poke1024@gmx.de>
*
* 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_projection_updates_filter.h"
+#include "kis_texture_tile_info_pool.h"
+KisTextureTileInfoPoolWorker::KisTextureTileInfoPoolWorker(KisTextureTileInfoPool *pool)
+ : m_pool(pool)
+ , m_compressor(1000, KisSignalCompressor::POSTPONE)
+{
+ connect(&m_compressor, SIGNAL(timeout()), this, SLOT(slotDelayedPurge()));
+}
-#include <QtGlobal>
-#include <QRect>
-
-KisProjectionUpdatesFilter::~KisProjectionUpdatesFilter()
+void KisTextureTileInfoPoolWorker::slotPurge(int pixelSize, int numFrees)
{
+ m_purge[pixelSize] = numFrees;
+ m_compressor.start();
}
-bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache)
+void KisTextureTileInfoPoolWorker::slotDelayedPurge()
{
- Q_UNUSED(image);
- Q_UNUSED(node);
- Q_UNUSED(rect);
- Q_UNUSED(resetAnimationCache);
- return true;
+ for (QMap<int, int>::const_iterator i = m_purge.constBegin(); i != m_purge.constEnd(); i++) {
+ m_pool->tryPurge(i.key(), i.value());
+ }
+
+ m_purge.clear();
}
diff --git a/libs/ui/opengl/kis_texture_tile_info_pool.h b/libs/ui/opengl/kis_texture_tile_info_pool.h
index 8eb5e861a8..d0b947f53b 100644
--- a/libs/ui/opengl/kis_texture_tile_info_pool.h
+++ b/libs/ui/opengl/kis_texture_tile_info_pool.h
@@ -1,182 +1,231 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_TEXTURE_TILE_INFO_POOL_H
#define __KIS_TEXTURE_TILE_INFO_POOL_H
#include <boost/pool/pool.hpp>
#include <QtGlobal>
#include <QVector>
#include <QMutex>
#include <QMutexLocker>
#include <QSharedPointer>
+#include <QApplication>
#include "kis_assert.h"
#include "kis_debug.h"
#include "kis_global.h"
+#include "kis_signal_compressor.h"
const int minPoolChunk = 32; // 8 MiB (default, with tilesize 256)
const int maxPoolChunk = 128; // 32 MiB (default, with tilesize 256)
const int freeThreshold = 64; // 16 MiB (default, with tilesize 256)
/**
* A pool for keeping the chunks of data of constant size. We have one
* such pool per used openGL tile size. The size of the chunk
* obviously depends on the size of the tile in pixels and the size of
* a single pixel in bytes.
*
* As soon as the number of allocations drops to zero, all the memory
* is returned back to the operating system. Please note, that there
* is *no way* of reclaiming even unused pool memory untill *all* the
* allocated chunks are free'd.
*/
class KisTextureTileInfoPoolSingleSize
{
public:
KisTextureTileInfoPoolSingleSize(int tileWidth, int tileHeight, int pixelSize)
: m_chunkSize(tileWidth * tileHeight * pixelSize),
m_pool(m_chunkSize, minPoolChunk, maxPoolChunk),
m_numAllocations(0),
- m_maxAllocations(0)
+ m_maxAllocations(0),
+ m_numFrees(0)
{
}
quint8* malloc() {
m_numAllocations++;
m_maxAllocations = qMax(m_maxAllocations, m_numAllocations);
return (quint8*)m_pool.malloc();
}
- void free(quint8 *ptr) {
+ bool free(quint8 *ptr) {
m_numAllocations--;
+ m_numFrees++;
m_pool.free(ptr);
KIS_ASSERT_RECOVER_NOOP(m_numAllocations >= 0);
- if (!m_numAllocations && m_maxAllocations > freeThreshold) {
- // qDebug() << "Purging memory" << ppVar(m_maxAllocations);
- m_pool.purge_memory();
- m_maxAllocations = 0;
- }
+ return !m_numAllocations && m_maxAllocations > freeThreshold;
}
int chunkSize() const {
return m_chunkSize;
}
+ int numFrees() const {
+ return m_numFrees;
+ }
+
+ void tryPurge(int numFrees) {
+ // checking numFrees here is asserting that there were no frees
+ // between the time we originally indicated the purge and now.
+ if (numFrees == m_numFrees && !m_numAllocations) {
+ m_pool.purge_memory();
+ m_maxAllocations = 0;
+ }
+ }
+
private:
const int m_chunkSize;
boost::pool<boost::default_user_allocator_new_delete> m_pool;
int m_numAllocations;
int m_maxAllocations;
+ int m_numFrees;
+};
+
+class KisTextureTileInfoPool;
+
+class KisTextureTileInfoPoolWorker : public QObject
+{
+ Q_OBJECT
+public:
+ KisTextureTileInfoPoolWorker(KisTextureTileInfoPool *pool);
+
+public Q_SLOTS:
+ void slotPurge(int pixelSize, int numFrees);
+ void slotDelayedPurge();
+
+private:
+ KisTextureTileInfoPool *m_pool;
+ KisSignalCompressor m_compressor;
+ QMap<int, int> m_purge;
};
/**
* A universal pool for keeping the openGL tile of different pixel
* sizes. The underlying pools are created for each pixel size on
* demand.
*/
-class KisTextureTileInfoPool
+class KisTextureTileInfoPool : public QObject
{
+ Q_OBJECT
public:
KisTextureTileInfoPool(int tileWidth, int tileHeight)
: m_tileWidth(tileWidth),
m_tileHeight(tileHeight)
{
+ m_worker = new KisTextureTileInfoPoolWorker(this);
+ m_worker->moveToThread(QApplication::instance()->thread());
+ connect(this, SIGNAL(purge(int, int)), m_worker, SLOT(slotPurge(int, int)));
}
~KisTextureTileInfoPool() {
+ delete m_worker;
qDeleteAll(m_pools);
}
/**
* Alloc a tile with the specified pixel size
*/
quint8* malloc(int pixelSize) {
QMutexLocker l(&m_mutex);
if (m_pools.size() <= pixelSize) {
m_pools.resize(pixelSize + 1);
}
if (!m_pools[pixelSize]) {
m_pools[pixelSize] =
new KisTextureTileInfoPoolSingleSize(m_tileWidth, m_tileHeight, pixelSize);
}
return m_pools[pixelSize]->malloc();
}
/**
* Free a tile with the specified pixel size
*/
void free(quint8 *ptr, int pixelSize) {
QMutexLocker l(&m_mutex);
- m_pools[pixelSize]->free(ptr);
+ KisTextureTileInfoPoolSingleSize *pool = m_pools[pixelSize];
+ if (pool->free(ptr)) {
+ emit purge(pixelSize, pool->numFrees());
+ }
}
/**
* \return the length of the chunks stored in the pool
*/
int chunkSize(int pixelSize) const {
QMutexLocker l(&m_mutex);
return m_pools[pixelSize]->chunkSize();
}
+ void tryPurge(int pixelSize, int numFrees) {
+ QMutexLocker l(&m_mutex);
+ m_pools[pixelSize]->tryPurge(numFrees);
+ }
+
+Q_SIGNALS:
+ void purge(int pixelSize, int numFrees);
+
private:
mutable QMutex m_mutex;
const int m_tileWidth;
const int m_tileHeight;
QVector<KisTextureTileInfoPoolSingleSize*> m_pools;
+ KisTextureTileInfoPoolWorker *m_worker;
};
typedef QSharedPointer<KisTextureTileInfoPool> KisTextureTileInfoPoolSP;
class KisTextureTileInfoPoolRegistry
{
typedef QWeakPointer<KisTextureTileInfoPool> KisTextureTileInfoPoolWSP;
typedef QPair<int, int> PoolId;
public:
KisTextureTileInfoPoolSP getPool(int tileWidth, int tileHeight) {
QMutexLocker l(&m_mutex);
PoolId id(tileWidth, tileHeight);
KisTextureTileInfoPoolSP pool =
m_storage[id].toStrongRef();
if (!pool) {
pool = toQShared(
new KisTextureTileInfoPool(tileWidth, tileHeight));
m_storage[id] = pool;
}
return pool;
}
private:
QMutex m_mutex;
QHash<PoolId, KisTextureTileInfoPoolWSP> m_storage;
};
#endif /* __KIS_TEXTURE_TILE_INFO_POOL_H */
diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt
index 40eef8169e..cc9d4629bc 100644
--- a/libs/ui/tests/CMakeLists.txt
+++ b/libs/ui/tests/CMakeLists.txt
@@ -1,177 +1,182 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
#add_subdirectory(scratchpad)
include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata
${CMAKE_SOURCE_DIR}/sdk/tests )
include(ECMAddTests)
macro_add_unittest_definitions()
ecm_add_tests(
kis_image_view_converter_test.cpp
squeezedcombobox_test.cpp
kis_shape_selection_test.cpp
kis_recorded_action_editor_test.cpp
kis_doc2_test.cpp
kis_coordinates_converter_test.cpp
kis_grid_config_test.cpp
kis_stabilized_events_sampler_test.cpp
kis_derived_resources_test.cpp
kis_brush_hud_properties_config_test.cpp
kis_shape_commands_test.cpp
kis_stop_gradient_editor_test.cpp
NAME_PREFIX "krita-ui-"
LINK_LIBRARIES kritaui Qt5::Test
)
ecm_add_tests(
kis_file_layer_test.cpp
kis_multinode_property_test.cpp
NAME_PREFIX "krita-ui-"
LINK_LIBRARIES kritaui kritaimage Qt5::Test
)
ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME krita-ui-KisSelectionDecorationTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-ui-KisNodeDummiesGraphTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-ui-KisNodeShapesGraphTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-ui-KisModelIndexConverterTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp
TEST_NAME krita-ui-KisCategorizedListModelTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_resource_server_provider_test.cpp modeltest.cpp
TEST_NAME krita-ui-KisResourceServerProviderTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-image-BaseNodeTest
LINK_LIBRARIES kritaimage kritaui Qt5::Test)
ecm_add_test(
kis_animation_exporter_test.cpp
TEST_NAME kritaui-animation_exporter_test
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
set(kis_node_view_test_SRCS kis_node_view_test.cpp ../../../sdk/tests/testutil.cpp)
qt5_add_resources(kis_node_view_test_SRCS ${krita_QRCS})
ecm_add_test(${kis_node_view_test_SRCS}
TEST_NAME krita-image-kis_node_view_test
LINK_LIBRARIES kritaimage kritaui Qt5::Test)
##### Tests that currently fail and should be fixed #####
include(KritaAddBrokenUnitTest)
krita_add_broken_unit_test(
kis_node_model_test.cpp modeltest.cpp
TEST_NAME krita-ui-kis_node_model_test
LINK_LIBRARIES kritaui Qt5::Test)
krita_add_broken_unit_test(
kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp
TEST_NAME krita-ui-kis_shape_controller_test
LINK_LIBRARIES kritaimage kritaui Qt5::Test)
krita_add_broken_unit_test(
kis_prescaled_projection_test.cpp
TEST_NAME krita-ui-kis_prescaled_projection_test
LINK_LIBRARIES kritaui Qt5::Test)
krita_add_broken_unit_test(
kis_exiv2_test.cpp
TEST_NAME krita-ui-KisExiv2Test
LINK_LIBRARIES kritaimage kritaui Qt5::Test)
krita_add_broken_unit_test(
kis_clipboard_test.cpp
TEST_NAME krita-ui-KisClipboardTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME krita-ui-FreehandStrokeTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
+krita_add_broken_unit_test(
+ FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
+ TEST_NAME krita-ui-FreehandStrokeBenchmark
+ LINK_LIBRARIES kritaui kritaimage Qt5::Test)
+
krita_add_broken_unit_test(
fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME krita-ui-FillProcessingVisitorTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME krita-ui-FilterStrokeTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_selection_manager_test.cpp
TEST_NAME krita-ui-KisSelectionManagerTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
#set_tests_properties(krita-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300)
krita_add_broken_unit_test(
kis_node_manager_test.cpp
TEST_NAME krita-ui-KisNodeManagerTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-ui-KisDummiesFacadeTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-ui-KisZoomAndPanTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
#set_tests_properties(krita-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300)
krita_add_broken_unit_test(
kis_action_manager_test.cpp
TEST_NAME krita-ui-KisActionManagerTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_categories_mapper_test.cpp testing_categories_mapper.cpp
TEST_NAME krita-ui-KisCategoriesMapperTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_asl_layer_style_serializer_test.cpp
TEST_NAME krita-ui-KisAslLayerStyleSerializerTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_animation_importer_test.cpp
TEST_NAME kritaui-animation_importer_test
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_animation_frame_cache_test.cpp
TEST_NAME kritaui-animation_frame_cache_test
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
ResourceBundleTest.cpp
TEST_NAME krita-resourcemanager-ResourceBundleTest
LINK_LIBRARIES kritaui kritalibbrush kritalibpaintop Qt5::Test)
# FIXME this test doesn't compile
#ecm_add_test(
# kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp
# TEST_NAME krita-ui-KisInputManagerTest
# LINK_LIBRARIES kritaui kritaimage Qt5::Test)
diff --git a/libs/ui/tests/FreehandStrokeBenchmark.cpp b/libs/ui/tests/FreehandStrokeBenchmark.cpp
new file mode 100644
index 0000000000..6e6a65305d
--- /dev/null
+++ b/libs/ui/tests/FreehandStrokeBenchmark.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FreehandStrokeBenchmark.h"
+
+#include <QTest>
+#include <KoCompositeOpRegistry.h>
+#include <KoColor.h>
+#include "stroke_testing_utils.h"
+#include "strokes/freehand_stroke.h"
+#include "kis_resources_snapshot.h"
+#include "kis_image.h"
+#include <brushengine/kis_paint_information.h>
+
+class FreehandStrokeBenchmarkTester : public utils::StrokeTester
+{
+public:
+ FreehandStrokeBenchmarkTester(const QString &presetFilename)
+ : StrokeTester("freehand_benchmark", QSize(5000, 5000), presetFilename)
+ {
+ setBaseFuzziness(3);
+ }
+
+ void setCpuCoresLimit(int value) {
+ m_cpuCoresLimit = value;
+ }
+
+protected:
+ using utils::StrokeTester::initImage;
+ void initImage(KisImageWSP image, KisNodeSP activeNode) override {
+ Q_UNUSED(activeNode);
+
+ if (m_cpuCoresLimit > 0) {
+ image->setWorkingThreadsLimit(m_cpuCoresLimit);
+ }
+ }
+
+ KisStrokeStrategy* createStroke(bool indirectPainting,
+ KisResourcesSnapshotSP resources,
+ KisImageWSP image) override {
+ Q_UNUSED(image);
+
+ FreehandStrokeStrategy::PainterInfo *painterInfo =
+ new FreehandStrokeStrategy::PainterInfo();
+
+ QScopedPointer<FreehandStrokeStrategy> stroke(
+ new FreehandStrokeStrategy(indirectPainting, COMPOSITE_ALPHA_DARKEN, resources, painterInfo, kundo2_noi18n("Freehand Stroke")));
+
+ return stroke.take();
+ }
+
+ void addPaintingJobs(KisImageWSP image,
+ KisResourcesSnapshotSP resources) override
+ {
+ addPaintingJobs(image, resources, 0);
+ }
+
+ void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources, int iteration) override {
+ Q_UNUSED(iteration);
+ Q_UNUSED(resources);
+
+ for (int y = 100; y < 4900; y += 300) {
+ KisPaintInformation pi1;
+ KisPaintInformation pi2;
+
+ pi1 = KisPaintInformation(QPointF(100, y), 0.5);
+ pi2 = KisPaintInformation(QPointF(4900, y + 100), 1.0);
+
+ QScopedPointer<KisStrokeJobData> data(
+ new FreehandStrokeStrategy::Data(0, pi1, pi2));
+
+ image->addJob(strokeId(), data.take());
+ }
+
+ image->addJob(strokeId(), new FreehandStrokeStrategy::UpdateData(true));
+ }
+
+private:
+ FreehandStrokeStrategy::PainterInfo *m_painterInfo;
+ int m_cpuCoresLimit = -1;
+};
+
+void benchmarkBrush(const QString &presetName)
+{
+ FreehandStrokeBenchmarkTester tester(presetName);
+
+ for (int i = 1; i <= QThread::idealThreadCount(); i++) {
+ tester.setCpuCoresLimit(i);
+ tester.benchmark();
+
+ qDebug() << qPrintable(QString("Cores: %1 Time: %2 (ms)").arg(i).arg(tester.lastStrokeTime()));
+ }
+}
+
+#include <KoResourcePaths.h>
+
+void FreehandStrokeBenchmark::initTestCase()
+{
+ KoResourcePaths::addResourceType("kis_brushes", "data", FILES_DATA_DIR);
+}
+
+void FreehandStrokeBenchmark::testDefaultTip()
+{
+ benchmarkBrush("testing_1000px_auto_deafult.kpp");
+}
+
+void FreehandStrokeBenchmark::testSoftTip()
+{
+ benchmarkBrush("testing_1000px_auto_soft.kpp");
+}
+
+void FreehandStrokeBenchmark::testGaussianTip()
+{
+ benchmarkBrush("testing_1000px_auto_gaussian.kpp");
+}
+
+void FreehandStrokeBenchmark::testStampTip()
+{
+ benchmarkBrush("testing_1000px_stamp_450_rotated.kpp");
+}
+
+void FreehandStrokeBenchmark::testColorsmudgeDefaultTip()
+{
+ benchmarkBrush("testing_200px_colorsmudge_default.kpp");
+}
+
+QTEST_MAIN(FreehandStrokeBenchmark)
diff --git a/plugins/paintops/chalk/chalk_paintop_plugin.h b/libs/ui/tests/FreehandStrokeBenchmark.h
similarity index 63%
copy from plugins/paintops/chalk/chalk_paintop_plugin.h
copy to libs/ui/tests/FreehandStrokeBenchmark.h
index 0d78033afd..943b611d9f 100644
--- a/plugins/paintops/chalk/chalk_paintop_plugin.h
+++ b/libs/ui/tests/FreehandStrokeBenchmark.h
@@ -1,36 +1,38 @@
/*
- * Copyright (c) 2008 Lukáš Tvrdý (lukast.dev@gmail.com)
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef CHALK_PAINTOP_PLUGIN_H_
-#define CHALK_PAINTOP_PLUGIN_H_
+#ifndef FREEHANDSTROKEBENCHMARK_H
+#define FREEHANDSTROKEBENCHMARK_H
-#include <QObject>
-#include <QVariant>
+#include <QtTest>
-/**
- * A plugin wrapper that adds the paintop factories to the paintop registry.
- */
-class ChalkPaintOpPlugin : public QObject
+class FreehandStrokeBenchmark : public QObject
{
Q_OBJECT
-public:
- ChalkPaintOpPlugin(QObject *parent, const QVariantList &);
- ~ChalkPaintOpPlugin() override;
+private Q_SLOTS:
+ void initTestCase();
+
+ void testDefaultTip();
+ void testSoftTip();
+ void testGaussianTip();
+ void testStampTip();
+
+ void testColorsmudgeDefaultTip();
};
-#endif // CHALK_PAINTOP_PLUGIN_H_
+#endif // FREEHANDSTROKEBENCHMARK_H
diff --git a/libs/ui/tests/data/3_texture.png b/libs/ui/tests/data/3_texture.png
new file mode 100644
index 0000000000..85fa56d26d
Binary files /dev/null and b/libs/ui/tests/data/3_texture.png differ
diff --git a/libs/ui/tests/data/testing_1000px_auto_deafult.kpp b/libs/ui/tests/data/testing_1000px_auto_deafult.kpp
new file mode 100644
index 0000000000..b3bddc8217
Binary files /dev/null and b/libs/ui/tests/data/testing_1000px_auto_deafult.kpp differ
diff --git a/libs/ui/tests/data/testing_1000px_auto_gaussian.kpp b/libs/ui/tests/data/testing_1000px_auto_gaussian.kpp
new file mode 100644
index 0000000000..cd03fe18d9
Binary files /dev/null and b/libs/ui/tests/data/testing_1000px_auto_gaussian.kpp differ
diff --git a/libs/ui/tests/data/testing_1000px_auto_soft.kpp b/libs/ui/tests/data/testing_1000px_auto_soft.kpp
new file mode 100644
index 0000000000..705bcee1e8
Binary files /dev/null and b/libs/ui/tests/data/testing_1000px_auto_soft.kpp differ
diff --git a/libs/ui/tests/data/testing_1000px_stamp_450_rotated.kpp b/libs/ui/tests/data/testing_1000px_stamp_450_rotated.kpp
new file mode 100644
index 0000000000..90a8a13e4c
Binary files /dev/null and b/libs/ui/tests/data/testing_1000px_stamp_450_rotated.kpp differ
diff --git a/libs/ui/tests/data/testing_200px_colorsmudge_default.kpp b/libs/ui/tests/data/testing_200px_colorsmudge_default.kpp
new file mode 100644
index 0000000000..fd45d050d8
Binary files /dev/null and b/libs/ui/tests/data/testing_200px_colorsmudge_default.kpp differ
diff --git a/libs/ui/tests/freehand_stroke_test.cpp b/libs/ui/tests/freehand_stroke_test.cpp
index 5d1f618c1c..041cc3acd3 100644
--- a/libs/ui/tests/freehand_stroke_test.cpp
+++ b/libs/ui/tests/freehand_stroke_test.cpp
@@ -1,187 +1,190 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "freehand_stroke_test.h"
#include <QTest>
#include <KoCompositeOpRegistry.h>
#include <KoColor.h>
#include "stroke_testing_utils.h"
#include "strokes/freehand_stroke.h"
#include "kis_resources_snapshot.h"
#include "kis_image.h"
#include "kis_painter.h"
#include <brushengine/kis_paint_information.h>
class FreehandStrokeTester : public utils::StrokeTester
{
public:
FreehandStrokeTester(const QString &presetFilename, bool useLod = false)
: StrokeTester(useLod ? "freehand-lod" : "freehand", QSize(500, 500), presetFilename),
m_useLod(useLod),
m_flipLineDirection(false)
{
+ setBaseFuzziness(3);
}
void setFlipLineDirection(bool value) {
m_flipLineDirection = value;
setNumIterations(2);
}
void setPaintColor(const QColor &color) {
m_paintColor.reset(new QColor(color));
}
protected:
using utils::StrokeTester::initImage;
void initImage(KisImageWSP image, KisNodeSP activeNode) override {
Q_UNUSED(activeNode);
if (m_useLod) {
image->setDesiredLevelOfDetail(1);
}
}
void beforeCheckingResult(KisImageWSP image, KisNodeSP activeNode) override {
Q_UNUSED(image)
Q_UNUSED(activeNode);
if (m_useLod) {
//image->testingSetLevelOfDetailsEnabled(true);
}
}
void modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image) override
{
modifyResourceManager(manager, image, 0);
}
void modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image,
int iteration) override {
if (m_paintColor && iteration > 0) {
QVariant i;
i.setValue(KoColor(*m_paintColor, image->colorSpace()));
manager->setResource(KoCanvasResourceManager::ForegroundColor, i);
}
}
KisStrokeStrategy* createStroke(bool indirectPainting,
KisResourcesSnapshotSP resources,
KisImageWSP image) override {
Q_UNUSED(image);
FreehandStrokeStrategy::PainterInfo *painterInfo =
new FreehandStrokeStrategy::PainterInfo();
QScopedPointer<FreehandStrokeStrategy> stroke(
new FreehandStrokeStrategy(indirectPainting, COMPOSITE_ALPHA_DARKEN, resources, painterInfo, kundo2_noi18n("Freehand Stroke")));
return stroke.take();
}
void addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources) override
{
addPaintingJobs(image, resources, 0);
}
void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources, int iteration) override {
+ Q_UNUSED(resources);
+
KisPaintInformation pi1;
KisPaintInformation pi2;
if (!iteration) {
pi1 = KisPaintInformation(QPointF(200, 200));
pi2 = KisPaintInformation(QPointF(300, 300));
} else {
pi1 = KisPaintInformation(QPointF(200, 300));
pi2 = KisPaintInformation(QPointF(300, 200));
}
QScopedPointer<KisStrokeJobData> data(
- new FreehandStrokeStrategy::Data(resources->currentNode(),
- 0, pi1, pi2));
+ new FreehandStrokeStrategy::Data(0, pi1, pi2));
image->addJob(strokeId(), data.take());
+ image->addJob(strokeId(), new FreehandStrokeStrategy::UpdateData(true));
}
private:
FreehandStrokeStrategy::PainterInfo *m_painterInfo;
bool m_useLod;
bool m_flipLineDirection;
QScopedPointer<QColor> m_paintColor;
};
void FreehandStrokeTest::testAutoBrushStroke()
{
FreehandStrokeTester tester("autobrush_300px.kpp");
tester.test();
}
void FreehandStrokeTest::testHatchingStroke()
{
FreehandStrokeTester tester("hatching_30px.kpp");
tester.test();
}
void FreehandStrokeTest::testColorSmudgeStroke()
{
FreehandStrokeTester tester("colorsmudge_predefined.kpp");
tester.test();
}
void FreehandStrokeTest::testAutoTextured17()
{
FreehandStrokeTester tester("auto_textured_17.kpp");
tester.test();
}
void FreehandStrokeTest::testAutoTextured38()
{
FreehandStrokeTester tester("auto_textured_38.kpp");
tester.test();
}
void FreehandStrokeTest::testMixDullCompositioning()
{
FreehandStrokeTester tester("Mix_dull.kpp");
tester.setFlipLineDirection(true);
tester.setPaintColor(Qt::red);
tester.test();
}
void FreehandStrokeTest::testAutoBrushStrokeLod()
{
FreehandStrokeTester tester("Basic_tip_default.kpp", true);
tester.testSimpleStroke();
}
void FreehandStrokeTest::testPredefinedBrushStrokeLod()
{
qsrand(QTime::currentTime().msec());
FreehandStrokeTester tester("testing_predefined_lod_spc13.kpp", true);
//FreehandStrokeTester tester("testing_predefined_lod.kpp", true);
tester.testSimpleStroke();
}
QTEST_MAIN(FreehandStrokeTest)
diff --git a/libs/ui/tests/modeltest.cpp b/libs/ui/tests/modeltest.cpp
index 9db9840c50..fc5948f573 100644
--- a/libs/ui/tests/modeltest.cpp
+++ b/libs/ui/tests/modeltest.cpp
@@ -1,545 +1,545 @@
/****************************************************************************
**
** Copyright (C) 2007 Trolltech ASA. All rights reserved.
**
** This file is part of the Qt Concurrent project on Trolltech Labs.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file. Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://www.trolltech.com/products/qt/opensource.html
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://www.trolltech.com/products/qt/licensing.html or contact the
** sales department at sales@trolltech.com.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/
#include <QtGui>
#include "modeltest.h"
Q_DECLARE_METATYPE(QModelIndex)
/*!
Connect to all of the models signals. Whenever anything happens recheck everything.
*/
ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false)
{
Q_ASSERT(model);
connect(model, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(runAllTests()));
connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(runAllTests()));
connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)),
this, SLOT(runAllTests()));
connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
this, SLOT(runAllTests()));
connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(runAllTests()));
connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
this, SLOT(runAllTests()));
connect(model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(runAllTests()));
connect(model, SIGNAL(layoutChanged()), this, SLOT(runAllTests()));
connect(model, SIGNAL(modelReset()), this, SLOT(runAllTests()));
connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(runAllTests()));
connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(runAllTests()));
connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(runAllTests()));
connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(runAllTests()));
// Special checks for inserting/removing
connect(model, SIGNAL(layoutAboutToBeChanged()),
this, SLOT(layoutAboutToBeChanged()));
connect(model, SIGNAL(layoutChanged()),
this, SLOT(layoutChanged()));
connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(rowsAboutToBeInserted(QModelIndex,int,int)));
connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)));
connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(rowsInserted(QModelIndex,int,int)));
connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(rowsRemoved(QModelIndex,int,int)));
runAllTests();
}
void ModelTest::runAllTests()
{
if (fetchingMore)
return;
nonDestructiveBasicTest();
rowCount();
columnCount();
hasIndex();
index();
parent();
data();
}
/*!
nonDestructiveBasicTest tries to call a number of the basic functions (not all)
to make sure the model doesn't outright segfault, testing the functions that makes sense.
*/
void ModelTest::nonDestructiveBasicTest()
{
Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex());
model->canFetchMore(QModelIndex());
Q_ASSERT(model->columnCount(QModelIndex()) >= 0);
Q_ASSERT(model->data(QModelIndex()) == QVariant());
fetchingMore = true;
model->fetchMore(QModelIndex());
fetchingMore = false;
Qt::ItemFlags flags = model->flags(QModelIndex());
Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0);
Q_UNUSED(flags);
model->hasChildren(QModelIndex());
model->hasIndex(0, 0);
model->headerData(0, Qt::Horizontal);
model->index(0, 0);
model->itemData(QModelIndex());
QVariant cache;
model->match(QModelIndex(), -1, cache);
model->mimeTypes();
Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
Q_ASSERT(model->rowCount() >= 0);
QVariant variant;
model->setData(QModelIndex(), variant, -1);
model->setHeaderData(-1, Qt::Horizontal, QVariant());
model->setHeaderData(0, Qt::Horizontal, QVariant());
model->setHeaderData(999999, Qt::Horizontal, QVariant());
QMap<int, QVariant> roles;
model->sibling(0, 0, QModelIndex());
model->span(QModelIndex());
model->supportedDropActions();
}
/*!
Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
Models that are dynamically populated are not as fully tested here.
*/
void ModelTest::rowCount()
{
// check top row
QModelIndex topIndex = model->index(0, 0, QModelIndex());
int rows = model->rowCount(topIndex);
Q_ASSERT(rows >= 0);
if (rows > 0)
Q_ASSERT(model->hasChildren(topIndex) == true);
QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
if (secondLevelIndex.isValid()) { // not the top level
// check a row count where parent is valid
rows = model->rowCount(secondLevelIndex);
Q_ASSERT(rows >= 0);
if (rows > 0)
Q_ASSERT(model->hasChildren(secondLevelIndex) == true);
}
// The models rowCount() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
*/
void ModelTest::columnCount()
{
// check top row
QModelIndex topIndex = model->index(0, 0, QModelIndex());
Q_ASSERT(model->columnCount(topIndex) >= 0);
// check a column count where parent is valid
QModelIndex childIndex = model->index(0, 0, topIndex);
if (childIndex.isValid())
Q_ASSERT(model->columnCount(childIndex) >= 0);
// columnCount() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::hasIndex()
*/
void ModelTest::hasIndex()
{
// Make sure that invalid values returns an invalid index
Q_ASSERT(model->hasIndex(-2, -2) == false);
Q_ASSERT(model->hasIndex(-2, 0) == false);
Q_ASSERT(model->hasIndex(0, -2) == false);
int rows = model->rowCount();
int columns = model->columnCount();
Q_UNUSED(columns);
// check out of bounds
Q_ASSERT(model->hasIndex(rows, columns) == false);
Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false);
if (rows > 0)
Q_ASSERT(model->hasIndex(0, 0) == true);
// hasIndex() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::index()
*/
void ModelTest::index()
{
// Make sure that invalid values returns an invalid index
Q_ASSERT(model->index(-2, -2) == QModelIndex());
Q_ASSERT(model->index(-2, 0) == QModelIndex());
Q_ASSERT(model->index(0, -2) == QModelIndex());
int rows = model->rowCount();
int columns = model->columnCount();
Q_UNUSED(columns);
if (rows == 0)
return;
// Catch off by one errors
Q_ASSERT(model->index(rows, columns) == QModelIndex());
Q_ASSERT(model->index(0, 0).isValid() == true);
// Make sure that the same index is *always* returned
QModelIndex a = model->index(0, 0);
QModelIndex b = model->index(0, 0);
Q_ASSERT(a == b);
// index() is tested more extensively in checkChildren(),
// but this catches the big mistakes
}
/*!
Tests model's implementation of QAbstractItemModel::parent()
*/
void ModelTest::parent()
{
// Make sure the model wont crash and will return an invalid QModelIndex
// when asked for the parent of an invalid index.
Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
if (model->rowCount() == 0)
return;
// Column 0 | Column 1 |
// QModelIndex() | |
// \- topIndex | topIndex1 |
// \- childIndex | childIndex1 |
// Common error test #1, make sure that a top level index has a parent
// that is a invalid QModelIndex.
QModelIndex topIndex = model->index(0, 0, QModelIndex());
Q_ASSERT(model->parent(topIndex) == QModelIndex());
// Common error test #2, make sure that a second level index has a parent
// that is the first level index.
if (model->rowCount(topIndex) > 0) {
QModelIndex childIndex = model->index(0, 0, topIndex);
Q_ASSERT(model->parent(childIndex) == topIndex);
}
// Common error test #3, the second column should NOT have the same children
// as the first column in a row.
// Usually the second column shouldn't have children.
QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
if (model->rowCount(topIndex1) > 0) {
QModelIndex childIndex = model->index(0, 0, topIndex);
QModelIndex childIndex1 = model->index(0, 0, topIndex1);
Q_ASSERT(childIndex != childIndex1);
}
// Full test, walk n levels deep through the model making sure that all
// parent's children correctly specify their parent.
checkChildren(QModelIndex());
}
/*!
Called from the parent() test.
A model that returns an index of parent X should also return X when asking
for the parent of the index.
This recursive function does pretty extensive testing on the whole model in an
effort to catch edge cases.
This function assumes that rowCount(), columnCount() and index() already work.
If they have a bug it will point it out, but the above tests should have already
found the basic bugs because it is easier to figure out the problem in
those tests then this one.
*/
void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth)
{
// First just try walking back up the tree.
QModelIndex p = parent;
while (p.isValid())
p = p.parent();
// For models that are dynamically populated
if (model->canFetchMore(parent)) {
fetchingMore = true;
model->fetchMore(parent);
fetchingMore = false;
}
int rows = model->rowCount(parent);
int columns = model->columnCount(parent);
if (rows > 0)
Q_ASSERT(model->hasChildren(parent));
// Some further testing against rows(), columns(), and hasChildren()
Q_ASSERT(rows >= 0);
Q_ASSERT(columns >= 0);
if (rows > 0)
Q_ASSERT(model->hasChildren(parent) == true);
//dbgKrita << "parent:" << model->data(parent).toString() << "rows:" << rows
// << "columns:" << columns << "parent column:" << parent.column();
Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false);
for (int r = 0; r < rows; ++r) {
if (model->canFetchMore(parent)) {
fetchingMore = true;
model->fetchMore(parent);
fetchingMore = false;
}
Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false);
for (int c = 0; c < columns; ++c) {
Q_ASSERT(model->hasIndex(r, c, parent) == true);
QModelIndex index = model->index(r, c, parent);
// rowCount() and columnCount() said that it existed...
Q_ASSERT(index.isValid() == true);
// index() should always return the same index when called twice in a row
QModelIndex modifiedIndex = model->index(r, c, parent);
Q_ASSERT(index == modifiedIndex);
// Make sure we get the same index if we request it twice in a row
QModelIndex a = model->index(r, c, parent);
QModelIndex b = model->index(r, c, parent);
Q_ASSERT(a == b);
// Some basic checking on the index that is returned
Q_ASSERT(index.model() == model);
Q_ASSERT(index.row() == r);
Q_ASSERT(index.column() == c);
// While you can technically return a QVariant usually this is a sign
// of an bug in data() Disable if this really is ok in your model.
Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true);
// If the next test fails here is some somewhat useful debug you play with.
/*
if (model->parent(index) != parent) {
dbgKrita << r << c << currentDepth << model->data(index).toString()
<< model->data(parent).toString();
dbgKrita << index << parent << model->parent(index);
// And a view that you can even use to show the model.
//QTreeView view;
//view.setModel(model);
//view.show();
}*/
// Check that we can get back our real parent.
Q_ASSERT(model->parent(index) == parent);
// recursively go down the children
if (model->hasChildren(index) && currentDepth < 10) {
//dbgKrita << r << c << "has children" << model->rowCount(index);
checkChildren(index, ++currentDepth);
}/* else { if (currentDepth >= 10) dbgKrita << "checked 10 deep"; };*/
// make sure that after testing the children that the index doesn't change.
QModelIndex newerIndex = model->index(r, c, parent);
Q_ASSERT(index == newerIndex);
}
}
}
/*!
Tests model's implementation of QAbstractItemModel::data()
*/
void ModelTest::data()
{
// Invalid index should return an invalid qvariant
Q_ASSERT(!model->data(QModelIndex()).isValid());
if (model->rowCount() == 0)
return;
// A valid index should have a valid QVariant data
Q_ASSERT(model->index(0, 0).isValid());
// shouldn't be able to set data on an invalid index
Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false);
// General Purpose roles that should return a QString
QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole);
if (variant.isValid()) {
- Q_ASSERT(qVariantCanConvert<QString>(variant));
+ Q_ASSERT(variant.canConvert<QString>());
}
variant = model->data(model->index(0, 0), Qt::StatusTipRole);
if (variant.isValid()) {
- Q_ASSERT(qVariantCanConvert<QString>(variant));
+ Q_ASSERT(variant.canConvert<QString>());
}
variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
if (variant.isValid()) {
- Q_ASSERT(qVariantCanConvert<QString>(variant));
+ Q_ASSERT(variant.canConvert<QString>());
}
// General Purpose roles that should return a QSize
variant = model->data(model->index(0, 0), Qt::SizeHintRole);
if (variant.isValid()) {
- Q_ASSERT(qVariantCanConvert<QSize>(variant));
+ Q_ASSERT(variant.canConvert<QSize>());
}
// General Purpose roles that should return a QFont
QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole);
if (fontVariant.isValid()) {
- Q_ASSERT(qVariantCanConvert<QFont>(fontVariant));
+ Q_ASSERT(fontVariant.canConvert<QFont>());
}
// Check that the alignment is one we know about
QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
if (textAlignmentVariant.isValid()) {
int alignment = textAlignmentVariant.toInt();
Q_UNUSED(alignment);
Q_ASSERT(alignment == Qt::AlignLeft ||
alignment == Qt::AlignRight ||
alignment == Qt::AlignHCenter ||
alignment == Qt::AlignJustify ||
alignment == Qt::AlignTop ||
alignment == Qt::AlignBottom ||
alignment == Qt::AlignVCenter ||
alignment == Qt::AlignCenter ||
alignment == Qt::AlignAbsolute ||
alignment == Qt::AlignLeading ||
alignment == Qt::AlignTrailing);
}
// General Purpose roles that should return a QColor
QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole);
if (colorVariant.isValid()) {
- Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
+ Q_ASSERT(colorVariant.canConvert<QColor>());
}
colorVariant = model->data(model->index(0, 0), Qt::TextColorRole);
if (colorVariant.isValid()) {
- Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
+ Q_ASSERT(colorVariant.canConvert<QColor>());
}
// Check that the "check state" is one we know about.
QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
if (checkStateVariant.isValid()) {
int state = checkStateVariant.toInt();
Q_UNUSED(state);
Q_ASSERT(state == Qt::Unchecked ||
state == Qt::PartiallyChecked ||
state == Qt::Checked);
}
}
/*!
Store what is about to be inserted to make sure it actually happens
\sa rowsInserted()
*/
void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
{
Q_UNUSED(end);
Changing c;
c.parent = parent;
c.oldSize = model->rowCount(parent);
c.last = model->data(model->index(start - 1, 0, parent));
c.next = model->data(model->index(start, 0, parent));
insert.push(c);
}
/*!
Confirm that what was said was going to happen actually did
\sa rowsAboutToBeInserted()
*/
void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end)
{
Q_UNUSED(parent);
Q_UNUSED(start);
Q_UNUSED(end);
Changing c = insert.pop();
Q_ASSERT(c.parent == parent);
Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent));
Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
/*
if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
dbgKrita << start << end;
for (int i=0; i < model->rowCount(); ++i)
dbgKrita << model->index(i, 0).data().toString();
dbgKrita << c.next << model->data(model->index(end + 1, 0, c.parent));
}
*/
Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent)));
}
void ModelTest::layoutAboutToBeChanged()
{
for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
changing.append(QPersistentModelIndex(model->index(i, 0)));
}
void ModelTest::layoutChanged()
{
for (int i = 0; i < changing.count(); ++i) {
QPersistentModelIndex p = changing[i];
Q_ASSERT(p == model->index(p.row(), p.column(), p.parent()));
}
changing.clear();
}
/*!
Store what is about to be inserted to make sure it actually happens
\sa rowsRemoved()
*/
void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
Changing c;
c.parent = parent;
c.oldSize = model->rowCount(parent);
c.last = model->data(model->index(start - 1, 0, parent));
c.next = model->data(model->index(end + 1, 0, parent));
remove.push(c);
}
/*!
Confirm that what was said was going to happen actually did
\sa rowsAboutToBeRemoved()
*/
void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end)
{
Q_UNUSED(parent);
Q_UNUSED(start);
Q_UNUSED(end);
Changing c = remove.pop();
Q_ASSERT(c.parent == parent);
Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent));
Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent)));
}
diff --git a/libs/ui/tool/KisStrokeSpeedMonitor.cpp b/libs/ui/tool/KisStrokeSpeedMonitor.cpp
new file mode 100644
index 0000000000..4dccf85d83
--- /dev/null
+++ b/libs/ui/tool/KisStrokeSpeedMonitor.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisStrokeSpeedMonitor.h"
+
+#include <QGlobalStatic>
+#include <QMutex>
+#include <QMutexLocker>
+
+#include <KisRollingMeanAccumulatorWrapper.h>
+#include "kis_paintop_preset.h"
+#include "kis_paintop_settings.h"
+
+#include "kis_config.h"
+#include "kis_config_notifier.h"
+#include "KisUpdateSchedulerConfigNotifier.h"
+
+
+Q_GLOBAL_STATIC(KisStrokeSpeedMonitor, s_instance)
+
+
+struct KisStrokeSpeedMonitor::Private
+{
+ static const int averageWindow = 10;
+
+ Private()
+ : avgCursorSpeed(averageWindow),
+ avgRenderingSpeed(averageWindow),
+ avgFps(averageWindow)
+ {
+ }
+
+ KisRollingMeanAccumulatorWrapper avgCursorSpeed;
+ KisRollingMeanAccumulatorWrapper avgRenderingSpeed;
+ KisRollingMeanAccumulatorWrapper avgFps;
+
+ qreal cachedAvgCursorSpeed = 0;
+ qreal cachedAvgRenderingSpeed = 0;
+ qreal cachedAvgFps = 0;
+
+ qreal lastCursorSpeed = 0;
+ qreal lastRenderingSpeed = 0;
+ qreal lastFps = 0;
+ bool lastStrokeSaturated = false;
+
+ QByteArray lastPresetMd5;
+ QString lastPresetName;
+ qreal lastPresetSize = 0;
+
+ bool haveStrokeSpeedMeasurement = true;
+
+ QMutex mutex;
+};
+
+KisStrokeSpeedMonitor::KisStrokeSpeedMonitor()
+ : m_d(new Private())
+{
+ connect(KisUpdateSchedulerConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetAccumulatedValues()));
+ connect(KisUpdateSchedulerConfigNotifier::instance(), SIGNAL(configChanged()), SIGNAL(sigStatsUpdated()));
+ connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
+
+ slotConfigChanged();
+}
+
+KisStrokeSpeedMonitor::~KisStrokeSpeedMonitor()
+{
+}
+
+KisStrokeSpeedMonitor *KisStrokeSpeedMonitor::instance()
+{
+ return s_instance;
+}
+
+bool KisStrokeSpeedMonitor::haveStrokeSpeedMeasurement() const
+{
+ return m_d->haveStrokeSpeedMeasurement;
+}
+
+void KisStrokeSpeedMonitor::setHaveStrokeSpeedMeasurement(bool value)
+{
+ m_d->haveStrokeSpeedMeasurement = value;
+}
+
+void KisStrokeSpeedMonitor::resetAccumulatedValues()
+{
+ m_d->avgCursorSpeed.reset(m_d->averageWindow);
+ m_d->avgRenderingSpeed.reset(m_d->averageWindow);
+ m_d->avgFps.reset(m_d->averageWindow);
+}
+
+void KisStrokeSpeedMonitor::slotConfigChanged()
+{
+ KisConfig cfg;
+ m_d->haveStrokeSpeedMeasurement = cfg.enableBrushSpeedLogging();
+ resetAccumulatedValues();
+ emit sigStatsUpdated();
+}
+
+void KisStrokeSpeedMonitor::notifyStrokeFinished(qreal cursorSpeed, qreal renderingSpeed, qreal fps, KisPaintOpPresetSP preset)
+{
+ if (qFuzzyCompare(cursorSpeed, 0.0) || qFuzzyCompare(renderingSpeed, 0.0)) return;
+
+ QMutexLocker locker(&m_d->mutex);
+
+ const bool isSamePreset =
+ m_d->lastPresetName == preset->name() &&
+ qFuzzyCompare(m_d->lastPresetSize, preset->settings()->paintOpSize());
+
+ ENTER_FUNCTION() << ppVar(isSamePreset);
+
+ if (!isSamePreset) {
+ resetAccumulatedValues();
+ m_d->lastPresetName = preset->name();
+ m_d->lastPresetSize = preset->settings()->paintOpSize();
+ }
+
+ m_d->lastCursorSpeed = cursorSpeed;
+ m_d->lastRenderingSpeed = renderingSpeed;
+ m_d->lastFps = fps;
+
+
+ static const qreal saturationSpeedThreshold = 0.30; // cursor speed should be at least 30% higher
+ m_d->lastStrokeSaturated = cursorSpeed / renderingSpeed > (1.0 + saturationSpeedThreshold);
+
+
+ if (m_d->lastStrokeSaturated) {
+ m_d->avgCursorSpeed(cursorSpeed);
+ m_d->avgRenderingSpeed(renderingSpeed);
+ m_d->avgFps(fps);
+
+ m_d->cachedAvgCursorSpeed = m_d->avgCursorSpeed.rollingMean();
+ m_d->cachedAvgRenderingSpeed = m_d->avgRenderingSpeed.rollingMean();
+ m_d->cachedAvgFps = m_d->avgFps.rollingMean();
+ }
+
+ emit sigStatsUpdated();
+
+
+ ENTER_FUNCTION() <<
+ QString(" CS: %1 RS: %2 FPS: %3 %4")
+ .arg(m_d->lastCursorSpeed, 5)
+ .arg(m_d->lastRenderingSpeed, 5)
+ .arg(m_d->lastFps, 5)
+ .arg(m_d->lastStrokeSaturated ? "(saturated)" : "");
+ ENTER_FUNCTION() <<
+ QString("ACS: %1 ARS: %2 AFPS: %3")
+ .arg(m_d->cachedAvgCursorSpeed, 5)
+ .arg(m_d->cachedAvgRenderingSpeed, 5)
+ .arg(m_d->cachedAvgFps, 5);
+}
+
+QString KisStrokeSpeedMonitor::lastPresetName() const
+{
+ return m_d->lastPresetName;
+}
+
+qreal KisStrokeSpeedMonitor::lastPresetSize() const
+{
+ return m_d->lastPresetSize;
+}
+
+qreal KisStrokeSpeedMonitor::lastCursorSpeed() const
+{
+ return m_d->lastCursorSpeed;
+}
+
+qreal KisStrokeSpeedMonitor::lastRenderingSpeed() const
+{
+ return m_d->lastRenderingSpeed;
+}
+
+qreal KisStrokeSpeedMonitor::lastFps() const
+{
+ return m_d->lastFps;
+}
+
+bool KisStrokeSpeedMonitor::lastStrokeSaturated() const
+{
+ return m_d->lastStrokeSaturated;
+}
+
+qreal KisStrokeSpeedMonitor::avgCursorSpeed() const
+{
+ return m_d->cachedAvgCursorSpeed;
+}
+
+qreal KisStrokeSpeedMonitor::avgRenderingSpeed() const
+{
+ return m_d->cachedAvgRenderingSpeed;
+}
+
+qreal KisStrokeSpeedMonitor::avgFps() const
+{
+ return m_d->cachedAvgFps;
+}
diff --git a/libs/ui/tool/KisStrokeSpeedMonitor.h b/libs/ui/tool/KisStrokeSpeedMonitor.h
new file mode 100644
index 0000000000..51536ed1b6
--- /dev/null
+++ b/libs/ui/tool/KisStrokeSpeedMonitor.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISSTROKESPEEDMONITOR_H
+#define KISSTROKESPEEDMONITOR_H
+
+#include <QObject>
+
+#include "kis_types.h"
+#include "kritaui_export.h"
+
+class KRITAUI_EXPORT KisStrokeSpeedMonitor : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString lastPresetName READ lastPresetName NOTIFY sigStatsUpdated)
+ Q_PROPERTY(qreal lastPresetSize READ lastPresetSize NOTIFY sigStatsUpdated)
+
+ Q_PROPERTY(qreal lastCursorSpeed READ lastCursorSpeed NOTIFY sigStatsUpdated)
+ Q_PROPERTY(qreal lastRenderingSpeed READ lastRenderingSpeed NOTIFY sigStatsUpdated)
+ Q_PROPERTY(qreal lastFps READ lastFps NOTIFY sigStatsUpdated)
+
+ Q_PROPERTY(bool lastStrokeSaturated READ lastCursorSpeed NOTIFY sigStatsUpdated)
+
+ Q_PROPERTY(qreal avgCursorSpeed READ avgCursorSpeed NOTIFY sigStatsUpdated)
+ Q_PROPERTY(qreal avgRenderingSpeed READ avgRenderingSpeed NOTIFY sigStatsUpdated)
+ Q_PROPERTY(qreal avgFps READ avgFps NOTIFY sigStatsUpdated)
+
+public:
+ KisStrokeSpeedMonitor();
+ ~KisStrokeSpeedMonitor();
+
+ static KisStrokeSpeedMonitor* instance();
+
+ bool haveStrokeSpeedMeasurement() const;
+
+ void notifyStrokeFinished(qreal cursorSpeed, qreal renderingSpeed, qreal fps, KisPaintOpPresetSP preset);
+
+
+ QString lastPresetName() const;
+ qreal lastPresetSize() const;
+
+ qreal lastCursorSpeed() const;
+ qreal lastRenderingSpeed() const;
+ qreal lastFps() const;
+ bool lastStrokeSaturated() const;
+
+ qreal avgCursorSpeed() const;
+ qreal avgRenderingSpeed() const;
+ qreal avgFps() const;
+
+
+Q_SIGNALS:
+ void sigStatsUpdated();
+
+public Q_SLOTS:
+ void setHaveStrokeSpeedMeasurement(bool value);
+
+private Q_SLOTS:
+ void resetAccumulatedValues();
+ void slotConfigChanged();
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KISSTROKESPEEDMONITOR_H
diff --git a/libs/ui/tool/kis_figure_painting_tool_helper.cpp b/libs/ui/tool/kis_figure_painting_tool_helper.cpp
index 96880b689f..549c8db0a9 100644
--- a/libs/ui/tool/kis_figure_painting_tool_helper.cpp
+++ b/libs/ui/tool/kis_figure_painting_tool_helper.cpp
@@ -1,151 +1,145 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_figure_painting_tool_helper.h"
#include <KoCanvasResourceManager.h>
#include "kis_resources_snapshot.h"
#include <kis_distance_information.h>
#include "kis_image.h"
#include "kis_painter.h"
KisFigurePaintingToolHelper::KisFigurePaintingToolHelper(const KUndo2MagicString &name,
KisImageWSP image,
KisNodeSP currentNode,
KoCanvasResourceManager *resourceManager,
KisPainter::StrokeStyle strokeStyle,
KisPainter::FillStyle fillStyle)
{
m_strokesFacade = image.data();
m_resources =
new KisResourcesSnapshot(image,
currentNode,
resourceManager);
m_resources->setStrokeStyle(strokeStyle);
m_resources->setFillStyle(fillStyle);
PainterInfo *painterInfo = new PainterInfo();
KisStrokeStrategy *stroke =
new FreehandStrokeStrategy(m_resources->needsIndirectPainting(),
m_resources->indirectPaintingCompositeOp(),
m_resources, painterInfo, name);
m_strokeId = m_strokesFacade->startStroke(stroke);
}
KisFigurePaintingToolHelper::~KisFigurePaintingToolHelper()
{
+ m_strokesFacade->addJob(m_strokeId,
+ new FreehandStrokeStrategy::UpdateData(true));
m_strokesFacade->endStroke(m_strokeId);
}
void KisFigurePaintingToolHelper::paintLine(const KisPaintInformation &pi0,
const KisPaintInformation &pi1)
{
m_strokesFacade->addJob(m_strokeId,
- new FreehandStrokeStrategy::Data(m_resources->currentNode(),
- 0,
+ new FreehandStrokeStrategy::Data(0,
pi0, pi1));
}
void KisFigurePaintingToolHelper::paintPolyline(const vQPointF &points)
{
m_strokesFacade->addJob(m_strokeId,
- new FreehandStrokeStrategy::Data(m_resources->currentNode(),
- 0,
+ new FreehandStrokeStrategy::Data(0,
FreehandStrokeStrategy::Data::POLYLINE,
points));
}
void KisFigurePaintingToolHelper::paintPolygon(const vQPointF &points)
{
m_strokesFacade->addJob(m_strokeId,
- new FreehandStrokeStrategy::Data(m_resources->currentNode(),
- 0,
+ new FreehandStrokeStrategy::Data(0,
FreehandStrokeStrategy::Data::POLYGON,
points));
}
void KisFigurePaintingToolHelper::paintRect(const QRectF &rect)
{
m_strokesFacade->addJob(m_strokeId,
- new FreehandStrokeStrategy::Data(m_resources->currentNode(),
- 0,
+ new FreehandStrokeStrategy::Data(0,
FreehandStrokeStrategy::Data::RECT,
rect));
}
void KisFigurePaintingToolHelper::paintEllipse(const QRectF &rect)
{
m_strokesFacade->addJob(m_strokeId,
- new FreehandStrokeStrategy::Data(m_resources->currentNode(),
- 0,
+ new FreehandStrokeStrategy::Data(0,
FreehandStrokeStrategy::Data::ELLIPSE,
rect));
}
void KisFigurePaintingToolHelper::paintPainterPath(const QPainterPath &path)
{
m_strokesFacade->addJob(m_strokeId,
- new FreehandStrokeStrategy::Data(m_resources->currentNode(),
- 0,
+ new FreehandStrokeStrategy::Data(0,
FreehandStrokeStrategy::Data::PAINTER_PATH,
path));
}
void KisFigurePaintingToolHelper::setFGColorOverride(const KoColor &color)
{
m_resources->setFGColorOverride(color);
}
void KisFigurePaintingToolHelper::setBGColorOverride(const KoColor &color)
{
m_resources->setBGColorOverride(color);
}
void KisFigurePaintingToolHelper::setSelectionOverride(KisSelectionSP m_selection)
{
m_resources->setSelectionOverride(m_selection);
}
void KisFigurePaintingToolHelper::setBrush(const KisPaintOpPresetSP &brush)
{
m_resources->setBrush(brush);
}
void KisFigurePaintingToolHelper::paintPainterPathQPen(const QPainterPath path, const QPen &pen, const KoColor &color)
{
m_strokesFacade->addJob(m_strokeId,
- new FreehandStrokeStrategy::Data(m_resources->currentNode(),
- 0,
+ new FreehandStrokeStrategy::Data(0,
FreehandStrokeStrategy::Data::QPAINTER_PATH,
path, pen, color));
}
void KisFigurePaintingToolHelper::paintPainterPathQPenFill(const QPainterPath path, const QPen &pen, const KoColor &color)
{
m_strokesFacade->addJob(m_strokeId,
- new FreehandStrokeStrategy::Data(m_resources->currentNode(),
- 0,
+ new FreehandStrokeStrategy::Data(0,
FreehandStrokeStrategy::Data::QPAINTER_PATH_FILL,
path, pen, color));
}
diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp
index dc20e63113..1e4f87d28e 100644
--- a/libs/ui/tool/kis_resources_snapshot.cpp
+++ b/libs/ui/tool/kis_resources_snapshot.cpp
@@ -1,394 +1,399 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_resources_snapshot.h"
#include <KoColor.h>
#include <resources/KoAbstractGradient.h>
#include <KoCompositeOpRegistry.h>
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paintop_settings.h>
#include <brushengine/kis_paintop_registry.h>
#include <kis_threaded_text_rendering_workaround.h>
#include <resources/KoPattern.h>
#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 "recorder/kis_recorded_paint_action.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;
- KoPattern *currentPattern;
+ KoPattern *currentPattern = 0;
KoAbstractGradient *currentGradient;
KisPaintOpPresetSP currentPaintOpPreset;
KisNodeSP currentNode;
qreal currentExposure;
KisFilterConfigurationSP currentGenerator;
QPointF axesCenter;
- bool mirrorMaskHorizontal;
- bool mirrorMaskVertical;
+ bool mirrorMaskHorizontal = false;
+ bool mirrorMaskVertical = false;
- quint8 opacity;
- QString compositeOpId;
+ quint8 opacity = OPACITY_OPAQUE_U8;
+ QString compositeOpId = COMPOSITE_OVER;
const KoCompositeOp *compositeOp;
- KisPainter::StrokeStyle strokeStyle;
- KisPainter::FillStyle fillStyle;
+ KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush;
+ KisPainter::FillStyle fillStyle = KisPainter::FillStyleForegroundColor;
- bool globalAlphaLock;
- qreal effectiveZoom;
- bool presetAllowsLod;
+ bool globalAlphaLock = false;
+ qreal effectiveZoom = 1.0;
+ bool presetAllowsLod = false;
KisSelectionSP selectionOverride;
};
KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceManager *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(KoCanvasResourceManager::ForegroundColor).value<KoColor>();
m_d->currentBgColor = resourceManager->resource(KoCanvasResourceManager::BackgroundColor).value<KoColor>();
m_d->currentPattern = resourceManager->resource(KisCanvasResourceProvider::CurrentPattern).value<KoPattern*>();
m_d->currentGradient = resourceManager->resource(KisCanvasResourceProvider::CurrentGradient).value<KoAbstractGradient*>();
/**
* We should deep-copy the preset, so that long-runnign actions
* will have correct brush parameters. Theoretically this cloniong
* can be expensive, but according to measurements, it takes
* something like 0.1 ms for an average preset.
*/
m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>()->clone();
#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<KisFilterConfiguration*>();
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::PresetAllowsLod).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);
/**
* 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::setupPaintAction(KisRecordedPaintAction *action)
{
action->setPaintOpPreset(m_d->currentPaintOpPreset);
action->setPaintIncremental(!needsIndirectPainting());
action->setPaintColor(m_d->currentFgColor);
action->setBackgroundColor(m_d->currentBgColor);
action->setGenerator(m_d->currentGenerator);
action->setGradient(m_d->currentGradient);
action->setPattern(m_d->currentPattern);
action->setOpacity(m_d->opacity / qreal(OPACITY_OPAQUE_U8));
action->setCompositeOp(m_d->compositeOp->id());
action->setStrokeStyle(m_d->strokeStyle);
action->setFillStyle(m_d->fillStyle);
}
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;
}
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->settings()->indirectPaintingCompositeOp();
}
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->selectionOverride) {
return m_d->selectionOverride;
}
KisSelectionSP selection = m_d->image ? m_d->image->globalSelection() : 0;
KisLayerSP layer = qobject_cast<KisLayer*>(m_d->currentNode.data());
KisSelectionMaskSP mask;
if((layer = qobject_cast<KisLayer*>(m_d->currentNode.data()))) {
selection = layer->selection();
} else if ((mask = dynamic_cast<KisSelectionMask*>(m_d->currentNode.data())) &&
mask->selection() == selection) {
selection = 0;
}
return selection;
}
bool KisResourcesSnapshot::needsAirbrushing() const
{
return m_d->currentPaintOpPreset->settings()->isAirbrushing();
}
qreal KisResourcesSnapshot::airbrushingInterval() const
{
return m_d->currentPaintOpPreset->settings()->airbrushInterval();
}
bool KisResourcesSnapshot::needsSpacingUpdates() const
{
return 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;
}
KoPattern* 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;
}
QBitArray KisResourcesSnapshot::channelLockFlags() const
{
QBitArray channelFlags;
KisPaintLayer *paintLayer;
if ((paintLayer = dynamic_cast<KisPaintLayer*>(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;
}
void KisResourcesSnapshot::setBrush(const KisPaintOpPresetSP &brush)
{
m_d->currentPaintOpPreset = brush;
}
diff --git a/libs/ui/tool/kis_resources_snapshot.h b/libs/ui/tool/kis_resources_snapshot.h
index dede9ba147..b223f401c1 100644
--- a/libs/ui/tool/kis_resources_snapshot.h
+++ b/libs/ui/tool/kis_resources_snapshot.h
@@ -1,106 +1,107 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#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 KoCanvasResourceManager;
class KoCompositeOp;
class KisPainter;
class KisPostExecutionUndoAdapter;
class KisRecordedPaintAction;
class KoPattern;
/**
* @brief The KisResourcesSnapshot class takes a snapshot of the various resources
* like colors and settings used at the begin of a stroke or a recording 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, KoCanvasResourceManager *resourceManager, KisDefaultBoundsBaseSP bounds = 0);
KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds = 0);
~KisResourcesSnapshot();
void setupPainter(KisPainter *painter);
// XXX: This was marked as KDE_DEPRECATED, but no althernative was
// given in the apidox.
void setupPaintAction(KisRecordedPaintAction *action);
KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const;
void setCurrentNode(KisNodeSP node);
void setStrokeStyle(KisPainter::StrokeStyle strokeStyle);
void setFillStyle(KisPainter::FillStyle fillStyle);
KisNodeSP currentNode() const;
KisImageSP image() const;
bool needsIndirectPainting() const;
QString indirectPaintingCompositeOp() 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;
KoPattern* currentPattern() const;
KoColor currentFgColor() const;
KoColor currentBgColor() const;
KisPaintOpPresetSP currentPaintOpPreset() 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<KisResourcesSnapshot> KisResourcesSnapshotSP;
#endif /* __KIS_RESOURCES_SNAPSHOT_H */
diff --git a/libs/ui/tool/kis_tool_freehand_helper.cpp b/libs/ui/tool/kis_tool_freehand_helper.cpp
index 5e74ba16d1..eb4490709f 100644
--- a/libs/ui/tool/kis_tool_freehand_helper.cpp
+++ b/libs/ui/tool/kis_tool_freehand_helper.cpp
@@ -1,982 +1,997 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_freehand_helper.h"
#include <QTimer>
#include <QQueue>
#include <klocalizedstring.h>
#include <KoPointerEvent.h>
#include <KoCanvasResourceManager.h>
#include "kis_algebra_2d.h"
#include "kis_distance_information.h"
#include "kis_painting_information_builder.h"
#include "kis_recording_adapter.h"
#include "kis_image.h"
#include "kis_painter.h"
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paintop_utils.h>
#include "kis_update_time_monitor.h"
#include "kis_stabilized_events_sampler.h"
#include "KisStabilizerDelayedPaintHelper.h"
#include "kis_config.h"
#include <math.h>
//#define DEBUG_BEZIER_CURVES
// Factor by which to scale the airbrush timer's interval, relative to the actual airbrushing rate.
// Setting this less than 1 makes the timer-generated pseudo-events happen faster than the desired
// airbrush rate, which can improve responsiveness.
const qreal AIRBRUSH_INTERVAL_FACTOR = 0.5;
// The amount of time, in milliseconds, to allow between updates of the spacing information. Only
// used when spacing updates between dabs are enabled.
const qreal SPACING_UPDATE_INTERVAL = 50.0;
// The amount of time, in milliseconds, to allow between updates of the timing information. Only
// used when airbrushing.
const qreal TIMING_UPDATE_INTERVAL = 50.0;
struct KisToolFreehandHelper::Private
{
KisPaintingInformationBuilder *infoBuilder;
KisRecordingAdapter *recordingAdapter;
KisStrokesFacade *strokesFacade;
KUndo2MagicString transactionText;
bool haveTangent;
QPointF previousTangent;
bool hasPaintAtLeastOnce;
QTime strokeTime;
QTimer strokeTimeoutTimer;
QVector<PainterInfo*> painterInfos;
KisResourcesSnapshotSP resources;
KisStrokeId strokeId;
KisPaintInformation previousPaintInformation;
KisPaintInformation olderPaintInformation;
KisSmoothingOptionsSP smoothingOptions;
// Timer used to generate paint updates periodically even without input events. This is only
// used for paintops that depend on timely updates even when the cursor is not moving, e.g. for
// airbrushing effects.
QTimer airbrushingTimer;
QList<KisPaintInformation> history;
QList<qreal> distanceHistory;
// Keeps track of past cursor positions. This is used to determine the drawing angle when
// drawing the brush outline or starting a stroke.
KisPaintOpUtils::PositionHistory lastCursorPos;
// Stabilizer data
QQueue<KisPaintInformation> stabilizerDeque;
QTimer stabilizerPollTimer;
KisStabilizedEventsSampler stabilizedSampler;
KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper;
+ QTimer asynchronousUpdatesThresholdTimer;
+
int canvasRotation;
bool canvasMirroredH;
qreal effectiveSmoothnessDistance() const;
};
KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder,
const KUndo2MagicString &transactionText,
KisRecordingAdapter *recordingAdapter,
KisSmoothingOptions *smoothingOptions)
: m_d(new Private())
{
m_d->infoBuilder = infoBuilder;
m_d->recordingAdapter = recordingAdapter;
m_d->transactionText = transactionText;
m_d->smoothingOptions = KisSmoothingOptionsSP(
smoothingOptions ? smoothingOptions : new KisSmoothingOptions());
m_d->canvasRotation = 0;
m_d->strokeTimeoutTimer.setSingleShot(true);
connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke()));
connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing()));
+ connect(&m_d->asynchronousUpdatesThresholdTimer, SIGNAL(timeout()), SLOT(doAsynchronousUpdate()));
connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint()));
m_d->stabilizerDelayedPaintHelper.setPaintLineCallback(
[this](const KisPaintInformation &pi1, const KisPaintInformation &pi2) {
paintLine(pi1, pi2);
});
m_d->stabilizerDelayedPaintHelper.setUpdateOutlineCallback(
[this]() {
emit requestExplicitUpdateOutline();
});
}
KisToolFreehandHelper::~KisToolFreehandHelper()
{
delete m_d;
}
void KisToolFreehandHelper::setSmoothness(KisSmoothingOptionsSP smoothingOptions)
{
m_d->smoothingOptions = smoothingOptions;
}
KisSmoothingOptionsSP KisToolFreehandHelper::smoothingOptions() const
{
return m_d->smoothingOptions;
}
QPainterPath KisToolFreehandHelper::paintOpOutline(const QPointF &savedCursorPos,
const KoPointerEvent *event,
const KisPaintOpSettingsSP globalSettings,
KisPaintOpSettings::OutlineMode mode) const
{
KisPaintOpSettingsSP settings = globalSettings;
KisPaintInformation info = m_d->infoBuilder->hover(savedCursorPos, event);
QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(savedCursorPos);
qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, savedCursorPos, 0);
info.setCanvasRotation(m_d->canvasRotation);
info.setCanvasHorizontalMirrorState( m_d->canvasMirroredH );
- KisDistanceInformation distanceInfo(prevPoint, 0, startAngle);
+ KisDistanceInformation distanceInfo(prevPoint, startAngle);
if (!m_d->painterInfos.isEmpty()) {
settings = m_d->resources->currentPaintOpPreset()->settings();
if (m_d->stabilizerDelayedPaintHelper.running() &&
m_d->stabilizerDelayedPaintHelper.hasLastPaintInformation()) {
info = m_d->stabilizerDelayedPaintHelper.lastPaintInformation();
} else {
info = m_d->previousPaintInformation;
}
/**
* When LoD mode is active it may happen that the helper has
* already started a stroke, but it painted noting, because
* all the work is being calculated by the scaled-down LodN
* stroke. So at first we try to fetch the data from the lodN
* stroke ("buddy") and then check if there is at least
* something has been painted with this distance information
* object.
*/
KisDistanceInformation *buddyDistance =
m_d->painterInfos.first()->buddyDragDistance();
if (buddyDistance) {
/**
* Tiny hack alert: here we fetch the distance information
* directly from the LodN stroke. Ideally, we should
* upscale its data, but here we just override it with our
* local copy of the coordinates.
*/
distanceInfo = *buddyDistance;
- distanceInfo.overrideLastValues(prevPoint, 0, startAngle);
+ distanceInfo.overrideLastValues(prevPoint, startAngle);
} else if (m_d->painterInfos.first()->dragDistance->isStarted()) {
distanceInfo = *m_d->painterInfos.first()->dragDistance;
}
}
KisPaintInformation::DistanceInformationRegistrar registrar =
info.registerDistanceInformation(&distanceInfo);
QPainterPath outline = settings->brushOutline(info, mode);
if (m_d->resources &&
m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER &&
m_d->smoothingOptions->useDelayDistance()) {
const qreal R = m_d->smoothingOptions->delayDistance() /
m_d->resources->effectiveZoom();
outline.addEllipse(info.pos(), R, R);
}
return outline;
}
void KisToolFreehandHelper::cursorMoved(const QPointF &cursorPos)
{
m_d->lastCursorPos.pushThroughHistory(cursorPos);
}
void KisToolFreehandHelper::initPaint(KoPointerEvent *event,
const QPointF &pixelCoords,
KoCanvasResourceManager *resourceManager,
KisImageWSP image, KisNodeSP currentNode,
KisStrokesFacade *strokesFacade,
KisNodeSP overrideNode,
KisDefaultBoundsBaseSP bounds)
{
QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(pixelCoords);
m_d->strokeTime.start();
KisPaintInformation pi =
m_d->infoBuilder->startStroke(event, elapsedStrokeTime(), resourceManager);
qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, pixelCoords, 0.0);
initPaintImpl(startAngle,
pi,
resourceManager,
image,
currentNode,
strokesFacade,
overrideNode,
bounds);
}
bool KisToolFreehandHelper::isRunning() const
{
return m_d->strokeId;
}
void KisToolFreehandHelper::initPaintImpl(qreal startAngle,
const KisPaintInformation &pi,
KoCanvasResourceManager *resourceManager,
KisImageWSP image,
KisNodeSP currentNode,
KisStrokesFacade *strokesFacade,
KisNodeSP overrideNode,
KisDefaultBoundsBaseSP bounds)
{
m_d->strokesFacade = strokesFacade;
m_d->haveTangent = false;
m_d->previousTangent = QPointF();
m_d->hasPaintAtLeastOnce = false;
m_d->previousPaintInformation = pi;
m_d->resources = new KisResourcesSnapshot(image,
currentNode,
resourceManager,
bounds);
if(overrideNode) {
m_d->resources->setCurrentNode(overrideNode);
}
const bool airbrushing = m_d->resources->needsAirbrushing();
const bool useSpacingUpdates = m_d->resources->needsSpacingUpdates();
KisDistanceInitInfo startDistInfo(m_d->previousPaintInformation.pos(),
- m_d->previousPaintInformation.currentTime(),
startAngle,
useSpacingUpdates ? SPACING_UPDATE_INTERVAL : LONG_TIME,
- airbrushing ? TIMING_UPDATE_INTERVAL : LONG_TIME);
+ airbrushing ? TIMING_UPDATE_INTERVAL : LONG_TIME,
+ 0);
KisDistanceInformation startDist = startDistInfo.makeDistInfo();
createPainters(m_d->painterInfos,
startDist);
if(m_d->recordingAdapter) {
m_d->recordingAdapter->startStroke(image, m_d->resources, startDistInfo);
}
KisStrokeStrategy *stroke =
new FreehandStrokeStrategy(m_d->resources->needsIndirectPainting(),
m_d->resources->indirectPaintingCompositeOp(),
m_d->resources, m_d->painterInfos, m_d->transactionText);
m_d->strokeId = m_d->strokesFacade->startStroke(stroke);
m_d->history.clear();
m_d->distanceHistory.clear();
- if(airbrushing) {
+ if (airbrushing) {
m_d->airbrushingTimer.setInterval(computeAirbrushTimerInterval());
m_d->airbrushingTimer.start();
+ } else if (m_d->resources->presetNeedsAsynchronousUpdates()) {
+ m_d->asynchronousUpdatesThresholdTimer.setInterval(80 /* msec */);
+ m_d->asynchronousUpdatesThresholdTimer.start();
}
if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
stabilizerStart(m_d->previousPaintInformation);
}
// If airbrushing, paint an initial dab immediately. This is a workaround for an issue where
// some paintops (Dyna, Particle, Sketch) might never initialize their spacing/timing
// information until paintAt is called.
if (airbrushing) {
paintAt(pi);
}
}
void KisToolFreehandHelper::paintBezierSegment(KisPaintInformation pi1, KisPaintInformation pi2,
QPointF tangent1, QPointF tangent2)
{
if (tangent1.isNull() || tangent2.isNull()) return;
const qreal maxSanePoint = 1e6;
QPointF controlTarget1;
QPointF controlTarget2;
// Shows the direction in which control points go
QPointF controlDirection1 = pi1.pos() + tangent1;
QPointF controlDirection2 = pi2.pos() - tangent2;
// Lines in the direction of the control points
QLineF line1(pi1.pos(), controlDirection1);
QLineF line2(pi2.pos(), controlDirection2);
// Lines to check whether the control points lay on the opposite
// side of the line
QLineF line3(controlDirection1, controlDirection2);
QLineF line4(pi1.pos(), pi2.pos());
QPointF intersection;
if (line3.intersect(line4, &intersection) == QLineF::BoundedIntersection) {
qreal controlLength = line4.length() / 2;
line1.setLength(controlLength);
line2.setLength(controlLength);
controlTarget1 = line1.p2();
controlTarget2 = line2.p2();
} else {
QLineF::IntersectType type = line1.intersect(line2, &intersection);
if (type == QLineF::NoIntersection ||
intersection.manhattanLength() > maxSanePoint) {
intersection = 0.5 * (pi1.pos() + pi2.pos());
// dbgKrita << "WARINING: there is no intersection point "
// << "in the basic smoothing algoriths";
}
controlTarget1 = intersection;
controlTarget2 = intersection;
}
// shows how near to the controlTarget the value raises
qreal coeff = 0.8;
qreal velocity1 = QLineF(QPointF(), tangent1).length();
qreal velocity2 = QLineF(QPointF(), tangent2).length();
if (velocity1 == 0.0 || velocity2 == 0.0) {
velocity1 = 1e-6;
velocity2 = 1e-6;
warnKrita << "WARNING: Basic Smoothing: Velocity is Zero! Please report a bug:" << ppVar(velocity1) << ppVar(velocity2);
}
qreal similarity = qMin(velocity1/velocity2, velocity2/velocity1);
// the controls should not differ more than 50%
similarity = qMax(similarity, qreal(0.5));
// when the controls are symmetric, their size should be smaller
// to avoid corner-like curves
coeff *= 1 - qMax(qreal(0.0), similarity - qreal(0.8));
Q_ASSERT(coeff > 0);
QPointF control1;
QPointF control2;
if (velocity1 > velocity2) {
control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
coeff *= similarity;
control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
} else {
control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
coeff *= similarity;
control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
}
paintBezierCurve(pi1,
control1,
control2,
pi2);
}
qreal KisToolFreehandHelper::Private::effectiveSmoothnessDistance() const
{
const qreal effectiveSmoothnessDistance =
!smoothingOptions->useScalableDistance() ?
smoothingOptions->smoothnessDistance() :
smoothingOptions->smoothnessDistance() / resources->effectiveZoom();
return effectiveSmoothnessDistance;
}
void KisToolFreehandHelper::paintEvent(KoPointerEvent *event)
{
KisPaintInformation info =
m_d->infoBuilder->continueStroke(event,
elapsedStrokeTime());
info.setCanvasRotation( m_d->canvasRotation );
info.setCanvasHorizontalMirrorState( m_d->canvasMirroredH );
KisUpdateTimeMonitor::instance()->reportMouseMove(info.pos());
paint(info);
}
void KisToolFreehandHelper::paint(KisPaintInformation &info)
{
/**
* Smooth the coordinates out using the history and the
* distance. This is a heavily modified version of an algo used in
* Gimp and described in https://bugs.kde.org/show_bug.cgi?id=281267 and
* http://www24.atwiki.jp/sigetch_2007/pages/17.html. The main
* differences are:
*
* 1) It uses 'distance' instead of 'velocity', since time
* measurements are too unstable in realworld environment
*
* 2) There is no 'Quality' parameter, since the number of samples
* is calculated automatically
*
* 3) 'Tail Aggressiveness' is used for controling the end of the
* stroke
*
* 4) The formila is a little bit different: 'Distance' parameter
* stands for $3 \Sigma$
*/
if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING
&& m_d->smoothingOptions->smoothnessDistance() > 0.0) {
{ // initialize current distance
QPointF prevPos;
if (!m_d->history.isEmpty()) {
const KisPaintInformation &prevPi = m_d->history.last();
prevPos = prevPi.pos();
} else {
prevPos = m_d->previousPaintInformation.pos();
}
qreal currentDistance = QVector2D(info.pos() - prevPos).length();
m_d->distanceHistory.append(currentDistance);
}
m_d->history.append(info);
qreal x = 0.0;
qreal y = 0.0;
if (m_d->history.size() > 3) {
const qreal sigma = m_d->effectiveSmoothnessDistance() / 3.0; // '3.0' for (3 * sigma) range
qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma);
qreal gaussianWeight2 = sigma * sigma;
qreal distanceSum = 0.0;
qreal scaleSum = 0.0;
qreal pressure = 0.0;
qreal baseRate = 0.0;
Q_ASSERT(m_d->history.size() == m_d->distanceHistory.size());
for (int i = m_d->history.size() - 1; i >= 0; i--) {
qreal rate = 0.0;
const KisPaintInformation nextInfo = m_d->history.at(i);
double distance = m_d->distanceHistory.at(i);
Q_ASSERT(distance >= 0.0);
qreal pressureGrad = 0.0;
if (i < m_d->history.size() - 1) {
pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure();
const qreal tailAgressiveness = 40.0 * m_d->smoothingOptions->tailAggressiveness();
if (pressureGrad > 0.0 ) {
pressureGrad *= tailAgressiveness * (1.0 - nextInfo.pressure());
distance += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region
}
}
if (gaussianWeight2 != 0.0) {
distanceSum += distance;
rate = gaussianWeight * exp(-distanceSum * distanceSum / (2 * gaussianWeight2));
}
if (m_d->history.size() - i == 1) {
baseRate = rate;
} else if (baseRate / rate > 100) {
break;
}
scaleSum += rate;
x += rate * nextInfo.pos().x();
y += rate * nextInfo.pos().y();
if (m_d->smoothingOptions->smoothPressure()) {
pressure += rate * nextInfo.pressure();
}
}
if (scaleSum != 0.0) {
x /= scaleSum;
y /= scaleSum;
if (m_d->smoothingOptions->smoothPressure()) {
pressure /= scaleSum;
}
}
if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) {
info.setPos(QPointF(x, y));
if (m_d->smoothingOptions->smoothPressure()) {
info.setPressure(pressure);
}
m_d->history.last() = info;
}
}
}
if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::SIMPLE_SMOOTHING
|| m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING)
{
// Now paint between the coordinates, using the bezier curve interpolation
if (!m_d->haveTangent) {
m_d->haveTangent = true;
m_d->previousTangent =
(info.pos() - m_d->previousPaintInformation.pos()) /
qMax(qreal(1.0), info.currentTime() - m_d->previousPaintInformation.currentTime());
} else {
QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) /
qMax(qreal(1.0), info.currentTime() - m_d->olderPaintInformation.currentTime());
if (newTangent.isNull() || m_d->previousTangent.isNull())
{
paintLine(m_d->previousPaintInformation, info);
} else {
paintBezierSegment(m_d->olderPaintInformation, m_d->previousPaintInformation,
m_d->previousTangent, newTangent);
}
m_d->previousTangent = newTangent;
}
m_d->olderPaintInformation = m_d->previousPaintInformation;
// Enable stroke timeout only when not airbrushing.
if (!m_d->airbrushingTimer.isActive()) {
m_d->strokeTimeoutTimer.start(100);
}
}
else if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::NO_SMOOTHING){
paintLine(m_d->previousPaintInformation, info);
}
if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
m_d->stabilizedSampler.addEvent(info);
if (m_d->stabilizerDelayedPaintHelper.running()) {
// Paint here so we don't have to rely on the timer
// This is just a tricky source for a relatively stable 7ms "timer"
m_d->stabilizerDelayedPaintHelper.paintSome();
}
} else {
m_d->previousPaintInformation = info;
}
if(m_d->airbrushingTimer.isActive()) {
m_d->airbrushingTimer.start();
}
}
void KisToolFreehandHelper::endPaint()
{
if (!m_d->hasPaintAtLeastOnce) {
paintAt(m_d->previousPaintInformation);
} else if (m_d->smoothingOptions->smoothingType() != KisSmoothingOptions::NO_SMOOTHING) {
finishStroke();
}
m_d->strokeTimeoutTimer.stop();
if(m_d->airbrushingTimer.isActive()) {
m_d->airbrushingTimer.stop();
}
+ if (m_d->asynchronousUpdatesThresholdTimer.isActive()) {
+ m_d->asynchronousUpdatesThresholdTimer.stop();
+ }
+
if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
stabilizerEnd();
}
/**
* There might be some timer events still pending, so
* we should cancel them. Use this flag for the purpose.
* Please note that we are not in MT here, so no mutex
* is needed
*/
m_d->painterInfos.clear();
+ // last update to complete rendering if there is still something pending
+ doAsynchronousUpdate(true);
+
m_d->strokesFacade->endStroke(m_d->strokeId);
m_d->strokeId.clear();
if(m_d->recordingAdapter) {
m_d->recordingAdapter->endStroke();
}
}
void KisToolFreehandHelper::cancelPaint()
{
if (!m_d->strokeId) return;
m_d->strokeTimeoutTimer.stop();
if (m_d->airbrushingTimer.isActive()) {
m_d->airbrushingTimer.stop();
}
+ if (m_d->asynchronousUpdatesThresholdTimer.isActive()) {
+ m_d->asynchronousUpdatesThresholdTimer.stop();
+ }
+
if (m_d->stabilizerPollTimer.isActive()) {
m_d->stabilizerPollTimer.stop();
}
if (m_d->stabilizerDelayedPaintHelper.running()) {
m_d->stabilizerDelayedPaintHelper.cancel();
}
// see a comment in endPaint()
m_d->painterInfos.clear();
m_d->strokesFacade->cancelStroke(m_d->strokeId);
m_d->strokeId.clear();
if(m_d->recordingAdapter) {
//FIXME: not implemented
//m_d->recordingAdapter->cancelStroke();
}
}
int KisToolFreehandHelper::elapsedStrokeTime() const
{
return m_d->strokeTime.elapsed();
}
void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo)
{
// FIXME: Ugly hack, this is no a "distance" in any way
int sampleSize = qRound(m_d->effectiveSmoothnessDistance());
sampleSize = qMax(3, sampleSize);
// Fill the deque with the current value repeated until filling the sample
m_d->stabilizerDeque.clear();
for (int i = sampleSize; i > 0; i--) {
m_d->stabilizerDeque.enqueue(firstPaintInfo);
}
// Poll and draw regularly
KisConfig cfg;
int stabilizerSampleSize = cfg.stabilizerSampleSize();
m_d->stabilizerPollTimer.setInterval(stabilizerSampleSize);
m_d->stabilizerPollTimer.start();
bool delayedPaintEnabled = cfg.stabilizerDelayedPaint();
if (delayedPaintEnabled) {
m_d->stabilizerDelayedPaintHelper.start(firstPaintInfo);
}
m_d->stabilizedSampler.clear();
m_d->stabilizedSampler.addEvent(firstPaintInfo);
}
KisPaintInformation
KisToolFreehandHelper::getStabilizedPaintInfo(const QQueue<KisPaintInformation> &queue,
const KisPaintInformation &lastPaintInfo)
{
KisPaintInformation result(lastPaintInfo.pos(),
lastPaintInfo.pressure(),
lastPaintInfo.xTilt(),
lastPaintInfo.yTilt(),
lastPaintInfo.rotation(),
lastPaintInfo.tangentialPressure(),
lastPaintInfo.perspective(),
elapsedStrokeTime(),
lastPaintInfo.drawingSpeed());
if (queue.size() > 1) {
QQueue<KisPaintInformation>::const_iterator it = queue.constBegin();
QQueue<KisPaintInformation>::const_iterator end = queue.constEnd();
/**
* The first point is going to be overridden by lastPaintInfo, skip it.
*/
it++;
int i = 2;
if (m_d->smoothingOptions->stabilizeSensors()) {
while (it != end) {
qreal k = qreal(i - 1) / i; // coeff for uniform averaging
result.KisPaintInformation::mixOtherWithoutTime(k, *it);
it++;
i++;
}
} else{
while (it != end) {
qreal k = qreal(i - 1) / i; // coeff for uniform averaging
result.KisPaintInformation::mixOtherOnlyPosition(k, *it);
it++;
i++;
}
}
}
return result;
}
void KisToolFreehandHelper::stabilizerPollAndPaint()
{
KisStabilizedEventsSampler::iterator it;
KisStabilizedEventsSampler::iterator end;
std::tie(it, end) = m_d->stabilizedSampler.range();
QVector<KisPaintInformation> delayedPaintTodoItems;
for (; it != end; ++it) {
KisPaintInformation sampledInfo = *it;
bool canPaint = true;
if (m_d->smoothingOptions->useDelayDistance()) {
const qreal R = m_d->smoothingOptions->delayDistance() /
m_d->resources->effectiveZoom();
QPointF diff = sampledInfo.pos() - m_d->previousPaintInformation.pos();
qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y()));
if (!(dx > R)) {
if (m_d->resources->needsAirbrushing()) {
sampledInfo.setPos(m_d->previousPaintInformation.pos());
}
else {
canPaint = false;
}
}
}
if (canPaint) {
KisPaintInformation newInfo = getStabilizedPaintInfo(m_d->stabilizerDeque, sampledInfo);
if (m_d->stabilizerDelayedPaintHelper.running()) {
delayedPaintTodoItems.append(newInfo);
} else {
paintLine(m_d->previousPaintInformation, newInfo);
}
m_d->previousPaintInformation = newInfo;
// Push the new entry through the queue
m_d->stabilizerDeque.dequeue();
m_d->stabilizerDeque.enqueue(sampledInfo);
} else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) {
QQueue<KisPaintInformation>::iterator it = m_d->stabilizerDeque.begin();
QQueue<KisPaintInformation>::iterator end = m_d->stabilizerDeque.end();
while (it != end) {
*it = m_d->previousPaintInformation;
++it;
}
}
}
m_d->stabilizedSampler.clear();
if (m_d->stabilizerDelayedPaintHelper.running()) {
m_d->stabilizerDelayedPaintHelper.update(delayedPaintTodoItems);
} else {
emit requestExplicitUpdateOutline();
}
}
void KisToolFreehandHelper::stabilizerEnd()
{
// Stop the timer
m_d->stabilizerPollTimer.stop();
// Finish the line
if (m_d->smoothingOptions->finishStabilizedCurve()) {
// Process all the existing events first
stabilizerPollAndPaint();
// Draw the finish line with pending events and a time override
m_d->stabilizedSampler.addFinishingEvent(m_d->stabilizerDeque.size());
stabilizerPollAndPaint();
}
if (m_d->stabilizerDelayedPaintHelper.running()) {
m_d->stabilizerDelayedPaintHelper.end();
}
}
-const KisPaintOp* KisToolFreehandHelper::currentPaintOp() const
-{
- return !m_d->painterInfos.isEmpty() ? m_d->painterInfos.first()->painter->paintOp() : 0;
-}
-
void KisToolFreehandHelper::finishStroke()
{
if (m_d->haveTangent) {
m_d->haveTangent = false;
QPointF newTangent = (m_d->previousPaintInformation.pos() - m_d->olderPaintInformation.pos()) /
(m_d->previousPaintInformation.currentTime() - m_d->olderPaintInformation.currentTime());
paintBezierSegment(m_d->olderPaintInformation,
m_d->previousPaintInformation,
m_d->previousTangent,
newTangent);
}
}
void KisToolFreehandHelper::doAirbrushing()
{
// Check that the stroke hasn't ended.
if (!m_d->painterInfos.isEmpty()) {
// Add a new painting update at a point identical to the previous one, except for the time
// and speed information.
const KisPaintInformation &prevPaint = m_d->previousPaintInformation;
KisPaintInformation nextPaint(prevPaint.pos(),
prevPaint.pressure(),
prevPaint.xTilt(),
prevPaint.yTilt(),
prevPaint.rotation(),
prevPaint.tangentialPressure(),
prevPaint.perspective(),
elapsedStrokeTime(),
0.0);
paint(nextPaint);
}
}
+void KisToolFreehandHelper::doAsynchronousUpdate(bool forceUpdate)
+{
+ m_d->strokesFacade->addJob(m_d->strokeId,
+ new FreehandStrokeStrategy::UpdateData(forceUpdate));
+}
+
int KisToolFreehandHelper::computeAirbrushTimerInterval() const
{
qreal realInterval = m_d->resources->airbrushingInterval() * AIRBRUSH_INTERVAL_FACTOR;
return qMax(1, qFloor(realInterval));
}
void KisToolFreehandHelper::paintAt(int painterInfoId,
const KisPaintInformation &pi)
{
m_d->hasPaintAtLeastOnce = true;
m_d->strokesFacade->addJob(m_d->strokeId,
- new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
- painterInfoId, pi));
+ new FreehandStrokeStrategy::Data(painterInfoId, pi));
if(m_d->recordingAdapter) {
m_d->recordingAdapter->addPoint(pi);
}
}
void KisToolFreehandHelper::paintLine(int painterInfoId,
const KisPaintInformation &pi1,
const KisPaintInformation &pi2)
{
m_d->hasPaintAtLeastOnce = true;
m_d->strokesFacade->addJob(m_d->strokeId,
- new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
- painterInfoId, pi1, pi2));
+ new FreehandStrokeStrategy::Data(painterInfoId, pi1, pi2));
if(m_d->recordingAdapter) {
m_d->recordingAdapter->addLine(pi1, pi2);
}
}
void KisToolFreehandHelper::paintBezierCurve(int painterInfoId,
const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2)
{
#ifdef DEBUG_BEZIER_CURVES
KisPaintInformation tpi1;
KisPaintInformation tpi2;
tpi1 = pi1;
tpi2 = pi2;
tpi1.setPressure(0.3);
tpi2.setPressure(0.3);
paintLine(tpi1, tpi2);
tpi1.setPressure(0.6);
tpi2.setPressure(0.3);
tpi1.setPos(pi1.pos());
tpi2.setPos(control1);
paintLine(tpi1, tpi2);
tpi1.setPos(pi2.pos());
tpi2.setPos(control2);
paintLine(tpi1, tpi2);
#endif
m_d->hasPaintAtLeastOnce = true;
m_d->strokesFacade->addJob(m_d->strokeId,
- new FreehandStrokeStrategy::Data(m_d->resources->currentNode(),
- painterInfoId,
+ new FreehandStrokeStrategy::Data(painterInfoId,
pi1, control1, control2, pi2));
if(m_d->recordingAdapter) {
m_d->recordingAdapter->addCurve(pi1, control1, control2, pi2);
}
}
void KisToolFreehandHelper::createPainters(QVector<PainterInfo*> &painterInfos,
const KisDistanceInformation &startDist)
{
painterInfos << new PainterInfo(startDist);
}
void KisToolFreehandHelper::paintAt(const KisPaintInformation &pi)
{
paintAt(0, pi);
}
void KisToolFreehandHelper::paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2)
{
paintLine(0, pi1, pi2);
}
void KisToolFreehandHelper::paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2)
{
paintBezierCurve(0, pi1, control1, control2, pi2);
}
int KisToolFreehandHelper::canvasRotation()
{
return m_d->canvasRotation;
}
void KisToolFreehandHelper::setCanvasRotation(int rotation)
{
m_d->canvasRotation = rotation;
}
bool KisToolFreehandHelper::canvasMirroredH()
{
return m_d->canvasMirroredH;
}
void KisToolFreehandHelper::setCanvasHorizontalMirrorState(bool mirrored)
{
m_d->canvasMirroredH = mirrored;
}
diff --git a/libs/ui/tool/kis_tool_freehand_helper.h b/libs/ui/tool/kis_tool_freehand_helper.h
index dcf07292b5..389d955d28 100644
--- a/libs/ui/tool/kis_tool_freehand_helper.h
+++ b/libs/ui/tool/kis_tool_freehand_helper.h
@@ -1,162 +1,162 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_TOOL_FREEHAND_HELPER_H
#define __KIS_TOOL_FREEHAND_HELPER_H
#include <QObject>
#include "kis_types.h"
#include "kritaui_export.h"
#include <brushengine/kis_paint_information.h>
#include "kis_default_bounds.h"
#include <brushengine/kis_paintop_settings.h>
#include "kis_smoothing_options.h"
#include "strokes/freehand_stroke.h"
class KoPointerEvent;
class KoCanvasResourceManager;
class KisPaintingInformationBuilder;
class KisRecordingAdapter;
class KisStrokesFacade;
class KisPostExecutionUndoAdapter;
class KisPaintOp;
class KRITAUI_EXPORT KisToolFreehandHelper : public QObject
{
Q_OBJECT
protected:
typedef FreehandStrokeStrategy::PainterInfo PainterInfo;
public:
KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder,
const KUndo2MagicString &transactionText = KUndo2MagicString(),
KisRecordingAdapter *recordingAdapter = 0,
KisSmoothingOptions *smoothingOptions = 0);
~KisToolFreehandHelper() override;
void setSmoothness(KisSmoothingOptionsSP smoothingOptions);
KisSmoothingOptionsSP smoothingOptions() const;
bool isRunning() const;
void cursorMoved(const QPointF &cursorPos);
/**
* @param pixelCoords - The position of the KoPointerEvent, in pixel coordinates.
*/
void initPaint(KoPointerEvent *event,
const QPointF &pixelCoords,
KoCanvasResourceManager *resourceManager,
KisImageWSP image,
KisNodeSP currentNode,
KisStrokesFacade *strokesFacade,
KisNodeSP overrideNode = 0,
KisDefaultBoundsBaseSP bounds = 0);
void paintEvent(KoPointerEvent *event);
void endPaint();
- const KisPaintOp* currentPaintOp() const;
QPainterPath paintOpOutline(const QPointF &savedCursorPos,
const KoPointerEvent *event,
const KisPaintOpSettingsSP globalSettings,
KisPaintOpSettings::OutlineMode mode) const;
int canvasRotation();
void setCanvasRotation(int rotation = 0);
bool canvasMirroredH();
void setCanvasHorizontalMirrorState (bool mirrored = false);
Q_SIGNALS:
/**
* The signal is emitted when the outline should be updated
* explicitly by the tool. Used by Stabilizer option, because it
* paints on internal timer events instead of the on every paint()
* event
*/
void requestExplicitUpdateOutline();
protected:
void cancelPaint();
int elapsedStrokeTime() const;
void initPaintImpl(qreal startAngle,
const KisPaintInformation &pi,
KoCanvasResourceManager *resourceManager,
KisImageWSP image,
KisNodeSP node,
KisStrokesFacade *strokesFacade,
KisNodeSP overrideNode = 0,
KisDefaultBoundsBaseSP bounds = 0);
protected:
virtual void createPainters(QVector<PainterInfo*> &painterInfos,
const KisDistanceInformation &startDist);
// lo-level methods for painting primitives
void paintAt(int painterInfoId, const KisPaintInformation &pi);
void paintLine(int painterInfoId,
const KisPaintInformation &pi1,
const KisPaintInformation &pi2);
void paintBezierCurve(int painterInfoId,
const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2);
// hi-level methods for painting primitives
virtual void paintAt(const KisPaintInformation &pi);
virtual void paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2);
virtual void paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2);
private:
void paint(KisPaintInformation &info);
void paintBezierSegment(KisPaintInformation pi1, KisPaintInformation pi2,
QPointF tangent1, QPointF tangent2);
void stabilizerStart(KisPaintInformation firstPaintInfo);
void stabilizerEnd();
KisPaintInformation getStabilizedPaintInfo(const QQueue<KisPaintInformation> &queue,
const KisPaintInformation &lastPaintInfo);
int computeAirbrushTimerInterval() const;
private Q_SLOTS:
void finishStroke();
void doAirbrushing();
+ void doAsynchronousUpdate(bool forceUpdate = false);
void stabilizerPollAndPaint();
private:
struct Private;
Private * const m_d;
};
#endif /* __KIS_TOOL_FREEHAND_HELPER_H */
diff --git a/libs/ui/tool/kis_tool_paint.cc b/libs/ui/tool/kis_tool_paint.cc
index ecdc74e9e5..84848152c6 100644
--- a/libs/ui/tool/kis_tool_paint.cc
+++ b/libs/ui/tool/kis_tool_paint.cc
@@ -1,782 +1,793 @@
/*
* Copyright (c) 2003-2009 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2015 Moritz Molch <kde@moritzmolch.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_paint.h"
#include <algorithm>
#include <QWidget>
#include <QRect>
#include <QLayout>
#include <QLabel>
#include <QPushButton>
#include <QWhatsThis>
#include <QCheckBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QKeyEvent>
#include <QEvent>
#include <QVariant>
#include <QAction>
#include <kis_debug.h>
#include <QPoint>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include <QAction>
#include <kis_icon.h>
#include <KoShape.h>
#include <KoCanvasResourceManager.h>
#include <KoColorSpace.h>
#include <KoPointerEvent.h>
#include <KoColor.h>
#include <KoCanvasBase.h>
#include <KoCanvasController.h>
#include <kis_types.h>
#include <kis_global.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_layer.h>
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <kis_cubic_curve.h>
#include "kis_display_color_converter.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_cursor.h"
#include "widgets/kis_cmb_composite.h"
#include "widgets/kis_slider_spin_box.h"
#include "kis_canvas_resource_provider.h"
#include <recorder/kis_recorded_paint_action.h>
#include "kis_tool_utils.h"
#include <brushengine/kis_paintop.h>
#include <brushengine/kis_paintop_preset.h>
#include <kis_action_manager.h>
#include <kis_action.h>
#include "strokes/kis_color_picker_stroke_strategy.h"
+#include <kis_canvas_resource_provider.h>
KisToolPaint::KisToolPaint(KoCanvasBase * canvas, const QCursor & cursor)
: KisTool(canvas, cursor),
m_showColorPreview(false),
m_colorPreviewShowComparePlate(false),
m_colorPickerDelayTimer(),
m_isOutlineEnabled(true)
{
m_specialHoverModifier = false;
m_optionsWidgetLayout = 0;
m_opacity = OPACITY_OPAQUE_U8;
updateTabletPressureSamples();
m_supportOutline = false;
{
int maxSize = KisConfig().readEntry("maximumBrushSize", 1000);
int brushSize = 1;
do {
m_standardBrushSizes.push_back(brushSize);
int increment = qMax(1, int(std::ceil(qreal(brushSize) / 15)));
brushSize += increment;
} while (brushSize < maxSize);
m_standardBrushSizes.push_back(maxSize);
}
KisCanvas2 * kiscanvas = dynamic_cast<KisCanvas2*>(canvas);
KisActionManager *actionManager = kiscanvas->viewManager()->actionManager();
// XXX: Perhaps a better place for these?
if (!actionManager->actionByName("increase_brush_size")) {
KisAction *increaseBrushSize = new KisAction(i18n("Increase Brush Size"));
increaseBrushSize->setShortcut(Qt::Key_BracketRight);
actionManager->addAction("increase_brush_size", increaseBrushSize);
}
if (!actionManager->actionByName("decrease_brush_size")) {
KisAction *decreaseBrushSize = new KisAction(i18n("Decrease Brush Size"));
decreaseBrushSize->setShortcut(Qt::Key_BracketLeft);
actionManager->addAction("decrease_brush_size", decreaseBrushSize);
}
addAction("increase_brush_size", dynamic_cast<QAction *>(actionManager->actionByName("increase_brush_size")));
addAction("decrease_brush_size", dynamic_cast<QAction *>(actionManager->actionByName("decrease_brush_size")));
if (kiscanvas && kiscanvas->viewManager()) {
connect(this, SIGNAL(sigPaintingFinished()), kiscanvas->viewManager()->resourceProvider(), SLOT(slotPainting()));
}
m_colorPickerDelayTimer.setSingleShot(true);
connect(&m_colorPickerDelayTimer, SIGNAL(timeout()), this, SLOT(activatePickColorDelayed()));
using namespace std::placeholders; // For _1 placeholder
std::function<void(PickingJob)> callback =
std::bind(&KisToolPaint::addPickerJob, this, _1);
m_colorPickingCompressor.reset(
new PickingCompressor(100, callback, KisSignalCompressor::FIRST_ACTIVE));
}
KisToolPaint::~KisToolPaint()
{
}
int KisToolPaint::flags() const
{
return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP;
}
void KisToolPaint::canvasResourceChanged(int key, const QVariant& v)
{
KisTool::canvasResourceChanged(key, v);
switch(key) {
case(KisCanvasResourceProvider::Opacity):
setOpacity(v.toDouble());
break;
default: //nothing
break;
}
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle()), Qt::UniqueConnection);
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(updateTabletPressureSamples()), Qt::UniqueConnection);
}
void KisToolPaint::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
if (currentPaintOpPreset()) emit statusTextChanged(currentPaintOpPreset()->name());
KisTool::activate(toolActivation, shapes);
connect(action("increase_brush_size"), SIGNAL(triggered()), SLOT(increaseBrushSize()), Qt::UniqueConnection);
connect(action("decrease_brush_size"), SIGNAL(triggered()), SLOT(decreaseBrushSize()), Qt::UniqueConnection);
+
+ KisCanvasResourceProvider *provider = qobject_cast<KisCanvas2*>(canvas())->viewManager()->resourceProvider();
+ m_oldOpacity = provider->opacity();
+ provider->setOpacity(m_localOpacity);
}
void KisToolPaint::deactivate()
{
+
disconnect(action("increase_brush_size"), 0, this, 0);
disconnect(action("decrease_brush_size"), 0, this, 0);
+
+ KisCanvasResourceProvider *provider = qobject_cast<KisCanvas2*>(canvas())->viewManager()->resourceProvider();
+ m_localOpacity = provider->opacity();
+ provider->setOpacity(m_oldOpacity);
+
KisTool::deactivate();
}
QPainterPath KisToolPaint::tryFixBrushOutline(const QPainterPath &originalOutline)
{
KisConfig cfg;
if (cfg.newOutlineStyle() == OUTLINE_NONE) return originalOutline;
const qreal minThresholdSize = cfg.outlineSizeMinimum();
/**
* If the brush outline is bigger than the canvas itself (which
* would make it invisible for a user in most of the cases) just
* add a cross in the center of it
*/
QSize widgetSize = canvas()->canvasWidget()->size();
const int maxThresholdSum = widgetSize.width() + widgetSize.height();
QPainterPath outline = originalOutline;
QRectF boundingRect = outline.boundingRect();
const qreal sum = boundingRect.width() + boundingRect.height();
QPointF center = boundingRect.center();
if (sum > maxThresholdSum) {
const int hairOffset = 7;
outline.moveTo(center.x(), center.y() - hairOffset);
outline.lineTo(center.x(), center.y() + hairOffset);
outline.moveTo(center.x() - hairOffset, center.y());
outline.lineTo(center.x() + hairOffset, center.y());
} else if (sum < minThresholdSize && !outline.isEmpty()) {
outline = QPainterPath();
outline.addEllipse(center, 0.5 * minThresholdSize, 0.5 * minThresholdSize);
}
return outline;
}
void KisToolPaint::paint(QPainter &gc, const KoViewConverter &converter)
{
Q_UNUSED(converter);
QPainterPath path = tryFixBrushOutline(pixelToView(m_currentOutline));
paintToolOutline(&gc, path);
if (m_showColorPreview) {
QRectF viewRect = converter.documentToView(m_oldColorPreviewRect);
gc.fillRect(viewRect, m_colorPreviewCurrentColor);
if (m_colorPreviewShowComparePlate) {
QRectF baseColorRect = viewRect.translated(viewRect.width(), 0);
gc.fillRect(baseColorRect, m_colorPreviewBaseColor);
}
}
}
void KisToolPaint::setMode(ToolMode mode)
{
if(this->mode() == KisTool::PAINT_MODE &&
mode != KisTool::PAINT_MODE) {
// Let's add history information about recently used colors
emit sigPaintingFinished();
}
KisTool::setMode(mode);
}
void KisToolPaint::activatePickColor(AlternateAction action)
{
m_showColorPreview = true;
requestUpdateOutline(m_outlineDocPoint, 0);
int resource = colorPreviewResourceId(action);
KoColor color = canvas()->resourceManager()->koColorResource(resource);
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas);
m_colorPreviewCurrentColor = kisCanvas->displayColorConverter()->toQColor(color);
if (!m_colorPreviewBaseColor.isValid()) {
m_colorPreviewBaseColor = m_colorPreviewCurrentColor;
}
}
void KisToolPaint::deactivatePickColor(AlternateAction action)
{
Q_UNUSED(action);
m_showColorPreview = false;
m_oldColorPreviewRect = QRect();
m_oldColorPreviewUpdateRect = QRect();
m_colorPreviewCurrentColor = QColor();
}
void KisToolPaint::pickColorWasOverridden()
{
m_colorPreviewShowComparePlate = false;
m_colorPreviewBaseColor = QColor();
}
void KisToolPaint::activateAlternateAction(AlternateAction action)
{
switch (action) {
case PickFgNode:
case PickBgNode:
case PickFgImage:
case PickBgImage:
delayedAction = action;
m_colorPickerDelayTimer.start(100);
default:
pickColorWasOverridden();
KisTool::activateAlternateAction(action);
};
}
void KisToolPaint::activatePickColorDelayed()
{
switch (delayedAction) {
case PickFgNode:
useCursor(KisCursor::pickerLayerForegroundCursor());
activatePickColor(delayedAction);
break;
case PickBgNode:
useCursor(KisCursor::pickerLayerBackgroundCursor());
activatePickColor(delayedAction);
break;
case PickFgImage:
useCursor(KisCursor::pickerImageForegroundCursor());
activatePickColor(delayedAction);
break;
case PickBgImage:
useCursor(KisCursor::pickerImageBackgroundCursor());
activatePickColor(delayedAction);
break;
default:
break;
};
repaintDecorations();
}
bool KisToolPaint::isPickingAction(AlternateAction action) {
return action == PickFgNode ||
action == PickBgNode ||
action == PickFgImage ||
action == PickBgImage;
}
void KisToolPaint::deactivateAlternateAction(AlternateAction action)
{
if (!isPickingAction(action)) {
KisTool::deactivateAlternateAction(action);
return;
}
delayedAction = KisTool::NONE;
m_colorPickerDelayTimer.stop();
resetCursorStyle();
deactivatePickColor(action);
}
void KisToolPaint::addPickerJob(const PickingJob &pickingJob)
{
/**
* The actual picking is delayed by a compressor, so we can get this
* event when the stroke is already closed
*/
if (!m_pickerStrokeId) return;
KIS_ASSERT_RECOVER_RETURN(isPickingAction(pickingJob.action));
const QPoint imagePoint = image()->documentToIntPixel(pickingJob.documentPixel);
const bool fromCurrentNode = pickingJob.action == PickFgNode || pickingJob.action == PickBgNode;
m_pickingResource = colorPreviewResourceId(pickingJob.action);
KisPaintDeviceSP device = fromCurrentNode ?
currentNode()->projection() : image()->projection();
image()->addJob(m_pickerStrokeId,
new KisColorPickerStrokeStrategy::Data(device, imagePoint));
}
void KisToolPaint::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
{
if (isPickingAction(action)) {
KIS_ASSERT_RECOVER_RETURN(!m_pickerStrokeId);
setMode(SECONDARY_PAINT_MODE);
KisColorPickerStrokeStrategy *strategy = new KisColorPickerStrokeStrategy();
connect(strategy, &KisColorPickerStrokeStrategy::sigColorUpdated,
this, &KisToolPaint::slotColorPickingFinished);
m_pickerStrokeId = image()->startStroke(strategy);
m_colorPickingCompressor->start(PickingJob(event->point, action));
requestUpdateOutline(event->point, event);
} else {
KisTool::beginAlternateAction(event, action);
}
}
void KisToolPaint::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
{
if (isPickingAction(action)) {
KIS_ASSERT_RECOVER_RETURN(m_pickerStrokeId);
m_colorPickingCompressor->start(PickingJob(event->point, action));
requestUpdateOutline(event->point, event);
} else {
KisTool::continueAlternateAction(event, action);
}
}
void KisToolPaint::endAlternateAction(KoPointerEvent *event, AlternateAction action)
{
if (isPickingAction(action)) {
KIS_ASSERT_RECOVER_RETURN(m_pickerStrokeId);
image()->endStroke(m_pickerStrokeId);
m_pickerStrokeId.clear();
requestUpdateOutline(event->point, event);
setMode(HOVER_MODE);
} else {
KisTool::endAlternateAction(event, action);
}
}
int KisToolPaint::colorPreviewResourceId(AlternateAction action)
{
bool toForegroundColor = action == PickFgNode || action == PickFgImage;
int resource = toForegroundColor ?
KoCanvasResourceManager::ForegroundColor : KoCanvasResourceManager::BackgroundColor;
return resource;
}
void KisToolPaint::slotColorPickingFinished(const KoColor &color)
{
canvas()->resourceManager()->setResource(m_pickingResource, color);
if (!m_showColorPreview) return;
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas);
QColor previewColor = kisCanvas->displayColorConverter()->toQColor(color);
m_colorPreviewShowComparePlate = true;
m_colorPreviewCurrentColor = previewColor;
requestUpdateOutline(m_outlineDocPoint, 0);
}
void KisToolPaint::mousePressEvent(KoPointerEvent *event)
{
KisTool::mousePressEvent(event);
if (mode() == KisTool::HOVER_MODE) {
requestUpdateOutline(event->point, event);
}
}
void KisToolPaint::mouseMoveEvent(KoPointerEvent *event)
{
KisTool::mouseMoveEvent(event);
if (mode() == KisTool::HOVER_MODE) {
requestUpdateOutline(event->point, event);
}
}
void KisToolPaint::mouseReleaseEvent(KoPointerEvent *event)
{
KisTool::mouseReleaseEvent(event);
if (mode() == KisTool::HOVER_MODE) {
requestUpdateOutline(event->point, event);
}
}
QWidget * KisToolPaint::createOptionWidget()
{
QWidget * optionWidget = new QWidget();
optionWidget->setObjectName(toolId());
QVBoxLayout* verticalLayout = new QVBoxLayout(optionWidget);
verticalLayout->setObjectName("KisToolPaint::OptionWidget::VerticalLayout");
verticalLayout->setContentsMargins(0,0,0,0);
verticalLayout->setSpacing(5);
// See https://bugs.kde.org/show_bug.cgi?id=316896
QWidget *specialSpacer = new QWidget(optionWidget);
specialSpacer->setObjectName("SpecialSpacer");
specialSpacer->setFixedSize(0, 0);
verticalLayout->addWidget(specialSpacer);
verticalLayout->addWidget(specialSpacer);
m_optionsWidgetLayout = new QGridLayout();
m_optionsWidgetLayout->setColumnStretch(1, 1);
verticalLayout->addLayout(m_optionsWidgetLayout);
m_optionsWidgetLayout->setContentsMargins(0,0,0,0);
m_optionsWidgetLayout->setSpacing(5);
if (!quickHelp().isEmpty()) {
QPushButton* push = new QPushButton(KisIconUtils::loadIcon("help-contents"), QString(), optionWidget);
connect(push, SIGNAL(clicked()), this, SLOT(slotPopupQuickHelp()));
QHBoxLayout* hLayout = new QHBoxLayout(optionWidget);
hLayout->addWidget(push);
hLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed));
verticalLayout->addLayout(hLayout);
}
return optionWidget;
}
QWidget* findLabelWidget(QGridLayout *layout, QWidget *control)
{
QWidget *result = 0;
int index = layout->indexOf(control);
int row, col, rowSpan, colSpan;
layout->getItemPosition(index, &row, &col, &rowSpan, &colSpan);
if (col > 0) {
QLayoutItem *item = layout->itemAtPosition(row, col - 1);
if (item) {
result = item->widget();
}
} else {
QLayoutItem *item = layout->itemAtPosition(row, col + 1);
if (item) {
result = item->widget();
}
}
return result;
}
void KisToolPaint::showControl(QWidget *control, bool value)
{
control->setVisible(value);
QWidget *label = findLabelWidget(m_optionsWidgetLayout, control);
if (label) {
label->setVisible(value);
}
}
void KisToolPaint::enableControl(QWidget *control, bool value)
{
control->setEnabled(value);
QWidget *label = findLabelWidget(m_optionsWidgetLayout, control);
if (label) {
label->setEnabled(value);
}
}
void KisToolPaint::addOptionWidgetLayout(QLayout *layout)
{
Q_ASSERT(m_optionsWidgetLayout != 0);
int rowCount = m_optionsWidgetLayout->rowCount();
m_optionsWidgetLayout->addLayout(layout, rowCount, 0, 1, 2);
}
void KisToolPaint::addOptionWidgetOption(QWidget *control, QWidget *label)
{
Q_ASSERT(m_optionsWidgetLayout != 0);
if (label) {
m_optionsWidgetLayout->addWidget(label, m_optionsWidgetLayout->rowCount(), 0);
m_optionsWidgetLayout->addWidget(control, m_optionsWidgetLayout->rowCount() - 1, 1);
}
else {
m_optionsWidgetLayout->addWidget(control, m_optionsWidgetLayout->rowCount(), 0, 1, 2);
}
}
void KisToolPaint::setOpacity(qreal opacity)
{
m_opacity = quint8(opacity * OPACITY_OPAQUE_U8);
}
const KoCompositeOp* KisToolPaint::compositeOp()
{
if (currentNode()) {
KisPaintDeviceSP device = currentNode()->paintDevice();
if (device) {
QString op = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentCompositeOp).toString();
return device->colorSpace()->compositeOp(op);
}
}
return 0;
}
void KisToolPaint::slotPopupQuickHelp()
{
QWhatsThis::showText(QCursor::pos(), quickHelp());
}
void KisToolPaint::updateTabletPressureSamples()
{
KisConfig cfg;
KisCubicCurve curve;
curve.fromString(cfg.pressureTabletCurve());
m_pressureSamples = curve.floatTransfer(LEVEL_OF_PRESSURE_RESOLUTION + 1);
}
void KisToolPaint::setupPaintAction(KisRecordedPaintAction* action)
{
KisTool::setupPaintAction(action);
action->setOpacity(m_opacity / qreal(255.0));
const KoCompositeOp* op = compositeOp();
if (op) {
action->setCompositeOp(op->id());
}
}
KisToolPaint::NodePaintAbility KisToolPaint::nodePaintAbility()
{
KisNodeSP node = currentNode();
if (!node) {
return NONE;
}
if (node->inherits("KisShapeLayer")) {
return VECTOR;
}
if (node->paintDevice()) {
return PAINT;
}
return NONE;
}
void KisToolPaint::activatePrimaryAction()
{
pickColorWasOverridden();
setOutlineEnabled(true);
KisTool::activatePrimaryAction();
}
void KisToolPaint::deactivatePrimaryAction()
{
setOutlineEnabled(false);
KisTool::deactivatePrimaryAction();
}
bool KisToolPaint::isOutlineEnabled() const
{
return m_isOutlineEnabled;
}
void KisToolPaint::setOutlineEnabled(bool value)
{
m_isOutlineEnabled = value;
requestUpdateOutline(m_outlineDocPoint, 0);
}
void KisToolPaint::increaseBrushSize()
{
qreal paintopSize = currentPaintOpPreset()->settings()->paintOpSize();
std::vector<int>::iterator result =
std::upper_bound(m_standardBrushSizes.begin(),
m_standardBrushSizes.end(),
qRound(paintopSize));
int newValue = result != m_standardBrushSizes.end() ? *result : m_standardBrushSizes.back();
currentPaintOpPreset()->settings()->setPaintOpSize(newValue);
requestUpdateOutline(m_outlineDocPoint, 0);
}
void KisToolPaint::decreaseBrushSize()
{
qreal paintopSize = currentPaintOpPreset()->settings()->paintOpSize();
std::vector<int>::reverse_iterator result =
std::upper_bound(m_standardBrushSizes.rbegin(),
m_standardBrushSizes.rend(),
(int)paintopSize,
std::greater<int>());
int newValue = result != m_standardBrushSizes.rend() ? *result : m_standardBrushSizes.front();
currentPaintOpPreset()->settings()->setPaintOpSize(newValue);
requestUpdateOutline(m_outlineDocPoint, 0);
}
QRectF KisToolPaint::colorPreviewDocRect(const QPointF &outlineDocPoint)
{
if (!m_showColorPreview) return QRect();
KisConfig cfg;
const QRectF colorPreviewViewRect = cfg.colorPreviewRect();
const QRectF colorPreviewDocumentRect = canvas()->viewConverter()->viewToDocument(colorPreviewViewRect);
return colorPreviewDocumentRect.translated(outlineDocPoint);
}
void KisToolPaint::requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event)
{
if (!m_supportOutline) return;
KisConfig cfg;
KisPaintOpSettings::OutlineMode outlineMode;
outlineMode = KisPaintOpSettings::CursorNoOutline;
if (isOutlineEnabled() &&
(mode() == KisTool::GESTURE_MODE ||
((cfg.newOutlineStyle() == OUTLINE_FULL ||
cfg.newOutlineStyle() == OUTLINE_CIRCLE ||
cfg.newOutlineStyle() == OUTLINE_TILT ||
cfg.newOutlineStyle() == OUTLINE_COLOR ) &&
((mode() == HOVER_MODE) ||
(mode() == PAINT_MODE && cfg.showOutlineWhilePainting()))))) { // lisp forever!
if(cfg.newOutlineStyle() == OUTLINE_CIRCLE) {
outlineMode = KisPaintOpSettings::CursorIsCircleOutline;
} else if(cfg.newOutlineStyle() == OUTLINE_TILT) {
outlineMode = KisPaintOpSettings::CursorTiltOutline;
} else if(cfg.newOutlineStyle() == OUTLINE_COLOR) {
outlineMode = KisPaintOpSettings::CursorColorOutline;
} else {
outlineMode = KisPaintOpSettings::CursorIsOutline;
}
}
m_outlineDocPoint = outlineDocPoint;
m_currentOutline = getOutlinePath(m_outlineDocPoint, event, outlineMode);
QRectF outlinePixelRect = m_currentOutline.boundingRect();
QRectF outlineDocRect = currentImage()->pixelToDocument(outlinePixelRect);
// This adjusted call is needed as we paint with a 3 pixel wide brush and the pen is outside the bounds of the path
// Pen uses view coordinates so we have to zoom the document value to match 2 pixel in view coordiates
// See BUG 275829
qreal zoomX;
qreal zoomY;
canvas()->viewConverter()->zoom(&zoomX, &zoomY);
qreal xoffset = 2.0/zoomX;
qreal yoffset = 2.0/zoomY;
if (!outlineDocRect.isEmpty()) {
outlineDocRect.adjust(-xoffset,-yoffset,xoffset,yoffset);
}
QRectF colorPreviewDocRect = this->colorPreviewDocRect(m_outlineDocPoint);
QRectF colorPreviewDocUpdateRect;
if (!colorPreviewDocRect.isEmpty()) {
colorPreviewDocUpdateRect.adjust(-xoffset,-yoffset,xoffset,yoffset);
}
// DIRTY HACK ALERT: we should fetch the assistant's dirty rect when requesting
// the update, instead of just dumbly update the entire canvas!
KisCanvas2 * kiscanvas = dynamic_cast<KisCanvas2*>(canvas());
KisPaintingAssistantsDecorationSP decoration = kiscanvas->paintingAssistantsDecoration();
if (decoration && decoration->visible()) {
kiscanvas->updateCanvas();
} else {
// TODO: only this branch should be present!
if (!m_oldColorPreviewUpdateRect.isEmpty()) {
canvas()->updateCanvas(m_oldColorPreviewUpdateRect);
}
if (!m_oldOutlineRect.isEmpty()) {
canvas()->updateCanvas(m_oldOutlineRect);
}
if (!outlineDocRect.isEmpty()) {
canvas()->updateCanvas(outlineDocRect);
}
if (!colorPreviewDocUpdateRect.isEmpty()) {
canvas()->updateCanvas(colorPreviewDocUpdateRect);
}
}
m_oldOutlineRect = outlineDocRect;
m_oldColorPreviewRect = colorPreviewDocRect;
m_oldColorPreviewUpdateRect = colorPreviewDocUpdateRect;
}
QPainterPath KisToolPaint::getOutlinePath(const QPointF &documentPos,
const KoPointerEvent *event,
KisPaintOpSettings::OutlineMode outlineMode)
{
Q_UNUSED(event);
QPointF imagePos = currentImage()->documentToPixel(documentPos);
QPainterPath path = currentPaintOpPreset()->settings()->
brushOutline(KisPaintInformation(imagePos), outlineMode);
return path;
}
diff --git a/libs/ui/tool/kis_tool_paint.h b/libs/ui/tool/kis_tool_paint.h
index d7eae7272f..609199ec07 100644
--- a/libs/ui/tool/kis_tool_paint.h
+++ b/libs/ui/tool/kis_tool_paint.h
@@ -1,236 +1,239 @@
/*
* Copyright (c) 2003 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TOOL_PAINT_H_
#define KIS_TOOL_PAINT_H_
#include <vector>
#include <QCursor>
#include <QLayout>
#include <QGridLayout>
#include <QVariant>
#include <QTimer>
#include <KoCanvasResourceManager.h>
#include <KoToolBase.h>
#include <resources/KoAbstractGradient.h>
#include <kritaui_export.h>
#include <kis_types.h>
#include <kis_image.h>
#include "kis_signal_compressor_with_param.h"
#include <brushengine/kis_paintop_settings.h>
#include <resources/KoPattern.h>
#include "kis_tool.h"
#include <QCheckBox>
class QGridLayout;
class KoCompositeOp;
class KoCanvasBase;
// wacom
const static int LEVEL_OF_PRESSURE_RESOLUTION = 1024;
class KRITAUI_EXPORT KisToolPaint : public KisTool
{
Q_OBJECT
public:
KisToolPaint(KoCanvasBase * canvas, const QCursor & cursor);
~KisToolPaint() override;
int flags() const override;
void mousePressEvent(KoPointerEvent *event) override;
void mouseReleaseEvent(KoPointerEvent *event) override;
void mouseMoveEvent(KoPointerEvent *event) override;
protected:
void setMode(ToolMode mode) override;
void canvasResourceChanged(int key, const QVariant & v) override;
void paint(QPainter& gc, const KoViewConverter &converter) override;
void activatePrimaryAction() override;
void deactivatePrimaryAction() override;
void activateAlternateAction(AlternateAction action) override;
void deactivateAlternateAction(AlternateAction action) override;
void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override;
void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override;
void endAlternateAction(KoPointerEvent *event, AlternateAction action) override;
virtual void requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event);
/** If the paint tool support outline like brushes, set to true.
* If not (e.g. gradient tool), set to false. Default is false.
*/
void setSupportOutline(bool supportOutline) {
m_supportOutline = supportOutline;
}
virtual QPainterPath getOutlinePath(const QPointF &documentPos,
const KoPointerEvent *event,
KisPaintOpSettings::OutlineMode outlineMode);
protected:
bool isOutlineEnabled() const;
void setOutlineEnabled(bool enabled);
bool pickColor(const QPointF &documentPixel, AlternateAction action);
/// Add the tool-specific layout to the default option widget layout.
void addOptionWidgetLayout(QLayout *layout);
/// Add a widget and a label to the current option widget layout.
virtual void addOptionWidgetOption(QWidget *control, QWidget *label = 0);
void showControl(QWidget *control, bool value);
void enableControl(QWidget *control, bool value);
QWidget * createOptionWidget() override;
/**
* Quick help is a short help text about the way the tool functions.
*/
virtual QString quickHelp() const {
return QString();
}
void setupPaintAction(KisRecordedPaintAction* action) override;
qreal pressureToCurve(qreal pressure){
qreal p = qRound(pressure * LEVEL_OF_PRESSURE_RESOLUTION);
if (p < 0) {
return m_pressureSamples.first();
}
else if (p < m_pressureSamples.size()) {
return m_pressureSamples.at(p);
}
return m_pressureSamples.last();
}
enum NodePaintAbility {
NONE,
PAINT,
VECTOR
};
/// Checks if and how the tool can paint on the current node
NodePaintAbility nodePaintAbility();
const KoCompositeOp* compositeOp();
public Q_SLOTS:
void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes) override;
void deactivate() override;
private Q_SLOTS:
void slotPopupQuickHelp();
void increaseBrushSize();
void decreaseBrushSize();
void activatePickColorDelayed();
void slotColorPickingFinished(const KoColor &color);
protected Q_SLOTS:
void updateTabletPressureSamples();
protected:
quint8 m_opacity;
bool m_paintOutline;
QVector<qreal> m_pressureSamples;
QPointF m_outlineDocPoint;
QPainterPath m_currentOutline;
QRectF m_oldOutlineRect;
bool m_showColorPreview;
QRectF m_oldColorPreviewRect;
QRectF m_oldColorPreviewUpdateRect;
QColor m_colorPreviewCurrentColor;
bool m_colorPreviewShowComparePlate;
QColor m_colorPreviewBaseColor;
private:
QPainterPath tryFixBrushOutline(const QPainterPath &originalOutline);
void setOpacity(qreal opacity);
void activatePickColor(AlternateAction action);
void deactivatePickColor(AlternateAction action);
void pickColorWasOverridden();
int colorPreviewResourceId(AlternateAction action);
QRectF colorPreviewDocRect(const QPointF &outlineDocPoint);
bool isPickingAction(AlternateAction action);
struct PickingJob {
PickingJob() {}
PickingJob(QPointF _documentPixel,
AlternateAction _action)
: documentPixel(_documentPixel),
action(_action) {}
QPointF documentPixel;
AlternateAction action;
};
void addPickerJob(const PickingJob &pickingJob);
private:
bool m_specialHoverModifier;
QGridLayout *m_optionsWidgetLayout;
bool m_supportOutline;
/**
* Used as a switch for pickColor
*/
// used to skip some of the tablet events and don't update the colour that often
QTimer m_colorPickerDelayTimer;
AlternateAction delayedAction;
bool m_isOutlineEnabled;
std::vector<int> m_standardBrushSizes;
KisStrokeId m_pickerStrokeId;
int m_pickingResource;
typedef KisSignalCompressorWithParam<PickingJob> PickingCompressor;
QScopedPointer<PickingCompressor> m_colorPickingCompressor;
+ qreal m_localOpacity {1.0};
+ qreal m_oldOpacity {1.0};
+
Q_SIGNALS:
void sigPaintingFinished();
};
#endif // KIS_TOOL_PAINT_H_
diff --git a/libs/ui/tool/strokes/FreehandStrokeRunnableJobDataWithUpdate.h b/libs/ui/tool/strokes/FreehandStrokeRunnableJobDataWithUpdate.h
new file mode 100644
index 0000000000..e268c4e0a3
--- /dev/null
+++ b/libs/ui/tool/strokes/FreehandStrokeRunnableJobDataWithUpdate.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef FREEHANDSTROKERUNNABLEJOBDATAWITHUPDATE_H
+#define FREEHANDSTROKERUNNABLEJOBDATAWITHUPDATE_H
+
+#include "KisRunnableStrokeJobData.h"
+
+
+class FreehandStrokeRunnableJobDataWithUpdate : public KisRunnableStrokeJobData
+{
+public:
+ FreehandStrokeRunnableJobDataWithUpdate(QRunnable *runnable, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL,
+ KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL)
+ : KisRunnableStrokeJobData(runnable, sequentiality, exclusivity)
+ {
+ }
+
+ FreehandStrokeRunnableJobDataWithUpdate(std::function<void()> func, KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL,
+ KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL)
+ : KisRunnableStrokeJobData(func, sequentiality, exclusivity)
+ {
+ }
+};
+
+#endif // FREEHANDSTROKERUNNABLEJOBDATAWITHUPDATE_H
diff --git a/libs/ui/tool/strokes/KisStrokeEfficiencyMeasurer.cpp b/libs/ui/tool/strokes/KisStrokeEfficiencyMeasurer.cpp
new file mode 100644
index 0000000000..fc41abd846
--- /dev/null
+++ b/libs/ui/tool/strokes/KisStrokeEfficiencyMeasurer.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisStrokeEfficiencyMeasurer.h"
+
+#include <QPointF>
+#include <QVector>
+#include <QElapsedTimer>
+
+#include <boost/optional.hpp>
+
+#include "kis_global.h"
+
+struct KisStrokeEfficiencyMeasurer::Private
+{
+ boost::optional<QPointF> lastSamplePos;
+ qreal distance = 0;
+
+ QElapsedTimer strokeTimeSource;
+ bool isEnabled = true;
+
+ int renderingStartTime = 0;
+ int renderingTime = 0;
+
+ int cursorMoveStartTime = 0;
+ int cursorMoveTime = 0;
+
+ int framesCount = 0;
+
+};
+
+KisStrokeEfficiencyMeasurer::KisStrokeEfficiencyMeasurer()
+ : m_d(new Private())
+{
+ m_d->strokeTimeSource.start();
+}
+
+KisStrokeEfficiencyMeasurer::~KisStrokeEfficiencyMeasurer()
+{
+}
+
+void KisStrokeEfficiencyMeasurer::setEnabled(bool value)
+{
+ m_d->isEnabled = value;
+}
+
+bool KisStrokeEfficiencyMeasurer::isEnabled() const
+{
+ return m_d->isEnabled;
+}
+
+void KisStrokeEfficiencyMeasurer::addSample(const QPointF &pt)
+{
+ if (!m_d->isEnabled) return;
+
+ if (!m_d->lastSamplePos) {
+ m_d->lastSamplePos = pt;
+ } else {
+ m_d->distance += kisDistance(pt, *m_d->lastSamplePos);
+ *m_d->lastSamplePos = pt;
+ }
+}
+
+void KisStrokeEfficiencyMeasurer::addSamples(const QVector<QPointF> &points)
+{
+ if (!m_d->isEnabled) return;
+
+ Q_FOREACH (const QPointF &pt, points) {
+ addSample(pt);
+ }
+}
+
+void KisStrokeEfficiencyMeasurer::notifyRenderingStarted()
+{
+ m_d->renderingStartTime = m_d->strokeTimeSource.elapsed();
+}
+
+void KisStrokeEfficiencyMeasurer::notifyRenderingFinished()
+{
+ m_d->renderingTime = m_d->strokeTimeSource.elapsed() - m_d->renderingStartTime;
+}
+
+void KisStrokeEfficiencyMeasurer::notifyCursorMoveStarted()
+{
+ m_d->cursorMoveStartTime = m_d->strokeTimeSource.elapsed();
+}
+
+void KisStrokeEfficiencyMeasurer::notifyCursorMoveFinished()
+{
+ m_d->cursorMoveTime = m_d->strokeTimeSource.elapsed() - m_d->cursorMoveStartTime;
+}
+
+void KisStrokeEfficiencyMeasurer::notifyFrameRenderingStarted()
+{
+ m_d->framesCount++;
+}
+
+qreal KisStrokeEfficiencyMeasurer::averageCursorSpeed() const
+{
+ return m_d->cursorMoveTime ? m_d->distance / m_d->cursorMoveTime : 0.0;
+}
+
+qreal KisStrokeEfficiencyMeasurer::averageRenderingSpeed() const
+{
+ return m_d->renderingTime ? m_d->distance / m_d->renderingTime : 0.0;
+}
+
+qreal KisStrokeEfficiencyMeasurer::averageFps() const
+{
+ return m_d->renderingTime ? m_d->framesCount * 1000.0 / m_d->renderingTime : 0.0;
+}
+
+
diff --git a/libs/ui/opengl/kis_opengl_canvas_debugger.h b/libs/ui/tool/strokes/KisStrokeEfficiencyMeasurer.h
similarity index 50%
copy from libs/ui/opengl/kis_opengl_canvas_debugger.h
copy to libs/ui/tool/strokes/KisStrokeEfficiencyMeasurer.h
index 8d7e605e96..4fa26e5082 100644
--- a/libs/ui/opengl/kis_opengl_canvas_debugger.h
+++ b/libs/ui/tool/strokes/KisStrokeEfficiencyMeasurer.h
@@ -1,45 +1,60 @@
/*
- * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef __KIS_OPENGL_CANVAS_DEBUGGER_H
-#define __KIS_OPENGL_CANVAS_DEBUGGER_H
+#ifndef KISSTROKEEFFICIENCYMEASURER_H
+#define KISSTROKEEFFICIENCYMEASURER_H
+#include "kritaui_export.h"
#include <QScopedPointer>
+#include <QtGlobal>
+class QPointF;
-class KisOpenglCanvasDebugger
+class KRITAUI_EXPORT KisStrokeEfficiencyMeasurer
{
public:
- KisOpenglCanvasDebugger();
- ~KisOpenglCanvasDebugger();
+ KisStrokeEfficiencyMeasurer();
+ ~KisStrokeEfficiencyMeasurer();
- static KisOpenglCanvasDebugger* instance();
+ void setEnabled(bool value);
+ bool isEnabled() const;
- bool showFpsOnCanvas() const;
+ void addSample(const QPointF &pt);
+ void addSamples(const QVector<QPointF> &points);
- void nofityPaintRequested();
- void nofitySyncStatus(bool value);
- qreal accumulatedFps();
+ qreal averageCursorSpeed() const;
+ qreal averageRenderingSpeed() const;
+ qreal averageFps() const;
+
+ void notifyRenderingStarted();
+ void notifyRenderingFinished();
+
+ void notifyCursorMoveStarted();
+ void notifyCursorMoveFinished();
+
+ void notifyFrameRenderingStarted();
+
+ void reset();
private:
struct Private;
const QScopedPointer<Private> m_d;
};
-#endif /* __KIS_OPENGL_CANVAS_DEBUGGER_H */
+#endif // KISSTROKEEFFICIENCYMEASURER_H
diff --git a/libs/ui/tool/strokes/freehand_stroke.cpp b/libs/ui/tool/strokes/freehand_stroke.cpp
index 2ca96405bc..1b35c3b2c1 100644
--- a/libs/ui/tool/strokes/freehand_stroke.cpp
+++ b/libs/ui/tool/strokes/freehand_stroke.cpp
@@ -1,149 +1,300 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "freehand_stroke.h"
+#include <QElapsedTimer>
+
#include "kis_canvas_resource_provider.h"
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paintop_settings.h>
#include "kis_painter.h"
+#include "kis_paintop.h"
#include "kis_update_time_monitor.h"
#include <brushengine/kis_stroke_random_source.h>
+#include <KisRunnableStrokeJobsInterface.h>
+#include "FreehandStrokeRunnableJobDataWithUpdate.h"
+#include <mutex>
+#include "KisStrokeEfficiencyMeasurer.h"
+#include <KisStrokeSpeedMonitor.h>
struct FreehandStrokeStrategy::Private
{
- Private(KisResourcesSnapshotSP _resources) : resources(_resources) {}
+ Private(KisResourcesSnapshotSP _resources)
+ : resources(_resources),
+ needsAsynchronousUpdates(_resources->presetNeedsAsynchronousUpdates())
+ {
+ if (needsAsynchronousUpdates) {
+ timeSinceLastUpdate.start();
+ }
+ }
+
+ Private(const Private &rhs)
+ : randomSource(rhs.randomSource),
+ resources(rhs.resources),
+ needsAsynchronousUpdates(rhs.needsAsynchronousUpdates)
+ {
+ if (needsAsynchronousUpdates) {
+ timeSinceLastUpdate.start();
+ }
+ }
KisStrokeRandomSource randomSource;
KisResourcesSnapshotSP resources;
+
+ KisStrokeEfficiencyMeasurer efficiencyMeasurer;
+
+ QElapsedTimer timeSinceLastUpdate;
+ int currentUpdatePeriod = 40;
+
+ const bool needsAsynchronousUpdates = false;
+ std::mutex updateEntryMutex;
};
FreehandStrokeStrategy::FreehandStrokeStrategy(bool needsIndirectPainting,
const QString &indirectPaintingCompositeOp,
KisResourcesSnapshotSP resources,
PainterInfo *painterInfo,
const KUndo2MagicString &name)
: KisPainterBasedStrokeStrategy("FREEHAND_STROKE", name,
resources, painterInfo),
m_d(new Private(resources))
{
init(needsIndirectPainting, indirectPaintingCompositeOp);
}
FreehandStrokeStrategy::FreehandStrokeStrategy(bool needsIndirectPainting,
const QString &indirectPaintingCompositeOp,
KisResourcesSnapshotSP resources,
QVector<PainterInfo*> painterInfos,
const KUndo2MagicString &name)
: KisPainterBasedStrokeStrategy("FREEHAND_STROKE", name,
resources, painterInfos),
m_d(new Private(resources))
{
init(needsIndirectPainting, indirectPaintingCompositeOp);
}
FreehandStrokeStrategy::FreehandStrokeStrategy(const FreehandStrokeStrategy &rhs, int levelOfDetail)
: KisPainterBasedStrokeStrategy(rhs, levelOfDetail),
m_d(new Private(*rhs.m_d))
{
m_d->randomSource.setLevelOfDetail(levelOfDetail);
}
FreehandStrokeStrategy::~FreehandStrokeStrategy()
{
+ KisStrokeSpeedMonitor::instance()->notifyStrokeFinished(m_d->efficiencyMeasurer.averageCursorSpeed(),
+ m_d->efficiencyMeasurer.averageRenderingSpeed(),
+ m_d->efficiencyMeasurer.averageFps(),
+ m_d->resources->currentPaintOpPreset());
+
KisUpdateTimeMonitor::instance()->endStrokeMeasure();
}
void FreehandStrokeStrategy::init(bool needsIndirectPainting,
const QString &indirectPaintingCompositeOp)
{
setNeedsIndirectPainting(needsIndirectPainting);
setIndirectPaintingCompositeOp(indirectPaintingCompositeOp);
setSupportsWrapAroundMode(true);
enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE);
+ if (m_d->needsAsynchronousUpdates) {
+ /**
+ * In case the paintop uses asynchronous updates, we should set priority to it,
+ * because FPS is controlled separately, not by the queue's merging algorithm.
+ */
+ setBalancingRatioOverride(0.01); // set priority to updates
+ }
+
KisUpdateTimeMonitor::instance()->startStrokeMeasure();
+ m_d->efficiencyMeasurer.setEnabled(KisStrokeSpeedMonitor::instance()->haveStrokeSpeedMeasurement());
+}
+
+void FreehandStrokeStrategy::initStrokeCallback()
+{
+ KisPainterBasedStrokeStrategy::initStrokeCallback();
+ m_d->efficiencyMeasurer.notifyRenderingStarted();
+}
+
+void FreehandStrokeStrategy::finishStrokeCallback()
+{
+ m_d->efficiencyMeasurer.notifyRenderingFinished();
+ KisPainterBasedStrokeStrategy::finishStrokeCallback();
}
+
void FreehandStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
{
- Data *d = dynamic_cast<Data*>(data);
- PainterInfo *info = painterInfos()[d->painterInfoId];
-
- KisUpdateTimeMonitor::instance()->reportPaintOpPreset(info->painter->preset());
- KisRandomSourceSP rnd = m_d->randomSource.source();
-
- switch(d->type) {
- case Data::POINT:
- d->pi1.setRandomSource(rnd);
- info->painter->paintAt(d->pi1, info->dragDistance);
- break;
- case Data::LINE:
- d->pi1.setRandomSource(rnd);
- d->pi2.setRandomSource(rnd);
- info->painter->paintLine(d->pi1, d->pi2, info->dragDistance);
- break;
- case Data::CURVE:
- d->pi1.setRandomSource(rnd);
- d->pi2.setRandomSource(rnd);
- info->painter->paintBezierCurve(d->pi1,
- d->control1,
- d->control2,
- d->pi2,
- info->dragDistance);
- break;
- case Data::POLYLINE:
- info->painter->paintPolyline(d->points, 0, d->points.size());
- break;
- case Data::POLYGON:
- info->painter->paintPolygon(d->points);
- break;
- case Data::RECT:
- info->painter->paintRect(d->rect);
- break;
- case Data::ELLIPSE:
- info->painter->paintEllipse(d->rect);
- break;
- case Data::PAINTER_PATH:
- info->painter->paintPainterPath(d->path);
- break;
- case Data::QPAINTER_PATH:
- info->painter->drawPainterPath(d->path, d->pen);
- break;
- case Data::QPAINTER_PATH_FILL: {
- info->painter->setBackgroundColor(d->customColor);
- info->painter->fillPainterPath(d->path);}
- info->painter->drawPainterPath(d->path, d->pen);
- break;
- };
-
- QVector<QRect> dirtyRects = info->painter->takeDirtyRegion();
- KisUpdateTimeMonitor::instance()->reportJobFinished(data, dirtyRects);
- d->node->setDirty(dirtyRects);
+ PainterInfo *info = 0;
+
+ if (UpdateData *d = dynamic_cast<UpdateData*>(data)) {
+ // this job is lod-clonable in contrast to FreehandStrokeRunnableJobDataWithUpdate!
+ tryDoUpdate(d->forceUpdate);
+
+ } else if (Data *d = dynamic_cast<Data*>(data)) {
+ info = painterInfos()[d->painterInfoId];
+
+ KisUpdateTimeMonitor::instance()->reportPaintOpPreset(info->painter->preset());
+ KisRandomSourceSP rnd = m_d->randomSource.source();
+ KisPerStrokeRandomSourceSP strokeRnd = m_d->randomSource.perStrokeSource();
+
+ switch(d->type) {
+ case Data::POINT:
+ d->pi1.setRandomSource(rnd);
+ d->pi1.setPerStrokeRandomSource(strokeRnd);
+ info->painter->paintAt(d->pi1, info->dragDistance);
+ m_d->efficiencyMeasurer.addSample(d->pi1.pos());
+ break;
+ case Data::LINE:
+ d->pi1.setRandomSource(rnd);
+ d->pi2.setRandomSource(rnd);
+ d->pi1.setPerStrokeRandomSource(strokeRnd);
+ d->pi2.setPerStrokeRandomSource(strokeRnd);
+ info->painter->paintLine(d->pi1, d->pi2, info->dragDistance);
+ m_d->efficiencyMeasurer.addSample(d->pi2.pos());
+ break;
+ case Data::CURVE:
+ d->pi1.setRandomSource(rnd);
+ d->pi2.setRandomSource(rnd);
+ d->pi1.setPerStrokeRandomSource(strokeRnd);
+ d->pi2.setPerStrokeRandomSource(strokeRnd);
+ info->painter->paintBezierCurve(d->pi1,
+ d->control1,
+ d->control2,
+ d->pi2,
+ info->dragDistance);
+ m_d->efficiencyMeasurer.addSample(d->pi2.pos());
+ break;
+ case Data::POLYLINE:
+ info->painter->paintPolyline(d->points, 0, d->points.size());
+ m_d->efficiencyMeasurer.addSamples(d->points);
+ break;
+ case Data::POLYGON:
+ info->painter->paintPolygon(d->points);
+ m_d->efficiencyMeasurer.addSamples(d->points);
+ break;
+ case Data::RECT:
+ info->painter->paintRect(d->rect);
+ m_d->efficiencyMeasurer.addSample(d->rect.topLeft());
+ m_d->efficiencyMeasurer.addSample(d->rect.topRight());
+ m_d->efficiencyMeasurer.addSample(d->rect.bottomRight());
+ m_d->efficiencyMeasurer.addSample(d->rect.bottomLeft());
+ break;
+ case Data::ELLIPSE:
+ info->painter->paintEllipse(d->rect);
+ // TODO: add speed measures
+ break;
+ case Data::PAINTER_PATH:
+ info->painter->paintPainterPath(d->path);
+ // TODO: add speed measures
+ break;
+ case Data::QPAINTER_PATH:
+ info->painter->drawPainterPath(d->path, d->pen);
+ break;
+ case Data::QPAINTER_PATH_FILL: {
+ info->painter->setBackgroundColor(d->customColor);
+ info->painter->fillPainterPath(d->path);}
+ info->painter->drawPainterPath(d->path, d->pen);
+ break;
+ };
+
+ tryDoUpdate();
+ } else {
+ KisPainterBasedStrokeStrategy::doStrokeCallback(data);
+
+ FreehandStrokeRunnableJobDataWithUpdate *dataWithUpdate =
+ dynamic_cast<FreehandStrokeRunnableJobDataWithUpdate*>(data);
+
+ if (dataWithUpdate) {
+ tryDoUpdate();
+ }
+ }
+}
+
+void FreehandStrokeStrategy::tryDoUpdate(bool forceEnd)
+{
+ // we should enter this function only once!
+ std::unique_lock<std::mutex> entryLock(m_d->updateEntryMutex, std::try_to_lock);
+ if (!entryLock.owns_lock()) return;
+
+ if (m_d->needsAsynchronousUpdates) {
+ if (forceEnd || m_d->timeSinceLastUpdate.elapsed() > m_d->currentUpdatePeriod) {
+ m_d->timeSinceLastUpdate.restart();
+
+ Q_FOREACH (PainterInfo *info, painterInfos()) {
+ KisPaintOp *paintop = info->painter->paintOp();
+ KIS_SAFE_ASSERT_RECOVER_RETURN(paintop);
+
+ // TODO: well, we should count all N simultaneous painters for FPS rate!
+ QVector<KisRunnableStrokeJobData*> jobs;
+ m_d->currentUpdatePeriod = paintop->doAsyncronousUpdate(jobs);
+
+ if (!jobs.isEmpty()) {
+ jobs.append(new KisRunnableStrokeJobData(
+ [this] () {
+ this->issueSetDirtySignals();
+ },
+ KisStrokeJobData::SEQUENTIAL));
+
+ runnableJobsInterface()->addRunnableJobs(jobs);
+ m_d->efficiencyMeasurer.notifyFrameRenderingStarted();
+ }
+
+ }
+ }
+ } else {
+ issueSetDirtySignals();
+ }
+
+
+}
+
+void FreehandStrokeStrategy::issueSetDirtySignals()
+{
+ QVector<QRect> dirtyRects;
+
+ Q_FOREACH (PainterInfo *info, painterInfos()) {
+ dirtyRects.append(info->painter->takeDirtyRegion());
+ }
+
+ //KisUpdateTimeMonitor::instance()->reportJobFinished(data, dirtyRects);
+ targetNode()->setDirty(dirtyRects);
}
KisStrokeStrategy* FreehandStrokeStrategy::createLodClone(int levelOfDetail)
{
if (!m_d->resources->presetAllowsLod()) return 0;
FreehandStrokeStrategy *clone = new FreehandStrokeStrategy(*this, levelOfDetail);
return clone;
}
+
+void FreehandStrokeStrategy::notifyUserStartedStroke()
+{
+ m_d->efficiencyMeasurer.notifyCursorMoveStarted();
+}
+
+void FreehandStrokeStrategy::notifyUserEndedStroke()
+{
+ m_d->efficiencyMeasurer.notifyCursorMoveFinished();
+}
diff --git a/libs/ui/tool/strokes/freehand_stroke.h b/libs/ui/tool/strokes/freehand_stroke.h
index 09c316b000..3ee13e65b6 100644
--- a/libs/ui/tool/strokes/freehand_stroke.h
+++ b/libs/ui/tool/strokes/freehand_stroke.h
@@ -1,204 +1,241 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __FREEHAND_STROKE_H
#define __FREEHAND_STROKE_H
#include "kritaui_export.h"
#include "kis_types.h"
#include "kis_node.h"
#include "kis_painter_based_stroke_strategy.h"
#include <kis_distance_information.h>
#include <brushengine/kis_paint_information.h>
#include "kis_lod_transform.h"
#include "KoColor.h"
class KRITAUI_EXPORT FreehandStrokeStrategy : public KisPainterBasedStrokeStrategy
{
public:
class Data : public KisStrokeJobData {
public:
enum DabType {
POINT,
LINE,
CURVE,
POLYLINE,
POLYGON,
RECT,
ELLIPSE,
PAINTER_PATH,
QPAINTER_PATH,
QPAINTER_PATH_FILL
};
- Data(KisNodeSP _node, int _painterInfoId,
+ Data(int _painterInfoId,
const KisPaintInformation &_pi)
- : node(_node), painterInfoId(_painterInfoId),
+ : KisStrokeJobData(KisStrokeJobData::UNIQUELY_CONCURRENT),
+ painterInfoId(_painterInfoId),
type(POINT), pi1(_pi)
{}
- Data(KisNodeSP _node, int _painterInfoId,
+ Data(int _painterInfoId,
const KisPaintInformation &_pi1,
const KisPaintInformation &_pi2)
- : node(_node), painterInfoId(_painterInfoId),
+ : KisStrokeJobData(KisStrokeJobData::UNIQUELY_CONCURRENT),
+ painterInfoId(_painterInfoId),
type(LINE), pi1(_pi1), pi2(_pi2)
{}
- Data(KisNodeSP _node, int _painterInfoId,
+ Data(int _painterInfoId,
const KisPaintInformation &_pi1,
const QPointF &_control1,
const QPointF &_control2,
const KisPaintInformation &_pi2)
- : node(_node), painterInfoId(_painterInfoId),
+ : KisStrokeJobData(KisStrokeJobData::UNIQUELY_CONCURRENT),
+ painterInfoId(_painterInfoId),
type(CURVE), pi1(_pi1), pi2(_pi2),
control1(_control1), control2(_control2)
{}
- Data(KisNodeSP _node, int _painterInfoId,
+ Data(int _painterInfoId,
DabType _type,
const vQPointF &_points)
- : node(_node), painterInfoId(_painterInfoId),
+ : KisStrokeJobData(KisStrokeJobData::UNIQUELY_CONCURRENT),
+ painterInfoId(_painterInfoId),
type(_type), points(_points)
{}
- Data(KisNodeSP _node, int _painterInfoId,
+ Data(int _painterInfoId,
DabType _type,
const QRectF &_rect)
- : node(_node), painterInfoId(_painterInfoId),
+ : KisStrokeJobData(KisStrokeJobData::UNIQUELY_CONCURRENT),
+ painterInfoId(_painterInfoId),
type(_type), rect(_rect)
{}
- Data(KisNodeSP _node, int _painterInfoId,
+ Data(int _painterInfoId,
DabType _type,
const QPainterPath &_path)
- : node(_node), painterInfoId(_painterInfoId),
+ : KisStrokeJobData(KisStrokeJobData::UNIQUELY_CONCURRENT),
+ painterInfoId(_painterInfoId),
type(_type), path(_path)
{}
- Data(KisNodeSP _node, int _painterInfoId,
+ Data(int _painterInfoId,
DabType _type,
const QPainterPath &_path,
const QPen &_pen, const KoColor &_customColor)
- : node(_node), painterInfoId(_painterInfoId),
+ : KisStrokeJobData(KisStrokeJobData::UNIQUELY_CONCURRENT),
+ painterInfoId(_painterInfoId),
type(_type), path(_path),
pen(_pen), customColor(_customColor)
{}
KisStrokeJobData* createLodClone(int levelOfDetail) override {
return new Data(*this, levelOfDetail);
}
private:
Data(const Data &rhs, int levelOfDetail)
: KisStrokeJobData(rhs),
- node(rhs.node),
painterInfoId(rhs.painterInfoId),
type(rhs.type)
{
KisLodTransform t(levelOfDetail);
switch(type) {
case Data::POINT:
pi1 = t.map(rhs.pi1);
break;
case Data::LINE:
pi1 = t.map(rhs.pi1);
pi2 = t.map(rhs.pi2);
break;
case Data::CURVE:
pi1 = t.map(rhs.pi1);
pi2 = t.map(rhs.pi2);
control1 = t.map(rhs.control1);
control2 = t.map(rhs.control2);
break;
case Data::POLYLINE:
points = t.map(rhs.points);
break;
case Data::POLYGON:
points = t.map(rhs.points);
break;
case Data::RECT:
rect = t.map(rhs.rect);
break;
case Data::ELLIPSE:
rect = t.map(rhs.rect);
break;
case Data::PAINTER_PATH:
path = t.map(rhs.path);
break;
case Data::QPAINTER_PATH:
path = t.map(rhs.path);
pen = rhs.pen;
break;
case Data::QPAINTER_PATH_FILL:
path = t.map(rhs.path);
pen = rhs.pen;
customColor = rhs.customColor;
break;
};
}
public:
- KisNodeSP node;
int painterInfoId;
DabType type;
KisPaintInformation pi1;
KisPaintInformation pi2;
QPointF control1;
QPointF control2;
vQPointF points;
QRectF rect;
QPainterPath path;
QPen pen;
KoColor customColor;
};
+ class UpdateData : public KisStrokeJobData {
+ public:
+ UpdateData(bool _forceUpdate)
+ : KisStrokeJobData(KisStrokeJobData::SEQUENTIAL),
+ forceUpdate(_forceUpdate)
+ {}
+
+
+ KisStrokeJobData* createLodClone(int levelOfDetail) override {
+ return new UpdateData(*this, levelOfDetail);
+ }
+
+ private:
+ UpdateData(const UpdateData &rhs, int levelOfDetail)
+ : KisStrokeJobData(rhs),
+ forceUpdate(rhs.forceUpdate)
+ {
+ Q_UNUSED(levelOfDetail);
+ }
+ public:
+ bool forceUpdate = false;
+ };
+
public:
FreehandStrokeStrategy(bool needsIndirectPainting,
const QString &indirectPaintingCompositeOp,
KisResourcesSnapshotSP resources,
PainterInfo *painterInfo,
const KUndo2MagicString &name);
FreehandStrokeStrategy(bool needsIndirectPainting,
const QString &indirectPaintingCompositeOp,
KisResourcesSnapshotSP resources,
QVector<PainterInfo*> painterInfos,
const KUndo2MagicString &name);
~FreehandStrokeStrategy() override;
+ void initStrokeCallback() override;
+ void finishStrokeCallback() override;
+
void doStrokeCallback(KisStrokeJobData *data) override;
KisStrokeStrategy* createLodClone(int levelOfDetail) override;
+ void notifyUserStartedStroke() override;
+ void notifyUserEndedStroke() override;
+
protected:
FreehandStrokeStrategy(const FreehandStrokeStrategy &rhs, int levelOfDetail);
private:
void init(bool needsIndirectPainting, const QString &indirectPaintingCompositeOp);
+ void tryDoUpdate(bool forceEnd = false);
+ void issueSetDirtySignals();
+
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __FREEHAND_STROKE_H */
diff --git a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp
index ba003cb9a4..6291dde590 100644
--- a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp
+++ b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp
@@ -1,316 +1,322 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_painter_based_stroke_strategy.h"
#include <KoColorSpace.h>
#include <KoColor.h>
#include <KoCompositeOp.h>
#include "kis_painter.h"
#include "kis_paint_device.h"
#include "kis_paint_layer.h"
#include "kis_transaction.h"
#include "kis_image.h"
#include <kis_distance_information.h>
#include "kis_undo_stores.h"
KisPainterBasedStrokeStrategy::PainterInfo::PainterInfo()
: painter(new KisPainter()),
dragDistance(new KisDistanceInformation()),
m_parentPainterInfo(0),
m_childPainterInfo(0)
{
}
KisPainterBasedStrokeStrategy::PainterInfo::PainterInfo(const KisDistanceInformation &startDist)
: painter(new KisPainter()),
dragDistance(new KisDistanceInformation(startDist)),
m_parentPainterInfo(0),
m_childPainterInfo(0)
{
}
KisPainterBasedStrokeStrategy::PainterInfo::PainterInfo(PainterInfo *rhs, int levelOfDetail)
: painter(new KisPainter()),
dragDistance(new KisDistanceInformation(*rhs->dragDistance, levelOfDetail)),
m_parentPainterInfo(rhs)
{
rhs->m_childPainterInfo = this;
}
KisPainterBasedStrokeStrategy::PainterInfo::~PainterInfo()
{
if (m_parentPainterInfo) {
m_parentPainterInfo->m_childPainterInfo = 0;
}
delete(painter);
delete(dragDistance);
}
KisDistanceInformation* KisPainterBasedStrokeStrategy::PainterInfo::buddyDragDistance()
{
return m_childPainterInfo ? m_childPainterInfo->dragDistance : 0;
}
KisPainterBasedStrokeStrategy::KisPainterBasedStrokeStrategy(const QString &id,
const KUndo2MagicString &name,
KisResourcesSnapshotSP resources,
QVector<PainterInfo*> painterInfos,bool useMergeID)
- : KisSimpleStrokeStrategy(id, name),
+ : KisRunnableBasedStrokeStrategy(id, name),
m_resources(resources),
m_painterInfos(painterInfos),
m_transaction(0),
m_useMergeID(useMergeID)
{
init();
}
KisPainterBasedStrokeStrategy::KisPainterBasedStrokeStrategy(const QString &id,
const KUndo2MagicString &name,
KisResourcesSnapshotSP resources,
PainterInfo *painterInfo,bool useMergeID)
- : KisSimpleStrokeStrategy(id, name),
+ : KisRunnableBasedStrokeStrategy(id, name),
m_resources(resources),
m_painterInfos(QVector<PainterInfo*>() << painterInfo),
m_transaction(0),
m_useMergeID(useMergeID)
{
init();
}
void KisPainterBasedStrokeStrategy::init()
{
enableJob(KisSimpleStrokeStrategy::JOB_INIT);
enableJob(KisSimpleStrokeStrategy::JOB_FINISH);
enableJob(KisSimpleStrokeStrategy::JOB_CANCEL, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
enableJob(KisSimpleStrokeStrategy::JOB_SUSPEND);
enableJob(KisSimpleStrokeStrategy::JOB_RESUME);
}
KisPainterBasedStrokeStrategy::KisPainterBasedStrokeStrategy(const KisPainterBasedStrokeStrategy &rhs, int levelOfDetail)
- : KisSimpleStrokeStrategy(rhs),
+ : KisRunnableBasedStrokeStrategy(rhs),
m_resources(rhs.m_resources),
m_transaction(rhs.m_transaction),
m_useMergeID(rhs.m_useMergeID)
{
Q_FOREACH (PainterInfo *info, rhs.m_painterInfos) {
m_painterInfos.append(new PainterInfo(info, levelOfDetail));
}
KIS_ASSERT_RECOVER_NOOP(
!rhs.m_transaction &&
!rhs.m_targetDevice &&
!rhs.m_activeSelection &&
"After the stroke has been started, no copying must happen");
}
KisPaintDeviceSP KisPainterBasedStrokeStrategy::targetDevice() const
{
return m_targetDevice;
}
KisSelectionSP KisPainterBasedStrokeStrategy::activeSelection() const
{
return m_activeSelection;
}
const QVector<KisPainterBasedStrokeStrategy::PainterInfo*>
KisPainterBasedStrokeStrategy::painterInfos() const
{
return m_painterInfos;
}
void KisPainterBasedStrokeStrategy::initPainters(KisPaintDeviceSP targetDevice,
KisSelectionSP selection,
bool hasIndirectPainting,
const QString &indirectPaintingCompositeOp)
{
Q_FOREACH (PainterInfo *info, m_painterInfos) {
KisPainter *painter = info->painter;
painter->begin(targetDevice, !hasIndirectPainting ? selection : 0);
+ painter->setRunnableStrokeJobsInterface(runnableJobsInterface());
m_resources->setupPainter(painter);
if(hasIndirectPainting) {
painter->setCompositeOp(targetDevice->colorSpace()->compositeOp(indirectPaintingCompositeOp));
painter->setOpacity(OPACITY_OPAQUE_U8);
painter->setChannelFlags(QBitArray());
}
}
}
void KisPainterBasedStrokeStrategy::deletePainters()
{
Q_FOREACH (PainterInfo *info, m_painterInfos) {
delete info;
}
m_painterInfos.clear();
}
void KisPainterBasedStrokeStrategy::initStrokeCallback()
{
KisNodeSP node = m_resources->currentNode();
KisPaintDeviceSP paintDevice = node->paintDevice();
KisPaintDeviceSP targetDevice = paintDevice;
bool hasIndirectPainting = needsIndirectPainting();
KisSelectionSP selection = m_resources->activeSelection();
if (hasIndirectPainting) {
KisIndirectPaintingSupport *indirect =
dynamic_cast<KisIndirectPaintingSupport*>(node.data());
if (indirect) {
targetDevice = paintDevice->createCompositionSourceDevice();
targetDevice->setParentNode(node);
indirect->setCurrentColor(m_resources->currentFgColor());
indirect->setTemporaryTarget(targetDevice);
indirect->setTemporaryCompositeOp(m_resources->compositeOpId());
indirect->setTemporaryOpacity(m_resources->opacity());
indirect->setTemporarySelection(selection);
QBitArray channelLockFlags = m_resources->channelLockFlags();
indirect->setTemporaryChannelFlags(channelLockFlags);
}
else {
hasIndirectPainting = false;
}
}
if(m_useMergeID){
m_transaction = new KisTransaction(name(), targetDevice,0,timedID(this->id()));
}
else{
m_transaction = new KisTransaction(name(), targetDevice);
}
initPainters(targetDevice, selection, hasIndirectPainting, indirectPaintingCompositeOp());
m_targetDevice = targetDevice;
m_activeSelection = selection;
// sanity check: selection should be applied only once
if (selection && !m_painterInfos.isEmpty()) {
KisIndirectPaintingSupport *indirect =
dynamic_cast<KisIndirectPaintingSupport*>(node.data());
KIS_ASSERT_RECOVER_RETURN(hasIndirectPainting || m_painterInfos.first()->painter->selection());
KIS_ASSERT_RECOVER_RETURN(!hasIndirectPainting || !indirect->temporarySelection() || !m_painterInfos.first()->painter->selection());
}
}
void KisPainterBasedStrokeStrategy::finishStrokeCallback()
{
KisNodeSP node = m_resources->currentNode();
KisIndirectPaintingSupport *indirect =
dynamic_cast<KisIndirectPaintingSupport*>(node.data());
KisPostExecutionUndoAdapter *undoAdapter =
m_resources->postExecutionUndoAdapter();
QScopedPointer<KisPostExecutionUndoAdapter> dumbUndoAdapter;
QScopedPointer<KisUndoStore> dumbUndoStore;
if (!undoAdapter) {
dumbUndoStore.reset(new KisDumbUndoStore());
dumbUndoAdapter.reset(new KisPostExecutionUndoAdapter(dumbUndoStore.data(), 0));
undoAdapter = dumbUndoAdapter.data();
}
if (indirect && indirect->hasTemporaryTarget()) {
KUndo2MagicString transactionText = m_transaction->text();
m_transaction->end();
if(m_useMergeID){
indirect->mergeToLayer(node,
undoAdapter,
transactionText,timedID(this->id()));
}
else{
indirect->mergeToLayer(node,
undoAdapter,
transactionText);
}
}
else {
m_transaction->commit(undoAdapter);
}
delete m_transaction;
deletePainters();
}
void KisPainterBasedStrokeStrategy::cancelStrokeCallback()
{
KisNodeSP node = m_resources->currentNode();
KisIndirectPaintingSupport *indirect =
dynamic_cast<KisIndirectPaintingSupport*>(node.data());
bool revert = true;
if (indirect) {
KisPaintDeviceSP t = indirect->temporaryTarget();
if (t) {
delete m_transaction;
deletePainters();
QRegion region = t->region();
indirect->setTemporaryTarget(0);
node->setDirty(region);
revert = false;
}
}
if (revert) {
m_transaction->revert();
delete m_transaction;
deletePainters();
}
}
void KisPainterBasedStrokeStrategy::suspendStrokeCallback()
{
KisNodeSP node = m_resources->currentNode();
KisIndirectPaintingSupport *indirect =
dynamic_cast<KisIndirectPaintingSupport*>(node.data());
if(indirect && indirect->hasTemporaryTarget()) {
indirect->setTemporaryTarget(0);
}
}
void KisPainterBasedStrokeStrategy::resumeStrokeCallback()
{
KisNodeSP node = m_resources->currentNode();
KisIndirectPaintingSupport *indirect =
dynamic_cast<KisIndirectPaintingSupport*>(node.data());
if(indirect) {
// todo: don't ask about paint device
if (node->paintDevice() != m_targetDevice) {
indirect->setTemporaryTarget(m_targetDevice);
indirect->setTemporaryCompositeOp(m_resources->compositeOpId());
indirect->setTemporaryOpacity(m_resources->opacity());
indirect->setTemporarySelection(m_activeSelection);
}
}
}
+
+KisNodeSP KisPainterBasedStrokeStrategy::targetNode() const
+{
+ return m_resources->currentNode();
+}
diff --git a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h
index 040559ca91..e772029bbb 100644
--- a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h
+++ b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h
@@ -1,110 +1,111 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_PAINTER_BASED_STROKE_STRATEGY_H
#define __KIS_PAINTER_BASED_STROKE_STRATEGY_H
-#include "kis_simple_stroke_strategy.h"
+#include "KisRunnableBasedStrokeStrategy.h"
#include "kis_resources_snapshot.h"
#include "kis_selection.h"
class KisPainter;
class KisDistanceInformation;
class KisTransaction;
-class KRITAUI_EXPORT KisPainterBasedStrokeStrategy : public KisSimpleStrokeStrategy
+class KRITAUI_EXPORT KisPainterBasedStrokeStrategy : public KisRunnableBasedStrokeStrategy
{
public:
/**
* The distance information should be associated with each
* painter individually, so we strore and manipulate with
* them together using the structure PainterInfo
*/
class KRITAUI_EXPORT PainterInfo {
public:
PainterInfo();
PainterInfo(const KisDistanceInformation &startDist);
PainterInfo(PainterInfo *rhs, int levelOfDetail);
~PainterInfo();
KisPainter *painter;
KisDistanceInformation *dragDistance;
/**
* The distance inforametion of the associated LodN
* stroke. Returns zero if LodN stroke has already finished
* execution or does not exist.
*/
KisDistanceInformation* buddyDragDistance();
private:
PainterInfo *m_parentPainterInfo;
PainterInfo *m_childPainterInfo;
};
public:
KisPainterBasedStrokeStrategy(const QString &id,
const KUndo2MagicString &name,
KisResourcesSnapshotSP resources,
QVector<PainterInfo*> painterInfos,bool useMergeID = false);
KisPainterBasedStrokeStrategy(const QString &id,
const KUndo2MagicString &name,
KisResourcesSnapshotSP resources,
PainterInfo *painterInfo,bool useMergeID = false);
void initStrokeCallback() override;
void finishStrokeCallback() override;
void cancelStrokeCallback() override;
void suspendStrokeCallback() override;
void resumeStrokeCallback() override;
protected:
+ KisNodeSP targetNode() const;
KisPaintDeviceSP targetDevice() const;
KisSelectionSP activeSelection() const;
const QVector<PainterInfo*> painterInfos() const;
void setUndoEnabled(bool value);
protected:
KisPainterBasedStrokeStrategy(const KisPainterBasedStrokeStrategy &rhs, int levelOfDetail);
private:
void init();
void initPainters(KisPaintDeviceSP targetDevice,
KisSelectionSP selection,
bool hasIndirectPainting,
const QString &indirectPaintingCompositeOp);
void deletePainters();
inline int timedID(const QString &id){
return int(qHash(id));
}
private:
KisResourcesSnapshotSP m_resources;
QVector<PainterInfo*> m_painterInfos;
KisTransaction *m_transaction;
KisPaintDeviceSP m_targetDevice;
KisSelectionSP m_activeSelection;
bool m_useMergeID;
};
#endif /* __KIS_PAINTER_BASED_STROKE_STRATEGY_H */
diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc
index e98f7cb161..8a88b4889c 100644
--- a/libs/ui/widgets/kis_advanced_color_space_selector.cc
+++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc
@@ -1,793 +1,793 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
* Copyright (C) 2011 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
* Copyright (C) 2015 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_advanced_color_space_selector.h"
#include <KoFileDialog.h>
#include <KoColorProfile.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpaceEngine.h>
#include <KoColorModelStandardIds.h>
#include <KoID.h>
#include <KoConfig.h>
#include <kis_icon.h>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QTextBrowser>
#include <QScrollBar>
#include <KoResourcePaths.h>
#include <QUrl>
#include "ui_wdgcolorspaceselectoradvanced.h"
#include <kis_debug.h>
struct KisAdvancedColorSpaceSelector::Private {
Ui_WdgColorSpaceSelectorAdvanced* colorSpaceSelector;
QString knsrcFile;
};
KisAdvancedColorSpaceSelector::KisAdvancedColorSpaceSelector(QWidget* parent, const QString &caption)
: QDialog(parent)
, d(new Private)
{
setWindowTitle(caption);
d->colorSpaceSelector = new Ui_WdgColorSpaceSelectorAdvanced;
d->colorSpaceSelector->setupUi(this);
d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible));
fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem());
d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open"));
d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") );
connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)),
this, SLOT(fillCmbDepths(const KoID &)));
connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(const KoID &)),
this, SLOT(fillLstProfiles()));
connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)),
this, SLOT(fillLstProfiles()));
connect(d->colorSpaceSelector->lstProfile, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
this, SLOT(colorSpaceChanged()));
connect(this, SIGNAL(selectionChanged(bool)),
this, SLOT(fillDescription()));
connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TongueWidget, SLOT(repaint()));
connect(this, SIGNAL(selectionChanged(bool)), d->colorSpaceSelector->TRCwidget, SLOT(repaint()));
connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()), this, SLOT(installProfile()));
connect(d->colorSpaceSelector->bnOK, SIGNAL(accepted()), this, SLOT(accept()));
connect(d->colorSpaceSelector->bnOK, SIGNAL(rejected()), this, SLOT(reject()));
fillLstProfiles();
}
KisAdvancedColorSpaceSelector::~KisAdvancedColorSpaceSelector()
{
delete d->colorSpaceSelector;
delete d;
}
void KisAdvancedColorSpaceSelector::fillLstProfiles()
{
d->colorSpaceSelector->lstProfile->blockSignals(true);
const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem());
const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId);
d->colorSpaceSelector->lstProfile->clear();
QList<const KoColorProfile *> profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId);
QStringList profileNames;
Q_FOREACH (const KoColorProfile *profile, profileList) {
profileNames.append(profile->name());
}
std::sort(profileNames.begin(), profileNames.end());
QListWidgetItem *defaultProfile = new QListWidgetItem;
defaultProfile->setText(defaultProfileName + " " + i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)"));
Q_FOREACH (QString stringName, profileNames) {
if (stringName == defaultProfileName) {
d->colorSpaceSelector->lstProfile->addItem(defaultProfile);
} else {
d->colorSpaceSelector->lstProfile->addItem(stringName);
}
}
d->colorSpaceSelector->lstProfile->setCurrentItem(defaultProfile);
d->colorSpaceSelector->lstProfile->blockSignals(false);
colorSpaceChanged();
}
void KisAdvancedColorSpaceSelector::fillCmbDepths(const KoID& id)
{
KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem();
d->colorSpaceSelector->cmbColorDepth->clear();
QList<KoID> depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible);
QList<KoID> sortedDepths;
if (depths.contains(Integer8BitsColorDepthID)) {
sortedDepths << Integer8BitsColorDepthID;
}
if (depths.contains(Integer16BitsColorDepthID)) {
sortedDepths << Integer16BitsColorDepthID;
}
if (depths.contains(Float16BitsColorDepthID)) {
sortedDepths << Float16BitsColorDepthID;
}
if (depths.contains(Float32BitsColorDepthID)) {
sortedDepths << Float32BitsColorDepthID;
}
if (depths.contains(Float64BitsColorDepthID)) {
sortedDepths << Float64BitsColorDepthID;
}
d->colorSpaceSelector->cmbColorDepth->setIDList(sortedDepths);
if (sortedDepths.contains(activeDepth)) {
d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth);
}
}
void KisAdvancedColorSpaceSelector::fillDescription()
{
QString notApplicable = i18nc("Not Applicable, used where there's no colorants or gamma curve found","N/A");
QString notApplicableTooltip = i18nc("@info:tooltip","This profile has no colorants.");
QString profileName = i18nc("Shows up instead of the name when there's no profile","No Profile Found");
QString whatIsColorant = i18n("Colorant in d50-adapted xyY.");
//set colorants
const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem());
QList<const KoColorProfile *> profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId);
if (!profileList.isEmpty()) {
profileName = currentColorSpace()->profile()->name();
if (currentColorSpace()->profile()->hasColorants()){
QVector <double> colorants = currentColorSpace()->profile()->getColorantsxyY();
QVector <double> whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
//QString text = currentColorSpace()->profile()->info() + " =" +
d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint));
d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint[0], 'f', 4) + ", " + QString::number(whitepoint[1], 'f', 4) + ", " + QString::number(whitepoint[2], 'f', 4));
d->colorSpaceSelector->TongueWidget->setToolTip("<html><head/><body><table><tr><th colspan='4'>"+i18nc("@info:tooltip","This profile has the following xyY colorants:")+"</th></tr><tr><td>"+
i18n("Red:") +"</td><td>"+QString::number(colorants[0], 'f', 4) + "</td><td>" + QString::number(colorants[1], 'f', 4) + "</td><td>" + QString::number(colorants[2], 'f', 4)+"</td></tr><tr><td>"+
i18n("Green:")+"</td><td>"+QString::number(colorants[3], 'f', 4) + "</td><td>" + QString::number(colorants[4], 'f', 4) + "</td><td>" + QString::number(colorants[5], 'f', 4)+"</th></tr><tr><td>"+
i18n("Blue:") +"</td><td>"+QString::number(colorants[6], 'f', 4) + "</td><td>" + QString::number(colorants[7], 'f', 4) + "</td><td>" + QString::number(colorants[8], 'f', 4)+"</th></tr></table></body></html>");
} else {
QVector <double> whitepoint2 = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->lblXYZ_W->setText(nameWhitePoint(whitepoint2));
d->colorSpaceSelector->lblXYZ_W->setToolTip(QString::number(whitepoint2[0], 'f', 4) + ", " + QString::number(whitepoint2[1], 'f', 4) + ", " + QString::number(whitepoint2[2], 'f', 4));
d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip);
}
} else {
d->colorSpaceSelector->lblXYZ_W->setText(notApplicable);
d->colorSpaceSelector->lblXYZ_W->setToolTip(notApplicableTooltip);
d->colorSpaceSelector->TongueWidget->setToolTip(notApplicableTooltip);
}
//set TRC
QVector <double> estimatedTRC(3);
QString estimatedGamma = i18nc("Estimated Gamma indicates how the TRC (Tone Response Curve or Tone Reproduction Curve) is bent. A Gamma of 1.0 means linear.", "<b>Estimated Gamma</b>: ");
QString estimatedsRGB = i18nc("This is for special Gamma types that LCMS cannot differentiate between", "<b>Estimated Gamma</b>: sRGB, L* or rec709 TRC");
QString whatissRGB = i18nc("@info:tooltip","The Tone Response Curve of this color space is either sRGB, L* or rec709 TRC.");
QString currentModelStr = d->colorSpaceSelector->cmbColorModels->currentItem().id();
if (profileList.isEmpty()) {
d->colorSpaceSelector->TongueWidget->setProfileDataAvailable(false);
d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false);
}
else if (currentModelStr == "RGBA") {
QVector <qreal> colorants = currentColorSpace()->profile()->getColorantsxyY();
QVector <qreal> whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
if (currentColorSpace()->profile()->hasColorants()){
d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants);
} else {
colorants.fill(0.0);
d->colorSpaceSelector->TongueWidget->setRGBData(whitepoint, colorants);
}
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC();
QString estimatedCurve = " Estimated curve: ";
QPolygonF redcurve;
QPolygonF greencurve;
QPolygonF bluecurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector <qreal> linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
redcurve<<tonepoint;
tonepoint.setX(linear[1]);
greencurve<<tonepoint;
tonepoint.setX(linear[2]);
bluecurve<<tonepoint;
}
d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve);
} else {
QPolygonF curve = currentColorSpace()->estimatedTRCXYY();
redcurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4);
greencurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9);
bluecurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14);
d->colorSpaceSelector->TRCwidget->setRGBCurve(redcurve, greencurve, bluecurve);
}
if (estimatedTRC[0] == -1) {
d->colorSpaceSelector->TRCwidget->setToolTip("<html><head/><body>"+whatissRGB+"<br />"+estimatedCurve+"</body></html>");
} else {
d->colorSpaceSelector->TRCwidget->setToolTip("<html><head/><body>"+estimatedGamma + QString::number(estimatedTRC[0]) + "," + QString::number(estimatedTRC[1]) + "," + QString::number(estimatedTRC[2])+"<br />"+estimatedCurve+"</body></html>");
}
}
else if (currentModelStr == "GRAYA") {
QVector <qreal> whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->TongueWidget->setGrayData(whitepoint);
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC();
QString estimatedCurve = " Estimated curve: ";
QPolygonF tonecurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector <qreal> linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
tonecurve<<tonepoint;
}
} else {
d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false);
}
d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve);
if (estimatedTRC[0] == -1) {
d->colorSpaceSelector->TRCwidget->setToolTip("<html><head/><body>"+whatissRGB+"<br />"+estimatedCurve+"</body></html>");
} else {
d->colorSpaceSelector->TRCwidget->setToolTip("<html><head/><body>"+estimatedGamma + QString::number(estimatedTRC[0])+"<br />"+estimatedCurve+"</body></html>");
}
}
else if (currentModelStr == "CMYKA") {
QVector <qreal> whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->TongueWidget->setCMYKData(whitepoint);
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
QString estimatedCurve = " Estimated curve: ";
QPolygonF tonecurve;
QPolygonF cyancurve;
QPolygonF magentacurve;
QPolygonF yellowcurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector <qreal> linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
tonecurve<<tonepoint;
}
d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve);
} else {
QPolygonF curve = currentColorSpace()->estimatedTRCXYY();
cyancurve << curve.at(0) << curve.at(1) << curve.at(2) << curve.at(3) << curve.at(4);
magentacurve << curve.at(5) << curve.at(6) << curve.at(7) << curve.at(8) << curve.at(9);
yellowcurve << curve.at(10) << curve.at(11) << curve.at(12) << curve.at(13) << curve.at(14);
tonecurve << curve.at(15) << curve.at(16) << curve.at(17) << curve.at(18) << curve.at(19);
d->colorSpaceSelector->TRCwidget->setCMYKCurve(cyancurve, magentacurve, yellowcurve, tonecurve);
}
d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for CMYK."));
}
else if (currentModelStr == "XYZA") {
QString estimatedCurve = " Estimated curve: ";
estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC();
QPolygonF tonecurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector <qreal> linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
tonecurve<<tonepoint;
}
d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve);
} else {
d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false);
}
QVector <qreal> whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->TongueWidget->setXYZData(whitepoint);
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
d->colorSpaceSelector->TRCwidget->setToolTip("<html><head/><body>"+estimatedGamma + QString::number(estimatedTRC[0])+"< br />"+estimatedCurve+"</body></html>");
}
else if (currentModelStr == "LABA") {
estimatedTRC = currentColorSpace()->profile()->getEstimatedTRC();
QString estimatedCurve = " Estimated curve: ";
QPolygonF tonecurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector <qreal> linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
tonecurve<<tonepoint;
}
d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve);
} else {
d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false);
}
QVector <qreal> whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->TongueWidget->setLABData(whitepoint);
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
d->colorSpaceSelector->TRCwidget->setToolTip("<html><head/><body>"+i18nc("@info:tooltip","This is assumed to be the L * TRC. ")+"<br />"+estimatedCurve+"</body></html>");
}
else if (currentModelStr == "YCbCrA") {
QVector <qreal> whitepoint = currentColorSpace()->profile()->getWhitePointxyY();
d->colorSpaceSelector->TongueWidget->setYCbCrData(whitepoint);
QString estimatedCurve = " Estimated curve: ";
QPolygonF tonecurve;
if (currentColorSpace()->profile()->hasTRC()){
for (int i=0; i<=10; i++) {
QVector <qreal> linear(3);
linear.fill(i*0.1);
currentColorSpace()->profile()->linearizeFloatValue(linear);
estimatedCurve = estimatedCurve + ", " + QString::number(linear[0]);
QPointF tonepoint(linear[0],i*0.1);
tonecurve<<tonepoint;
}
d->colorSpaceSelector->TRCwidget->setGreyscaleCurve(tonecurve);
} else {
d->colorSpaceSelector->TRCwidget->setProfileDataAvailable(false);
}
d->colorSpaceSelector->TongueWidget->setGamut(currentColorSpace()->gamutXYY());
d->colorSpaceSelector->TRCwidget->setToolTip(i18nc("@info:tooltip","Estimated Gamma cannot be retrieved for YCrCb."));
}
d->colorSpaceSelector->textProfileDescription->clear();
if (profileList.isEmpty()==false) {
d->colorSpaceSelector->textProfileDescription->append("<h3>"+i18nc("About <Profilename>","About ") + currentColorSpace()->name() + "/" + profileName + "</h3>");
d->colorSpaceSelector->textProfileDescription->append("<p>"+ i18nc("ICC profile version","ICC Version: ") + QString::number(currentColorSpace()->profile()->version()) + "</p>");
//d->colorSpaceSelector->textProfileDescription->append("<p>"+ i18nc("Who made the profile?","Manufacturer: ") + currentColorSpace()->profile()->manufacturer() + "</p>"); //This would work if people actually wrote the manufacturer into the manufacturer fiedl...
d->colorSpaceSelector->textProfileDescription->append("<p>"+ i18nc("What is the copyright? These are from embedded strings from the icc profile, so they default to english.","Copyright: ") + currentColorSpace()->profile()->copyright() + "</p>");
} else {
d->colorSpaceSelector->textProfileDescription->append("<h3>" + profileName + "</h3>");
}
if (currentModelStr == "RGBA") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("If the selected model is RGB",
"<b><a href=\"https://en.wikipedia.org/wiki/RGB_color_space\">RGB (Red, Green, Blue)</a></b>, is the color model used by screens and other light-based media.<br/>"
"RGB is an additive color model: adding colors together makes them brighter. This color "
"model is the most extensive of all color models, and is recommended as a model for painting,"
"that you can later convert to other spaces. RGB is also the recommended colorspace for HDR editing.")+"</p>");
} else if (currentModelStr == "CMYKA") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("If the selected model is CMYK",
"<b><a href=\"https://en.wikipedia.org/wiki/CMYK_color_model\">CMYK (Cyan, Magenta, Yellow, Key)</a></b>, "
"is the model used by printers and other ink-based media.<br/>"
"CMYK is a subtractive model, meaning that adding colors together will turn them darker. Because of CMYK "
"profiles being very specific per printer, it is recommended to work in RGB space, and then later convert "
"to a CMYK profile, preferably one delivered by your printer. <br/>"
"CMYK is <b>not</b> recommended for painting."
"Unfortunately, Krita cannot retrieve colorants or the TRC for this space.")+"</p>");
} else if (currentModelStr == "XYZA") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("If the selected model is XYZ",
"<b><a href=\"https://en.wikipedia.org/wiki/CIE_1931_color_space\">CIE XYZ</a></b>"
"is the space determined by the CIE as the space that encompasses all other colors, and used to "
"convert colors between profiles. XYZ is an additive color model, meaning that adding colors together "
"makes them brighter. XYZ is <b>not</b> recommended for painting, but can be useful to encode in. The Tone Response "
"Curve is assumed to be linear.")+"</p>");
} else if (currentModelStr == "GRAYA") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("If the selected model is Grayscale",
"<b><a href=\"https://en.wikipedia.org/wiki/Grayscale\">Grayscale</a></b> only allows for "
"gray values and transparent values. Grayscale images use half "
"the memory and disk space compared to an RGB image of the same bit-depth.<br/>"
"Grayscale is useful for inking and greyscale images. In "
"Krita, you can mix Grayscale and RGB layers in the same image.")+"</p>");
} else if (currentModelStr == "LABA") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("If the selected model is LAB",
"<b><a href=\"https://en.wikipedia.org/wiki/Lab_color_space\">L*a*b</a></b>. <b>L<b> stands for Lightness, "
"the <b>a</b> and <b>b</b> components represent color channels.<br/>"
"L*a*b is a special model for color correction. It is based on human perception, meaning that it "
"tries to encode the difference in lightness, red-green balance and yellow-blue balance. "
"This makes it useful for color correction, but the vast majority of color maths in the blending "
"modes do <b>not</b> work as expected here.<br/>"
"Similarly, Krita does not support HDR in LAB, meaning that HDR images converted to LAB lose color "
"information. This colorspace is <b>not</b> recommended for painting, nor for export, "
"but best as a space to do post-processing in. The TRC is assumed to be the L* TRC.")+"</p>");
} else if (currentModelStr == "YCbCrA") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("If the selected model is YCbCr",
"<b><a href=\"https://en.wikipedia.org/wiki/YCbCr\">YCbCr (Luma, Blue Chroma, Red Chroma)</a></b>, is a "
"model designed for video encoding. It is based on human perception, meaning that it tries to "
"encode the difference in lightness, red-green balance and yellow-blue balance. Chroma in "
"this case is then a word indicating a special type of saturation, in these cases the saturation "
"of Red and Blue, of which the desaturated equivalents are Green and Yellow respectively. It "
"is available to open up certain images correctly, but Krita does not currently ship a profile for "
"this due to lack of open source ICC profiles for YCrCb.")+"</p>");
}
QString currentDepthStr = d->colorSpaceSelector->cmbColorDepth->currentItem().id();
if (currentDepthStr == "U8") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("When the selected Bitdepth is 8",
"<b>8 bit integer</b>: The default amount of colors per channel. Each channel will have 256 values available, "
"leading to a total amount of 256*amount of channels. Recommended to use for images intended for the web, "
"or otherwise simple images.")+"</p>");
}
else if (currentDepthStr == "U16") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("When the selected Bitdepth is 16",
"<b>16 bit integer</b>: Also known as 'deep color'. 16 bit is ideal for editing images with a linear TRC, large "
"color space, or just when you need more precise color blending. This does take twice as much space on "
"the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it "
"takes much more processing power. We recommend watching the RAM usage of the file carefully, or "
"otherwise use 8 bit if your computer slows down. Take care to disable conversion optimization "
"when converting from 16 bit/channel to 8 bit/channel.")+"</p>");
}
else if (currentDepthStr == "F16") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("When the selected Bitdepth is 16 bit float",
"<b>16 bit floating point</b>: Also known as 'Half Floating Point', and the standard in VFX industry images. "
"16 bit float is ideal for editing images with a linear Tone Response Curve, large color space, or just when you need "
"more precise color blending. It being floating point is an absolute requirement for Scene Referred "
"(HDR) images. This does take twice as much space on the RAM and hard-drive than any given 8 bit image "
"of the same properties, and for some devices it takes much more processing power. We recommend watching "
"the RAM usage of the file carefully, or otherwise use 8 bit if your computer slows down.")+"</p>");
}
else if (currentDepthStr == "F32") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("When the selected Bitdepth is 32bit float",
"<b>32 bit float point</b>: Also known as 'Full Floating Point'. 32 bit float is ideal for editing images "
"with a linear TRC, large color space, or just when you need more precise color blending. It being "
"floating point is an absolute requirement for Scene Referred (HDR) images. This does take four times "
"as much space on the RAM and hard-drive than any given 8 bit image of the same properties, and for "
"some devices it takes much more processing power. We recommend watching the RAM usage of the file "
"carefully, or otherwise use 8 bit if your computer slows down.")+"</p>");
}
else if (currentDepthStr == "F64") {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("When the selected Bitdepth is 64bit float, but this isn't actually available in Krita at the moment.",\
"<b>64 bit float point</b>: 64 bit float is as precise as it gets in current technology, and this depth is used "
"most of the time for images that are generated or used as an input for software. It being floating point "
"is an absolute requirement for Scene Referred (HDR) images. This does take eight times as much space on "
"the RAM and hard-drive than any given 8 bit image of the same properties, and for some devices it takes "
"much more processing power. We recommend watching the RAM usage of the file carefully, or otherwise use "
"8 bit if your computer slows down.")+"</p>");
}
if (profileList.isEmpty()==false) {
QString possibleConversionIntents = "<p>"+i18n("The following conversion intents are possible: ")+"<ul>";
if (currentColorSpace()->profile()->supportsPerceptual()){
possibleConversionIntents += "<li>"+i18n("Perceptual")+"</li>";
}
if (currentColorSpace()->profile()->supportsRelative()){
possibleConversionIntents += "<li>"+i18n("Relative Colorimetric")+"</li>";
}
if (currentColorSpace()->profile()->supportsAbsolute()){
possibleConversionIntents += "<li>"+i18n("Absolute Colorimetric")+"</li>";
}
if (currentColorSpace()->profile()->supportsSaturation()){
possibleConversionIntents += "<li>"+i18n("Saturation")+"</li>";
}
possibleConversionIntents += "</ul></ul></p>";
d->colorSpaceSelector->textProfileDescription->append(possibleConversionIntents);
}
if (profileName.contains("-elle-")) {
d->colorSpaceSelector->textProfileDescription->append("<p>"+i18nc("These are Elle Stone's notes on her profiles that we ship.",
"<p><b>Extra notes on profiles by Elle Stone:</b></p>"
"<p><i>Krita comes with a number of high quality profiles created by "
"<a href=\"http://ninedegreesbelow.com\">Elle Stone</a>. This is a summary. Please check "
"<a href=\"http://ninedegreesbelow.com/photography/lcms-make-icc-profiles.html\">the full documentation</a> as well.</i></p>"));
if (profileName.contains("ACES-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>Quoting Wikipedia, 'Academy Color Encoding System (ACES) is a color image "
"encoding system proposed by the Academy of Motion Picture Arts and Sciences that will allow for "
"a fully encompassing color accurate workflow, with 'seamless interchange of high quality motion "
"picture images regardless of source'.</p>"));
}
if (profileName.contains("ACEScg-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>The ACEScg color space is smaller than the ACES color space, but large enough to contain the 'Rec-2020 gamut "
"and the DCI-P3 gamut', unlike the ACES color space it has no negative values and contains only few colors "
"that fall just barely outside the area of real colors humans can see</p>"));
}
if (profileName.contains("ClayRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>To avoid possible copyright infringement issues, I used 'ClayRGB' (following ArgyllCMS) as the base name "
"for these profiles. As used below, 'Compatible with Adobe RGB 1998' is terminology suggested in the preamble "
"to the AdobeRGB 1998 color space specifications.</p><p>"
"The Adobe RGB 1998 color gamut covers a higher "
"percentage of real-world cyans, greens, and yellow-greens than sRGB, but still doesn't include all printable "
"cyans, greens, yellow-greens, especially when printing using today's high-end, wider gamut, ink jet printers. "
"BetaRGB (not included in the profile pack) and Rec.2020 are better matches for the color gamuts of today's "
"wide gamut printers.</p><p>"
"The Adobe RGB 1998 color gamut is a reasonable approximation to some of today's "
"high-end wide gamut monitors.</p>"));
}
if (profileName.contains("AllColorsRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>This profile's color gamut is roughly the same size and shape as the ACES color space gamut, "
"and like the ACES color space, AllColorsRGB holds all possible real colors. But AllColorsRGB "
"actually has a slightly larger color gamut (to capture some fringe colors that barely qualify "
"as real when viewed by the standard observer) and uses the D50 white point.</p><p>"
"Just like the ACES color space, AllColorsRGB holds a high percentage of imaginary colors. See the Completely "
"<a href=\"http://ninedegreesbelow.com/photography/xyz-rgb.html\">"
"Painless Programmer's Guide to XYZ, RGB, ICC, xyY, and TRCs</a> for more information about imaginary "
"colors.</p><p>"
"There is no particular reason why anyone would want to use this profile "
"for editing, unless one needs to make sure your color space really does hold all "
"possible real colors.</p>"));
}
if (profileName.contains("CIERGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>This profile is included mostly for its historical significance. "
"It's the color space that was used in the original color matching experiments "
"that led to the creation of the XYZ reference color space.</p><p>"
"The ASTM E white point "
"is probably the right E white point to use when making the CIERGB color space profile. "
"It's not clear to me what the correct CIERGB primaries really are. "
"Lindbloom gives one set. The LCMS version 1 tutorial gives a different set. "
"Experts in the field contend that the real primaries "
"should be calculated from the spectral wavelengths, so I did.</p>"));
}
if (profileName.contains("IdentityRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>The IdentityRGB working space is included in the profile pack because it's a mathematically "
"obvious way to include all possible visible colors, though it has a higher percentage of "
"imaginary colors than the ACES and AllColorsRGB color spaces. I cannot think of any reason "
"why you'd ever want to actually edit images in the IdentityRGB working space.</p>"));
}
if (profileName.contains("LargeRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>To avoid possible copyright infringement issues, I used 'LargeRGB' (following RawTherapee) "
"as the base name for these profiles.<p>"
"Kodak designed the RIMM/ROMM (ProPhotoRGB) color "
"gamut to include all printable and most real world colors. It includes some imaginary colors "
"and excludes some of the real world blues and violet blues that can be captured by digital "
"cameras. It also excludes some very saturated 'camera-captured' yellows as interpreted by "
"some (and probably many) camera matrix input profiles.<p>"
"The ProPhotoRGB primaries are "
"hard-coded into Adobe products such as Lightroom and the Dng-DCP camera 'profiles'. However, "
"other than being large enough to hold a lot of colors, ProPhotoRGB has no particular merit "
"as an RGB working space. Personally and for most editing purposes, I recommend BetaRGB, Rec2020, "
"or the ACEScg profiles ProPhotoRGB.</p>"));
}
if (profileName.contains("Rec2020-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>Rec.2020 is the up-and-coming replacement for the thoroughly outdated sRGB color space. As of "
"June 2015, very few (if any) display devices (and certainly no affordable display devices) can "
"display all of Rec.2020. However, display technology is closing in on Rec.2020, movies are "
"already being made for Rec.2020, and various cameras offer support for Rec.2020. And in the "
"digital darkroom Rec.2020 is much more suitable as a general RGB working space than the "
"exceedingly small sRGB color space.</p>"));
}
if (profileName.contains("sRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>Hewlett-Packard and Microsoft designed sRGB to match the color gamut of consumer-grade CRTs "
"from the 1990s. sRGB is the standard color space for the world wide web and is still the best "
"choice for exporting images to the internet.</p><p>"
"The sRGB color gamut was a good match to "
"calibrated decent quality CRTs. But sRGB is not a good match to many consumer-grade LCD monitors, "
"which often cannot display the more saturated sRGB blues and magentas (the good news: as technology "
"progresses, wider gamuts are trickling down to consumer grade monitors).</p><p>"
"Printer color gamuts can easily exceed the sRGB color gamut in cyans, greens, and yellow-greens. Colors from interpolated "
"camera raw files also often exceed the sRGB color gamut.</p><p>"
"As a very relevant aside, using perceptual "
"intent when converting to sRGB does not magically makes otherwise out of gamut colors fit inside the "
"sRGB color gamut! The standard sRGB color space (along with all the other the RGB profiles provided "
"in my profile pack) is a matrix profile, and matrix profiles don't have perceptual intent tables.</p>"));
}
if (profileName.contains("WideRGB-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>To avoid possible copyright infringement issues, I used 'WideRGB' as the base name for these profiles.</p><p>"
"WideGamutRGB was designed by Adobe to be a wide gamut color space that uses spectral colors "
"as its primaries. Pascale's primary values produce a profile that matches old V2 Widegamut profiles "
"from Adobe and Canon. It is an interesting color space, but shortly after its introduction, Adobe "
"switched their emphasis to the ProPhotoRGB color space.</p>"));
}
if (profileName.contains("Gray-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>These profiles are for use with RGB images that have been converted to monotone gray (black and white). "
"The main reason to convert from RGB to Gray is to save the file space needed to encode the image. "
"Google places a premium on fast-loading web pages, and images are one of the slower-loading elements "
"of a web page. So converting black and white images to Grayscale images does save some kilobytes. "
" For grayscale images uploaded to the internet, convert the image to the V2 Gray profile with the sRGB TRC.</p>"));
}
if (profileName.contains("-g10")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>The profiles that end in '-g10.icc' are linear gamma (gamma=1.0, 'linear light', etc) profiles and "
"should only be used when editing at high bit depths (16-bit floating point, 16-bit integer, 32-bit "
"floating point, 32-bit integer). Many editing operations produce better results in linear gamma color "
"spaces.</p>"));
}
if (profileName.contains("-labl")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>The profiles that end in '-labl.icc' have perceptually uniform TRCs. A few editing operations really "
"should be done on perceptually uniform RGB. Make sure you use the V4 versions for editing high bit depth "
"images.</p>"));
}
if (profileName.contains("-srgbtrc") || profileName.contains("-g22") || profileName.contains("-g18") || profileName.contains("-bt709")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>The profiles that end in '-srgbtrc.icc', '-g22.icc', and '-bt709.icc' have approximately but not exactly "
"perceptually uniform TRCs. ProPhotoRGB's gamma=1.8 TRC is not quite as close to being perceptually uniform.</p>"));
}
if (d->colorSpaceSelector->cmbColorDepth->currentItem().id()=="U8") {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>When editing 8-bit images, you should use a profile with a small color gamut and an approximately or "
"exactly perceptually uniform TRC. Of the profiles supplied in my profile pack, only the sRGB and AdobeRGB1998 "
"(ClayRGB) color spaces are small enough for 8-bit editing. Even with the AdobeRGB1998 color space you need to "
"be careful to not cause posterization. And of course you cannot use the linear gamma versions of these profiles "
"for 8-bit editing.</p>"));
}
if (profileName.contains("-V4-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>Use V4 profiles for editing images using high bit depth image editors that use LCMS as the Color Management Module. "
"This includes Krita, digiKam/showFoto, and GIMP 2.9.</p>"));
}
if (profileName.contains("-V2-")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>Use V2 profiles for exporting finished images to be uploaded to the web or for use with imaging software that "
"cannot read V4 profiles.</p>"));
}
}
d->colorSpaceSelector->textProfileDescription->moveCursor(QTextCursor::Start);
}
QString KisAdvancedColorSpaceSelector::nameWhitePoint(QVector <double> whitePoint) {
QString name=(QString::number(whitePoint[0]) + ", " + QString::number(whitePoint[1], 'f', 4));
//A (0.451170, 0.40594) (2856K)(tungsten)
if ((whitePoint[0]>0.451170-0.005 && whitePoint[0]<0.451170 + 0.005) &&
(whitePoint[1]>0.40594-0.005 && whitePoint[1]<0.40594 + 0.005)){
name="A";
return name;
}
//B (0.34980, 0.35270) (4874K) (Direct Sunlight at noon)(obsolete)
//C (0.31039, 0.31905) (6774K) (avarage/north sky daylight)(obsolete)
//D50 (0.34773, 0.35952) (5003K) (Horizon Light, default color of white paper, ICC profile standard illuminant)
if ((whitePoint[0]>0.34773-0.005 && whitePoint[0]<0.34773 + 0.005) &&
(whitePoint[1]>0.35952-0.005 && whitePoint[1]<0.35952 + 0.005)){
name="D50";
return name;
}
//D55 (0.33411, 0.34877) (5503K) (Mid-morning / Mid-afternoon Daylight)
if ((whitePoint[0]>0.33411-0.001 && whitePoint[0]<0.33411 + 0.001) &&
(whitePoint[1]>0.34877-0.005 && whitePoint[1]<0.34877 + 0.005)){
name="D55";
return name;
}
//D60 (0.3217, 0.3378) (~6000K) (ACES colorspace default)
if ((whitePoint[0]>0.3217-0.001 && whitePoint[0]<0.3217 + 0.001) &&
(whitePoint[1]>0.3378-0.005 && whitePoint[1]<0.3378 + 0.005)){
name="D60";
return name;
}
//D65 (0.31382, 0.33100) (6504K) (Noon Daylight, default for computer and tv screens, sRGB default)
//Elle's are old school with 0.3127 and 0.3289
if ((whitePoint[0]>0.31382-0.002 && whitePoint[0]<0.31382 + 0.002) &&
(whitePoint[1]>0.33100-0.005 && whitePoint[1]<0.33100 + 0.002)){
name="D65";
return name;
}
//D75 (0.29968, 0.31740) (7504K) (North sky Daylight)
if ((whitePoint[0]>0.29968-0.001 && whitePoint[0]<0.29968 + 0.001) &&
(whitePoint[1]>0.31740-0.005 && whitePoint[1]<0.31740 + 0.005)){
name="D75";
return name;
}
//E (1/3, 1/3) (5454K) (Equal Energy. CIERGB default)
if ((whitePoint[0]>(1.0/3.0)-0.001 && whitePoint[0]<(1.0/3.0) + 0.001) &&
(whitePoint[1]>(1.0/3.0)-0.001 && whitePoint[1]<(1.0/3.0) + 0.001)){
name="E";
return name;
}
//The F series seems to sorta overlap with the D series, so I'll just leave them in comment here.//
//F1 (0.31811, 0.33559) (6430K) (Daylight Fluorescent)
//F2 (0.37925, 0.36733) (4230K) (Cool White Fluorescent)
//F3 (0.41761, 0.38324) (3450K) (White Florescent)
//F4 (0.44920, 0.39074) (2940K) (Warm White Fluorescent)
//F5 (0.31975, 0.34246) (6350K) (Daylight Fluorescent)
//F6 (0.38660, 0.37847) (4150K) (Lite White Fluorescent)
//F7 (0.31569, 0.32960) (6500K) (D65 simulator, Daylight simulator)
//F8 (0.34902, 0.35939) (5000K) (D50 simulator)
//F9 (0.37829, 0.37045) (4150K) (Cool White Deluxe Fluorescent)
//F10 (0.35090, 0.35444) (5000K) (Philips TL85, Ultralume 50)
//F11 (0.38541, 0.37123) (4000K) (Philips TL84, Ultralume 40)
//F12 (0.44256, 0.39717) (3000K) (Philips TL83, Ultralume 30)
return name;
}
const KoColorSpace* KisAdvancedColorSpaceSelector::currentColorSpace()
{
QString check = "";
if (d->colorSpaceSelector->lstProfile->currentItem()) {
check = d->colorSpaceSelector->lstProfile->currentItem()->text();
} else if (d->colorSpaceSelector->lstProfile->item(0)) {
check = d->colorSpaceSelector->lstProfile->item(0)->text();
}
return KoColorSpaceRegistry::instance()->colorSpace(d->colorSpaceSelector->cmbColorModels->currentItem().id(),
d->colorSpaceSelector->cmbColorDepth->currentItem().id(),
check);
}
void KisAdvancedColorSpaceSelector::setCurrentColorModel(const KoID& id)
{
d->colorSpaceSelector->cmbColorModels->setCurrent(id);
fillLstProfiles();
fillCmbDepths(id);
}
void KisAdvancedColorSpaceSelector::setCurrentColorDepth(const KoID& id)
{
d->colorSpaceSelector->cmbColorDepth->setCurrent(id);
fillLstProfiles();
}
void KisAdvancedColorSpaceSelector::setCurrentProfile(const QString& name)
{
QList<QListWidgetItem *> Items= d->colorSpaceSelector->lstProfile->findItems(name, Qt::MatchStartsWith);
d->colorSpaceSelector->lstProfile->setCurrentItem(Items.at(0));
}
void KisAdvancedColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace)
{
if (!colorSpace) {
return;
}
setCurrentColorModel(colorSpace->colorModelId());
setCurrentColorDepth(colorSpace->colorDepthId());
setCurrentProfile(colorSpace->profile()->name());
}
void KisAdvancedColorSpaceSelector::colorSpaceChanged()
{
bool valid = d->colorSpaceSelector->lstProfile->count() != 0;
emit(selectionChanged(valid));
if (valid) {
emit colorSpaceChanged(currentColorSpace());
}
}
void KisAdvancedColorSpaceSelector::installProfile()
{
KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC");
dialog.setCaption(i18n("Install Color Profiles"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile");
QStringList profileNames = dialog.filenames();
KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
Q_ASSERT(iccEngine);
QString saveLocation = KoResourcePaths::saveLocation("icc_profiles");
Q_FOREACH (const QString &profileName, profileNames) {
QUrl file(profileName);
if (!QFile::copy(profileName, saveLocation + file.fileName())) {
dbgKrita << "Could not install profile!";
return;
}
iccEngine->addProfile(saveLocation + file.fileName());
}
fillLstProfiles();
}
diff --git a/libs/ui/widgets/kis_color_space_selector.cc b/libs/ui/widgets/kis_color_space_selector.cc
index 2f508b769b..8aaf38c79c 100644
--- a/libs/ui/widgets/kis_color_space_selector.cc
+++ b/libs/ui/widgets/kis_color_space_selector.cc
@@ -1,274 +1,274 @@
/*
* Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
* Copyright (C) 2011 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_color_space_selector.h"
#include "kis_advanced_color_space_selector.h"
#include <QUrl>
#include <KoFileDialog.h>
#include <KoColorProfile.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpaceEngine.h>
#include <KoID.h>
#include <KoConfig.h>
#include <kis_icon.h>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <KoResourcePaths.h>
#include <kis_debug.h>
#include "ui_wdgcolorspaceselector.h"
struct KisColorSpaceSelector::Private {
Ui_WdgColorSpaceSelector* colorSpaceSelector;
QString knsrcFile;
bool profileValid;
QString defaultsuffix;
};
KisColorSpaceSelector::KisColorSpaceSelector(QWidget* parent) : QWidget(parent), m_advancedSelector(0), d(new Private)
{
setObjectName("KisColorSpaceSelector");
d->colorSpaceSelector = new Ui_WdgColorSpaceSelector;
d->colorSpaceSelector->setupUi(this);
d->colorSpaceSelector->cmbColorModels->setIDList(KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::OnlyUserVisible));
fillCmbDepths(d->colorSpaceSelector->cmbColorModels->currentItem());
d->colorSpaceSelector->bnInstallProfile->setIcon(KisIconUtils::loadIcon("document-open"));
d->colorSpaceSelector->bnInstallProfile->setToolTip( i18n("Open Color Profile") );
connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)),
this, SLOT(fillCmbDepths(const KoID &)));
connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(const KoID &)),
this, SLOT(fillCmbProfiles()));
connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(const KoID &)),
this, SLOT(fillCmbProfiles()));
connect(d->colorSpaceSelector->cmbProfile, SIGNAL(activated(const QString &)),
this, SLOT(colorSpaceChanged()));
connect(d->colorSpaceSelector->bnInstallProfile, SIGNAL(clicked()),
this, SLOT(installProfile()));
d->defaultsuffix = " "+i18nc("This is appended to the color profile which is the default for the given colorspace and bit-depth","(Default)");
connect(d->colorSpaceSelector->bnAdvanced, SIGNAL(clicked()), this, SLOT(slotOpenAdvancedSelector()));
fillCmbProfiles();
}
KisColorSpaceSelector::~KisColorSpaceSelector()
{
delete d->colorSpaceSelector;
delete d;
}
void KisColorSpaceSelector::fillCmbProfiles()
{
const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(d->colorSpaceSelector->cmbColorModels->currentItem(), d->colorSpaceSelector->cmbColorDepth->currentItem());
const QString defaultProfileName = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId);
d->colorSpaceSelector->cmbProfile->clear();
QList<const KoColorProfile *> profileList = KoColorSpaceRegistry::instance()->profilesFor(colorSpaceId);
QStringList profileNames;
Q_FOREACH (const KoColorProfile *profile, profileList) {
profileNames.append(profile->name());
}
std::sort(profileNames.begin(), profileNames.end());
Q_FOREACH (QString stringName, profileNames) {
if (stringName == defaultProfileName) {
d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName + d->defaultsuffix);
} else {
d->colorSpaceSelector->cmbProfile->addSqueezedItem(stringName);
}
}
d->colorSpaceSelector->cmbProfile->setCurrent(defaultProfileName + d->defaultsuffix);
colorSpaceChanged();
}
void KisColorSpaceSelector::fillCmbDepths(const KoID& id)
{
KoID activeDepth = d->colorSpaceSelector->cmbColorDepth->currentItem();
d->colorSpaceSelector->cmbColorDepth->clear();
QList<KoID> depths = KoColorSpaceRegistry::instance()->colorDepthList(id, KoColorSpaceRegistry::OnlyUserVisible);
// order the depth by name
std::sort(depths.begin(), depths.end(), sortBitDepthsComparer);
d->colorSpaceSelector->cmbColorDepth->setIDList(depths);
if (depths.contains(activeDepth)) {
d->colorSpaceSelector->cmbColorDepth->setCurrent(activeDepth);
}
}
bool KisColorSpaceSelector::sortBitDepthsComparer(KoID depthOne, KoID depthTwo) {
// to order these right, we need to first order by bit depth, then by if it is floating or not
QString bitDepthOne = depthOne.name().split(" ")[0];
QString bitDepthTwo = depthTwo.name().split(" ")[0];
if (bitDepthOne.toInt() > bitDepthTwo.toInt()) {
return false;
}
if (bitDepthOne.toInt() == bitDepthTwo.toInt()) {
// bit depth number is the same, so now we need to compare if it is a floating type or not
// the second value [1], just says 'bits', so that is why we look for [2] which has the float word
QString bitDepthOneType = "";
QString bitDepthTwoType = "";
if (depthOne.name().split(" ").length() > 2) {
bitDepthOneType = depthOne.name().split(" ")[2];
}
if (depthTwo.name().split(" ").length() > 2) {
bitDepthTwoType = depthTwo.name().split(" ")[2];
}
if (bitDepthOneType.length() > bitDepthTwoType.length()) {
return false;
}
}
return true;
}
const KoColorSpace* KisColorSpaceSelector::currentColorSpace()
{
QString profilenamestring = d->colorSpaceSelector->cmbProfile->itemHighlighted();
if (profilenamestring.contains(d->defaultsuffix)) {
profilenamestring.remove(d->defaultsuffix);
return KoColorSpaceRegistry::instance()->colorSpace(
d->colorSpaceSelector->cmbColorModels->currentItem().id(),
d->colorSpaceSelector->cmbColorDepth->currentItem().id(),
profilenamestring);
} else {
return KoColorSpaceRegistry::instance()->colorSpace(
d->colorSpaceSelector->cmbColorModels->currentItem().id(),
d->colorSpaceSelector->cmbColorDepth->currentItem().id(),
profilenamestring);
}
}
void KisColorSpaceSelector::setCurrentColorModel(const KoID& id)
{
d->colorSpaceSelector->cmbColorModels->setCurrent(id);
fillCmbDepths(id);
}
void KisColorSpaceSelector::setCurrentColorDepth(const KoID& id)
{
d->colorSpaceSelector->cmbColorDepth->setCurrent(id);
fillCmbProfiles();
}
void KisColorSpaceSelector::setCurrentProfile(const QString& name)
{
d->colorSpaceSelector->cmbProfile->setCurrent(name);
}
void KisColorSpaceSelector::setCurrentColorSpace(const KoColorSpace* colorSpace)
{
if (!colorSpace) {
return;
}
setCurrentColorModel(colorSpace->colorModelId());
setCurrentColorDepth(colorSpace->colorDepthId());
setCurrentProfile(colorSpace->profile()->name());
}
void KisColorSpaceSelector::showColorBrowserButton(bool showButton) {
d->colorSpaceSelector->bnAdvanced->setVisible(showButton);
}
void KisColorSpaceSelector::colorSpaceChanged()
{
bool valid = d->colorSpaceSelector->cmbProfile->count() != 0;
d->profileValid = valid;
emit(selectionChanged(valid));
if(valid) {
emit colorSpaceChanged(currentColorSpace());
QString text = currentColorSpace()->profile()->name();
}
}
void KisColorSpaceSelector::installProfile()
{
QStringList mime;
KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC");
dialog.setCaption(i18n("Install Color Profiles"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile");
QStringList profileNames = dialog.filenames();
KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
Q_ASSERT(iccEngine);
QString saveLocation = KoResourcePaths::saveLocation("icc_profiles");
Q_FOREACH (const QString &profileName, profileNames) {
QUrl file(profileName);
if (!QFile::copy(profileName, saveLocation + file.fileName())) {
dbgKrita << "Could not install profile!";
return;
}
iccEngine->addProfile(saveLocation + file.fileName());
}
fillCmbProfiles();
}
void KisColorSpaceSelector::slotOpenAdvancedSelector()
{
if (!m_advancedSelector) {
m_advancedSelector = new KisAdvancedColorSpaceSelector(this, "Select a Colorspace");
m_advancedSelector->setModal(true);
if (currentColorSpace()) {
m_advancedSelector->setCurrentColorSpace(currentColorSpace());
}
connect(m_advancedSelector, SIGNAL(selectionChanged(bool)), this, SLOT(slotProfileValid(bool)) );
}
QDialog::DialogCode result = (QDialog::DialogCode)m_advancedSelector->exec();
if (result) {
if (d->profileValid==true) {
setCurrentColorSpace(m_advancedSelector->currentColorSpace());
}
}
}
void KisColorSpaceSelector::slotProfileValid(bool valid)
{
d->profileValid = valid;
}
diff --git a/libs/ui/widgets/kis_curve_widget.cpp b/libs/ui/widgets/kis_curve_widget.cpp
index efff154e95..036bc959c8 100644
--- a/libs/ui/widgets/kis_curve_widget.cpp
+++ b/libs/ui/widgets/kis_curve_widget.cpp
@@ -1,502 +1,502 @@
/*
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// C++ includes.
#include <cmath>
#include <cstdlib>
// Qt includes.
#include <QPixmap>
#include <QPainter>
#include <QPoint>
#include <QPen>
#include <QEvent>
#include <QRect>
#include <QFont>
#include <QFontMetrics>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QPaintEvent>
#include <QList>
#include <QApplication>
#include <QSpinBox>
// KDE includes.
#include <kis_debug.h>
#include <kis_config.h>
#include <klocalizedstring.h>
// Local includes.
#include "widgets/kis_curve_widget.h"
#define bounds(x,a,b) (x<a ? a : (x>b ? b :x))
#define MOUSE_AWAY_THRES 15
#define POINT_AREA 1E-4
#define CURVE_AREA 1E-4
#include "kis_curve_widget_p.h"
-KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WFlags f)
+KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WindowFlags f)
: QWidget(parent, f), d(new KisCurveWidget::Private(this))
{
setObjectName("KisCurveWidget");
d->m_grab_point_index = -1;
d->m_readOnlyMode = false;
d->m_guideVisible = false;
d->m_pixmapDirty = true;
d->m_pixmapCache = 0;
d->setState(ST_NORMAL);
d->m_intIn = 0;
d->m_intOut = 0;
setMouseTracking(true);
setAutoFillBackground(false);
setAttribute(Qt::WA_OpaquePaintEvent);
setMinimumSize(150, 50);
setMaximumSize(250, 250);
d->setCurveModified();
setFocusPolicy(Qt::StrongFocus);
}
KisCurveWidget::~KisCurveWidget()
{
delete d->m_pixmapCache;
delete d;
}
void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int min, int max)
{
d->m_intIn = in;
d->m_intOut = out;
if (!d->m_intIn || !d->m_intOut)
return;
d->m_inOutMin = min;
d->m_inOutMax = max;
d->m_intIn->setRange(d->m_inOutMin, d->m_inOutMax);
d->m_intOut->setRange(d->m_inOutMin, d->m_inOutMax);
connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
d->syncIOControls();
}
void KisCurveWidget::dropInOutControls()
{
if (!d->m_intIn || !d->m_intOut)
return;
disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int)));
d->m_intIn = d->m_intOut = 0;
}
void KisCurveWidget::inOutChanged(int)
{
QPointF pt;
Q_ASSERT(d->m_grab_point_index >= 0);
pt.setX(d->io2sp(d->m_intIn->value()));
pt.setY(d->io2sp(d->m_intOut->value()));
if (d->jumpOverExistingPoints(pt, d->m_grab_point_index)) {
d->m_curve.setPoint(d->m_grab_point_index, pt);
d->m_grab_point_index = d->m_curve.points().indexOf(pt);
emit pointSelectedChanged();
} else
pt = d->m_curve.points()[d->m_grab_point_index];
d->m_intIn->blockSignals(true);
d->m_intOut->blockSignals(true);
d->m_intIn->setValue(d->sp2io(pt.x()));
d->m_intOut->setValue(d->sp2io(pt.y()));
d->m_intIn->blockSignals(false);
d->m_intOut->blockSignals(false);
d->setCurveModified();
}
void KisCurveWidget::reset(void)
{
d->m_grab_point_index = -1;
emit pointSelectedChanged();
d->m_guideVisible = false;
d->setCurveModified();
}
void KisCurveWidget::setCurveGuide(const QColor & color)
{
d->m_guideVisible = true;
d->m_colorGuide = color;
}
void KisCurveWidget::setPixmap(const QPixmap & pix)
{
d->m_pix = pix;
d->m_pixmapDirty = true;
d->setCurveRepaint();
}
QPixmap KisCurveWidget::getPixmap()
{
return d->m_pix;
}
void KisCurveWidget::setBasePixmap(const QPixmap &pix)
{
d->m_pixmapBase = pix;
}
QPixmap KisCurveWidget::getBasePixmap()
{
return d->m_pixmapBase;
}
bool KisCurveWidget::pointSelected() const
{
return d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1;
}
void KisCurveWidget::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) {
if (d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1) {
//x() find closest point to get focus afterwards
double grab_point_x = d->m_curve.points()[d->m_grab_point_index].x();
int left_of_grab_point_index = d->m_grab_point_index - 1;
int right_of_grab_point_index = d->m_grab_point_index + 1;
int new_grab_point_index;
if (fabs(d->m_curve.points()[left_of_grab_point_index].x() - grab_point_x) <
fabs(d->m_curve.points()[right_of_grab_point_index].x() - grab_point_x)) {
new_grab_point_index = left_of_grab_point_index;
} else {
new_grab_point_index = d->m_grab_point_index;
}
d->m_curve.removePoint(d->m_grab_point_index);
d->m_grab_point_index = new_grab_point_index;
emit pointSelectedChanged();
setCursor(Qt::ArrowCursor);
d->setState(ST_NORMAL);
}
e->accept();
d->setCurveModified();
} else if (e->key() == Qt::Key_Escape && d->state() != ST_NORMAL) {
d->m_curve.setPoint(d->m_grab_point_index, QPointF(d->m_grabOriginalX, d->m_grabOriginalY) );
setCursor(Qt::ArrowCursor);
d->setState(ST_NORMAL);
e->accept();
d->setCurveModified();
} else if ((e->key() == Qt::Key_A || e->key() == Qt::Key_Insert) && d->state() == ST_NORMAL) {
/* FIXME: Lets user choose the hotkeys */
addPointInTheMiddle();
e->accept();
} else
QWidget::keyPressEvent(e);
}
void KisCurveWidget::addPointInTheMiddle()
{
QPointF pt(0.5, d->m_curve.value(0.5));
if (!d->jumpOverExistingPoints(pt, -1))
return;
d->m_grab_point_index = d->m_curve.addPoint(pt);
emit pointSelectedChanged();
if (d->m_intIn)
d->m_intIn->setFocus(Qt::TabFocusReason);
d->setCurveModified();
}
void KisCurveWidget::resizeEvent(QResizeEvent *e)
{
d->m_pixmapDirty = true;
QWidget::resizeEvent(e);
}
void KisCurveWidget::paintEvent(QPaintEvent *)
{
int wWidth = width() - 1;
int wHeight = height() - 1;
QPainter p(this);
QPalette appPalette = QApplication::palette();
// Antialiasing is not a good idea here, because
// the grid will drift one pixel to any side due to rounding of int
// FIXME: let's user tell the last word (in config)
//p.setRenderHint(QPainter::Antialiasing);
// fill with color to show widget bounds
p.fillRect(rect(), appPalette.color(QPalette::Base));
// draw background
if (!d->m_pix.isNull()) {
if (d->m_pixmapDirty || !d->m_pixmapCache) {
delete d->m_pixmapCache;
d->m_pixmapCache = new QPixmap(width(), height());
QPainter cachePainter(d->m_pixmapCache);
cachePainter.scale(1.0*width() / d->m_pix.width(), 1.0*height() / d->m_pix.height());
cachePainter.drawPixmap(0, 0, d->m_pix);
d->m_pixmapDirty = false;
}
p.drawPixmap(0, 0, *d->m_pixmapCache);
}
d->drawGrid(p, wWidth, wHeight);
KisConfig cfg;
if (cfg.antialiasCurves())
p.setRenderHint(QPainter::Antialiasing);
// Draw curve.
double curY;
double normalizedX;
int x;
QPolygonF poly;
p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine));
for (x = 0 ; x < wWidth ; x++) {
normalizedX = double(x) / wWidth;
curY = wHeight - d->m_curve.value(normalizedX) * wHeight;
/**
* Keep in mind that QLineF rounds doubles
* to ints mathematically, not just rounds down
* like in C
*/
poly.append(QPointF(x, curY));
}
poly.append(QPointF(x, wHeight - d->m_curve.value(1.0) * wHeight));
p.drawPolyline(poly);
QPainterPath fillCurvePath;
QPolygonF fillPoly = poly;
fillPoly.append(QPoint(rect().width(), rect().height()));
fillPoly.append(QPointF(0,rect().height()));
// add a couple points to the edges so it fills in below always
QColor fillColor = appPalette.color(QPalette::Text);
fillColor.setAlphaF(0.2);
fillCurvePath.addPolygon(fillPoly);
p.fillPath(fillCurvePath, fillColor);
// Drawing curve handles.
double curveX;
double curveY;
if (!d->m_readOnlyMode) {
for (int i = 0; i < d->m_curve.points().count(); ++i) {
curveX = d->m_curve.points().at(i).x();
curveY = d->m_curve.points().at(i).y();
if (i == d->m_grab_point_index) {
p.setPen(QPen(appPalette.color(QPalette::Text), 6, Qt::SolidLine));
p.drawEllipse(QRectF(curveX * wWidth - 2,
wHeight - 2 - curveY * wHeight, 4, 4));
} else {
p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine));
p.drawEllipse(QRectF(curveX * wWidth - 3,
wHeight - 3 - curveY * wHeight, 6, 6));
}
}
}
// add border around widget to help contain everything
QPainterPath widgetBoundsPath;
widgetBoundsPath.addRect(rect());
p.strokePath(widgetBoundsPath, appPalette.color(QPalette::Text));
}
void KisCurveWidget::mousePressEvent(QMouseEvent * e)
{
if (d->m_readOnlyMode) return;
if (e->button() != Qt::LeftButton)
return;
double x = e->pos().x() / (double)(width() - 1);
double y = 1.0 - e->pos().y() / (double)(height() - 1);
int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height());
if (closest_point_index < 0) {
QPointF newPoint(x, y);
if (!d->jumpOverExistingPoints(newPoint, -1))
return;
d->m_grab_point_index = d->m_curve.addPoint(newPoint);
emit pointSelectedChanged();
} else {
d->m_grab_point_index = closest_point_index;
emit pointSelectedChanged();
}
d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x();
d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y();
d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x;
d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y;
d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY));
d->m_draggedAwayPointIndex = -1;
d->setState(ST_DRAG);
d->setCurveModified();
}
void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e)
{
if (d->m_readOnlyMode) return;
if (e->button() != Qt::LeftButton)
return;
setCursor(Qt::ArrowCursor);
d->setState(ST_NORMAL);
d->setCurveModified();
}
void KisCurveWidget::mouseMoveEvent(QMouseEvent * e)
{
if (d->m_readOnlyMode) return;
double x = e->pos().x() / (double)(width() - 1);
double y = 1.0 - e->pos().y() / (double)(height() - 1);
if (d->state() == ST_NORMAL) { // If no point is selected set the cursor shape if on top
int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height());
if (nearestPointIndex < 0)
setCursor(Qt::ArrowCursor);
else
setCursor(Qt::CrossCursor);
} else { // Else, drag the selected point
bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES ||
e->pos().x() < -MOUSE_AWAY_THRES;
bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES ||
e->pos().y() < -MOUSE_AWAY_THRES;
bool removePoint = (crossedHoriz || crossedVert);
if (!removePoint && d->m_draggedAwayPointIndex >= 0) {
// point is no longer dragged away so reinsert it
QPointF newPoint(d->m_draggedAwayPoint);
d->m_grab_point_index = d->m_curve.addPoint(newPoint);
d->m_draggedAwayPointIndex = -1;
}
if (removePoint &&
(d->m_draggedAwayPointIndex >= 0))
return;
setCursor(Qt::CrossCursor);
x += d->m_grabOffsetX;
y += d->m_grabOffsetY;
double leftX;
double rightX;
if (d->m_grab_point_index == 0) {
leftX = 0.0;
if (d->m_curve.points().count() > 1)
rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
else
rightX = 1.0;
} else if (d->m_grab_point_index == d->m_curve.points().count() - 1) {
leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
rightX = 1.0;
} else {
Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1);
// the 1E-4 addition so we can grab the dot later.
leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA;
rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA;
}
x = bounds(x, leftX, rightX);
y = bounds(y, 0., 1.);
d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y));
if (removePoint && d->m_curve.points().count() > 2) {
d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index];
d->m_draggedAwayPointIndex = d->m_grab_point_index;
d->m_curve.removePoint(d->m_grab_point_index);
d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1);
emit pointSelectedChanged();
}
d->setCurveModified();
}
}
KisCubicCurve KisCurveWidget::curve()
{
return d->m_curve;
}
void KisCurveWidget::setCurve(KisCubicCurve inlist)
{
d->m_curve = inlist;
d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1);
emit pointSelectedChanged();
d->setCurveModified();
}
void KisCurveWidget::leaveEvent(QEvent *)
{
}
diff --git a/libs/ui/widgets/kis_curve_widget.h b/libs/ui/widgets/kis_curve_widget.h
index a03225540e..4ee4330fa2 100644
--- a/libs/ui/widgets/kis_curve_widget.h
+++ b/libs/ui/widgets/kis_curve_widget.h
@@ -1,160 +1,160 @@
/*
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CURVE_WIDGET_H
#define KIS_CURVE_WIDGET_H
// Qt includes.
#include <QWidget>
#include <QColor>
#include <QPointF>
#include <QPixmap>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QEvent>
#include <QPaintEvent>
#include <QList>
#include <kritaui_export.h>
class QSpinBox;
class KisCubicCurve;
/**
* KisCurveWidget is a widget that shows a single curve that can be edited
* by the user. The user can grab the curve and move it; this creates
* a new control point. Control points can be deleted by selecting a point
* and pressing the delete key.
*
* (From: http://techbase.kde.org/Projects/Widgets_and_Classes#KisCurveWidget)
* KisCurveWidget allows editing of spline based y=f(x) curves. Handy for cases
* where you want the user to control such things as tablet pressure
* response, color transformations, acceleration by time, aeroplane lift
*by angle of attack.
*/
class KRITAUI_EXPORT KisCurveWidget : public QWidget
{
Q_OBJECT
Q_PROPERTY(bool pointSelected READ pointSelected NOTIFY pointSelectedChanged);
public:
friend class CurveEditorItem;
/**
* Create a new curve widget with a default curve, that is a straight
* line from bottom-left to top-right.
*/
- KisCurveWidget(QWidget *parent = 0, Qt::WFlags f = 0);
+ KisCurveWidget(QWidget *parent = 0, Qt::WindowFlags f = 0);
~KisCurveWidget() override;
/**
* Reset the curve to the default shape
*/
void reset(void);
/**
* Enable the guide and set the guide color to the specified color.
*
* XXX: it seems that the guide feature isn't actually implemented yet?
*/
void setCurveGuide(const QColor & color);
/**
* Set a background pixmap. The background pixmap will be drawn under
* the grid and the curve.
*
* XXX: or is the pixmap what is drawn to the left and bottom of the curve
* itself?
*/
void setPixmap(const QPixmap & pix);
QPixmap getPixmap();
void setBasePixmap(const QPixmap & pix);
QPixmap getBasePixmap();
/**
* Whether or not there is a point selected
* This does NOT include the first and last points
*/
bool pointSelected() const;
Q_SIGNALS:
/**
* Emitted whenever a control point has changed position.
*/
void modified(void);
/**
* Emitted whenever the status of whether a control point is selected or not changes
*/
void pointSelectedChanged();
protected Q_SLOTS:
void inOutChanged(int);
protected:
void keyPressEvent(QKeyEvent *) override;
void paintEvent(QPaintEvent *) override;
void mousePressEvent(QMouseEvent * e) override;
void mouseReleaseEvent(QMouseEvent * e) override;
void mouseMoveEvent(QMouseEvent * e) override;
void leaveEvent(QEvent *) override;
void resizeEvent(QResizeEvent *e) override;
public:
/**
* @return get a list with all defined points. If you want to know what the
* y value for a given x is on the curve defined by these points, use getCurveValue().
* @see getCurveValue
*/
KisCubicCurve curve();
/**
* Replace the current curve with a curve specified by the curve defined by the control
* points in @param inlist.
*/
void setCurve(KisCubicCurve inlist);
/**
* Connect/disconnect external spinboxes to the curve
* @min/@max - is the range for their values
*/
void setupInOutControls(QSpinBox *in, QSpinBox *out, int min, int max);
void dropInOutControls();
/**
* Handy function that creates new point in the middle
* of the curve and sets focus on the m_intIn field,
* so the user can move this point anywhere in a moment
*/
void addPointInTheMiddle();
private:
class Private;
Private * const d;
};
#endif /* KIS_CURVE_WIDGET_H */
diff --git a/libs/ui/widgets/kis_gradient_slider_widget.cc b/libs/ui/widgets/kis_gradient_slider_widget.cc
index f504dd178c..8f84608f55 100644
--- a/libs/ui/widgets/kis_gradient_slider_widget.cc
+++ b/libs/ui/widgets/kis_gradient_slider_widget.cc
@@ -1,225 +1,225 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* 2004 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "widgets/kis_gradient_slider_widget.h"
#include <QPainter>
#include <QContextMenuEvent>
#include <QPixmap>
#include <QMouseEvent>
#include <QPolygon>
#include <QPaintEvent>
#include <QMenu>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <QAction>
#include <resources/KoSegmentGradient.h>
#define MARGIN 5
#define HANDLE_SIZE 10
-KisGradientSliderWidget::KisGradientSliderWidget(QWidget *parent, const char* name, Qt::WFlags f)
+KisGradientSliderWidget::KisGradientSliderWidget(QWidget *parent, const char* name, Qt::WindowFlags f)
: QWidget(parent, f),
m_currentSegment(0),
m_selectedSegment(0),
m_drag(0)
{
setObjectName(name);
setMinimumHeight(30);
m_segmentMenu = new QMenu();
m_segmentMenu->addAction(i18n("Split Segment"), this, SLOT(slotSplitSegment()));
m_segmentMenu->addAction(i18n("Duplicate Segment"), this, SLOT(slotDuplicateSegment()));
m_segmentMenu->addAction(i18n("Mirror Segment"), this, SLOT(slotMirrorSegment()));
m_removeSegmentAction = new QAction(i18n("Remove Segment"), this);
connect(m_removeSegmentAction, SIGNAL(triggered()), this, SLOT(slotRemoveSegment()));
m_segmentMenu->addAction(m_removeSegmentAction);
}
void KisGradientSliderWidget::setGradientResource(KoSegmentGradient* agr)
{
m_autogradientResource = agr;
m_selectedSegment = m_autogradientResource->segmentAt(0.0);
emit sigSelectedSegment(m_selectedSegment);
}
void KisGradientSliderWidget::paintEvent(QPaintEvent* pe)
{
QWidget::paintEvent(pe);
QPainter painter(this);
painter.fillRect(rect(), palette().background());
painter.setPen(Qt::black);
painter.drawRect(MARGIN, MARGIN, width() - 2 * MARGIN, height() - 2 * MARGIN - HANDLE_SIZE);
if (m_autogradientResource) {
QImage image = m_autogradientResource->generatePreview(width() - 2 * MARGIN - 2, height() - 2 * MARGIN - HANDLE_SIZE - 2);
QPixmap pixmap(image.width(), image.height());
if (!image.isNull()) {
painter.drawImage(MARGIN + 1, MARGIN + 1, image);
}
painter.fillRect(MARGIN + 1, height() - MARGIN - HANDLE_SIZE, width() - 2 * MARGIN, HANDLE_SIZE, QBrush(Qt::white));
if (m_selectedSegment) {
QRect selection(qRound(m_selectedSegment->startOffset()*(double)(width() - 2 * MARGIN - 2)) + 6,
height() - HANDLE_SIZE - MARGIN,
qRound((m_selectedSegment->endOffset() - m_selectedSegment->startOffset())*(double)(width() - 12)),
HANDLE_SIZE);
painter.fillRect(selection, QBrush(palette().highlight()));
}
QPolygon triangle(3);
QList<double> handlePositions = m_autogradientResource->getHandlePositions();
int position;
painter.setBrush(QBrush(Qt::black));
for (int i = 0; i < handlePositions.count(); i++) {
position = qRound(handlePositions[i] * (double)(width() - 12)) + 6;
triangle[0] = QPoint(position, height() - HANDLE_SIZE - MARGIN);
triangle[1] = QPoint(position + (HANDLE_SIZE / 2 - 1), height() - MARGIN);
triangle[2] = QPoint(position - (HANDLE_SIZE / 2 - 1), height() - MARGIN);
painter.drawPolygon(triangle);
}
painter.setBrush(QBrush(Qt::white));
QList<double> middleHandlePositions = m_autogradientResource->getMiddleHandlePositions();
for (int i = 0; i < middleHandlePositions.count(); i++) {
position = qRound(middleHandlePositions[i] * (double)(width() - 12)) + 6;
triangle[0] = QPoint(position, height() - HANDLE_SIZE - MARGIN);
triangle[1] = QPoint(position + (HANDLE_SIZE / 2 - 2), height() - MARGIN);
triangle[2] = QPoint(position - (HANDLE_SIZE / 2 - 2), height() - MARGIN);
painter.drawPolygon(triangle);
}
}
}
void KisGradientSliderWidget::mousePressEvent(QMouseEvent * e)
{
if ((e->y() < MARGIN || e->y() > height() - MARGIN) || (e->x() < MARGIN || e->x() > width() - MARGIN) || e-> button() != Qt::LeftButton) {
QWidget::mousePressEvent(e);
return;
}
double t = (double)(e->x() - MARGIN) / (double)(width() - 2 * MARGIN);
KoGradientSegment* segment = 0;
segment = m_autogradientResource->segmentAt(t);
if (segment != 0) {
m_currentSegment = segment;
QRect leftHandle(qRound(m_currentSegment->startOffset() *(double)(width() - 2*MARGIN - 2) + MARGIN - (HANDLE_SIZE / 2 - 1)),
height() - HANDLE_SIZE,
HANDLE_SIZE - 1,
HANDLE_SIZE);
QRect middleHandle(qRound(m_currentSegment->middleOffset() *(double)(width() - 2*MARGIN - 2) + MARGIN - (HANDLE_SIZE / 2 - 2)),
height() - HANDLE_SIZE - MARGIN,
HANDLE_SIZE - 1,
HANDLE_SIZE);
QRect rightHandle(qRound(m_currentSegment->endOffset() *(double)(width() - 2*MARGIN - 2) + MARGIN - (HANDLE_SIZE / 2 - 1)),
height() - HANDLE_SIZE,
HANDLE_SIZE - 1,
HANDLE_SIZE);
// Change the activation order of the handles to avoid deadlocks
if (t > 0.5) {
if (leftHandle.contains(e->pos()))
m_drag = LEFT_DRAG;
else if (middleHandle.contains(e->pos()))
m_drag = MIDDLE_DRAG;
else if (rightHandle.contains(e->pos()))
m_drag = RIGHT_DRAG;
} else {
if (rightHandle.contains(e->pos()))
m_drag = RIGHT_DRAG;
else if (middleHandle.contains(e->pos()))
m_drag = MIDDLE_DRAG;
else if (leftHandle.contains(e->pos()))
m_drag = LEFT_DRAG;
}
if (m_drag == NO_DRAG) {
m_selectedSegment = m_currentSegment;
emit sigSelectedSegment(m_selectedSegment);
}
}
repaint();
}
void KisGradientSliderWidget::mouseReleaseEvent(QMouseEvent * e)
{
Q_UNUSED(e);
m_drag = NO_DRAG;
}
void KisGradientSliderWidget::mouseMoveEvent(QMouseEvent * e)
{
if ((e->y() < MARGIN || e->y() > height() - MARGIN) || (e->x() < MARGIN || e->x() > width() - MARGIN)) {
QWidget::mouseMoveEvent(e);
return;
}
double t = (double)(e->x() - MARGIN) / (double)(width() - 2 * MARGIN);
switch (m_drag) {
case RIGHT_DRAG:
m_autogradientResource->moveSegmentEndOffset(m_currentSegment, t);
break;
case LEFT_DRAG:
m_autogradientResource->moveSegmentStartOffset(m_currentSegment, t);
break;
case MIDDLE_DRAG:
m_autogradientResource->moveSegmentMiddleOffset(m_currentSegment, t);
break;
}
if (m_drag != NO_DRAG)
emit sigChangedSegment(m_currentSegment);
repaint();
}
void KisGradientSliderWidget::contextMenuEvent(QContextMenuEvent * e)
{
m_removeSegmentAction->setEnabled(m_autogradientResource->removeSegmentPossible());
m_segmentMenu->popup(e->globalPos());
}
void KisGradientSliderWidget::slotSplitSegment()
{
m_autogradientResource->splitSegment(m_selectedSegment);
emit sigSelectedSegment(m_selectedSegment);
repaint();
}
void KisGradientSliderWidget::slotDuplicateSegment()
{
m_autogradientResource->duplicateSegment(m_selectedSegment);
emit sigSelectedSegment(m_selectedSegment);
repaint();
}
void KisGradientSliderWidget::slotMirrorSegment()
{
m_autogradientResource->mirrorSegment(m_selectedSegment);
emit sigSelectedSegment(m_selectedSegment);
repaint();
}
void KisGradientSliderWidget::slotRemoveSegment()
{
m_selectedSegment = m_autogradientResource->removeSegment(m_selectedSegment);
emit sigSelectedSegment(m_selectedSegment);
repaint();
}
diff --git a/libs/ui/widgets/kis_gradient_slider_widget.h b/libs/ui/widgets/kis_gradient_slider_widget.h
index e8b5b9aa11..533a62d87e 100644
--- a/libs/ui/widgets/kis_gradient_slider_widget.h
+++ b/libs/ui/widgets/kis_gradient_slider_widget.h
@@ -1,86 +1,86 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* 2004 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_GRADIENT_SLIDER_WIDGET_H_
#define _KIS_GRADIENT_SLIDER_WIDGET_H_
#include <QWidget>
#include <QMouseEvent>
#include <QPaintEvent>
class QAction;
class QMenu;
class KoGradientSegment;
class KoSegmentGradient;
class KisGradientSliderWidget : public QWidget
{
Q_OBJECT
public:
- KisGradientSliderWidget(QWidget *parent = 0, const char* name = 0, Qt::WFlags f = 0);
+ KisGradientSliderWidget(QWidget *parent = 0, const char* name = 0, Qt::WindowFlags f = 0);
public:
void paintEvent(QPaintEvent *) override;
void setGradientResource(KoSegmentGradient* agr);
KoGradientSegment* selectedSegment() {
return m_selectedSegment;
}
Q_SIGNALS:
void sigSelectedSegment(KoGradientSegment*);
void sigChangedSegment(KoGradientSegment*);
protected:
void mousePressEvent(QMouseEvent * e) override;
void mouseReleaseEvent(QMouseEvent * e) override;
void mouseMoveEvent(QMouseEvent * e) override;
void contextMenuEvent(QContextMenuEvent * e) override;
private Q_SLOTS:
void slotSplitSegment();
void slotDuplicateSegment();
void slotMirrorSegment();
void slotRemoveSegment();
private:
enum {
NO_DRAG,
LEFT_DRAG,
RIGHT_DRAG,
MIDDLE_DRAG
};
enum {
SPLIT_SEGMENT,
DUPLICATE_SEGMENT,
MIRROR_SEGMENT,
REMOVE_SEGMENT
};
KoSegmentGradient* m_autogradientResource;
KoGradientSegment* m_currentSegment;
KoGradientSegment* m_selectedSegment;
QMenu* m_segmentMenu;
int m_drag;
QAction *m_removeSegmentAction;
};
#endif
diff --git a/libs/ui/widgets/kis_paintop_presets_popup.cpp b/libs/ui/widgets/kis_paintop_presets_popup.cpp
index 3e1a15cc6b..535bf4b751 100644
--- a/libs/ui/widgets/kis_paintop_presets_popup.cpp
+++ b/libs/ui/widgets/kis_paintop_presets_popup.cpp
@@ -1,777 +1,772 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* 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 <QList>
#include <QComboBox>
#include <QHBoxLayout>
#include <QToolButton>
#include <QGridLayout>
#include <QFont>
#include <QMenu>
#include <QAction>
#include <QShowEvent>
#include <QFontDatabase>
#include <QWidgetAction>
#include <kconfig.h>
#include <klocalizedstring.h>
#include <KoDockRegistry.h>
#include <kis_icon.h>
#include <kis_icon.h>
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paintop_config_widget.h>
#include <kis_canvas_resource_provider.h>
#include <widgets/kis_preset_chooser.h>
#include <widgets/kis_preset_selector_strip.h>
#include <ui_wdgpaintopsettings.h>
#include <kis_node.h>
#include "kis_config.h"
#include "kis_resource_server_provider.h"
#include "kis_lod_availability_widget.h"
#include "kis_signal_auto_connection.h"
#include <kis_paintop_settings_update_proxy.h>
// ones from brush engine selector
#include <brushengine/kis_paintop_factory.h>
#include <kis_preset_live_preview_view.h>
struct KisPaintOpPresetsPopup::Private
{
public:
Ui_WdgPaintOpSettings uiWdgPaintOpPresetSettings;
QGridLayout *layout;
KisPaintOpConfigWidget *settingsWidget;
QFont smallFont;
KisCanvasResourceProvider *resourceProvider;
KisFavoriteResourceManager *favoriteResManager;
bool detached;
bool ignoreHideEvents;
QSize minimumSettingsWidgetSize;
QRect detachedGeometry;
KisSignalAutoConnectionsStore widgetConnections;
};
KisPaintOpPresetsPopup::KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider,
KisFavoriteResourceManager* favoriteResourceManager,
KisPresetSaveWidget* savePresetWidget,
QWidget * parent)
: QWidget(parent)
, m_d(new Private())
{
setObjectName("KisPaintOpPresetsPopup");
setFont(KoDockRegistry::dockFont());
current_paintOpId = "";
m_d->resourceProvider = resourceProvider;
m_d->favoriteResManager = favoriteResourceManager;
m_d->uiWdgPaintOpPresetSettings.setupUi(this);
m_d->layout = new QGridLayout(m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer);
m_d->layout->setSizeConstraint(QLayout::SetFixedSize);
m_d->uiWdgPaintOpPresetSettings.scratchPad->setupScratchPad(resourceProvider, Qt::white);
m_d->uiWdgPaintOpPresetSettings.scratchPad->setCutoutOverlayRect(QRect(25, 25, 200, 200));
m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand"));
m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new"));
m_d->uiWdgPaintOpPresetSettings.fillLayer->hide();
m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient"));
m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill"));
m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete"));
m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setIcon(KisIconUtils::loadIcon("updateColorize")); // refresh icon
m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setIcon(KisIconUtils::loadIcon("dirty-preset")); // edit icon
m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setIcon(KisIconUtils::loadIcon("warning"));
m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setToolTip(i18n("The settings for this preset have changed from their default."));
+ m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02"));
+ m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setToolTip(i18n("Toggle showing presets"));
+
+ m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setToolTip(i18n("Toggle showing scratchpad"));
m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setToolTip(i18n("Reload the brush preset"));
m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setToolTip(i18n("Rename the brush preset"));
// overwrite existing preset and saving a new preset use the same dialog
saveDialog = savePresetWidget;
saveDialog->scratchPadSetup(resourceProvider);
saveDialog->setFavoriteResourceManager(m_d->favoriteResManager); // this is needed when saving the preset
saveDialog->hide();
// the area on the brush editor for renaming the brush. make sure edit fields are hidden by default
toggleBrushRenameUIActive(false);
// DETAIL and THUMBNAIL view changer
QMenu* menu = new QMenu(this);
menu->setStyleSheet("margin: 6px");
menu->addSection(i18n("Display"));
QActionGroup *actionGroup = new QActionGroup(this);
KisPresetChooser::ViewMode mode = (KisPresetChooser::ViewMode)KisConfig().presetChooserViewMode();
QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotThumbnailMode()));
action->setCheckable(true);
action->setChecked(mode == KisPresetChooser::THUMBNAIL);
action->setActionGroup(actionGroup);
action = menu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotDetailMode()));
action->setCheckable(true);
action->setChecked(mode == KisPresetChooser::DETAIL);
action->setActionGroup(actionGroup);
// add horizontal slider for the icon size
QSlider* iconSizeSlider = new QSlider(this);
iconSizeSlider->setOrientation(Qt::Horizontal);
iconSizeSlider->setRange(30, 80);
iconSizeSlider->setValue(m_d->uiWdgPaintOpPresetSettings.presetWidget->iconSize());
iconSizeSlider->setMinimumHeight(20);
iconSizeSlider->setMinimumWidth(40);
iconSizeSlider->setTickInterval(10);
QWidgetAction *sliderAction= new QWidgetAction(this);
sliderAction->setDefaultWidget(iconSizeSlider);
menu->addSection(i18n("Icon Size"));
menu->addAction(sliderAction);
// configure the button and assign menu
m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setMenu(menu);
m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose"));
m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setPopupMode(QToolButton::InstantPopup);
// show/hide buttons
KisConfig cfg;
m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setCheckable(true);
m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setChecked(cfg.scratchpadVisible());
- m_d->uiWdgPaintOpPresetSettings.showEditorButton->setCheckable(true);
- m_d->uiWdgPaintOpPresetSettings.showEditorButton->setChecked(true);
- m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setText(i18n("Presets"));
+ if (cfg.scratchpadVisible()) {
+ slotSwitchScratchpad(true); // show scratchpad
+ } else {
+ slotSwitchScratchpad(false);
+ }
+
m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setCheckable(true);
- m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setChecked(false); // use a config to load/save this state
+ m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setChecked(false);
slotSwitchShowPresets(false); // hide presets by default
// Connections
-
connect(m_d->uiWdgPaintOpPresetSettings.paintPresetIcon, SIGNAL(clicked()),
m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(paintPresetImage()));
connect(saveDialog, SIGNAL(resourceSelected(KoResource*)), this, SLOT(resourceSelected(KoResource*)));
connect (m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton, SIGNAL(clicked(bool)),
this, SLOT(slotRenameBrushActivated()));
connect (m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton, SIGNAL(clicked(bool)),
this, SLOT(slotRenameBrushDeactivated()));
connect(m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton, SIGNAL(clicked(bool)),
this, SLOT(slotSaveRenameCurrentBrush()));
connect(m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField, SIGNAL(returnPressed()),
SLOT(slotSaveRenameCurrentBrush()));
connect(iconSizeSlider, SIGNAL(sliderMoved(int)),
m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSetIconSize(int)));
connect(iconSizeSlider, SIGNAL(sliderReleased()),
m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSaveIconSize()));
connect(m_d->uiWdgPaintOpPresetSettings.showScratchpadButton, SIGNAL(clicked(bool)),
this, SLOT(slotSwitchScratchpad(bool)));
- connect(m_d->uiWdgPaintOpPresetSettings.showEditorButton, SIGNAL(clicked(bool)),
- this, SLOT(slotSwitchShowEditor(bool)));
connect(m_d->uiWdgPaintOpPresetSettings.showPresetsButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowPresets(bool)));
connect(m_d->uiWdgPaintOpPresetSettings.eraseScratchPad, SIGNAL(clicked()),
m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillDefault()));
connect(m_d->uiWdgPaintOpPresetSettings.fillLayer, SIGNAL(clicked()),
m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillLayer()));
connect(m_d->uiWdgPaintOpPresetSettings.fillGradient, SIGNAL(clicked()),
m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillGradient()));
connect(m_d->uiWdgPaintOpPresetSettings.fillSolid, SIGNAL(clicked()),
m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillBackground()));
m_d->settingsWidget = 0;
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
connect(m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton, SIGNAL(clicked()),
this, SLOT(slotSaveBrushPreset()));
connect(m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton, SIGNAL(clicked()),
this, SLOT(slotSaveNewBrushPreset()));
connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()),
this, SIGNAL(reloadPresetClicked()));
connect(m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset, SIGNAL(clicked()),
this, SIGNAL(defaultPresetClicked()));
connect(m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox, SIGNAL(toggled(bool)),
this, SIGNAL(dirtyPresetToggled(bool)));
connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox, SIGNAL(toggled(bool)),
this, SIGNAL(eraserBrushSizeToggled(bool)));
connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox, SIGNAL(toggled(bool)),
this, SIGNAL(eraserBrushOpacityToggled(bool)));
// preset widget connections
connect(m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SIGNAL(resourceSelected(KoResource*)),
this, SIGNAL(signalResourceSelected(KoResource*)));
connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()),
m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings()));
connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), SLOT(slotUpdatePresetSettings()));
m_d->detached = false;
m_d->ignoreHideEvents = false;
m_d->minimumSettingsWidgetSize = QSize(0, 0);
- m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(cfg.scratchpadVisible());
+
m_d->detachedGeometry = QRect(100, 100, 0, 0);
m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox->setChecked(cfg.useDirtyPresets());
m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox->setChecked(cfg.useEraserBrushSize());
m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox->setChecked(cfg.useEraserBrushOpacity());
m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setCanvasResourceManager(resourceProvider->resourceManager());
connect(resourceProvider->resourceManager(),
SIGNAL(canvasResourceChanged(int,QVariant)),
SLOT(slotResourceChanged(int, QVariant)));
connect(m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability,
SIGNAL(sigUserChangedLodAvailability(bool)),
SLOT(slotLodAvailabilityChanged(bool)));
slotResourceChanged(KisCanvasResourceProvider::LodAvailability,
resourceProvider->resourceManager()->
resource(KisCanvasResourceProvider::LodAvailability));
connect(m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdatePaintOpFilter()));
// setup things like the scene construct images, layers, etc that is a one-time thing
m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setup();
- m_d->uiWdgPaintOpPresetSettings.zoomOutGraphicsViewButton->setIcon(KisIconUtils::loadIcon("view-fullscreen"));
- connect(m_d->uiWdgPaintOpPresetSettings.zoomOutGraphicsViewButton, SIGNAL(clicked(bool)),
- m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView,
- SLOT(slotZoomToOneHundredPercent()));
-
-
- m_d->uiWdgPaintOpPresetSettings.resetGraphicsViewButton->setIcon(KisIconUtils::loadIcon("view-refresh"));
- connect(m_d->uiWdgPaintOpPresetSettings.resetGraphicsViewButton, SIGNAL(clicked(bool)),
- m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView,
- SLOT(slotResetViewZoom()));
-
}
void KisPaintOpPresetsPopup::slotRenameBrushActivated()
{
toggleBrushRenameUIActive(true);
}
void KisPaintOpPresetsPopup::slotRenameBrushDeactivated()
{
toggleBrushRenameUIActive(false);
}
void KisPaintOpPresetsPopup::toggleBrushRenameUIActive(bool isRenaming)
{
// This function doesn't really do anything except get the UI in a state to rename a brush preset
-
- m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(!isRenaming);
-
m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setVisible(isRenaming);
m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton->setVisible(isRenaming);
m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton->setVisible(isRenaming);
+
+ // hide these below areas while renaming
+ m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setVisible(!isRenaming);
+ m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(!isRenaming);
m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(!isRenaming);
+ m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setVisible(!isRenaming);
m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setEnabled(!isRenaming);
+ m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setVisible(!isRenaming);
+
+ // if the presets area is shown, only then can you show/hide the load default brush
+ // need to think about weird state when you are in the middle of renaming a brush
+ // what happens if you try to change presets. maybe we should auto-hide (or disable)
+ // the presets area in this case
+ if (m_d->uiWdgPaintOpPresetSettings.presetWidget->isVisible()) {
+ m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset->setVisible(!isRenaming);
+ }
- m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset->setVisible(!isRenaming);
}
void KisPaintOpPresetsPopup::slotSaveRenameCurrentBrush()
{
// if you are renaming a brush, that is different than updating the settings
// make sure we are in a clean state before renaming. This logic might change,
// but that is what we are going with for now
emit reloadPresetClicked();
m_d->favoriteResManager->setBlockUpdates(true);
// get a reference to the existing (and new) file name and path that we are working with
KisPaintOpPresetSP curPreset = m_d->resourceProvider->currentPreset();
if (!curPreset)
return;
KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer();
QString saveLocation = rServer->saveLocation();
QString originalPresetName = curPreset->name();
QString renamedPresetName = m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->text();
QString originalPresetPathAndFile = saveLocation + originalPresetName + curPreset->defaultFileExtension();
QString renamedPresetPathAndFile = saveLocation + renamedPresetName + curPreset->defaultFileExtension();
// create a new brush preset with the name specified and add to resource provider
KisPaintOpPresetSP newPreset = curPreset->clone();
newPreset->setFilename(renamedPresetPathAndFile); // this also contains the path
newPreset->setName(renamedPresetName);
newPreset->setImage(curPreset->image()); // use existing thumbnail (might not need to do this)
newPreset->setPresetDirty(false);
newPreset->setValid(true);
rServer->addResource(newPreset);
resourceSelected(newPreset.data()); // refresh and select our freshly renamed resource
// Now blacklist the original file
if (rServer->resourceByName(originalPresetName)) {
rServer->removeResourceAndBlacklist(curPreset);
}
m_d->favoriteResManager->setBlockUpdates(false);
toggleBrushRenameUIActive(false); // this returns the UI to its original state after saving
slotUpdatePresetSettings(); // update visibility of dirty preset and icon
}
void KisPaintOpPresetsPopup::slotResourceChanged(int key, const QVariant &value)
{
if (key == KisCanvasResourceProvider::LodAvailability) {
m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedLodAvailability(value.toBool());
}
}
void KisPaintOpPresetsPopup::slotLodAvailabilityChanged(bool value)
{
m_d->resourceProvider->resourceManager()->setResource(KisCanvasResourceProvider::LodAvailability, QVariant(value));
}
KisPaintOpPresetsPopup::~KisPaintOpPresetsPopup()
{
if (m_d->settingsWidget) {
m_d->layout->removeWidget(m_d->settingsWidget);
m_d->settingsWidget->hide();
m_d->settingsWidget->setParent(0);
m_d->settingsWidget = 0;
}
delete m_d;
}
void KisPaintOpPresetsPopup::setPaintOpSettingsWidget(QWidget * widget)
{
if (m_d->settingsWidget) {
m_d->layout->removeWidget(m_d->settingsWidget);
m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer->updateGeometry();
}
m_d->layout->update();
updateGeometry();
m_d->widgetConnections.clear();
m_d->settingsWidget = 0;
if (widget) {
m_d->settingsWidget = dynamic_cast<KisPaintOpConfigWidget*>(widget);
KIS_ASSERT_RECOVER_RETURN(m_d->settingsWidget);
- if (m_d->settingsWidget->supportScratchBox()) {
- showScratchPad();
+ KisConfig cfg;
+ if (m_d->settingsWidget->supportScratchBox() && cfg.scratchpadVisible()) {
+ slotSwitchScratchpad(true);
} else {
- hideScratchPad();
+ slotSwitchScratchpad(false);
}
m_d->widgetConnections.addConnection(m_d->settingsWidget, SIGNAL(sigConfigurationItemChanged()),
this, SLOT(slotUpdateLodAvailability()));
widget->setFont(m_d->smallFont);
QSize hint = widget->sizeHint();
m_d->minimumSettingsWidgetSize = QSize(qMax(hint.width(), m_d->minimumSettingsWidgetSize.width()),
qMax(hint.height(), m_d->minimumSettingsWidgetSize.height()));
widget->setMinimumSize(m_d->minimumSettingsWidgetSize);
m_d->layout->addWidget(widget);
// hook up connections that will monitor if our preset is dirty or not. Show a notification if it is
if (m_d->resourceProvider && m_d->resourceProvider->currentPreset() ) {
KisPaintOpPresetSP preset = m_d->resourceProvider->currentPreset();
m_d->widgetConnections.addConnection(preset->updateProxy(), SIGNAL(sigSettingsChanged()),
this, SLOT(slotUpdatePresetSettings()));
}
m_d->layout->update();
widget->show();
}
slotUpdateLodAvailability();
}
void KisPaintOpPresetsPopup::slotUpdateLodAvailability()
{
if (!m_d->settingsWidget) return;
KisPaintopLodLimitations l = m_d->settingsWidget->lodLimitations();
m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setLimitations(l);
}
QImage KisPaintOpPresetsPopup::cutOutOverlay()
{
return m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay();
}
void KisPaintOpPresetsPopup::contextMenuEvent(QContextMenuEvent *e)
{
Q_UNUSED(e);
}
void KisPaintOpPresetsPopup::switchDetached(bool show)
{
if (parentWidget()) {
m_d->detached = !m_d->detached;
if (m_d->detached) {
m_d->ignoreHideEvents = true;
if (show) {
parentWidget()->show();
}
m_d->ignoreHideEvents = false;
}
else {
KisConfig cfg;
parentWidget()->hide();
}
KisConfig cfg;
cfg.setPaintopPopupDetached(m_d->detached);
}
}
-void KisPaintOpPresetsPopup::hideScratchPad()
-{
- m_d->uiWdgPaintOpPresetSettings.scratchPad->setEnabled(false);
- m_d->uiWdgPaintOpPresetSettings.fillGradient->setEnabled(false);
- m_d->uiWdgPaintOpPresetSettings.fillSolid->setEnabled(false);
- m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setEnabled(false);
-}
-
-void KisPaintOpPresetsPopup::showScratchPad()
-{
- m_d->uiWdgPaintOpPresetSettings.scratchPad->setEnabled(true);
- m_d->uiWdgPaintOpPresetSettings.fillGradient->setEnabled(true);
- m_d->uiWdgPaintOpPresetSettings.fillSolid->setEnabled(true);
- m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setEnabled(true);
-}
-
void KisPaintOpPresetsPopup::resourceSelected(KoResource* resource)
{
// this gets called every time the brush editor window is opened
// TODO: this gets called multiple times whenever the preset is changed in the presets area
// the connections probably need to be thought about with this a bit more to keep things in sync
m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(resource);
// find the display name of the brush engine and append it to the selected preset display
QString currentBrushEngineName;
for(int i=0; i < sortedBrushEnginesList.length(); i++) {
if (sortedBrushEnginesList.at(i).id == currentPaintOpId() ) {
currentBrushEngineName = sortedBrushEnginesList.at(i).name;
}
}
QString selectedBrush = resource->name();
m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setText(selectedBrush);
m_d->uiWdgPaintOpPresetSettings.currentBrushEngineLabel->setText(currentBrushEngineName.append(" ").append("Engine"));
m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setText(resource->name());
// get the preset image and pop it into the thumbnail area on the top of the brush editor
QGraphicsScene * thumbScene = new QGraphicsScene(this);
thumbScene->addPixmap(QPixmap::fromImage(resource->image().scaled(55, 55)));
thumbScene->setSceneRect(0, 0, 55, 55); // 55 x 55 image for thumb. this is also set in the UI
m_d->uiWdgPaintOpPresetSettings.presetThumbnailicon->setScene(thumbScene);
toggleBrushRenameUIActive(false); // reset the UI state of renaming a brush if we are changing brush presets
slotUpdatePresetSettings(); // check to see if the dirty preset icon needs to be shown
}
bool variantLessThan(const KisPaintOpInfo v1, const KisPaintOpInfo v2)
{
return v1.priority < v2.priority;
}
void KisPaintOpPresetsPopup::setPaintOpList(const QList< KisPaintOpFactory* >& list)
{
m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->clear(); // reset combobox list just in case
// create a new list so we can sort it and populate the brush engine combo box
sortedBrushEnginesList.clear(); // just in case this function is called again, don't keep adding to the list
for(int i=0; i < list.length(); i++) {
QString fileName = KoResourcePaths::findResource("kis_images", list.at(i)->pixmap());
QPixmap pixmap(fileName);
if(pixmap.isNull()){
pixmap = QPixmap(22,22);
pixmap.fill();
}
KisPaintOpInfo paintOpInfo;
paintOpInfo.id = list.at(i)->id();
paintOpInfo.name = list.at(i)->name();
paintOpInfo.icon = pixmap;
paintOpInfo.priority = list.at(i)->priority();
sortedBrushEnginesList.append(paintOpInfo);
}
std::stable_sort(sortedBrushEnginesList.begin(), sortedBrushEnginesList.end(), variantLessThan );
// add an "All" option at the front to show all presets
QPixmap emptyPixmap = QPixmap(22,22);
emptyPixmap.fill(palette().color(QPalette::Background));
sortedBrushEnginesList.push_front(KisPaintOpInfo(QString("all_options"), i18n("All"), QString(""), emptyPixmap, 0 ));
// fill the list into the brush combo box
for (int m = 0; m < sortedBrushEnginesList.length(); m++) {
m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->addItem(sortedBrushEnginesList[m].icon, sortedBrushEnginesList[m].name, QVariant(sortedBrushEnginesList[m].id));
}
}
void KisPaintOpPresetsPopup::setCurrentPaintOpId(const QString& paintOpId)
{
current_paintOpId = paintOpId;
}
QString KisPaintOpPresetsPopup::currentPaintOpId() {
return current_paintOpId;
}
void KisPaintOpPresetsPopup::setPresetImage(const QImage& image)
{
m_d->uiWdgPaintOpPresetSettings.scratchPad->setPresetImage(image);
saveDialog->brushPresetThumbnailWidget->setPresetImage(image);
}
void KisPaintOpPresetsPopup::hideEvent(QHideEvent *event)
{
if (m_d->ignoreHideEvents) {
return;
}
if (m_d->detached) {
m_d->detachedGeometry = window()->geometry();
}
QWidget::hideEvent(event);
}
void KisPaintOpPresetsPopup::showEvent(QShowEvent *)
{
if (m_d->detached) {
window()->setGeometry(m_d->detachedGeometry);
}
+
emit brushEditorShown();
}
void KisPaintOpPresetsPopup::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
emit sizeChanged();
}
bool KisPaintOpPresetsPopup::detached() const
{
return m_d->detached;
}
void KisPaintOpPresetsPopup::slotSwitchScratchpad(bool visible)
{
- m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(visible);
- KisConfig cfg;
- cfg.setScratchpadVisible(visible);
- calculateShowingTopArea();
-}
-
-void KisPaintOpPresetsPopup::calculateShowingTopArea() {
- // if the scratchpad is the only area visible, we should hide the currently selected brush and settings
- // so the top area doesn't
-
- bool shouldDisplayTopBar = true;
-
- if (m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->isChecked() && !m_d->uiWdgPaintOpPresetSettings.showPresetsButton->isChecked() &&
- !m_d->uiWdgPaintOpPresetSettings.showEditorButton->isChecked() ) {
- shouldDisplayTopBar = false;
+ // 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"));
}
- m_d->uiWdgPaintOpPresetSettings.presetThumbnailicon->setVisible(shouldDisplayTopBar);
- m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setVisible(shouldDisplayTopBar);
- m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(shouldDisplayTopBar);
- // always hide these since they are part of the brush renaming field and can make things get in a weird state
- m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setVisible(false);
- m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton->setVisible(false);
- m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton->setVisible(false);
- slotUpdatePresetSettings(); // find out if the preset is dirty or not and update visibility
+ KisConfig cfg;
+ cfg.setScratchpadVisible(visible);
}
-
void KisPaintOpPresetsPopup::slotSwitchShowEditor(bool visible) {
m_d->uiWdgPaintOpPresetSettings.brushEditorSettingsControls->setVisible(visible);
- calculateShowingTopArea();
}
void KisPaintOpPresetsPopup::slotSwitchShowPresets(bool visible) {
- m_d->uiWdgPaintOpPresetSettings.presetsContainer->setVisible(visible);
- calculateShowingTopArea() ;
+
+ m_d->uiWdgPaintOpPresetSettings.presetWidget->setVisible(visible);
+ m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setVisible(visible);
+ m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->setVisible(visible);
+ m_d->uiWdgPaintOpPresetSettings.engineFilterLabel->setVisible(visible);
+ m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset->setVisible(visible);
+ m_d->uiWdgPaintOpPresetSettings.presetsSidebarLabel->setVisible(visible);
+
+
+ // we only want a spacer to work when the toggle icon is present. Otherwise the list of presets will shrink
+ // which is something we don't want
+ if (visible) {
+ m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::Ignored,QSizePolicy::Ignored);
+ m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-right"));
+ } else {
+ m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding);
+ m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-left"));
+ }
+
}
void KisPaintOpPresetsPopup::slotUpdatePaintOpFilter() {
QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->currentData(); // grab paintOpID from data
QString filterPaintOpId = userData.toString();
if (filterPaintOpId == "all_options") {
filterPaintOpId = "";
}
m_d->uiWdgPaintOpPresetSettings.presetWidget->setPresetFilter(filterPaintOpId);
}
void KisPaintOpPresetsPopup::slotSaveBrushPreset() {
// here we are assuming that people want to keep their existing preset icon. We will just update the
// settings and save a new copy with the same name.
// there is a dialog with save options, but we don't need to show it in this situation
saveDialog->isSavingNewBrush(false); // this mostly just makes sure we keep the existing brush preset name when saving
saveDialog->loadExistingThumbnail(); // This makes sure we use the existing preset icon when updating the existing brush preset
saveDialog->savePreset();
// refresh the view settings so the brush doesn't appear dirty
slotUpdatePresetSettings();
}
void KisPaintOpPresetsPopup::slotSaveNewBrushPreset() {
saveDialog->isSavingNewBrush(true);
saveDialog->saveScratchPadThumbnailArea(m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay());
saveDialog->showDialog();
}
-
void KisPaintOpPresetsPopup::updateViewSettings()
{
m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->updateViewSettings();
}
void KisPaintOpPresetsPopup::currentPresetChanged(KisPaintOpPresetSP preset)
{
if (preset) {
m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(preset.data());
setCurrentPaintOpId(preset->paintOp().id());
}
}
void KisPaintOpPresetsPopup::updateThemedIcons()
{
m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new"));
m_d->uiWdgPaintOpPresetSettings.fillLayer->hide();
m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient"));
m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill"));
m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete"));
m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose"));
}
void KisPaintOpPresetsPopup::slotUpdatePresetSettings()
{
if (!m_d->resourceProvider) {
return;
}
if (!m_d->resourceProvider->currentPreset()) {
return;
}
bool isPresetDirty = m_d->resourceProvider->currentPreset()->isPresetDirty();
// don't need to reload or overwrite a clean preset
m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setVisible(isPresetDirty);
m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setVisible(isPresetDirty);
m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(isPresetDirty);
// update live preview area in here...
// don't update the live preview if the widget is not visible.
if (m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->isVisible()) {
m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setCurrentPreset(m_d->resourceProvider->currentPreset());
m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->updateStroke();
}
}
diff --git a/libs/ui/widgets/kis_paintop_presets_popup.h b/libs/ui/widgets/kis_paintop_presets_popup.h
index 784753c8e0..406b2172ef 100644
--- a/libs/ui/widgets/kis_paintop_presets_popup.h
+++ b/libs/ui/widgets/kis_paintop_presets_popup.h
@@ -1,138 +1,134 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2008
* Copyright (C) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PAINTOP_PRESETS_POPUP_H
#define KIS_PAINTOP_PRESETS_POPUP_H
#include <QWidget>
#include <QList>
#include <KoID.h>
#include <kis_types.h>
#include <brushengine/kis_paintop_factory.h>
#include "../kis_paint_ops_model.h"
#include <widgets/kis_paintop_presets_save.h>
#include "widgets/kis_paintop_presets_popup.h"
#include "kis_favorite_resource_manager.h"
class QString;
class KisCanvasResourceProvider;
class KoResource;
/**
* Popup widget for presets with built-in functionality
* for adding and removing presets.
*/
class KisPaintOpPresetsPopup : public QWidget
{
Q_OBJECT
public:
KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider,
KisFavoriteResourceManager* favoriteResourceManager,
KisPresetSaveWidget* savePresetWidget,
QWidget * parent = 0);
~KisPaintOpPresetsPopup() override;
void setPaintOpSettingsWidget(QWidget * widget);
///Image for preset preview
///@return image cut out from the scratchpad
QImage cutOutOverlay();
void setPaintOpList(const QList<KisPaintOpFactory*>& list);
void setCurrentPaintOpId(const QString & paintOpId);
/// returns the internal ID for the paint op (brush engine)
QString currentPaintOpId();
///fill the cutoutOverlay rect with the cotent of an image, used to get the image back when selecting a preset
///@param image image that will be used, should be image of an existing preset resource
void setPresetImage(const QImage& image);
void resizeEvent(QResizeEvent* ) override;
bool detached() const;
void updateViewSettings();
void currentPresetChanged(KisPaintOpPresetSP preset);
KisPresetSaveWidget * saveDialog;
protected:
void contextMenuEvent(QContextMenuEvent *) override;
void hideEvent(QHideEvent *) override;
void showEvent(QShowEvent *) override;
public Q_SLOTS:
void switchDetached(bool show = true);
- void hideScratchPad();
- void showScratchPad();
void resourceSelected(KoResource* resource);
void updateThemedIcons();
void slotUpdatePresetSettings();
void slotUpdateLodAvailability();
void slotRenameBrushActivated();
void slotRenameBrushDeactivated();
void slotSaveRenameCurrentBrush();
Q_SIGNALS:
void savePresetClicked();
void saveBrushPreset();
void defaultPresetClicked();
void paintopActivated(const QString& presetName);
void signalResourceSelected(KoResource* resource);
void reloadPresetClicked();
void dirtyPresetToggled(bool value);
void eraserBrushSizeToggled(bool value);
void eraserBrushOpacityToggled(bool value);
void sizeChanged();
void brushEditorShown();
private Q_SLOTS:
void slotSwitchScratchpad(bool visible);
void slotResourceChanged(int key, const QVariant &value);
void slotLodAvailabilityChanged(bool value);
void slotSwitchShowEditor(bool visible);
void slotUpdatePaintOpFilter();
void slotSwitchShowPresets(bool visible);
void slotSaveBrushPreset();
void slotSaveNewBrushPreset();
private:
struct Private;
Private * const m_d;
QString current_paintOpId;
QList<KisPaintOpInfo> sortedBrushEnginesList;
void toggleBrushRenameUIActive(bool isRenaming);
- void calculateShowingTopArea();
-
};
#endif
diff --git a/libs/ui/widgets/kis_paintop_presets_save.cpp b/libs/ui/widgets/kis_paintop_presets_save.cpp
index 9f6e807ec4..174b6a46cf 100644
--- a/libs/ui/widgets/kis_paintop_presets_save.cpp
+++ b/libs/ui/widgets/kis_paintop_presets_save.cpp
@@ -1,228 +1,228 @@
/* This file is part of the KDE project
* Copyright (C) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "widgets/kis_paintop_presets_save.h"
#include <QDebug>
#include <QDate>
#include <QTime>
#include <KoFileDialog.h>
#include "KisImportExportManager.h"
#include "QDesktopServices"
#include "kis_resource_server_provider.h"
KisPresetSaveWidget::KisPresetSaveWidget(QWidget * parent)
: KisPaintOpPresetSaveDialog(parent)
{
// this is setting the area we will "capture" for saving the brush preset. It can potentially be a different
// area that the entire scratchpad
brushPresetThumbnailWidget->setCutoutOverlayRect(QRect(0, 0, brushPresetThumbnailWidget->height(), brushPresetThumbnailWidget->width()));
// we will default to reusing the previous preset thumbnail
// have that checked by default, hide the other elements, and load the last preset image
connect(clearBrushPresetThumbnailButton, SIGNAL(clicked(bool)), brushPresetThumbnailWidget, SLOT(fillDefault()));
connect(loadImageIntoThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadImageFromFile()));
connect(loadScratchPadThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadScratchpadThumbnail()));
connect(loadExistingThumbnailButton, SIGNAL(clicked(bool)), this, SLOT(loadExistingThumbnail()));
connect(savePresetButton, SIGNAL(clicked(bool)), this, SLOT(savePreset()));
connect(cancelButton, SIGNAL(clicked(bool)), this, SLOT(close()));
}
KisPresetSaveWidget::~KisPresetSaveWidget()
{
}
void KisPresetSaveWidget::scratchPadSetup(KisCanvasResourceProvider* resourceProvider)
{
m_resourceProvider = resourceProvider;
brushPresetThumbnailWidget->setupScratchPad(m_resourceProvider, Qt::white);
}
void KisPresetSaveWidget::showDialog()
{
setModal(true);
// set the name of the current brush preset area.
KisPaintOpPresetSP preset = m_resourceProvider->currentPreset();
// UI will look a bit different if we are saving a new brush
if (m_isSavingNewBrush) {
setWindowTitle(i18n("Save New Brush Preset"));
newBrushNameTexField->setVisible(true);
clearBrushPresetThumbnailButton->setVisible(true);
loadImageIntoThumbnailButton->setVisible(true);
currentBrushNameLabel->setVisible(false);
if (preset) {
newBrushNameTexField->setText(preset->name().append(" ").append(i18n("Copy")));
}
} else {
setWindowTitle(i18n("Save Brush Preset"));
if (preset) {
currentBrushNameLabel->setText(preset->name());
}
newBrushNameTexField->setVisible(false);
currentBrushNameLabel->setVisible(true);
}
brushPresetThumbnailWidget->paintPresetImage();
show();
}
void KisPresetSaveWidget::loadImageFromFile()
{
// create a dialog to retrieve an image file.
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
QString filename = dialog.filename(); // the filename() returns the entire path & file name, not just the file name
if (filename != "") { // empty if "cancel" is pressed
// take that file and load it into the thumbnail are
const QImage imageToLoad(filename);
brushPresetThumbnailWidget->fillTransparent(); // clear the background in case our new image has transparency
brushPresetThumbnailWidget->paintCustomImage(imageToLoad);
}
}
void KisPresetSaveWidget::loadScratchpadThumbnail()
{
brushPresetThumbnailWidget->paintCustomImage(scratchPadThumbnailArea);
}
void KisPresetSaveWidget::loadExistingThumbnail()
{
brushPresetThumbnailWidget->paintPresetImage();
}
void KisPresetSaveWidget::setFavoriteResourceManager(KisFavoriteResourceManager * favManager)
{
m_favoriteResourceManager = favManager;
}
void KisPresetSaveWidget::savePreset()
{
KisPaintOpPresetSP curPreset = m_resourceProvider->currentPreset();
if (!curPreset)
return;
m_favoriteResourceManager->setBlockUpdates(true);
KisPaintOpPresetSP oldPreset = curPreset->clone();
oldPreset->load();
KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer();
QString saveLocation = rServer->saveLocation();
// if we are saving a new brush, use what we type in for the input
QString presetName = m_isSavingNewBrush ? newBrushNameTexField->text() : curPreset->name();
QString currentPresetFileName = saveLocation + presetName + curPreset->defaultFileExtension();
// if the preset already exists, make a back up of it
if (rServer->resourceByName(presetName)) {
QString currentDate = QDate::currentDate().toString(Qt::ISODate);
QString currentTime = QTime::currentTime().toString(Qt::ISODate);
QString presetFilename = saveLocation + presetName + "_backup_" + currentDate + "-" + currentTime + oldPreset->defaultFileExtension();
oldPreset->setFilename(presetFilename);
oldPreset->setName(presetName);
oldPreset->setPresetDirty(false);
oldPreset->setValid(true);
// add resource to the blacklist
rServer->addResource(oldPreset);
rServer->removeResourceAndBlacklist(oldPreset.data());
QStringList tags;
tags = rServer->assignedTagsList(curPreset.data());
Q_FOREACH (const QString & tag, tags) {
rServer->addTag(oldPreset.data(), tag);
}
}
if (m_isSavingNewBrush) {
KisPaintOpPresetSP newPreset = curPreset->clone();
newPreset->setFilename(currentPresetFileName);
newPreset->setName(presetName);
newPreset->setImage(brushPresetThumbnailWidget->cutoutOverlay());
newPreset->setPresetDirty(false);
newPreset->setValid(true);
rServer->addResource(newPreset);
// trying to get brush preset to load after it is created
emit resourceSelected(newPreset.data());
} else {
if (curPreset->filename().contains(saveLocation)==false || curPreset->filename().contains(presetName)==false) {
rServer->removeResourceAndBlacklist(curPreset.data());
curPreset->setFilename(currentPresetFileName);
curPreset->setName(presetName);
}
if (!rServer->resourceByFilename(curPreset->filename())){
//this is necessary so that we can get the preset afterwards.
rServer->addResource(curPreset, false, false);
rServer->removeFromBlacklist(curPreset.data());
}
curPreset->setImage(brushPresetThumbnailWidget->cutoutOverlay());
curPreset->save();
curPreset->load();
}
// HACK ALERT! the server does not notify the observers
// automatically, so we need to call theupdate manually!
rServer->tagCategoryMembersChanged();
m_favoriteResourceManager->setBlockUpdates(false);
close(); // we are done... so close the save brush dialog
}
void KisPresetSaveWidget::saveScratchPadThumbnailArea(QImage image)
{
scratchPadThumbnailArea = image;
}
void KisPresetSaveWidget::isSavingNewBrush(bool newBrush)
{
m_isSavingNewBrush = newBrush;
}
#include "moc_kis_paintop_presets_save.cpp"
diff --git a/libs/ui/widgets/kis_preset_chooser.cpp b/libs/ui/widgets/kis_preset_chooser.cpp
index 9fa4e3e3b7..b3c1b9e81f 100644
--- a/libs/ui/widgets/kis_preset_chooser.cpp
+++ b/libs/ui/widgets/kis_preset_chooser.cpp
@@ -1,343 +1,355 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2009 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
* Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
* Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_preset_chooser.h"
#include <QVBoxLayout>
#include <QPainter>
#include <QAbstractItemDelegate>
#include <QStyleOptionViewItem>
#include <QSortFilterProxyModel>
#include <QApplication>
#include <klocalizedstring.h>
#include <KoIcon.h>
#include <KoResourceItemChooser.h>
#include <KoResourceModel.h>
#include <KoResourceServerAdapter.h>
#include <KoResourceItemChooserSync.h>
#include "KoResourceItemView.h"
#include <brushengine/kis_paintop_settings.h>
#include <brushengine/kis_paintop_preset.h>
#include "kis_resource_server_provider.h"
#include "kis_global.h"
#include "kis_slider_spin_box.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include <kis_icon.h>
/// The resource item delegate for rendering the resource preview
class KisPresetDelegate : public QAbstractItemDelegate
{
public:
KisPresetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent), m_showText(false), m_useDirtyPresets(false) {}
~KisPresetDelegate() override {}
/// reimplemented
void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override;
/// reimplemented
QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override {
return option.decorationSize;
}
void setShowText(bool showText) {
m_showText = showText;
}
void setUseDirtyPresets(bool value) {
m_useDirtyPresets = value;
}
private:
bool m_showText;
bool m_useDirtyPresets;
};
void KisPresetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
painter->save();
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
if (! index.isValid())
return;
KisPaintOpPreset* preset = static_cast<KisPaintOpPreset*>(index.internalPointer());
QImage preview = preset->image();
if(preview.isNull()) {
return;
}
QRect paintRect = option.rect.adjusted(1, 1, -1, -1);
if (!m_showText) {
painter->drawImage(paintRect.x(), paintRect.y(),
preview.scaled(paintRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else {
QSize pixSize(paintRect.height(), paintRect.height());
painter->drawImage(paintRect.x(), paintRect.y(),
preview.scaled(pixSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
// Put an asterisk after the preset if it is dirty. This will help in case the pixmap icon is too small
QString dirtyPresetIndicator = QString("");
if (m_useDirtyPresets && preset->isPresetDirty()) {
dirtyPresetIndicator = QString("*");
}
- painter->drawText(pixSize.width() + 10, option.rect.y() + option.rect.height() - 10, preset->name().append(dirtyPresetIndicator));
+ qreal brushSize = preset->settings()->paintOpSize();
+ QString brushSizeText;
+
+ // Disable displayed decimal precision beyond a certain brush size
+ if (brushSize < 100) {
+ brushSizeText = QString::number(brushSize, 'g', 3);
+ } else {
+ brushSizeText = QString::number(brushSize, 'f', 0);
+ }
+
+ painter->drawText(pixSize.width() + 10, option.rect.y() + option.rect.height() - 10, brushSizeText);
+ painter->drawText(pixSize.width() + 40, option.rect.y() + option.rect.height() - 10, preset->name().append(dirtyPresetIndicator));
+
}
if (m_useDirtyPresets && preset->isPresetDirty()) {
const QIcon icon = KisIconUtils::loadIcon(koIconName("dirty-preset"));
QPixmap pixmap = icon.pixmap(QSize(15,15));
painter->drawPixmap(paintRect.x() + 3, paintRect.y() + 3, pixmap);
}
if (!preset->settings() || !preset->settings()->isValid()) {
const QIcon icon = KisIconUtils::loadIcon("broken-preset");
icon.paint(painter, QRect(paintRect.x() + paintRect.height() - 25, paintRect.y() + paintRect.height() - 25, 25, 25));
}
if (option.state & QStyle::State_Selected) {
painter->setCompositionMode(QPainter::CompositionMode_HardLight);
painter->setOpacity(1.0);
painter->fillRect(option.rect, option.palette.highlight());
// highlight is not strong enough to pick out preset. draw border around it.
painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
painter->setPen(QPen(option.palette.highlight(), 4, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
QRect selectedBorder = option.rect.adjusted(2 , 2, -2, -2); // constrict the rectangle so it doesn't bleed into other presets
painter->drawRect(selectedBorder);
}
painter->restore();
}
class KisPresetProxyAdapter : public KisPaintOpPresetResourceServerAdapter
{
public:
KisPresetProxyAdapter(KisPaintOpPresetResourceServer* resourceServer)
: KisPaintOpPresetResourceServerAdapter(resourceServer)
{
setSortingEnabled(true);
}
~KisPresetProxyAdapter() override {}
QList< KoResource* > resources() override {
QList<KoResource*> serverResources =
KisPaintOpPresetResourceServerAdapter::resources();
if (m_paintopID.isEmpty()) {
return serverResources;
}
QList<KoResource*> resources;
Q_FOREACH (KoResource *resource, serverResources) {
KisPaintOpPreset *preset = dynamic_cast<KisPaintOpPreset*>(resource);
if (preset && preset->paintOp().id() == m_paintopID) {
resources.append(preset);
}
}
return resources;
}
///Set id for paintop to be accept by the proxy model, if not filter is set all
///presets will be shown.
void setPresetFilter(const QString& paintOpId)
{
m_paintopID = paintOpId;
invalidate();
}
///Resets the model connected to the adapter
void invalidate() {
emitRemovingResource(0);
}
QString currentPaintOpId() const {
return m_paintopID;
}
private:
QString m_paintopID;
};
KisPresetChooser::KisPresetChooser(QWidget *parent, const char *name)
: QWidget(parent)
{
setObjectName(name);
QVBoxLayout * layout = new QVBoxLayout(this);
layout->setMargin(0);
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false);
m_adapter = QSharedPointer<KoAbstractResourceServerAdapter>(new KisPresetProxyAdapter(rserver));
m_chooser = new KoResourceItemChooser(m_adapter, this);
m_chooser->setObjectName("ResourceChooser");
m_chooser->setColumnCount(10);
m_chooser->setRowHeight(50);
m_delegate = new KisPresetDelegate(this);
m_chooser->setItemDelegate(m_delegate);
m_chooser->setSynced(true);
layout->addWidget(m_chooser);
connect(m_chooser, SIGNAL(resourceSelected(KoResource*)),
this, SIGNAL(resourceSelected(KoResource*)));
connect(m_chooser, SIGNAL(resourceClicked(KoResource*)),
this, SIGNAL(resourceClicked(KoResource*)));
m_mode = THUMBNAIL;
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()),
SLOT(notifyConfigChanged()));
notifyConfigChanged();
}
KisPresetChooser::~KisPresetChooser()
{
}
void KisPresetChooser::showButtons(bool show)
{
m_chooser->showButtons(show);
}
void KisPresetChooser::setViewMode(KisPresetChooser::ViewMode mode)
{
m_mode = mode;
updateViewSettings();
}
void KisPresetChooser::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
updateViewSettings();
}
void KisPresetChooser::notifyConfigChanged()
{
KisConfig cfg;
m_delegate->setUseDirtyPresets(cfg.useDirtyPresets());
setIconSize(cfg.presetIconSize());
updateViewSettings();
}
void KisPresetChooser::updateViewSettings()
{
if (m_mode == THUMBNAIL) {
m_chooser->setSynced(true);
m_delegate->setShowText(false);
} else if (m_mode == DETAIL) {
m_chooser->setSynced(false);
m_chooser->setColumnCount(1);
m_chooser->setColumnWidth(m_chooser->width());
KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance();
m_chooser->setRowHeight(chooserSync->baseLength());
m_delegate->setShowText(true);
} else if (m_mode == STRIP) {
m_chooser->setSynced(false);
m_chooser->setRowCount(1);
m_chooser->itemView()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_chooser->itemView()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// An offset of 7 keeps the cell exactly square, TODO: use constants, not hardcoded numbers
m_chooser->setColumnWidth(m_chooser->viewSize().height() - 7);
m_delegate->setShowText(false);
}
}
void KisPresetChooser::setCurrentResource(KoResource *resource)
{
/**
* HACK ALERT: here we use a direct call to an adapter to notify the view
* that the preset might have changed its dirty state. This state
* doesn't affect the filtering so the server's cache must not be
* invalidated!
*
* Ideally, we should call some method of KoResourceServer instead,
* but ut seems like a bit too much effort for such a small fix.
*/
if (resource == currentResource()) {
KisPresetProxyAdapter *adapter = static_cast<KisPresetProxyAdapter*>(m_adapter.data());
KisPaintOpPreset *preset = dynamic_cast<KisPaintOpPreset*>(resource);
if (preset) {
adapter->resourceChangedNoCacheInvalidation(preset);
}
}
m_chooser->setCurrentResource(resource);
}
KoResource* KisPresetChooser::currentResource() const
{
return m_chooser->currentResource();
}
void KisPresetChooser::showTaggingBar(bool show)
{
m_chooser->showTaggingBar(show);
}
KoResourceItemChooser *KisPresetChooser::itemChooser()
{
return m_chooser;
}
void KisPresetChooser::setPresetFilter(const QString& paintOpId)
{
KisPresetProxyAdapter *adapter = static_cast<KisPresetProxyAdapter*>(m_adapter.data());
if (adapter->currentPaintOpId() != paintOpId) {
adapter->setPresetFilter(paintOpId);
updateViewSettings();
}
}
void KisPresetChooser::setIconSize(int newSize)
{
KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance();
chooserSync->setBaseLength(newSize);
updateViewSettings();
}
int KisPresetChooser::iconSize()
{
KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance();
return chooserSync->baseLength();
}
void KisPresetChooser::saveIconSize()
{
// save icon size
KisConfig cfg;
cfg.setPresetIconSize(iconSize());
}
diff --git a/libs/ui/widgets/kis_preset_live_preview_view.cpp b/libs/ui/widgets/kis_preset_live_preview_view.cpp
index 35496f655a..e194f71818 100644
--- a/libs/ui/widgets/kis_preset_live_preview_view.cpp
+++ b/libs/ui/widgets/kis_preset_live_preview_view.cpp
@@ -1,309 +1,265 @@
/*
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <kis_preset_live_preview_view.h>
#include <QDebug>
#include <QGraphicsPixmapItem>
#include "kis_paintop_settings.h"
+#include <strokes/freehand_stroke.h>
KisPresetLivePreviewView::KisPresetLivePreviewView(QWidget *parent): QGraphicsView(parent)
{
}
KisPresetLivePreviewView::~KisPresetLivePreviewView()
{
- delete m_brushPreviewPainter;
delete m_noPreviewText;
delete m_brushPreviewScene;
}
void KisPresetLivePreviewView::setup()
{
// initializing to 0 helps check later if they actually have something in them
m_noPreviewText = 0;
m_sceneImageItem = 0;
- setCursor(Qt::SizeAllCursor);
-
setHorizontalScrollBarPolicy ( Qt::ScrollBarAlwaysOff );
setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOff );
// layer image needs to be big enough to get an entire stroke of data
- m_canvasSize.setWidth(1200);
- m_canvasSize.setHeight(400);
+ 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);
- m_brushPreviewPainter = new KisPainter(m_layer->paintDevice());
// set scene for the view
m_brushPreviewScene = new QGraphicsScene();
setScene(m_brushPreviewScene);
- zoomToBrushSize();
}
void KisPresetLivePreviewView::setCurrentPreset(KisPaintOpPresetSP preset)
{
m_currentPreset = preset;
}
void KisPresetLivePreviewView::updateStroke()
{
paintBackground();
// 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") {
return;
}
setupAndPaintStroke();
// crop the layer so a brush stroke won't go outside of the area
m_layer->paintDevice()->crop(0,0, m_layer->image()->width(), m_layer->image()->height());
QImage m_temp_image;
m_temp_image = m_layer->paintDevice()->convertToQImage(0);
// 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));
- m_sceneImageItem->setFlag(QGraphicsItem::ItemIsSelectable);
- m_sceneImageItem->setFlag(QGraphicsItem::ItemIsMovable);
- m_sceneImageItem->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
- m_sceneImageItem->setPos(-m_canvasCenterPoint.x(), -m_canvasCenterPoint.y()); // center the object
} else {
m_sceneImageItem->setPixmap(QPixmap::fromImage(m_temp_image));
}
}
-void KisPresetLivePreviewView::slotResetViewZoom()
-{
- zoomToBrushSize();
-}
-void KisPresetLivePreviewView::slotZoomToOneHundredPercent()
-{
- m_scaleFactor = 1.0;
- resetMatrix();
- this->scale(m_scaleFactor, m_scaleFactor);
-}
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;
+ KoColor fillColor(m_layer->paintDevice()->colorSpace());
if (isAlternating) {
fillColor.fromQColor(QColor(80,80,80));
} else {
fillColor.fromQColor(QColor(140,140,140));
}
- m_brushPreviewPainter->fill(m_layer->image()->width()*sectionPercent*i,
- 0,
- m_layer->image()->width()*(sectionPercent*i +sectionPercent),
- m_layer->image()->height(),
- fillColor);
-
+ const QRect fillRect(m_layer->image()->width()*sectionPercent*i,
+ 0,
+ m_layer->image()->width()*(sectionPercent*i +sectionPercent),
+ m_layer->image()->height());
+ m_layer->paintDevice()->fill(fillRect, fillColor);
}
- m_brushPreviewPainter->setPaintColor(KoColor(Qt::white, m_colorSpace));
+ 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);
- slotZoomToOneHundredPercent(); // 100% zoom if we are showing the text
-
m_noPreviewText = this->scene()->addText(i18n("No Preview for this engine"),font);
- m_noPreviewText->setPos(-this->width()/3, -this->height()/4); // this mostly centers the text in the viewport
+ m_noPreviewText->setPos(50, this->height()/4);
return;
}
else {
// fill with gray first to clear out what existed from previous preview
- m_brushPreviewPainter->fill(0,0,
- m_layer->image()->width(),
- m_layer->image()->height(),
- KoColor(palette().color(QPalette::Background) , m_colorSpace));
-
- m_brushPreviewPainter->setPaintColor(KoColor(palette().color(QPalette::Text), m_colorSpace));
+ m_layer->paintDevice()->fill(m_image->bounds(), KoColor(palette().color(QPalette::Background) , m_colorSpace));
+ m_paintColor = KoColor(palette().color(QPalette::Text), m_colorSpace);
}
}
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 originalPresetSize = m_currentPreset->settings()->paintOpSize();
- qreal previewSize = qBound(1.0, m_currentPreset->settings()->paintOpSize(), 150.0 ); // constrain live preview brush size
+ qreal previewSize = qBound(3.0, m_currentPreset->settings()->paintOpSize(), 25.0 ); // constrain live preview brush size
KisPaintOpPresetSP proxy_preset = m_currentPreset->clone();
proxy_preset->settings()->setPaintOpSize(previewSize);
- m_brushPreviewPainter->setPaintOpPreset(proxy_preset, m_layer, m_image);
-
- // slope-intercept is good for mapping two values.
- // find the slope of the line (slope-intercept form)
- float slope = (m_maxScale-m_maxStrokeScale) / (m_minScale-m_minStrokeScale); // y2-y1 / x2-x1
- float yIntercept = m_maxStrokeScale - slope * m_minStrokeScale; // y1 − m * x1
- float strokeScaleAmount = m_scaleFactor * slope + yIntercept; // y = mx + b
- strokeScaleAmount = qBound(m_minStrokeScale, strokeScaleAmount, m_maxStrokeScale);
+ KisResourcesSnapshotSP resources =
+ new KisResourcesSnapshot(m_image,
+ m_layer);
+ resources->setBrush(proxy_preset);
+ resources->setFGColorOverride(m_paintColor);
+ FreehandStrokeStrategy::PainterInfo *painterInfo = new FreehandStrokeStrategy::PainterInfo();
+ KisStrokeStrategy *stroke =
+ new FreehandStrokeStrategy(resources->needsIndirectPainting(),
+ resources->indirectPaintingCompositeOp(),
+ resources, painterInfo, kundo2_noi18n("temp_stroke"));
+ KisStrokeId strokeId = m_image->startStroke(stroke);
- // we only need to change the zoom amount if we are changing the brush size
- if (m_currentBrushSize != m_currentPreset->settings()->paintOpSize()) {
- m_currentBrushSize = m_currentPreset->settings()->paintOpSize();
- zoomToBrushSize();
-
- }
+ //m_brushPreviewPainter->setPaintOpPreset(proxy_preset, m_layer, m_image);
- // points for drawing an S curve
- // we are going to paint the stroke right in the middle of the canvas to make sure
- // everything is captured for big brush strokes
+ // paint the stroke. The sketchbrush gets a differnet shape than the others to show how it works
if (m_currentPreset->paintOp().id() == "sketchbrush") {
- slotZoomToOneHundredPercent(); // sketch brush is always scaled at 100%
-
KisPaintInformation pointOne;
pointOne.setPressure(0.0);
pointOne.setPos(QPointF(m_canvasCenterPoint.x() - (this->width() * 0.4),
m_canvasCenterPoint.y() - (this->height()*0.2) ));
KisPaintInformation pointTwo;
pointTwo.setPressure(1.0);
pointTwo.setPos(QPointF(m_canvasCenterPoint.x() + (this->width() * 0.4),
m_canvasCenterPoint.y() + (this->height()*0.2) ));
- m_brushPreviewPainter->paintBezierCurve(pointOne,
- QPointF(m_canvasCenterPoint.x() + this->width(),
- m_canvasCenterPoint.y() - (this->height()*0.2) ),
- QPointF(m_canvasCenterPoint.x() - this->width(),
- m_canvasCenterPoint.y() + (this->height()*0.2) ),
- pointTwo, &m_currentDistance);
-
+ m_image->addJob(strokeId,
+ new FreehandStrokeStrategy::Data(0,
+ pointOne,
+ QPointF(m_canvasCenterPoint.x() + this->width(),
+ m_canvasCenterPoint.y() - (this->height()*0.2) ),
+ QPointF(m_canvasCenterPoint.x() - this->width(),
+ m_canvasCenterPoint.y() + (this->height()*0.2) ),
+ pointTwo));
} else {
- m_curvePointPI1.setPos(QPointF(m_canvasCenterPoint.x() - (this->width()*strokeScaleAmount),
- m_canvasCenterPoint.y() + (this->height()*strokeScaleAmount)));
+ // 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()*strokeScaleAmount),
- m_canvasCenterPoint.y()));
+ m_curvePointPI2.setPos(QPointF(m_canvasCenterPoint.x() + (this->width()*0.4),
+ m_canvasCenterPoint.y() - (this->height()*0.2) ));
m_curvePointPI2.setPressure(1.0);
- m_brushPreviewPainter->paintBezierCurve(m_curvePointPI1,
- QPointF(m_canvasCenterPoint.x(),
- m_canvasCenterPoint.y()-this->height()),
- QPointF(m_canvasCenterPoint.x(),
- m_canvasCenterPoint.y()+this->height()),
- m_curvePointPI2, &m_currentDistance);
+ 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 FreehandStrokeStrategy::UpdateData(true));
+ m_image->endStroke(strokeId);
+ m_image->waitForDone();
// 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);
}
-void KisPresetLivePreviewView::zoomToBrushSize()
-{
- // find the slope of the line (slope-intercept form)
- float slope = (m_maxScale-m_minScale) / (m_maxBrushVal-m_minBrushVal); // y2-y1 / x2-x1
- float yIntercept = m_minScale - slope * m_minBrushVal; // y1 − m * x1
-
-
- // finally calculate our zoom level
- float thresholdValue = qBound(m_minBrushVal, m_currentBrushSize, m_maxBrushVal);
- m_scaleFactor = thresholdValue * slope + yIntercept; // y = mx + b
-
- resetMatrix();
- this->scale(m_scaleFactor,m_scaleFactor);
-
- // reset position of image preview in case we moved it
- if(m_sceneImageItem) {
- m_sceneImageItem->setPos(-m_canvasSize.width()/2, -m_canvasSize.height()/2 );
- }
-}
-
diff --git a/libs/ui/widgets/kis_preset_live_preview_view.h b/libs/ui/widgets/kis_preset_live_preview_view.h
index f0a9b497f0..a09d1c366d 100644
--- a/libs/ui/widgets/kis_preset_live_preview_view.h
+++ b/libs/ui/widgets/kis_preset_live_preview_view.h
@@ -1,169 +1,150 @@
/*
* Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_PRESET_LIVE_PREVIEW_
#define _KIS_PRESET_LIVE_PREVIEW_
#include <QImage>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPainterPath>
#include <QGraphicsPixmapItem>
#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 <kis_image.h>
#include <kis_types.h>
+#include <KoColor.h>
/**
* 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();
/**
* @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 the current preset from the resource manager
*/
void setCurrentPreset(KisPaintOpPresetSP preset);
void updateStroke();
-public Q_SLOTS:
- /**
- * @brief scales the view to fit the brush stroke and moves it back to the center position
- */
- void slotResetViewZoom();
-
- /**
- * @brief sets the zoom level to full size to get a close up of larger brushes
- */
- void slotZoomToOneHundredPercent();
-
-
-
private:
/// 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;
- /// painter that actually paints the stroke
- KisPainter *m_brushPreviewPainter;
+ /// 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 internall if it has changed
float m_currentBrushSize = 1.0;
/// 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 reads the brush size and scales the view out to fit it
- * used internally when resetting the views or changing brush sizes
- */
- void zoomToBrushSize();
-
/**
* @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_scratch_pad.cpp b/libs/ui/widgets/kis_scratch_pad.cpp
index dc62ec1fc7..46ecce2397 100644
--- a/libs/ui/widgets/kis_scratch_pad.cpp
+++ b/libs/ui/widgets/kis_scratch_pad.cpp
@@ -1,509 +1,512 @@
/* This file is part of the KDE project
* Copyright 2010 (C) Boudewijn Rempt <boud@valdyas.org>
* Copyright 2011 (C) Dmitry Kazakov <dimula73@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_scratch_pad.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QMutex>
#include <KoColorSpace.h>
#include <KoColorProfile.h>
#include <KoColorSpaceRegistry.h>
#include <KoPointerEvent.h>
#include <resources/KoAbstractGradient.h>
#include <kis_cursor.h>
#include <kis_tool_utils.h>
#include <kis_paint_layer.h>
#include <kis_paint_device.h>
#include <kis_gradient_painter.h>
#include <kis_default_bounds.h>
#include <kis_canvas_resource_provider.h>
#include "kis_config.h"
#include "kis_image.h"
#include "kis_undo_stores.h"
#include "kis_update_scheduler.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_scratch_pad_event_filter.h"
#include "kis_painting_information_builder.h"
#include "kis_tool_freehand_helper.h"
#include "kis_image_patch.h"
#include "kis_canvas_widget_base.h"
#include "kis_layer_projection_plane.h"
#include "kis_node_graph_listener.h"
class KisScratchPadNodeListener : public KisNodeGraphListener
{
public:
KisScratchPadNodeListener(KisScratchPad *scratchPad)
: m_scratchPad(scratchPad)
{
}
- void requestProjectionUpdate(KisNode *node, const QRect& rect, bool resetAnimationCache) override {
- KisNodeGraphListener::requestProjectionUpdate(node, rect, resetAnimationCache);
+ void requestProjectionUpdate(KisNode *node, const QVector<QRect> &rects, bool resetAnimationCache) override {
+ KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache);
QMutexLocker locker(&m_lock);
- m_scratchPad->imageUpdated(rect);
+
+ Q_FOREACH (const QRect &rc, rects) {
+ m_scratchPad->imageUpdated(rc);
+ }
}
private:
KisScratchPad *m_scratchPad;
QMutex m_lock;
};
class KisScratchPadDefaultBounds : public KisDefaultBounds
{
public:
KisScratchPadDefaultBounds(KisScratchPad *scratchPad)
: m_scratchPad(scratchPad)
{
}
~KisScratchPadDefaultBounds() override {}
QRect bounds() const override {
return m_scratchPad->imageBounds();
}
private:
Q_DISABLE_COPY(KisScratchPadDefaultBounds)
KisScratchPad *m_scratchPad;
};
KisScratchPad::KisScratchPad(QWidget *parent)
: QWidget(parent)
, m_toolMode(HOVERING)
, m_paintLayer(0)
, m_displayProfile(0)
, m_resourceProvider(0)
{
setAutoFillBackground(false);
setMouseTracking(true);
m_cursor = KisCursor::load("tool_freehand_cursor.png", 5, 5);
setCursor(m_cursor);
KisConfig cfg;
QImage checkImage = KisCanvasWidgetBase::createCheckersImage(cfg.checkSize());
m_checkBrush = QBrush(checkImage);
// We are not supposed to use updates here,
// so just set the listener to null
m_updateScheduler = new KisUpdateScheduler(0);
m_undoStore = new KisSurrogateUndoStore();
m_undoAdapter = new KisPostExecutionUndoAdapter(m_undoStore, m_updateScheduler);
m_nodeListener = new KisScratchPadNodeListener(this);
connect(this, SIGNAL(sigUpdateCanvas(QRect)), SLOT(slotUpdateCanvas(QRect)), Qt::QueuedConnection);
// filter will be deleted by the QObject hierarchy
m_eventFilter = new KisScratchPadEventFilter(this);
m_infoBuilder = new KisPaintingInformationBuilder();
m_helper = new KisToolFreehandHelper(m_infoBuilder);
m_scaleBorderWidth = 1;
}
KisScratchPad::~KisScratchPad() {
delete m_helper;
delete m_infoBuilder;
delete m_undoAdapter;
delete m_undoStore;
delete m_updateScheduler;
delete m_nodeListener;
}
KisScratchPad::Mode KisScratchPad::modeFromButton(Qt::MouseButton button) const
{
return
button == Qt::NoButton ? HOVERING :
button == Qt::MidButton ? PANNING :
button == Qt::RightButton ? PICKING :
PAINTING;
}
void KisScratchPad::pointerPress(KoPointerEvent *event)
{
if (m_toolMode != HOVERING) return;
m_toolMode = modeFromButton(event->button());
if (m_toolMode == PAINTING) {
beginStroke(event);
event->accept();
}
else if (m_toolMode == PANNING) {
beginPan(event);
event->accept();
}
else if (m_toolMode == PICKING) {
pick(event);
event->accept();
}
}
void KisScratchPad::pointerRelease(KoPointerEvent *event)
{
if (modeFromButton(event->button()) != m_toolMode) return;
if (m_toolMode == PAINTING) {
endStroke(event);
m_toolMode = HOVERING;
event->accept();
}
else if (m_toolMode == PANNING) {
endPan(event);
m_toolMode = HOVERING;
event->accept();
}
else if (m_toolMode == PICKING) {
event->accept();
m_toolMode = HOVERING;
}
}
void KisScratchPad::pointerMove(KoPointerEvent *event)
{
m_helper->cursorMoved(documentToWidget().map(event->point));
if (m_toolMode == PAINTING) {
doStroke(event);
event->accept();
}
else if (m_toolMode == PANNING) {
doPan(event);
event->accept();
}
else if (m_toolMode == PICKING) {
pick(event);
event->accept();
}
}
void KisScratchPad::beginStroke(KoPointerEvent *event)
{
KoCanvasResourceManager *resourceManager = m_resourceProvider->resourceManager();
m_helper->initPaint(event,
documentToWidget().map(event->point),
resourceManager,
0,
0,
m_updateScheduler,
m_paintLayer,
m_paintLayer->paintDevice()->defaultBounds());
}
void KisScratchPad::doStroke(KoPointerEvent *event)
{
m_helper->paintEvent(event);
}
void KisScratchPad::endStroke(KoPointerEvent *event)
{
Q_UNUSED(event);
m_helper->endPaint();
}
void KisScratchPad::beginPan(KoPointerEvent *event)
{
setCursor(QCursor(Qt::ClosedHandCursor));
m_panDocPoint = event->point;
}
void KisScratchPad::doPan(KoPointerEvent *event)
{
QPointF docOffset = event->point - m_panDocPoint;
m_translateTransform.translate(-docOffset.x(), -docOffset.y());
updateTransformations();
update();
}
void KisScratchPad::endPan(KoPointerEvent *event)
{
Q_UNUSED(event);
setCursor(m_cursor);
}
void KisScratchPad::pick(KoPointerEvent *event)
{
KoColor color;
if (KisToolUtils::pick(m_paintLayer->projection(), event->point.toPoint(), &color)) {
emit colorSelected(color);
}
}
void KisScratchPad::setOnScreenResolution(qreal scaleX, qreal scaleY)
{
m_scaleBorderWidth = BORDER_SIZE(qMax(scaleX, scaleY));
m_scaleTransform = QTransform::fromScale(scaleX, scaleY);
updateTransformations();
update();
}
QTransform KisScratchPad::documentToWidget() const
{
return m_translateTransform.inverted() * m_scaleTransform;
}
QTransform KisScratchPad::widgetToDocument() const
{
return m_scaleTransform.inverted() * m_translateTransform;
}
void KisScratchPad::updateTransformations()
{
m_eventFilter->setWidgetToDocumentTransform(widgetToDocument());
}
QRect KisScratchPad::imageBounds() const
{
return widgetToDocument().mapRect(rect());
}
void KisScratchPad::imageUpdated(const QRect &rect)
{
emit sigUpdateCanvas(documentToWidget().mapRect(QRectF(rect)).toAlignedRect());
}
void KisScratchPad::slotUpdateCanvas(const QRect &rect)
{
update(rect);
}
void KisScratchPad::paintEvent ( QPaintEvent * event ) {
if(!m_paintLayer) return;
QRectF imageRect = widgetToDocument().mapRect(QRectF(event->rect()));
QRect alignedImageRect =
imageRect.adjusted(-m_scaleBorderWidth, -m_scaleBorderWidth,
m_scaleBorderWidth, m_scaleBorderWidth).toAlignedRect();
QPointF offset = alignedImageRect.topLeft();
m_paintLayer->projectionPlane()->recalculate(alignedImageRect, m_paintLayer);
KisPaintDeviceSP projection = m_paintLayer->projection();
QImage image = projection->convertToQImage(m_displayProfile,
alignedImageRect.x(),
alignedImageRect.y(),
alignedImageRect.width(),
alignedImageRect.height(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
QPainter gc(this);
gc.fillRect(event->rect(), m_checkBrush);
gc.setRenderHints(QPainter::SmoothPixmapTransform);
gc.drawImage(QRectF(event->rect()), image, imageRect.translated(-offset));
QBrush brush(Qt::lightGray);
QPen pen(brush, 1, Qt::DotLine);
gc.setPen(pen);
if (m_cutoutOverlay.isValid()) {
gc.drawRect(m_cutoutOverlay);
}
if(!isEnabled()) {
QColor color(Qt::lightGray);
color.setAlphaF(0.5);
QBrush disabledBrush(color);
gc.fillRect(event->rect(), disabledBrush);
}
gc.end();
}
void KisScratchPad::setupScratchPad(KisCanvasResourceProvider* resourceProvider,
const QColor &defaultColor)
{
m_resourceProvider = resourceProvider;
KisConfig cfg;
setDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this)));
connect(m_resourceProvider, SIGNAL(sigDisplayProfileChanged(const KoColorProfile*)),
SLOT(setDisplayProfile(const KoColorProfile*)));
connect(m_resourceProvider, SIGNAL(sigOnScreenResolutionChanged(qreal,qreal)),
SLOT(setOnScreenResolution(qreal,qreal)));
connect(this, SIGNAL(colorSelected(const KoColor&)),
m_resourceProvider, SLOT(slotSetFGColor(const KoColor&)));
m_defaultColor = KoColor(defaultColor, KoColorSpaceRegistry::instance()->rgb8());
KisPaintDeviceSP paintDevice =
new KisPaintDevice(m_defaultColor.colorSpace(), "scratchpad");
m_paintLayer = new KisPaintLayer(0, "ScratchPad", OPACITY_OPAQUE_U8, paintDevice);
m_paintLayer->setGraphListener(m_nodeListener);
m_paintLayer->paintDevice()->setDefaultBounds(new KisScratchPadDefaultBounds(this));
fillDefault();
}
void KisScratchPad::setCutoutOverlayRect(const QRect& rc)
{
m_cutoutOverlay = rc;
}
QImage KisScratchPad::cutoutOverlay() const
{
if(!m_paintLayer) return QImage();
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QRect rc = widgetToDocument().mapRect(m_cutoutOverlay);
QImage rawImage = paintDevice->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
QImage scaledImage = rawImage.scaled(m_cutoutOverlay.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
return scaledImage;
}
void KisScratchPad::setPresetImage(const QImage& image)
{
m_presetImage = image;
}
void KisScratchPad::paintCustomImage(const QImage& loadedImage)
{
// this is 99% copied from the normal paintPresetImage()
// we dont' want to save over the preset image, so we don't
// want to store it in the m_presetImage
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay);
QRect imageRect(QPoint(), overlayRect.size());
QImage scaledImage = loadedImage.scaled(overlayRect.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace());
device->convertFromQImage(scaledImage, 0);
KisPainter painter(paintDevice);
painter.bitBlt(overlayRect.topLeft(), device, imageRect);
update();
}
void KisScratchPad::paintPresetImage()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay);
QRect imageRect(QPoint(), overlayRect.size());
QImage scaledImage = m_presetImage.scaled(overlayRect.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace());
device->convertFromQImage(scaledImage, 0);
KisPainter painter(paintDevice);
painter.bitBlt(overlayRect.topLeft(), device, imageRect);
update();
}
void KisScratchPad::setDisplayProfile(const KoColorProfile *colorProfile)
{
if (colorProfile) {
m_displayProfile = colorProfile;
QWidget::update();
}
}
void KisScratchPad::fillDefault()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
paintDevice->setDefaultPixel(m_defaultColor);
paintDevice->clear();
update();
}
void KisScratchPad::fillTransparent() {
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QColor transQColor(0,0,0,0);
KoColor transparentColor(transQColor, KoColorSpaceRegistry::instance()->rgb8());
transparentColor.setOpacity(0.0);
paintDevice->setDefaultPixel(transparentColor);
paintDevice->clear();
update();
}
void KisScratchPad::fillGradient()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
KoAbstractGradient* gradient = m_resourceProvider->currentGradient();
QRect gradientRect = widgetToDocument().mapRect(rect());
paintDevice->clear();
KisGradientPainter painter(paintDevice);
painter.setGradient(gradient);
painter.setGradientShape(KisGradientPainter::GradientShapeLinear);
painter.paintGradient(gradientRect.topLeft(),
gradientRect.bottomRight(),
KisGradientPainter::GradientRepeatNone,
0.2, false,
gradientRect.left(), gradientRect.top(),
gradientRect.width(), gradientRect.height());
update();
}
void KisScratchPad::fillBackground()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
paintDevice->setDefaultPixel(m_resourceProvider->bgColor());
paintDevice->clear();
update();
}
void KisScratchPad::fillLayer()
{
// TODO
}
diff --git a/libs/ui/widgets/kis_scratch_pad_event_filter.cpp b/libs/ui/widgets/kis_scratch_pad_event_filter.cpp
index 50b637a797..00321060ab 100644
--- a/libs/ui/widgets/kis_scratch_pad_event_filter.cpp
+++ b/libs/ui/widgets/kis_scratch_pad_event_filter.cpp
@@ -1,112 +1,112 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_scratch_pad_event_filter.h"
#include "kis_scratch_pad.h"
#include <QWidget>
#include <QDebug>
KisScratchPadEventFilter::KisScratchPadEventFilter(QWidget *parent)
: QObject(parent),
m_tabletPressed(false)
{
parent->installEventFilter(this);
m_scratchPad = qobject_cast<KisScratchPad *>(parent);
}
void KisScratchPadEventFilter::setWidgetToDocumentTransform(const QTransform &transform)
{
m_widgetToDocument = transform;
}
QWidget* KisScratchPadEventFilter::parentWidget()
{
return static_cast<QWidget*>(parent());
}
KoPointerEvent* KisScratchPadEventFilter::createMouseEvent(QEvent *event)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
return new KoPointerEvent(mouseEvent, m_widgetToDocument.map(mouseEvent->pos()));
}
KoPointerEvent* KisScratchPadEventFilter::createTabletEvent(QEvent *event)
{
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
- const QPointF pos = tabletEvent->hiResGlobalPos();
+ const QPointF pos = tabletEvent->posF();
KoPointerEvent *ev = new KoPointerEvent(tabletEvent, m_widgetToDocument.map(pos));
ev->setTabletButton(Qt::LeftButton);
return ev;
}
bool KisScratchPadEventFilter::eventFilter(QObject *obj, QEvent *event)
{
Q_UNUSED(obj);
bool result = true;
KoPointerEvent *ev = 0;
switch(event->type()) {
case QEvent::MouseButtonPress:
if(m_tabletPressed) break;
ev = createMouseEvent(event);
m_scratchPad->pointerPress(ev);
break;
case QEvent::MouseButtonRelease:
if(m_tabletPressed) break;
ev = createMouseEvent(event);
m_scratchPad->pointerRelease(ev);
break;
case QEvent::MouseMove:
if(m_tabletPressed) break;
ev = createMouseEvent(event);
m_scratchPad->pointerMove(ev);
break;
case QEvent::TabletPress:
// if(m_tabletPressed) break;
m_tabletPressed = true;
ev = createTabletEvent(event);
m_scratchPad->pointerPress(ev);
break;
case QEvent::TabletRelease:
// if(!m_tabletPressed) break;
m_tabletPressed = false;
ev = createTabletEvent(event);
m_scratchPad->pointerRelease(ev);
break;
case QEvent::TabletMove:
// if(!m_tabletPressed) break;
ev = createTabletEvent(event);
m_scratchPad->pointerMove(ev);
break;
default:
result = false;
}
if(ev) {
result = ev->isAccepted();
event->setAccepted(result);
delete ev;
}
return result;
}
diff --git a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp
index 9ffcb95bed..fc5b52cf74 100644
--- a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp
+++ b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp
@@ -1,360 +1,360 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* 2016 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "widgets/kis_stopgradient_slider_widget.h"
#include <QWindow>
#include <QPainter>
#include <QPixmap>
#include <QMouseEvent>
#include <QPolygon>
#include <QPaintEvent>
#include <QFontMetrics>
#include <QStyle>
#include <QApplication>
#include <QStyleOptionToolButton>
#include "kis_global.h"
#include "kis_debug.h"
#include "krita_utils.h"
-KisStopGradientSliderWidget::KisStopGradientSliderWidget(QWidget *parent, Qt::WFlags f)
+KisStopGradientSliderWidget::KisStopGradientSliderWidget(QWidget *parent, Qt::WindowFlags f)
: QWidget(parent, f)
, m_selectedStop(0)
, m_drag(0)
{
QLinearGradient defaultGradient;
m_defaultGradient.reset(KoStopGradient::fromQGradient(&defaultGradient));
setGradientResource(0);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
setMouseTracking(true);
QWindow *window = this->window()->windowHandle();
if (window) {
connect(window, SIGNAL(screenChanged(QScreen*)), SLOT(updateHandleSize()));
}
updateHandleSize();
}
void KisStopGradientSliderWidget::updateHandleSize()
{
QFontMetrics fm(font());
const int h = fm.height();
m_handleSize = QSize(0.34 * h, h);
}
int KisStopGradientSliderWidget::handleClickTolerance() const
{
// the size of the default text!
return m_handleSize.height();
}
void KisStopGradientSliderWidget::setGradientResource(KoStopGradient* gradient)
{
m_gradient = gradient ? gradient : m_defaultGradient.data();
if (m_selectedStop >= 0) {
m_selectedStop = qBound(0, m_selectedStop, m_gradient->stops().size() - 1);
emit sigSelectedStop(m_selectedStop);
}
}
void KisStopGradientSliderWidget::paintHandle(qreal position, const QColor &color, bool isSelected, QPainter *painter)
{
const QRect handlesRect = this->handlesStipeRect();
const int handleCenter = handlesRect.left() + position * handlesRect.width();
const int handlesHalfWidth = handlesRect.height() * 0.26; // = 1.0 / 0.66 * 0.34 / 2.0 <-- golden ratio
QPolygon triangle(3);
triangle[0] = QPoint(handleCenter, handlesRect.top());
triangle[1] = QPoint(handleCenter - handlesHalfWidth, handlesRect.bottom());
triangle[2] = QPoint(handleCenter + handlesHalfWidth, handlesRect.bottom());
const qreal lineWidth = 1.0;
if (!isSelected) {
painter->setPen(QPen(palette().text(), lineWidth));
painter->setBrush(QBrush(color));
painter->setRenderHint(QPainter::Antialiasing);
painter->drawPolygon(triangle);
} else {
painter->setPen(QPen(palette().highlight(), 1.5 * lineWidth));
painter->setBrush(QBrush(color));
painter->setRenderHint(QPainter::Antialiasing);
painter->drawPolygon(triangle);
}
}
void KisStopGradientSliderWidget::paintEvent(QPaintEvent* pe)
{
QWidget::paintEvent(pe);
QPainter painter(this);
painter.setPen(Qt::black);
const QRect previewRect = gradientStripeRect();
KritaUtils::renderExactRect(&painter, kisGrowRect(previewRect, 1));
painter.drawRect(previewRect);
if (m_gradient) {
QImage image = m_gradient->generatePreview(previewRect.width(), previewRect.height());
if (!image.isNull()) {
painter.drawImage(previewRect.topLeft(), image);
}
QList<KoGradientStop> handlePositions = m_gradient->stops();
for (int i = 0; i < handlePositions.count(); i++) {
if (i == m_selectedStop) continue;
paintHandle(handlePositions[i].first,
handlePositions[i].second.toQColor(),
false, &painter);
}
if (m_selectedStop >= 0) {
paintHandle(handlePositions[m_selectedStop].first,
handlePositions[m_selectedStop].second.toQColor(),
true, &painter);
}
}
}
int findNearestHandle(qreal t, const qreal tolerance, const QList<KoGradientStop> &stops)
{
int result = -1;
qreal minDistance = tolerance;
for (int i = 0; i < stops.size(); i++) {
const KoGradientStop &stop = stops[i];
const qreal distance = qAbs(t - stop.first);
if (distance < minDistance) {
minDistance = distance;
result = i;
}
}
return result;
}
void KisStopGradientSliderWidget::mousePressEvent(QMouseEvent * e)
{
if (!allowedClickRegion(handleClickTolerance()).contains(e->pos())) {
QWidget::mousePressEvent(e);
return;
}
const QRect handlesRect = this->handlesStipeRect();
const qreal t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width();
const QList<KoGradientStop> stops = m_gradient->stops();
const int clickedStop = findNearestHandle(t, qreal(handleClickTolerance()) / handlesRect.width(), stops);
if (clickedStop >= 0) {
if (m_selectedStop != clickedStop) {
m_selectedStop = clickedStop;
emit sigSelectedStop(m_selectedStop);
}
m_drag = true;
} else {
insertStop(qBound(0.0, t, 1.0));
m_drag = true;
}
update();
updateCursor(e->pos());
}
void KisStopGradientSliderWidget::mouseReleaseEvent(QMouseEvent * e)
{
Q_UNUSED(e);
m_drag = false;
updateCursor(e->pos());
}
int getNewInsertPosition(const KoGradientStop &stop, const QList<KoGradientStop> &stops)
{
int result = 0;
for (int i = 0; i < stops.size(); i++) {
if (stop.first <= stops[i].first) break;
result = i + 1;
}
return result;
}
void KisStopGradientSliderWidget::mouseMoveEvent(QMouseEvent * e)
{
updateCursor(e->pos());
if (m_drag) {
const QRect handlesRect = this->handlesStipeRect();
double t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width();
QList<KoGradientStop> stops = m_gradient->stops();
if (t < -0.1 || t > 1.1) {
if (stops.size() > 2 && m_selectedStop >= 0) {
m_removedStop = stops[m_selectedStop];
stops.removeAt(m_selectedStop);
m_selectedStop = -1;
}
} else {
if (m_selectedStop < 0) {
m_removedStop.first = qBound(0.0, t, 1.0);
const int newPos = getNewInsertPosition(m_removedStop, stops);
stops.insert(newPos, m_removedStop);
m_selectedStop = newPos;
} else {
KoGradientStop draggedStop = stops[m_selectedStop];
draggedStop.first = qBound(0.0, t, 1.0);
stops.removeAt(m_selectedStop);
const int newPos = getNewInsertPosition(draggedStop, stops);
stops.insert(newPos, draggedStop);
m_selectedStop = newPos;
}
}
m_gradient->setStops(stops);
emit sigSelectedStop(m_selectedStop);
update();
} else {
QWidget::mouseMoveEvent(e);
}
}
void KisStopGradientSliderWidget::updateCursor(const QPoint &pos)
{
const bool isInAllowedRegion =
allowedClickRegion(handleClickTolerance()).contains(pos);
QCursor currentCursor;
if (isInAllowedRegion) {
const QRect handlesRect = this->handlesStipeRect();
const qreal t = (qreal(pos.x()) - handlesRect.x()) / handlesRect.width();
const QList<KoGradientStop> stops = m_gradient->stops();
const int clickedStop = findNearestHandle(t, qreal(handleClickTolerance()) / handlesRect.width(), stops);
if (clickedStop >= 0) {
currentCursor = m_drag ? Qt::ClosedHandCursor : Qt::OpenHandCursor;
}
}
if (currentCursor.shape() != Qt::ArrowCursor) {
setCursor(currentCursor);
} else {
unsetCursor();
}
}
void KisStopGradientSliderWidget::insertStop(double t)
{
KIS_ASSERT_RECOVER(t >= 0 && t <= 1.0 ) {
t = qBound(0.0, t, 1.0);
}
QList<KoGradientStop> stops = m_gradient->stops();
KoColor color;
m_gradient->colorAt(color, t);
const KoGradientStop stop(t, color);
const int newPos = getNewInsertPosition(stop, stops);
stops.insert(newPos, stop);
m_gradient->setStops(stops);
m_selectedStop = newPos;
emit sigSelectedStop(m_selectedStop);
}
QRect KisStopGradientSliderWidget::sliderRect() const
{
return QRect(QPoint(), size()).adjusted(m_handleSize.width(), 1, -m_handleSize.width(), -1);
}
QRect KisStopGradientSliderWidget::gradientStripeRect() const
{
const QRect rc = sliderRect();
return rc.adjusted(0, 0, 0, -m_handleSize.height());
}
QRect KisStopGradientSliderWidget::handlesStipeRect() const
{
const QRect rc = sliderRect();
return rc.adjusted(0, rc.height() - m_handleSize.height(), 0, 0);
}
QRegion KisStopGradientSliderWidget::allowedClickRegion(int tolerance) const
{
QRegion result;
result += sliderRect();
result += handlesStipeRect().adjusted(-tolerance, 0, tolerance, 0);
return result;
}
int KisStopGradientSliderWidget::selectedStop()
{
return m_selectedStop;
}
void KisStopGradientSliderWidget::setSelectedStop(int selected)
{
m_selectedStop = selected;
emit sigSelectedStop(m_selectedStop);
update();
}
int KisStopGradientSliderWidget::minimalHeight() const
{
QFontMetrics fm(font());
const int h = fm.height();
QStyleOptionToolButton opt;
QSize sz = (style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(h, h), this).
expandedTo(QApplication::globalStrut()));
return sz.height() + m_handleSize.height();
}
QSize KisStopGradientSliderWidget::sizeHint() const
{
const int h = minimalHeight();
return QSize(2 * h, h);
}
QSize KisStopGradientSliderWidget::minimumSizeHint() const
{
const int h = minimalHeight();
return QSize(h, h);
}
diff --git a/libs/ui/widgets/kis_stopgradient_slider_widget.h b/libs/ui/widgets/kis_stopgradient_slider_widget.h
index b7d5c91f87..f00d7f83c4 100644
--- a/libs/ui/widgets/kis_stopgradient_slider_widget.h
+++ b/libs/ui/widgets/kis_stopgradient_slider_widget.h
@@ -1,82 +1,82 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* 2016 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_STOP_GRADIENT_SLIDER_WIDGET_H_
#define _KIS_STOP_GRADIENT_SLIDER_WIDGET_H_
#include <QWidget>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QScopedPointer>
#include <resources/KoStopGradient.h>
class KisStopGradientSliderWidget : public QWidget
{
Q_OBJECT
public:
- KisStopGradientSliderWidget(QWidget *parent = 0, Qt::WFlags f = 0);
+ KisStopGradientSliderWidget(QWidget *parent = 0, Qt::WindowFlags f = 0);
public:
void paintEvent(QPaintEvent *) override;
void setGradientResource(KoStopGradient* gradient);
int selectedStop();
void setSelectedStop(int selected);
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
Q_SIGNALS:
void sigSelectedStop(int stop);
protected:
void mousePressEvent(QMouseEvent * e) override;
void mouseReleaseEvent(QMouseEvent * e) override;
void mouseMoveEvent(QMouseEvent * e) override;
private Q_SLOTS:
void updateHandleSize();
private:
void insertStop(double t);
QRect sliderRect() const;
QRect gradientStripeRect() const;
QRect handlesStipeRect() const;
QRegion allowedClickRegion(int tolerance) const;
void updateCursor(const QPoint &pos);
void paintHandle(qreal position, const QColor &color, bool isSelected, QPainter *painter);
int handleClickTolerance() const;
int minimalHeight() const;
private:
QScopedPointer<KoStopGradient> m_defaultGradient;
KoStopGradient* m_gradient;
int m_selectedStop;
KoGradientStop m_removedStop;
bool m_drag;
QSize m_handleSize;
};
#endif
diff --git a/libs/vectorimage/libwmf/WmfParser.cpp b/libs/vectorimage/libwmf/WmfParser.cpp
index f2f31e328c..6539f5cd7e 100644
--- a/libs/vectorimage/libwmf/WmfParser.cpp
+++ b/libs/vectorimage/libwmf/WmfParser.cpp
@@ -1,1695 +1,1695 @@
/* This file is part of the KDE libraries
*
* Copyright (c) 1998 Stefan Taferner
* 2001/2003 thierry lorthiois (lorthioist@wanadoo.fr)
* 2007-2008 Jan Hambrecht <jaham@gmx.net>
* 2009-2011 Inge Wallin <inge@lysator.liu.se>
* With the help of WMF documentation by Caolan Mc Namara
*
* 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 "WmfParser.h"
#include "WmfAbstractBackend.h"
#include <VectorImageDebug.h>
#include <QImage>
#include <QMatrix>
#include <QDataStream>
#include <QByteArray>
#include <QBuffer>
#include <QPolygon>
#include <math.h>
#define DEBUG_BBOX 0
#define DEBUG_RECORDS 0
/**
Namespace for Windows Metafile (WMF) classes
*/
namespace Libwmf
{
#if defined(DEBUG_RECORDS)
// Used for debugging of records
static const struct KoWmfFunc {
const char *name;
} koWmfFunc[] = {
// index metafunc
{ "end" }, // 0 0x00
{ "setBkColor" }, // 1 0x01
{ "setBkMode" }, // 2 0x02
{ "setMapMode" }, // 3 0x03
{ "setRop" }, // 4 0x04
{ "setRelAbs" }, // 5 0x05
{ "setPolyFillMode" }, // 6 0x06
{ "setStretchBltMode" }, // 7 0x07
{ "setTextCharExtra" }, // 8 0x08
{ "setTextColor" }, // 9 0x09
{ "setTextJustification" }, // 10 0x0a
{ "setWindowOrg" }, // 11 0x0b
{ "setWindowExt" }, // 12 0x0c
{ "setViewportOrg" }, // 13 0x0d
{ "setViewportExt" }, // 14 0x0e
{ "offsetWindowOrg" }, // 15 0x0f
{ "scaleWindowExt" }, // 16 0x10
{ "offsetViewportOrg" }, // 17 0x11
{ "scaleViewportExt" }, // 18 0x12
{ "lineTo" }, // 19 0x13
{ "moveTo" }, // 20 0x14
{ "excludeClipRect" }, // 21 0x15
{ "intersectClipRect" }, // 22 0x16
{ "arc" }, // 23 0x17
{ "ellipse" }, // 24 0x18
{ "floodfill" }, // 25 0x19 floodfill
{ "pie" }, // 26 0x1a
{ "rectangle" }, // 27 0x1b
{ "roundRect" }, // 28 0x1c
{ "patBlt" }, // 29 0x1d
{ "saveDC" }, // 30 0x1e
{ "setPixel" }, // 31 0x1f
{ "offsetClipRegion" }, // 32 0x20
{ "textOut" }, // 33 0x21
{ "bitBlt" }, // 34 0x22
{ "stretchBlt" }, // 35 0x23
{ "polygon" }, // 36 0x24
{ "polyline" }, // 37 0x25
{ "escape" }, // 38 0x26
{ "restoreDC" }, // 39 0x27
{ "fillRegion" }, // 40 0x28
{ "frameRegion" }, // 41 0x29
{ "invertRegion" }, // 42 0x2a
{ "paintRegion" }, // 43 0x2b
{ "selectClipRegion" }, // 44 0x2c
{ "selectObject" }, // 45 0x2d
{ "setTextAlign" }, // 46 0x2e
{ "noSuchRecord" }, // 47 0x2f
{ "chord" }, // 48 0x30
{ "setMapperFlags" }, // 49 0x31
{ "extTextOut" }, // 50 0x32
{ "setDibToDev" }, // 51 0x33
{ "selectPalette" }, // 52 0x34
{ "realizePalette" }, // 53 0x35
{ "animatePalette" }, // 54 0x36
{ "setPalEntries" }, // 55 0x37
{ "polyPolygon" }, // 56 0x38
{ "resizePalette" }, // 57 0x39
{ "noSuchRecord" }, // 58 0x3a
{ "noSuchRecord" }, // 59 0x3b
{ "noSuchRecord" }, // 60 0x3c
{ "noSuchRecord" }, // 61 0x3d
{ "noSuchRecord" }, // 62 0x3e
{ "unimplemented" }, // 63 0x3f
{ "dibBitBlt" }, // 64 0x40
{ "dibStretchBlt" }, // 65 0x41
{ "dibCreatePatternBrush" }, // 66 0x42
{ "stretchDib" }, // 67 0x43
{ "noSuchRecord" }, // 68 0x44
{ "noSuchRecord" }, // 69 0x45
{ "noSuchRecord" }, // 70 0x46
{ "noSuchRecord" }, // 71 0x47
{ "extFloodFill" }, // 72 0x48
{ "setLayout" }, // 73 0x49
{ "unimplemented" }, // 74 0x4a
{ "unimplemented" }, // 75 0x4b
{ "resetDC" }, // 76 0x4c
{ "startDoc" }, // 77 0x4d
{ "unimplemented" }, // 78 0x4e
{ "startPage" }, // 79 0x4f
{ "endPage" }, // 80 0x50
{ "unimplemented" }, // 81 0x51
{ "unimplemented" }, // 82 0x52
{ "unimplemented" }, // 83 0x53
{ "unimplemented" }, // 84 0x54
{ "unimplemented" }, // 85 0x55
{ "unimplemented" }, // 86 0x56
{ "unimplemented" }, // 87 0x57
{ "unimplemented" }, // 88 0x58
{ "unimplemented" }, // 89 0x59
{ "unimplemented" }, // 90 0x5a
{ "unimplemented" }, // 91 0x5b
{ "unimplemented" }, // 92 0x5c
{ "unimplemented" }, // 93 0x5d
{ "endDoc" }, // 94 0x5e
{ "unimplemented" }, // 95 0x5f
{ "deleteObject" }, // 96 0xf0
{ "noSuchRecord" }, // 97 0xf1
{ "noSuchRecord" }, // 98 0xf2
{ "noSuchRecord" }, // 99 0xf3
{ "noSuchRecord" }, // 100 0xf4
{ "noSuchRecord" }, // 101 0xf5
{ "noSuchRecord" }, // 102 0xf6
{ "createPalette" }, // 103 0xf7
{ "createBrush" }, // 104 0xf8
{ "createPatternBrush" }, // 105 0xf9
{ "createPenIndirect" }, // 106 0xfa
{ "createFontIndirect" }, // 107 0xfb
{ "createBrushIndirect" }, //108 0xfc
{ "createBitmapIndirect" }, //109 0xfd
{ "createBitmap" }, // 110 0xfe
{ "createRegion" } // 111 0xff
};
#endif
WmfParser::WmfParser()
{
mNbrFunc = 0;
mValid = false;
mStandard = false;
mPlaceable = false;
mEnhanced = false;
mBuffer = 0;
mObjHandleTab = 0;
}
WmfParser::~WmfParser()
{
if (mObjHandleTab != 0) {
for (int i = 0 ; i < mNbrObject ; i++) {
if (mObjHandleTab[i] != 0)
delete mObjHandleTab[i];
}
delete[] mObjHandleTab;
}
if (mBuffer != 0) {
mBuffer->close();
delete mBuffer;
}
}
bool WmfParser::load(const QByteArray& array)
{
// delete previous buffer
if (mBuffer != 0) {
mBuffer->close();
delete mBuffer;
mBuffer = 0;
}
if (array.size() == 0)
return false;
// load into buffer
mBuffer = new QBuffer();
mBuffer->setData(array);
mBuffer->open(QIODevice::ReadOnly);
// read and check the header
WmfMetaHeader header;
WmfEnhMetaHeader eheader;
WmfPlaceableHeader pheader; // Contains a bounding box
unsigned short checksum;
int filePos;
QDataStream stream(mBuffer);
stream.setByteOrder(QDataStream::LittleEndian);
mStackOverflow = false;
mLayout = LAYOUT_LTR;
mTextColor = Qt::black;
mMapMode = MM_ANISOTROPIC;
mValid = false;
mStandard = false;
mPlaceable = false;
mEnhanced = false;
// Initialize the bounding box.
//mBBoxTop = 0; // The default origin is (0, 0).
//mBBoxLeft = 0;
mBBoxTop = 32767;
mBBoxLeft = 32767;
mBBoxRight = -32768;
mBBoxBottom = -32768;
mMaxWidth = 0;
mMaxHeight = 0;
#if DEBUG_RECORDS
debugVectorImage << "--------------------------- Starting parsing WMF ---------------------------";
#endif
stream >> pheader.key;
if (pheader.key == (quint32)APMHEADER_KEY) {
//----- Read placeable metafile header
mPlaceable = true;
#if DEBUG_RECORDS
debugVectorImage << "Placeable header! Yessss!";
#endif
stream >> pheader.handle;
stream >> pheader.left;
stream >> pheader.top;
stream >> pheader.right;
stream >> pheader.bottom;
stream >> pheader.inch;
stream >> pheader.reserved;
stream >> pheader.checksum;
checksum = calcCheckSum(&pheader);
if (pheader.checksum != checksum) {
warnVectorImage << "Checksum for placeable metafile header is incorrect ( actual checksum" << pheader.checksum << ", expected checksum" << checksum << ")";
return false;
}
stream >> header.fileType;
stream >> header.headerSize;
stream >> header.version;
stream >> header.fileSize;
stream >> header.numOfObjects;
stream >> header.maxRecordSize;
stream >> header.numOfParameters;
mNbrObject = header.numOfObjects;
// The bounding box of the WMF
mBBoxLeft = pheader.left;
mBBoxTop = pheader.top;
mBBoxRight = pheader.right;
mBBoxBottom = pheader.bottom;
#if DEBUG_RECORDS
debugVectorImage << "bounding box in header: " << mBBoxLeft << mBBoxTop << mBBoxRight << mBBoxBottom
<< "width, height: " << mBBoxRight - mBBoxLeft << mBBoxBottom - mBBoxTop;
#endif
mMaxWidth = abs(pheader.right - pheader.left);
mMaxHeight = abs(pheader.bottom - pheader.top);
mDpi = pheader.inch;
} else {
mBuffer->reset();
//----- Read as enhanced metafile header
filePos = mBuffer->pos();
stream >> eheader.recordType;
stream >> eheader.recordSize;
stream >> eheader.boundsLeft;
stream >> eheader.boundsTop;
stream >> eheader.boundsRight;
stream >> eheader.boundsBottom;
stream >> eheader.frameLeft;
stream >> eheader.frameTop;
stream >> eheader.frameRight;
stream >> eheader.frameBottom;
stream >> eheader.signature;
if (eheader.signature == ENHMETA_SIGNATURE) {
mEnhanced = true;
stream >> eheader.version;
stream >> eheader.size;
stream >> eheader.numOfRecords;
stream >> eheader.numHandles;
stream >> eheader.reserved;
stream >> eheader.sizeOfDescription;
stream >> eheader.offsetOfDescription;
stream >> eheader.numPaletteEntries;
stream >> eheader.widthDevicePixels;
stream >> eheader.heightDevicePixels;
stream >> eheader.widthDeviceMM;
stream >> eheader.heightDeviceMM;
} else {
//----- Read as standard metafile header
mStandard = true;
mBuffer->seek(filePos);
stream >> header.fileType;
stream >> header.headerSize;
stream >> header.version;
stream >> header.fileSize;
stream >> header.numOfObjects;
stream >> header.maxRecordSize;
stream >> header.numOfParameters;
mNbrObject = header.numOfObjects;
}
}
mOffsetFirstRecord = mBuffer->pos();
//----- Test header validity
if (((header.headerSize == 9) && (header.numOfParameters == 0)) || (mPlaceable)) {
// valid wmf file
mValid = true;
} else {
debugVectorImage << "WmfParser : incorrect file format !";
}
// check bounding rectangle for standard meta file
if (mStandard && mValid) {
// Note that this call can change mValid.
createBoundingBox(stream);
#if DEBUG_RECORDS
debugVectorImage << "bounding box created by going through all records: "
<< mBBoxLeft << mBBoxTop << mBBoxRight << mBBoxBottom
<< "width, height: " << mBBoxRight - mBBoxLeft << mBBoxBottom - mBBoxTop;
#endif
}
return mValid;
}
bool WmfParser::play(WmfAbstractBackend* backend)
{
if (!(mValid)) {
debugVectorImage << "WmfParser::play : invalid WMF file";
return false;
}
if (mNbrFunc) {
#if DEBUG_RECORDS
if ((mStandard)) {
debugVectorImage << "Standard :" << mBBoxLeft << "" << mBBoxTop << "" << mBBoxRight - mBBoxLeft << "" << mBBoxBottom - mBBoxTop;
} else {
debugVectorImage << "DPI :" << mDpi;
debugVectorImage << "size (inch):" << (mBBoxRight - mBBoxLeft) / mDpi
<< "" << (mBBoxBottom - mBBoxTop) / mDpi;
debugVectorImage << "size (mm):" << (mBBoxRight - mBBoxLeft) * 25.4 / mDpi
<< "" << (mBBoxBottom - mBBoxTop) * 25.4 / mDpi;
}
debugVectorImage << mValid << "" << mStandard << "" << mPlaceable;
#endif
}
// Stack of handles
mObjHandleTab = new KoWmfHandle* [ mNbrObject ];
for (int i = 0; i < mNbrObject ; i++) {
mObjHandleTab[ i ] = 0;
}
mDeviceContext.reset();
quint16 recordType;
quint32 size;
int bufferOffset;
// Create a stream from which the records will be read.
QDataStream stream(mBuffer);
stream.setByteOrder(QDataStream::LittleEndian);
// Set the output backend.
m_backend = backend;
// Set some initial values.
mDeviceContext.windowOrg = QPoint(0, 0);
mDeviceContext.windowExt = QSize(1, 1);
QRect bbox(QPoint(mBBoxLeft,mBBoxTop),
QSize(mBBoxRight - mBBoxLeft, mBBoxBottom - mBBoxTop));
if (m_backend->begin(bbox)) {
// Play WMF functions.
mBuffer->seek(mOffsetFirstRecord);
recordType = 1;
while ((recordType) && (!mStackOverflow)) {
int j = 1;
bufferOffset = mBuffer->pos();
stream >> size;
stream >> recordType;
-
+
// mapping between n function and index of table 'metaFuncTab'
// lower 8 digits of the function => entry in the table
quint16 index = recordType & 0xff;
if (index > 0x5F) {
index -= 0x90;
}
#if defined(DEBUG_RECORDS)
debugVectorImage << "Record = " << koWmfFunc[ index ].name
<< " (" << hex << recordType
<< ", index" << dec << index << ")";
#endif
if (mNbrFunc) {
// debug mode
if ((j + 12) > mNbrFunc) {
// output last 12 functions
int offBuff = mBuffer->pos();
quint16 param;
debugVectorImage << j << " :" << index << " :";
for (quint16 i = 0 ; i < (size - 3) ; i++) {
stream >> param;
debugVectorImage << param << "";
}
debugVectorImage;
mBuffer->seek(offBuff);
}
if (j >= mNbrFunc) {
break;
}
j++;
}
// Execute the function and parse the record.
switch (recordType & 0xff) {
case (META_EOF & 0xff):
// Don't need to do anything here.
break;
case (META_SETBKCOLOR & 0xff):
{
quint32 color;
stream >> color;
mDeviceContext.backgroundColor = qtColor(color);
mDeviceContext.changedItems |= DCBgTextColor;
}
break;
case (META_SETBKMODE & 0xff):
{
quint16 bkMode;
stream >> bkMode;
//debugVectorImage << "New bkMode: " << bkMode;
mDeviceContext.bgMixMode = bkMode;
mDeviceContext.changedItems |= DCBgMixMode;
}
break;
case (META_SETMAPMODE & 0xff):
{
stream >> mMapMode;
//debugVectorImage << "New mapmode: " << mMapMode;
//mDeviceContext.FontMapMode = mMapMode;Not defined yet
mDeviceContext.changedItems |= DCFontMapMode;
}
break;
case (META_SETROP2 & 0xff):
{
quint16 rop;
stream >> rop;
m_backend->setCompositionMode(winToQtComposition(rop));
mDeviceContext.rop = rop;
mDeviceContext.changedItems |= DCFgMixMode;
} break;
case (META_SETRELABS & 0xff):
break;
case (META_SETPOLYFILLMODE & 0xff):
{
stream >> mDeviceContext.polyFillMode;
mDeviceContext.changedItems |= DCPolyFillMode;
}
break;
case (META_SETSTRETCHBLTMODE & 0xff):
case (META_SETTEXTCHAREXTRA & 0xff):
break;
case (META_SETTEXTCOLOR & 0xff):
{
quint32 color;
stream >> color;
mDeviceContext.foregroundTextColor = qtColor(color);
mDeviceContext.changedItems |= DCFgTextColor;
}
break;
case (META_SETTEXTJUSTIFICATION & 0xff):
break;
case (META_SETWINDOWORG & 0xff):
{
qint16 top, left;
stream >> top >> left;
m_backend->setWindowOrg(left, top);
mDeviceContext.windowOrg = QPoint(left, top);
#if DEBUG_RECORDS
debugVectorImage <<"Org: (" << left <<"," << top <<")";
#endif
}
break;
case (META_SETWINDOWEXT & 0xff):
{
qint16 width, height;
// negative value allowed for width and height
stream >> height >> width;
#if DEBUG_RECORDS
debugVectorImage <<"Ext: (" << width <<"," << height <<")";
#endif
m_backend->setWindowExt(width, height);
mDeviceContext.windowExt = QSize(width, height);
}
break;
case (META_SETVIEWPORTORG & 0xff):
{
qint16 top, left;
stream >> top >> left;
m_backend->setViewportOrg(left, top);
mDeviceContext.viewportOrg = QPoint(left, top);
#if DEBUG_RECORDS
debugVectorImage <<"Org: (" << left <<"," << top <<")";
#endif
}
break;
case (META_SETVIEWPORTEXT & 0xff):
{
qint16 width, height;
// Negative value allowed for width and height
stream >> height >> width;
#if DEBUG_RECORDS
debugVectorImage <<"Ext: (" << width <<"," << height <<")";
#endif
m_backend->setViewportExt(width, height);
mDeviceContext.viewportExt = QSize(width, height);
}
break;
case (META_OFFSETWINDOWORG & 0xff):
{
qint16 offTop, offLeft;
stream >> offTop >> offLeft;
m_backend->setWindowOrg(mDeviceContext.windowOrg.x() + offLeft,
mDeviceContext.windowOrg.y() + offTop);
mDeviceContext.windowOrg += QPoint(offLeft, offTop);
}
break;
case (META_SCALEWINDOWEXT & 0xff):
{
// Use 32 bits in the calculations to not lose precision.
qint32 width, height;
qint16 heightDenum, heightNum, widthDenum, widthNum;
stream >> heightDenum >> heightNum >> widthDenum >> widthNum;
if ((widthDenum != 0) && (heightDenum != 0)) {
width = (qint32(mDeviceContext.windowExt.width()) * widthNum) / widthDenum;
height = (qint32(mDeviceContext.windowExt.height()) * heightNum) / heightDenum;
m_backend->setWindowExt(width, height);
mDeviceContext.windowExt = QSize(width, height);
}
//debugVectorImage <<"WmfParser::ScaleWindowExt :" << widthDenum <<"" << heightDenum;
}
break;
case (META_OFFSETVIEWPORTORG & 0xff):
{
qint16 offTop, offLeft;
stream >> offTop >> offLeft;
m_backend->setViewportOrg(mDeviceContext.windowOrg.x() + offLeft,
mDeviceContext.windowOrg.y() + offTop);
mDeviceContext.viewportOrg += QPoint(offLeft, offTop);
}
break;
case (META_SCALEVIEWPORTEXT & 0xff):
{
// Use 32 bits in the calculations to not lose precision.
qint32 width, height;
qint16 heightDenum, heightNum, widthDenum, widthNum;
stream >> heightDenum >> heightNum >> widthDenum >> widthNum;
if ((widthDenum != 0) && (heightDenum != 0)) {
width = (qint32(mDeviceContext.windowExt.width()) * widthNum) / widthDenum;
height = (qint32(mDeviceContext.windowExt.height()) * heightNum) / heightDenum;
m_backend->setViewportExt(width, height);
mDeviceContext.viewportExt = QSize(width, height);
}
//debugVectorImage <<"WmfParser::ScaleWindowExt :" << widthDenum <<"" << heightDenum;
}
break;
// ----------------------------------------------------------------
// Drawing records
case (META_LINETO & 0xff):
{
qint16 top, left;
stream >> top >> left;
m_backend->lineTo(mDeviceContext, left, top);
}
break;
case (META_MOVETO & 0xff):
{
qint16 top, left;
stream >> top >> left;
mDeviceContext.currentPosition = QPoint(left, top);
}
break;
case (META_EXCLUDECLIPRECT & 0xff):
{
qint16 top, left, right, bottom;
stream >> bottom >> right >> top >> left;
-
+
QRegion region = mDeviceContext.clipRegion;
QRegion newRegion(left, top, right - left, bottom - top);
if (region.isEmpty()) {
// FIXME: I doubt that if the region is previously empty,
// it should be set to the new region. /iw
region = newRegion;
} else {
- region = region.subtract(newRegion);
+ region = region.subtracted(newRegion);
}
mDeviceContext.clipRegion = region;
mDeviceContext.changedItems |= DCClipRegion;
}
break;
case (META_INTERSECTCLIPRECT & 0xff):
{
qint16 top, left, right, bottom;
stream >> bottom >> right >> top >> left;
QRegion region = mDeviceContext.clipRegion;
QRegion newRegion(left, top, right - left, bottom - top);
if (region.isEmpty()) {
// FIXME: I doubt that if the region is previously empty,
// it should be set to the new region. /iw
region = newRegion;
} else {
- region = region.intersect(newRegion);
+ region = region.intersected(newRegion);
}
mDeviceContext.clipRegion = region;
mDeviceContext.changedItems |= DCClipRegion;
}
break;
case (META_ARC & 0xff):
{
int xCenter, yCenter, angleStart, aLength;
qint16 topEnd, leftEnd, topStart, leftStart;
qint16 top, left, right, bottom;
stream >> topEnd >> leftEnd >> topStart >> leftStart;
stream >> bottom >> right >> top >> left;
xCenter = left + ((right - left) / 2);
yCenter = top + ((bottom - top) / 2);
xyToAngle(leftStart - xCenter, yCenter - topStart,
leftEnd - xCenter, yCenter - topEnd, angleStart, aLength);
m_backend->drawArc(mDeviceContext, left, top, right - left, bottom - top,
angleStart, aLength);
}
break;
case (META_ELLIPSE & 0xff):
{
qint16 top, left, right, bottom;
stream >> bottom >> right >> top >> left;
m_backend->drawEllipse(mDeviceContext, left, top, right - left, bottom - top);
}
break;
case (META_FLOODFILL & 0xff):
break;
case (META_PIE & 0xff):
{
int xCenter, yCenter, angleStart, aLength;
qint16 topEnd, leftEnd, topStart, leftStart;
qint16 top, left, right, bottom;
stream >> topEnd >> leftEnd >> topStart >> leftStart;
stream >> bottom >> right >> top >> left;
xCenter = left + ((right - left) / 2);
yCenter = top + ((bottom - top) / 2);
xyToAngle(leftStart - xCenter, yCenter - topStart, leftEnd - xCenter, yCenter - topEnd, angleStart, aLength);
m_backend->drawPie(mDeviceContext, left, top, right - left, bottom - top,
angleStart, aLength);
}
break;
case (META_RECTANGLE & 0xff):
{
qint16 top, left, right, bottom;
stream >> bottom >> right >> top >> left;
//debugVectorImage << left << top << right << bottom;
m_backend->drawRect(mDeviceContext, left, top, right - left, bottom - top);
}
break;
case (META_ROUNDRECT & 0xff):
{
int xRnd = 0, yRnd = 0;
quint16 widthCorner, heightCorner;
qint16 top, left, right, bottom;
stream >> heightCorner >> widthCorner;
stream >> bottom >> right >> top >> left;
// convert (widthCorner, heightCorner) in percentage
if ((right - left) != 0)
xRnd = (widthCorner * 100) / (right - left);
if ((bottom - top) != 0)
yRnd = (heightCorner * 100) / (bottom - top);
m_backend->drawRoundRect(mDeviceContext, left, top, right - left, bottom - top,
xRnd, yRnd);
}
break;
case (META_PATBLT & 0xff):
{
quint32 rasterOperation;
quint16 height, width;
qint16 y, x;
stream >> rasterOperation;
stream >> height >> width;
stream >> y >> x;
//debugVectorImage << "patBlt record" << hex << rasterOperation << dec
// << x << y << width << height;
m_backend->patBlt(mDeviceContext, x, y, width, height, rasterOperation);
}
break;
case (META_SAVEDC & 0xff):
m_backend->save();
break;
case (META_SETPIXEL & 0xff):
{
qint16 left, top;
quint32 color;
stream >> color >> top >> left;
m_backend->setPixel(mDeviceContext, left, top, qtColor(color));
}
break;
case (META_OFFSETCLIPRGN & 0xff):
break;
case (META_TEXTOUT & 0xff):
{
quint16 textLength;
qint16 x, y;
stream >> textLength;
QByteArray text;
text.resize(textLength);
stream.readRawData(text.data(), textLength);
// The string is always of even length, so if the actual data is
// of uneven length, read an extra byte.
if (textLength & 0x01) {
quint8 dummy;
stream >> dummy;
}
stream >> y;
stream >> x;
m_backend->drawText(mDeviceContext, x, y, text);
}
break;
case (META_BITBLT & 0xff):
case (META_STRETCHBLT & 0xff):
break;
case (META_POLYGON & 0xff):
{
quint16 num;
stream >> num;
QPolygon pa(num);
pointArray(stream, pa);
m_backend->drawPolygon(mDeviceContext, pa);
}
break;
case (META_POLYLINE & 0xff):
{
quint16 num;
stream >> num;
QPolygon pa(num);
pointArray(stream, pa);
m_backend->drawPolyline(mDeviceContext, pa);
}
break;
case (META_ESCAPE & 0xff):
break;
case (META_RESTOREDC & 0xff):
{
qint16 num;
stream >> num;
for (int i = 0; i > num ; i--)
m_backend->restore();
}
break;
case (META_FILLREGION & 0xff):
case (META_FRAMEREGION & 0xff):
case (META_INVERTREGION & 0xff):
case (META_PAINTREGION & 0xff):
case (META_SELECTCLIPREGION & 0xff):
break;
case (META_SELECTOBJECT & 0xff):
{
quint16 idx;
stream >> idx;
if ((idx < mNbrObject) && (mObjHandleTab[ idx ] != 0))
mObjHandleTab[ idx ]->apply(&mDeviceContext);
else
debugVectorImage << "WmfParser::selectObject : selection of an empty object";
}
break;
case (META_SETTEXTALIGN & 0xff):
stream >> mDeviceContext.textAlign;
mDeviceContext.changedItems |= DCTextAlignMode;
break;
case (META_CHORD & 0xff):
{
int xCenter, yCenter, angleStart, aLength;
qint16 topEnd, leftEnd, topStart, leftStart;
qint16 top, left, right, bottom;
stream >> topEnd >> leftEnd >> topStart >> leftStart;
stream >> bottom >> right >> top >> left;
xCenter = left + ((right - left) / 2);
yCenter = top + ((bottom - top) / 2);
xyToAngle(leftStart - xCenter, yCenter - topStart, leftEnd - xCenter, yCenter - topEnd, angleStart, aLength);
m_backend->drawChord(mDeviceContext, left, top, right - left, bottom - top,
angleStart, aLength);
}
break;
case (META_SETMAPPERFLAGS & 0xff):
break;
case (META_EXTTEXTOUT & 0xff):
{
qint16 y, x;
qint16 stringLength;
quint16 fwOpts;
qint16 top, left, right, bottom; // optional cliprect
stream >> y;
stream >> x;
stream >> stringLength;
stream >> fwOpts;
// ETO_CLIPPED flag adds 4 parameters
if (fwOpts & (ETO_CLIPPED | ETO_OPAQUE)) {
// read the optional clip rect
stream >> bottom >> right >> top >> left;
}
// Read the string. Note that it's padded to 16 bits.
QByteArray text;
text.resize(stringLength);
stream.readRawData(text.data(), stringLength);
if (stringLength & 0x01) {
quint8 padding;
stream >> padding;
}
#if DEBUG_RECORDS
debugVectorImage << "text at" << x << y << "length" << stringLength
<< ':' << text;
//debugVectorImage << "flags:" << hex << fwOpts << dec;
debugVectorImage << "flags:" << fwOpts;
debugVectorImage << "record length:" << size;
#endif
m_backend->drawText(mDeviceContext, x, y, text);
}
break;
case (META_SETDIBTODEV & 0xff):
case (META_SELECTPALETTE & 0xff):
case (META_REALIZEPALETTE & 0xff):
case (META_ANIMATEPALETTE & 0xff):
case (META_SETPALENTRIES & 0xff):
break;
case (META_POLYPOLYGON & 0xff):
{
quint16 numberPoly;
quint16 sizePoly;
QList<QPolygon> listPa;
stream >> numberPoly;
for (int i = 0 ; i < numberPoly ; i++) {
stream >> sizePoly;
listPa.append(QPolygon(sizePoly));
}
// list of point array
for (int i = 0; i < numberPoly; i++) {
pointArray(stream, listPa[i]);
}
// draw polygon's
m_backend->drawPolyPolygon(mDeviceContext, listPa);
listPa.clear();
}
break;
case (META_RESIZEPALETTE & 0xff):
break;
case (META_DIBBITBLT & 0xff):
{
quint32 raster;
qint16 topSrc, leftSrc, widthSrc, heightSrc;
qint16 topDst, leftDst;
stream >> raster;
stream >> topSrc >> leftSrc >> heightSrc >> widthSrc;
stream >> topDst >> leftDst;
if (size > 11) { // DIB image
QImage bmpSrc;
if (dibToBmp(bmpSrc, stream, (size - 11) * 2)) {
m_backend->setCompositionMode(winToQtComposition(raster));
m_backend->save();
if (widthSrc < 0) {
// negative width => horizontal flip
QMatrix m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
m_backend->setMatrix(mDeviceContext, m, true);
}
if (heightSrc < 0) {
// negative height => vertical flip
QMatrix m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
m_backend->setMatrix(mDeviceContext, m, true);
}
m_backend->drawImage(mDeviceContext, leftDst, topDst,
bmpSrc, leftSrc, topSrc, widthSrc, heightSrc);
m_backend->restore();
}
} else {
debugVectorImage << "WmfParser::dibBitBlt without image not implemented";
}
}
break;
case (META_DIBSTRETCHBLT & 0xff):
{
quint32 raster;
qint16 topSrc, leftSrc, widthSrc, heightSrc;
qint16 topDst, leftDst, widthDst, heightDst;
QImage bmpSrc;
stream >> raster;
stream >> heightSrc >> widthSrc >> topSrc >> leftSrc;
stream >> heightDst >> widthDst >> topDst >> leftDst;
if (dibToBmp(bmpSrc, stream, (size - 13) * 2)) {
m_backend->setCompositionMode(winToQtComposition(raster));
m_backend->save();
if (widthDst < 0) {
// negative width => horizontal flip
QMatrix m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
m_backend->setMatrix(mDeviceContext, m, true);
}
if (heightDst < 0) {
// negative height => vertical flip
QMatrix m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
m_backend->setMatrix(mDeviceContext, m, true);
}
bmpSrc = bmpSrc.copy(leftSrc, topSrc, widthSrc, heightSrc);
// TODO: scale the bitmap : QImage::scale(widthDst, heightDst)
// is actually too slow
m_backend->drawImage(mDeviceContext, leftDst, topDst, bmpSrc);
m_backend->restore();
}
}
break;
case (META_DIBCREATEPATTERNBRUSH & 0xff):
{
KoWmfPatternBrushHandle* handle = new KoWmfPatternBrushHandle;
if (addHandle(handle)) {
quint32 arg;
QImage bmpSrc;
stream >> arg;
if (dibToBmp(bmpSrc, stream, (size - 5) * 2)) {
handle->image = bmpSrc;
handle->brush.setTextureImage(handle->image);
} else {
debugVectorImage << "WmfParser::dibCreatePatternBrush : incorrect DIB image";
}
}
}
break;
case (META_STRETCHDIB & 0xff):
{
quint32 raster;
qint16 arg, topSrc, leftSrc, widthSrc, heightSrc;
qint16 topDst, leftDst, widthDst, heightDst;
QImage bmpSrc;
stream >> raster >> arg;
stream >> heightSrc >> widthSrc >> topSrc >> leftSrc;
stream >> heightDst >> widthDst >> topDst >> leftDst;
if (dibToBmp(bmpSrc, stream, (size - 14) * 2)) {
m_backend->setCompositionMode(winToQtComposition(raster));
m_backend->save();
if (widthDst < 0) {
// negative width => horizontal flip
QMatrix m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
m_backend->setMatrix(mDeviceContext, m, true);
}
if (heightDst < 0) {
// negative height => vertical flip
QMatrix m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
m_backend->setMatrix(mDeviceContext, m, true);
}
bmpSrc = bmpSrc.copy(leftSrc, topSrc, widthSrc, heightSrc);
// TODO: scale the bitmap ( QImage::scale(param[ 8 ], param[ 7 ]) is actually too slow )
m_backend->drawImage(mDeviceContext, leftDst, topDst, bmpSrc);
m_backend->restore();
}
}
break;
case (META_EXTFLOODFILL & 0xff):
break;
case (META_SETLAYOUT & 0xff):
{
quint16 layout;
quint16 reserved;
// negative value allowed for width and height
stream >> layout >> reserved;
#if DEBUG_RECORDS
debugVectorImage << "layout=" << layout;
#endif
mLayout = (WmfLayout)layout;
mDeviceContext.layoutMode = mLayout;
mDeviceContext.changedItems |= DCLayoutMode;
}
break;
case (META_DELETEOBJECT & 0xff):
{
quint16 idx;
stream >> idx;
deleteHandle(idx);
}
break;
case (META_CREATEPALETTE & 0xff):
// Unimplemented
createEmptyObject();
break;
case (META_CREATEBRUSH & 0xff):
case (META_CREATEPATTERNBRUSH & 0xff):
break;
case (META_CREATEPENINDIRECT & 0xff):
{
// TODO: userStyle and alternateStyle
quint32 color;
quint16 style, width, arg;
KoWmfPenHandle* handle = new KoWmfPenHandle;
if (addHandle(handle)) {
stream >> style >> width >> arg >> color;
// set the style defaults
handle->pen.setStyle(Qt::SolidLine);
handle->pen.setCapStyle(Qt::RoundCap);
handle->pen.setJoinStyle(Qt::RoundJoin);
const int PenStyleMask = 0x0000000F;
const int PenCapMask = 0x00000F00;
const int PenJoinMask = 0x0000F000;
quint16 penStyle = style & PenStyleMask;
if (penStyle < 7)
handle->pen.setStyle(koWmfStylePen[ penStyle ]);
else
debugVectorImage << "WmfParser::createPenIndirect: invalid pen" << style;
quint16 capStyle = (style & PenCapMask) >> 8;
if (capStyle < 3)
handle->pen.setCapStyle(koWmfCapStylePen[ capStyle ]);
else
debugVectorImage << "WmfParser::createPenIndirect: invalid pen cap style" << style;
quint16 joinStyle = (style & PenJoinMask) >> 12;
if (joinStyle < 3)
handle->pen.setJoinStyle(koWmfJoinStylePen[ joinStyle ]);
else
debugVectorImage << "WmfParser::createPenIndirect: invalid pen join style" << style;
handle->pen.setColor(qtColor(color));
handle->pen.setWidth(width);
debugVectorImage << "Creating pen" << handle->pen;
}
}
break;
case (META_CREATEFONTINDIRECT & 0xff):
{
qint16 height; // Height of the character cell
qint16 width; // Average width (not used)
qint16 escapement; // The rotation of the text in 1/10th degrees
qint16 orientation; // The rotation of each character
quint16 weight, property, fixedPitch, arg;
KoWmfFontHandle* handle = new KoWmfFontHandle;
if (addHandle(handle)) {
stream >> height >> width;
stream >> escapement >> orientation;
stream >> weight >> property >> arg >> arg;
stream >> fixedPitch;
//debugVectorImage << height << width << weight << property;
// text rotation (in 1/10 degree)
handle->font.setFixedPitch(((fixedPitch & 0x01) == 0));
handle->escapement = escapement;
handle->orientation = orientation;
// A negative height means to use device units.
//debugVectorImage << "Font height:" << height;
handle->height = height;
// FIXME: For some reason this value needs to be multiplied by
// a factor. 0.6 seems to give a good result, but why??
// ANSWER(?): The doc says the height is the height of the character cell.
// But normally the font height is only the height above the
// baseline, isn't it?
handle->font.setPointSize(qAbs(height) * 6 / 10);
if (weight == 0)
weight = QFont::Normal;
else {
// Linear transform between MS weights to Qt weights
// MS: 400=normal, 700=bold
// Qt: 50=normal, 75=bold
// This makes the line cross x=0 at y=50/3. (x=MS weight, y=Qt weight)
//
// FIXME: Is this a linear relationship?
weight = (50 + 3 * ((weight * (75-50))/(700-400))) / 3;
}
handle->font.setWeight(weight);
handle->font.setItalic((property & 0x01));
handle->font.setUnderline((property & 0x100));
// TODO: Strikethrough
// font name
int maxChar = (size - 12) * 2;
char* nameFont = new char[maxChar];
stream.readRawData(nameFont, maxChar);
handle->font.setFamily(nameFont);
delete[] nameFont;
}
}
break;
case (META_CREATEBRUSHINDIRECT & 0xff):
{
Qt::BrushStyle style;
quint16 sty, arg2;
quint32 color;
KoWmfBrushHandle* handle = new KoWmfBrushHandle;
if (addHandle(handle)) {
stream >> sty >> color >> arg2;
if (sty == 2) {
if (arg2 < 6)
style = koWmfHatchedStyleBrush[ arg2 ];
else {
debugVectorImage << "WmfParser::createBrushIndirect: invalid hatched brush" << arg2;
style = Qt::SolidPattern;
}
} else {
if (sty < 9)
style = koWmfStyleBrush[ sty ];
else {
debugVectorImage << "WmfParser::createBrushIndirect: invalid brush" << sty;
style = Qt::SolidPattern;
}
}
handle->brush.setStyle(style);
handle->brush.setColor(qtColor(color));
}
}
break;
#if 0
- UNSPECIFIED in the Spec:
+ UNSPECIFIED in the Spec:
{ &WmfParser::createBitmapIndirect, "createBitmapIndirect" }, //109 0xfd
{ &WmfParser::createBitmap, "createBitmap" }, // 110 0xfe
#endif
case (META_CREATEREGION & 0xff):
// FIXME: Unimplemented
createEmptyObject();
break;
default:
// function outside WMF specification
errorVectorImage << "BROKEN WMF file: Record number" << hex << recordType << dec
<< " index " << index;
mValid = false;
break;
}
mBuffer->seek(bufferOffset + (size << 1));
}
// Let the backend clean up it's internal state.
m_backend->end();
}
for (int i = 0 ; i < mNbrObject ; i++) {
if (mObjHandleTab[ i ] != 0)
delete mObjHandleTab[ i ];
}
delete[] mObjHandleTab;
mObjHandleTab = 0;
return true;
}
//-----------------------------------------------------------------------------
void WmfParser::createBoundingBox(QDataStream &stream)
{
// Check bounding rectangle for standard meta file.
// This calculation is done in device coordinates.
- if (!mStandard || !mValid)
+ if (!mStandard || !mValid)
return;
bool windowExtIsSet = false;
bool viewportExtIsSet = false;
quint16 recordType = 1;
quint32 size;
int filePos;
// Search for records setWindowOrg and setWindowExt to
// determine what the total bounding box of this WMF is.
// This initialization assumes that setWindowOrg comes before setWindowExt.
qint16 windowOrgX = 0;
qint16 windowOrgY = 0;
qint16 windowWidth = 0;
qint16 windowHeight = 0;
qint16 viewportOrgX = 0;
qint16 viewportOrgY = 0;
qint16 viewportWidth = 0;
qint16 viewportHeight = 0;
bool bboxRecalculated = false;
while (recordType) {
filePos = mBuffer->pos();
stream >> size >> recordType;
if (size == 0) {
debugVectorImage << "WmfParser: incorrect file!";
mValid = 0;
return;
}
bool doRecalculateBBox = false;
qint16 orgX = 0;
qint16 orgY = 0;
qint16 extX = 0;
qint16 extY = 0;
switch (recordType &= 0xFF) {
case 11: // setWindowOrg
{
stream >> windowOrgY >> windowOrgX;
#if DEBUG_BBOX
debugVectorImage << "setWindowOrg" << windowOrgX << windowOrgY;
#endif
if (!windowExtIsSet)
break;
// The bounding box doesn't change just because we get
// a new window. Remember we are working in device
// (viewport) coordinates when deciding the bounding
// box.
if (viewportExtIsSet)
break;
// If there is no viewport, then use the window ext as
// size, and (0, 0) as origin.
//
// FIXME: Handle the case where the window is defined
// first and then the viewport, without any
// drawing in between. If that happens, I
// don't think that the window definition
// should influence the bounding box.
orgX = 0;
orgY = 0;
extX = windowWidth;
extY = windowHeight;
}
break;
case 12: // setWindowExt
{
stream >> windowHeight >> windowWidth;
windowExtIsSet = true;
bboxRecalculated = false;
#if DEBUG_BBOX
debugVectorImage << "setWindowExt" << windowWidth << windowHeight
<< "(viewportOrg = " << viewportOrgX << viewportOrgY << ")";
#endif
// If the viewport is set, then a changed window
// changes nothing in the bounding box.
if (viewportExtIsSet)
break;
bboxRecalculated = false;
// Collect the maximum width and height.
if (abs(windowWidth - windowOrgX) > mMaxWidth)
mMaxWidth = abs(windowWidth - windowOrgX);
if (abs(windowHeight - windowOrgY) > mMaxHeight)
mMaxHeight = abs(windowHeight - windowOrgY);
orgX = 0;
orgY = 0;
extX = windowWidth;
extY = windowHeight;
}
break;
case 13: //setViewportOrg
{
stream >> viewportOrgY >> viewportOrgX;
bboxRecalculated = false;
#if DEBUG_BBOX
debugVectorImage << "setViewportOrg" << viewportOrgX << viewportOrgY;
#endif
orgX = viewportOrgX;
orgY = viewportOrgY;
if (viewportExtIsSet) {
extX = viewportWidth;
extY = viewportHeight;
}
else {
// If the viewportExt is not set, then either a
// subsequent setViewportExt will set it, or the
- // windowExt will be used instead.
+ // windowExt will be used instead.
extX = windowWidth;
extY = windowHeight;
}
break;
// FIXME: Handle the case where the org changes but
// there is no subsequent Ext change (should be
// rather uncommon).
}
break;
case 14: //setViewportExt
{
stream >> viewportHeight >> viewportWidth;
viewportExtIsSet = true;
bboxRecalculated = false;
#if DEBUG_BBOX
debugVectorImage << "setViewportExt" << viewportWidth << viewportHeight;
#endif
orgX = viewportOrgX;
orgY = viewportOrgY;
extX = viewportWidth;
extY = viewportHeight;
}
break;
// FIXME: Also support:
- // ScaleWindowExt, ScaleViewportExt,
+ // ScaleWindowExt, ScaleViewportExt,
// OffsetWindowOrg, OffsetViewportOrg
// The following are drawing commands. It is only when
// there is an actual drawing command that we should check
// the bounding box. It seems that some WMF files have
// lots of changes of the window or viewports but without
// any drawing commands in between. These changes should
// not affect the bounding box.
case 19: // lineTo
//case 20: // moveTo
case 23: // arc
case 24: // ellipse
case 26: // pie
case 27: // rectangle
case 28: // roundRect
case 29: // patBlt
case 31: // setPixel
case 33: // textOut
case 34: // bitBlt
case 36: // polygon
case 37: // polyline
//case 38: // escape FIXME: is this a drawing command?
case 40: // fillRegion
case 41:
case 42:
case 43:
case 44:
case 48: // chord
case 50: // extTextOut
case 56: // polyPolygon
case 64: // dibBitBlt
case 65: // dibStretchBlt
case 67: // stretchDib
case 72: // extFloodFill
#if DEBUG_BBOX
debugVectorImage << "drawing record: " << (recordType & 0xff);
#endif
doRecalculateBBox = true;
break;
default:
;
}
// Recalculate the BBox if it was indicated above that it should be.
if (doRecalculateBBox && !bboxRecalculated) {
#if DEBUG_BBOX
debugVectorImage << "Recalculating BBox";
#endif
// If we have a viewport, always use that one.
if (viewportExtIsSet) {
orgX = viewportOrgX;
orgY = viewportOrgY;
extX = viewportWidth;
extY = viewportHeight;
}
else {
// If there is no defined viewport, then use the
// window as the fallback viewport. But only the size,
// the origin is always (0, 0).
orgX = 0;
orgY = 0;
extX = qAbs(windowWidth);
extY = qAbs(windowHeight);
}
// If ext < 0, switch the org and org+ext
if (extX < 0) {
orgX += extX;
extX = -extX;
}
if (extY < 0) {
orgY += extY;
extY = -extY;
}
// At this point, the ext is always >= 0, i.e. org <= org+ext
#if DEBUG_BBOX
debugVectorImage << orgX << orgY << extX << extY;
#endif
if (orgX < mBBoxLeft) mBBoxLeft = orgX;
if (orgY < mBBoxTop) mBBoxTop = orgY;
if (orgX + extX > mBBoxRight) mBBoxRight = orgX + extX;
if (orgY + extY > mBBoxBottom) mBBoxBottom = orgY + extY;
bboxRecalculated = true;
}
#if DEBUG_BBOX
if (isOrgOrExt) {
debugVectorImage << " mBBoxTop = " << mBBoxTop;
debugVectorImage << "mBBoxLeft = " << mBBoxLeft << " mBBoxRight = " << mBBoxRight;
debugVectorImage << " MBBoxBotton = " << mBBoxBottom;
debugVectorImage << "Max width,height = " << mMaxWidth << mMaxHeight;
}
#endif
mBuffer->seek(filePos + (size << 1));
}
}
//-----------------------------------------------------------------------------
// Object handle
void WmfParser::createEmptyObject()
{
// allocation of an empty object (to keep object counting in sync)
KoWmfPenHandle* handle = new KoWmfPenHandle;
addHandle(handle);
}
//-----------------------------------------------------------------------------
// Misc functions
quint16 WmfParser::calcCheckSum(WmfPlaceableHeader* apmfh)
{
quint16* lpWord;
quint16 wResult, i;
// Start with the first word
wResult = *(lpWord = (quint16*)(apmfh));
// XOR in each of the other 9 words
for (i = 1; i <= 9; i++) {
wResult ^= lpWord[ i ];
}
return wResult;
}
//-----------------------------------------------------------------------------
// Utilities and conversion Wmf -> Qt
bool WmfParser::addHandle(KoWmfHandle* handle)
{
int idx;
for (idx = 0; idx < mNbrObject ; idx++) {
if (mObjHandleTab[ idx ] == 0) break;
}
if (idx < mNbrObject) {
mObjHandleTab[ idx ] = handle;
return true;
} else {
delete handle;
mStackOverflow = true;
debugVectorImage << "WmfParser::addHandle : stack overflow = broken file !";
return false;
}
}
void WmfParser::deleteHandle(int idx)
{
if ((idx < mNbrObject) && (mObjHandleTab[idx] != 0)) {
delete mObjHandleTab[ idx ];
mObjHandleTab[ idx ] = 0;
} else {
debugVectorImage << "WmfParser::deletehandle() : bad index number";
}
}
void WmfParser::pointArray(QDataStream& stream, QPolygon& pa)
{
qint16 left, top;
int i, max;
for (i = 0, max = pa.size() ; i < max ; i++) {
stream >> left >> top;
pa.setPoint(i, left, top);
}
}
void WmfParser::xyToAngle(int xStart, int yStart, int xEnd, int yEnd, int& angleStart, int& angleLength)
{
double aStart, aLength;
aStart = atan2((double)yStart, (double)xStart);
aLength = atan2((double)yEnd, (double)xEnd) - aStart;
angleStart = (int)((aStart * 2880) / 3.14166);
angleLength = (int)((aLength * 2880) / 3.14166);
if (angleLength < 0) angleLength = 5760 + angleLength;
}
QPainter::CompositionMode WmfParser::winToQtComposition(quint16 param) const
{
if (param < 17)
return koWmfOpTab16[ param ];
else
return QPainter::CompositionMode_Source;
}
QPainter::CompositionMode WmfParser::winToQtComposition(quint32 param) const
{
/* TODO: Ternary raster operations
0x00C000CA dest = (source AND pattern)
0x00F00021 dest = pattern
0x00FB0A09 dest = DPSnoo
0x005A0049 dest = pattern XOR dest */
int i;
for (i = 0 ; i < 15 ; i++) {
if (koWmfOpTab32[ i ].winRasterOp == param) break;
}
if (i < 15)
return koWmfOpTab32[ i ].qtRasterOp;
else
return QPainter::CompositionMode_SourceOver;
}
bool WmfParser::dibToBmp(QImage& bmp, QDataStream& stream, quint32 size)
{
typedef struct _BMPFILEHEADER {
quint16 bmType;
quint32 bmSize;
quint16 bmReserved1;
quint16 bmReserved2;
quint32 bmOffBits;
} BMPFILEHEADER;
int sizeBmp = size + 14;
QByteArray pattern; // BMP header and DIB data
pattern.resize(sizeBmp);
pattern.fill(0);
stream.readRawData(pattern.data() + 14, size);
// add BMP header
BMPFILEHEADER* bmpHeader;
bmpHeader = (BMPFILEHEADER*)(pattern.data());
bmpHeader->bmType = 0x4D42;
bmpHeader->bmSize = sizeBmp;
// if ( !bmp.loadFromData( (const uchar*)bmpHeader, pattern.size(), "BMP" ) ) {
if (!bmp.loadFromData(pattern, "BMP")) {
debugVectorImage << "WmfParser::dibToBmp: invalid bitmap";
return false;
} else {
return true;
}
}
}
diff --git a/libs/widgets/KoConfigAuthorPage.ui b/libs/widgets/KoConfigAuthorPage.ui
index aab1da4b24..26878bc8e8 100644
--- a/libs/widgets/KoConfigAuthorPage.ui
+++ b/libs/widgets/KoConfigAuthorPage.ui
@@ -1,269 +1,272 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KoConfigAuthorPage</class>
<widget class="QWidget" name="KoConfigAuthorPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>462</width>
- <height>386</height>
+ <width>695</width>
+ <height>342</height>
</rect>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <property name="margin">
- <number>0</number>
- </property>
- <item row="0" column="1">
- <widget class="QLabel" name="fullNameLabel">
- <property name="text">
- <string>Name:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLineEdit" name="leFullName">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>1</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="1" column="0" colspan="2">
- <widget class="QLabel" name="lblInitialsDesc">
- <property name="text">
- <string>Initials:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>leInitials</cstring>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QLineEdit" name="leInitials">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>1</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="2" column="0" colspan="2">
- <widget class="QLabel" name="lblTitleDesc">
- <property name="text">
- <string>Title:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>leTitle</cstring>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QLineEdit" name="leTitle"/>
- </item>
- <item row="3" column="0" colspan="2">
- <widget class="QLabel" name="lblPositionDesc">
- <property name="text">
- <string>Position:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>lePosition</cstring>
- </property>
- </widget>
- </item>
- <item row="3" column="2">
- <widget class="QLineEdit" name="lePosition"/>
- </item>
- <item row="4" column="0" colspan="2">
- <widget class="QLabel" name="lblCompanyDesc">
- <property name="text">
- <string>Company:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>leCompany</cstring>
- </property>
- </widget>
- </item>
- <item row="4" column="2">
- <widget class="QLineEdit" name="leCompany"/>
- </item>
- <item row="5" column="0" colspan="2">
- <widget class="QLabel" name="lblEmailDesc">
- <property name="text">
- <string>Email:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>leEmail</cstring>
- </property>
- </widget>
- </item>
- <item row="5" column="2">
- <widget class="QLineEdit" name="leEmail"/>
- </item>
- <item row="6" column="0" colspan="2">
- <widget class="QLabel" name="lblPhoneHome">
- <property name="text">
- <string>Telephone (home):</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>lePhoneHome</cstring>
- </property>
- </widget>
- </item>
- <item row="6" column="2">
- <widget class="QLineEdit" name="lePhoneHome"/>
- </item>
- <item row="7" column="0" colspan="2">
- <widget class="QLabel" name="lblPhoneWork">
- <property name="text">
- <string>Telephone (work):</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>lePhoneWork</cstring>
- </property>
- </widget>
- </item>
- <item row="7" column="2">
- <widget class="QLineEdit" name="lePhoneWork"/>
- </item>
- <item row="8" column="0" colspan="2">
- <widget class="QLabel" name="lblFaxDesc">
- <property name="text">
- <string>Fax:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>leFax</cstring>
- </property>
- </widget>
- </item>
- <item row="8" column="2">
- <widget class="QLineEdit" name="leFax"/>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>12</number>
+ </property>
+ <item row="5" column="2">
+ <widget class="QLabel" name="lblPhoneWork">
+ <property name="text">
+ <string>Wor&amp;k Number:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>lePhoneWork</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblTitleDesc">
+ <property name="text">
+ <string>Title:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>leTitle</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="leTitle"/>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLineEdit" name="lePhoneHome"/>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLineEdit" name="lePosition"/>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="lblPostalDesc">
+ <property name="text">
+ <string>Postal code:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>lePostal</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="3">
+ <widget class="QLineEdit" name="leCountry"/>
+ </item>
+ <item row="2" column="3">
+ <widget class="QLineEdit" name="leEmail"/>
+ </item>
+ <item row="4" column="2">
+ <widget class="QLabel" name="lblCountryDesc">
+ <property name="text">
+ <string>Country:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>leCountry</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="fullNameLabel">
+ <property name="text">
+ <string>Name:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="leFullName">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLineEdit" name="lePostal"/>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLineEdit" name="leInitials">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="lblCompanyDesc">
+ <property name="text">
+ <string>Co&amp;mpany:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>leCompany</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="lblStreetDesc">
+ <property name="text">
+ <string>Street:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>leStreet</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="lblPhoneHome">
+ <property name="text">
+ <string>Home Number:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>lePhoneHome</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="leCompany"/>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="lblInitialsDesc">
+ <property name="text">
+ <string>Initials:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>leInitials</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="3">
+ <widget class="QLineEdit" name="lePhoneWork"/>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="lblPositionDesc">
+ <property name="text">
+ <string>Position:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>lePosition</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="3">
+ <widget class="QLineEdit" name="leCity"/>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="lblEmailDesc">
+ <property name="text">
+ <string>E&amp;mail:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>leEmail</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QLabel" name="lblCityDesc">
+ <property name="text">
+ <string>Cit&amp;y:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>leCity</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="leStreet"/>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="lblFaxDesc">
+ <property name="text">
+ <string>Fa&amp;x Number:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>leFax</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QLineEdit" name="leFax"/>
+ </item>
+ </layout>
</item>
- <item row="9" column="0" colspan="2">
- <widget class="QLabel" name="lblStreetDesc">
- <property name="text">
- <string>Street:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>leStreet</cstring>
- </property>
- </widget>
- </item>
- <item row="9" column="2">
- <widget class="QLineEdit" name="leStreet"/>
- </item>
- <item row="10" column="0" colspan="2">
- <widget class="QLabel" name="lblPostalDesc">
- <property name="text">
- <string>Postal code:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>lePostal</cstring>
- </property>
- </widget>
- </item>
- <item row="10" column="2">
- <widget class="QLineEdit" name="lePostal"/>
- </item>
- <item row="11" column="0" colspan="2">
- <widget class="QLabel" name="lblCityDesc">
- <property name="text">
- <string>City:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>leCity</cstring>
- </property>
- </widget>
- </item>
- <item row="11" column="2">
- <widget class="QLineEdit" name="leCity"/>
- </item>
- <item row="12" column="0" colspan="2">
- <widget class="QLabel" name="lblCountryDesc">
- <property name="text">
- <string>Country:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>leCountry</cstring>
- </property>
- </widget>
- </item>
- <item row="12" column="2">
- <widget class="QLineEdit" name="leCountry"/>
- </item>
- <item row="14" column="2">
+ <item row="1" column="0">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
- <item row="13" column="2">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Minimum</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>200</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
</layout>
</widget>
+ <tabstops>
+ <tabstop>leFullName</tabstop>
+ <tabstop>leInitials</tabstop>
+ <tabstop>leTitle</tabstop>
+ <tabstop>lePosition</tabstop>
+ <tabstop>leCompany</tabstop>
+ <tabstop>leEmail</tabstop>
+ <tabstop>leStreet</tabstop>
+ <tabstop>leCity</tabstop>
+ <tabstop>lePostal</tabstop>
+ <tabstop>leCountry</tabstop>
+ <tabstop>lePhoneHome</tabstop>
+ <tabstop>lePhoneWork</tabstop>
+ <tabstop>leFax</tabstop>
+ </tabstops>
<resources/>
<connections/>
</ui>
diff --git a/libs/widgets/KoDockWidgetTitleBar.cpp b/libs/widgets/KoDockWidgetTitleBar.cpp
index d32c2a475a..b34e60f74a 100644
--- a/libs/widgets/KoDockWidgetTitleBar.cpp
+++ b/libs/widgets/KoDockWidgetTitleBar.cpp
@@ -1,375 +1,379 @@
/* This file is part of the KDE project
Copyright (c) 2007 Marijn Kruisselbrink <mkruisselbrink@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
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 "KoDockWidgetTitleBar.h"
#include "KoDockWidgetTitleBar_p.h"
#include "KoDockWidgetTitleBarButton.h"
#include <KoIcon.h>
#include <kis_icon_utils.h>
#include <WidgetsDebug.h>
#include <klocalizedstring.h>
#include <QAbstractButton>
#include <QAction>
#include <QLabel>
#include <QLayout>
#include <QStyle>
#include <QStylePainter>
#include <QStyleOptionFrame>
static inline bool hasFeature(const QDockWidget *dockwidget, QDockWidget::DockWidgetFeature feature)
{
return (dockwidget->features() & feature) == feature;
}
KoDockWidgetTitleBar::KoDockWidgetTitleBar(QDockWidget* dockWidget)
: QWidget(dockWidget), d(new Private(this))
{
QDockWidget *q = dockWidget;
d->floatIcon = kisIcon("docker_float");
d->floatButton = new KoDockWidgetTitleBarButton(this);
d->floatButton->setIcon(d->floatIcon);
connect(d->floatButton, SIGNAL(clicked()), SLOT(toggleFloating()));
d->floatButton->setVisible(true);
d->floatButton->setToolTip(i18nc("@info:tooltip", "Float Docker"));
d->floatButton->setStyleSheet("border: 0");
d->removeIcon = kisIcon("docker_close");
d->closeButton = new KoDockWidgetTitleBarButton(this);
d->closeButton->setIcon(d->removeIcon);
connect(d->closeButton, SIGNAL(clicked()), q, SLOT(close()));
d->closeButton->setVisible(true);
d->closeButton->setToolTip(i18nc("@info:tooltip", "Close Docker"));
d->closeButton->setStyleSheet("border: 0"); // border makes the header busy looking (appears on some OSs)
d->openIcon = kisIcon("docker_collapse_a");
d->closeIcon = kisIcon("docker_collapse_b");
d->collapseButton = new KoDockWidgetTitleBarButton(this);
d->collapseButton->setIcon(d->openIcon);
connect(d->collapseButton, SIGNAL(clicked()), SLOT(toggleCollapsed()));
d->collapseButton->setVisible(true);
d->collapsable = true;
d->collapseButton->setToolTip(i18nc("@info:tooltip", "Collapse Docker"));
d->collapseButton->setStyleSheet("border: 0");
d->lockIcon = kisIcon("docker_lock_a");
d->lockButton = new KoDockWidgetTitleBarButton(this);
d->lockButton->setCheckable(true);
d->lockButton->setIcon(d->lockIcon);
connect(d->lockButton, SIGNAL(toggled(bool)), SLOT(setLocked(bool)));
d->lockButton->setVisible(true);
d->lockable = true;
d->lockButton->setToolTip(i18nc("@info:tooltip", "Lock Docker"));
d->lockButton->setStyleSheet("border: 0");
connect(dockWidget, SIGNAL(featuresChanged(QDockWidget::DockWidgetFeatures)), SLOT(featuresChanged(QDockWidget::DockWidgetFeatures)));
connect(dockWidget, SIGNAL(topLevelChanged(bool)), SLOT(topLevelChanged(bool)));
d->featuresChanged(0);
}
KoDockWidgetTitleBar::~KoDockWidgetTitleBar()
{
delete d;
}
QSize KoDockWidgetTitleBar::minimumSizeHint() const
{
return sizeHint();
}
QSize KoDockWidgetTitleBar::sizeHint() const
{
if (isHidden()) {
return QSize(0, 0);
}
QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget());
int mw = q->style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, q);
int fw = q->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, q);
// get size of buttons...
QSize closeSize(0, 0);
if (d->closeButton && hasFeature(q, QDockWidget::DockWidgetClosable)) {
closeSize = d->closeButton->sizeHint();
}
QSize floatSize(0, 0);
if (d->floatButton && hasFeature(q, QDockWidget::DockWidgetFloatable)) {
floatSize = d->floatButton->sizeHint();
}
QSize hideSize(0, 0);
if (d->collapseButton && d->collapsable) {
hideSize = d->collapseButton->sizeHint();
}
QSize lockSize(0, 0);
if (d->lockButton && d->lockable) {
lockSize = d->lockButton->sizeHint();
}
int buttonHeight = qMax(qMax(qMax(closeSize.height(), floatSize.height()), hideSize.height()), lockSize.height()) + 2;
int buttonWidth = closeSize.width() + floatSize.width() + hideSize.width() + lockSize.width();
int height = buttonHeight;
if (d->textVisibilityMode == FullTextAlwaysVisible) {
// get font size
QFontMetrics titleFontMetrics = q->fontMetrics();
int fontHeight = titleFontMetrics.lineSpacing() + 2 * mw;
height = qMax(height, fontHeight);
}
/*
* Calculate the width of title and add to the total width of the docker window when collapsed.
*/
const int titleWidth =
(d->textVisibilityMode == FullTextAlwaysVisible) ? (q->fontMetrics().width(q->windowTitle()) + 2*mw) :
0;
if (d->preCollapsedWidth > 0) {
return QSize(d->preCollapsedWidth, height);
}
else {
if (d->textVisibilityMode == FullTextAlwaysVisible) {
return QSize(buttonWidth /*+ height*/ + 2*mw + 2*fw + titleWidth, height);
}
else {
if (q->widget()) {
return QSize(qMin(q->widget()->sizeHint().width(), buttonWidth), height);
}
else {
return QSize(buttonWidth, height);
}
}
}
}
void KoDockWidgetTitleBar::paintEvent(QPaintEvent*)
{
QStylePainter p(this);
QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget());
int fw = q->isFloating() ? q->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, q) : 0;
int mw = q->style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, q);
QStyleOptionDockWidget titleOpt;
titleOpt.initFrom(q);
QSize collapseButtonSize(0,0);
- if (d->collapsable) {
+ if (d->collapsable && d->collapseButton->isVisible()) {
collapseButtonSize = d->collapseButton->size();
}
QSize lockButtonSize(0,0);
- if (d->lockable) {
+ if (d->lockable && d->lockButton->isVisible()) {
lockButtonSize = d->lockButton->size();
}
titleOpt.rect = QRect(QPoint(fw + mw + collapseButtonSize.width() + lockButtonSize.width(), 0),
QSize(geometry().width() - (fw * 2) - mw - collapseButtonSize.width() - lockButtonSize.width(), geometry().height()));
titleOpt.title = q->windowTitle();
titleOpt.closable = hasFeature(q, QDockWidget::DockWidgetClosable);
titleOpt.floatable = hasFeature(q, QDockWidget::DockWidgetFloatable);
p.drawControl(QStyle::CE_DockWidgetTitle, titleOpt);
}
void KoDockWidgetTitleBar::resizeEvent(QResizeEvent*)
{
QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget());
int fw = q->isFloating() ? q->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, q) : 0;
QStyleOptionDockWidget opt;
opt.initFrom(q);
opt.rect = QRect(QPoint(fw, fw), QSize(geometry().width() - (fw * 2), geometry().height() - (fw * 2)));
opt.title = q->windowTitle();
opt.closable = hasFeature(q, QDockWidget::DockWidgetClosable);
opt.floatable = hasFeature(q, QDockWidget::DockWidgetFloatable);
QRect floatRect = q->style()->subElementRect(QStyle::SE_DockWidgetFloatButton, &opt, q);
if (!floatRect.isNull())
d->floatButton->setGeometry(floatRect);
QRect closeRect = q->style()->subElementRect(QStyle::SE_DockWidgetCloseButton, &opt, q);
if (!closeRect.isNull())
d->closeButton->setGeometry(closeRect);
int top = fw;
if (!floatRect.isNull())
top = floatRect.y();
else if (!closeRect.isNull())
top = closeRect.y();
QSize size = d->collapseButton->size();
if (!closeRect.isNull()) {
size = d->closeButton->size();
} else if (!floatRect.isNull()) {
size = d->floatButton->size();
}
QRect collapseRect = QRect(QPoint(fw, top), size);
d->collapseButton->setGeometry(collapseRect);
size = d->lockButton->size();
if (!closeRect.isNull()) {
size = d->closeButton->size();
} else if (!floatRect.isNull()) {
size = d->floatButton->size();
}
- int offset = 0;
-
- if (d->collapsable) {
- offset = collapseRect.width();
- }
- QRect lockRect = QRect(QPoint(fw + 2 + offset, top), size);
- d->lockButton->setGeometry(lockRect);
+ const QSize lockRectSize = size;
- if (width() < (closeRect.width() + lockRect.width()) + 50) {
+ if (q->isFloating() || (width() < (closeRect.width() + lockRectSize.width()) + 50)) {
d->collapsable = false;
d->collapseButton->setVisible(false);
d->lockButton->setVisible(false);
d->lockable = false;
} else {
d->collapsable = d->collapsableSet;
d->collapseButton->setVisible(d->collapsableSet);
d->lockButton->setVisible(true);
d->lockable = true;
}
+
+ int offset = 0;
+
+ if (d->collapsable) {
+ offset = collapseRect.width();
+ }
+ QRect lockRect = QRect(QPoint(fw + 2 + offset, top), lockRectSize);
+ d->lockButton->setGeometry(lockRect);
}
void KoDockWidgetTitleBar::setCollapsed(bool collapsed)
{
QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget());
if (q && q->widget() && q->widget()->isHidden() != collapsed)
d->toggleCollapsed();
}
void KoDockWidgetTitleBar::setLocked(bool locked)
{
QDockWidget *q = qobject_cast<QDockWidget*>(parentWidget());
d->locked = locked;
d->lockButton->blockSignals(true);
d->lockButton->setChecked(locked);
d->lockButton->blockSignals(false);
//qDebug() << "setlocked" << q << d->locked << locked;
if (locked) {
d->features = q->features();
q->setFeatures(QDockWidget::NoDockWidgetFeatures);
}
else {
q->setFeatures(d->features);
}
q->toggleViewAction()->setEnabled(!locked);
d->closeButton->setEnabled(!locked);
d->floatButton->setEnabled(!locked);
d->collapseButton->setEnabled(!locked);
d->updateIcons();
q->setProperty("Locked", locked);
resizeEvent(0);
}
void KoDockWidgetTitleBar::setCollapsable(bool collapsable)
{
d->collapsableSet = collapsable;
d->collapsable = collapsable;
d->collapseButton->setVisible(collapsable);
}
void KoDockWidgetTitleBar::setTextVisibilityMode(TextVisibilityMode textVisibilityMode)
{
d->textVisibilityMode = textVisibilityMode;
}
void KoDockWidgetTitleBar::updateIcons()
{
d->updateIcons();
}
void KoDockWidgetTitleBar::Private::toggleFloating()
{
QDockWidget *q = qobject_cast<QDockWidget*>(thePublic->parentWidget());
q->setFloating(!q->isFloating());
+ updateIcons();
}
void KoDockWidgetTitleBar::Private::topLevelChanged(bool topLevel)
{
lockButton->setEnabled(!topLevel);
+ updateIcons();
}
void KoDockWidgetTitleBar::Private::toggleCollapsed()
{
QDockWidget *q = qobject_cast<QDockWidget*>(thePublic->parentWidget());
if (q == 0) // there does not *have* to be anything on the dockwidget.
return;
preCollapsedWidth = q->widget()->isHidden() ? -1 : thePublic->width();
q->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); // will be overwritten again next
if (q->widget()) {
q->widget()->setVisible(q->widget()->isHidden());
collapseButton->setIcon(q->widget()->isHidden() ? kisIcon("docker_collapse_b") : kisIcon("docker_collapse_a"));
}
}
void KoDockWidgetTitleBar::Private::featuresChanged(QDockWidget::DockWidgetFeatures)
{
QDockWidget *q = qobject_cast<QDockWidget*>(thePublic->parentWidget());
closeButton->setVisible(hasFeature(q, QDockWidget::DockWidgetClosable));
floatButton->setVisible(hasFeature(q, QDockWidget::DockWidgetFloatable));
thePublic->resizeEvent(0);
}
void KoDockWidgetTitleBar::Private::updateIcons()
{
QDockWidget *q = qobject_cast<QDockWidget*>(thePublic->parentWidget());
lockIcon = (!locked) ? kisIcon("docker_lock_a") : kisIcon("docker_lock_b");
lockButton->setIcon(lockIcon);
// this method gets called when switching themes, so update all of the themed icons now
floatButton->setIcon(kisIcon("docker_float"));
closeButton->setIcon(kisIcon("docker_close"));
if (q->widget()) {
collapseButton->setIcon(q->widget()->isHidden() ? kisIcon("docker_collapse_b") : kisIcon("docker_collapse_a"));
}
thePublic->resizeEvent(0);
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoDockWidgetTitleBar.cpp"
diff --git a/libs/widgets/KoLineStyleModel.cpp b/libs/widgets/KoLineStyleModel.cpp
index 6260d17b74..eb030806f2 100644
--- a/libs/widgets/KoLineStyleModel.cpp
+++ b/libs/widgets/KoLineStyleModel.cpp
@@ -1,101 +1,107 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* 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 "KoLineStyleModel_p.h"
#include <QPen>
KoLineStyleModel::KoLineStyleModel(QObject *parent)
: QAbstractListModel(parent),
m_hasTempStyle(false)
{
// add standard dash patterns
for (int i = Qt::NoPen; i < Qt::CustomDashLine; i++) {
QPen pen(static_cast<Qt::PenStyle>(i));
m_styles << pen.dashPattern();
}
}
int KoLineStyleModel::rowCount(const QModelIndex &/*parent*/) const
{
return m_styles.count() + (m_hasTempStyle ? 1 : 0);
}
QVariant KoLineStyleModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
switch(role) {
case Qt::DecorationRole: {
QPen pen(Qt::black);
pen.setWidth(2);
if (index.row() < Qt::CustomDashLine)
pen.setStyle(static_cast<Qt::PenStyle>(index.row()));
else if (index.row() < m_styles.count())
pen.setDashPattern(m_styles[index.row()]);
else if (m_hasTempStyle)
pen.setDashPattern(m_tempStyle);
else
pen.setStyle(Qt::NoPen);
return QVariant(pen);
}
case Qt::SizeHintRole:
return QSize(100, 15);
default:
return QVariant();
}
}
bool KoLineStyleModel::addCustomStyle(const QVector<qreal> &style)
{
if (m_styles.contains(style))
return false;
m_styles.append(style);
return true;
}
int KoLineStyleModel::setLineStyle(Qt::PenStyle style, const QVector<qreal> &dashes)
{
// check if we select a standard or custom style
if (style < Qt::CustomDashLine) {
// a standard style
m_hasTempStyle = false;
- reset();
+ beginResetModel();
+ endResetModel();
+
return style;
} else if (style == Qt::CustomDashLine) {
// a custom style -> check if already added
int index = m_styles.indexOf(dashes, Qt::CustomDashLine);
if (index < 0) {
// not already added -> add temporarly
m_tempStyle = dashes;
m_hasTempStyle = true;
- reset();
+ beginResetModel();
+ endResetModel();
+
return m_styles.count();
} else {
// already added -> return index
m_hasTempStyle = false;
- reset();
+ beginResetModel();
+ endResetModel();
+
return index;
}
}
return -1;
}
diff --git a/libs/widgets/KoResourceModel.cpp b/libs/widgets/KoResourceModel.cpp
index a423abde35..194e90af38 100644
--- a/libs/widgets/KoResourceModel.cpp
+++ b/libs/widgets/KoResourceModel.cpp
@@ -1,325 +1,327 @@
/* This file is part of the KDE project
* Copyright (C) 2008-2009 Jan Hambrecht <jaham@gmx.net>
* Copyright (c) 2013 Sascha Suelzer <s.suelzer@gmail.com>
*
* 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 "KoResourceModel.h"
#include <klocalizedstring.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <KoResourceServerAdapter.h>
#include <math.h>
KoResourceModel::KoResourceModel(QSharedPointer<KoAbstractResourceServerAdapter> resourceAdapter, QObject * parent)
: KoResourceModelBase(parent)
, m_resourceAdapter(resourceAdapter)
, m_columnCount(4)
{
Q_ASSERT(m_resourceAdapter);
m_resourceAdapter->connectToResourceServer();
connect(m_resourceAdapter.data(), SIGNAL(resourceAdded(KoResource*)),
this, SLOT(resourceAdded(KoResource*)));
connect(m_resourceAdapter.data(), SIGNAL(removingResource(KoResource*)),
this, SLOT(resourceRemoved(KoResource*)));
connect(m_resourceAdapter.data(), SIGNAL(resourceChanged(KoResource*)),
this, SLOT(resourceChanged(KoResource*)));
connect(m_resourceAdapter.data(), SIGNAL(tagsWereChanged()),
this, SLOT(tagBoxEntryWasModified()));
connect(m_resourceAdapter.data(), SIGNAL(tagCategoryWasAdded(QString)),
this, SLOT(tagBoxEntryWasAdded(QString)));
connect(m_resourceAdapter.data(), SIGNAL(tagCategoryWasRemoved(QString)),
this, SLOT(tagBoxEntryWasRemoved(QString)));
}
KoResourceModel::~KoResourceModel()
{
if (!m_currentTag.isEmpty()) {
KConfigGroup group = KSharedConfig::openConfig()->group("SelectedTags");
group.writeEntry(serverType(), m_currentTag);
}
}
int KoResourceModel::rowCount( const QModelIndex &/*parent*/ ) const
{
int resourceCount = m_resourceAdapter->resources().count();
if (!resourceCount)
return 0;
return static_cast<int>(ceil(static_cast<qreal>(resourceCount) / m_columnCount));
}
int KoResourceModel::columnCount ( const QModelIndex & ) const
{
return m_columnCount;
}
QVariant KoResourceModel::data( const QModelIndex &index, int role ) const
{
if( ! index.isValid() )
return QVariant();
switch( role )
{
case Qt::DisplayRole:
{
KoResource * resource = static_cast<KoResource*>(index.internalPointer());
if( ! resource )
return QVariant();
QString resName = i18n( resource->name().toUtf8().data());
return QVariant( resName );
}
case KoResourceModel::TagsRole:
{
KoResource * resource = static_cast<KoResource*>(index.internalPointer());
if( ! resource )
return QVariant();
if (m_resourceAdapter->assignedTagsList(resource).count()) {
QString taglist = m_resourceAdapter->assignedTagsList(resource).join("</li><li>");
return QString("<li>%2</li>").arg(taglist);
} else {
return QString();
}
}
case Qt::DecorationRole:
{
KoResource * resource = static_cast<KoResource*>(index.internalPointer());
if( ! resource )
return QVariant();
return QVariant( resource->image() );
}
case KoResourceModel::LargeThumbnailRole:
{
KoResource * resource = static_cast<KoResource*>(index.internalPointer());
if( ! resource )
return QVariant();
QSize imageSize = resource->image().size();
QSize thumbSize( 100, 100 );
if(imageSize.height() > thumbSize.height() || imageSize.width() > thumbSize.width()) {
qreal scaleW = static_cast<qreal>( thumbSize.width() ) / static_cast<qreal>( imageSize.width() );
qreal scaleH = static_cast<qreal>( thumbSize.height() ) / static_cast<qreal>( imageSize.height() );
qreal scale = qMin( scaleW, scaleH );
int thumbW = static_cast<int>( imageSize.width() * scale );
int thumbH = static_cast<int>( imageSize.height() * scale );
return QVariant(resource->image().scaled( thumbW, thumbH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
else
return QVariant(resource->image());
}
default:
return QVariant();
}
}
QModelIndex KoResourceModel::index ( int row, int column, const QModelIndex & ) const
{
int index = row * m_columnCount + column;
const QList<KoResource*> resources = m_resourceAdapter->resources();
if( index >= resources.count() || index < 0)
return QModelIndex();
return createIndex( row, column, resources[index] );
}
void KoResourceModel::doSafeLayoutReset(KoResource *activateAfterReformat)
{
emit beforeResourcesLayoutReset(activateAfterReformat);
- reset();
+ beginResetModel();
+ endResetModel();
emit afterResourcesLayoutReset();
}
void KoResourceModel::setColumnCount( int columnCount )
{
if (columnCount != m_columnCount) {
emit beforeResourcesLayoutReset(0);
m_columnCount = columnCount;
- reset();
+ beginResetModel();
+ endResetModel();
emit afterResourcesLayoutReset();
}
}
void KoResourceModel::resourceAdded(KoResource *resource)
{
int newIndex = m_resourceAdapter->resources().indexOf(resource);
if (newIndex >= 0) {
doSafeLayoutReset(0);
}
}
void KoResourceModel::resourceRemoved(KoResource *resource)
{
Q_UNUSED(resource);
doSafeLayoutReset(0);
}
void KoResourceModel::resourceChanged(KoResource* resource)
{
int resourceIndex = m_resourceAdapter->resources().indexOf(resource);
int row = resourceIndex / columnCount();
int column = resourceIndex % columnCount();
QModelIndex modelIndex = index(row, column);
if (!modelIndex.isValid()) {
return;
}
emit dataChanged(modelIndex, modelIndex);
}
void KoResourceModel::tagBoxEntryWasModified()
{
m_resourceAdapter->updateServer();
emit tagBoxEntryModified();
}
void KoResourceModel::tagBoxEntryWasAdded(const QString& tag)
{
emit tagBoxEntryAdded(tag);
}
void KoResourceModel::tagBoxEntryWasRemoved(const QString& tag)
{
emit tagBoxEntryRemoved(tag);
}
QModelIndex KoResourceModel::indexFromResource(KoResource* resource) const
{
int resourceIndex = m_resourceAdapter->resources().indexOf(resource);
if (columnCount() > 0) {
int row = resourceIndex / columnCount();
int column = resourceIndex % columnCount();
return index(row, column);
}
return QModelIndex();
}
QString KoResourceModel::extensions() const
{
return m_resourceAdapter->extensions();
}
void KoResourceModel::importResourceFile(const QString &filename)
{
m_resourceAdapter->importResourceFile(filename);
}
void KoResourceModel::importResourceFile(const QString & filename, bool fileCreation)
{
m_resourceAdapter->importResourceFile(filename, fileCreation);
}
bool KoResourceModel::removeResource(KoResource* resource)
{
return m_resourceAdapter->removeResource(resource);
}
void KoResourceModel::removeResourceFile(const QString &filename)
{
m_resourceAdapter->removeResourceFile(filename);
}
QStringList KoResourceModel::assignedTagsList(KoResource *resource) const
{
return m_resourceAdapter->assignedTagsList(resource);
}
void KoResourceModel::addTag(KoResource* resource, const QString& tag)
{
m_resourceAdapter->addTag(resource, tag);
emit tagBoxEntryAdded(tag);
}
void KoResourceModel::deleteTag(KoResource *resource, const QString &tag)
{
m_resourceAdapter->deleteTag(resource, tag);
}
QStringList KoResourceModel::tagNamesList() const
{
return m_resourceAdapter->tagNamesList();
}
QStringList KoResourceModel::searchTag(const QString& lineEditText)
{
return m_resourceAdapter->searchTag(lineEditText);
}
void KoResourceModel::searchTextChanged(const QString& searchString)
{
m_resourceAdapter->searchTextChanged(searchString);
}
void KoResourceModel::enableResourceFiltering(bool enable)
{
m_resourceAdapter->enableResourceFiltering(enable);
}
void KoResourceModel::setCurrentTag(const QString& currentTag)
{
m_currentTag = currentTag;
m_resourceAdapter->setCurrentTag(currentTag);
}
void KoResourceModel::updateServer()
{
m_resourceAdapter->updateServer();
}
int KoResourceModel::resourcesCount() const
{
return m_resourceAdapter->resources().count();
}
QList<KoResource *> KoResourceModel::currentlyVisibleResources() const
{
return m_resourceAdapter->resources();
}
void KoResourceModel::tagCategoryMembersChanged()
{
m_resourceAdapter->tagCategoryMembersChanged();
}
void KoResourceModel::tagCategoryAdded(const QString& tag)
{
m_resourceAdapter->tagCategoryAdded(tag);
}
void KoResourceModel::tagCategoryRemoved(const QString& tag)
{
m_resourceAdapter->tagCategoryRemoved(tag);
}
QString KoResourceModel::serverType() const
{
return m_resourceAdapter->serverType();
}
QList< KoResource* > KoResourceModel::serverResources() const
{
return m_resourceAdapter->serverResources();
}
diff --git a/libs/widgets/kis_file_name_requester.cpp b/libs/widgets/kis_file_name_requester.cpp
index db21da770e..3942e80a1f 100644
--- a/libs/widgets/kis_file_name_requester.cpp
+++ b/libs/widgets/kis_file_name_requester.cpp
@@ -1,103 +1,104 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_file_name_requester.h"
#include "ui_wdg_file_name_requester.h"
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QDebug>
#include "KoIcon.h"
KisFileNameRequester::KisFileNameRequester(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::WdgFileNameRequester)
, m_mode(KoFileDialog::OpenFile)
{
m_ui->setupUi(this);
m_ui->btnSelectFile->setIcon(kisIcon("folder"));
connect(m_ui->btnSelectFile, SIGNAL(clicked()), SLOT(slotSelectFile()));
connect(m_ui->txtFileName, SIGNAL(textChanged(const QString&)), SIGNAL(textChanged(const QString&)));
}
KisFileNameRequester::~KisFileNameRequester()
{
}
void KisFileNameRequester::setStartDir(const QString &path)
{
m_basePath = path;
}
void KisFileNameRequester::setFileName(const QString &path)
{
m_ui->txtFileName->setText(path);
+ m_basePath = path;
emit fileSelected(path);
}
QString KisFileNameRequester::fileName() const
{
return m_ui->txtFileName->text();
}
void KisFileNameRequester::setMode(KoFileDialog::DialogType mode)
{
m_mode = mode;
}
KoFileDialog::DialogType KisFileNameRequester::mode() const
{
return m_mode;
}
void KisFileNameRequester::setMimeTypeFilters(const QStringList &filterList,
QString defaultFilter)
{
m_mime_filter_list = filterList;
m_mime_default_filter = defaultFilter;
}
void KisFileNameRequester::slotSelectFile()
{
KoFileDialog dialog(this, m_mode, "OpenDocument");
if (m_mode == KoFileDialog::OpenFile)
{
dialog.setCaption(i18n("Select a file to load..."));
}
else if (m_mode == KoFileDialog::OpenDirectory)
{
dialog.setCaption(i18n("Select a directory to load..."));
}
if (m_basePath.isEmpty()) {
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
}
else {
dialog.setDefaultDir(m_basePath);
}
Q_ASSERT(!m_mime_filter_list.isEmpty());
dialog.setMimeTypeFilters(m_mime_filter_list, m_mime_default_filter);
QString newFileName = dialog.filename();
if (!newFileName.isEmpty()) {
setFileName(newFileName);
}
}
diff --git a/libs/widgetutils/KoResourcePaths.cpp b/libs/widgetutils/KoResourcePaths.cpp
index 6e53a16c52..1ea1d30d27 100644
--- a/libs/widgetutils/KoResourcePaths.cpp
+++ b/libs/widgetutils/KoResourcePaths.cpp
@@ -1,564 +1,564 @@
/*
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "KoResourcePaths.h"
#include <QGlobalStatic>
#include <QString>
#include <QStringList>
#include <QMap>
#include <QStandardPaths>
#include <QDir>
#include <QFileInfo>
#include <QDebug>
#include <QApplication>
#include <QMutex>
#include "kis_debug.h"
#include "WidgetUtilsDebug.h"
Q_GLOBAL_STATIC(KoResourcePaths, s_instance);
static QString cleanup(const QString &path)
{
return QDir::cleanPath(path);
}
static QStringList cleanup(const QStringList &pathList)
{
QStringList cleanedPathList;
Q_FOREACH(const QString &path, pathList) {
cleanedPathList << cleanup(path);
}
return cleanedPathList;
}
static QString cleanupDirs(const QString &path)
{
return QDir::cleanPath(path) + QDir::separator();
}
static QStringList cleanupDirs(const QStringList &pathList)
{
QStringList cleanedPathList;
Q_FOREACH(const QString &path, pathList) {
cleanedPathList << cleanupDirs(path);
}
return cleanedPathList;
}
void appendResources(QStringList *dst, const QStringList &src, bool eliminateDuplicates)
{
Q_FOREACH (const QString &resource, src) {
QString realPath = QDir::cleanPath(resource);
if (!eliminateDuplicates || !dst->contains(realPath)) {
*dst << realPath;
}
}
}
#ifdef Q_OS_WIN
static const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
#else
static const Qt::CaseSensitivity cs = Qt::CaseSensitive;
#endif
#ifdef Q_OS_OSX
#include <ApplicationServices/ApplicationServices.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#endif
QString getInstallationPrefix() {
#ifdef Q_OS_OSX
QString appPath = qApp->applicationDirPath();
debugWidgetUtils << "1" << appPath;
appPath.chop(QString("MacOS/").length());
debugWidgetUtils << "2" << appPath;
bool makeInstall = QDir(appPath + "/../../../share/kritaplugins").exists();
bool inBundle = QDir(appPath + "/Resources/kritaplugins").exists();
debugWidgetUtils << "3. After make install" << makeInstall;
debugWidgetUtils << "4. In Bundle" << inBundle;
QString bundlePath;
if (inBundle) {
bundlePath = appPath + "/";
}
else if (makeInstall) {
appPath.chop(QString("Contents/").length());
bundlePath = appPath + "/../../";
}
else {
qFatal("Cannot calculate the bundle path from the app path");
}
debugWidgetUtils << ">>>>>>>>>>>" << bundlePath;
return bundlePath;
#else
#ifdef Q_OS_QWIN
QDir appdir(qApp->applicationDirPath());
// Corrects for mismatched case errors in path (qtdeclarative fails to load)
wchar_t buffer[1024];
QString absolute = appdir.absolutePath();
DWORD rv = ::GetShortPathName((wchar_t*)absolute.utf16(), buffer, 1024);
rv = ::GetLongPathName(buffer, buffer, 1024);
QString correctedPath((QChar *)buffer);
appdir.setPath(correctedPath);
appdir.cdUp();
return appdir.canonicalPath();
#else
return qApp->applicationDirPath() + "/../";
#endif
#endif
}
class Q_DECL_HIDDEN KoResourcePaths::Private {
public:
QMap<QString, QStringList> absolutes; // For each resource type, the list of absolute paths, from most local (most priority) to most global
QMap<QString, QStringList> relatives; // Same with relative paths
QMutex relativesMutex;
QMutex absolutesMutex;
bool ready = false; // Paths have been initialized
QStringList aliases(const QString &type)
{
QStringList r;
QStringList a;
relativesMutex.lock();
if (relatives.contains(type)) {
r += relatives[type];
}
relativesMutex.unlock();
debugWidgetUtils << "\trelatives" << r;
absolutesMutex.lock();
if (absolutes.contains(type)) {
a += absolutes[type];
}
debugWidgetUtils << "\tabsolutes" << a;
absolutesMutex.unlock();
return r + a;
}
QStandardPaths::StandardLocation mapTypeToQStandardPaths(const QString &type)
{
if (type == "tmp") {
return QStandardPaths::TempLocation;
}
else if (type == "appdata") {
return QStandardPaths::AppDataLocation;
}
else if (type == "data") {
return QStandardPaths::AppDataLocation;
}
else if (type == "cache") {
return QStandardPaths::CacheLocation;
}
else if (type == "locale") {
return QStandardPaths::AppDataLocation;
}
else {
return QStandardPaths::AppDataLocation;
}
}
};
KoResourcePaths::KoResourcePaths()
: d(new Private)
{
}
KoResourcePaths::~KoResourcePaths()
{
}
QString KoResourcePaths::getApplicationRoot()
{
return getInstallationPrefix();
}
void KoResourcePaths::addResourceType(const char *type, const char *basetype,
const QString &relativeName, bool priority)
{
s_instance->addResourceTypeInternal(QString::fromLatin1(type), QString::fromLatin1(basetype), relativeName, priority);
}
void KoResourcePaths::addResourceDir(const char *type, const QString &dir, bool priority)
{
s_instance->addResourceDirInternal(QString::fromLatin1(type), dir, priority);
}
QString KoResourcePaths::findResource(const char *type, const QString &fileName)
{
return cleanup(s_instance->findResourceInternal(QString::fromLatin1(type), fileName));
}
QStringList KoResourcePaths::findDirs(const char *type)
{
return cleanupDirs(s_instance->findDirsInternal(QString::fromLatin1(type)));
}
QStringList KoResourcePaths::findAllResources(const char *type,
const QString &filter,
SearchOptions options)
{
return cleanup(s_instance->findAllResourcesInternal(QString::fromLatin1(type), filter, options));
}
QStringList KoResourcePaths::resourceDirs(const char *type)
{
return cleanupDirs(s_instance->resourceDirsInternal(QString::fromLatin1(type)));
}
QString KoResourcePaths::saveLocation(const char *type, const QString &suffix, bool create)
{
return cleanupDirs(s_instance->saveLocationInternal(QString::fromLatin1(type), suffix, create));
}
QString KoResourcePaths::locate(const char *type, const QString &filename)
{
return cleanup(s_instance->locateInternal(QString::fromLatin1(type), filename));
}
QString KoResourcePaths::locateLocal(const char *type, const QString &filename, bool createDir)
{
return cleanup(s_instance->locateLocalInternal(QString::fromLatin1(type), filename, createDir));
}
void KoResourcePaths::addResourceTypeInternal(const QString &type, const QString &basetype,
const QString &relativename,
bool priority)
{
Q_UNUSED(basetype);
if (relativename.isEmpty()) return;
QString copy = relativename;
Q_ASSERT(basetype == "data");
if (!copy.endsWith(QLatin1Char('/'))) {
copy += QLatin1Char('/');
}
d->relativesMutex.lock();
QStringList &rels = d->relatives[type]; // find or insert
if (!rels.contains(copy, cs)) {
if (priority) {
rels.prepend(copy);
} else {
rels.append(copy);
}
}
d->relativesMutex.unlock();
debugWidgetUtils << "addResourceType: type" << type << "basetype" << basetype << "relativename" << relativename << "priority" << priority << d->relatives[type];
}
void KoResourcePaths::addResourceDirInternal(const QString &type, const QString &absdir, bool priority)
{
if (absdir.isEmpty() || type.isEmpty()) return;
// find or insert entry in the map
QString copy = absdir;
if (copy.at(copy.length() - 1) != QLatin1Char('/')) {
copy += QLatin1Char('/');
}
d->absolutesMutex.lock();
QStringList &paths = d->absolutes[type];
if (!paths.contains(copy, cs)) {
if (priority) {
paths.prepend(copy);
} else {
paths.append(copy);
}
}
d->absolutesMutex.unlock();
debugWidgetUtils << "addResourceDir: type" << type << "absdir" << absdir << "priority" << priority << d->absolutes[type];
}
QString KoResourcePaths::findResourceInternal(const QString &type, const QString &fileName)
{
QStringList aliases = d->aliases(type);
debugWidgetUtils << "aliases" << aliases << getApplicationRoot();
QString resource = QStandardPaths::locate(QStandardPaths::AppDataLocation, fileName, QStandardPaths::LocateFile);
if (resource.isEmpty()) {
Q_FOREACH (const QString &alias, aliases) {
resource = QStandardPaths::locate(d->mapTypeToQStandardPaths(type), alias + '/' + fileName, QStandardPaths::LocateFile);
debugWidgetUtils << "\t1" << resource;
if (QFile::exists(resource)) {
continue;
}
}
}
if (resource.isEmpty() || !QFile::exists(resource)) {
QString approot = getApplicationRoot();
Q_FOREACH (const QString &alias, aliases) {
resource = approot + "/share/" + alias + '/' + fileName;
debugWidgetUtils << "\t1" << resource;
if (QFile::exists(resource)) {
continue;
}
}
}
if (resource.isEmpty() || !QFile::exists(resource)) {
QString approot = getApplicationRoot();
Q_FOREACH (const QString &alias, aliases) {
resource = approot + "/share/krita/" + alias + '/' + fileName;
debugWidgetUtils << "\t1" << resource;
if (QFile::exists(resource)) {
continue;
}
}
}
debugWidgetUtils << "findResource: type" << type << "filename" << fileName << "resource" << resource;
Q_ASSERT(!resource.isEmpty());
return resource;
}
QStringList KoResourcePaths::findDirsInternal(const QString &type)
{
QStringList aliases = d->aliases(type);
debugWidgetUtils << type << aliases << d->mapTypeToQStandardPaths(type);
QStringList dirs;
QStringList standardDirs =
QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), "", QStandardPaths::LocateDirectory);
appendResources(&dirs, standardDirs, true);
Q_FOREACH (const QString &alias, aliases) {
QStringList aliasDirs =
QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias + '/', QStandardPaths::LocateDirectory);
appendResources(&dirs, aliasDirs, true);
#ifdef Q_OS_OSX
debugWidgetUtils << "MAC:" << getApplicationRoot();
QStringList bundlePaths;
bundlePaths << getApplicationRoot() + "/share/krita/" + alias;
bundlePaths << getApplicationRoot() + "/../share/krita/" + alias;
debugWidgetUtils << "bundlePaths" << bundlePaths;
appendResources(&dirs, bundlePaths, true);
Q_ASSERT(!dirs.isEmpty());
#endif
QStringList fallbackPaths;
fallbackPaths << getApplicationRoot() + "/share/" + alias;
fallbackPaths << getApplicationRoot() + "/share/krita/" + alias;
appendResources(&dirs, fallbackPaths, true);
}
debugWidgetUtils << "findDirs: type" << type << "resource" << dirs;
return dirs;
}
QStringList filesInDir(const QString &startdir, const QString & filter, bool recursive)
{
debugWidgetUtils << "filesInDir: startdir" << startdir << "filter" << filter << "recursive" << recursive;
QStringList result;
// First the entries in this path
QStringList nameFilters;
nameFilters << filter;
const QStringList fileNames = QDir(startdir).entryList(nameFilters, QDir::Files | QDir::CaseSensitive, QDir::Name);
debugWidgetUtils << "\tFound:" << fileNames.size() << ":" << fileNames;
Q_FOREACH (const QString &fileName, fileNames) {
QString file = startdir + '/' + fileName;
result << file;
}
// And then everything underneath, if recursive is specified
if (recursive) {
const QStringList entries = QDir(startdir).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
Q_FOREACH (const QString &subdir, entries) {
debugWidgetUtils << "\tGoing to look in subdir" << subdir << "of" << startdir;
result << filesInDir(startdir + '/' + subdir, filter, recursive);
}
}
return result;
}
QStringList KoResourcePaths::findAllResourcesInternal(const QString &type,
const QString &_filter,
SearchOptions options) const
{
debugWidgetUtils << "=====================================================";
debugWidgetUtils << type << _filter << QStandardPaths::standardLocations(d->mapTypeToQStandardPaths(type));
bool recursive = options & KoResourcePaths::Recursive;
debugWidgetUtils << "findAllResources: type" << type << "filter" << _filter << "recursive" << recursive;
QStringList aliases = d->aliases(type);
QString filter = _filter;
// In cases where the filter is like "color-schemes/*.colors" instead of "*.kpp", used with unregistered resource types
if (filter.indexOf('*') > 0) {
aliases << filter.split('*').first();
filter = '*' + filter.split('*')[1];
debugWidgetUtils << "Split up alias" << aliases << "filter" << filter;
}
QStringList resources;
if (aliases.isEmpty()) {
QStringList standardResources =
QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type),
filter, QStandardPaths::LocateFile);
appendResources(&resources, standardResources, true);
}
debugWidgetUtils << "\tresources from qstandardpaths:" << resources.size();
Q_FOREACH (const QString &alias, aliases) {
debugWidgetUtils << "\t\talias:" << alias;
QStringList dirs;
QFileInfo dirInfo(alias);
if (dirInfo.exists() && dirInfo.isDir() && dirInfo.isAbsolute()) {
dirs << alias;
} else {
dirs << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory)
<< getInstallationPrefix() + "share/" + alias + "/"
<< getInstallationPrefix() + "share/krita/" + alias + "/";
}
Q_FOREACH (const QString &dir, dirs) {
appendResources(&resources,
filesInDir(dir, filter, recursive),
true);
}
}
debugWidgetUtils << "\tresources also from aliases:" << resources.size();
// if the original filter is "input/*", we only want share/input/* and share/krita/input/* here, but not
// share/*. therefore, use _filter here instead of filter which was split into alias and "*".
QFileInfo fi(_filter);
QStringList prefixResources;
prefixResources << filesInDir(getInstallationPrefix() + "share/" + fi.path(), fi.fileName(), false);
prefixResources << filesInDir(getInstallationPrefix() + "share/krita/" + fi.path(), fi.fileName(), false);
appendResources(&resources, prefixResources, true);
debugWidgetUtils << "\tresources from installation:" << resources.size();
debugWidgetUtils << "=====================================================";
return resources;
}
QStringList KoResourcePaths::resourceDirsInternal(const QString &type)
{
QStringList resourceDirs;
QStringList aliases = d->aliases(type);
Q_FOREACH (const QString &alias, aliases) {
QStringList aliasDirs;
aliasDirs << QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory);
aliasDirs << getInstallationPrefix() + "share/" + alias + "/"
<< QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory);
aliasDirs << getInstallationPrefix() + "share/krita/" + alias + "/"
<< QStandardPaths::locateAll(d->mapTypeToQStandardPaths(type), alias, QStandardPaths::LocateDirectory);
appendResources(&resourceDirs, aliasDirs, true);
}
debugWidgetUtils << "resourceDirs: type" << type << resourceDirs;
return resourceDirs;
}
QString KoResourcePaths::saveLocationInternal(const QString &type, const QString &suffix, bool create)
{
QStringList aliases = d->aliases(type);
QString path;
if (aliases.size() > 0) {
path = QStandardPaths::writableLocation(d->mapTypeToQStandardPaths(type)) + '/' + aliases.first();
}
else {
path = QStandardPaths::writableLocation(d->mapTypeToQStandardPaths(type));
if (!path.endsWith("krita")) {
path += "/krita";
}
if (!suffix.isEmpty()) {
path += "/" + suffix;
}
}
QDir d(path);
if (!d.exists() && create) {
d.mkpath(path);
}
debugWidgetUtils << "saveLocation: type" << type << "suffix" << suffix << "create" << create << "path" << path;
return path;
}
QString KoResourcePaths::locateInternal(const QString &type, const QString &filename)
{
QStringList aliases = d->aliases(type);
QStringList locations;
if (aliases.isEmpty()) {
locations << QStandardPaths::locate(d->mapTypeToQStandardPaths(type), filename, QStandardPaths::LocateFile);
}
Q_FOREACH (const QString &alias, aliases) {
locations << QStandardPaths::locate(d->mapTypeToQStandardPaths(type),
(alias.endsWith('/') ? alias : alias + '/') + filename, QStandardPaths::LocateFile);
}
debugWidgetUtils << "locate: type" << type << "filename" << filename << "locations" << locations;
if (locations.size() > 0) {
return locations.first();
}
else {
return "";
}
}
QString KoResourcePaths::locateLocalInternal(const QString &type, const QString &filename, bool createDir)
{
QString path = saveLocationInternal(type, "", createDir);
debugWidgetUtils << "locateLocal: type" << type << "filename" << filename << "CreateDir" << createDir << "path" << path;
return path + '/' + filename;
}
void KoResourcePaths::setReady()
{
s_instance->d->ready = true;
}
-void KoResourcePaths::assertReady()
+bool KoResourcePaths::isReady()
{
- KIS_ASSERT_X(s_instance->d->ready, "KoResourcePaths::assertReady", "Resource paths are not ready yet.");
+ return s_instance->d->ready;
}
diff --git a/libs/widgetutils/KoResourcePaths.h b/libs/widgetutils/KoResourcePaths.h
index 0d9c5bb787..853ad89bbc 100644
--- a/libs/widgetutils/KoResourcePaths.h
+++ b/libs/widgetutils/KoResourcePaths.h
@@ -1,262 +1,263 @@
/*
* Copyright (c) 2015 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef KORESOURCEPATHS_H
#define KORESOURCEPATHS_H
#include <QScopedPointer>
#include <QString>
#include <QStringList>
#include <kritawidgetutils_export.h>
/**
* DEBUGGING KoResourcePaths:
*
* The usual place to look for resources is Qt's AppDataLocation.
* This corresponds to XDG_DATA_DIRS on Linux. To ensure your installation and
* path are configured correctly, ensure your files are located in the directories
* contained in this variable:
*
* QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
*
* There are many debug lines that can be uncommented for more specific installation
* checks. In the future these should be converted to qloggingcategory to enable
* convenient enable/disable functionality.
*/
class KRITAWIDGETUTILS_EXPORT KoResourcePaths
{
public:
KoResourcePaths();
virtual ~KoResourcePaths();
enum SearchOption { NoSearchOptions = 0,
Recursive = 1,
IgnoreExecBit = 4
};
Q_DECLARE_FLAGS(SearchOptions, SearchOption)
static QString getApplicationRoot();
/**
* Adds suffixes for types.
*
* You may add as many as you need, but it is advised that there
* is exactly one to make writing definite.
*
* The later a suffix is added, the higher its priority. Note, that the
* suffix should end with / but doesn't have to start with one (as prefixes
* should end with one). So adding a suffix for app_pics would look
* like KoStandardPaths::addResourceType("app_pics", "data", "app/pics");
*
* @param type Specifies a short descriptive string to access
* files of this type.
* @param basetype Specifies an already known type, or 0 if none
* @param relativename Specifies a directory relative to the basetype
* @param priority if true, the directory is added before any other,
* otherwise after
*/
static void addResourceType(const char *type, const char *basetype,
const QString &relativeName, bool priority = true);
/**
* Adds absolute path at the beginning of the search path for
* particular types (for example in case of icons where
* the user specifies extra paths).
*
* You shouldn't need this function in 99% of all cases besides
* adding user-given paths.
*
* @param type Specifies a short descriptive string to access files
* of this type.
* @param absdir Points to directory where to look for this specific
* type. Non-existent directories may be saved but pruned.
* @param priority if true, the directory is added before any other,
* otherwise after
*/
static void addResourceDir(const char *type, const QString &dir, bool priority = true);
/**
* Tries to find a resource in the following order:
* @li All PREFIX/\<relativename> paths (most recent first).
* @li All absolute paths (most recent first).
*
* The filename should be a filename relative to the base dir
* for resources. So is a way to get the path to libkdecore.la
* to findResource("lib", "libkdecore.la"). KStandardDirs will
* then look into the subdir lib of all elements of all prefixes
* ($KDEDIRS) for a file libkdecore.la and return the path to
* the first one it finds (e.g. /opt/kde/lib/libkdecore.la).
*
* Example:
* @code
* QString iconfilename = KStandardPaths::findResource("icon",QString("oxygen/22x22/apps/ktip.png"));
* @endcode
*
* @param type The type of the wanted resource
* @param filename A relative filename of the resource.
*
* @return A full path to the filename specified in the second
* argument, or QString() if not found.
*/
static QString findResource(const char *type, const QString &fileName);
/**
* Tries to find all directories whose names consist of the
* specified type and a relative path. So
* findDirs("xdgdata-apps", "Settings") would return
* @li /home/joe/.local/share/applications/Settings/
* @li /usr/share/applications/Settings/
*
* (from the most local to the most global)
*
* Note that it appends @c / to the end of the directories,
* so you can use this right away as directory names.
*
* @param type The type of the base directory.
* @param reldir Relative directory.
*
* @return A list of matching directories, or an empty
* list if the resource specified is not found.
*/
static QStringList findDirs(const char *type);
/**
* Tries to find all resources with the specified type.
*
* The function will look into all specified directories
* and return all filenames in these directories.
*
* The "most local" files are returned before the "more global" files.
*
* @param type The type of resource to locate directories for.
* @param filter Only accept filenames that fit to filter. The filter
* may consist of an optional directory and a QRegExp
* wildcard expression. E.g. <tt>"images\*.jpg"</tt>.
* Use QString() if you do not want a filter.
* @param options if the flags passed include Recursive, subdirectories
* will also be search.
*
* @return List of all the files whose filename matches the
* specified filter.
*/
static QStringList findAllResources(const char *type,
const QString &filter = QString(),
SearchOptions options = NoSearchOptions);
/**
* @param type The type of resource
* @return The list of possible directories for the specified @p type.
* The function updates the cache if possible. If the resource
* type specified is unknown, it will return an empty list.
* Note, that the directories are assured to exist beside the save
* location, which may not exist, but is returned anyway.
*/
static QStringList resourceDirs(const char *type);
/**
* Finds a location to save files into for the given type
* in the user's home directory.
*
* @param type The type of location to return.
* @param suffix A subdirectory name.
* Makes it easier for you to create subdirectories.
* You can't pass filenames here, you _have_ to pass
* directory names only and add possible filename in
* that directory yourself. A directory name always has a
* trailing slash ('/').
* @param create If set, saveLocation() will create the directories
* needed (including those given by @p suffix).
*
* @return A path where resources of the specified type should be
* saved, or QString() if the resource type is unknown.
*/
static QString saveLocation(const char *type, const QString &suffix = QString(), bool create = true);
/**
* This function is just for convenience. It simply calls
* KoResourcePaths::findResource((type, filename).
*
* @param type The type of the wanted resource, see KStandardDirs
* @param filename A relative filename of the resource
*
* @return A full path to the filename specified in the second
* argument, or QString() if not found
**/
static QString locate(const char *type, const QString &filename);
/**
* This function is much like locate. However it returns a
* filename suitable for writing to. No check is made if the
* specified @p filename actually exists. Missing directories
* are created. If @p filename is only a directory, without a
* specific file, @p filename must have a trailing slash.
*
* @param type The type of the wanted resource, see KStandardDirs
* @param filename A relative filename of the resource
*
* @return A full path to the filename specified in the second
* argument, or QString() if not found
**/
static QString locateLocal(const char *type, const QString &filename, bool createDir = false);
/**
* Indicate that resource paths have been initialized and users
* of this class may expect to load resources from the proper paths.
*/
static void setReady();
/**
- * Assert that all resource paths have been initialized.
+ * Return if resource paths have been initialized and users
+ * of this class may expect to load resources from the proper paths.
*/
- static void assertReady();
+ static bool isReady();
private:
void addResourceTypeInternal(const QString &type, const QString &basetype,
const QString &relativeName, bool priority);
void addResourceDirInternal(const QString &type, const QString &absdir, bool priority);
QString findResourceInternal(const QString &type, const QString &fileName);
QStringList findDirsInternal(const QString &type);
QStringList findAllResourcesInternal(const QString &type,
const QString &filter = QString(),
SearchOptions options = NoSearchOptions) const;
QStringList resourceDirsInternal(const QString &type);
QString saveLocationInternal(const QString &type, const QString &suffix = QString(), bool create = true);
QString locateInternal(const QString &type, const QString &filename);
QString locateLocalInternal(const QString &type, const QString &filename, bool createDir = false);
class Private;
QScopedPointer<Private> d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KoResourcePaths::SearchOptions)
#endif // KORESOURCEPATHS_H
diff --git a/libs/widgetutils/config/khelpclient.cpp b/libs/widgetutils/config/khelpclient.cpp
index 53a331e954..ba9ea43937 100644
--- a/libs/widgetutils/config/khelpclient.cpp
+++ b/libs/widgetutils/config/khelpclient.cpp
@@ -1,78 +1,79 @@
/* This file is part of the KDE libraries
* Copyright 2012 David Faure <faure@kde.org>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License or ( at
* your option ) version 3 or, at the discretion of KDE e.V. ( which shall
* act as a proxy as in section 14 of the GPLv3 ), any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "khelpclient.h"
#include "kdesktopfile.h"
#include <QCoreApplication>
#include <QUrl>
#include <QDirIterator>
#include <QUrlQuery>
+#include <QStandardPaths>
#include <QDesktopServices>
void KHelpClient::invokeHelp(const QString &anchor, const QString &_appname)
{
QString appname;
if (_appname.isEmpty()) {
appname = QCoreApplication::instance()->applicationName();
} else {
appname = _appname;
}
// Look for the .desktop file of the application
// was:
//KService::Ptr service(KService::serviceByDesktopName(appname));
//if (service)
// docPath = service->docPath();
// but we don't want to depend on KService here.
QString docPath;
const QStringList desktopDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
Q_FOREACH (const QString &dir, desktopDirs) {
QDirIterator it(dir, QStringList() << appname + QLatin1String(".desktop"), QDir::NoFilter, QDirIterator::Subdirectories);
while (it.hasNext()) {
const QString desktopPath(it.next());
KDesktopFile desktopFile(desktopPath);
docPath = desktopFile.readDocPath();
break;
}
}
// docPath could be a path or a full URL, I think.
QUrl url;
if (!docPath.isEmpty()) {
url = QUrl(QLatin1String("help:/")).resolved(QUrl(docPath));
} else {
url = QUrl(QString::fromLatin1("help:/%1/index.html").arg(appname));
}
if (!anchor.isEmpty()) {
QUrlQuery query(url);
query.addQueryItem(QString::fromLatin1("anchor"), anchor);
url.setQuery(query);
}
// launch khelpcenter, or a browser for URIs not handled by khelpcenter
QDesktopServices::openUrl(url);
}
diff --git a/libs/widgetutils/kis_icon_utils.cpp b/libs/widgetutils/kis_icon_utils.cpp
index 3fdba220d7..f47ed236cf 100644
--- a/libs/widgetutils/kis_icon_utils.cpp
+++ b/libs/widgetutils/kis_icon_utils.cpp
@@ -1,193 +1,213 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_icon_utils.h"
#include <QApplication>
#include <QAction>
#include <QAbstractButton>
#include <QComboBox>
#include <QIcon>
#include <QFile>
#include <QPair>
#include <QDebug>
#include <KoIcon.h>
-
+#if QT_VERSION >= 0x050900
+#define CACHE_ICONS
+#endif
namespace KisIconUtils
{
+#if defined CACHE_ICONS
+static QMap<QString, QIcon> s_cache;
+#endif
static QMap<qint64, QString> s_icons;
QIcon loadIcon(const QString &name)
{
-
+#if defined CACHE_ICONS
+ QMap<QString, QIcon>::const_iterator cached = s_cache.constFind(name);
+ if (cached != s_cache.constEnd()) {
+ return cached.value();
+ }
+#endif
// try load themed icon
const char * const prefix = useDarkIcons() ? "dark_" : "light_";
QString realName = QLatin1String(prefix) + name;
// Dark and light, no size specified
const QStringList names = { ":/pics/" + realName + ".png",
":/pics/" + realName + ".svg",
":/pics/" + realName + ".svgz",
":/pics/" + name + ".png",
":/pics/" + name + ".svg",
":/pics/" + name + ".svz",
":/" + realName + ".png",
":/" + realName + ".svg",
":/" + realName + ".svz",
":/" + name,
":/" + name + ".png",
":/" + name + ".svg",
":/" + name + ".svgz"};
for (const QString &resname : names) {
if (QFile(resname).exists()) {
QIcon icon(resname);
s_icons.insert(icon.cacheKey(), name);
+#if defined CACHE_ICONS
+ s_cache.insert(name, icon);
+#endif
return icon;
}
}
// Now check for icons with sizes
QStringList sizes = QStringList() << "16_" << "22_" << "32_" << "48_" << "64_" << "128_" << "256_" << "512_" << "1048_";
QVector<QPair<QString, QString> > icons;
Q_FOREACH (const QString &size, sizes) {
const QStringList names = { ":/pics/" + size + realName + ".png",
":/pics/" + size + realName + ".svg",
":/pics/" + size + realName + ".svgz",
":/pics/" + size + name + ".png",
":/pics/" + size + name + ".svg",
":/pics/" + size + name + ".svz",
":/" + size + realName + ".png",
":/" + size + realName + ".svg",
":/" + size + realName + ".svz",
":/" + size + name,
":/" + size + name + ".png",
":/" + size + name + ".svg",
":/" + size + name + ".svgz"};
for (const QString &resname : names) {
if (QFile(resname).exists()) {
icons << qMakePair(size, resname);
}
}
}
if (!icons.isEmpty()) {
QIcon icon;
Q_FOREACH (auto p, icons) {
QString sz = p.first;
sz.chop(1);
int size = sz.toInt();
icon.addFile(p.second, QSize(size, size));
}
s_icons.insert(icon.cacheKey(), name);
+#if defined CACHE_ICONS
+ s_cache.insert(name, icon);
+#endif
return icon;
}
QIcon icon = QIcon::fromTheme(name);
qWarning() << "\tfalling back on QIcon::FromTheme:" << name;
+ s_icons.insert(icon.cacheKey(), name);
+#if defined CACHE_ICONS
+ s_cache.insert(name, icon);
+#endif
return icon;
}
bool useDarkIcons() {
QColor background = qApp->palette().background().color();
return background.value() > 100;
}
bool adjustIcon(QIcon *icon)
{
bool result = false;
QString iconName = icon->name();
if (iconName.isNull()) {
if (s_icons.contains(icon->cacheKey())) {
iconName = s_icons.take(icon->cacheKey());
}
}
QString realIconName = iconName;
if (iconName.startsWith("dark_")) {
realIconName = iconName.mid(5);
}
if (iconName.startsWith("light_")) {
realIconName = iconName.mid(6);
}
if (!realIconName.isNull()) {
*icon = loadIcon(realIconName);
result = !icon->isNull();
s_icons.insert(icon->cacheKey(), iconName);
}
return result;
}
void updateIconCommon(QObject *object)
{
QAbstractButton* button = qobject_cast<QAbstractButton*>(object);
if (button) {
updateIcon(button);
}
QComboBox* comboBox = qobject_cast<QComboBox*>(object);
if (comboBox) {
updateIcon(comboBox);
}
QAction* action = qobject_cast<QAction*>(object);
if (action) {
updateIcon(action);
}
}
void updateIcon(QAbstractButton *button)
{
QIcon icon = button->icon();
if (adjustIcon(&icon)) {
button->setIcon(icon);
}
}
void updateIcon(QComboBox *comboBox)
{
for (int i = 0; i < comboBox->count(); i++) {
QIcon icon = comboBox->itemIcon(i);
if (adjustIcon(&icon)) {
comboBox->setItemIcon(i, icon);
}
}
}
void updateIcon(QAction *action)
{
QIcon icon = action->icon();
if (adjustIcon(&icon)) {
action->setIcon(icon);
}
}
}
diff --git a/libs/widgetutils/xmlgui/kbugreport.cpp b/libs/widgetutils/xmlgui/kbugreport.cpp
index 95e91473b7..3931011c24 100644
--- a/libs/widgetutils/xmlgui/kbugreport.cpp
+++ b/libs/widgetutils/xmlgui/kbugreport.cpp
@@ -1,226 +1,227 @@
/* This file is part of the KDE project
Copyright (C) 1999 David Faure <faure@kde.org>
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 "kbugreport.h"
#include <QtCore/QProcess>
#include <QtCore/QCoreApplication>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QLayout>
#include <QRadioButton>
#include <QGroupBox>
#include <QLocale>
#include <QCloseEvent>
#include <QLabel>
#include <QUrl>
#include <QUrlQuery>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QComboBox>
#include <QLineEdit>
#include <QStandardPaths>
#include <QDebug>
#include <QTextEdit>
+#include <QDesktopServices>
#include <kaboutdata.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <kemailsettings.h>
#include <klocalizedstring.h>
#include <kmessagebox.h>
#include <ktitlewidget.h>
#include "systeminformation_p.h"
#include "config-xmlgui.h"
#include <kis_icon_utils.h>
class KBugReportPrivate
{
public:
KBugReportPrivate(KBugReport *q): q(q), m_aboutData(KAboutData::applicationData()) {}
void _k_updateUrl();
KBugReport *q;
QProcess *m_process;
KAboutData m_aboutData;
QTextEdit *m_lineedit;
QLineEdit *m_subject;
QLabel *m_version;
QString m_strVersion;
QGroupBox *m_bgSeverity;
QComboBox *appcombo;
QString lastError;
QString appname;
QString os;
QUrl url;
QList<QRadioButton *> severityButtons;
int currentSeverity()
{
for (int i = 0; i < severityButtons.count(); i++)
if (severityButtons[i]->isChecked()) {
return i;
}
return -1;
}
};
KBugReport::KBugReport(const KAboutData &aboutData, QWidget *_parent)
: QDialog(_parent), d(new KBugReportPrivate(this))
{
setWindowTitle(i18n("Submit Bug Report"));
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
d->m_aboutData = aboutData;
d->m_process = 0;
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::close());
QLabel *tmpLabel;
QVBoxLayout *lay = new QVBoxLayout;
setLayout(lay);
KTitleWidget *title = new KTitleWidget(this);
title->setText(i18n("Submit Bug Report"));
title->setPixmap(KisIconUtils::loadIcon(QStringLiteral("tools-report-bug")).pixmap(32));
lay->addWidget(title);
QGridLayout *glay = new QGridLayout();
lay->addLayout(glay);
int row = 0;
// Program name
QString qwtstr = i18n("The application for which you wish to submit a bug report - if incorrect, please use the Report Bug menu item of the correct application");
tmpLabel = new QLabel(i18n("Application: "), this);
glay->addWidget(tmpLabel, row, 0);
tmpLabel->setWhatsThis(qwtstr);
d->appcombo = new QComboBox(this);
d->appcombo->setWhatsThis(qwtstr);
QStringList packageList = QStringList() << "krita";
d->appcombo->addItems(packageList);
connect(d->appcombo, SIGNAL(activated(int)), SLOT(_k_appChanged(int)));
d->appname = d->m_aboutData.productName();
glay->addWidget(d->appcombo, row, 1);
int index = 0;
for (; index < d->appcombo->count(); index++) {
if (d->appcombo->itemText(index) == d->appname) {
break;
}
}
if (index == d->appcombo->count()) { // not present
d->appcombo->addItem(d->appname);
}
d->appcombo->setCurrentIndex(index);
tmpLabel->setWhatsThis(qwtstr);
// Version
qwtstr = i18n("The version of this application - please make sure that no newer version is available before sending a bug report");
tmpLabel = new QLabel(i18n("Version:"), this);
glay->addWidget(tmpLabel, ++row, 0);
tmpLabel->setWhatsThis(qwtstr);
d->m_strVersion = d->m_aboutData.version();
if (d->m_strVersion.isEmpty()) {
d->m_strVersion = i18n("no version set (programmer error)");
}
d->m_version = new QLabel(d->m_strVersion, this);
d->m_version->setTextInteractionFlags(Qt::TextBrowserInteraction);
//glay->addWidget( d->m_version, row, 1 );
glay->addWidget(d->m_version, row, 1, 1, 2);
d->m_version->setWhatsThis(qwtstr);
tmpLabel = new QLabel(i18n("OS:"), this);
glay->addWidget(tmpLabel, ++row, 0);
d->os = SystemInformation::operatingSystemVersion();
tmpLabel = new QLabel(d->os, this);
tmpLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
glay->addWidget(tmpLabel, row, 1, 1, 2);
tmpLabel = new QLabel(i18n("Compiler:"), this);
glay->addWidget(tmpLabel, ++row, 0);
tmpLabel = new QLabel(QString::fromLatin1(XMLGUI_COMPILER_VERSION), this);
tmpLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
glay->addWidget(tmpLabel, row, 1, 1, 2);
// Point to the web form
lay->addSpacing(10);
QString text = i18n("<qt>To submit a bug report, click on the button below. This will open a web browser "
"window on <a href=\"http://bugs.kde.org\">http://bugs.kde.org</a> where you will find "
"a form to fill in. The information displayed above will be transferred to that server.</qt>");
QLabel *label = new QLabel(text, this);
label->setOpenExternalLinks(true);
label->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard);
label->setWordWrap(true);
lay->addWidget(label);
lay->addSpacing(10);
d->appcombo->setFocus();
d->_k_updateUrl();
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setText(i18n("&Launch Bug Report Wizard"));
okButton->setIcon(KisIconUtils::loadIcon(QStringLiteral("tools-report-bug")));
lay->addWidget(buttonBox);
setMinimumHeight(sizeHint().height() + 20); // WORKAROUND: prevent "cropped" qcombobox
}
KBugReport::~KBugReport()
{
delete d;
}
void KBugReportPrivate::_k_updateUrl()
{
url = QUrl(QStringLiteral("https://bugs.kde.org/enter_bug.cgi"));
QUrlQuery query;
query.addQueryItem(QStringLiteral("format"), QLatin1String("guided")); // use the guided form
// the string format is product/component, where component is optional
QStringList list = appcombo->currentText().split(QLatin1Char('/'));
query.addQueryItem(QStringLiteral("product"), list[0]);
if (list.size() == 2) {
query.addQueryItem(QStringLiteral("component"), list[1]);
}
query.addQueryItem(QStringLiteral("version"), m_strVersion);
url.setQuery(query);
// TODO: guess and fill OS(sys_os) and Platform(rep_platform) fields
}
void KBugReport::accept()
{
QDesktopServices::openUrl(d->url);
}
#include "moc_kbugreport.cpp"
diff --git a/libs/widgetutils/xmlgui/khelpmenu.cpp b/libs/widgetutils/xmlgui/khelpmenu.cpp
index 29ca437f00..7a8d7b4e96 100644
--- a/libs/widgetutils/xmlgui/khelpmenu.cpp
+++ b/libs/widgetutils/xmlgui/khelpmenu.cpp
@@ -1,303 +1,303 @@
/*
* This file is part of the KDE Libraries
* Copyright (C) 1999-2000 Espen Sand (espen@kde.org)
*
* 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.
*
*/
// I (espen) prefer that header files are included alphabetically
#include "khelpmenu.h"
#include "config-xmlgui.h"
#include <QtCore/QTimer>
#include <QAction>
#include <QApplication>
#include <QDialogButtonBox>
#include <QLabel>
#include <QMenu>
#include <QStyle>
#include <QWidget>
#include <QWhatsThis>
#include <QFile>
#include <QDir>
#include <QUrl>
#include <QBoxLayout>
-#include <QDesktopServices>
#include <QStandardPaths>
+#include <QDesktopServices>
#include <QDebug>
#include "kaboutkdedialog_p.h"
#include "kbugreport.h"
#include "kswitchlanguagedialog_p.h"
#include <kaboutdata.h>
#include <kauthorized.h>
#include <klocalizedstring.h>
#include <kstandardaction.h>
using namespace KDEPrivate;
class KHelpMenuPrivate
{
public:
KHelpMenuPrivate()
:
mSwitchApplicationLanguage(0),
mActionsCreated(false),
mSwitchApplicationLanguageAction(0),
mAboutData(KAboutData::applicationData())
{
mMenu = 0;
mAboutApp = 0;
mAboutKDE = 0;
mBugReport = 0;
mHandBookAction = 0;
mWhatsThisAction = 0;
mReportBugAction = 0;
mAboutAppAction = 0;
mAboutKDEAction = 0;
}
~KHelpMenuPrivate()
{
delete mMenu;
delete mAboutApp;
delete mAboutKDE;
delete mBugReport;
delete mSwitchApplicationLanguage;
}
void createActions(KHelpMenu *q);
QMenu *mMenu;
QDialog *mAboutApp;
KAboutKdeDialog *mAboutKDE;
KBugReport *mBugReport;
KSwitchLanguageDialog *mSwitchApplicationLanguage;
// TODO evaluate if we use static_cast<QWidget*>(parent()) instead of mParent to win that bit of memory
QWidget *mParent;
QString mAboutAppText;
bool mShowWhatsThis;
bool mActionsCreated;
QAction *mHandBookAction, *mWhatsThisAction;
QAction *mReportBugAction, *mSwitchApplicationLanguageAction, *mAboutAppAction, *mAboutKDEAction;
KAboutData mAboutData;
};
KHelpMenu::KHelpMenu(QWidget *parent, const QString &aboutAppText,
bool showWhatsThis)
: QObject(parent), d(new KHelpMenuPrivate)
{
d->mAboutAppText = aboutAppText;
d->mShowWhatsThis = showWhatsThis;
d->mParent = parent;
d->createActions(this);
}
KHelpMenu::KHelpMenu(QWidget *parent, const KAboutData &aboutData,
bool showWhatsThis)
: QObject(parent), d(new KHelpMenuPrivate)
{
d->mShowWhatsThis = showWhatsThis;
d->mParent = parent;
d->mAboutData = aboutData;
d->createActions(this);
}
KHelpMenu::~KHelpMenu()
{
delete d;
}
void KHelpMenuPrivate::createActions(KHelpMenu *q)
{
if (mActionsCreated) {
return;
}
mActionsCreated = true;
mHandBookAction = KStandardAction::helpContents(q, SLOT(appHelpActivated()), q);
if (mShowWhatsThis) {
mWhatsThisAction = KStandardAction::whatsThis(q, SLOT(contextHelpActivated()), q);
}
if (!mAboutData.bugAddress().isEmpty()) {
mReportBugAction = KStandardAction::reportBug(q, SLOT(reportBug()), q);
}
mSwitchApplicationLanguageAction = KStandardAction::create(KStandardAction::SwitchApplicationLanguage, q, SLOT(switchApplicationLanguage()), q);
mAboutAppAction = KStandardAction::aboutApp(q, SLOT(aboutApplication()), q);
mAboutKDEAction = KStandardAction::aboutKDE(q, SLOT(aboutKDE()), q);
}
// Used in the non-xml-gui case, like kfind or ksnapshot's help button.
QMenu *KHelpMenu::menu()
{
if (!d->mMenu) {
d->mMenu = new QMenu();
connect(d->mMenu, SIGNAL(destroyed()), this, SLOT(menuDestroyed()));
d->mMenu->setTitle(i18n("&Help"));
d->createActions(this);
bool need_separator = false;
if (d->mHandBookAction) {
d->mMenu->addAction(d->mHandBookAction);
need_separator = true;
}
if (d->mWhatsThisAction) {
d->mMenu->addAction(d->mWhatsThisAction);
need_separator = true;
}
if (d->mReportBugAction) {
if (need_separator) {
d->mMenu->addSeparator();
}
d->mMenu->addAction(d->mReportBugAction);
need_separator = true;
}
if (d->mSwitchApplicationLanguageAction) {
if (need_separator) {
d->mMenu->addSeparator();
}
d->mMenu->addAction(d->mSwitchApplicationLanguageAction);
need_separator = true;
}
if (need_separator) {
d->mMenu->addSeparator();
}
if (d->mAboutAppAction) {
d->mMenu->addAction(d->mAboutAppAction);
}
if (d->mAboutKDEAction) {
d->mMenu->addAction(d->mAboutKDEAction);
}
}
return d->mMenu;
}
QAction *KHelpMenu::action(MenuId id) const
{
switch (id) {
case menuHelpContents:
return d->mHandBookAction;
break;
case menuWhatsThis:
return d->mWhatsThisAction;
break;
case menuReportBug:
return d->mReportBugAction;
break;
case menuSwitchLanguage:
return d->mSwitchApplicationLanguageAction;
break;
case menuAboutApp:
return d->mAboutAppAction;
break;
case menuAboutKDE:
return d->mAboutKDEAction;
break;
}
return 0;
}
void KHelpMenu::appHelpActivated()
{
QDesktopServices::openUrl(QUrl(QStringLiteral("help:/")));
}
void KHelpMenu::aboutApplication()
{
if (receivers(SIGNAL(showAboutApplication())) > 0) {
emit showAboutApplication();
}
}
void KHelpMenu::aboutKDE()
{
if (!d->mAboutKDE) {
d->mAboutKDE = new KAboutKdeDialog(d->mParent);
connect(d->mAboutKDE, SIGNAL(finished(int)), this, SLOT(dialogFinished()));
}
d->mAboutKDE->show();
}
void KHelpMenu::reportBug()
{
if (!d->mBugReport) {
d->mBugReport = new KBugReport(d->mAboutData, d->mParent);
connect(d->mBugReport, SIGNAL(finished(int)), this, SLOT(dialogFinished()));
}
d->mBugReport->show();
}
void KHelpMenu::switchApplicationLanguage()
{
if (!d->mSwitchApplicationLanguage) {
d->mSwitchApplicationLanguage = new KSwitchLanguageDialog(d->mParent);
connect(d->mSwitchApplicationLanguage, SIGNAL(finished(int)), this, SLOT(dialogFinished()));
}
d->mSwitchApplicationLanguage->show();
}
void KHelpMenu::dialogFinished()
{
QTimer::singleShot(0, this, SLOT(timerExpired()));
}
void KHelpMenu::timerExpired()
{
if (d->mAboutKDE && !d->mAboutKDE->isVisible()) {
delete d->mAboutKDE; d->mAboutKDE = 0;
}
if (d->mBugReport && !d->mBugReport->isVisible()) {
delete d->mBugReport; d->mBugReport = 0;
}
if (d->mSwitchApplicationLanguage && !d->mSwitchApplicationLanguage->isVisible()) {
delete d->mSwitchApplicationLanguage; d->mSwitchApplicationLanguage = 0;
}
if (d->mAboutApp && !d->mAboutApp->isVisible()) {
delete d->mAboutApp; d->mAboutApp = 0;
}
}
void KHelpMenu::menuDestroyed()
{
d->mMenu = 0;
}
void KHelpMenu::contextHelpActivated()
{
QWhatsThis::enterWhatsThisMode();
}
diff --git a/libs/widgetutils/xmlgui/systeminformation_p.h b/libs/widgetutils/xmlgui/systeminformation_p.h
index 49c02358e7..d4f98e15e5 100644
--- a/libs/widgetutils/xmlgui/systeminformation_p.h
+++ b/libs/widgetutils/xmlgui/systeminformation_p.h
@@ -1,103 +1,97 @@
/* This file is part of the KDE project
Copyright (C) 1999 David Faure <faure@kde.org>
Copyright (C) 2014 Alex Richardson <arichardson.kde@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef SYSTEMINFORMATION_P_H
#define SYSTEMINFORMATION_P_H
#include <QString>
namespace SystemInformation {
QString userName();
QString operatingSystemVersion();
}
#if !defined(Q_OS_WIN)
#include <pwd.h>
#include <unistd.h>
#include <sys/utsname.h>
inline QString SystemInformation::userName()
{
struct passwd *p = getpwuid(getuid());
return QString::fromLatin1(p->pw_name);
}
inline QString SystemInformation::operatingSystemVersion()
{
struct utsname unameBuf;
uname(&unameBuf);
return QString::fromUtf8(unameBuf.sysname) +
QStringLiteral(" (") + QString::fromUtf8(unameBuf.machine) + QLatin1String(") ") +
QStringLiteral("release ") + QString::fromUtf8(unameBuf.release);
}
#else
#include <QSysInfo>
#include <qt_windows.h>
#define SECURITY_WIN32
#include <security.h>
//#include <secext.h> // GetUserNameEx
inline QString SystemInformation::userName()
{
WCHAR nameBuffer[256];
DWORD bufsize = 256;
if (!GetUserNameExW(NameDisplay, nameBuffer, &bufsize)) {
return QStringLiteral("Unknown User"); //should never happen (translate?)
}
return QString::fromWCharArray(nameBuffer);
}
static inline QString windowsVersionString() {
- switch (QSysInfo::windowsVersion()) {
- case QSysInfo::WV_XP: return QStringLiteral("Windows XP");
- case QSysInfo::WV_2003: return QStringLiteral("Windows 2003");
- case QSysInfo::WV_VISTA: return QStringLiteral("Windows Vista");
- case QSysInfo::WV_WINDOWS7: return QStringLiteral("Windows 7");
- case QSysInfo::WV_WINDOWS8: return QStringLiteral("Windows 8");
- case QSysInfo::WV_WINDOWS8_1: return QStringLiteral("Windows 8.1");
- default: return QStringLiteral("Unknown Windows");
- }
+ QString productVersion = QSysInfo::productVersion();
+ if (productVersion == "unknown") return QStringLiteral("Unknown Windows");
+ return "Windows " + productVersion;
}
inline QString SystemInformation::operatingSystemVersion()
{
SYSTEM_INFO info;
GetNativeSystemInfo(&info);
QString arch;
- switch (info.dwProcessorType) {
+ switch (info.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_AMD64:
case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64:
arch = QStringLiteral(" (x86_64)");
break;
case PROCESSOR_ARCHITECTURE_INTEL:
arch = QStringLiteral(" (x86)");
break;
case PROCESSOR_ARCHITECTURE_ARM:
arch = QStringLiteral(" (ARM)");
break;
default:
arch = QStringLiteral(" (unknown architecture)");
}
QString winVer;
//TODO: handle Service packs?
return windowsVersionString() + arch;
}
#endif
#endif // SYSTEMINFORMATION_P_H
diff --git a/packaging/linux/snap/setup/gui/krita.desktop b/packaging/linux/snap/setup/gui/krita.desktop
index 7ceb2ae56c..7095372cb3 100755
--- a/packaging/linux/snap/setup/gui/krita.desktop
+++ b/packaging/linux/snap/setup/gui/krita.desktop
@@ -1,127 +1,129 @@
[Desktop Entry]
Name=Krita
Name[af]=Krita
Name[bg]=Krita
Name[br]=Krita
Name[bs]=Krita
Name[ca]=Krita
Name[ca@valencia]=Krita
Name[cs]=Krita
Name[cy]=Krita
Name[da]=Krita
Name[de]=Krita
Name[el]=Krita
Name[en_GB]=Krita
Name[eo]=Krita
Name[es]=Krita
Name[et]=Krita
Name[eu]=Krita
Name[fi]=Krita
Name[fr]=Krita
Name[fy]=Krita
Name[ga]=Krita
Name[gl]=Krita
Name[he]=Krita
Name[hi]=केरिता
Name[hne]=केरिता
Name[hr]=Krita
Name[hu]=Krita
Name[ia]=Krita
Name[is]=Krita
Name[it]=Krita
Name[ja]=Krita
Name[kk]=Krita
Name[ko]=Krita
Name[lt]=Krita
Name[lv]=Krita
Name[mr]=क्रिटा
Name[ms]=Krita
Name[nb]=Krita
Name[nds]=Krita
Name[ne]=क्रिता
Name[nl]=Krita
Name[pl]=Krita
Name[pt]=Krita
Name[pt_BR]=Krita
Name[ro]=Krita
Name[ru]=Krita
Name[se]=Krita
Name[sk]=Krita
Name[sl]=Krita
Name[sv]=Krita
Name[ta]=கிரிட்டா
Name[tg]=Krita
Name[tr]=Krita
Name[ug]=Krita
Name[uk]=Krita
Name[uz]=Krita
Name[uz@cyrillic]=Krita
Name[wa]=Krita
Name[xh]=Krita
Name[x-test]=xxKritaxx
Name[zh_CN]=Krita
Name[zh_TW]=繪圖_Krita
Exec=krita %U
GenericName=Digital Painting
GenericName[bs]=Digitalno Bojenje
GenericName[ca]=Dibuix digital
GenericName[ca@valencia]=Dibuix digital
GenericName[cs]=Digitální malování
GenericName[da]=Digital tegning
GenericName[de]=Digitales Malen
GenericName[el]=Ψηφιακή ζωγραφική
GenericName[en_GB]=Digital Painting
GenericName[es]=Pintura digital
GenericName[et]=Digitaalne joonistamine
GenericName[eu]=Pintura digitala
GenericName[fi]=Digitaalimaalaus
GenericName[fr]=Peinture numérique
GenericName[gl]=Debuxo dixital
GenericName[hu]=Digitális festészet
GenericName[ia]=Pintura Digital
GenericName[it]=Pittura digitale
GenericName[ja]=デジタルペインティング
GenericName[kk]=Цифрлық сурет салу
GenericName[lt]=Skaitmeninis piešimas
GenericName[mr]=डिजिटल पेंटिंग
GenericName[nb]=Digital maling
GenericName[nl]=Digitaal schilderen
GenericName[pl]=Cyfrowe malowanie
GenericName[pt]=Pintura Digital
GenericName[pt_BR]=Pintura digital
GenericName[ru]=Цифровая живопись
GenericName[sk]=Digitálne maľovanie
GenericName[sl]=Digitalno slikanje
GenericName[sv]=Digital målning
GenericName[tr]=Sayısal Boyama
GenericName[ug]=سىفىرلىق رەسىم سىزغۇ
GenericName[uk]=Цифрове малювання
GenericName[x-test]=xxDigital Paintingxx
GenericName[zh_CN]=数字绘画
MimeType=application/x-krita;image/openraster;application/x-krita-paintoppreset;
Comment=Pixel-based image manipulation program for the Calligra Suite
Comment[ca]=Programa de manipulació d'imatges basades en píxels per a la Suite Calligra
Comment[ca@valencia]=Programa de manipulació d'imatges basades en píxels per a la Suite Calligra
Comment[de]=Pixelbasiertes Bildbearbeitungsprogramm für die Calligra-Suite
+Comment[el]=Πρόγραμμα επεξεργασίας εικόνας με βάση εικονοστοιχεία για το Calligra Stage
Comment[en_GB]=Pixel-based image manipulation program for the Calligra Suite
Comment[es]=Programa de manipulación de imágenes basado en píxeles para la suite Calligra
Comment[et]=Calligra pikslipõhine pilditöötluse rakendus
Comment[gl]=Programa da colección de Calligra para a manipulación de imaxes baseadas en píxeles.
Comment[it]=Programma di manipolazione delle immagini basato su pixel per Calligra Suite
Comment[nl]=Afbeeldingsbewerkingsprogramma gebaseerd op pixels voor de Calligra Suite
Comment[pl]=Program do obróbki obrazów na poziomie pikseli dla Pakietu Calligra
Comment[pt]='Plugin' de manipulação de imagens em pixels para o Calligra Stage
Comment[pt_BR]=Programa de manipulação de imagens baseado em pixels para o Calligra Suite
Comment[ru]=Программа редактирования пиксельной анимации для the Calligra Suite
Comment[sk]=Program na manipuláciu s pixelmi pre Calligra Suite
Comment[sv]=Bildpunktsbaserat bildbehandlingsprogram för Calligra-sviten
Comment[tr]=Calligra Suite için Pixel tabanlı görüntü düzenleme programı
Comment[uk]=Програма для роботи із растровими зображеннями для комплексу програм Calligra
+Comment[x-test]=xxPixel-based image manipulation program for the Calligra Suitexx
Comment[zh_CN]=Calligra 套件的位图图像处理程序
Type=Application
Icon=${SNAP}/meta/gui/calligrakrita.png
Categories=Qt;KDE;Graphics;
X-KDE-NativeMimeType=application/x-krita
X-KDE-ExtraNativeMimeTypes=
StartupNotify=true
X-Krita-Version=28
diff --git a/packaging/windows/package_2.cmd b/packaging/windows/package_2.cmd
index b14be85387..ca3c4a6a90 100644
--- a/packaging/windows/package_2.cmd
+++ b/packaging/windows/package_2.cmd
@@ -1,364 +1,374 @@
@echo off
:: This batch script is meant to prepare a Krita package folder to be zipped or
:: to be a base for the installer.
::
:: Just drop it next to the "i" install folder where the dependencies and Krita
:: binaries are.
::
:: Also copy filelist_bin_dll.txt and filelist_lib_dll.txt if you want more
:: fine-grained DLL dependencies copying.
::
:: You may want to review the following parameters.
::
:: Configuration parameters:
::
:: MINGW_GCC_BIN: Path to the mingw-w64/bin dir
:: SEVENZIP_EXE: Path to 7-Zip executable (either 7z.exe or 7zG.exe)
:: BUILDDIR_SRC: Path to krita source dir (for package shortcut)
:: BUILDDIR_INSTALL: Path to INSTALL prefix of the build
::
:: Note that paths should only contain alphanumeric, _ and -, except for the
:: path to 7-Zip, which is fine if quoted.
::
set MINGW_GCC_BIN=C:\mingw-x64\mingw64\bin
set SEVENZIP_EXE="C:\Program Files\7-Zip\7z.exe"
rem set BUILDDIR_SRC=%CD%\krita
set BUILDDIR_SRC=%CD%\krita
set BUILDDIR_INSTALL=%CD%\i
:: --------
set PATH=%MINGW_GCC_BIN%;%PATH%
echo Krita Windows packaging script
echo.
echo Configurations:
echo MINGW_GCC_BIN: %MINGW_GCC_BIN%
echo SEVENZIP_EXE: %SEVENZIP_EXE%
echo BUILDDIR_SRC: %BUILDDIR_SRC%
echo BUILDDIR_INSTALL: %BUILDDIR_INSTALL%
echo.
if "%1" == "" (
set PAUSE=pause
) else (
set "PAUSE= "
)
:: Simple checking
if not exist %BUILDDIR_INSTALL% (
echo ERROR: Cannot find the install folder!
%PAUSE%
exit /B 1
)
if not "%BUILDDIR_INSTALL: =%" == "%BUILDDIR_INSTALL%" (
echo ERROR: Install path contains space, which will not work properly!
%PAUSE%
exit /B 1
)
:: Decide package name to use
if "%1" == "" (
echo Please input a package name. It will be used for the output directory, package
echo file name and as the top-level directory of the package.
echo Alternatively, you can pass it as the first argument to this script.
echo You should only use alphanumeric, _ and -
echo.
set /P pkg_name=Package name^>
setlocal
if "!pkg_name!" == "" (
echo ERROR: You cannot choose an empty name!
%PAUSE%
exit /B 1
)
endlocal
) else (
set pkg_name=%1
)
echo Package name is "%pkg_name%"
set pkg_root=%CD%\%pkg_name%
echo Packaging dir is %pkg_root%
echo.
if exist %pkg_root% (
echo ERROR: Packaging dir already exists! Please remove or rename it first.
%PAUSE%
exit /B 1
)
echo.
echo Trying to guess GCC version...
g++ --version > NUL
if errorlevel 1 (
echo ERROR: g++ is not working.
%PAUSE%
exit /B 1
)
for /f "delims=" %%a in ('g++ --version ^| find "g++"') do set GCC_VERSION_LINE=%%a
echo -- %GCC_VERSION_LINE%
if "%GCC_VERSION_LINE:tdm64=%" == "%GCC_VERSION_LINE%" (
echo Compiler doesn't look like TDM64-GCC, assuming simple mingw-w64
set IS_TDM=
) else (
echo Compiler looks like TDM64-GCC
set IS_TDM=1
)
echo.
echo Trying to guess target architecture...
objdump --version > NUL
if errorlevel 1 (
echo ERROR: objdump is not working.
%PAUSE%
exit /B 1
)
for /f "delims=, tokens=1" %%a in ('objdump -f %BUILDDIR_INSTALL%\bin\krita.exe ^| find "architecture"') do set TARGET_ARCH_LINE=%%a
echo -- %TARGET_ARCH_LINE%
if "%TARGET_ARCH_LINE:x86-64=%" == "%TARGET_ARCH_LINE%" (
echo Target looks like x86
set IS_X64=
) else (
echo Target looks like x64
set IS_x64=1
)
echo.
echo Testing for objcopy...
objcopy --version > NUL
if errorlevel 1 (
echo ERROR: objcopy is not working.
%PAUSE%
exit /B 1
)
echo.
echo Testing for strip...
strip --version > NUL
if errorlevel 1 (
echo ERROR: strip is not working.
%PAUSE%
exit /B 1
)
echo.
echo Creating base directories...
mkdir %pkg_root% && ^
mkdir %pkg_root%\bin && ^
mkdir %pkg_root%\lib && ^
mkdir %pkg_root%\share
if errorlevel 1 (
echo ERROR: Cannot create packaging dir tree!
%PAUSE%
exit /B 1
)
echo.
echo Copying GCC libraries...
if x%IS_TDM% == x (
if x%is_x64% == x (
:: mingw-w64 x86
set "STDLIBS=gcc_s_dw2-1 gomp-1 stdc++-6 winpthread-1"
) else (
:: mingw-w64 x64
set "STDLIBS=gcc_s_seh-1 gomp-1 stdc++-6 winpthread-1"
)
) else (
if x%is_x64% == x (
:: TDM-GCC x86
set "STDLIBS=gomp-1"
) else (
:: TDM-GCC x64
set "STDLIBS=gomp_64-1"
)
)
for %%L in (%STDLIBS%) do copy "%MINGW_GCC_BIN%\lib%%L.dll" %pkg_root%\bin
echo.
echo Copying files...
:: krita.exe
copy %BUILDDIR_INSTALL%\bin\krita.exe %pkg_root%\bin
:: DLLs from bin/
if exist filelist_bin_dll.txt (
for /f %%F in (filelist_bin_dll.txt) do copy %BUILDDIR_INSTALL%\bin\%%F %pkg_root%\bin
) else (
echo INFO: filelist_bin_dll.txt not found, copying all DLLs except Qt5 from bin/
setlocal enableextensions enabledelayedexpansion
for /f "delims=" %%F in ('dir /b "%BUILDDIR_INSTALL%\bin\*.dll"') do (
set file=%%F
set file=!file:~0,3!
if not x!file! == xQt5 copy %BUILDDIR_INSTALL%\bin\%%F %pkg_root%\bin
)
endlocal
)
:: symsrv.yes for Dr. Mingw
copy %BUILDDIR_INSTALL%\bin\symsrv.yes %pkg_root%\bin
:: DLLs from lib/
if exist filelist_lib_dll.txt (
for /f %%F in (filelist_lib_dll.txt) do copy %BUILDDIR_INSTALL%\lib\%%F %pkg_root%\bin
) else (
echo INFO: filelist_lib_dll.txt not found, copying all DLLs from lib/
copy %BUILDDIR_INSTALL%\lib\*.dll %pkg_root%\bin
)
:: Boost, there might be more than one leftover but we can't really do much
copy %BUILDDIR_INSTALL%\bin\libboost_system-*.dll %pkg_root%\bin
:: KF5 plugins may be placed at different locations depending on how Qt is built
xcopy /S /Y /I %BUILDDIR_INSTALL%\lib\plugins\imageformats %pkg_root%\bin\imageformats
xcopy /S /Y /I %BUILDDIR_INSTALL%\plugins\imageformats %pkg_root%\bin\imageformats
xcopy /S /Y /I %BUILDDIR_INSTALL%\lib\plugins\kf5 %pkg_root%\bin\kf5
xcopy /S /Y /I %BUILDDIR_INSTALL%\plugins\kf5 %pkg_root%\bin\kf5
:: Qt Translations
:: it seems that windeployqt does these, but only *some* of these???
mkdir %pkg_root%\bin\translations
setlocal enableextensions enabledelayedexpansion
for /f "delims=" %%F in ('dir /b "%BUILDDIR_INSTALL%\translations\qt_*.qm"') do (
:: Exclude qt_help_*.qm
set temp=%%F
set temp2=!temp:_help=!
if x!temp2! == x!temp! copy %BUILDDIR_INSTALL%\translations\!temp! %pkg_root%\bin\translations\!temp!
)
endlocal
:: Krita plugins
xcopy /Y %BUILDDIR_INSTALL%\lib\kritaplugins\*.dll %pkg_root%\lib\kritaplugins\
:: Share
xcopy /Y /S /I %BUILDDIR_INSTALL%\share\color %pkg_root%\share\color
xcopy /Y /S /I %BUILDDIR_INSTALL%\share\color-schemes %pkg_root%\share\color-schemes
xcopy /Y /S /I %BUILDDIR_INSTALL%\share\icons %pkg_root%\share\icons
xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kf5 %pkg_root%\share\kf5
xcopy /Y /S /I %BUILDDIR_INSTALL%\share\krita %pkg_root%\share\krita
xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kritaplugins %pkg_root%\share\kritaplugins
xcopy /Y /S /I %BUILDDIR_INSTALL%\share\mime %pkg_root%\share\mime
:: Not useful on Windows it seems
rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\appdata %pkg_root%\share\appdata
rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\applications %pkg_root%\share\applications
rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\doc %pkg_root%\share\doc
rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\kservices5 %pkg_root%\share\kservices5
rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\man %pkg_root%\share\man
rem xcopy /Y /S /I %BUILDDIR_INSTALL%\share\ocio %pkg_root%\share\ocio
:: Copy locale to bin
xcopy /Y /S /I %BUILDDIR_INSTALL%\share\locale %pkg_root%\bin\locale
:: Copy shortcut link from source (can't create it dynamically)
copy %BUILDDIR_SRC%\packaging\windows\krita.lnk %pkg_root%
:: windeployqt
%BUILDDIR_INSTALL%\bin\windeployqt.exe --qmldir %BUILDDIR_INSTALL%\qml --release -gui -core -concurrent -network -printsupport -svg -xml -multimedia -qml -quick -quickwidgets %pkg_root%\bin\krita.exe
if errorlevel 1 (
echo ERROR: WinDeployQt failed!!
%PAUSE%
exit /B 1
)
+if EXIST "%BUILDDIR_INSTALL%\bin\gmic_krita_qt.exe" (
+ copy %BUILDDIR_INSTALL%\bin\gmic_krita_qt.exe %pkg_root%\bin
+ %BUILDDIR_INSTALL%\bin\windeployqt.exe --release %pkg_root%\bin\gmic_krita_qt.exe
+ if errorlevel 1 (
+ echo ERROR: WinDeployQt failed!!
+ %PAUSE%
+ exit /B 1
+ )
+)
+
:: Copy embedded Python
xcopy /Y /S /I %BUILDDIR_INSTALL%\python %pkg_root%\python
:: For chopping relative path
:: 512 should be enough
:: n+2 to also account for a trailing backslash
setlocal enableextensions enabledelayedexpansion
for /L %%n in (1 1 512) do if "!pkg_root:~%%n,1!" neq "" set /a "pkg_root_len_plus_one=%%n+2"
endlocal & set pkg_root_len_plus_one=%pkg_root_len_plus_one%
echo.
setlocal enableextensions enabledelayedexpansion
:: Remove Python cache files
for /d /r "%pkg_root%" %%F in (__pycache__\) do (
if EXIST "%%F" (
set relpath=%%F
set relpath=!relpath:~%pkg_root_len_plus_one%!
echo Deleting Python cache !relpath!
rmdir /S /Q "%%F"
)
)
endlocal
echo.
echo Splitting debug info from binaries...
call :split-debug "%pkg_root%\bin\krita.exe" bin\krita.exe
setlocal enableextensions enabledelayedexpansion
:: Find all DLLs
for /r "%pkg_root%" %%F in (*.dll) do (
set relpath=%%F
set relpath=!relpath:~%pkg_root_len_plus_one%!
call :split-debug "%%F" !relpath!
)
endlocal
setlocal enableextensions enabledelayedexpansion
:: Find all Python native modules
for /r "%pkg_root%\share\krita\pykrita\" %%F in (*.pyd) do (
set relpath=%%F
set relpath=!relpath:~%pkg_root_len_plus_one%!
call :split-debug "%%F" !relpath!
)
endlocal
echo.
echo Packaging debug info...
:: (note that the top-level package dir is not included)
%SEVENZIP_EXE% a -tzip %pkg_name%-dbg.zip -r %pkg_root%\*.debug
echo --------
echo.
echo Packaging stripped binaries...
%SEVENZIP_EXE% a -tzip %pkg_name%.zip %pkg_root%\ -xr!.debug
echo --------
echo.
echo.
echo Krita packaged as %pkg_name%.zip
if exist %pkg_name%-dbg.zip echo Debug info packaged as %pkg_name%-dbg.zip
echo Packaging dir is %pkg_root%
echo NOTE: Do not create installer with packaging dir. Extract from
echo %pkg_name%.zip instead,
echo and do _not_ run krita inside the extracted directory because it will
echo create extra unnecessary files.
echo.
echo Please remember to actually test the package before releasing it.
echo.
%PAUSE%
exit /b
:split-debug
echo Splitting debug info of %2
objcopy --only-keep-debug %~1 %~1.debug
if ERRORLEVEL 1 exit /b %ERRORLEVEL%
:: If the debug file is small enough then consider there being no debug info.
:: Discard these files since they somehow make gdb crash.
call :getfilesize %~1.debug
if /i %getfilesize_retval% LEQ 2048 (
echo Discarding %2.debug
del %~1.debug
exit /b 0
)
if not exist %~dp1.debug mkdir %~dp1.debug
move %~1.debug %~dp1.debug\ > NUL
strip %~1
:: Add debuglink
:: FIXME: There is a problem with gdb that cause it to output this warning
:: FIXME: "warning: section .gnu_debuglink not found in xxx.debug"
:: FIXME: I tried adding a link to itself but this kills drmingw :(
objcopy --add-gnu-debuglink="%~dp1.debug\%~nx1.debug" %~1
exit /b %ERRORLEVEL%
:getfilesize
set getfilesize_retval=%~z1
goto :eof
:relpath_dirpath
call :relpath_dirpath_internal "" "%~1"
goto :eof
:relpath_dirpath_internal
for /f "tokens=1* delims=\" %%a in ("%~2") do (
:: If part 2 is empty, it means part 1 is probably the file name
if x%%b==x (
set relpath_dirpath_retval=%~1
) else (
call :relpath_dirpath_internal "%~1%%a\" %%b
)
)
goto :eof
diff --git a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc
index 70894493a7..9300d8a5d3 100644
--- a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc
+++ b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc
@@ -1,913 +1,913 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Geoffry Song <goffrie@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <kis_ruler_assistant_tool.h>
#include <QPainter>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QFile>
#include <QLineF>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <QMessageBox>
#include <KoIcon.h>
#include <kis_icon.h>
#include <KoFileDialog.h>
#include <KoViewConverter.h>
#include <KoPointerEvent.h>
#include <canvas/kis_canvas2.h>
#include <kis_canvas_resource_provider.h>
#include <kis_cursor.h>
#include <kis_image.h>
#include <KisViewManager.h>
#include <kis_abstract_perspective_grid.h>
#include <kis_painting_assistants_decoration.h>
#include "kis_global.h"
#include <math.h>
KisRulerAssistantTool::KisRulerAssistantTool(KoCanvasBase * canvas)
: KisTool(canvas, KisCursor::arrowCursor()), m_canvas(dynamic_cast<KisCanvas2*>(canvas)),
m_assistantDrag(0), m_newAssistant(0), m_optionsWidget(0), m_handleSize(32), m_handleHalfSize(16)
{
Q_ASSERT(m_canvas);
setObjectName("tool_rulerassistanttool");
}
KisRulerAssistantTool::~KisRulerAssistantTool()
{
}
QPointF adjustPointF(const QPointF& _pt, const QRectF& _rc)
{
return QPointF(qBound(_rc.left(), _pt.x(), _rc.right()), qBound(_rc.top(), _pt.y(), _rc.bottom()));
}
void KisRulerAssistantTool::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
// Add code here to initialize your tool when it got activated
KisTool::activate(toolActivation, shapes);
m_handles = m_canvas->paintingAssistantsDecoration()->handles();
m_canvas->paintingAssistantsDecoration()->setVisible(true);
m_canvas->updateCanvas();
m_handleDrag = 0;
m_internalMode = MODE_CREATION;
m_assistantHelperYOffset = 10;
}
void KisRulerAssistantTool::deactivate()
{
// Add code here to initialize your tool when it got deactivated
m_canvas->updateCanvas();
KisTool::deactivate();
}
bool KisRulerAssistantTool::mouseNear(const QPointF& mousep, const QPointF& point)
{
QRectF handlerect(point-QPointF(m_handleHalfSize,m_handleHalfSize), QSizeF(m_handleSize, m_handleSize));
return handlerect.contains(mousep);
}
KisPaintingAssistantHandleSP KisRulerAssistantTool::nodeNearPoint(KisPaintingAssistantSP grid, QPointF point)
{
if (mouseNear(point, pixelToView(*grid->topLeft()))) {
return grid->topLeft();
} else if (mouseNear(point, pixelToView(*grid->topRight()))) {
return grid->topRight();
} else if (mouseNear(point, pixelToView(*grid->bottomLeft()))) {
return grid->bottomLeft();
} else if (mouseNear(point, pixelToView(*grid->bottomRight()))) {
return grid->bottomRight();
}
return 0;
}
inline double norm2(const QPointF& p)
{
return p.x() * p.x() + p.y() * p.y();
}
void KisRulerAssistantTool::beginPrimaryAction(KoPointerEvent *event)
{
setMode(KisTool::PAINT_MODE);
bool newAssistantAllowed = true;
if (m_newAssistant) {
m_internalMode = MODE_CREATION;
*m_newAssistant->handles().back() = snapToGuide(event, QPointF(), false);
if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) {
addAssistant();
} else {
m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false)));
}
m_canvas->updateCanvas();
return;
}
m_handleDrag = 0;
double minDist = 81.0;
QPointF mousePos = m_canvas->viewConverter()->documentToView(snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point);
Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle));
if (dist < minDist) {
minDist = dist;
m_handleDrag = handle;
}
}
if(m_handleDrag && assistant->id() == "perspective") {
// Look for the handle which was pressed
if (m_handleDrag == assistant->topLeft()) {
double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
if (dist < minDist) {
minDist = dist;
}
m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y());
m_internalMode = MODE_DRAGGING_NODE;
} else if (m_handleDrag == assistant->topRight()) {
double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
if (dist < minDist) {
minDist = dist;
}
m_internalMode = MODE_DRAGGING_NODE;
m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
} else if (m_handleDrag == assistant->bottomLeft()) {
double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
if (dist < minDist) {
minDist = dist;
}
m_internalMode = MODE_DRAGGING_NODE;
m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
} else if (m_handleDrag == assistant->bottomRight()) {
double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag));
if (dist < minDist) {
minDist = dist;
}
m_internalMode = MODE_DRAGGING_NODE;
m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
} else if (m_handleDrag == assistant->leftMiddle()) {
m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5,
(assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5);
m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
m_newAssistant->addHandle(assistant->topLeft());
m_newAssistant->addHandle(m_selectedNode1);
m_newAssistant->addHandle(m_selectedNode2);
m_newAssistant->addHandle(assistant->bottomLeft());
m_dragEnd = event->point;
m_handleDrag = 0;
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
return;
} else if (m_handleDrag == assistant->rightMiddle()) {
m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5,
(assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5);
m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y());
m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
m_newAssistant->addHandle(assistant->topRight());
m_newAssistant->addHandle(m_selectedNode1);
m_newAssistant->addHandle(m_selectedNode2);
m_newAssistant->addHandle(assistant->bottomRight());
m_dragEnd = event->point;
m_handleDrag = 0;
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
return;
} else if (m_handleDrag == assistant->topMiddle()) {
m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5,
(assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5);
m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y());
m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y());
m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
m_newAssistant->addHandle(m_selectedNode1);
m_newAssistant->addHandle(m_selectedNode2);
m_newAssistant->addHandle(assistant->topRight());
m_newAssistant->addHandle(assistant->topLeft());
m_dragEnd = event->point;
m_handleDrag = 0;
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
return;
} else if (m_handleDrag == assistant->bottomMiddle()) {
m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5,
(assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5);
m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES;
m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y());
m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y());
m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant());
m_newAssistant->addHandle(assistant->bottomLeft());
m_newAssistant->addHandle(assistant->bottomRight());
m_newAssistant->addHandle(m_selectedNode2);
m_newAssistant->addHandle(m_selectedNode1);
m_dragEnd = event->point;
m_handleDrag = 0;
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
return;
}
m_snapIsRadial = false;
}
else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" ||
assistant->id() == "parallel ruler" ||
assistant->id() == "infinite ruler" ||
assistant->id() == "spline")){
if (m_handleDrag == assistant->handles()[0]) {
m_dragStart = *assistant->handles()[1];
} else if (m_handleDrag == assistant->handles()[1]) {
m_dragStart = *assistant->handles()[0];
} else if(assistant->handles().size()==4){
if (m_handleDrag == assistant->handles()[2]) {
m_dragStart = *assistant->handles()[0];
} else if (m_handleDrag == assistant->handles()[3]) {
m_dragStart = *assistant->handles()[1];
}
}
m_snapIsRadial = false;
} else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" ||
assistant->id() == "concentric ellipse" ||
assistant->id() == "fisheye-point")){
m_snapIsRadial = false;
if (m_handleDrag == assistant->handles()[0]) {
m_dragStart = *assistant->handles()[1];
} else if (m_handleDrag == assistant->handles()[1]) {
m_dragStart = *assistant->handles()[0];
} else if (m_handleDrag == assistant->handles()[2]) {
m_dragStart = assistant->buttonPosition();
m_radius = QLineF(m_dragStart, *assistant->handles()[0]);
m_snapIsRadial = true;
}
} else {
m_dragStart = assistant->buttonPosition();
m_snapIsRadial = false;
}
}
if (m_handleDrag) {
// TODO: Shift-press should now be handled using the alternate actions
// if (event->modifiers() & Qt::ShiftModifier) {
// m_handleDrag->uncache();
// m_handleDrag = m_handleDrag->split()[0];
// m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles();
// }
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
return;
}
m_assistantDrag.clear();
Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
// This code contains the click event behavior. The actual display of the icons are done at the bottom
// of the paint even. Make sure the rectangles positions are the same between the two.
// TODO: These 6 lines are duplicated below in the paint layer. It shouldn't be done like this.
QPointF actionsPosition = m_canvas->viewConverter()->documentToView(assistant->buttonPosition());
QPointF iconDeletePosition(actionsPosition + QPointF(78, m_assistantHelperYOffset + 7));
QPointF iconSnapPosition(actionsPosition + QPointF(54, m_assistantHelperYOffset + 7));
QPointF iconMovePosition(actionsPosition + QPointF(15, m_assistantHelperYOffset));
QRectF deleteRect(iconDeletePosition, QSizeF(16, 16));
QRectF visibleRect(iconSnapPosition, QSizeF(16, 16));
QRectF moveRect(iconMovePosition, QSizeF(32, 32));
if (moveRect.contains(mousePos)) {
m_assistantDrag = assistant;
m_cursorStart = event->point;
m_currentAdjustment = QPointF();
m_internalMode = MODE_EDITING;
return;
}
if (deleteRect.contains(mousePos)) {
removeAssistant(assistant);
if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) {
m_internalMode = MODE_CREATION;
}
else
m_internalMode = MODE_EDITING;
m_canvas->updateCanvas();
return;
}
if (visibleRect.contains(mousePos)) {
newAssistantAllowed = false;
if (assistant->snapping()==true){
snappingOff(assistant);
outlineOff(assistant);
}
else{
snappingOn(assistant);
outlineOn(assistant);
}
assistant->uncache();//this updates the chache of the assistant, very important.
}
}
if (newAssistantAllowed==true){//don't make a new assistant when I'm just toogling visiblity//
QString key = m_options.comboBox->model()->index( m_options.comboBox->currentIndex(), 0 ).data(Qt::UserRole).toString();
m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get(key)->createPaintingAssistant());
m_internalMode = MODE_CREATION;
m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false)));
if (m_newAssistant->numHandles() <= 1) {
if (key == "vanishing point"){
m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(-70,0)));
m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(-140,0)));
m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(70,0)));
m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(140,0)));
}
addAssistant();
} else {
m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false)));
}
}
m_canvas->updateCanvas();
}
void KisRulerAssistantTool::continuePrimaryAction(KoPointerEvent *event)
{
if (m_handleDrag) {
*m_handleDrag = event->point;
//ported from the gradient tool... we need to think about this more in the future.
if (event->modifiers() == Qt::ShiftModifier && m_snapIsRadial) {
QLineF dragRadius = QLineF(m_dragStart, event->point);
dragRadius.setLength(m_radius.length());
*m_handleDrag = dragRadius.p2();
} else if (event->modifiers() == Qt::ShiftModifier ) {
QPointF move = snapToClosestAxis(event->point - m_dragStart);
*m_handleDrag = m_dragStart + move;
} else {
*m_handleDrag = snapToGuide(event, QPointF(), false);
}
m_handleDrag->uncache();
m_handleCombine = 0;
if (!(event->modifiers() & Qt::ShiftModifier)) {
double minDist = 49.0;
QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point);
Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
if (handle == m_handleDrag) continue;
double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle));
if (dist < minDist) {
minDist = dist;
m_handleCombine = handle;
}
}
}
m_canvas->updateCanvas();
} else if (m_assistantDrag) {
QPointF newAdjustment = snapToGuide(event, QPointF(), false) - m_cursorStart;
if (event->modifiers() == Qt::ShiftModifier ) {
newAdjustment = snapToClosestAxis(newAdjustment);
}
Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) {
*handle += (newAdjustment - m_currentAdjustment);
}
if (m_assistantDrag->id()== "vanishing point"){
Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) {
*handle += (newAdjustment - m_currentAdjustment);
}
}
m_currentAdjustment = newAdjustment;
m_canvas->updateCanvas();
} else {
event->ignore();
}
bool wasHiglightedNode = m_higlightedNode != 0;
QPointF mousep = m_canvas->viewConverter()->documentToView(event->point);
QList <KisPaintingAssistantSP> pAssistant= m_canvas->paintingAssistantsDecoration()->assistants();
Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) {
if(assistant->id() == "perspective") {
if ((m_higlightedNode = nodeNearPoint(assistant, mousep))) {
if (m_higlightedNode == m_selectedNode1 || m_higlightedNode == m_selectedNode2) {
m_higlightedNode = 0;
} else {
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
break;
}
}
}
//this following bit sets the translations for the vanishing-point handles.
if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) {
//for inner handles, the outer handle gets translated.
if (m_handleDrag == assistant->sideHandles()[0]){
QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]);
qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length();
if (length<2.0){length=2.0;}
length +=perspectiveline.length();
perspectiveline.setLength(length);
*assistant->sideHandles()[1] = perspectiveline.p2();
}
else if (m_handleDrag == assistant->sideHandles()[2]){
QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]);
qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length();
if (length<2.0){length=2.0;}
length +=perspectiveline.length();
perspectiveline.setLength(length);
*assistant->sideHandles()[3] = perspectiveline.p2();
}
//for outer handles, only the vanishing point is translated, but only if there's an intersection.
else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){
QPointF vanishingpoint(0,0);
QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]);
QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]);
if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){
*assistant->handles()[0] = vanishingpoint;}
}//and for the vanishing point itself, only the outer handles get translated.
else if (m_handleDrag == assistant->handles()[0]){
QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]);
QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]);
qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length();
qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length();
if (length<2.0){length=2.0;}
if (length2<2.0){length2=2.0;}
length +=perspectiveline.length();
length2 +=perspectiveline2.length();
perspectiveline.setLength(length);
perspectiveline2.setLength(length2);
*assistant->sideHandles()[1] = perspectiveline.p2();
*assistant->sideHandles()[3] = perspectiveline2.p2();
}
}
}
if (wasHiglightedNode && !m_higlightedNode) {
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
}
}
void KisRulerAssistantTool::endPrimaryAction(KoPointerEvent *event)
{
setMode(KisTool::HOVER_MODE);
if (m_handleDrag) {
if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) {
m_handleCombine->mergeWith(m_handleDrag);
m_handleCombine->uncache();
m_handles = m_canvas->paintingAssistantsDecoration()->handles();
}
m_handleDrag = m_handleCombine = 0;
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
} else if (m_assistantDrag) {
m_assistantDrag.clear();
m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas
} else if(m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) {
addAssistant();
m_internalMode = MODE_CREATION;
m_canvas->updateCanvas();
}
else {
event->ignore();
}
}
void KisRulerAssistantTool::addAssistant()
{
m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant);
m_handles = m_canvas->paintingAssistantsDecoration()->handles();
KisAbstractPerspectiveGrid* grid = dynamic_cast<KisAbstractPerspectiveGrid*>(m_newAssistant.data());
if (grid) {
m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid);
}
m_newAssistant.clear();
}
void KisRulerAssistantTool::removeAssistant(KisPaintingAssistantSP assistant)
{
KisAbstractPerspectiveGrid* grid = dynamic_cast<KisAbstractPerspectiveGrid*>(assistant.data());
if (grid) {
m_canvas->viewManager()->resourceProvider()->removePerspectiveGrid(grid);
}
m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant);
m_handles = m_canvas->paintingAssistantsDecoration()->handles();
}
void KisRulerAssistantTool::snappingOn(KisPaintingAssistantSP assistant)
{
assistant->setSnapping(true);
}
void KisRulerAssistantTool::snappingOff(KisPaintingAssistantSP assistant)
{
assistant->setSnapping(false);
}
void KisRulerAssistantTool::outlineOn(KisPaintingAssistantSP assistant)
{
assistant->setOutline(true);
}
void KisRulerAssistantTool::outlineOff(KisPaintingAssistantSP assistant)
{
assistant->setOutline(false);
}
#include <KoSnapGuide.h>
QPointF KisRulerAssistantTool::snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers)
{
if (!m_canvas->currentImage())
return e->point;
KoSnapGuide *snapGuide = m_canvas->snapGuide();
QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier);
//return m_canvas->currentImage()->documentToPixel(pos);
return pos;
}
QPointF KisRulerAssistantTool::snapToGuide(const QPointF& pt, const QPointF &offset)
{
if (!m_canvas)
return pt;
KoSnapGuide *snapGuide = m_canvas->snapGuide();
QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier);
return pos;
}
void KisRulerAssistantTool::mouseMoveEvent(KoPointerEvent *event)
{
if (m_newAssistant && m_internalMode == MODE_CREATION) {
*m_newAssistant->handles().back() = event->point;
m_canvas->updateCanvas();
} else if (m_newAssistant && m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) {
QPointF translate = event->point - m_dragEnd;;
m_dragEnd = event->point;
m_selectedNode1.data()->operator =(QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate);
m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate);
m_canvas->updateCanvas();
}
}
void KisRulerAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter)
{
QPixmap iconDelete = KisIconUtils::loadIcon("dialog-cancel").pixmap(16, 16);
QPixmap iconSnapOn = KisIconUtils::loadIcon("visible").pixmap(16, 16);
QPixmap iconSnapOff = KisIconUtils::loadIcon("novisible").pixmap(16, 16);
QPixmap iconMove = KisIconUtils::loadIcon("transform-move").pixmap(32, 32);
QColor handlesColor(0, 0, 0, 125);
if (m_newAssistant) {
m_newAssistant->drawAssistant(_gc, QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size())), m_canvas->coordinatesConverter(), false,m_canvas, true, false);
Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) {
QPainterPath path;
path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(6, 6), QSizeF(12, 12)));
KisPaintingAssistant::drawPath(_gc, path);
}
}
// TODO: too many Q_FOREACH loops going through all assistants. Condense this to one to be a little more performant
// Draw corner and middle perspective nodes
Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
QRectF ellipse(_converter.documentToView(*handle) - QPointF(6, 6), QSizeF(12, 12));
// render handles when they are being dragged and moved
if (handle == m_handleDrag || handle == m_handleCombine) {
_gc.save();
_gc.setPen(Qt::transparent);
_gc.setBrush(handlesColor);
_gc.drawEllipse(ellipse);
_gc.restore();
}
if ( assistant->id() =="vanishing point") {
if (assistant->handles().at(0) == handle ) { // vanishing point handle
ellipse = QRectF(_converter.documentToView(*handle) - QPointF(10, 10), QSizeF(20, 20));
// TODO: change this to be smaller, but fill in with a color
}
//TODO: render outside handles a little bigger than rotation anchor handles
}
QPainterPath path;
path.addEllipse(ellipse);
KisPaintingAssistant::drawPath(_gc, path);
}
}
Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
// Draw middle perspective handles
if(assistant->id()=="perspective") {
assistant->findHandleLocation();
QPointF topMiddle, bottomMiddle, rightMiddle, leftMiddle;
topMiddle = (_converter.documentToView(*assistant->topLeft()) + _converter.documentToView(*assistant->topRight()))*0.5;
bottomMiddle = (_converter.documentToView(*assistant->bottomLeft()) + _converter.documentToView(*assistant->bottomRight()))*0.5;
rightMiddle = (_converter.documentToView(*assistant->topRight()) + _converter.documentToView(*assistant->bottomRight()))*0.5;
leftMiddle = (_converter.documentToView(*assistant->topLeft()) + _converter.documentToView(*assistant->bottomLeft()))*0.5;
QPainterPath path;
path.addEllipse(QRectF(leftMiddle-QPointF(6,6),QSizeF(12,12)));
path.addEllipse(QRectF(topMiddle-QPointF(6,6),QSizeF(12,12)));
path.addEllipse(QRectF(rightMiddle-QPointF(6,6),QSizeF(12,12)));
path.addEllipse(QRectF(bottomMiddle-QPointF(6,6),QSizeF(12,12)));
KisPaintingAssistant::drawPath(_gc, path);
}
if(assistant->id()=="vanishing point") {
if (assistant->sideHandles().size() == 4) {
// Draw the line
QPointF p0 = _converter.documentToView(*assistant->handles()[0]);
QPointF p1 = _converter.documentToView(*assistant->sideHandles()[0]);
QPointF p2 = _converter.documentToView(*assistant->sideHandles()[1]);
QPointF p3 = _converter.documentToView(*assistant->sideHandles()[2]);
QPointF p4 = _converter.documentToView(*assistant->sideHandles()[3]);
_gc.setPen(QColor(0, 0, 0, 75));
// Draw control lines
QPen penStyle(QColor(120, 120, 120, 60), 2.0, Qt::DashDotDotLine);
_gc.setPen(penStyle);
_gc.drawLine(p0, p1);
_gc.drawLine(p0, p3);
_gc.drawLine(p1, p2);
_gc.drawLine(p3, p4);
}
}
}
// Draw the assistant widget
Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
// We are going to put all of the assistant actions below the bounds of the assistant
// so they are out of the way
// assistant->buttonPosition() gets the center X/Y position point
QPointF actionsPosition = m_canvas->viewConverter()->documentToView(assistant->buttonPosition());
QPointF iconDeletePosition(actionsPosition + QPointF(78, m_assistantHelperYOffset + 7));
QPointF iconSnapPosition(actionsPosition + QPointF(54, m_assistantHelperYOffset + 7));
QPointF iconMovePosition(actionsPosition + QPointF(15, m_assistantHelperYOffset ));
// Background container for helpers
QBrush backgroundColor = m_canvas->viewManager()->mainWindow()->palette().window();
QPointF actionsBGRectangle(actionsPosition + QPointF(25, m_assistantHelperYOffset));
_gc.setRenderHint(QPainter::Antialiasing);
QPainterPath bgPath;
bgPath.addRoundedRect(QRectF(actionsBGRectangle.x(), actionsBGRectangle.y(), 80, 30), 6, 6);
QPen stroke(QColor(60, 60, 60, 80), 2);
_gc.setPen(stroke);
_gc.fillPath(bgPath, backgroundColor);
_gc.drawPath(bgPath);
QPainterPath movePath; // render circle behind by move helper
_gc.setPen(stroke);
movePath.addEllipse(iconMovePosition.x()-5, iconMovePosition.y()-5, 40, 40);// background behind icon
_gc.fillPath(movePath, backgroundColor);
_gc.drawPath(movePath);
// Preview/Snap Tool helper
_gc.drawPixmap(iconDeletePosition, iconDelete);
if (assistant->snapping()==true) {
_gc.drawPixmap(iconSnapPosition, iconSnapOn);
}
else
{
_gc.drawPixmap(iconSnapPosition, iconSnapOff);
}
// Move Assistant Tool helper
_gc.drawPixmap(iconMovePosition, iconMove);
}
}
void KisRulerAssistantTool::removeAllAssistants()
{
m_canvas->viewManager()->resourceProvider()->clearPerspectiveGrids();
m_canvas->paintingAssistantsDecoration()->removeAll();
m_handles = m_canvas->paintingAssistantsDecoration()->handles();
m_canvas->updateCanvas();
}
void KisRulerAssistantTool::loadAssistants()
{
KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenAssistant");
dialog.setCaption(i18n("Select an Assistant"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant");
QString filename = dialog.filename();
if (filename.isEmpty()) return;
if (!QFileInfo(filename).exists()) return;
QFile file(filename);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
QXmlStreamReader xml(data);
QMap<int, KisPaintingAssistantHandleSP> handleMap;
KisPaintingAssistantSP assistant;
bool errors = false;
while (!xml.atEnd()) {
switch (xml.readNext()) {
case QXmlStreamReader::StartElement:
if (xml.name() == "handle") {
if (assistant && !xml.attributes().value("ref").isEmpty()) {
KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt());
if (handle) {
assistant->addHandle(handle);
} else {
errors = true;
}
} else {
QString strId = xml.attributes().value("id").toString(),
strX = xml.attributes().value("x").toString(),
strY = xml.attributes().value("y").toString();
if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
int id = strId.toInt();
double x = strX.toDouble(),
y = strY.toDouble();
if (!handleMap.contains(id)) {
handleMap.insert(id, new KisPaintingAssistantHandle(x, y));
} else {
errors = true;
}
} else {
errors = true;
}
}
} else if (xml.name() == "assistant") {
const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString());
if (factory) {
if (assistant) {
errors = true;
assistant.clear();
}
assistant = toQShared(factory->createPaintingAssistant());
} else {
errors = true;
}
}
break;
case QXmlStreamReader::EndElement:
if (xml.name() == "assistant") {
if (assistant) {
if (assistant->handles().size() == assistant->numHandles()) {
if (assistant->id() == "vanishing point"){
//ideally we'd save and load side-handles as well, but this is all I've got//
QPointF pos = *assistant->handles()[0];
assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0)));
assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0)));
assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0)));
assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0)));
}
m_canvas->paintingAssistantsDecoration()->addAssistant(assistant);
KisAbstractPerspectiveGrid* grid = dynamic_cast<KisAbstractPerspectiveGrid*>(assistant.data());
if (grid) {
m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid);
}
} else {
errors = true;
}
assistant.clear();
}
}
break;
default:
break;
}
}
if (assistant) {
errors = true;
assistant.clear();
}
if (xml.hasError()) {
QMessageBox::warning(0, i18nc("@title:window", "Krita"), xml.errorString());
}
if (errors) {
QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded."));
}
m_handles = m_canvas->paintingAssistantsDecoration()->handles();
m_canvas->updateCanvas();
}
void KisRulerAssistantTool::saveAssistants()
{
if (m_handles.isEmpty()) return;
QByteArray data;
QXmlStreamWriter xml(&data);
xml.writeStartDocument();
xml.writeStartElement("paintingassistant");
xml.writeStartElement("handles");
QMap<KisPaintingAssistantHandleSP, int> handleMap;
Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) {
int id = handleMap.size();
handleMap.insert(handle, id);
xml.writeStartElement("handle");
//xml.writeAttribute("type", handle->handleType());
xml.writeAttribute("id", QString::number(id));
xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3));
xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3));
xml.writeEndElement();
}
xml.writeEndElement();
xml.writeStartElement("assistants");
Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) {
xml.writeStartElement("assistant");
xml.writeAttribute("type", assistant->id());
xml.writeStartElement("handles");
Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) {
xml.writeStartElement("handle");
xml.writeAttribute("ref", QString::number(handleMap.value(handle)));
xml.writeEndElement();
}
xml.writeEndElement();
xml.writeEndElement();
}
xml.writeEndElement();
xml.writeEndElement();
xml.writeEndDocument();
KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "OpenAssistant");
dialog.setCaption(i18n("Save Assistant"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant");
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QFile file(filename);
file.open(QIODevice::WriteOnly);
file.write(data);
}
QWidget *KisRulerAssistantTool::createOptionWidget()
{
if (!m_optionsWidget) {
m_optionsWidget = new QWidget;
m_options.setupUi(m_optionsWidget);
// See https://bugs.kde.org/show_bug.cgi?id=316896
QWidget *specialSpacer = new QWidget(m_optionsWidget);
specialSpacer->setObjectName("SpecialSpacer");
specialSpacer->setFixedSize(0, 0);
m_optionsWidget->layout()->addWidget(specialSpacer);
m_options.loadButton->setIcon(KisIconUtils::loadIcon("document-open"));
m_options.saveButton->setIcon(KisIconUtils::loadIcon("document-save"));
m_options.deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
QList<KoID> assistants;
Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) {
QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name();
assistants << KoID(key, name);
}
std::sort(assistants.begin(), assistants.end(), KoID::compareNames);
Q_FOREACH(const KoID &id, assistants) {
m_options.comboBox->addItem(id.name(), id.id());
}
connect(m_options.saveButton, SIGNAL(clicked()), SLOT(saveAssistants()));
connect(m_options.loadButton, SIGNAL(clicked()), SLOT(loadAssistants()));
connect(m_options.deleteButton, SIGNAL(clicked()), SLOT(removeAllAssistants()));
}
return m_optionsWidget;
}
diff --git a/plugins/color/lcms2engine/LcmsEnginePlugin.cpp b/plugins/color/lcms2engine/LcmsEnginePlugin.cpp
index c3d5364939..9078645234 100644
--- a/plugins/color/lcms2engine/LcmsEnginePlugin.cpp
+++ b/plugins/color/lcms2engine/LcmsEnginePlugin.cpp
@@ -1,297 +1,303 @@
/*
* Copyright (c) 2003 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004,2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "LcmsEnginePlugin.h"
#include <QHash>
#include <QStringList>
#include <QDir>
#include <kpluginfactory.h>
#include <KoResourcePaths.h>
#include <klocalizedstring.h>
#include <QDebug>
+#include <QApplication>
#include "kis_assert.h"
#include <KoBasicHistogramProducers.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpaceEngine.h>
#include "IccColorSpaceEngine.h"
#include "colorprofiles/LcmsColorProfileContainer.h"
#include "colorspaces/cmyk_u8/CmykU8ColorSpace.h"
#include "colorspaces/cmyk_u16/CmykU16ColorSpace.h"
#include "colorspaces/cmyk_f32/CmykF32ColorSpace.h"
#include "colorspaces/gray_u8/GrayU8ColorSpace.h"
#include "colorspaces/gray_u16/GrayU16ColorSpace.h"
#include "colorspaces/gray_f32/GrayF32ColorSpace.h"
#include "colorspaces/lab_u8/LabU8ColorSpace.h"
#include "colorspaces/lab_u16/LabColorSpace.h"
#include "colorspaces/lab_f32/LabF32ColorSpace.h"
#include "colorspaces/xyz_u8/XyzU8ColorSpace.h"
#include "colorspaces/xyz_u16/XyzU16ColorSpace.h"
#include "colorspaces/xyz_f32/XyzF32ColorSpace.h"
#include "colorspaces/rgb_u8/RgbU8ColorSpace.h"
#include "colorspaces/rgb_u16/RgbU16ColorSpace.h"
#include "colorspaces/rgb_f32/RgbF32ColorSpace.h"
#include "colorspaces/ycbcr_u8/YCbCrU8ColorSpace.h"
#include "colorspaces/ycbcr_u16/YCbCrU16ColorSpace.h"
#include "colorspaces/ycbcr_f32/YCbCrF32ColorSpace.h"
#include <KoConfig.h>
#ifdef HAVE_OPENEXR
#include <half.h>
#ifdef HAVE_LCMS24
#include "colorspaces/gray_f16/GrayF16ColorSpace.h"
#include "colorspaces/xyz_f16/XyzF16ColorSpace.h"
#include "colorspaces/rgb_f16/RgbF16ColorSpace.h"
#endif
#endif
void lcms2LogErrorHandlerFunction(cmsContext /*ContextID*/, cmsUInt32Number ErrorCode, const char *Text)
{
qCritical() << "Lcms2 error: " << ErrorCode << Text;
}
K_PLUGIN_FACTORY_WITH_JSON(PluginFactory, "kolcmsengine.json",
registerPlugin<LcmsEnginePlugin>();)
LcmsEnginePlugin::LcmsEnginePlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
// We need all resource paths to be properly initialized via KisApplication, otherwise we will
// initialize this instance with lacking color profiles which will cause lookup errors later on.
- KoResourcePaths::assertReady();
+
+ KIS_ASSERT_X(KoResourcePaths::isReady() ||
+ (QApplication::instance()->applicationName() != "krita" &&
+ QApplication::instance()->applicationName() != "krita.exe"),
+ "LcmsEnginePlugin::LcmsEnginePlugin", "Resource paths are not ready yet.");
+
// Set the lmcs error reporting function
cmsSetLogErrorHandler(&lcms2LogErrorHandlerFunction);
KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
// Initialise color engine
KoColorSpaceEngineRegistry::instance()->add(new IccColorSpaceEngine);
QStringList profileFilenames;
profileFilenames += KoResourcePaths::findAllResources("icc_profiles", "*.icm", KoResourcePaths::Recursive);
profileFilenames += KoResourcePaths::findAllResources("icc_profiles", "*.ICM", KoResourcePaths::Recursive);
profileFilenames += KoResourcePaths::findAllResources("icc_profiles", "*.ICC", KoResourcePaths::Recursive);
profileFilenames += KoResourcePaths::findAllResources("icc_profiles", "*.icc", KoResourcePaths::Recursive);
// Load the profiles
if (!profileFilenames.empty()) {
KoColorProfile *profile = 0;
for (QStringList::Iterator it = profileFilenames.begin(); it != profileFilenames.end(); ++it) {
profile = new IccColorProfile(*it);
Q_CHECK_PTR(profile);
profile->load();
if (profile->valid()) {
//qDebug() << "Valid profile : " << profile->fileName() << profile->name();
registry->addProfileToMap(profile);
} else {
qDebug() << "Invalid profile : " << profile->fileName() << profile->name();
delete profile;
}
}
}
// ------------------- LAB ---------------------------------
KoColorProfile *labProfile = LcmsColorProfileContainer::createFromLcmsProfile(cmsCreateLab2Profile(0));
registry->addProfile(labProfile);
registry->add(new LabU8ColorSpaceFactory());
registry->add(new LabU16ColorSpaceFactory());
registry->add(new LabF32ColorSpaceFactory());
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU8HistogramProducer>
(KoID("LABAU8HISTO", i18n("L*a*b* Histogram")), LABAColorModelID.id(), Integer8BitsColorDepthID.id()));
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU16HistogramProducer>
(KoID("LABAU16HISTO", i18n("L*a*b* Histogram")), LABAColorModelID.id(), Integer16BitsColorDepthID.id()));
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicF32HistogramProducer>
(KoID("LABAF32HISTO", i18n("L*a*b* Histogram")), LABAColorModelID.id(), Float32BitsColorDepthID.id()));
// ------------------- RGB ---------------------------------
KoColorProfile *rgbProfile = LcmsColorProfileContainer::createFromLcmsProfile(cmsCreate_sRGBProfile());
registry->addProfile(rgbProfile);
registry->add(new RgbU8ColorSpaceFactory());
registry->add(new RgbU16ColorSpaceFactory());
#ifdef HAVE_LCMS24
#ifdef HAVE_OPENEXR
registry->add(new RgbF16ColorSpaceFactory());
#endif
#endif
registry->add(new RgbF32ColorSpaceFactory());
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU8HistogramProducer>
(KoID("RGBU8HISTO", i18n("RGB8 Histogram")), RGBAColorModelID.id(), Integer8BitsColorDepthID.id()));
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU16HistogramProducer>
(KoID("RGBU16HISTO", i18n("RGB16 Histogram")), RGBAColorModelID.id(), Integer16BitsColorDepthID.id()));
#ifdef HAVE_LCMS24
#ifdef HAVE_OPENEXR
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicF16HalfHistogramProducer>
(KoID("RGBF16HISTO", i18n("RGBF16 Histogram")), RGBAColorModelID.id(), Float16BitsColorDepthID.id()));
#endif
#endif
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicF32HistogramProducer>
(KoID("RGF328HISTO", i18n("RGBF32 Histogram")), RGBAColorModelID.id(), Float32BitsColorDepthID.id()));
// ------------------- GRAY ---------------------------------
cmsToneCurve *Gamma = cmsBuildGamma(0, 2.2);
cmsHPROFILE hProfile = cmsCreateGrayProfile(cmsD50_xyY(), Gamma);
cmsFreeToneCurve(Gamma);
KoColorProfile *defProfile = LcmsColorProfileContainer::createFromLcmsProfile(hProfile);
registry->addProfile(defProfile);
registry->add(new GrayAU8ColorSpaceFactory());
registry->add(new GrayAU16ColorSpaceFactory());
#ifdef HAVE_LCMS24
#ifdef HAVE_OPENEXR
registry->add(new GrayF16ColorSpaceFactory());
#endif
#endif
registry->add(new GrayF32ColorSpaceFactory());
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU8HistogramProducer>
(KoID("GRAYA8HISTO", i18n("GRAY/Alpha8 Histogram")), GrayAColorModelID.id(), Integer8BitsColorDepthID.id()));
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU16HistogramProducer>
(KoID("GRAYA16HISTO", i18n("GRAY/Alpha16 Histogram")), GrayAColorModelID.id(), Integer16BitsColorDepthID.id()));
#ifdef HAVE_LCMS24
#ifdef HAVE_OPENEXR
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicF16HalfHistogramProducer>
(KoID("GRAYF16HISTO", i18n("GRAYF16 Histogram")), GrayAColorModelID.id(), Float16BitsColorDepthID.id()));
#endif
#endif
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicF32HistogramProducer>
(KoID("GRAYAF32HISTO", i18n("GRAY/Alpha 32 float Histogram")), GrayAColorModelID.id(), Float32BitsColorDepthID.id()));
// ------------------- CMYK ---------------------------------
registry->add(new CmykU8ColorSpaceFactory());
registry->add(new CmykU16ColorSpaceFactory());
registry->add(new CmykF32ColorSpaceFactory());
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU8HistogramProducer>
(KoID("CMYK8HISTO", i18n("CMYK8 Histogram")), CMYKAColorModelID.id(), Integer8BitsColorDepthID.id()));
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU16HistogramProducer>
(KoID("CMYK16HISTO", i18n("CMYK16 Histogram")), CMYKAColorModelID.id(), Integer16BitsColorDepthID.id()));
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicF32HistogramProducer>
(KoID("CMYKF32HISTO", i18n("CMYK F32 Histogram")), CMYKAColorModelID.id(), Float32BitsColorDepthID.id()));
// ------------------- XYZ ---------------------------------
KoColorProfile *xyzProfile = LcmsColorProfileContainer::createFromLcmsProfile(cmsCreateXYZProfile());
registry->addProfile(xyzProfile);
registry->add(new XyzU8ColorSpaceFactory());
registry->add(new XyzU16ColorSpaceFactory());
#ifdef HAVE_LCMS24
#ifdef HAVE_OPENEXR
registry->add(new XyzF16ColorSpaceFactory());
#endif
#endif
registry->add(new XyzF32ColorSpaceFactory());
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU8HistogramProducer>
(KoID("XYZ8HISTO", i18n("XYZ8 Histogram")), XYZAColorModelID.id(), Integer8BitsColorDepthID.id()));
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU16HistogramProducer>
(KoID("XYZ16HISTO", i18n("XYZ16 Histogram")), XYZAColorModelID.id(), Integer16BitsColorDepthID.id()));
#ifdef HAVE_LCMS24
#ifdef HAVE_OPENEXR
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicF32HistogramProducer>
(KoID("XYZF16HISTO", i18n("XYZF16 Histogram")), XYZAColorModelID.id(), Float16BitsColorDepthID.id()));
#endif
#endif
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicF32HistogramProducer>
(KoID("XYZF32HISTO", i18n("XYZF32 Histogram")), XYZAColorModelID.id(), Float32BitsColorDepthID.id()));
// ------------------- YCBCR ---------------------------------
// KoColorProfile *yCbCrProfile = LcmsColorProfileContainer::createFromLcmsProfile(cmsCreateYCBCRProfile());
// registry->addProfile(yCbCrProfile);
registry->add(new YCbCrU8ColorSpaceFactory());
registry->add(new YCbCrU16ColorSpaceFactory());
registry->add(new YCbCrF32ColorSpaceFactory());
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU8HistogramProducer>
(KoID("YCBCR8HISTO", i18n("YCBCR8 Histogram")), YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id()));
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicU16HistogramProducer>
(KoID("YCBCR16HISTO", i18n("YCBCR16 Histogram")), YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id()));
KoHistogramProducerFactoryRegistry::instance()->add(
new KoBasicHistogramProducerFactory<KoBasicF32HistogramProducer>
(KoID("YCBCRF32HISTO", i18n("YCBCRF32 Histogram")), YCbCrAColorModelID.id(), Float32BitsColorDepthID.id()));
// Add profile alias for default profile from lcms1
registry->addProfileAlias("sRGB built-in - (lcms internal)", "sRGB built-in");
registry->addProfileAlias("gray built-in - (lcms internal)", "gray built-in");
registry->addProfileAlias("Lab identity built-in - (lcms internal)", "Lab identity built-in");
registry->addProfileAlias("XYZ built-in - (lcms internal)", "XYZ identity built-in");
}
#include <LcmsEnginePlugin.moc>
diff --git a/plugins/dockers/advancedcolorselector/wdg_color_selector_settings.ui b/plugins/dockers/advancedcolorselector/wdg_color_selector_settings.ui
index e1adbd3810..2eff486a4d 100644
--- a/plugins/dockers/advancedcolorselector/wdg_color_selector_settings.ui
+++ b/plugins/dockers/advancedcolorselector/wdg_color_selector_settings.ui
@@ -1,1459 +1,1557 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KisColorSelectorSettings</class>
<widget class="QWidget" name="KisColorSelectorSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>635</width>
- <height>1014</height>
+ <width>612</width>
+ <height>973</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Color Selector Settings</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
+ <layout class="QVBoxLayout" name="verticalLayout_16">
<item>
<layout class="QFormLayout" name="dockerSelectForm">
<property name="labelAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="verticalSpacing">
<number>6</number>
</property>
<property name="bottomMargin">
<number>20</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="dockerColorSettingsLabel">
<property name="text">
<string>Docker:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="dockerColorSettingsComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTabWidget" name="advancedColorSelectorOptions">
<property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
- <width>600</width>
- <height>421</height>
+ <width>0</width>
+ <height>280</height>
</size>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_typeshape">
<attribute name="title">
<string>Color Selector</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
- <widget class="KisColorSelectorComboBox" name="colorSelectorConfiguration"/>
+ <widget class="KisColorSelectorComboBox" name="colorSelectorConfiguration">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
+ <property name="sizeType">
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
- <height>40</height>
+ <height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>6</number>
+ </property>
<item>
- <widget class="QLabel" name="l_type">
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="text">
- <string>Color &amp;Model Type: </string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="buddy">
- <cstring>colorSelectorConfiguration</cstring>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="colorSelectorTypeComboBox"/>
- </item>
- <item>
- <widget class="QLabel" name="ACSTypeDescriptionLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <property name="spacing">
+ <number>12</number>
</property>
- <property name="text">
- <string notr="true">Type Description goes here</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ <property name="topMargin">
+ <number>6</number>
</property>
- <property name="wordWrap">
- <bool>true</bool>
+ <property name="rightMargin">
+ <number>6</number>
</property>
- </widget>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QLabel" name="l_type">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>Color &amp;Model Type: </string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>colorSelectorConfiguration</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="colorSelectorTypeComboBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLabel" name="ACSTypeDescriptionLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string notr="true">Type Description goes here</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item>
<widget class="QGroupBox" name="lumaCoefficientGroupbox">
<property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Luma Coefficients</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_15">
<item>
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="verticalSpacing">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="redlabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Red': </string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="KisDoubleParseSpinBox" name="l_lumaG">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>4</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="greenlabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Green':</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="bluelabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Blue':</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisDoubleParseSpinBox" name="l_lumaR">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>4</number>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="KisDoubleParseSpinBox" name="l_lumaB">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>4</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisDoubleParseSpinBox" name="SP_Gamma">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>-3.000000000000000</double>
</property>
<property name="maximum">
<double>3.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>2.200000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="gamma_label">
<property name="text">
<string>Gamma:</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="useDifferentColorSpaceCheckbox">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Color Selector Uses Different Color Space than Ima&amp;ge</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<widget class="KisColorSpaceSelector" name="colorSpace" native="true">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+ <item>
+ <spacer name="verticalSpacer_9">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
</layout>
</widget>
<widget class="QWidget" name="tab_general">
<attribute name="title">
<string>Behavior</string>
</attribute>
- <layout class="QFormLayout" name="formLayout">
- <item row="0" column="0">
- <widget class="QLabel" name="label_3">
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="text">
- <string>When Docker Resizes: </string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="dockerResizeOptionsComboBox">
- <property name="enabled">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>Show Zoom Selector UI: </string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QComboBox" name="zoomSelectorOptionComboBox"/>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_11">
- <property name="text">
- <string>Zoom Selector Size: </string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <item>
+ <layout class="QGridLayout" name="gridLayout_6">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>When Docker Resizes: </string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="zoomSelectorOptionComboBox"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Show Zoom Selector UI: </string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>Zoom Selector Size: </string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="KisIntParseSpinBox" name="popupSize">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="minimum">
+ <number>100</number>
+ </property>
+ <property name="maximum">
+ <number>1000</number>
+ </property>
+ <property name="singleStep">
+ <number>10</number>
+ </property>
+ <property name="value">
+ <number>260</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="dockerResizeOptionsComboBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QCheckBox" name="hidePopupOnClickCheck">
+ <property name="text">
+ <string>Hide Popup on click.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
- <item row="2" column="1">
- <widget class="KisIntParseSpinBox" name="popupSize">
- <property name="suffix">
- <string> px</string>
- </property>
- <property name="minimum">
- <number>100</number>
- </property>
- <property name="maximum">
- <number>1000</number>
- </property>
- <property name="singleStep">
- <number>10</number>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
</property>
- <property name="value">
- <number>260</number>
+ <property name="sizeType">
+ <enum>QSizePolicy::MinimumExpanding</enum>
</property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QCheckBox" name="hidePopupOnClickCheck">
- <property name="text">
- <string>Hide Popup on click.</string>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>228</height>
+ </size>
</property>
- </widget>
+ </spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabShadeSelector">
<attribute name="title">
<string>Shade Selector</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
- <item row="0" column="1">
+ <item row="0" column="2">
<widget class="QLabel" name="myPaintColorModelLabel">
<property name="text">
<string>Color model:</string>
</property>
</widget>
</item>
- <item row="1" column="1">
- <widget class="QComboBox" name="ACSshadeSelectorMyPaintColorModelComboBox"/>
- </item>
- <item row="1" column="0">
+ <item row="0" column="1">
<widget class="QComboBox" name="ACSShadeSelectorTypeComboBox"/>
</item>
+ <item row="0" column="3">
+ <widget class="QComboBox" name="ACSshadeSelectorMyPaintColorModelComboBox"/>
+ </item>
</layout>
</item>
- <item>
- <spacer name="verticalSpacer_4">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>10</height>
- </size>
- </property>
- </spacer>
- </item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_7">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="text">
<string>Update Selector When:</string>
</property>
</widget>
</item>
- <item>
- <spacer name="verticalSpacer_3">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>6</height>
- </size>
- </property>
- </spacer>
- </item>
<item>
<widget class="QCheckBox" name="shadeSelectorUpdateOnRightClick">
<property name="text">
<string>Right clicking on shade selector</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="shadeSelectorUpdateOnLeftClick">
<property name="text">
<string>Left clicking on shade selector</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="shadeSelectorUpdateOnForeground">
<property name="toolTip">
<string>this doesn't include a color change by the shade selector</string>
</property>
<property name="text">
<string>Foreground color changes</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="shadeSelectorUpdateOnBackground">
<property name="toolTip">
<string>this doesn't include a color change by the shade selector</string>
</property>
<property name="text">
<string>Background color change</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
- <item>
- <spacer name="verticalSpacer_5">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>109</height>
- </size>
- </property>
- </spacer>
- </item>
<item>
<widget class="QGroupBox" name="minimalShadeSelectorGroup">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="title">
<string>Minimal Shade Selector</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLabel" name="label_15">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
<property name="text">
<string>Display:</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="minimalShadeSelectorAsGradient">
<property name="text">
<string>&amp;Gradient</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="minimalShadeSelectorAsColorPatches">
<property name="text">
<string>Colo&amp;r Patches</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="KisShadeSelectorLinesSettings" name="minimalShadeSelectorLineSettings">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>59</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Line Count: </string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisIntParseSpinBox" name="minimalShadeSelectorLineCount">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>10</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Line Height: </string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisIntParseSpinBox" name="minimalShadeSelectorLineHeight">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>8</number>
</property>
<property name="maximum">
<number>99</number>
</property>
<property name="value">
<number>16</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="patchesPerLineLabel">
<property name="text">
<string>Patches Per Line: </string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="KisIntParseSpinBox" name="minimalShadeSelectorPatchesPerLine"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
+ <item>
+ <spacer name="verticalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
</layout>
</widget>
<widget class="QWidget" name="tab_ColorHistory">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<attribute name="title">
<string>Color History</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_11">
<item>
<widget class="QGroupBox" name="lastUsedColorsShow">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Show Color Histor&amp;y</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Patch Options</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Height:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisIntParseSpinBox" name="lastUsedColorsWidth">
<property name="suffix">
<string> px</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>16</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Width: </string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisIntParseSpinBox" name="lastUsedColorsHeight">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>16</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Max Patches: </string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="KisIntParseSpinBox" name="lastUsedColorsPatchCount">
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>30</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Layout</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QRadioButton" name="lastUsedColorsAlignVertical">
<property name="text">
<string>&amp;Vertical</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lbl_lastUsedNumCols">
<property name="text">
<string>Colu&amp;mns:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>lastUsedColorsNumCols</cstring>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="KisIntParseSpinBox" name="lastUsedColorsNumCols">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>20</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="lastUsedColorsAlignHorizontal">
<property name="text">
- <string>&amp;Horizontal</string>
+ <string>Hori&amp;zontal</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="lbl_lastUsedNumRows">
<property name="text">
<string>&amp;Rows:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>lastUsedColorsNumRows</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="KisIntParseSpinBox" name="lastUsedColorsNumRows">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>20</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="lastUsedColorsAllowScrolling">
<property name="text">
<string>Allow scrolling</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
+ <property name="sizeType">
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
- <height>40</height>
+ <height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_colorsFromImage">
<attribute name="title">
<string>Colors from Image</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<widget class="QGroupBox" name="commonColorsShow">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Show Colors from the ima&amp;ge</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Patch Options</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Height:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisIntParseSpinBox" name="commonColorsWidth">
<property name="suffix">
<string> px</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>16</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Width:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisIntParseSpinBox" name="commonColorsHeight">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>16</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Max Patches: </string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="KisIntParseSpinBox" name="commonColorsPatchCount">
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>30</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Layout</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QRadioButton" name="commonColorsAlignVertical">
<property name="text">
<string>&amp;Vertical</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lbl_commonColorsNumCols">
<property name="text">
<string>Colu&amp;mns:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>commonColorsNumCols</cstring>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="KisIntParseSpinBox" name="commonColorsNumCols">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>20</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="commonColorsAlignHorizontal">
<property name="text">
- <string>&amp;Horizontal</string>
+ <string>Hori&amp;zontal</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="lbl_commonColorsNumRows">
<property name="text">
<string>&amp;Rows:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>commonColorsNumRows</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="KisIntParseSpinBox" name="commonColorsNumRows">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>20</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="commonColorsAutoUpdate">
<property name="toolTip">
<string>this can be slow on big images</string>
</property>
<property name="text">
<string>Update after every stroke</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="commonColorsAllowScrolling">
<property name="text">
<string>Allow scrolling</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
+ <property name="sizeType">
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
- <height>138</height>
+ <height>10</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QWidget" name="colorSliderOptions" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupHsv">
<property name="title">
<string>HSV Sliders to Show</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="csl_hsvH">
<property name="text">
<string>Hue</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="csl_hsvS">
<property name="text">
<string>Saturation</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="csl_hsvV">
<property name="text">
<string>Value</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupHsl">
<property name="title">
<string>HSL Sliders to Show</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="csl_hslH">
<property name="text">
<string>Hue</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="csl_hslS">
<property name="text">
<string>Saturation</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="csl_hslL">
<property name="text">
<string>Lightness</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupHsi">
<property name="title">
<string>HSI Sliders to Show</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="csl_hsiH">
<property name="text">
<string>Hue</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="csl_hsiS">
<property name="text">
<string>Saturation</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="csl_hsiI">
<property name="text">
<string>Intensity</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupHsy">
<property name="title">
<string>HSY' Sliders to Show</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QCheckBox" name="csl_hsyH">
<property name="text">
<string>Hue</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="csl_hsyS">
<property name="text">
<string>Saturation</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="csl_hsyY">
<property name="text">
<string>Luma</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="hotKeyOptions" native="true">
<layout class="QVBoxLayout" name="hotkeyOptions">
<item>
<widget class="QGroupBox" name="hsy">
<property name="title">
<string>Lightness, Saturation and Hue hotkey steps</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>Lightness: </string>
</property>
</widget>
</item>
<item>
<widget class="KisIntParseSpinBox" name="sb_lightness">
<property name="suffix">
<string> steps</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_13">
<property name="text">
<string>Saturation: </string>
</property>
</widget>
</item>
<item>
<widget class="KisIntParseSpinBox" name="sb_saturation">
<property name="suffix">
<string> steps</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_17">
<property name="text">
<string>Hue: </string>
</property>
</widget>
</item>
<item>
<widget class="KisIntParseSpinBox" name="sb_hue">
<property name="suffix">
<string> steps</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="horizontalGroupBox_2">
<property name="title">
<string>YUV Redder/Greener/Bluer/Yellower hotkey steps</string>
</property>
<layout class="QHBoxLayout" name="yuv">
<item>
<widget class="QLabel" name="label_18">
<property name="text">
<string>Redder/Greener: </string>
</property>
</widget>
</item>
<item>
<widget class="KisIntParseSpinBox" name="sb_rg">
<property name="suffix">
<string> steps</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_19">
<property name="text">
<string>Bluer/Yellower: </string>
</property>
</widget>
</item>
<item>
<widget class="KisIntParseSpinBox" name="sb_by">
<property name="suffix">
<string> steps</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
- <height>40</height>
+ <height>10</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisColorSpaceSelector</class>
<extends>QWidget</extends>
<header>widgets/kis_color_space_selector.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>KisIntParseSpinBox</class>
+ <extends>QSpinBox</extends>
+ <header>kis_int_parse_spin_box.h</header>
+ </customwidget>
<customwidget>
<class>KisColorSelectorComboBox</class>
<extends>QComboBox</extends>
<header>kis_color_selector_combo_box.h</header>
</customwidget>
<customwidget>
<class>KisShadeSelectorLinesSettings</class>
<extends>QComboBox</extends>
<header>kis_shade_selector_lines_settings.h</header>
</customwidget>
- <customwidget>
- <class>KisIntParseSpinBox</class>
- <extends>QSpinBox</extends>
- <header>kis_int_parse_spin_box.h</header>
- </customwidget>
<customwidget>
<class>KisDoubleParseSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>kis_double_parse_spin_box.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>minimalShadeSelectorAsColorPatches</sender>
<signal>toggled(bool)</signal>
<receiver>minimalShadeSelectorPatchesPerLine</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>194</x>
<y>393</y>
</hint>
<hint type="destinationlabel">
<x>520</x>
<y>463</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/plugins/dockers/animation/kis_time_based_item_model.cpp b/plugins/dockers/animation/kis_time_based_item_model.cpp
index 55b158cdee..b34e9dd393 100644
--- a/plugins/dockers/animation/kis_time_based_item_model.cpp
+++ b/plugins/dockers/animation/kis_time_based_item_model.cpp
@@ -1,436 +1,437 @@
/*
* Copyright (c) 2016 Jouni Pentikäinen <joupent@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_time_based_item_model.h"
#include <QPointer>
#include <kis_config.h>
#include "kis_animation_frame_cache.h"
#include "kis_animation_player.h"
#include "kis_signal_compressor_with_param.h"
#include "kis_image.h"
#include "kis_image_animation_interface.h"
#include "kis_time_range.h"
#include "kis_animation_utils.h"
#include "kis_keyframe_channel.h"
#include "kis_processing_applicator.h"
#include "KisImageBarrierLockerWithFeedback.h"
struct KisTimeBasedItemModel::Private
{
Private()
: animationPlayer(0)
, numFramesOverride(0)
, activeFrameIndex(0)
, scrubInProgress(false)
, scrubStartFrame(-1)
{}
KisImageWSP image;
KisAnimationFrameCacheWSP framesCache;
QPointer<KisAnimationPlayer> animationPlayer;
QVector<bool> cachedFrames;
int numFramesOverride;
int activeFrameIndex;
bool scrubInProgress;
int scrubStartFrame;
QScopedPointer<KisSignalCompressorWithParam<int> > scrubbingCompressor;
int baseNumFrames() const {
if (image.isNull()) return 0;
KisImageAnimationInterface *i = image->animationInterface();
if (!i) return 1;
return i->totalLength();
}
int effectiveNumFrames() const {
if (image.isNull()) return 0;
return qMax(baseNumFrames(), numFramesOverride);
}
int framesPerSecond() {
return image->animationInterface()->framerate();
}
};
KisTimeBasedItemModel::KisTimeBasedItemModel(QObject *parent)
: QAbstractTableModel(parent)
, m_d(new Private())
{
KisConfig cfg;
using namespace std::placeholders;
std::function<void (int)> callback(
std::bind(&KisTimeBasedItemModel::slotInternalScrubPreviewRequested, this, _1));
m_d->scrubbingCompressor.reset(
new KisSignalCompressorWithParam<int>(cfg.scrubbingUpdatesDelay(), callback, KisSignalCompressor::FIRST_ACTIVE));
}
KisTimeBasedItemModel::~KisTimeBasedItemModel()
{}
void KisTimeBasedItemModel::setImage(KisImageWSP image)
{
KisImageWSP oldImage = m_d->image;
m_d->image = image;
if (image) {
KisImageAnimationInterface *ai = image->animationInterface();
slotCurrentTimeChanged(ai->currentUITime());
connect(ai, SIGNAL(sigFramerateChanged()), SLOT(slotFramerateChanged()));
connect(ai, SIGNAL(sigUiTimeChanged(int)), SLOT(slotCurrentTimeChanged(int)));
}
if (image != oldImage) {
- reset();
+ 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, 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 (channel->keyframeAt(srcIndex.column())) {
srcFrameItems << KisAnimationUtils::FrameItem(srcNode, channel->id(), srcIndex.column());
dstFrameItems << KisAnimationUtils::FrameItem(dstNode, channel->id(), dstIndex.column());
}
}
}
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(srcFrameItems.size() == dstFrameItems.size(), 0);
if (srcFrameItems.isEmpty()) return 0;
return
KisAnimationUtils::createMoveKeyframesCommand(srcFrameItems,
dstFrameItems,
copyFrames,
parentCommand);
}
bool KisTimeBasedItemModel::offsetFrames(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames)
{
KUndo2Command *cmd = 0;
{
KisImageBarrierLockerWithFeedback locker(m_d->image);
cmd = createOffsetFramesCommand(srcIndexes, offset, copyFrames);
}
if (cmd) {
KisProcessingApplicator::runSingleCommandStroke(m_d->image, cmd, KisStrokeJobData::BARRIER);
}
return cmd;
}
void KisTimeBasedItemModel::slotInternalScrubPreviewRequested(int time)
{
if (m_d->animationPlayer && !m_d->animationPlayer->isPlaying()) {
m_d->animationPlayer->displayFrame(time);
}
}
void KisTimeBasedItemModel::setScrubState(bool active)
{
if (!m_d->scrubInProgress && active) {
m_d->scrubStartFrame = m_d->activeFrameIndex;
m_d->scrubInProgress = true;
}
if (m_d->scrubInProgress && !active) {
m_d->scrubInProgress = false;
if (m_d->scrubStartFrame >= 0 &&
m_d->scrubStartFrame != m_d->activeFrameIndex) {
scrubTo(m_d->activeFrameIndex, false);
}
m_d->scrubStartFrame = -1;
}
}
void KisTimeBasedItemModel::scrubTo(int time, bool preview)
{
if (m_d->animationPlayer && m_d->animationPlayer->isPlaying()) return;
KIS_ASSERT_RECOVER_RETURN(m_d->image);
if (preview) {
if (m_d->animationPlayer) {
m_d->scrubbingCompressor->start(time);
}
} else {
m_d->image->animationInterface()->requestTimeSwitchWithUndo(time);
}
}
void KisTimeBasedItemModel::slotFramerateChanged()
{
emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
}
void KisTimeBasedItemModel::slotCurrentTimeChanged(int time)
{
if (time != m_d->activeFrameIndex) {
setHeaderData(time, Qt::Horizontal, true, ActiveFrameRole);
}
}
void KisTimeBasedItemModel::slotCacheChanged()
{
const int numFrames = columnCount();
m_d->cachedFrames.resize(numFrames);
for (int i = 0; i < numFrames; i++) {
m_d->cachedFrames[i] =
m_d->framesCache->frameStatus(i) == KisAnimationFrameCache::Cached;
}
emit headerDataChanged(Qt::Horizontal, 0, numFrames);
}
void KisTimeBasedItemModel::slotPlaybackFrameChanged()
{
if (!m_d->animationPlayer->isPlaying()) return;
setData(index(0, m_d->animationPlayer->currentTime()), true, ActiveFrameRole);
}
void KisTimeBasedItemModel::slotPlaybackStopped()
{
setData(index(0, m_d->image->animationInterface()->currentUITime()), true, ActiveFrameRole);
}
void KisTimeBasedItemModel::setPlaybackRange(const KisTimeRange &range)
{
if (m_d->image.isNull()) return;
KisImageAnimationInterface *i = m_d->image->animationInterface();
i->setPlaybackRange(range);
}
bool KisTimeBasedItemModel::isPlaybackActive() const
{
return m_d->animationPlayer && m_d->animationPlayer->isPlaying();
}
int KisTimeBasedItemModel::currentTime() const
{
return m_d->image->animationInterface()->currentUITime();
}
KisImageWSP KisTimeBasedItemModel::image() const
{
return m_d->image;
}
diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp
index e42bb122f5..732d3a6ada 100644
--- a/plugins/dockers/animation/timeline_frames_model.cpp
+++ b/plugins/dockers/animation/timeline_frames_model.cpp
@@ -1,717 +1,718 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "timeline_frames_model.h"
#include <QFont>
#include <QSize>
#include <QColor>
#include <QMimeData>
#include <QPointer>
#include <KoResourceModel.h>
#include "kis_layer.h"
#include "kis_config.h"
#include "kis_global.h"
#include "kis_debug.h"
#include "kis_image.h"
#include "kis_image_animation_interface.h"
#include "kis_undo_adapter.h"
#include "kis_node_dummies_graph.h"
#include "kis_dummies_facade_base.h"
#include "kis_signal_compressor.h"
#include "kis_signal_compressor_with_param.h"
#include "kis_keyframe_channel.h"
#include "kundo2command.h"
#include "kis_post_execution_undo_adapter.h"
#include <commands/kis_node_property_list_command.h>
#include "kis_animation_utils.h"
#include "timeline_color_scheme.h"
#include "kis_node_model.h"
#include "kis_projection_leaf.h"
#include "kis_time_range.h"
#include "kis_node_view_color_scheme.h"
#include "krita_utils.h"
#include <QApplication>
struct TimelineFramesModel::Private
{
Private()
: activeLayerIndex(0),
dummiesFacade(0),
needFinishInsertRows(false),
needFinishRemoveRows(false),
updateTimer(200, KisSignalCompressor::FIRST_INACTIVE),
parentOfRemovedNode(0)
{}
int activeLayerIndex;
QPointer<KisDummiesFacadeBase> dummiesFacade;
KisImageWSP image;
bool needFinishInsertRows;
bool needFinishRemoveRows;
QList<KisNodeDummy*> updateQueue;
KisSignalCompressor updateTimer;
KisNodeDummy* parentOfRemovedNode;
QScopedPointer<TimelineNodeListKeeper> converter;
QScopedPointer<NodeManipulationInterface> nodeInterface;
QPersistentModelIndex lastClickedIndex;
QVariant layerName(int row) const {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return QVariant();
return dummy->node()->name();
}
bool layerEditable(int row) const {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return true;
return dummy->node()->visible() && !dummy->node()->userLocked();
}
bool frameExists(int row, int column) const {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return false;
KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id());
return (primaryChannel && primaryChannel->keyframeAt(column));
}
bool specialKeyframeExists(int row, int column) {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return false;
Q_FOREACH(KisKeyframeChannel *channel, dummy->node()->keyframeChannels()) {
if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) {
return true;
}
}
return false;
}
int frameColorLabel(int row, int column) {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return -1;
KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id());
if (!primaryChannel) return -1;
KisKeyframeSP frame = primaryChannel->keyframeAt(column);
if (!frame) return -1;
return frame->colorLabel();
}
void setFrameColorLabel(int row, int column, int color) {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return;
KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id());
if (!primaryChannel) return;
KisKeyframeSP frame = primaryChannel->keyframeAt(column);
if (!frame) return;
frame->setColorLabel(color);
}
int layerColorLabel(int row) const {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return -1;
return dummy->node()->colorLabelIndex();
}
QVariant layerProperties(int row) const {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return QVariant();
PropertyList props = dummy->node()->sectionModelProperties();
return QVariant::fromValue(props);
}
bool setLayerProperties(int row, PropertyList props) {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return false;
KisNodePropertyListCommand::setNodePropertiesNoUndo(dummy->node(), image, props);
return true;
}
bool addKeyframe(int row, int column, bool copy) {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return false;
KisNodeSP node = dummy->node();
if (!KisAnimationUtils::supportsContentFrames(node)) return false;
KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy);
return true;
}
bool addNewLayer(int row) {
Q_UNUSED(row);
if (nodeInterface) {
KisLayerSP layer = nodeInterface->addPaintLayer();
layer->setUseInTimeline(true);
}
return true;
}
bool removeLayer(int row) {
KisNodeDummy *dummy = converter->dummyFromRow(row);
if (!dummy) return false;
if (nodeInterface) {
nodeInterface->removeNode(dummy->node());
}
return true;
}
};
TimelineFramesModel::TimelineFramesModel(QObject *parent)
: ModelWithExternalNotifications(parent),
m_d(new Private)
{
connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue()));
}
TimelineFramesModel::~TimelineFramesModel()
{
}
bool TimelineFramesModel::hasConnectionToCanvas() const
{
return m_d->dummiesFacade;
}
void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface)
{
m_d->nodeInterface.reset(iface);
}
KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const
{
/**
* The dummy might not exist because the user could (quickly) change
* active layer and the list of the nodes in m_d->converter will change.
*/
KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row());
return dummy ? dummy->node() : 0;
}
QMap<QString, KisKeyframeChannel*> TimelineFramesModel::channelsAt(QModelIndex index) const
{
KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row());
return srcDummy->node()->keyframeChannels();
}
void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image)
{
KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade;
if (m_d->dummiesFacade && m_d->image) {
m_d->image->animationInterface()->disconnect(this);
m_d->image->disconnect(this);
m_d->dummiesFacade->disconnect(this);
}
m_d->image = image;
KisTimeBasedItemModel::setImage(image);
m_d->dummiesFacade = dummiesFacade;
m_d->converter.reset();
if (m_d->dummiesFacade) {
m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade));
connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)),
SLOT(slotDummyChanged(KisNodeDummy*)));
connect(m_d->image->animationInterface(),
SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded()));
connect(m_d->image->animationInterface(),
SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged()));
connect(m_d->image->animationInterface(),
SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged()));
}
if (m_d->dummiesFacade != oldDummiesFacade) {
- reset();
+ beginResetModel();
+ endResetModel();
}
if (m_d->dummiesFacade) {
emit sigInfiniteTimelineUpdateNeeded();
emit sigAudioChannelChanged();
}
}
void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy)
{
if (!m_d->updateQueue.contains(dummy)) {
m_d->updateQueue.append(dummy);
}
m_d->updateTimer.start();
}
void TimelineFramesModel::processUpdateQueue()
{
Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) {
int row = m_d->converter->rowForDummy(dummy);
if (row >= 0) {
emit headerDataChanged (Qt::Vertical, row, row);
emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1));
}
}
m_d->updateQueue.clear();
}
void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node)
{
if (!node) {
m_d->activeLayerIndex = -1;
return;
}
KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node);
KIS_ASSERT_RECOVER_RETURN(dummy);
m_d->converter->updateActiveDummy(dummy);
const int row = m_d->converter->rowForDummy(dummy);
if (row < 0) {
qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!";
}
if (row >= 0 && m_d->activeLayerIndex != row) {
setData(index(row, 0), true, ActiveLayerRole);
}
}
int TimelineFramesModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
if(!m_d->dummiesFacade) return 0;
return m_d->converter->rowCount();
}
QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const
{
if(!m_d->dummiesFacade) return QVariant();
switch (role) {
case ActiveLayerRole: {
return index.row() == m_d->activeLayerIndex;
}
case FrameEditableRole: {
return m_d->layerEditable(index.row());
}
case FrameExistsRole: {
return m_d->frameExists(index.row(), index.column());
}
case SpecialKeyframeExists: {
return m_d->specialKeyframeExists(index.row(), index.column());
}
case FrameColorLabelIndexRole: {
int label = m_d->frameColorLabel(index.row(), index.column());
return label > 0 ? label : QVariant();
}
case Qt::DisplayRole: {
return m_d->layerName(index.row());
}
case Qt::TextAlignmentRole: {
return QVariant(Qt::AlignHCenter | Qt::AlignVCenter);
}
case KoResourceModel::LargeThumbnailRole: {
KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row());
if (!dummy) {
return QVariant();
}
const int maxSize = 200;
QSize size = dummy->node()->extent().size();
size.scale(maxSize, maxSize, Qt::KeepAspectRatio);
if (size.width() == 0 || size.height() == 0) {
// No thumbnail can be shown if there isn't width or height...
return QVariant();
}
QImage image(dummy->node()->createThumbnailForFrame(size.width(), size.height(), index.column()));
return image;
}
}
return ModelWithExternalNotifications::data(index, role);
}
bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid() || !m_d->dummiesFacade) return false;
switch (role) {
case ActiveLayerRole: {
if (value.toBool() &&
index.row() != m_d->activeLayerIndex) {
int prevLayer = m_d->activeLayerIndex;
m_d->activeLayerIndex = index.row();
emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1));
emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1));
emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer);
emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex);
KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex);
KIS_ASSERT_RECOVER(dummy) { return true; }
emit requestCurrentNodeChanged(dummy->node());
emit sigEnsureRowVisible(m_d->activeLayerIndex);
}
break;
}
case FrameColorLabelIndexRole: {
m_d->setFrameColorLabel(index.row(), index.column(), value.toInt());
}
break;
}
return ModelWithExternalNotifications::setData(index, value, role);
}
QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(!m_d->dummiesFacade) return QVariant();
if (orientation == Qt::Vertical) {
switch (role) {
case ActiveLayerRole:
return section == m_d->activeLayerIndex;
case Qt::DisplayRole: {
QVariant value = headerData(section, orientation, Qt::ToolTipRole);
if (!value.isValid()) return value;
QString name = value.toString();
const int maxNameSize = 13;
if (name.size() > maxNameSize) {
name = QString("%1...").arg(name.left(maxNameSize));
}
return name;
}
case Qt::TextColorRole: {
// WARNING: this role doesn't work for header views! Use
// bold font to show isolated mode instead!
return QVariant();
}
case Qt::FontRole: {
KisNodeDummy *dummy = m_d->converter->dummyFromRow(section);
if (!dummy) return QVariant();
KisNodeSP node = dummy->node();
QFont baseFont;
if (node->projectionLeaf()->isDroppedMask()) {
baseFont.setStrikeOut(true);
} else if (m_d->image && m_d->image->isolatedModeRoot() &&
KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) {
baseFont.setBold(true);
}
return baseFont;
}
case Qt::ToolTipRole: {
return m_d->layerName(section);
}
case TimelinePropertiesRole: {
return QVariant::fromValue(m_d->layerProperties(section));
}
case OtherLayersRole: {
TimelineNodeListKeeper::OtherLayersList list =
m_d->converter->otherLayersList();
return QVariant::fromValue(list);
}
case LayerUsedInTimelineRole: {
KisNodeDummy *dummy = m_d->converter->dummyFromRow(section);
if (!dummy) return QVariant();
return dummy->node()->useInTimeline();
}
case Qt::BackgroundRole: {
int label = m_d->layerColorLabel(section);
if (label > 0) {
KisNodeViewColorScheme scm;
QColor color = scm.colorLabel(label);
QPalette pal = qApp->palette();
color = KritaUtils::blendColors(color, pal.color(QPalette::Button), 0.3);
return QBrush(color);
} else {
return QVariant();
}
}
}
}
return ModelWithExternalNotifications::headerData(section, orientation, role);
}
bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
if (!m_d->dummiesFacade) return false;
if (orientation == Qt::Vertical) {
switch (role) {
case ActiveLayerRole: {
setData(index(section, 0), value, role);
break;
}
case TimelinePropertiesRole: {
TimelineFramesModel::PropertyList props = value.value<TimelineFramesModel::PropertyList>();
int result = m_d->setLayerProperties(section, props);
emit headerDataChanged (Qt::Vertical, section, section);
return result;
}
case LayerUsedInTimelineRole: {
KisNodeDummy *dummy = m_d->converter->dummyFromRow(section);
if (!dummy) return false;
dummy->node()->setUseInTimeline(value.toBool());
return true;
}
}
}
return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role);
}
Qt::DropActions TimelineFramesModel::supportedDragActions() const
{
return Qt::MoveAction | Qt::CopyAction;
}
Qt::DropActions TimelineFramesModel::supportedDropActions() const
{
return Qt::MoveAction | Qt::CopyAction;
}
QStringList TimelineFramesModel::mimeTypes() const
{
QStringList types;
types << QLatin1String("application/x-krita-frame");
return types;
}
void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index)
{
m_d->lastClickedIndex = index;
}
QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *data = new QMimeData();
QByteArray encoded;
QDataStream stream(&encoded, QIODevice::WriteOnly);
const int baseRow = m_d->lastClickedIndex.row();
const int baseColumn = m_d->lastClickedIndex.column();
stream << indexes.size();
stream << baseRow << baseColumn;
Q_FOREACH (const QModelIndex &index, indexes) {
stream << index.row() - baseRow << index.column() - baseColumn;
}
data->setData("application/x-krita-frame", encoded);
return data;
}
inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col)
{
int size_UNUSED = 0;
QDataStream stream(encoded, QIODevice::ReadOnly);
stream >> size_UNUSED >> *row >> *col;
}
bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index)
{
if (!index.isValid()) return false;
/**
* Now we support D&D around any layer, so just return 'true' all
* the time.
*/
return true;
}
bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_UNUSED(row);
Q_UNUSED(column);
bool result = false;
if ((action != Qt::MoveAction &&
action != Qt::CopyAction) || !parent.isValid()) return result;
const bool copyFrames = action == Qt::CopyAction;
QByteArray encoded = data->data("application/x-krita-frame");
QDataStream stream(&encoded, QIODevice::ReadOnly);
int size, baseRow, baseColumn;
stream >> size >> baseRow >> baseColumn;
QModelIndexList srcIndexes;
for (int i = 0; i < size; i++) {
int relRow, relColumn;
stream >> relRow >> relColumn;
int srcRow = baseRow + relRow;
int srcColumn = baseColumn + relColumn;
srcIndexes << index(srcRow, srcColumn);
}
const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow);
return offsetFrames(srcIndexes, offset, copyFrames);
}
Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index);
if (!index.isValid()) return flags;
if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) {
if (data(index, FrameEditableRole).toBool()) {
flags |= Qt::ItemIsDragEnabled;
}
}
/**
* Basically we should forbid overrides only if we D&D a single frame
* and allow it when we D&D multiple frames. But we cannot distinguish
* it here... So allow all the time.
*/
flags |= Qt::ItemIsDropEnabled;
return flags;
}
bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(parent);
KIS_ASSERT_RECOVER(count == 1) { return false; }
if (row < 0 || row > rowCount()) return false;
bool result = m_d->addNewLayer(row);
return result;
}
bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(parent);
KIS_ASSERT_RECOVER(count == 1) { return false; }
if (row < 0 || row >= rowCount()) return false;
bool result = m_d->removeLayer(row);
return result;
}
bool TimelineFramesModel::insertOtherLayer(int index, int dstRow)
{
Q_UNUSED(dstRow);
TimelineNodeListKeeper::OtherLayersList list =
m_d->converter->otherLayersList();
if (index < 0 || index >= list.size()) return false;
list[index].dummy->node()->setUseInTimeline(true);
dstRow = m_d->converter->rowForDummy(list[index].dummy);
setData(this->index(dstRow, 0), true, ActiveLayerRole);
return true;
}
int TimelineFramesModel::activeLayerRow() const
{
return m_d->activeLayerIndex;
}
bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex)
{
if (!dstIndex.isValid()) return false;
return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false);
}
bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex)
{
if (!dstIndex.isValid()) return false;
return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true);
}
QString TimelineFramesModel::audioChannelFileName() const
{
return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString();
}
void TimelineFramesModel::setAudioChannelFileName(const QString &fileName)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image);
m_d->image->animationInterface()->setAudioChannelFileName(fileName);
}
bool TimelineFramesModel::isAudioMuted() const
{
return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false;
}
void TimelineFramesModel::setAudioMuted(bool value)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image);
m_d->image->animationInterface()->setAudioMuted(value);
}
qreal TimelineFramesModel::audioVolume() const
{
return m_d->image ? m_d->image->animationInterface()->audioVolume() : 0.5;
}
void TimelineFramesModel::setAudioVolume(qreal value)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image);
m_d->image->animationInterface()->setAudioVolume(value);
}
diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp
index 3f9ee73af7..a5759a4664 100644
--- a/plugins/dockers/animation/timeline_frames_view.cpp
+++ b/plugins/dockers/animation/timeline_frames_view.cpp
@@ -1,1098 +1,1098 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "timeline_frames_view.h"
#include "timeline_frames_model.h"
#include "timeline_ruler_header.h"
#include "timeline_layers_header.h"
#include <cmath>
#include <limits>
#include <QPainter>
#include <QFileInfo>
#include <QApplication>
#include <QHeaderView>
#include <QDropEvent>
#include <QToolButton>
#include <QMenu>
#include <QScrollBar>
#include <QIcon>
#include <QDrag>
#include <QWidgetAction>
#include <kis_signals_blocker.h>
#include <kis_image_config.h>
#include "kis_debug.h"
#include "timeline_frames_item_delegate.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_slider_spin_box.h"
#include <KisImportExportManager.h>
#include <KoFileDialog.h>
#include <KoIconToolTip.h>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QWidgetAction>
#include "config-qtmultimedia.h"
typedef QPair<QRect, QModelIndex> QItemViewPaintPair;
typedef QList<QItemViewPaintPair> 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 *showHideLayerAction;
QToolButton *audioOptionsButton;
KisColorLabelSelectorWidget *colorSelector;
QWidgetAction *colorSelectorAction;
KisColorLabelSelectorWidget *multiframeColorSelector;
QWidgetAction *multiframeColorSelectorAction;
QMenu *audioOptionsMenu;
QAction *openAudioAction;
QAction *audioMuteAction;
KisSliderSpinBox *volumeSlider;
QMenu *layerEditingMenu;
QMenu *existingLayersMenu;
QMenu *frameCreationMenu;
QMenu *frameEditingMenu;
QMenu *multipleFrameEditingMenu;
QMap<QString, KisAction*> globalActions;
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;
KoIconToolTip tip;
};
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);
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()));
/********** New Layer Menu ***********************************************************/
m_d->addLayersButton = new QToolButton(this);
m_d->addLayersButton->setAutoRaise(true);
m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer"));
m_d->addLayersButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_d->addLayersButton->setPopupMode(QToolButton::InstantPopup);
m_d->layerEditingMenu = new QMenu(this);
m_d->layerEditingMenu->addAction(KisAnimationUtils::newLayerActionName, this, SLOT(slotAddNewLayer()));
m_d->existingLayersMenu = m_d->layerEditingMenu->addMenu(KisAnimationUtils::addExistingLayerActionName);
m_d->layerEditingMenu->addSeparator();
m_d->layerEditingMenu->addAction(KisAnimationUtils::removeLayerActionName, this, SLOT(slotRemoveLayer()));
connect(m_d->existingLayersMenu, SIGNAL(aboutToShow()), SLOT(slotUpdateLayersMenu()));
connect(m_d->existingLayersMenu, SIGNAL(triggered(QAction*)), SLOT(slotAddExistingLayer(QAction*)));
connect(m_d->layersHeader, SIGNAL(sigRequestContextMenu(const QPoint&)), SLOT(slotLayerContextMenuRequested(const QPoint&)));
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->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_d->audioOptionsButton->setPopupMode(QToolButton::InstantPopup);
m_d->audioOptionsMenu = new QMenu(this);
#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("%");
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->frameCreationMenu = new QMenu(this);
m_d->frameCreationMenu->addAction(KisAnimationUtils::addFrameActionName, this, SLOT(slotNewFrame()));
m_d->frameCreationMenu->addAction(KisAnimationUtils::duplicateFrameActionName, this, SLOT(slotCopyFrame()));
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->frameEditingMenu = new QMenu(this);
m_d->frameEditingMenu->addAction(KisAnimationUtils::removeFrameActionName, this, SLOT(slotRemoveFrame()));
m_d->frameEditingMenu->addAction(m_d->colorSelectorAction);
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);
m_d->multipleFrameEditingMenu = new QMenu(this);
m_d->multipleFrameEditingMenu->addAction(KisAnimationUtils::removeFramesActionName, this, SLOT(slotRemoveFrame()));
m_d->multipleFrameEditingMenu->addAction(m_d->multiframeColorSelectorAction);
/********** Zoom Button **************************************************************/
m_d->zoomDragButton = new KisZoomButton(this);
m_d->zoomDragButton->setAutoRaise(true);
m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal"));
m_d->zoomDragButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
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);
connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()),
SLOT(slotSelectionChanged()));
}
TimelineFramesView::~TimelineFramesView()
{
}
QMap<QString, KisAction*> TimelineFramesView::globalActions() const
{
return m_d->globalActions;
}
void TimelineFramesView::setShowInTimeline(KisAction* action)
{
m_d->showHideLayerAction = action;
m_d->layerEditingMenu->addAction(m_d->showHideLayerAction);
}
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<TimelineFramesModel*>(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(const 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(const QItemSelection &, const 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 config;
config.setDefaultFrameColorLabel(label);
}
void TimelineFramesView::slotSelectAudioChannelFile()
{
if (!m_d->model) return;
- QString defaultDir = QDesktopServices::storageLocation(QDesktopServices::MusicLocation);
+ 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::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::currentChanged(const QModelIndex &current, 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<const QMouseEvent*>(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<int>::max();
int maxColumn = std::numeric_limits<int>::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);
}
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;
setCurrentIndex(m_d->model->index(row, selectedColumn));
}
}
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 &current = 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<int> rows;
int leftmostColumn = std::numeric_limits<int>::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);
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::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);
}
m_d->frameEditingMenu->exec(event->globalPos());
} else {
m_d->frameCreationMenu->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) {
m_d->multiframeColorSelectorAction->setVisible(false);
} else {
KisSignalsBlocker b(m_d->multiframeColorSelector);
m_d->multiframeColorSelector->setCurrentIndex(labelIndex);
m_d->multiframeColorSelectorAction->setVisible(true);
}
m_d->multipleFrameEditingMenu->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<TimelineFramesModel::OtherLayersList>();
int i = 0;
Q_FOREACH (const TimelineFramesModel::OtherLayer &l, list) {
action = m_d->existingLayersMenu->addAction(l.name);
action->setData(i++);
}
}
}
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::slotNewFrame()
{
QModelIndex index = currentIndex();
if (!index.isValid() ||
!m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) {
return;
}
m_d->model->createFrame(index);
}
void TimelineFramesView::slotCopyFrame()
{
QModelIndex index = currentIndex();
if (!index.isValid() ||
!m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) {
return;
}
m_d->model->copyFrame(index);
}
void TimelineFramesView::slotRemoveFrame()
{
QModelIndexList indexes = selectionModel()->selectedIndexes();
for (auto it = indexes.begin(); it != indexes.end(); /*noop*/) {
if (!m_d->model->data(*it, TimelineFramesModel::FrameEditableRole).toBool()) {
it = indexes.erase(it);
} else {
++it;
}
}
if (!indexes.isEmpty()) {
m_d->model->removeFrames(indexes);
}
}
bool TimelineFramesView::viewportEvent(QEvent *event)
{
if (event->type() == QEvent::ToolTip && model()) {
QHelpEvent *he = static_cast<QHelpEvent *>(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/dockers/artisticcolorselector/kis_color_selector.cpp b/plugins/dockers/artisticcolorselector/kis_color_selector.cpp
index 717531fcda..8c479ba6cf 100644
--- a/plugins/dockers/artisticcolorselector/kis_color_selector.cpp
+++ b/plugins/dockers/artisticcolorselector/kis_color_selector.cpp
@@ -1,717 +1,717 @@
/*
Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
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 <QPaintDevice>
#include <QPainter>
#include <QColor>
#include <QBrush>
#include <QPen>
#include <QRadialGradient>
#include <QConicalGradient>
#include <QMouseEvent>
#include <QResizeEvent>
#include <QTransform>
#include <QList>
#include <cmath>
#include <kis_config.h>
#include "kis_color_selector.h"
static const int MIN_NUM_HUE_PIECES = 1;
static const int MAX_NUM_HUE_PIECES = 48;
static const int MIN_NUM_LIGHT_PIECES = 1;
static const int MAX_NUM_LIGHT_PIECES = 30;
static const int MIN_NUM_SATURATION_RINGS = 1;
static const int MAX_NUM_SATURATION_RINGS = 20;
KisColorSelector::KisColorSelector(QWidget* parent, KisColor::Type type):
QWidget(parent),
m_colorSpace(type),
m_inverseSaturation(false),
m_relativeLight(false),
m_light(0.5f),
m_selectedColorRole(Acs::Foreground),
m_clickedRing(-1)
{
recalculateRings(9, 12);
recalculateAreas(9);
selectColor(KisColor(Qt::red, KisColor::HSY));
using namespace std::placeholders; // For _1 placeholder
auto function = std::bind(&KisColorSelector::slotUpdateColorAndPreview, this, _1);
m_updateColorCompressor.reset(new ColorCompressorType(20 /* ms */, function));
}
void KisColorSelector::setColorSpace(KisColor::Type type)
{
m_colorSpace = type;
m_selectedColor = KisColor(m_selectedColor, m_colorSpace);
update();
}
void KisColorSelector::setNumLightPieces(int num)
{
num = qBound(MIN_NUM_LIGHT_PIECES, num, MAX_NUM_LIGHT_PIECES);
-
+
recalculateAreas(quint8(num));
-
+
if (m_selectedLightPiece >= 0)
m_selectedLightPiece = getLightIndex(m_selectedColor.getX());
-
+
update();
}
void KisColorSelector::setNumPieces(int num)
{
num = qBound(MIN_NUM_HUE_PIECES, num, MAX_NUM_HUE_PIECES);
-
+
recalculateRings(quint8(getNumRings()), quint8(num));
-
+
if (m_selectedPiece >= 0)
m_selectedPiece = getHueIndex(m_selectedColor.getH() * PI2);
-
+
update();
}
void KisColorSelector::setNumRings(int num)
{
num = qBound(MIN_NUM_SATURATION_RINGS, num, MAX_NUM_SATURATION_RINGS);
-
+
recalculateRings(quint8(num), quint8(getNumPieces()));
-
+
if (m_selectedRing >= 0)
m_selectedRing = getSaturationIndex(m_selectedColor.getS());
-
+
update();
}
void KisColorSelector::selectColor(const KisColor& color)
{
m_selectedColor = KisColor(color, m_colorSpace);
m_selectedPiece = getHueIndex(m_selectedColor.getH() * PI2);
m_selectedRing = getSaturationIndex(m_selectedColor.getS());
m_selectedLightPiece = getLightIndex(m_selectedColor.getX());
update();
}
void KisColorSelector::setFgColor(const KisColor& fgColor)
{
m_fgColor = KisColor(fgColor, m_colorSpace);
update();
}
void KisColorSelector::setBgColor(const KisColor& bgColor)
{
m_bgColor = KisColor(bgColor, m_colorSpace);
update();
}
void KisColorSelector::resetRings()
{
for(int i=0; i<m_colorRings.size(); ++i)
m_colorRings[i].angle = 0.0f;
-
+
update();
}
void KisColorSelector::resetLight()
{
m_light = (m_colorSpace == KisColor::HSV) ? 1.0f : 0.5f;
m_selectedLightPiece = getLightIndex(m_light);
update();
}
void KisColorSelector::resetSelectedRing()
{
if (m_selectedRing >= 0) {
m_colorRings[m_selectedRing].angle = 0.0f;
update();
}
}
void KisColorSelector::setLight(float light, bool relative)
{
m_light = qBound(0.0f, light, 1.0f);
-
+
m_selectedColor.setX(getLight(m_light, m_selectedColor.getH(), relative));
m_relativeLight = relative;
m_selectedLightPiece = getLightIndex(m_selectedColor.getX());
update();
}
void KisColorSelector::setInverseSaturation(bool inverse)
{
if (m_inverseSaturation != inverse) {
m_selectedRing = (getNumRings()-1) - m_selectedRing;
m_inverseSaturation = inverse;
recalculateRings(quint8(getNumRings()), quint8(getNumPieces()));
update();
}
}
QPointF KisColorSelector::mapCoord(const QPointF& pt, const QRectF& rect) const
{
qreal w = rect.width() / 2.0;
qreal h = rect.height() / 2.0;
qreal x = pt.x() - (rect.x() + w);
qreal y = pt.y() - (rect.y() + h);
return QPointF(x/w, y/h);
}
qint8 KisColorSelector::getLightIndex(const QPointF& pt) const
{
if (m_lightStripArea.contains(pt.toPoint(), true)) {
qreal t = (pt.x() - m_lightStripArea.x()) / qreal(m_lightStripArea.width());
t = (pt.y() - m_lightStripArea.y()) / qreal(m_lightStripArea.height());
return qint8(t * getNumLightPieces());
}
-
+
return -1;
}
qint8 KisColorSelector::getLightIndex(qreal light) const
{
light = qreal(1) - qBound(qreal(0), light, qreal(1));
return qint8(qRound(light * (getNumLightPieces()-1)));
}
qreal KisColorSelector::getLight(qreal light, qreal hue, bool relative) const
{
if (relative) {
KisColor color(hue, 1.0f, m_colorSpace);
qreal cl = color.getX();
light = (light * 2.0f) - 1.0f;
return (light < 0.0f) ? (cl + cl*light) : (cl + (1.0f-cl)*light);
}
-
+
return light;
}
qreal KisColorSelector::getLight(const QPointF& pt) const
{
qint8 clickedLightPiece = getLightIndex(pt);
-
+
if (clickedLightPiece >= 0) {
if (getNumLightPieces() > 1) {
return 1.0 - (qreal(clickedLightPiece) / qreal(getNumLightPieces()-1));
}
return 1.0 - (qreal(pt.y()) / qreal(m_lightStripArea.height()));
}
-
+
return qreal(0);
}
qint8 KisColorSelector::getHueIndex(Radian hue, Radian shift) const
{
hue -= shift;
qreal partSize = 1.0 / qreal(getNumPieces());
return qint8(qRound(hue.scaled(0.0f, 1.0f) / partSize) % getNumPieces());
}
qreal KisColorSelector::getHue(int hueIdx, Radian shift) const
{
Radian hue = (qreal(hueIdx) / qreal(getNumPieces())) * PI2;
hue += shift;
return hue.scaled(0.0f, 1.0f);
}
qint8 KisColorSelector::getSaturationIndex(qreal saturation) const
{
saturation = qBound(qreal(0), saturation, qreal(1));
saturation = m_inverseSaturation ? (qreal(1) - saturation) : saturation;
return qint8(saturation * qreal(getNumRings() - 1));
}
qint8 KisColorSelector::getSaturationIndex(const QPointF& pt) const
{
qreal length = std::sqrt(pt.x()*pt.x() + pt.y()*pt.y());
-
+
for(int i=0; i<m_colorRings.size(); ++i) {
if (length >= m_colorRings[i].innerRadius && length < m_colorRings[i].outerRadius)
return qint8(i);
}
return -1;
}
qreal KisColorSelector::getSaturation(int saturationIdx) const
{
qreal sat = qreal(saturationIdx) / qreal(getNumRings()-1);
return m_inverseSaturation ? (1.0 - sat) : sat;
}
void KisColorSelector::recalculateAreas(quint8 numLightPieces)
{
const qreal LIGHT_STRIP_RATIO = 0.075;
-
+
int width = QWidget::width();
int height = QWidget::height();
int size = qMin(width, height);
int stripThick = int(size * LIGHT_STRIP_RATIO);
-
+
width -= stripThick;
size = qMin(width, height);
-
+
int x = (width - size) / 2;
int y = (height - size) / 2;
-
+
m_renderArea = QRect(x+stripThick, y, size, size);
m_lightStripArea = QRect(0, 0, stripThick, QWidget::height());
m_renderBuffer = QImage(size, size, QImage::Format_ARGB32);
m_numLightPieces = numLightPieces;
}
void KisColorSelector::recalculateRings(quint8 numRings, quint8 numPieces)
{
m_colorRings.resize(numRings);
m_numPieces = numPieces;
-
+
for(int i=0; i<numRings; ++i) {
qreal innerRadius = qreal(i) / qreal(numRings);
qreal outerRadius = qreal(i+1) / qreal(numRings);
qreal saturation = qreal(i) / qreal(numRings-1);
-
+
createRing(m_colorRings[i], numPieces, innerRadius, outerRadius+0.001);
m_colorRings[i].saturation = m_inverseSaturation ? (1.0 - saturation) : saturation;
}
}
void KisColorSelector::createRing(ColorRing& ring, quint8 numPieces, qreal innerRadius, qreal outerRadius)
{
int numParts = qMax<int>(numPieces, 1);
ring.innerRadius = innerRadius;
ring.outerRadius = outerRadius;
ring.pieced.resize(numParts);
-
+
qreal partSize = 360.0 / qreal(numParts);
QRectF outerRect(-outerRadius, -outerRadius, outerRadius*2.0, outerRadius*2.0);
QRectF innerRect(-innerRadius, -innerRadius, innerRadius*2.0, innerRadius*2.0);
-
+
for(int i=0; i<numParts; ++i) {
qreal aBeg = partSize*i;
qreal aEnd = aBeg + partSize;
-
+
aBeg -= partSize / 2.0;
aEnd -= partSize / 2.0;
-
+
ring.pieced[i] = QPainterPath();
ring.pieced[i].arcMoveTo(innerRect, aBeg);
ring.pieced[i].arcTo(outerRect, aBeg, partSize);
ring.pieced[i].arcTo(innerRect, aEnd,-partSize);
}
}
void KisColorSelector::requestUpdateColorAndPreview(const KisColor &color, Acs::ColorRole role)
{
m_updateColorCompressor->start(qMakePair(color, role));
}
void KisColorSelector::slotUpdateColorAndPreview(QPair<KisColor, Acs::ColorRole> color)
{
const bool selectAsFgColor = color.second == Acs::Foreground;
if (selectAsFgColor) { m_fgColor = color.first; }
else { m_bgColor = color.first; }
m_selectedColor = color.first;
m_selectedColorRole = color.second;
if (selectAsFgColor) { emit sigFgColorChanged(m_selectedColor); }
else { emit sigBgColorChanged(m_selectedColor); }
}
void KisColorSelector::drawRing(QPainter& painter, KisColorSelector::ColorRing& ring, const QRect& rect)
{
painter.setRenderHint(QPainter::Antialiasing, false);
painter.resetTransform();
painter.translate(rect.width()/2, rect.height()/2);
-
+
if (ring.pieced.size() > 1) {
painter.rotate(-ring.getShift().degrees());
painter.scale(rect.width()/2, rect.height()/2);
painter.setPen(Qt::NoPen);
-
+
QBrush brush(Qt::SolidPattern);
-
+
for(int i=0; i<ring.pieced.size(); ++i) {
float hue = float(i) / float(ring.pieced.size()) + ring.getShift().scaled(0.0f, 1.0f);
hue = (hue >= 1.0f) ? (hue - 1.0f) : hue;
hue = (hue < 0.0f) ? (hue + 1.0f) : hue;
-
+
KisColor color(hue, 1.0f, m_colorSpace);
color.setS(ring.saturation);
color.setX(getLight(m_light, hue, m_relativeLight));
-
+
brush.setColor(color.getQColor());
painter.fillPath(ring.pieced[i], brush);
}
}
else {
KisColor colors[7] = {
KisColor(Qt::red , m_colorSpace),
KisColor(Qt::yellow , m_colorSpace),
KisColor(Qt::green , m_colorSpace),
KisColor(Qt::cyan , m_colorSpace),
KisColor(Qt::blue , m_colorSpace),
KisColor(Qt::magenta, m_colorSpace),
KisColor(Qt::red , m_colorSpace)
};
-
+
QConicalGradient gradient(0, 0, 0);
-
+
for(int i=0; i<=6; ++i) {
qreal hue = float(i) / 6.0f;
colors[i].setS(ring.saturation);
colors[i].setX(getLight(m_light, hue, m_relativeLight));
gradient.setColorAt(hue, colors[i].getQColor());
}
-
+
painter.scale(rect.width()/2, rect.height()/2);
painter.fillPath(ring.pieced[0], QBrush(gradient));
}
-
+
painter.resetTransform();
}
void KisColorSelector::drawOutline(QPainter& painter, const QRect& rect)
{
painter.setRenderHint(QPainter::Antialiasing, true);
painter.resetTransform();
painter.translate(rect.x() + rect.width()/2, rect.y() + rect.height()/2);
painter.scale(rect.width()/2, rect.height()/2);
painter.setPen(QPen(QBrush(Qt::gray), 0.005));
-
+
if (getNumPieces() > 1) {
for(int i=0; i<getNumRings(); ++i) {
painter.resetTransform();
painter.translate(rect.x() + rect.width()/2, rect.y() + rect.height()/2);
painter.scale(rect.width()/2, rect.height()/2);
painter.rotate(-m_colorRings[i].getShift().degrees());
-
+
for(int j=0; j<m_colorRings[i].pieced.size(); ++j)
painter.drawPath(m_colorRings[i].pieced[j]);
}
-
+
if (m_selectedRing >= 0 && m_selectedPiece >= 0) {
painter.resetTransform();
painter.translate(rect.x() + rect.width()/2, rect.y() + rect.height()/2);
painter.rotate(-m_colorRings[m_selectedRing].getShift().degrees());
painter.scale(rect.width()/2, rect.height()/2);
-
+
painter.setPen(QPen(QBrush(Qt::red), 0.01));
painter.drawPath(m_colorRings[m_selectedRing].pieced[m_selectedPiece]);
}
}
else {
for(int i=0; i<getNumRings(); ++i) {
qreal rad = m_colorRings[i].outerRadius;
painter.drawEllipse(QRectF(-rad, -rad, rad*2.0, rad*2.0));
}
}
-
+
if (m_selectedRing >= 0) {
qreal iRad = m_colorRings[m_selectedRing].innerRadius;
qreal oRad = m_colorRings[m_selectedRing].outerRadius;
-
+
painter.setPen(QPen(QBrush(Qt::red), 0.005));
painter.drawEllipse(QRectF(-iRad, -iRad, iRad*2.0, iRad*2.0));
painter.drawEllipse(QRectF(-oRad, -oRad, oRad*2.0, oRad*2.0));
-
+
if (getNumPieces() <= 1) {
float c = std::cos(-m_selectedColor.getH() * PI2);
float s = std::sin(-m_selectedColor.getH() * PI2);
painter.drawLine(QPointF(c*iRad, s*iRad), QPointF(c*oRad, s*oRad));
}
}
}
void KisColorSelector::drawLightStrip(QPainter& painter, const QRect& rect)
{
bool isVertical = true;
qreal penSize = qreal(qMin(QWidget::width(), QWidget::height())) / 200.0;
KisColor color(m_selectedColor);
-
+
painter.resetTransform();
-
+
if (getNumLightPieces() > 1) {
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(QBrush(Qt::red), penSize));
-
+
QTransform matrix;
matrix.translate(rect.x(), rect.y());
matrix.scale(rect.width(), rect.height());
-
+
for(int i=0; i<getNumLightPieces(); ++i) {
float t1 = float(i) / float(getNumLightPieces());
float t2 = float(i+1) / float(getNumLightPieces());
float light = 1.0f - (float(i) / float(getNumLightPieces()-1));
float diff = t2 - t1;// + 0.001;
QRectF r = isVertical ? QRectF(0.0, t1, 1.0, diff) : QRect(t1, 0.0, diff, 1.0);
-
+
color.setX(getLight(light, color.getH(), m_relativeLight));
-
+
r = matrix.mapRect(r);
painter.fillRect(r, color.getQColor());
-
+
if (i == m_selectedLightPiece)
painter.drawRect(r);
}
}
else {
int size = isVertical ? rect.height() : rect.width();
painter.setRenderHint(QPainter::Antialiasing, false);
-
+
if (isVertical) {
for(int i=0; i<size; ++i) {
int y = rect.y() + i;
float light = 1.0f - (float(i) / float(size-1));
color.setX(getLight(light, color.getH(), m_relativeLight));
painter.setPen(color.getQColor());
painter.drawLine(rect.left(), y, rect.right(), y);
}
}
else {
for(int i=0; i<size; ++i) {
int x = rect.x() + i;
float light = 1.0f - (float(i) / float(size-1));
color.setX(getLight(light, color.getH(), m_relativeLight));
painter.setPen(color.getQColor());
painter.drawLine(x, rect.top(), x, rect.bottom());
}
}
-
+
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(QBrush(Qt::red), penSize));
float t = 1.0f - m_light;
-
+
if (isVertical) {
int y = rect.y() + int(size * t);
painter.drawLine(rect.left(), y, rect.right(), y);
}
else {
int x = rect.x() + int(size * t);
painter.drawLine(x, rect.top(), x, rect.bottom());
}
}
}
void KisColorSelector::paintEvent(QPaintEvent* /*event*/)
{
// 0 red -> (1,0,0)
// 1 yellow -> (1,1,0)
// 2 green -> (0,1,0)
// 3 cyan -> (0,1,1)
// 4 blue -> (0,0,1)
// 5 maenta -> (1,0,1)
// 6 red -> (1,0,0)
-
+
m_renderBuffer.fill(0);
-
+
QPainter imgPainter(&m_renderBuffer);
QPainter wdgPainter(this);
-
+
QRect fgRect(0, 0 , QWidget::width(), QWidget::height()/2);
QRect bgRect(0, QWidget::height()/2, QWidget::width(), QWidget::height()/2);
wdgPainter.fillRect(fgRect, m_fgColor.getQColor());
wdgPainter.fillRect(bgRect, m_bgColor.getQColor());
-
+
for(int i=0; i<m_colorRings.size(); ++i)
drawRing(imgPainter, m_colorRings[i], m_renderArea);
-
+
wdgPainter.drawImage(m_renderArea, m_renderBuffer);
-
+
drawOutline (wdgPainter, m_renderArea);
drawLightStrip(wdgPainter, m_lightStripArea);
}
void KisColorSelector::mousePressEvent(QMouseEvent* event)
{
- m_clickPos = mapCoord(event->posF(), m_renderArea);
+ m_clickPos = mapCoord(event->localPos(), m_renderArea);
m_mouseMoved = false;
m_pressedButtons = event->buttons();
m_clickedRing = getSaturationIndex(m_clickPos);
-
- qint8 clickedLightPiece = getLightIndex(event->posF());
-
+
+ qint8 clickedLightPiece = getLightIndex(event->localPos());
+
if (clickedLightPiece >= 0) {
- setLight(getLight(event->posF()), m_relativeLight);
+ setLight(getLight(event->localPos()), m_relativeLight);
m_selectedLightPiece = clickedLightPiece;
requestUpdateColorAndPreview(m_selectedColor, Acs::buttonsToRole(Qt::NoButton, m_pressedButtons));
m_mouseMoved = true;
}
else if (m_clickedRing >= 0) {
if (getNumPieces() > 1) {
for(int i=0; i<getNumRings(); ++i)
m_colorRings[i].setTemporaries(m_selectedColor);
}
else {
Radian angle = std::atan2(m_clickPos.x(), m_clickPos.y()) - RAD_90;
m_selectedColor.setH(angle.scaled(0.0f, 1.0f));
m_selectedColor.setS(getSaturation(m_clickedRing));
m_selectedColor.setX(getLight(m_light, m_selectedColor.getH(), m_relativeLight));
requestUpdateColorAndPreview(m_selectedColor, Acs::buttonsToRole(Qt::NoButton, m_pressedButtons));
m_selectedRing = m_clickedRing;
m_mouseMoved = true;
update();
}
}
}
void KisColorSelector::mouseMoveEvent(QMouseEvent* event)
{
- QPointF dragPos = mapCoord(event->posF(), m_renderArea);
- qint8 clickedLightPiece = getLightIndex(event->posF());
-
+ QPointF dragPos = mapCoord(event->localPos(), m_renderArea);
+ qint8 clickedLightPiece = getLightIndex(event->localPos());
+
if (clickedLightPiece >= 0) {
- setLight(getLight(event->posF()), m_relativeLight);
+ setLight(getLight(event->localPos()), m_relativeLight);
m_selectedLightPiece = clickedLightPiece;
requestUpdateColorAndPreview(m_selectedColor, m_selectedColorRole);
}
-
+
if (m_clickedRing < 0)
return;
-
+
if (getNumPieces() > 1) {
float angle = std::atan2(dragPos.x(), dragPos.y()) - std::atan2(m_clickPos.x(), m_clickPos.y());
float dist = std::sqrt(dragPos.x()*dragPos.x() + dragPos.y()*dragPos.y()) * 0.80f;
float threshold = 5.0f * (1.0f-(dist*dist));
-
+
if (qAbs(angle * TO_DEG) >= threshold || m_mouseMoved) {
bool selectedRingMoved = true;
-
+
if (m_pressedButtons & Qt::RightButton) {
selectedRingMoved = m_clickedRing == m_selectedRing;
m_colorRings[m_clickedRing].angle = m_colorRings[m_clickedRing].tmpAngle + angle;
}
else for(int i=0; i<getNumRings(); ++i)
m_colorRings[i].angle = m_colorRings[i].tmpAngle + angle;
-
+
if (selectedRingMoved) {
KisColor color = m_colorRings[m_clickedRing].tmpColor;
Radian angle = m_colorRings[m_clickedRing].getMovedAngel() + (color.getH()*PI2);
color.setH(angle.scaled(0.0f, 1.0f));
color.setX(getLight(m_light, color.getH(), m_relativeLight));
-
+
m_selectedPiece = getHueIndex(angle, m_colorRings[m_clickedRing].getShift());
requestUpdateColorAndPreview(color, m_selectedColorRole);
}
-
+
m_mouseMoved = true;
}
}
else {
Radian angle = std::atan2(dragPos.x(), dragPos.y()) - RAD_90;
m_selectedColor.setH(angle.scaled(0.0f, 1.0f));
m_selectedColor.setX(getLight(m_light, m_selectedColor.getH(), m_relativeLight));
requestUpdateColorAndPreview(m_selectedColor, m_selectedColorRole);
}
-
+
update();
}
void KisColorSelector::mouseReleaseEvent(QMouseEvent* /*event*/)
{
if (!m_mouseMoved && m_clickedRing >= 0) {
Radian angle = std::atan2(m_clickPos.x(), m_clickPos.y()) - RAD_90;
-
+
m_selectedRing = m_clickedRing;
m_selectedPiece = getHueIndex(angle, m_colorRings[m_clickedRing].getShift());
-
+
if (getNumPieces() > 1)
m_selectedColor.setH(getHue(m_selectedPiece, m_colorRings[m_clickedRing].getShift()));
else
m_selectedColor.setH(angle.scaled(0.0f, 1.0f));
-
+
m_selectedColor.setS(getSaturation(m_selectedRing));
m_selectedColor.setX(getLight(m_light, m_selectedColor.getH(), m_relativeLight));
-
+
requestUpdateColorAndPreview(m_selectedColor, Acs::buttonsToRole(Qt::NoButton, m_pressedButtons));
}
else if (m_mouseMoved)
requestUpdateColorAndPreview(m_selectedColor, m_selectedColorRole);
-
+
m_clickedRing = -1;
update();
}
void KisColorSelector::resizeEvent(QResizeEvent* /*event*/)
{
recalculateAreas(quint8(getNumLightPieces()));
}
void KisColorSelector::saveSettings()
{
KisConfig cfg;
cfg.writeEntry("ArtColorSel.ColorSpace" , qint32(m_colorSpace));
cfg.writeEntry("ArtColorSel.NumRings" , m_colorRings.size());
cfg.writeEntry("ArtColorSel.RingPieces" , qint32(m_numPieces));
cfg.writeEntry("ArtColorSel.LightPieces", qint32(m_numLightPieces));
-
+
cfg.writeEntry("ArtColorSel.InversedSaturation", m_inverseSaturation);
cfg.writeEntry("ArtColorSel.RelativeLight" , m_relativeLight);
cfg.writeEntry("ArtColorSel.Light" , m_light);
-
+
cfg.writeEntry("ArtColorSel.SelColorH", m_selectedColor.getH());
cfg.writeEntry("ArtColorSel.SelColorS", m_selectedColor.getS());
cfg.writeEntry("ArtColorSel.SelColorX", m_selectedColor.getX());
cfg.writeEntry("ArtColorSel.SelColorA", m_selectedColor.getA());
-
+
QList<float> angles;
-
+
for(int i=0; i<m_colorRings.size(); ++i)
angles.push_back(m_colorRings[i].angle.value());
-
+
cfg.writeList("ArtColorSel.RingAngles", angles);
}
void KisColorSelector::loadSettings()
{
KisConfig cfg;
setColorSpace(KisColor::Type(cfg.readEntry<qint32>("ArtColorSel.ColorSpace" , KisColor::HSY)));
-
+
setNumLightPieces(cfg.readEntry("ArtColorSel.LightPieces", 19));
-
+
m_selectedColor.setH(cfg.readEntry<float>("ArtColorSel.SelColorH", 0.0f));
m_selectedColor.setS(cfg.readEntry<float>("ArtColorSel.SelColorS", 0.0f));
m_selectedColor.setX(cfg.readEntry<float>("ArtColorSel.SelColorX", 0.0f));
m_selectedColor.setA(1.0f);
-
+
setInverseSaturation(cfg.readEntry<bool>("ArtColorSel.InversedSaturation", false));
setLight(cfg.readEntry<float>("ArtColorSel.Light", 0.5f), cfg.readEntry<bool>("ArtColorSel.RelativeLight", false));
-
+
recalculateRings(
cfg.readEntry("ArtColorSel.NumRings" , 11),
cfg.readEntry("ArtColorSel.RingPieces", 12)
);
-
+
QList<float> angles = cfg.readList<float>("ArtColorSel.RingAngles");
for (int i = 0; i < m_colorRings.size(); ++i) {
if (i < angles.size() && i < m_colorRings.size()) {
m_colorRings[i].angle = angles[i];
}
}
-
+
selectColor(m_selectedColor);
update();
}
diff --git a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp
index 25b24757c2..91788aa87e 100644
--- a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp
+++ b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp
@@ -1,304 +1,304 @@
/*
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "compositiondocker_dock.h"
#include <QGridLayout>
#include <QListView>
#include <QHeaderView>
#include <QStyledItemDelegate>
#include <QPainter>
#include <QInputDialog>
#include <QThread>
#include <QAction>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QMenu>
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include <kis_icon.h>
#include <KoCanvasBase.h>
#include <KoFileDialog.h>
#include <KisPart.h>
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_painter.h>
#include <kis_paint_layer.h>
#include <kis_action.h>
#include <kis_action_manager.h>
#include <kis_action_registry.h>
#include "compositionmodel.h"
CompositionDockerDock::CompositionDockerDock( ) : QDockWidget(i18n("Compositions")), m_canvas(0)
{
QWidget* widget = new QWidget(this);
setupUi(widget);
m_model = new CompositionModel(this);
compositionView->setModel(m_model);
compositionView->installEventFilter(this);
deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
saveButton->setIcon(KisIconUtils::loadIcon("list-add"));
exportButton->setIcon(KisIconUtils::loadIcon("document-export"));
deleteButton->setToolTip(i18n("Delete Composition"));
saveButton->setToolTip(i18n("New Composition"));
exportButton->setToolTip(i18n("Export Composition"));
setWidget(widget);
connect( compositionView, SIGNAL(doubleClicked(QModelIndex)),
this, SLOT(activated ( const QModelIndex & ) ) );
compositionView->setContextMenuPolicy(Qt::CustomContextMenu);
connect( compositionView, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(customContextMenuRequested(QPoint)));
connect( deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked()));
connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked()));
connect( exportButton, SIGNAL(clicked(bool)), this, SLOT(exportClicked()));
saveNameEdit->setPlaceholderText(i18n("Insert Name"));
}
CompositionDockerDock::~CompositionDockerDock()
{
}
void CompositionDockerDock::setCanvas(KoCanvasBase * canvas)
{
if (m_canvas && m_canvas->viewManager()) {
Q_FOREACH (KisAction *action, m_actions) {
m_canvas->viewManager()->actionManager()->takeAction(action);
}
}
unsetCanvas();
setEnabled(canvas != 0);
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (m_canvas && m_canvas->viewManager()) {
if (m_actions.isEmpty()) {
KisAction *updateAction = m_canvas->viewManager()->actionManager()->createAction("update_composition");
connect(updateAction, SIGNAL(triggered()), this, SLOT(updateComposition()));
m_actions.append(updateAction);
KisAction *renameAction = m_canvas->viewManager()->actionManager()->createAction("rename_composition");
connect(renameAction, SIGNAL(triggered()), this, SLOT(renameComposition()));
m_actions.append(renameAction);
} else {
Q_FOREACH (KisAction *action, m_actions) {
m_canvas->viewManager()->actionManager()->addAction(action->objectName(), action);
}
}
updateModel();
}
}
void CompositionDockerDock::unsetCanvas()
{
setEnabled(false);
m_canvas = 0;
m_model->setCompositions(QList<KisLayerCompositionSP>());
}
void CompositionDockerDock::activated(const QModelIndex& index)
{
KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
composition->apply();
}
void CompositionDockerDock::deleteClicked()
{
QModelIndex index = compositionView->currentIndex();
if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
m_canvas->viewManager()->image()->removeComposition(composition);
updateModel();
}
}
void CompositionDockerDock::saveClicked()
{
KisImageWSP image = m_canvas->viewManager()->image();
if (!image) return;
// format as 001, 002 ...
QString name = saveNameEdit->text();
if (name.isEmpty()) {
bool found = false;
int i = 1;
do {
name = QString("%1").arg(i, 3, 10, QChar('0'));
found = false;
Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) {
if (composition->name() == name) {
found = true;
break;
}
}
i++;
} while(found && i < 1000);
}
KisLayerCompositionSP composition(new KisLayerComposition(image, name));
composition->store();
image->addComposition(composition);
saveNameEdit->clear();
updateModel();
compositionView->setCurrentIndex(m_model->index(image->compositions().count()-1, 0));
image->setModified();
}
void CompositionDockerDock::updateModel()
{
if (m_model && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) {
m_model->setCompositions(m_canvas->viewManager()->image()->compositions());
}
}
void CompositionDockerDock::exportClicked()
{
if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) {
QString path;
KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock");
dialog.setCaption(i18n("Select a Directory"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
path = dialog.filename();
if (path.isNull()) return;
if (!path.endsWith('/')) {
path.append('/');
}
KisImageWSP image = m_canvas->viewManager()->image();
QString filename = m_canvas->viewManager()->document()->localFilePath();
if (!filename.isEmpty()) {
QFileInfo info(filename);
path += info.baseName() + '_';
}
Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) {
if (!composition->isExportEnabled()) {
continue;
}
composition->apply();
image->refreshGraph();
image->lock();
#if 0
image->rootLayer()->projection()->convertToQImage(0, 0, 0, image->width(), image->height()).save(path + composition->name() + ".png");
#else
QRect r = image->bounds();
KisDocument *d = KisPart::instance()->createDocument();
KisImageWSP dst = new KisImage(d->createUndoStore(), r.width(), r.height(), image->colorSpace(), composition->name());
dst->setResolution(image->xRes(), image->yRes());
d->setCurrentImage(dst);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, "projection", OPACITY_OPAQUE_U8);
KisPainter gc(paintLayer->paintDevice());
gc.bitBlt(QPoint(0, 0), image->rootLayer()->projection(), r);
dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
dst->refreshGraph();
d->setFileBatchMode(true);
const QByteArray outputFormat("image/png");
d->exportDocumentSync(QUrl::fromLocalFile(path + composition->name() + ".png"), outputFormat);
delete d;
#endif
image->unlock();
}
}
}
bool CompositionDockerDock::eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::KeyPress ) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) {
// new index will be set after the method is called
QTimer::singleShot(0, this, SLOT(activateCurrentIndex()));
}
return false;
} else {
return QObject::eventFilter(obj, event);
}
}
void CompositionDockerDock::activateCurrentIndex()
{
QModelIndex index = compositionView->currentIndex();
if (index.isValid()) {
activated(index);
}
}
void CompositionDockerDock::customContextMenuRequested(QPoint pos)
{
if (m_actions.isEmpty()) return;
QMenu menu;
Q_FOREACH (KisAction *action, m_actions) {
menu.addAction(action);
}
menu.exec(compositionView->mapToGlobal(pos));
}
void CompositionDockerDock::updateComposition()
{
QModelIndex index = compositionView->currentIndex();
if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
composition->store();
m_canvas->image()->setModified();
}
}
void CompositionDockerDock::renameComposition()
{
dbgKrita << "rename";
QModelIndex index = compositionView->currentIndex();
if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) {
KisLayerCompositionSP composition = m_model->compositionFromIndex(index);
bool ok;
QString name = QInputDialog::getText(this, i18n("Rename Composition"),
i18n("New Name:"), QLineEdit::Normal,
composition->name(), &ok);
if (ok && !name.isEmpty()) {
composition->setName(name);
m_canvas->image()->setModified();
}
}
}
diff --git a/plugins/dockers/compositiondocker/compositionmodel.cpp b/plugins/dockers/compositiondocker/compositionmodel.cpp
index abe4796ffa..b8561b4aef 100644
--- a/plugins/dockers/compositiondocker/compositionmodel.cpp
+++ b/plugins/dockers/compositiondocker/compositionmodel.cpp
@@ -1,109 +1,110 @@
/*
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "compositionmodel.h"
#include <kis_icon.h>
#include <QAction>
#include <klocalizedstring.h>
CompositionModel::CompositionModel(QObject* parent): QAbstractTableModel(parent)
{
}
CompositionModel::~CompositionModel()
{
}
QVariant CompositionModel::data(const QModelIndex& index, int role) const
{
if (index.isValid()) {
switch (role) {
case Qt::DisplayRole:
{
return m_compositions.at(index.row())->name();
}
case Qt::DecorationRole:
{
return KisIconUtils::loadIcon("tools-wizard");
}
case Qt::CheckStateRole: {
return m_compositions.at(index.row())->isExportEnabled() ? Qt::Checked : Qt::Unchecked;
}
}
}
return QVariant();
}
bool CompositionModel::setData ( const QModelIndex& index, const QVariant& value, int role )
{
if (index.isValid()) {
if (role == Qt::CheckStateRole) {
Q_ASSERT(index.row() < rowCount());
Q_ASSERT(index.column() < columnCount());
if (index.column() == 0) {
bool exportEnabled = value.toInt() == Qt::Checked;
KisLayerCompositionSP layerComposition = m_compositions.at(index.row());
if (layerComposition) {
layerComposition->setExportEnabled(exportEnabled);
}
}
}
return true;
}
return false;
}
QVariant CompositionModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const
{
return i18n("Composition");
}
int CompositionModel::rowCount(const QModelIndex& /*parent*/) const
{
return m_compositions.count();
}
int CompositionModel::columnCount(const QModelIndex& /*parent*/) const
{
return 2;
}
Qt::ItemFlags CompositionModel::flags(const QModelIndex& /*index*/) const
{
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
return flags;
}
KisLayerCompositionSP CompositionModel::compositionFromIndex(const QModelIndex& index)
{
if(index.isValid()) {
return m_compositions.at(index.row());
}
return KisLayerCompositionSP();
}
void CompositionModel::setCompositions(QList< KisLayerCompositionSP > compositions)
{
m_compositions = compositions;
- reset();
+ beginResetModel();
+ endResetModel();
}
diff --git a/plugins/dockers/defaultdockers/kis_layer_box.cpp b/plugins/dockers/defaultdockers/kis_layer_box.cpp
index e0664604ac..57fd8b7ad8 100644
--- a/plugins/dockers/defaultdockers/kis_layer_box.cpp
+++ b/plugins/dockers/defaultdockers/kis_layer_box.cpp
@@ -1,967 +1,968 @@
/*
* kis_layer_box.cc - part of Krita aka Krayon aka KimageShop
*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (C) 2006 Gábor Lehel <illissius@gmail.com>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_layer_box.h"
#include <QToolButton>
#include <QLayout>
#include <QMouseEvent>
#include <QPainter>
#include <QPoint>
#include <QRect>
#include <QString>
#include <QToolTip>
#include <QWidget>
#include <QComboBox>
#include <QCheckBox>
#include <QVBoxLayout>
#include <QPixmap>
#include <QList>
#include <QVector>
#include <QLabel>
#include <QMenu>
#include <QWidgetAction>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <kis_icon.h>
#include <KisNodeView.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include <KisDocument.h>
#include <kis_types.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_layer.h>
#include <kis_group_layer.h>
#include <kis_mask.h>
#include <kis_node.h>
#include <kis_base_node.h>
#include <kis_composite_ops_model.h>
#include <kis_keyframe_channel.h>
#include <kis_image_animation_interface.h>
#include "kis_action.h"
#include "kis_action_manager.h"
#include "widgets/kis_cmb_composite.h"
#include "widgets/kis_slider_spin_box.h"
#include "KisViewManager.h"
#include "kis_node_manager.h"
#include "kis_node_model.h"
#include "canvas/kis_canvas2.h"
#include "KisDocument.h"
#include "kis_dummies_facade_base.h"
#include "kis_shape_controller.h"
#include "kis_selection_mask.h"
#include "kis_config.h"
#include "KisView.h"
#include "krita_utils.h"
#include "sync_button_and_action.h"
#include "kis_color_label_selector_widget.h"
#include "kis_signals_blocker.h"
#include "kis_color_filter_combo.h"
#include "kis_node_filter_proxy_model.h"
#include "kis_layer_utils.h"
#include "ui_wdglayerbox.h"
#include <QProxyStyle>
#include <QPainter>
class KisLayerBoxStyle : public QProxyStyle
{
public:
KisLayerBoxStyle(QStyle *baseStyle = 0) : QProxyStyle(baseStyle) {}
void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
QPainter *painter, const QWidget *widget) const
{
if (element == QStyle::PE_IndicatorItemViewItemDrop)
{
QColor color(widget->palette().color(QPalette::Highlight).lighter());
if (option->rect.height() == 0) {
QBrush brush(color);
QRect r(option->rect);
r.setTop(r.top() - 2);
r.setBottom(r.bottom() + 2);
painter->fillRect(r, brush);
} else {
color.setAlpha(200);
QBrush brush(color);
painter->fillRect(option->rect, brush);
}
}
else
{
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}
};
inline void KisLayerBox::connectActionToButton(KisViewManager* viewManager, QAbstractButton *button, const QString &id)
{
if (!viewManager || !button) return;
KisAction *action = viewManager->actionManager()->actionByName(id);
if (!action) return;
connect(button, SIGNAL(clicked()), action, SLOT(trigger()));
connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool)));
}
inline void KisLayerBox::addActionToMenu(QMenu *menu, const QString &id)
{
if (m_canvas) {
menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id));
}
}
KisLayerBox::KisLayerBox()
: QDockWidget(i18n("Layers"))
, m_canvas(0)
, m_wdgLayerBox(new Ui_WdgLayerBox)
, m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE)
, m_colorLabelCompressor(900, KisSignalCompressor::FIRST_INACTIVE)
{
KisConfig cfg;
QWidget* mainWidget = new QWidget(this);
setWidget(mainWidget);
m_opacityDelayTimer.setSingleShot(true);
m_wdgLayerBox->setupUi(mainWidget);
m_wdgLayerBox->listLayers->setStyle(new KisLayerBoxStyle(m_wdgLayerBox->listLayers->style()));
connect(m_wdgLayerBox->listLayers,
SIGNAL(contextMenuRequested(const QPoint&, const QModelIndex&)),
this, SLOT(slotContextMenuRequested(const QPoint&, const QModelIndex&)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(collapsed(const QModelIndex&)), SLOT(slotCollapsed(const QModelIndex &)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(expanded(const QModelIndex&)), SLOT(slotExpanded(const QModelIndex &)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(selectionChanged(const QModelIndexList&)), SLOT(selectionChanged(const QModelIndexList&)));
m_wdgLayerBox->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer"));
m_wdgLayerBox->bnDelete->setIcon(KisIconUtils::loadIcon("deletelayer"));
m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnRaise->setEnabled(false);
m_wdgLayerBox->bnRaise->setIcon(KisIconUtils::loadIcon("arrowupblr"));
m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnLower->setEnabled(false);
m_wdgLayerBox->bnLower->setIcon(KisIconUtils::loadIcon("arrowdown"));
m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnProperties->setIcon(KisIconUtils::loadIcon("properties"));
m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer"));
m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22));
if (cfg.sliderLabels()) {
m_wdgLayerBox->opacityLabel->hide();
m_wdgLayerBox->doubleOpacity->setPrefix(QString("%1: ").arg(i18n("Opacity")));
}
m_wdgLayerBox->doubleOpacity->setRange(0, 100, 0);
m_wdgLayerBox->doubleOpacity->setSuffix("%");
connect(m_wdgLayerBox->doubleOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderMoved(qreal)));
connect(&m_opacityDelayTimer, SIGNAL(timeout()), SLOT(slotOpacityChanged()));
connect(m_wdgLayerBox->cmbComposite, SIGNAL(activated(int)), SLOT(slotCompositeOpChanged(int)));
m_selectOpaque = new KisAction(i18n("&Select Opaque"), this);
m_selectOpaque->setActivationFlags(KisAction::ACTIVE_LAYER);
m_selectOpaque->setActivationConditions(KisAction::SELECTION_EDITABLE);
m_selectOpaque->setObjectName("select_opaque");
connect(m_selectOpaque, SIGNAL(triggered(bool)), this, SLOT(slotSelectOpaque()));
m_actions.append(m_selectOpaque);
m_newLayerMenu = new QMenu(this);
m_wdgLayerBox->bnAdd->setMenu(m_newLayerMenu);
m_wdgLayerBox->bnAdd->setPopupMode(QToolButton::MenuButtonPopup);
m_nodeModel = new KisNodeModel(this);
m_filteringModel = new KisNodeFilterProxyModel(this);
m_filteringModel->setNodeModel(m_nodeModel);
/**
* Connect model updateUI() to enable/disable controls.
* Note: nodeActivated() is connected separately in setImage(), because
* it needs particular order of calls: first the connection to the
* node manager should be called, then updateUI()
*/
connect(m_nodeModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotModelReset()));
KisAction *showGlobalSelectionMask = new KisAction(i18n("&Show Global Selection Mask"), this);
showGlobalSelectionMask->setObjectName("show-global-selection-mask");
showGlobalSelectionMask->setActivationFlags(KisAction::ACTIVE_IMAGE);
showGlobalSelectionMask->setToolTip(i18nc("@info:tooltip", "Shows global selection as a usual selection mask in <b>Layers</b> docker"));
showGlobalSelectionMask->setCheckable(true);
connect(showGlobalSelectionMask, SIGNAL(triggered(bool)), SLOT(slotEditGlobalSelection(bool)));
m_actions.append(showGlobalSelectionMask);
showGlobalSelectionMask->setChecked(cfg.showGlobalSelection());
m_colorSelector = new KisColorLabelSelectorWidget(this);
connect(m_colorSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelChanged(int)));
m_colorSelectorAction = new QWidgetAction(this);
m_colorSelectorAction->setDefaultWidget(m_colorSelector);
connect(m_nodeModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
&m_colorLabelCompressor, SLOT(start()));
m_wdgLayerBox->listLayers->setModel(m_filteringModel);
// this connection should be done *after* the setModel() call to
// happen later than the internal selection model
connect(m_filteringModel.data(), &KisNodeFilterProxyModel::rowsAboutToBeRemoved,
this, &KisLayerBox::slotAboutToRemoveRows);
connect(m_wdgLayerBox->cmbFilter, SIGNAL(selectedColorsChanged()), SLOT(updateLayerFiltering()));
setEnabled(false);
connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail()));
connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(updateAvailableLabels()));
}
KisLayerBox::~KisLayerBox()
{
delete m_wdgLayerBox;
}
void expandNodesRecursively(KisNodeSP root, QPointer<KisNodeFilterProxyModel> filteringModel, KisNodeView *nodeView)
{
if (!root) return;
if (filteringModel.isNull()) return;
if (!nodeView) return;
nodeView->blockSignals(true);
KisNodeSP node = root->firstChild();
while (node) {
QModelIndex idx = filteringModel->indexFromNode(node);
if (idx.isValid()) {
nodeView->setExpanded(idx, !node->collapsed());
}
if (node->childCount() > 0) {
expandNodesRecursively(node, filteringModel, nodeView);
}
node = node->nextSibling();
}
nodeView->blockSignals(false);
}
void KisLayerBox::setMainWindow(KisViewManager* kisview)
{
m_nodeManager = kisview->nodeManager();
Q_FOREACH (KisAction *action, m_actions) {
kisview->actionManager()->
addAction(action->objectName(),
action);
}
connectActionToButton(kisview, m_wdgLayerBox->bnAdd, "add_new_paint_layer");
connectActionToButton(kisview, m_wdgLayerBox->bnDuplicate, "duplicatelayer");
KisActionManager *actionManager = kisview->actionManager();
KisAction *action = actionManager->createAction("RenameCurrentLayer");
Q_ASSERT(action);
connect(action, SIGNAL(triggered()), this, SLOT(slotRenameCurrentNode()));
m_propertiesAction = actionManager->createAction("layer_properties");
Q_ASSERT(m_propertiesAction);
new SyncButtonAndAction(m_propertiesAction, m_wdgLayerBox->bnProperties, this);
connect(m_propertiesAction, SIGNAL(triggered()), this, SLOT(slotPropertiesClicked()));
m_removeAction = actionManager->createAction("remove_layer");
Q_ASSERT(m_removeAction);
new SyncButtonAndAction(m_removeAction, m_wdgLayerBox->bnDelete, this);
connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRmClicked()));
action = actionManager->createAction("move_layer_up");
Q_ASSERT(action);
new SyncButtonAndAction(action, m_wdgLayerBox->bnRaise, this);
connect(action, SIGNAL(triggered()), this, SLOT(slotRaiseClicked()));
action = actionManager->createAction("move_layer_down");
Q_ASSERT(action);
new SyncButtonAndAction(action, m_wdgLayerBox->bnLower, this);
connect(action, SIGNAL(triggered()), this, SLOT(slotLowerClicked()));
}
void KisLayerBox::setCanvas(KoCanvasBase *canvas)
{
if (m_canvas == canvas)
return;
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
m_nodeModel->setDummiesFacade(0, 0, 0, 0, 0);
if (m_image) {
KisImageAnimationInterface *animation = m_image->animationInterface();
animation->disconnect(this);
}
disconnect(m_image, 0, this, 0);
disconnect(m_nodeManager, 0, this, 0);
disconnect(m_nodeModel, 0, m_nodeManager, 0);
m_nodeManager->slotSetSelectedNodes(KisNodeList());
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (m_canvas) {
m_image = m_canvas->image();
connect(m_image, SIGNAL(sigImageUpdated(QRect)), &m_thumbnailCompressor, SLOT(start()));
KisDocument* doc = static_cast<KisDocument*>(m_canvas->imageView()->document());
KisShapeController *kritaShapeController =
dynamic_cast<KisShapeController*>(doc->shapeController());
KisDummiesFacadeBase *kritaDummiesFacade =
static_cast<KisDummiesFacadeBase*>(kritaShapeController);
m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController, m_nodeManager->nodeSelectionAdapter(), m_nodeManager->nodeInsertionAdapter());
connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted()));
connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged()));
// cold start
if (m_nodeManager) {
setCurrentNode(m_nodeManager->activeNode());
// Connection KisNodeManager -> KisLayerBox
connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)),
this, SLOT(setCurrentNode(KisNodeSP)));
connect(m_nodeManager,
SIGNAL(sigUiNeedChangeSelectedNodes(const QList<KisNodeSP> &)),
SLOT(slotNodeManagerChangedSelection(const QList<KisNodeSP> &)));
}
else {
setCurrentNode(m_canvas->imageView()->currentNode());
}
// Connection KisLayerBox -> KisNodeManager (isolate layer)
connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()),
m_nodeManager, SLOT(toggleIsolateActiveNode()));
KisImageAnimationInterface *animation = m_image->animationInterface();
connect(animation, &KisImageAnimationInterface::sigUiTimeChanged, this, &KisLayerBox::slotImageTimeChanged);
expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers);
m_wdgLayerBox->listLayers->scrollTo(m_wdgLayerBox->listLayers->currentIndex());
updateAvailableLabels();
addActionToMenu(m_newLayerMenu, "add_new_paint_layer");
addActionToMenu(m_newLayerMenu, "add_new_group_layer");
addActionToMenu(m_newLayerMenu, "add_new_clone_layer");
addActionToMenu(m_newLayerMenu, "add_new_shape_layer");
addActionToMenu(m_newLayerMenu, "add_new_adjustment_layer");
addActionToMenu(m_newLayerMenu, "add_new_fill_layer");
addActionToMenu(m_newLayerMenu, "add_new_file_layer");
m_newLayerMenu->addSeparator();
addActionToMenu(m_newLayerMenu, "add_new_transparency_mask");
addActionToMenu(m_newLayerMenu, "add_new_filter_mask");
addActionToMenu(m_newLayerMenu, "add_new_colorize_mask");
addActionToMenu(m_newLayerMenu, "add_new_transform_mask");
addActionToMenu(m_newLayerMenu, "add_new_selection_mask");
}
}
void KisLayerBox::unsetCanvas()
{
setEnabled(false);
if (m_canvas) {
m_newLayerMenu->clear();
}
m_filteringModel->unsetDummiesFacade();
disconnect(m_image, 0, this, 0);
disconnect(m_nodeManager, 0, this, 0);
disconnect(m_nodeModel, 0, m_nodeManager, 0);
m_nodeManager->slotSetSelectedNodes(KisNodeList());
m_canvas = 0;
}
void KisLayerBox::notifyImageDeleted()
{
setCanvas(0);
}
void KisLayerBox::updateUI()
{
if (!m_canvas) return;
if (!m_nodeManager) return;
KisNodeSP activeNode = m_nodeManager->activeNode();
if (activeNode != m_activeNode) {
if( !m_activeNode.isNull() )
m_activeNode->disconnect(this);
m_activeNode = activeNode;
if (activeNode) {
KisKeyframeChannel *opacityChannel = activeNode->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), false);
if (opacityChannel) {
watchOpacityChannel(opacityChannel);
} else {
watchOpacityChannel(0);
connect(activeNode.data(), &KisNode::keyframeChannelAdded, this, &KisLayerBox::slotKeyframeChannelAdded);
}
}
}
m_wdgLayerBox->bnRaise->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->nextSibling()
|| (activeNode->parent() && activeNode->parent() != m_image->root())));
m_wdgLayerBox->bnLower->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->prevSibling()
|| (activeNode->parent() && activeNode->parent() != m_image->root())));
m_wdgLayerBox->doubleOpacity->setEnabled(activeNode && activeNode->isEditable(false));
m_wdgLayerBox->cmbComposite->setEnabled(activeNode && activeNode->isEditable(false));
if (activeNode) {
if (m_nodeManager->activePaintDevice()) {
slotFillCompositeOps(m_nodeManager->activeColorSpace());
} else {
slotFillCompositeOps(m_image->colorSpace());
}
if (activeNode->inherits("KisColorizeMask") ||
activeNode->inherits("KisLayer")) {
m_wdgLayerBox->doubleOpacity->setEnabled(true);
slotSetOpacity(activeNode->opacity() * 100.0 / 255);
const KoCompositeOp* compositeOp = activeNode->compositeOp();
if (compositeOp) {
slotSetCompositeOp(compositeOp);
} else {
m_wdgLayerBox->cmbComposite->setEnabled(false);
}
const KisGroupLayer *group = qobject_cast<const KisGroupLayer*>(activeNode.data());
bool compositeSelectionActive = !(group && group->passThroughMode());
m_wdgLayerBox->cmbComposite->setEnabled(compositeSelectionActive);
} else if (activeNode->inherits("KisMask")) {
m_wdgLayerBox->cmbComposite->setEnabled(false);
m_wdgLayerBox->doubleOpacity->setEnabled(false);
}
}
}
/**
* This method is called *only* when non-GUI code requested the
* change of the current node
*/
void KisLayerBox::setCurrentNode(KisNodeSP node)
{
m_filteringModel->setActiveNode(node);
QModelIndex index = node ? m_filteringModel->indexFromNode(node) : QModelIndex();
m_filteringModel->setData(index, true, KisNodeModel::ActiveRole);
updateUI();
}
void KisLayerBox::slotModelReset()
{
if(m_nodeModel->hasDummiesFacade()) {
QItemSelection selection;
Q_FOREACH (const KisNodeSP node, m_nodeManager->selectedNodes()) {
const QModelIndex &idx = m_filteringModel->indexFromNode(node);
if(idx.isValid()){
QItemSelectionRange selectionRange(idx);
selection << selectionRange;
}
}
m_wdgLayerBox->listLayers->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
}
updateUI();
}
void KisLayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp)
{
KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id());
m_wdgLayerBox->cmbComposite->blockSignals(true);
m_wdgLayerBox->cmbComposite->selectCompositeOp(opId);
m_wdgLayerBox->cmbComposite->blockSignals(false);
}
void KisLayerBox::slotFillCompositeOps(const KoColorSpace* colorSpace)
{
m_wdgLayerBox->cmbComposite->validate(colorSpace);
}
// range: 0-100
void KisLayerBox::slotSetOpacity(double opacity)
{
Q_ASSERT(opacity >= 0 && opacity <= 100);
m_wdgLayerBox->doubleOpacity->blockSignals(true);
m_wdgLayerBox->doubleOpacity->setValue(opacity);
m_wdgLayerBox->doubleOpacity->blockSignals(false);
}
void KisLayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &index)
{
KisNodeList nodes = m_nodeManager->selectedNodes();
KisNodeSP activeNode = m_nodeManager->activeNode();
if (nodes.isEmpty() || !activeNode) return;
if (m_canvas) {
QMenu menu;
const bool singleLayer = nodes.size() == 1;
if (index.isValid()) {
menu.addAction(m_propertiesAction);
if (singleLayer) {
addActionToMenu(&menu, "layer_style");
}
{
KisSignalsBlocker b(m_colorSelector);
m_colorSelector->setCurrentIndex(singleLayer ? activeNode->colorLabelIndex() : -1);
}
menu.addAction(m_colorSelectorAction);
menu.addSeparator();
addActionToMenu(&menu, "cut_layer_clipboard");
addActionToMenu(&menu, "copy_layer_clipboard");
addActionToMenu(&menu, "paste_layer_from_clipboard");
menu.addAction(m_removeAction);
addActionToMenu(&menu, "duplicatelayer");
addActionToMenu(&menu, "merge_layer");
if (singleLayer) {
addActionToMenu(&menu, "flatten_image");
addActionToMenu(&menu, "flatten_layer");
}
menu.addSeparator();
QMenu *selectMenu = menu.addMenu(i18n("&Select"));
addActionToMenu(selectMenu, "select_all_layers");
addActionToMenu(selectMenu, "select_visible_layers");
addActionToMenu(selectMenu, "select_invisible_layers");
addActionToMenu(selectMenu, "select_locked_layers");
addActionToMenu(selectMenu, "select_unlocked_layers");
QMenu *groupMenu = menu.addMenu(i18n("&Group"));
addActionToMenu(groupMenu, "create_quick_group");
addActionToMenu(groupMenu, "create_quick_clipping_group");
addActionToMenu(groupMenu, "quick_ungroup");
QMenu *locksMenu = menu.addMenu(i18n("&Toggle Locks && Visibility"));
addActionToMenu(locksMenu, "toggle_layer_visibility");
addActionToMenu(locksMenu, "toggle_layer_lock");
addActionToMenu(locksMenu, "toggle_layer_inherit_alpha");
addActionToMenu(locksMenu, "toggle_layer_alpha_lock");
if (singleLayer) {
QMenu *addLayerMenu = menu.addMenu(i18n("&Add"));
addActionToMenu(addLayerMenu, "add_new_transparency_mask");
addActionToMenu(addLayerMenu, "add_new_filter_mask");
addActionToMenu(addLayerMenu, "add_new_colorize_mask");
addActionToMenu(addLayerMenu, "add_new_transform_mask");
addActionToMenu(addLayerMenu, "add_new_selection_mask");
QMenu *convertToMenu = menu.addMenu(i18n("&Convert"));
addActionToMenu(convertToMenu, "convert_to_paint_layer");
addActionToMenu(convertToMenu, "convert_to_transparency_mask");
addActionToMenu(convertToMenu, "convert_to_filter_mask");
addActionToMenu(convertToMenu, "convert_to_selection_mask");
+ addActionToMenu(convertToMenu, "convert_layer_to_file_layer");
QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha"));
addActionToMenu(splitAlphaMenu, "split_alpha_into_mask");
addActionToMenu(splitAlphaMenu, "split_alpha_write");
addActionToMenu(splitAlphaMenu, "split_alpha_save_merged");
}
menu.addSeparator();
addActionToMenu(&menu, "show_in_timeline");
if (singleLayer) {
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node && !node->inherits("KisTransformMask")) {
addActionToMenu(&menu, "isolate_layer");
}
menu.addAction(m_selectOpaque);
}
}
menu.exec(pos);
}
}
void KisLayerBox::slotMinimalView()
{
m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::MinimalMode);
}
void KisLayerBox::slotDetailedView()
{
m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::DetailedMode);
}
void KisLayerBox::slotThumbnailView()
{
m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::ThumbnailMode);
}
void KisLayerBox::slotRmClicked()
{
if (!m_canvas) return;
m_nodeManager->removeNode();
}
void KisLayerBox::slotRaiseClicked()
{
if (!m_canvas) return;
m_nodeManager->raiseNode();
}
void KisLayerBox::slotLowerClicked()
{
if (!m_canvas) return;
m_nodeManager->lowerNode();
}
void KisLayerBox::slotPropertiesClicked()
{
if (!m_canvas) return;
if (KisNodeSP active = m_nodeManager->activeNode()) {
m_nodeManager->nodeProperties(active);
}
}
void KisLayerBox::slotCompositeOpChanged(int index)
{
Q_UNUSED(index);
if (!m_canvas) return;
QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id();
m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp));
}
void KisLayerBox::slotOpacityChanged()
{
if (!m_canvas) return;
m_blockOpacityUpdate = true;
m_nodeManager->nodeOpacityChanged(m_newOpacity, true);
m_blockOpacityUpdate = false;
}
void KisLayerBox::slotOpacitySliderMoved(qreal opacity)
{
m_newOpacity = opacity;
m_opacityDelayTimer.start(200);
}
void KisLayerBox::slotCollapsed(const QModelIndex &index)
{
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node) {
node->setCollapsed(true);
}
}
void KisLayerBox::slotExpanded(const QModelIndex &index)
{
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node) {
node->setCollapsed(false);
}
}
void KisLayerBox::slotSelectOpaque()
{
if (!m_canvas) return;
QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque");
if (action) {
action->trigger();
}
}
void KisLayerBox::slotNodeCollapsedChanged()
{
expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers);
}
inline bool isSelectionMask(KisNodeSP node)
{
return dynamic_cast<KisSelectionMask*>(node.data());
}
KisNodeSP KisLayerBox::findNonHidableNode(KisNodeSP startNode)
{
if (isSelectionMask(startNode) &&
startNode->parent() &&
!startNode->parent()->parent()) {
KisNodeSP node = startNode->prevSibling();
while (node && isSelectionMask(node)) {
node = node->prevSibling();
}
if (!node) {
node = startNode->nextSibling();
while (node && isSelectionMask(node)) {
node = node->nextSibling();
}
}
if (!node) {
node = m_image->root()->lastChild();
while (node && isSelectionMask(node)) {
node = node->prevSibling();
}
}
KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!");
startNode = node;
}
return startNode;
}
void KisLayerBox::slotEditGlobalSelection(bool showSelections)
{
KisNodeSP lastActiveNode = m_nodeManager->activeNode();
KisNodeSP activateNode = lastActiveNode;
if (!showSelections) {
activateNode = findNonHidableNode(activateNode);
}
m_nodeModel->setShowGlobalSelection(showSelections);
if (showSelections) {
KisNodeSP newMask = m_image->rootLayer()->selectionMask();
if (newMask) {
activateNode = newMask;
}
}
if (activateNode) {
if (lastActiveNode != activateNode) {
m_nodeManager->slotNonUiActivatedNode(activateNode);
} else {
setCurrentNode(lastActiveNode);
}
}
}
void KisLayerBox::selectionChanged(const QModelIndexList selection)
{
if (!m_nodeManager) return;
/**
* When the user clears the extended selection by clicking on the
* empty area of the docker, the selection should be reset on to
* the active layer, which might be even unselected(!).
*/
if (selection.isEmpty() && m_nodeManager->activeNode()) {
QModelIndex selectedIndex =
m_filteringModel->indexFromNode(m_nodeManager->activeNode());
m_wdgLayerBox->listLayers->selectionModel()->
setCurrentIndex(selectedIndex, QItemSelectionModel::ClearAndSelect);
return;
}
QList<KisNodeSP> selectedNodes;
Q_FOREACH (const QModelIndex &idx, selection) {
selectedNodes << m_filteringModel->nodeFromIndex(idx);
}
m_nodeManager->slotSetSelectedNodes(selectedNodes);
updateUI();
}
void KisLayerBox::slotAboutToRemoveRows(const QModelIndex &parent, int start, int end)
{
/**
* Qt has changed its behavior when deleting an item. Previously
* the selection priority was on the next item in the list, and
* now it has shanged to the previous item. Here we just adjust
* the selected item after the node removal. Please take care that
* this method overrides what was done by the corresponding method
* of QItemSelectionModel, which *has already done* its work. That
* is why we use (start - 1) and (end + 1) in the activation
* condition.
*
* See bug: https://bugs.kde.org/show_bug.cgi?id=345601
*/
QModelIndex currentIndex = m_wdgLayerBox->listLayers->currentIndex();
QAbstractItemModel *model = m_filteringModel;
if (currentIndex.isValid() && parent == currentIndex.parent()
&& currentIndex.row() >= start - 1 && currentIndex.row() <= end + 1) {
QModelIndex old = currentIndex;
if (model && end < model->rowCount(parent) - 1) // there are rows left below the change
currentIndex = model->index(end + 1, old.column(), parent);
else if (start > 0) // there are rows left above the change
currentIndex = model->index(start - 1, old.column(), parent);
else // there are no rows left in the table
currentIndex = QModelIndex();
if (currentIndex.isValid() && currentIndex != old) {
m_wdgLayerBox->listLayers->setCurrentIndex(currentIndex);
}
}
}
void KisLayerBox::slotNodeManagerChangedSelection(const KisNodeList &nodes)
{
if (!m_nodeManager) return;
QModelIndexList newSelection;
Q_FOREACH(KisNodeSP node, nodes) {
newSelection << m_filteringModel->indexFromNode(node);
}
QItemSelectionModel *model = m_wdgLayerBox->listLayers->selectionModel();
if (KritaUtils::compareListsUnordered(newSelection, model->selectedIndexes())) {
return;
}
QItemSelection selection;
Q_FOREACH(const QModelIndex &idx, newSelection) {
selection.select(idx, idx);
}
model->select(selection, QItemSelectionModel::ClearAndSelect);
}
void KisLayerBox::updateThumbnail()
{
m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex());
}
void KisLayerBox::slotRenameCurrentNode()
{
m_wdgLayerBox->listLayers->edit(m_wdgLayerBox->listLayers->currentIndex());
}
void KisLayerBox::slotColorLabelChanged(int label)
{
KisNodeList nodes = m_nodeManager->selectedNodes();
Q_FOREACH(KisNodeSP node, nodes) {
auto applyLabelFunc =
[label](KisNodeSP node) {
node->setColorLabelIndex(label);
};
KisLayerUtils::recursiveApplyNodes(node, applyLabelFunc);
}
}
void KisLayerBox::updateAvailableLabels()
{
if (!m_image) return;
m_wdgLayerBox->cmbFilter->updateAvailableLabels(m_image->root());
}
void KisLayerBox::updateLayerFiltering()
{
m_filteringModel->setAcceptedLabels(m_wdgLayerBox->cmbFilter->selectedColors());
}
void KisLayerBox::slotKeyframeChannelAdded(KisKeyframeChannel *channel)
{
if (channel->id() == KisKeyframeChannel::Opacity.id()) {
watchOpacityChannel(channel);
}
}
void KisLayerBox::watchOpacityChannel(KisKeyframeChannel *channel)
{
if (m_opacityChannel) {
m_opacityChannel->disconnect(this);
}
m_opacityChannel = channel;
if (m_opacityChannel) {
connect(m_opacityChannel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeRemoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeMoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeMoved(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeChanged(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
}
}
void KisLayerBox::slotOpacityKeyframeChanged(KisKeyframeSP keyframe)
{
Q_UNUSED(keyframe);
if (m_blockOpacityUpdate) return;
updateUI();
}
void KisLayerBox::slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime)
{
Q_UNUSED(fromTime);
slotOpacityKeyframeChanged(keyframe);
}
void KisLayerBox::slotImageTimeChanged(int time)
{
Q_UNUSED(time);
updateUI();
}
#include "moc_kis_layer_box.cpp"
diff --git a/plugins/dockers/historydocker/KisUndoModel.cpp b/plugins/dockers/historydocker/KisUndoModel.cpp
index 89d56f914e..ae1ceb5a0d 100644
--- a/plugins/dockers/historydocker/KisUndoModel.cpp
+++ b/plugins/dockers/historydocker/KisUndoModel.cpp
@@ -1,282 +1,283 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Matus Talcik <matus.talcik@gmail.com>
*
* 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.
*/
/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "KisUndoModel.h"
#include <klocalizedstring.h>
KisUndoModel::KisUndoModel(QObject *parent)
: QAbstractItemModel(parent)
{
m_blockOutgoingHistoryChange = false;
m_stack = 0;
m_canvas = 0;
m_sel_model = new QItemSelectionModel(this, this);
connect(m_sel_model, SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(setStackCurrentIndex(QModelIndex)));
m_empty_label = i18n("<empty>");
}
QItemSelectionModel *KisUndoModel::selectionModel() const
{
return m_sel_model;
}
KUndo2QStack *KisUndoModel::stack() const
{
return m_stack;
}
void KisUndoModel::setStack(KUndo2QStack *stack)
{
if (m_stack == stack)
return;
if (m_stack != 0) {
disconnect(m_stack, SIGNAL(canRedoChanged(bool)), this, SLOT(stackChanged()));
disconnect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged()));
disconnect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged()));
disconnect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*)));
disconnect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(addImage(int)));
}
m_stack = stack;
if (m_stack != 0) {
connect(m_stack, SIGNAL(canRedoChanged(bool)), this, SLOT(stackChanged()));
connect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged()));
connect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged()));
connect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*)));
connect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(addImage(int)));
}
stackChanged();
}
void KisUndoModel::stackDestroyed(QObject *obj)
{
if (obj != m_stack)
return;
m_stack = 0;
stackChanged();
}
void KisUndoModel::stackChanged()
{
- reset();
+ beginResetModel();
+ endResetModel();
m_blockOutgoingHistoryChange = true;
m_sel_model->setCurrentIndex(selectedIndex(), QItemSelectionModel::ClearAndSelect);
m_blockOutgoingHistoryChange = false;
}
void KisUndoModel::setStackCurrentIndex(const QModelIndex &index)
{
if (m_blockOutgoingHistoryChange)
return;
if (m_stack == 0)
return;
if (index == selectedIndex())
return;
if (index.column() != 0)
return;
m_stack->setIndex(index.row());
}
QModelIndex KisUndoModel::selectedIndex() const
{
return m_stack == 0 ? QModelIndex() : createIndex(m_stack->index(), 0);
}
QModelIndex KisUndoModel::index(int row, int column, const QModelIndex &parent) const
{
if (m_stack == 0)
return QModelIndex();
if (parent.isValid())
return QModelIndex();
if (column != 0)
return QModelIndex();
if (row < 0 || row > m_stack->count())
return QModelIndex();
return createIndex(row, column);
}
QModelIndex KisUndoModel::parent(const QModelIndex&) const
{
return QModelIndex();
}
int KisUndoModel::rowCount(const QModelIndex &parent) const
{
if (m_stack == 0)
return 0;
if (parent.isValid())
return 0;
return m_stack->count() + 1;
}
int KisUndoModel::columnCount(const QModelIndex&) const
{
return 1;
}
QVariant KisUndoModel::data(const QModelIndex &index, int role) const
{
if (m_stack == 0){
return QVariant();
}
if (index.column() != 0){
return QVariant();
}
if (index.row() < 0 || index.row() > m_stack->count()){
return QVariant();
}
if (role == Qt::DisplayRole) {
if (index.row() == 0){
return m_empty_label;
}
KUndo2Command* currentCommand = const_cast<KUndo2Command*>(m_stack->command(index.row() - 1));
return currentCommand->isMerged()?m_stack->text(index.row() - 1)+"(Merged)":m_stack->text(index.row() - 1);
}
else if (role == Qt::DecorationRole) {
if (index.row() > 0) {
const KUndo2Command* currentCommand = m_stack->command(index.row() - 1);
if (m_imageMap.contains(currentCommand)) {
return m_imageMap[currentCommand];
}
}
}
return QVariant();
}
QString KisUndoModel::emptyLabel() const
{
return m_empty_label;
}
void KisUndoModel::setEmptyLabel(const QString &label)
{
m_empty_label = label;
stackChanged();
}
void KisUndoModel::setCleanIcon(const QIcon &icon)
{
m_clean_icon = icon;
stackChanged();
}
QIcon KisUndoModel::cleanIcon() const
{
return m_clean_icon;
}
void KisUndoModel::setCanvas(KisCanvas2 *canvas)
{
m_canvas = canvas;
}
void KisUndoModel::addImage(int idx)
{
if (m_stack == 0 || m_stack->count() == 0) {
return;
}
const KUndo2Command* currentCommand = m_stack->command(idx-1);
if (m_stack->count() == idx && !m_imageMap.contains(currentCommand)) {
KisImageWSP historyImage = m_canvas->viewManager()->image();
KisPaintDeviceSP paintDevice = historyImage->projection();
QImage image = paintDevice->createThumbnail(32, 32, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
m_imageMap[currentCommand] = image;
}
QList<const KUndo2Command*> list;
for (int i = 0; i < m_stack->count(); ++i) {
list << m_stack->command(i);
}
for (QMap<const KUndo2Command*, QImage>:: iterator it = m_imageMap.begin(); it != m_imageMap.end();) {
if (!list.contains(it.key())) {
it = m_imageMap.erase(it);
}
else {
++it;
}
}
}
bool KisUndoModel::checkMergedCommand(int index)
{
Q_UNUSED(index)
return false;
}
diff --git a/plugins/dockers/imagedocker/image_strip_scene.cpp b/plugins/dockers/imagedocker/image_strip_scene.cpp
index 48ad8838e4..534db68aed 100644
--- a/plugins/dockers/imagedocker/image_strip_scene.cpp
+++ b/plugins/dockers/imagedocker/image_strip_scene.cpp
@@ -1,199 +1,199 @@
/*
* Copyright (c) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "image_strip_scene.h"
#include <kis_icon.h>
#include <QApplication>
#include <QDir>
#include <QPainter>
#include <QHash>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
#include <QMutexLocker>
#include <kis_debug.h>
/////////////////////////////////////////////////////////////////////////////////////////////
// ------------- ImageLoader ---------------------------------------------------------- //
ImageLoader::ImageLoader(float size)
: m_size(size)
, m_run(true)
{
connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(stopExecution()));
}
void ImageLoader::run()
{
typedef QHash<ImageItem*,Data>::iterator Iterator;
for (Iterator data = m_data.begin(); data != m_data.end() && m_run; ++data) {
QImage img = QImage(data->path);
if (!img.isNull()) {
data->image = img.scaled(m_size, m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
//dbgKrita << "Loaded" << data->path;
data->isLoaded = true;
emit sigItemContentChanged(data.key());
}
}
void ImageLoader::stopExecution()
{
m_run = false;
}
/////////////////////////////////////////////////////////////////////////////////////////////
// ------------- ImageItem ------------------------------------------------------------ //
void ImageItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
if (m_loader->isImageLoaded(this)) {
QImage image = m_loader->getImage(this);
if (!image.isNull()) {
QPointF offset((m_size-image.width()) / 2.0, (m_size-image.height()) / 2.0);
painter->drawImage(offset, image);
}
else {
QIcon icon = KisIconUtils::loadIcon("edit-delete");
QRect rect = boundingRect().toRect();
QPixmap img = icon.pixmap(rect.size());
painter->drawPixmap(rect, img, img.rect());
}
}
else {
QIcon icon = KisIconUtils::loadIcon("folder-pictures");
QRect rect = boundingRect().toRect();
QPixmap img = icon.pixmap(rect.size());
painter->drawPixmap(rect, img, img.rect());
}
if (isSelected()) {
painter->setCompositionMode(QPainter::CompositionMode_HardLight);
painter->setOpacity(0.50);
painter->fillRect(boundingRect().toRect(), palette().color(QPalette::Active, QPalette::Highlight));
painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
QPen pen(palette().color(QPalette::Active, QPalette::Highlight), 3);
painter->setPen(pen);
}
painter->drawRect(boundingRect());
}
QSizeF ImageItem::sizeHint(Qt::SizeHint /*which*/, const QSizeF& /*constraint*/) const
{
return QSizeF(m_size, m_size);
}
/////////////////////////////////////////////////////////////////////////////////////////////
// ------------- ImageStripScene ------------------------------------------------------ //
ImageStripScene::ImageStripScene():
m_imgSize(80)
, m_loader(0)
{
}
ImageStripScene::~ImageStripScene()
{
delete m_loader;
}
bool ImageStripScene::setCurrentDirectory(const QString& path)
{
m_path = path;
QMutexLocker locker(&m_mutex);
QDir directory(path);
QImageReader reader;
if (directory.exists()) {
clear();
if (m_loader) {
m_loader->disconnect(this);
m_loader->stopExecution();
if (!m_loader->wait(500)) {
m_loader->terminate();
m_loader->wait();
}
}
delete m_loader;
m_numItems = 0;
m_loader = new ImageLoader(m_imgSize);
connect(m_loader, SIGNAL(sigItemContentChanged(ImageItem*)), SLOT(slotItemContentChanged(ImageItem*)));
QStringList files = directory.entryList(QDir::Files);
QGraphicsLinearLayout* layout = new QGraphicsLinearLayout();
for (QStringList::iterator name=files.begin(); name!=files.end(); ++name) {
QString path = directory.absoluteFilePath(*name);
QString fileExtension = QFileInfo(path).suffix();
if (!fileExtension.compare("DNG", Qt::CaseInsensitive)) {
warnKrita << "WARNING: Qt is known to crash when trying to open a DNG file. Skip it";
continue;
}
reader.setFileName(path);
if(reader.canRead()) {
ImageItem* item = new ImageItem(m_imgSize, path, m_loader);
m_loader->addPath(item, path);
layout->addItem(item);
++m_numItems;
}
}
QGraphicsWidget* widget = new QGraphicsWidget();
widget->setLayout(layout);
widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
addItem(widget);
setSceneRect(widget->boundingRect());
QTimer::singleShot(0, m_loader, SLOT(start()));
return true;
}
return false;
}
void ImageStripScene::slotItemContentChanged(ImageItem* item)
{
QMutexLocker locker(&m_mutex);
item->update();
}
void ImageStripScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event)
{
- ImageItem* item = static_cast<ImageItem*>(itemAt(event->scenePos()));
+ ImageItem* item = static_cast<ImageItem*>(itemAt(event->scenePos().x(), event->scenePos().y(), QTransform()));
if (item)
emit sigImageActivated(item->path());
}
diff --git a/plugins/dockers/imagedocker/imagedocker_dock.cpp b/plugins/dockers/imagedocker/imagedocker_dock.cpp
index 7c98b23996..7060aaba4f 100644
--- a/plugins/dockers/imagedocker/imagedocker_dock.cpp
+++ b/plugins/dockers/imagedocker/imagedocker_dock.cpp
@@ -1,593 +1,593 @@
/*
* Copyright (c) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include "imagedocker_dock.h"
#include "image_strip_scene.h"
#include "image_view.h"
#include <klocalizedstring.h>
#include <KoCanvasResourceManager.h>
#include <KoCanvasBase.h>
#include <KoColorSpaceRegistry.h>
#include <KoColor.h>
#include <kis_icon.h>
#include <QFileSystemModel>
#include <QImageReader>
#include <QSortFilterProxyModel>
#include <QFileInfo>
#include <QMouseEvent>
#include <QDir>
#include <QLineEdit>
#include <QLabel>
#include <QAbstractListModel>
#include <QButtonGroup>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QTemporaryFile>
#include <QMimeData>
#include "ui_wdgimagedocker.h"
#include "ui_wdgImageViewPopup.h"
///////////////////////////////////////////////////////////////////////////////
// --------- ImageFilter --------------------------------------------------- //
class ImageFilter: public QSortFilterProxyModel
{
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override {
QFileSystemModel* model = static_cast<QFileSystemModel*>(sourceModel());
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
if(model->isDir(index))
return true;
QString ext = model->fileInfo(index).suffix().toLower();
if(s_supportedImageFormats.isEmpty()) {
s_supportedImageFormats = QImageReader::supportedImageFormats();
}
//QImageReader::supportedImageFormats return a list with mixed-case ByteArrays so
//iterate over it manually to make it possible to do toLower().
Q_FOREACH (const QByteArray& format, s_supportedImageFormats) {
if(format.toLower() == ext.toUtf8()) {
return true;
}
}
return false;
}
bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const override {
Q_UNUSED(source_parent);
return source_column == 0;
}
static QList<QByteArray> s_supportedImageFormats;
};
QList<QByteArray> ImageFilter::s_supportedImageFormats;
///////////////////////////////////////////////////////////////////////////////
// --------- ImageListModel ------------------------------------------------ //
class ImageListModel: public QAbstractListModel
{
struct Data
{
QPixmap icon;
QString text;
qint64 id;
};
public:
void addImage(const QPixmap& pixmap, const QString& text, qint64 id) {
Data data;
data.icon = pixmap.scaled(70, 70, Qt::KeepAspectRatio, Qt::SmoothTransformation);
data.text = text;
data.id = id;
emit layoutAboutToBeChanged();
m_data.push_back(data);
emit layoutChanged();
}
qint64 imageID(int index) const { return m_data[index].id; }
void removeImage(qint64 id) {
typedef QList<Data>::iterator Iterator;
for(Iterator data=m_data.begin(); data!=m_data.end(); ++data) {
if(data->id == id) {
emit layoutAboutToBeChanged();
m_data.erase(data);
emit layoutChanged();
return;
}
}
}
int indexFromID(qint64 id) {
for(int i=0; i<m_data.size(); ++i) {
if(m_data[i].id == id)
return i;
}
return -1;
}
int rowCount(const QModelIndex& parent=QModelIndex()) const override {
Q_UNUSED(parent);
return m_data.size();
}
QVariant data(const QModelIndex& index, int role=Qt::DisplayRole) const override {
if(index.isValid() && index.row() < m_data.size()) {
switch(role)
{
case Qt::DisplayRole:
return m_data[index.row()].text;
case Qt::DecorationRole:
return m_data[index.row()].icon;
}
}
return QVariant();
}
private:
QList<Data> m_data;
};
///////////////////////////////////////////////////////////////////////////////
// --------- ImageDockerUI ------------------------------------------------- //
struct ImageDockerUI: public QWidget, public Ui_wdgImageDocker
{
ImageDockerUI() {
setupUi(this);
}
};
///////////////////////////////////////////////////////////////////////////////
// --------- PopupWidgetUI ------------------------------------------------- //
struct PopupWidgetUI: public QWidget, public Ui_wdgImageViewPopup
{
PopupWidgetUI() {
setupUi(this);
}
};
///////////////////////////////////////////////////////////////////////////////
// --------- ImageDockerDock ----------------------------------------------- //
ImageDockerDock::ImageDockerDock():
QDockWidget(i18n("Reference Images")),
m_canvas(0),
m_currImageID(-1)
{
m_ui = new ImageDockerUI();
m_popupUi = new PopupWidgetUI();
m_zoomButtons = new QButtonGroup();
m_imgListModel = new ImageListModel();
m_imageStripScene = new ImageStripScene();
m_model = new QFileSystemModel();
m_proxyModel = new ImageFilter();
m_proxyModel->setSourceModel(m_model);
m_proxyModel->setDynamicSortFilter(true);
m_ui->bnBack->setIcon(KisIconUtils::loadIcon("arrow-left"));
m_ui->bnUp->setIcon(KisIconUtils::loadIcon("arrow-up"));
m_ui->bnHome->setIcon(KisIconUtils::loadIcon("go-home"));
m_ui->bnImgPrev->setIcon(KisIconUtils::loadIcon("arrow-left"));
m_ui->bnImgNext->setIcon(KisIconUtils::loadIcon("arrow-right"));
m_ui->bnImgClose->setIcon(KisIconUtils::loadIcon("window-close"));
m_ui->thumbView->setScene(m_imageStripScene);
m_ui->treeView->setModel(m_proxyModel);
m_ui->cmbImg->setModel(m_imgListModel);
m_ui->bnPopup->setIcon(KisIconUtils::loadIcon("zoom-original"));
m_ui->bnPopup->setPopupWidget(m_popupUi);
m_popupUi->zoomSlider->setRange(5, 500);
m_popupUi->zoomSlider->setValue(100);
m_zoomButtons->addButton(m_popupUi->bnZoomFit , ImageView::VIEW_MODE_FIT);
m_zoomButtons->addButton(m_popupUi->bnZoomAdjust, ImageView::VIEW_MODE_ADJUST);
m_zoomButtons->addButton(m_popupUi->bnZoom25 , 25);
m_zoomButtons->addButton(m_popupUi->bnZoom50 , 50);
m_zoomButtons->addButton(m_popupUi->bnZoom75 , 75);
m_zoomButtons->addButton(m_popupUi->bnZoom100 , 100);
installEventFilter(this);
- m_ui->cmbPath->addItem(KisIconUtils::loadIcon("folder-pictures"), QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
- m_ui->cmbPath->addItem(KisIconUtils::loadIcon("folder-documents"), QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation));
- m_ui->cmbPath->addItem(KisIconUtils::loadIcon("go-home"), QDesktopServices::storageLocation(QDesktopServices::HomeLocation));
+ m_ui->cmbPath->addItem(KisIconUtils::loadIcon("folder-pictures"), QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
+ m_ui->cmbPath->addItem(KisIconUtils::loadIcon("folder-documents"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
+ m_ui->cmbPath->addItem(KisIconUtils::loadIcon("go-home"), QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
Q_FOREACH (const QFileInfo &info, QDir::drives()) {
m_ui->cmbPath->addItem(KisIconUtils::loadIcon("drive-harddisk"), info.absolutePath());
}
connect(m_ui->cmbPath, SIGNAL(activated(const QString&)), SLOT(slotChangeRoot(const QString&)));
connect(m_ui->treeView , SIGNAL(doubleClicked(const QModelIndex&)) , SLOT(slotItemDoubleClicked(const QModelIndex&)));
connect(m_ui->bnBack , SIGNAL(clicked(bool)) , SLOT(slotBackButtonClicked()));
connect(m_ui->bnHome , SIGNAL(clicked(bool)) , SLOT(slotHomeButtonClicked()));
connect(m_ui->bnUp , SIGNAL(clicked(bool)) , SLOT(slotUpButtonClicked()));
connect(m_imageStripScene , SIGNAL(sigImageActivated(const QString&)) , SLOT(slotOpenImage(QString)));
connect(m_ui->bnImgNext , SIGNAL(clicked(bool)) , SLOT(slotNextImage()));
connect(m_ui->bnImgPrev , SIGNAL(clicked(bool)) , SLOT(slotPrevImage()));
connect(m_ui->bnImgClose , SIGNAL(clicked(bool)) , SLOT(slotCloseCurrentImage()));
connect(m_ui->cmbImg , SIGNAL(activated(int)) , SLOT(slotImageChoosenFromComboBox(int)));
connect(m_ui->imgView , SIGNAL(sigColorSelected(const QColor&)) , SLOT(slotColorSelected(const QColor)));
connect(m_ui->imgView , SIGNAL(sigViewModeChanged(int, qreal)) , SLOT(slotViewModeChanged(int, qreal)));
connect(m_popupUi->zoomSlider , SIGNAL(valueChanged(int)) , SLOT(slotZoomChanged(int)));
connect(m_zoomButtons , SIGNAL(buttonClicked(int)) , SLOT(slotZoomChanged(int)));
connect(m_zoomButtons , SIGNAL(buttonClicked(int)) , SLOT(slotCloseZoomPopup()));
setWidget(m_ui);
setAcceptDrops(true);
}
ImageDockerDock::~ImageDockerDock()
{
saveConfigState();
delete m_proxyModel;
delete m_model;
delete m_imageStripScene;
delete m_imgListModel;
delete m_zoomButtons;
qDeleteAll(m_temporaryFiles);
}
void ImageDockerDock::dragEnterEvent(QDragEnterEvent *event)
{
event->setAccepted(event->mimeData()->hasImage() ||
event->mimeData()->hasUrls());
}
void ImageDockerDock::dropEvent(QDropEvent *event)
{
QImage image;
if (event->mimeData()->hasImage()) {
image = qvariant_cast<QImage>(event->mimeData()->imageData());
}
if (!image.isNull()) {
QTemporaryFile *file = new QTemporaryFile(QDir::tempPath () + QDir::separator() + "krita_reference_dnd_XXXXXX.png");
m_temporaryFiles.append(file);
file->open();
image.save(file, "PNG");
file->close();
slotOpenImage(file->fileName());
} else if (event->mimeData()->hasUrls()) {
QList<QUrl> urls = event->mimeData()->urls();
Q_FOREACH (const QUrl &url, urls) {
QString path = url.path();
QFileInfo info(path);
if (info.exists() &&
!QImageReader::imageFormat(path).isEmpty()) {
slotOpenImage(path);
}
}
}
}
void ImageDockerDock::showEvent(QShowEvent *)
{
loadConfigState();
}
void ImageDockerDock::hideEvent(QHideEvent *event)
{
saveConfigState();
}
void ImageDockerDock::setCanvas(KoCanvasBase* canvas)
{
// Intentionally not disabled if there's no canvas
// "Every connection you make emits a signal, so duplicate connections emit two signals"
if(m_canvas) {
m_canvas->disconnectCanvasObserver(this);
}
m_canvas = canvas;
}
void ImageDockerDock::addCurrentPathToHistory()
{
m_history.push_back(m_model->filePath(m_proxyModel->mapToSource(m_ui->treeView->rootIndex())));
}
void ImageDockerDock::updatePath(const QString& path)
{
m_ui->bnBack->setDisabled(m_history.empty());
m_imageStripScene->setCurrentDirectory(path);
}
qint64 ImageDockerDock::generateImageID() const
{
static qint64 id = 0;
return ++id;
}
void ImageDockerDock::setCurrentImage(qint64 imageID)
{
if(m_imgInfoMap.contains(m_currImageID))
m_imgInfoMap[m_currImageID].scrollPos = m_ui->imgView->getScrollPos();
m_ui->bnImgClose->setDisabled(imageID < 0);
m_ui->bnPopup->setDisabled(imageID < 0);
if(imageID < 0) {
m_currImageID = -1;
m_ui->imgView->setPixmap(QPixmap());
}
else if(m_imgInfoMap.contains(imageID)) {
ImageInfoIter info = m_imgInfoMap.find(imageID);
m_ui->imgView->blockSignals(true);
m_ui->imgView->setPixmap(info->pixmap);
setZoom(*info);
m_ui->imgView->blockSignals(false);
m_ui->bnImgPrev->setDisabled(info == m_imgInfoMap.begin());
m_ui->bnImgNext->setDisabled((info+1) == m_imgInfoMap.end());
m_ui->cmbImg->blockSignals(true);
m_ui->cmbImg->setCurrentIndex(m_imgListModel->indexFromID(imageID));
m_ui->cmbImg->blockSignals(false);
m_currImageID = imageID;
}
}
void ImageDockerDock::setZoom(const ImageInfo& info)
{
m_ui->imgView->setViewMode(info.viewMode, info.scale);
m_ui->imgView->setScrollPos(info.scrollPos);
int zoom = qRound(m_ui->imgView->getScale() * 100.0f);
m_popupUi->zoomSlider->blockSignals(true);
m_popupUi->zoomSlider->setValue(zoom);
m_popupUi->zoomSlider->blockSignals(false);
}
void ImageDockerDock::saveConfigState()
{
const QString lastUsedDirectory = m_model->filePath(m_proxyModel->mapToSource(m_ui->treeView->rootIndex()));
KConfigGroup cfg = KSharedConfig::openConfig()->group("referenceImageDocker");
cfg.writeEntry("lastUsedDirectory", lastUsedDirectory);
}
void ImageDockerDock::loadConfigState()
{
- const QString defaultLocation = QDesktopServices::storageLocation(QDesktopServices::PicturesLocation);
+ const QString defaultLocation = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
KConfigGroup cfg = KSharedConfig::openConfig()->group("referenceImageDocker");
QString lastUsedDirectory = cfg.readEntry("lastUsedDirectory", defaultLocation);
if (!QFileInfo(lastUsedDirectory).exists()) {
lastUsedDirectory = defaultLocation;
}
m_model->setRootPath(lastUsedDirectory);
m_ui->treeView->setRootIndex(m_proxyModel->mapFromSource(m_model->index(lastUsedDirectory)));
updatePath(lastUsedDirectory);
}
// ------------ slots ------------------------------------------------- //
void ImageDockerDock::slotItemDoubleClicked(const QModelIndex& index)
{
QModelIndex mappedIndex = m_proxyModel->mapToSource(index);
mappedIndex = m_model->index(mappedIndex.row(), 0, mappedIndex.parent());
QString path(m_model->filePath(mappedIndex));
if(m_model->isDir(mappedIndex)) {
addCurrentPathToHistory();
updatePath(path);
m_ui->treeView->setRootIndex(m_proxyModel->mapFromSource(mappedIndex));
}
else slotOpenImage(path);
}
void ImageDockerDock::slotBackButtonClicked()
{
if(!m_history.empty()) {
QString path = m_history.last();
QModelIndex index = m_proxyModel->mapFromSource(m_model->index(path));
m_ui->treeView->setRootIndex(index);
m_history.pop_back();
updatePath(path);
}
}
void ImageDockerDock::slotHomeButtonClicked()
{
addCurrentPathToHistory();
QModelIndex index = m_proxyModel->mapFromSource(m_model->index(QDir::homePath()));
m_ui->treeView->setRootIndex(index);
updatePath(QDir::homePath());
}
void ImageDockerDock::slotUpButtonClicked()
{
addCurrentPathToHistory();
QModelIndex index = m_proxyModel->mapToSource(m_ui->treeView->rootIndex());
QDir dir(m_model->filePath(index));
dir.makeAbsolute();
if(dir.cdUp()) {
index = m_proxyModel->mapFromSource(m_model->index(dir.path()));
m_ui->treeView->setRootIndex(index);
updatePath(dir.path());
}
}
void ImageDockerDock::slotOpenImage(const QString& path)
{
QPixmap pixmap(path);
if(!pixmap.isNull()) {
QFileInfo fileInfo(path);
ImageInfo imgInfo;
imgInfo.id = generateImageID();
imgInfo.name = fileInfo.fileName();
imgInfo.path = fileInfo.absoluteFilePath();
imgInfo.viewMode = ImageView::VIEW_MODE_FIT;
imgInfo.scale = 1.0f;
imgInfo.pixmap = pixmap;
imgInfo.scrollPos = QPoint(0, 0);
m_imgInfoMap[imgInfo.id] = imgInfo;
m_imgListModel->addImage(imgInfo.pixmap, imgInfo.name, imgInfo.id);
setCurrentImage(imgInfo.id);
m_ui->tabWidget->setCurrentIndex(1);
}
}
void ImageDockerDock::slotCloseCurrentImage()
{
ImageInfoIter info = m_imgInfoMap.find(m_currImageID);
if(info != m_imgInfoMap.end()) {
ImageInfoIter next = info + 1;
ImageInfoIter prev = info - 1;
qint64 id = -1;
if(next != m_imgInfoMap.end())
id = next->id;
else if(info != m_imgInfoMap.begin())
id = prev->id;
m_imgListModel->removeImage(info->id);
m_imgInfoMap.erase(info);
setCurrentImage(id);
if(id < 0)
m_ui->tabWidget->setCurrentIndex(0);
}
}
void ImageDockerDock::slotNextImage()
{
ImageInfoIter info = m_imgInfoMap.find(m_currImageID);
if(info != m_imgInfoMap.end()) {
++info;
if(info != m_imgInfoMap.end())
setCurrentImage(info->id);
}
}
void ImageDockerDock::slotPrevImage()
{
ImageInfoIter info = m_imgInfoMap.find(m_currImageID);
if(info != m_imgInfoMap.end() && info != m_imgInfoMap.begin()) {
--info;
setCurrentImage(info->id);
}
}
void ImageDockerDock::slotImageChoosenFromComboBox(int index)
{
setCurrentImage(m_imgListModel->imageID(index));
}
void ImageDockerDock::slotZoomChanged(int zoom)
{
if(isImageLoaded()) {
ImageInfoIter info = m_imgInfoMap.find(m_currImageID);
switch(zoom)
{
case ImageView::VIEW_MODE_FIT:
case ImageView::VIEW_MODE_ADJUST:
info->viewMode = zoom;
break;
default:
info->viewMode = ImageView::VIEW_MODE_FREE;
info->scale = float(zoom) / 100.0f;
break;
}
setZoom(*info);
}
}
void ImageDockerDock::slotColorSelected(const QColor& color)
{
if (m_canvas) {
m_canvas->resourceManager()->setForegroundColor(KoColor(color, KoColorSpaceRegistry::instance()->rgb8()));
}
}
void ImageDockerDock::slotViewModeChanged(int viewMode, qreal scale)
{
if(isImageLoaded()) {
m_imgInfoMap[m_currImageID].viewMode = viewMode;
m_imgInfoMap[m_currImageID].scale = scale;
int zoom = qRound(scale * 100.0);
m_popupUi->zoomSlider->blockSignals(true);
m_popupUi->zoomSlider->setValue(zoom);
m_popupUi->zoomSlider->blockSignals(false);
}
}
void ImageDockerDock::slotCloseZoomPopup()
{
m_ui->bnPopup->hidePopupWidget();
}
void ImageDockerDock::slotChangeRoot(const QString &path)
{
m_model->setRootPath(path);
m_ui->treeView->setRootIndex(m_proxyModel->mapFromSource(m_model->index(path)));
updatePath(path);
}
bool ImageDockerDock::eventFilter(QObject *obj, QEvent *event)
{
Q_UNUSED(obj);
if (event->type() == QEvent::Resize) {
m_ui->treeView->setColumnWidth(0, width());
return true;
}
return false;
}
diff --git a/plugins/dockers/shapedockers/CollectionItemModel.cpp b/plugins/dockers/shapedockers/CollectionItemModel.cpp
index f437b67c6f..9dbb287ac1 100644
--- a/plugins/dockers/shapedockers/CollectionItemModel.cpp
+++ b/plugins/dockers/shapedockers/CollectionItemModel.cpp
@@ -1,129 +1,134 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Peter Simonsson <peter.simonsson@gmail.com>
*
* 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 "CollectionItemModel.h"
#include <KoProperties.h>
#include <KoShapeFactoryBase.h>
#include <QDebug>
#include <QMimeData>
CollectionItemModel::CollectionItemModel(QObject *parent)
: QAbstractListModel(parent)
{
- setSupportedDragActions(Qt::CopyAction);
}
QVariant CollectionItemModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() > m_shapeTemplateList.count()) {
return QVariant();
}
switch (role) {
case Qt::ToolTipRole:
return m_shapeTemplateList[index.row()].toolTip;
case Qt::DecorationRole:
return m_shapeTemplateList[index.row()].icon;
case Qt::UserRole:
return m_shapeTemplateList[index.row()].id;
case Qt::DisplayRole:
return m_shapeTemplateList[index.row()].name;
default:
return QVariant();
}
return QVariant();
}
int CollectionItemModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_shapeTemplateList.count();
}
void CollectionItemModel::setShapeTemplateList(const QList<KoCollectionItem> &newlist)
{
m_shapeTemplateList = newlist;
- reset();
+ beginResetModel();
+ endResetModel();
}
QMimeData *CollectionItemModel::mimeData(const QModelIndexList &indexes) const
{
if (indexes.isEmpty()) {
return 0;
}
QModelIndex index = indexes.first();
if (!index.isValid()) {
return 0;
}
if (m_shapeTemplateList.isEmpty()) {
return 0;
}
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
dataStream << m_shapeTemplateList[index.row()].id;
const KoProperties *props = m_shapeTemplateList[index.row()].properties;
if (props) {
dataStream << props->store("shapes");
} else {
dataStream << QString();
}
QMimeData *mimeData = new QMimeData;
mimeData->setData(SHAPETEMPLATE_MIMETYPE, itemData);
return mimeData;
}
QStringList CollectionItemModel::mimeTypes() const
{
QStringList mimetypes;
mimetypes << SHAPETEMPLATE_MIMETYPE;
return mimetypes;
}
Qt::ItemFlags CollectionItemModel::flags(const QModelIndex &index) const
{
if (index.isValid()) {
return QAbstractListModel::flags(index) | Qt::ItemIsDragEnabled;
}
return QAbstractListModel::flags(index);
}
const KoProperties *CollectionItemModel::properties(const QModelIndex &index) const
{
if (!index.isValid() || index.row() > m_shapeTemplateList.count()) {
return 0;
}
return m_shapeTemplateList[index.row()].properties;
}
+
+Qt::DropActions CollectionItemModel::supportedDragActions() const
+{
+ return Qt::CopyAction;
+}
diff --git a/plugins/dockers/shapedockers/CollectionItemModel.h b/plugins/dockers/shapedockers/CollectionItemModel.h
index 7f0731854e..ccb887b0c8 100644
--- a/plugins/dockers/shapedockers/CollectionItemModel.h
+++ b/plugins/dockers/shapedockers/CollectionItemModel.h
@@ -1,75 +1,77 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Peter Simonsson <peter.simonsson@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIVIOSHAPETEMPLATEMODEL_H
#define KIVIOSHAPETEMPLATEMODEL_H
#include <KoShape.h>
#include <QAbstractItemModel>
#include <QList>
#include <QString>
#include <QIcon>
class KoProperties;
/**
* Struct containing the information stored in CollectionItemModel item
*/
struct KoCollectionItem {
KoCollectionItem()
{
properties = 0;
};
QString id;
QString name;
QString toolTip;
QIcon icon;
const KoProperties *properties;
};
class CollectionItemModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit CollectionItemModel(QObject *parent = 0);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
QStringList mimeTypes() const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
/**
* Set the list of KoCollectionItem to be stored in the model
*/
void setShapeTemplateList(const QList<KoCollectionItem> &newlist);
QList<KoCollectionItem> shapeTemplateList() const
{
return m_shapeTemplateList;
}
const KoProperties *properties(const QModelIndex &index) const;
+ Qt::DropActions supportedDragActions() const;
+
private:
QList<KoCollectionItem> m_shapeTemplateList;
QString m_family;
};
#endif //KIVIOSHAPETEMPLATEMODEL_H
diff --git a/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.cpp b/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.cpp
index a437720d22..752264d14b 100644
--- a/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.cpp
+++ b/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.cpp
@@ -1,208 +1,212 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Peter Simonsson <peter.simonsson@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "SvgSymbolCollectionDocker.h"
#include <klocalizedstring.h>
#include <QDebug>
#include <QAbstractListModel>
#include <QMimeData>
#include <QDomDocument>
#include <QDomElement>
#include <squeezedcombobox.h>
#include <KoResourceServerProvider.h>
#include <KoResourceServer.h>
#include <KoShapeFactoryBase.h>
#include <KoProperties.h>
#include <KoDrag.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include "ui_WdgSvgCollection.h"
#include <resources/KoSvgSymbolCollectionResource.h>
//
// SvgCollectionModel
//
SvgCollectionModel::SvgCollectionModel(QObject *parent)
: QAbstractListModel(parent)
{
- setSupportedDragActions(Qt::CopyAction);
}
QVariant SvgCollectionModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() > m_symbolCollection->symbols().count()) {
return QVariant();
}
switch (role) {
case Qt::ToolTipRole:
return m_symbolCollection->symbols()[index.row()]->title;
case Qt::DecorationRole:
{
- QPixmap px = QPixmap::fromImage(m_symbolCollection->symbols()[index.row()]->icon);
+ QPixmap px = QPixmap::fromImage(m_symbolCollection->symbols()[index.row()]->icon());
QIcon icon(px);
return icon;
}
case Qt::UserRole:
return m_symbolCollection->symbols()[index.row()]->id;
case Qt::DisplayRole:
return m_symbolCollection->symbols()[index.row()]->title;
default:
return QVariant();
}
return QVariant();
}
int SvgCollectionModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_symbolCollection->symbols().count();
}
QMimeData *SvgCollectionModel::mimeData(const QModelIndexList &indexes) const
{
if (indexes.isEmpty()) {
return 0;
}
QModelIndex index = indexes.first();
if (!index.isValid()) {
return 0;
}
if (m_symbolCollection->symbols().isEmpty()) {
return 0;
}
QList<KoShape*> shapes;
shapes.append(m_symbolCollection->symbols()[index.row()]->shape);
KoDrag drag;
drag.setSvg(shapes);
QMimeData *mimeData = drag.mimeData();
return mimeData;
}
QStringList SvgCollectionModel::mimeTypes() const
{
return QStringList() << SHAPETEMPLATE_MIMETYPE << "image/svg+xml";
}
Qt::ItemFlags SvgCollectionModel::flags(const QModelIndex &index) const
{
if (index.isValid()) {
return QAbstractListModel::flags(index) | Qt::ItemIsDragEnabled;
}
return QAbstractListModel::flags(index);
}
+Qt::DropActions SvgCollectionModel::supportedDragActions() const
+{
+ return Qt::CopyAction;
+}
+
void SvgCollectionModel::setSvgSymbolCollectionResource(KoSvgSymbolCollectionResource *resource)
{
m_symbolCollection = resource;
}
//
// SvgSymbolCollectionDockerFactory
//
SvgSymbolCollectionDockerFactory::SvgSymbolCollectionDockerFactory()
: KoDockFactoryBase()
{
}
QString SvgSymbolCollectionDockerFactory::id() const
{
return QString("SvgSymbolCollectionDocker");
}
QDockWidget *SvgSymbolCollectionDockerFactory::createDockWidget()
{
SvgSymbolCollectionDocker *docker = new SvgSymbolCollectionDocker();
return docker;
}
//
// SvgSymbolCollectionDocker
//
SvgSymbolCollectionDocker::SvgSymbolCollectionDocker(QWidget *parent)
: QDockWidget(parent)
, m_wdgSvgCollection(new Ui_WdgSvgCollection())
{
setWindowTitle(i18n("Vector Libraries"));
QWidget* mainWidget = new QWidget(this);
setWidget(mainWidget);
m_wdgSvgCollection->setupUi(mainWidget);
connect(m_wdgSvgCollection->cmbCollections, SIGNAL(activated(int)), SLOT(collectionActivated(int)));
KoResourceServer<KoSvgSymbolCollectionResource> *svgCollectionProvider = KoResourceServerProvider::instance()->svgSymbolCollectionServer();
Q_FOREACH(KoSvgSymbolCollectionResource *r, svgCollectionProvider->resources()) {
m_wdgSvgCollection->cmbCollections->addSqueezedItem(r->name());
SvgCollectionModel *model = new SvgCollectionModel();
model->setSvgSymbolCollectionResource(r);
m_models.append(model);
}
m_wdgSvgCollection->listCollection->setDragEnabled(true);
m_wdgSvgCollection->listCollection->setDragDropMode(QAbstractItemView::DragOnly);
m_wdgSvgCollection->listCollection->setSelectionMode(QListView::SingleSelection);
KConfigGroup cfg = KSharedConfig::openConfig()->group("SvgSymbolCollection");
int i = cfg.readEntry("currentCollection", 0);
if (i > m_wdgSvgCollection->cmbCollections->count()) {
i = 0;
}
m_wdgSvgCollection->cmbCollections->setCurrentIndex(i);
collectionActivated(i);
}
void SvgSymbolCollectionDocker::setCanvas(KoCanvasBase *canvas)
{
setEnabled(canvas != 0);
}
void SvgSymbolCollectionDocker::unsetCanvas()
{
setEnabled(false);
}
void SvgSymbolCollectionDocker::collectionActivated(int index)
{
if (index < m_models.size()) {
KConfigGroup cfg = KSharedConfig::openConfig()->group("SvgSymbolCollection");
cfg.writeEntry("currentCollection", index);
m_wdgSvgCollection->listCollection->setModel(m_models[index]);
}
}
diff --git a/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.h b/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.h
index bb349bc0ac..817d2df48a 100644
--- a/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.h
+++ b/plugins/dockers/shapedockers/SvgSymbolCollectionDocker.h
@@ -1,86 +1,87 @@
/* This file is part of the KDE project
* Copyright (C) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef SVGSYMBOLCOLLECTIONDOCKER_H
#define SVGSYMBOLCOLLECTIONDOCKER_H
#include <QDockWidget>
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QMap>
#include <QIcon>
#include <KoDockFactoryBase.h>
#include <KoCanvasObserverBase.h>
#include "ui_WdgSvgCollection.h"
class KoSvgSymbolCollectionResource;
class SvgCollectionModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit SvgCollectionModel(QObject *parent = 0);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
QStringList mimeTypes() const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
+ Qt::DropActions supportedDragActions() const;
public:
void setSvgSymbolCollectionResource(KoSvgSymbolCollectionResource *resource);
private:
KoSvgSymbolCollectionResource *m_symbolCollection;
};
class SvgSymbolCollectionDockerFactory : public KoDockFactoryBase
{
public:
SvgSymbolCollectionDockerFactory();
QString id() const override;
QDockWidget *createDockWidget() override;
DockPosition defaultDockPosition() const override
{
return DockRight;
}
};
class SvgSymbolCollectionDocker : public QDockWidget, public KoCanvasObserverBase
{
Q_OBJECT
public:
explicit SvgSymbolCollectionDocker(QWidget *parent = 0);
/// reimplemented
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
private Q_SLOTS:
void collectionActivated(int index);
private:
Ui_WdgSvgCollection *m_wdgSvgCollection;
QVector<SvgCollectionModel*> m_models;
};
#endif //KOSHAPECOLLECTIONDOCKER_H
diff --git a/plugins/dockers/tasksetdocker/tasksetmodel.cpp b/plugins/dockers/tasksetdocker/tasksetmodel.cpp
index 8595866a64..26661e1301 100644
--- a/plugins/dockers/tasksetdocker/tasksetmodel.cpp
+++ b/plugins/dockers/tasksetdocker/tasksetmodel.cpp
@@ -1,100 +1,102 @@
/*
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "tasksetmodel.h"
#include <QAction>
#include <klocalizedstring.h>
#include <kis_icon.h>
TasksetModel::TasksetModel(QObject* parent): QAbstractTableModel(parent)
{
}
TasksetModel::~TasksetModel()
{
}
QVariant TasksetModel::data(const QModelIndex& index, int role) const
{
if (index.isValid()) {
switch (role) {
case Qt::DisplayRole:
{
return m_actions.at(index.row())->iconText();
}
case Qt::DecorationRole:
{
const QIcon icon = m_actions.at(index.row())->icon();
if (icon.isNull()) {
return KisIconUtils::loadIcon("tools-wizard");
}
return icon;
}
}
}
return QVariant();
}
QVariant TasksetModel::headerData(int /*section*/, Qt::Orientation /*orientation*/, int /*role*/) const
{
return i18n("Task");
}
int TasksetModel::rowCount(const QModelIndex& /*parent*/) const
{
return m_actions.count();
}
int TasksetModel::columnCount(const QModelIndex& /*parent*/) const
{
return 1;
}
Qt::ItemFlags TasksetModel::flags(const QModelIndex& /*index*/) const
{
Qt::ItemFlags flags = /*Qt::ItemIsSelectable |*/ Qt::ItemIsEnabled;
return flags;
}
void TasksetModel::addAction(QAction* action)
{
m_actions.append(action);
- reset();
+ beginResetModel();
+ endResetModel();
}
QVector< QAction* > TasksetModel::actions()
{
return m_actions;
}
QAction* TasksetModel::actionFromIndex(const QModelIndex& index)
{
if(index.isValid()) {
return m_actions.at(index.row());
}
return 0;
}
void TasksetModel::clear()
{
m_actions.clear();
- reset();
+ beginResetModel();
+ endResetModel();
}
diff --git a/plugins/dockers/throttle/Throttle.cpp b/plugins/dockers/throttle/Throttle.cpp
index e8b8d08874..6ddf806a09 100644
--- a/plugins/dockers/throttle/Throttle.cpp
+++ b/plugins/dockers/throttle/Throttle.cpp
@@ -1,82 +1,95 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "Throttle.h"
#include <QAction>
#include <QThread>
#include <QQmlContext>
#include <QQmlEngine>
#include <qmath.h>
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include <kis_image_config.h>
#include <kis_icon.h>
#include <KoCanvasBase.h>
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <KisMainWindow.h>
+#include "kis_signal_compressor.h"
+
+#include "KisUpdateSchedulerConfigNotifier.h"
+
ThreadManager::ThreadManager(QObject *parent)
- : QObject(parent)
-{}
+ : QObject(parent),
+ m_configUpdateCompressor(new KisSignalCompressor(500, KisSignalCompressor::POSTPONE, this))
+{
+ connect(m_configUpdateCompressor, SIGNAL(timeout()), SLOT(slotDoUpdateConfig()));
+}
ThreadManager::~ThreadManager()
{
}
void ThreadManager::setThreadCount(int threadCount)
{
- threadCount = qMin(maxThreadCount(), int(qreal(threadCount) * (maxThreadCount() / 100.0)));
+ threadCount = 1 + qreal(threadCount) * (maxThreadCount() - 1) / 100.0;
if (m_threadCount != threadCount) {
m_threadCount = threadCount;
+ m_configUpdateCompressor->start();
emit threadCountChanged();
- KisImageConfig().setMaxNumberOfThreads(m_threadCount);
- KisImageConfig().setFrameRenderingClones(qCeil(m_threadCount * 0.5));
- // XXX: also set for the brush threads
}
}
int ThreadManager::threadCount() const
{
return m_threadCount;
}
int ThreadManager::maxThreadCount() const
{
return QThread::idealThreadCount();
}
+void ThreadManager::slotDoUpdateConfig()
+{
+ KisImageConfig cfg;
+ cfg.setMaxNumberOfThreads(m_threadCount);
+ cfg.setFrameRenderingClones(qCeil(m_threadCount * 0.5));
+ KisUpdateSchedulerConfigNotifier::instance()->notifyConfigChanged();
+}
+
Throttle::Throttle(QWidget *parent)
: QQuickWidget(parent)
{
m_threadManager = new ThreadManager();
// In % of available cores...
engine()->rootContext()->setContextProperty("ThreadManager", m_threadManager);
m_threadManager->setThreadCount(100);
setSource(QUrl("qrc:/slider.qml"));
setResizeMode(SizeRootObjectToView);
}
Throttle::~Throttle()
{
setSource(QUrl());
}
diff --git a/plugins/dockers/throttle/Throttle.h b/plugins/dockers/throttle/Throttle.h
index b23db516d0..84e7a0a9b9 100644
--- a/plugins/dockers/throttle/Throttle.h
+++ b/plugins/dockers/throttle/Throttle.h
@@ -1,55 +1,61 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef THROTTLE_H
#define THROTTLE_H
#include <QtQuickWidgets/QQuickWidget>
+
class KisCanvas2;
+class KisSignalCompressor;
class ThreadManager : public QObject {
Q_OBJECT
Q_PROPERTY(int threadCount READ threadCount WRITE setThreadCount NOTIFY threadCountChanged)
Q_PROPERTY(int maxThreadCount READ maxThreadCount)
public:
ThreadManager(QObject *parent = 0);
~ThreadManager() override;
void setThreadCount(int threadCount);
int threadCount() const;
int maxThreadCount() const;
+private Q_SLOTS:
+ void slotDoUpdateConfig();
+
Q_SIGNALS:
void threadCountChanged();
private:
- int m_threadCount {0};
+ int m_threadCount = 0;
+ KisSignalCompressor *m_configUpdateCompressor;
};
class Throttle : public QQuickWidget {
Q_OBJECT
public:
Throttle(QWidget *parent);
~Throttle() override;
private:
ThreadManager *m_threadManager {0};
};
#endif
diff --git a/plugins/dockers/touchdocker/TouchDockerDock.cpp b/plugins/dockers/touchdocker/TouchDockerDock.cpp
index ee373aa3ea..a8fe23633a 100644
--- a/plugins/dockers/touchdocker/TouchDockerDock.cpp
+++ b/plugins/dockers/touchdocker/TouchDockerDock.cpp
@@ -1,382 +1,382 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "TouchDockerDock.h"
#include <QtQuickWidgets/QQuickWidget>
#include <QQmlEngine>
#include <QQmlContext>
#include <QAction>
#include <QUrl>
#include <QAction>
#include <QKeyEvent>
#include <QApplication>
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <kis_action_registry.h>
#include <KoDialog.h>
#include <KoResourcePaths.h>
#include <kis_icon.h>
#include <KoCanvasBase.h>
#include <KisViewManager.h>
#include <kis_canvas2.h>
#include <KisMainWindow.h>
#include <kis_config.h>
#include <KisPart.h>
#include <KisDocument.h>
#include <KisMimeDatabase.h>
#include <kis_action_manager.h>
#include <kis_action.h>
#include <kis_config.h>
#include <Theme.h>
#include <Settings.h>
#include <DocumentManager.h>
#include <KisSketchView.h>
class TouchDockerDock::Private
{
public:
Private()
{
}
TouchDockerDock *q;
bool allowClose {true};
KisSketchView *sketchView {0};
QString currentSketchPage;
KoDialog *openDialog {0};
KoDialog *saveAsDialog {0};
QMap<QString, QString> buttonMapping;
bool shiftOn {false};
bool ctrlOn {false};
bool altOn {false};
};
TouchDockerDock::TouchDockerDock()
: QDockWidget(i18n("Touch Docker"))
, d(new Private())
{
QStringList defaultMapping = QStringList() << "decrease_opacity"
<< "increase_opacity"
<< "make_brush_color_lighter"
<< "make_brush_color_darker"
<< "decrease_brush_size"
<< "increase_brush_size"
<< "previous_preset"
<< "clear";
QStringList mapping = KisConfig().readEntry<QString>("touchdockermapping", defaultMapping.join(',')).split(',');
for (int i = 0; i < 8; ++i) {
if (i < mapping.size()) {
d->buttonMapping[QString("button%1").arg(i + 1)] = mapping[i];
}
else if (i < defaultMapping.size()) {
d->buttonMapping[QString("button%1").arg(i + 1)] = defaultMapping[i];
}
}
m_quickWidget = new QQuickWidget(this);
setWidget(m_quickWidget);
setEnabled(true);
m_quickWidget->engine()->rootContext()->setContextProperty("mainWindow", this);
m_quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/");
m_quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/");
-
+
m_quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/");
m_quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/");
-
+
Settings *settings = new Settings(this);
DocumentManager::instance()->setSettingsManager(settings);
m_quickWidget->engine()->rootContext()->setContextProperty("Settings", settings);
Theme *theme = Theme::load(KSharedConfig::openConfig()->group("General").readEntry<QString>("theme", "default"),
m_quickWidget->engine());
if (theme) {
settings->setTheme(theme);
}
m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
m_quickWidget->setSource(QUrl("qrc:/touchstrip.qml"));
}
TouchDockerDock::~TouchDockerDock()
{
}
bool TouchDockerDock::allowClose() const
{
return d->allowClose;
}
void TouchDockerDock::setAllowClose(bool allow)
{
d->allowClose = allow;
}
QString TouchDockerDock::currentSketchPage() const
{
return d->currentSketchPage;
}
void TouchDockerDock::setCurrentSketchPage(QString newPage)
{
d->currentSketchPage = newPage;
emit currentSketchPageChanged();
}
void TouchDockerDock::closeEvent(QCloseEvent* event)
{
if (!d->allowClose) {
event->ignore();
emit closeRequested();
} else {
event->accept();
}
}
void TouchDockerDock::slotButtonPressed(const QString &id)
{
if (id == "fileOpenButton") {
showFileOpenDialog();
}
else if (id == "fileSaveButton" && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document()) {
bool batchMode = m_canvas->viewManager()->document()->fileBatchMode();
m_canvas->viewManager()->document()->setFileBatchMode(true);
m_canvas->viewManager()->document()->save(true, 0);
m_canvas->viewManager()->document()->setFileBatchMode(batchMode);
}
else if (id == "fileSaveAsButton" && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->document()) {
showFileSaveAsDialog();
}
else {
QAction *a = action(id);
if (a) {
if (a->isCheckable()) {
a->toggle();
}
else {
a->trigger();
}
}
else if (id == "shift") {
// set shift state for the next pointer event, somehow
QKeyEvent event(d->shiftOn ? QEvent::KeyRelease : QEvent::KeyPress,
0,
Qt::ShiftModifier);
QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event);
- d->shiftOn == !d->shiftOn;
+ d->shiftOn = !d->shiftOn;
}
else if (id == "ctrl") {
// set ctrl state for the next pointer event, somehow
QKeyEvent event(d->ctrlOn ? QEvent::KeyRelease : QEvent::KeyPress,
0,
Qt::ControlModifier);
QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event);
- d->ctrlOn == !d->ctrlOn;
+ d->ctrlOn = !d->ctrlOn;
}
else if (id == "alt") {
// set alt state for the next pointer event, somehow
QKeyEvent event(d->altOn ? QEvent::KeyRelease : QEvent::KeyPress,
0,
Qt::AltModifier);
QApplication::sendEvent(KisPart::instance()->currentMainwindow(), &event);
- d->altOn == !d->altOn;
+ d->altOn = !d->altOn;
}
}
}
void TouchDockerDock::slotOpenImage(QString path)
{
if (d->openDialog) {
d->openDialog->accept();
}
KisPart::instance()->currentMainwindow()->openDocument(QUrl::fromLocalFile(path), KisMainWindow::None);
}
void TouchDockerDock::slotSaveAs(QString path, QString mime)
{
if (d->saveAsDialog) {
d->saveAsDialog->accept();
}
m_canvas->viewManager()->document()->saveAs(QUrl::fromLocalFile(path), mime.toLatin1(), true);
m_canvas->viewManager()->document()->waitForSavingToComplete();
}
void TouchDockerDock::hideFileOpenDialog()
{
if (d->openDialog) {
d->openDialog->accept();
}
}
void TouchDockerDock::hideFileSaveAsDialog()
{
if (d->saveAsDialog) {
d->saveAsDialog->accept();
}
}
QString TouchDockerDock::imageForButton(QString id)
{
if (d->buttonMapping.contains(id)) {
id = d->buttonMapping[id];
}
if (KisActionRegistry::instance()->hasAction(id)) {
QString a = KisActionRegistry::instance()->getActionProperty(id, "icon");
if (!a.isEmpty()) {
return "image://icon/" + a;
}
}
return QString();
}
QString TouchDockerDock::textForButton(QString id)
{
if (d->buttonMapping.contains(id)) {
id = d->buttonMapping[id];
}
if (KisActionRegistry::instance()->hasAction(id)) {
QString a = KisActionRegistry::instance()->getActionProperty(id, "iconText");
if (a.isEmpty()) {
a = KisActionRegistry::instance()->getActionProperty(id, "text");
}
return a;
}
return id;
}
QAction *TouchDockerDock::action(QString id) const
{
if (m_canvas && m_canvas->viewManager()) {
if (d->buttonMapping.contains(id)) {
id = d->buttonMapping[id];
}
return m_canvas->viewManager()->actionManager()->actionByName(id);
}
return 0;
}
void TouchDockerDock::showFileOpenDialog()
{
if (!d->openDialog) {
d->openDialog = createDialog("qrc:/opendialog.qml");
}
d->openDialog->exec();
}
void TouchDockerDock::showFileSaveAsDialog()
{
if (!d->openDialog) {
d->openDialog = createDialog("qrc:/saveasdialog.qml");
}
d->openDialog->exec();
}
KoDialog *TouchDockerDock::createDialog(const QString qml)
{
KoDialog *dlg = new KoDialog(this);
dlg->setButtons(KoDialog::None);
QQuickWidget *quickWidget = new QQuickWidget(this);
dlg->setMainWidget(quickWidget);
setEnabled(true);
quickWidget->engine()->rootContext()->setContextProperty("mainWindow", this);
quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/");
quickWidget->engine()->addImportPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/");
quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib/qml/");
quickWidget->engine()->addPluginPath(KoResourcePaths::getApplicationRoot() + "/lib64/qml/");
-
+
Settings *settings = new Settings(this);
DocumentManager::instance()->setSettingsManager(settings);
quickWidget->engine()->rootContext()->setContextProperty("Settings", settings);
Theme *theme = Theme::load(KSharedConfig::openConfig()->group("General").readEntry<QString>("theme", "default"),
quickWidget->engine());
settings->setTheme(theme);
quickWidget->setSource(QUrl(qml));
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
dlg->setMinimumSize(1280, 768);
return dlg;
}
QObject *TouchDockerDock::sketchKisView() const
{
return d->sketchView;
}
void TouchDockerDock::setSketchKisView(QObject* newView)
{
if (d->sketchView) {
d->sketchView->disconnect(this);
}
if (d->sketchView != newView) {
d->sketchView = qobject_cast<KisSketchView*>(newView);
emit sketchKisViewChanged();
}
}
void TouchDockerDock::setCanvas(KoCanvasBase *canvas)
{
setEnabled(true);
if (m_canvas == canvas) {
return;
}
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
}
if (!canvas) {
m_canvas = 0;
return;
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
}
void TouchDockerDock::unsetCanvas()
{
setEnabled(true);
m_canvas = 0;
}
diff --git a/plugins/extensions/bigbrother/bigbrother.cc b/plugins/extensions/bigbrother/bigbrother.cc
index 0586196030..2df5da991c 100644
--- a/plugins/extensions/bigbrother/bigbrother.cc
+++ b/plugins/extensions/bigbrother/bigbrother.cc
@@ -1,238 +1,238 @@
/*
* Copyright (c) 2007 Cyrille Berger (cberger@cberger.net)
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "bigbrother.h"
#include <cstdlib>
#include <unistd.h>
#include <kis_action.h>
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <KoIcon.h>
#include <kis_icon.h>
#include <KoUpdater.h>
#include <KoResourceServerProvider.h>
#include <KoFileDialog.h>
#include <KoProgressUpdater.h>
#include <kis_config.h>
#include <kis_cursor.h>
#include <kis_debug.h>
#include <kis_global.h>
#include <kis_image.h>
#include <kis_resource_server_provider.h>
#include <kis_types.h>
#include <KisViewManager.h>
#include <resources/KoPattern.h>
#include <recorder/kis_action_recorder.h>
#include <recorder/kis_macro.h>
#include <recorder/kis_macro_player.h>
#include <recorder/kis_play_info.h>
#include <recorder/kis_recorded_action_factory_registry.h>
#include <recorder/kis_recorded_action.h>
#include <recorder/kis_recorded_action_load_context.h>
#include <recorder/kis_recorded_action_save_context.h>
#include "actionseditor/kis_actions_editor.h"
#include "actionseditor/kis_actions_editor_dialog.h"
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QApplication>
K_PLUGIN_FACTORY_WITH_JSON(BigBrotherPluginFactory, "kritabigbrother.json", registerPlugin<BigBrotherPlugin>();)
class RecordedActionSaveContext : public KisRecordedActionSaveContext {
public:
void saveGradient(const KoAbstractGradient* ) override {}
void savePattern(const KoPattern* ) override {}
};
class RecordedActionLoadContext : public KisRecordedActionLoadContext {
public:
KoAbstractGradient* gradient(const QString& name) const override
{
return KoResourceServerProvider::instance()->gradientServer()->resourceByName(name);
}
KoPattern* pattern(const QString& name) const override
{
return KoResourceServerProvider::instance()->patternServer()->resourceByName(name);
}
};
BigBrotherPlugin::BigBrotherPlugin(QObject *parent, const QVariantList &)
: KisViewPlugin(parent)
, m_recorder(0)
{
if (parent->inherits("KisViewManager")) {
m_view = (KisViewManager*) parent;
// Open and play action
KisAction* action = createAction("Macro_Open_Play");
connect(action, SIGNAL(triggered()), this, SLOT(slotOpenPlay()));
// Open and edit action
action = createAction("Macro_Open_Edit");
connect(action, SIGNAL(triggered()), this, SLOT(slotOpenEdit()));
// Start recording action
m_startRecordingMacroAction = createAction("Recording_Start_Recording_Macro");
connect(m_startRecordingMacroAction, SIGNAL(triggered()), this, SLOT(slotStartRecordingMacro()));
// Save recorded action
m_stopRecordingMacroAction = createAction("Recording_Stop_Recording_Macro");
connect(m_stopRecordingMacroAction, SIGNAL(triggered()), this, SLOT(slotStopRecordingMacro()));
m_stopRecordingMacroAction->setEnabled(false);
}
}
BigBrotherPlugin::~BigBrotherPlugin()
{
m_view = 0;
delete m_recorder;
}
void BigBrotherPlugin::slotOpenPlay()
{
KisMacro* m = openMacro();
dbgKrita << m;
if (!m) return;
dbgPlugins << "Play the macro";
KoUpdaterPtr updater = m_view->createUnthreadedUpdater(i18n("Playing back macro"));
KisMacroPlayer player(m, KisPlayInfo(m_view->image(), m_view->activeNode()), updater);
player.start();
while(player.isRunning())
{
QApplication::processEvents();
}
dbgPlugins << "Finished";
delete m;
}
void BigBrotherPlugin::slotOpenEdit()
{
KisMacro *macro = openMacro();
if (!macro) return;
KisActionsEditorDialog aed(m_view->mainWindow());
aed.actionsEditor()->setMacro(macro);
if (aed.exec() == QDialog::Accepted) {
saveMacro(macro);
}
delete macro;
}
void BigBrotherPlugin::slotStartRecordingMacro()
{
dbgPlugins << "Start recording macro";
if (m_recorder) return;
// Alternate actions
m_startRecordingMacroAction->setEnabled(false);
m_stopRecordingMacroAction->setEnabled(true);
// Create recorder
m_recorder = new KisMacro();
connect(m_view->image()->actionRecorder(), SIGNAL(addedAction(const KisRecordedAction&)),
m_recorder, SLOT(addAction(const KisRecordedAction&)));
}
void BigBrotherPlugin::slotStopRecordingMacro()
{
dbgPlugins << "Stop recording macro";
if (!m_recorder) return;
// Alternate actions
m_startRecordingMacroAction->setEnabled(true);
m_stopRecordingMacroAction->setEnabled(false);
// Save the macro
saveMacro(m_recorder);
// Delete recorder
delete m_recorder;
m_recorder = 0;
}
KisMacro* BigBrotherPlugin::openMacro()
{
QStringList mimeFilter;
mimeFilter << "*.krarec|Recorded actions (*.krarec)";
KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::OpenFile, "OpenDocument");
dialog.setCaption(i18n("Open Macro"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(QStringList() << "application/krita-recorded-macro", "application/krita-recorded-macro");
QString filename = dialog.filename();
RecordedActionLoadContext loadContext;
if (!filename.isNull()) {
QDomDocument doc;
QFile f(filename);
if (f.exists()) {
dbgPlugins << f.open(QIODevice::ReadOnly);
QString err;
int line, col;
if (!doc.setContent(&f, &err, &line, &col)) {
// TODO error message
dbgPlugins << err << " line = " << line << " col = " << col;
f.close();
return 0;
}
f.close();
QDomElement docElem = doc.documentElement();
if (!docElem.isNull() && docElem.tagName() == "RecordedActions") {
dbgPlugins << "Load the macro";
KisMacro* m = new KisMacro();
m->fromXML(docElem, &loadContext);
return m;
} else {
// TODO error message
}
} else {
dbgPlugins << "Unexistant file : " << filename;
}
}
return 0;
}
void BigBrotherPlugin::saveMacro(const KisMacro* macro)
{
KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::SaveFile, "bigbrother");
dialog.setCaption(i18n("Save Macro"));
dialog.setMimeTypeFilters(QStringList() << "application/krita-recorded-macro", "application/krita-recorded-macro");
QString filename = dialog.filename();
if (!filename.isNull()) {
QDomDocument doc;
QDomElement e = doc.createElement("RecordedActions");
RecordedActionSaveContext context;
macro->toXML(doc, e, &context);
doc.appendChild(e);
QFile f(filename);
f.open(QIODevice::WriteOnly);
QTextStream stream(&f);
stream.setCodec("UTF-8");
doc.save(stream, 2);
f.close();
}
}
#include "bigbrother.moc"
diff --git a/plugins/extensions/imagesplit/imagesplit.cpp b/plugins/extensions/imagesplit/imagesplit.cpp
index 884d0b34a6..a0817223f4 100644
--- a/plugins/extensions/imagesplit/imagesplit.cpp
+++ b/plugins/extensions/imagesplit/imagesplit.cpp
@@ -1,207 +1,207 @@
/*
* imagesplit.cc -- Part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
* Copyright (c) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "imagesplit.h"
#include <QStringList>
#include <QDir>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QMessageBox>
#include <klocalizedstring.h>
#include <kpluginfactory.h>
#include <KisImportExportManager.h>
#include <KoFileDialog.h>
#include <KisDocument.h>
#include <KisPart.h>
#include <kis_debug.h>
#include <kis_types.h>
#include <KisViewManager.h>
#include <kis_image.h>
#include <kis_action.h>
#include <KisDocument.h>
#include <kis_paint_layer.h>
#include <kis_painter.h>
#include <kis_paint_device.h>
#include <KisMimeDatabase.h>
#include "dlg_imagesplit.h"
K_PLUGIN_FACTORY_WITH_JSON(ImagesplitFactory, "kritaimagesplit.json", registerPlugin<Imagesplit>();)
Imagesplit::Imagesplit(QObject *parent, const QVariantList &)
: KisViewPlugin(parent)
{
KisAction *action = createAction("imagesplit");
connect(action, SIGNAL(triggered()), this, SLOT(slotImagesplit()));
}
Imagesplit::~Imagesplit()
{
}
bool Imagesplit::saveAsImage(const QRect &imgSize, const QString &mimeType, const QString &url)
{
KisImageSP image = m_view->image();
KisDocument *document = KisPart::instance()->createDocument();
KisImageSP dst = new KisImage(document->createUndoStore(), imgSize.width(), imgSize.height(), image->colorSpace(), image->objectName());
dst->setResolution(image->xRes(), image->yRes());
document->setCurrentImage(dst);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, dst->nextLayerName(), 255);
KisPainter gc(paintLayer->paintDevice());
gc.bitBlt(QPoint(0, 0), image->projection(), imgSize);
dst->addNode(paintLayer, KisNodeSP(0));
dst->refreshGraph();
document->setFileBatchMode(true);
if (!document->exportDocumentSync(QUrl::fromLocalFile(url), mimeType.toLatin1())) {
if (document->errorMessage().isEmpty()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save\n%1", document->localFilePath()));
} else {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", document->localFilePath(), document->errorMessage()));
}
return false;
}
delete document;
return true;
}
void Imagesplit::slotImagesplit()
{
// Taking the title - url from caption function and removing file extension
QStringList strList = ((m_view->document())->caption()).split('.');
QString suffix = strList.at(0);
// Getting all mime types and converting them into names which are displayed at combo box
QStringList listMimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export);
QString defaultMime = QString::fromLatin1(m_view->document()->mimeType());
int defaultMimeIndex = 0;
listMimeFilter.sort();
QStringList filteredMimeTypes;
QStringList listFileType;
int i = 0;
Q_FOREACH (const QString & mimeType, listMimeFilter) {
listFileType.append(KisMimeDatabase::descriptionForMimeType(mimeType));
filteredMimeTypes.append(mimeType);
if (mimeType == defaultMime) {
defaultMimeIndex = i;
}
i++;
}
listMimeFilter = filteredMimeTypes;
Q_ASSERT(listMimeFilter.size() == listFileType.size());
DlgImagesplit *dlgImagesplit = new DlgImagesplit(m_view, suffix, listFileType, defaultMimeIndex);
dlgImagesplit->setObjectName("Imagesplit");
Q_CHECK_PTR(dlgImagesplit);
KisImageWSP image = m_view->image();
if (dlgImagesplit->exec() == QDialog::Accepted) {
int numHorizontalLines = dlgImagesplit->horizontalLines();
int numVerticalLines = dlgImagesplit->verticalLines();
int img_width = image->width() / (numVerticalLines + 1);
int img_height = image->height() / (numHorizontalLines + 1);
bool stop = false;
if (dlgImagesplit->autoSave()) {
KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::OpenDirectory, "OpenDocument");
dialog.setCaption(i18n("Save Image on Split"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
QStringList mimeFilter = m_view->document()->importExportManager()->mimeFilter(KisImportExportManager::Export);
QString defaultMime = QString::fromLatin1(m_view->document()->mimeType());
dialog.setMimeTypeFilters(mimeFilter, defaultMime);
QUrl directory = QUrl::fromUserInput(dialog.filename());
if (directory.isEmpty())
return;
for (int i = 0, k = 1; i < (numVerticalLines + 1); i++) {
for (int j = 0; j < (numHorizontalLines + 1); j++, k++) {
QString mimeTypeSelected = listMimeFilter.at(dlgImagesplit->cmbIndex);
QString homepath = directory.toLocalFile();
QString suffix = KisMimeDatabase::suffixesForMimeType(mimeTypeSelected).first();
qDebug() << "suffix" << suffix;
if (suffix.startsWith("*.")) {
suffix = suffix.remove(0, 1);
}
qDebug() << "\tsuffix" << suffix;
if (!suffix.startsWith(".")) {
suffix = suffix.prepend('.');
}
qDebug() << "\tsuffix" << suffix;
QString fileName = dlgImagesplit->suffix() + '_' + QString::number(k) + suffix;
QString url = homepath + '/' + fileName;
if (!saveAsImage(QRect((i * img_width), (j * img_height), img_width, img_height), listMimeFilter.at(dlgImagesplit->cmbIndex), url)) {
stop = true;
break;
}
}
if (stop) {
break;
}
}
}
else {
for (int i = 0; i < (numVerticalLines + 1); i++) {
for (int j = 0; j < (numHorizontalLines + 1); j++) {
KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::SaveFile, "OpenDocument");
dialog.setCaption(i18n("Save Image on Split"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(listMimeFilter, defaultMime);
QUrl url = QUrl::fromUserInput(dialog.filename());
QString mimefilter = KisMimeDatabase::mimeTypeForFile(url.toLocalFile());
if (url.isEmpty())
return;
if (!saveAsImage(QRect((i * img_width), (j * img_height), img_width, img_height), mimefilter, url.toLocalFile())) {
stop = true;
break;
}
}
if (stop) {
break;
}
}
}
}
delete dlgImagesplit;
}
#include "imagesplit.moc"
diff --git a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop
index 1622b2a976..a6954193fb 100644
--- a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop
@@ -1,36 +1,40 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=assignprofiledialog
X-Python-2-Compatible=false
Name=Assign Profile to Image
Name[ca]=Assigna un perfil a una imatge
Name[ca@valencia]=Assigna un perfil a una imatge
Name[cs]=Přiřadit obrázku profil
+Name[el]=Αντιστοίχιση προφίλ σε εικόνα
Name[en_GB]=Assign Profile to Image
Name[es]=Asignar perfil a imagen
Name[gl]=Asignar un perfil á imaxe
Name[it]=Assegna profilo a immagine
Name[nl]=Profiel aan afbeelding toekennen
Name[pl]=Przypisz profil do obrazu
Name[pt]=Atribuir um Perfil à Imagem
Name[pt_BR]=Atribuir perfil a imagem
Name[sv]=Tilldela profil till bild
Name[tr]=Görüntüye Profil Ata
Name[uk]=Призначити профіль до зображення
+Name[x-test]=xxAssign Profile to Imagexx
Name[zh_CN]=应用配置到图像
Comment=Assign a profile to an image without converting it.
Comment[ca]=Assigna un perfil a una imatge sense convertir-la.
Comment[ca@valencia]=Assigna un perfil a una imatge sense convertir-la.
+Comment[el]=Αντιστοιχίζει ένα προφίλ σε μια εικόνα χωρίς μετατροπή.
Comment[en_GB]=Assign a profile to an image without converting it.
Comment[es]=Asignar un perfil a una imagen sin convertirla.
Comment[gl]=Asignar un perfil a unha imaxe sen convertela.
Comment[it]=Assegna un profilo a un'immagine senza convertirla.
Comment[nl]=Een profiel aan een afbeelding toekennen zonder het te converteren.
Comment[pl]=Przypisz profil do obrazu bez jego przekształcania.
Comment[pt]=Atribui um perfil à imagem sem a converter.
Comment[pt_BR]=Atribui um perfil para uma imagem sem convertê-la.
Comment[sv]=Tilldela en profil till en bild utan att konvertera den.
Comment[tr]=Bir görüntüye, görüntüyü değiştirmeden bir profil ata.
Comment[uk]=Призначити профіль до зображення без його перетворення.
+Comment[x-test]=xxAssign a profile to an image without converting it.xx
Comment[zh_CN]=为图像设定配置而无需转换它。
diff --git a/plugins/extensions/pykrita/plugin/plugins/colorspace/kritapykrita_colorspace.desktop b/plugins/extensions/pykrita/plugin/plugins/colorspace/kritapykrita_colorspace.desktop
index 54afb461f1..902a6b2176 100644
--- a/plugins/extensions/pykrita/plugin/plugins/colorspace/kritapykrita_colorspace.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/colorspace/kritapykrita_colorspace.desktop
@@ -1,35 +1,39 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=colorspace
X-Python-2-Compatible=false
Name=Color Space
Name[ca]=Espai de color
Name[ca@valencia]=Espai de color
Name[cs]=Barevný prostor
Name[de]=Farbraum
+Name[el]=Χρωματικός χώρος
Name[es]=Espacio de color
Name[gl]=Espazo de cores
Name[it]=Spazio dei colori
Name[nl]=Kleurruimte
Name[pl]=Przestrzeń barw
Name[pt]=Espaço de Cores
Name[pt_BR]=Espaço de cores
Name[sk]=Farebný priestor
Name[sv]=Färgrymd
Name[uk]=Простір кольорів
+Name[x-test]=xxColor Spacexx
Name[zh_CN]=色彩空间
Comment=Plugin to change color space to selected documents
Comment[ca]=Un connector per canviar l'espai de color dels documents seleccionats
Comment[ca@valencia]=Un connector per canviar l'espai de color dels documents seleccionats
Comment[cs]=Modul pro změnu rozsahu barvy pro vybrané dokumenty
+Comment[el]=Πρόσθετο αλλαγής χρωματικού χώρου σε επιλεγμένα έγγραφα
Comment[es]=Complemento para cambiar el espacio de color de los documentos seleccionados
Comment[gl]=Complemento para cambiar o espazo de cores dos documentos seleccionados.
Comment[it]=Estensione per cambiare lo spazio dei colori ai documenti selezionati
Comment[nl]=Plug-in om kleurruimte in geselecteerde documenten te wijzigen
Comment[pl]=Wtyczka do zmiany przestrzeni barw wybranych dokumentów
Comment[pt]='Plugin' para mudar o espaço de cores do documento seleccionado
Comment[pt_BR]=Plug-in para alterar o espaço de cores em documentos selecionados
Comment[sv]=Insticksprogram för att ändra färgrymd för valda dokument
Comment[uk]=Додаток для зміни простору кольорів у позначених документах
+Comment[x-test]=xxPlugin to change color space to selected documentsxx
Comment[zh_CN]=将颜色空间改为所选文档的插件
diff --git a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop
index d35d6f0d3d..d5aab502aa 100644
--- a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop
@@ -1,27 +1,31 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=comics_project_management_tools
X-Python-2-Compatible=false
Name=Comics Project Management Tools
Name[ca]=Eines per a la gestió dels projectes de còmics
Name[ca@valencia]=Eines per a la gestió dels projectes de còmics
Name[cs]=Nástroje pro správu projektů komixů
+Name[el]=Εργαλεία διαχείρισης έργων ιστοριών σε εικόνες
Name[es]=Herramientas de gestión de proyectos de cómics
Name[it]=Strumenti per la gestione dei progetti di fumetti
Name[nl]=Hulpmiddelen voor projectbeheer van strips
Name[pl]=Narzędzia do zarządzania projektami komiksów
Name[pt]=Ferramentas de Gestão de Projectos de Banda Desenhada
Name[sv]=Projekthanteringsverktyg för tecknade serier
Name[uk]=Інструменти для керування проектами коміксів
+Name[x-test]=xxComics Project Management Toolsxx
Comment=Tools for managing comics.
Comment[ca]=Eines per a gestionar els còmics.
Comment[ca@valencia]=Eines per a gestionar els còmics.
Comment[cs]=Nástroje pro správu komixů.
+Comment[el]=Εργαλεία για τη διαχείριση ιστοριών σε εικόνες.
Comment[es]=Herramientas para gestionar cómics.
Comment[it]=Strumenti per la gestione dei fumetti.
Comment[nl]=Hulpmiddelen voor beheer van strips.
Comment[pl]=Narzędzie do zarządzania komiksami.
Comment[pt]=Ferramentas para gerir bandas desenhadas.
Comment[sv]=Verktyg för att hantera tecknade serier.
Comment[uk]=Інструменти для керування коміксами
+Comment[x-test]=xxTools for managing comics.xx
diff --git a/plugins/extensions/pykrita/plugin/plugins/documenttools/kritapykrita_documenttools.desktop b/plugins/extensions/pykrita/plugin/plugins/documenttools/kritapykrita_documenttools.desktop
index 82d80cb91e..a61f59f075 100644
--- a/plugins/extensions/pykrita/plugin/plugins/documenttools/kritapykrita_documenttools.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/documenttools/kritapykrita_documenttools.desktop
@@ -1,33 +1,37 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=documenttools
X-Python-2-Compatible=false
Name=Document Tools
Name[ca]=Eines de document
Name[ca@valencia]=Eines de document
Name[cs]=Dokumentové nástroje
+Name[el]=Εργαλεία για έγγραφα
Name[es]=Herramientas de documentos
Name[gl]=Ferramentas de documentos
Name[it]=Strumenti per i documenti
Name[nl]=Documenthulpmiddelen
Name[pl]=Narzędzia dokumentu
Name[pt]=Ferramentas de Documentos
Name[pt_BR]=Ferramentas de documento
Name[sv]=Dokumentverktyg
Name[uk]=Засоби документа
+Name[x-test]=xxDocument Toolsxx
Name[zh_CN]=文档工具
Comment=Plugin to manipulate properties of selected documents
Comment[ca]=Un connector per manipular propietats dels documents seleccionats
Comment[ca@valencia]=Un connector per manipular propietats dels documents seleccionats
Comment[cs]=Modul pro správu vlastností vybraných dokumentů
+Comment[el]=Πρόσθετο χειρισμού ιδιοτήτων σε επιλεγμένα έγγραφα
Comment[es]=Complemento para manipular las propiedades de los documentos seleccionados
Comment[gl]=Complemento para manipular as propiedades dos documentos seleccionados.
Comment[it]=Estensione per manipolare le proprietà dei documenti selezionati
Comment[nl]=Plug-in om eigenschappen van geselecteerde documenten te manipuleren
Comment[pl]=Wtyczka do zmiany właściwości wybranych dokumentów
Comment[pt]='Plugin' para manipular as propriedades dos documentos seleccionados
Comment[pt_BR]=Plug-in para manipular as propriedades de documentos selecionados
Comment[sv]=Insticksprogram för att ändra egenskaper för valda dokument
Comment[uk]=Додаток для керування властивостями позначених документів
+Comment[x-test]=xxPlugin to manipulate properties of selected documentsxx
Comment[zh_CN]=操纵选中文档的属性的插件
diff --git a/plugins/extensions/pykrita/plugin/plugins/exportlayers/kritapykrita_exportlayers.desktop b/plugins/extensions/pykrita/plugin/plugins/exportlayers/kritapykrita_exportlayers.desktop
index a684649886..beb23c90ce 100644
--- a/plugins/extensions/pykrita/plugin/plugins/exportlayers/kritapykrita_exportlayers.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/exportlayers/kritapykrita_exportlayers.desktop
@@ -1,34 +1,38 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=exportlayers
X-Python-2-Compatible=false
Name=Export Layers
Name[ca]=Exportació de capes
Name[ca@valencia]=Exportació de capes
Name[cs]=Exportovat vrstvy
Name[de]=Ebenen exportieren
+Name[el]=Εξαγωγή επιπέδων
Name[es]=Exportar capas
Name[gl]=Exportar as capas
Name[it]=Esporta livelli
Name[nl]=Lagen exporteren
Name[pl]=Eksportuj warstwy
Name[pt]=Exportar as Camadas
Name[pt_BR]=Exportar camadas
Name[sv]=Exportera lager
Name[uk]=Експортувати шари
+Name[x-test]=xxExport Layersxx
Name[zh_CN]=导出图层
Comment=Plugin to export layers from a document
Comment[ca]=Un connector per exportar capes d'un document
Comment[ca@valencia]=Un connector per exportar capes d'un document
Comment[cs]=Modul pro export vrstev z dokumentu
+Comment[el]=Πρόσθετο εξαγωγής επιπέδων από έγγραφο
Comment[es]=Complemento para exportar las capas de un documento
Comment[gl]=Complemento para exportar as capas dun documento.
Comment[it]=Estensione per esportare i livelli da un documento
Comment[nl]=Plug-in om lagen uit een document te exporteren
Comment[pl]=Wtyczka do eksportowania warstw z dokumentu
Comment[pt]='Plugin' para exportar as camadas de um documento
Comment[pt_BR]=Plug-in para exportar as camadas de um documento
Comment[sv]=Insticksprogram för att exportera lager från ett dokument
Comment[uk]=Додаток для експортування шарів з документа
+Comment[x-test]=xxPlugin to export layers from a documentxx
Comment[zh_CN]=从文档导出图层的插件
diff --git a/plugins/extensions/pykrita/plugin/plugins/filtermanager/kritapykrita_filtermanager.desktop b/plugins/extensions/pykrita/plugin/plugins/filtermanager/kritapykrita_filtermanager.desktop
index 1668dda5d8..afbfda6bb9 100644
--- a/plugins/extensions/pykrita/plugin/plugins/filtermanager/kritapykrita_filtermanager.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/filtermanager/kritapykrita_filtermanager.desktop
@@ -1,35 +1,39 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=filtermanager
X-Python-2-Compatible=false
Name=Filter Manager
Name[ca]=Gestor de filtres
Name[ca@valencia]=Gestor de filtres
Name[cs]=Správce filtrů
Name[de]=Filterverwaltung
+Name[el]=Διαχειριστής φίλτρων
Name[es]=Gestor de filtros
Name[gl]=Xestor de filtros
Name[it]=Gestore dei filtri
Name[nl]=Beheerder van filters
Name[pl]=Zarządzanie filtrami
Name[pt]=Gestor de Filtros
Name[pt_BR]=Gerenciador de filtros
Name[sv]=Filterhantering
Name[uk]=Керування фільтрами
+Name[x-test]=xxFilter Managerxx
Name[zh_CN]=滤镜管理器
Comment=Plugin to filters management
Comment[ca]=Un connector per gestionar filtres
Comment[ca@valencia]=Un connector per gestionar filtres
Comment[cs]=Modul pro správu filtrů
Comment[de]=Modul zum Verwalten von Filtern
+Comment[el]=Πρόσθετο για τη διαχείριση φίλτρων
Comment[es]=Complemento para la gestión de filtros
Comment[gl]=Complemento para a xestión de filtros.
Comment[it]=Estensione per la gestione dei filtri
Comment[nl]=Plug-in voor beheer van filters
Comment[pl]=Wtyczka do zarządzania filtrami
Comment[pt]='Plugin' para a gestão de filtros
Comment[pt_BR]=Plug-in para gerenciamento de filtros
Comment[sv]=Insticksprogram för filterhantering
Comment[uk]=Додаток для керування фільтрами
+Comment[x-test]=xxPlugin to filters managementxx
Comment[zh_CN]=滤镜管理插件
diff --git a/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop b/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop
index 5408a28df8..5c563133c2 100644
--- a/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop
@@ -1,40 +1,44 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=hello
X-Python-2-Compatible=false
Name=Hello World
Name[ca]=Hola món
Name[ca@valencia]=Hola món
Name[cs]=Hello World
Name[de]=Hallo Welt
+Name[el]=Hello World
Name[en_GB]=Hello World
Name[es]=Hola mundo
Name[fr]=Bonjour tout le monde
Name[gl]=Ola mundo
Name[it]=Ciao mondo
Name[nl]=Hallo wereld
Name[pl]=Witaj świecie
Name[pt]=Olá Mundo
Name[pt_BR]=Olá mundo
Name[sk]=Ahoj svet
Name[sv]=Hello World
Name[tr]=Merhaba Dünya
Name[uk]=Привіт, світе
+Name[x-test]=xxHello Worldxx
Name[zh_CN]=Hello World
Comment=Basic plugin to test PyKrita
Comment[ca]=Connector bàsic per provar el PyKrita
Comment[ca@valencia]=Connector bàsic per provar el PyKrita
Comment[cs]=Základní modul pro testování PyKrita
+Comment[el]=Βασικό πρόσθετο δοκιμής PyKrita
Comment[en_GB]=Basic plugin to test PyKrita
Comment[es]=Complemento básico para probar PyKrita
Comment[gl]=Complemento básico para probar PyKrita.
Comment[it]=Estensione di base per provare PyKrita
Comment[nl]=Basisplug-in om PyKrita te testen
Comment[pl]=Podstawowa wtyczka do wypróbowania PyKrity
Comment[pt]='Plugin' básico para testar o PyKrita
Comment[pt_BR]=Plug-in básico para testar o PyKrita
Comment[sv]=Enkelt insticksprogram för att utprova PyKrita
Comment[tr]=PyKrita'yı test etmek için temel eklenti
Comment[uk]=Базовий додаток для тестування PyKrita
+Comment[x-test]=xxBasic plugin to test PyKritaxx
Comment[zh_CN]=测试 PyKrita 的基本插件
diff --git a/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop b/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop
index 7cce8abbaf..25481c90ed 100644
--- a/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop
@@ -1,39 +1,43 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=highpass
X-Python-2-Compatible=false
Name=Highpass Filter
Name[ca]=Filtre passa-alt
Name[ca@valencia]=Filtre passa-alt
Name[cs]=Filtr s horní propustí
Name[de]=Hochpassfilter
+Name[el]=Υψιπερατό φίλτρο
Name[en_GB]=Highpass Filter
Name[es]=Filtro paso alto
Name[gl]=Filtro de paso alto
Name[it]=Filtro di accentuazione passaggio
Name[nl]=Hoogdoorlaatfilter
Name[pl]=Filtr górnoprzepustowy
Name[pt]=Filtro Passa-Alto
Name[pt_BR]=Filtro passa alta
Name[sv]=Högpassfilter
Name[tr]=Yüksek Geçirgen Süzgeç
Name[uk]=Високочастотний фільтр
+Name[x-test]=xxHighpass Filterxx
Name[zh_CN]=高通滤镜
Comment=Highpass Filter, based on http://registry.gimp.org/node/7385
Comment[ca]=Filtre passa-alt, basat en el http://registry.gimp.org/node/7385
Comment[ca@valencia]=Filtre passa-alt, basat en el http://registry.gimp.org/node/7385
Comment[cs]=Filtr s horní propustí založený na http://registry.gimp.org/node/7385
Comment[de]=Hochpassfilter, Grundlage ist http://registry.gimp.org/node/7385
+Comment[el]=Υψιπερατό φίλτρο, με βάση το http://registry.gimp.org/node/7385
Comment[en_GB]=Highpass Filter, based on http://registry.gimp.org/node/7385
Comment[es]=Filtro paso alto, basado en http://registry.gimp.org/node/7385
Comment[gl]=Filtro de paso alto, baseado en http://registry.gimp.org/node/7385.
Comment[it]=Filtro di accentuazione passaggio, basato su http://registry.gimp.org/node/7385
Comment[nl]=Hoogdoorlaatfilter, gebaseerd op http://registry.gimp.org/node/7385
Comment[pl]=Filtr górnoprzepustowy, oparty na http://registry.gimp.org/node/7385
Comment[pt]=Filtro passa-alto, baseado em http://registry.gimp.org/node/7385
Comment[pt_BR]=Filtro passa alta, baseado em http://registry.gimp.org/node/7385
Comment[sv]=Högpassfilter, baserat på http://registry.gimp.org/node/7385
Comment[tr]=http://registry.gimp.org/node/7385 tabanlı Yüksek Geçirgen Süzgeç
Comment[uk]=Високочастотний фільтр, засновано на on http://registry.gimp.org/node/7385
+Comment[x-test]=xxHighpass Filter, based on http://registry.gimp.org/node/7385xx
Comment[zh_CN]=高通滤镜,基于 http://registry.gimp.org/node/7385
diff --git a/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop
index 06f33abc4c..35517c68a9 100644
--- a/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop
@@ -1,29 +1,33 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=lastdocumentsdocker
X-Python-2-Compatible=false
Name=Last Documents Docker
Name[ca]=Acoblador dels darrers documents
Name[ca@valencia]=Acoblador dels darrers documents
+Name[el]=Προσάρτηση τελευταίων εγγράφοων
Name[es]=Panel de últimos documentos
Name[gl]=Doca dos últimos documentos
Name[it]=Area di aggancio Ultimi documenti
Name[nl]=Vastzetter van laatste documenten
Name[pl]=Dok ostatnich dokumentów
Name[pt]=Área dos Últimos Documentos
Name[sv]=Dockningsfönster för senaste dokument
Name[uk]=Бічна панель останніх документів
+Name[x-test]=xxLast Documents Dockerxx
Name[zh_CN]=最近文档坞窗
Comment=A Python-based docker for show thumbnails to last ten documents
Comment[ca]=Un acoblador basant en el Python per mostrar miniatures dels darrers deu documents
Comment[ca@valencia]=Un acoblador basant en el Python per mostrar miniatures dels darrers deu documents
+Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την εμφάνιση επισκοπήσεων των δέκα τελευταίων εγγράφων
Comment[es]=Un panel basado en Python para mostrar miniaturas de los últimos diez documentos
Comment[gl]=Unha doca baseada en Python para mostrar as miniaturas dos últimos dez documentos.
Comment[it]=Un'area di aggancio basata su Python per mostrare miniature degli ultimi dieci documenti.
Comment[nl]=Een op Python gebaseerde vastzetter om miniaturen te tonen naar de laatste tien documenten.
Comment[pl]=Dok oparty na pythonie do wyświetlania miniatur ostatnich dziesięciu dokumentów
Comment[pt]=Uma área acoplável, feita em Python, para mostrar as miniaturas dos últimos dez documentos
Comment[sv]=Ett Python-baserat dockningsfönster för att visa miniatyrbilder för de tio senaste dokumenten
Comment[uk]=Бічна панель на основі Python для показу мініатюр останніх десяти документів
+Comment[x-test]=xxA Python-based docker for show thumbnails to last ten documentsxx
Comment[zh_CN]=基于 Python 的坞窗,展示最近十个文档的缩略图
diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/kritapykrita_palette_docker.desktop b/plugins/extensions/pykrita/plugin/plugins/palette_docker/kritapykrita_palette_docker.desktop
index 329fbbb1f9..f403398c55 100644
--- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/kritapykrita_palette_docker.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/kritapykrita_palette_docker.desktop
@@ -1,31 +1,35 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=palette_docker
X-Python-2-Compatible=false
Name=Palette docker
Name[ca]=Acoblador de paletes
Name[ca@valencia]=Acoblador de paletes
Name[cs]=Dok palet
Name[de]=Paletten-Docker
+Name[el]=Προσάρτηση παλέτας
Name[es]=Panel de paleta
Name[gl]=Doca de paleta
Name[it]=Area di aggancio della tavolozza
Name[nl]=Vastzetter van palet
Name[pl]=Dok palety
Name[pt]=Área acoplável da paleta
Name[sv]=Dockningsfönster för palett
Name[uk]=Панель палітри
+Name[x-test]=xxPalette dockerxx
Name[zh_CN]=调色板坞窗
Comment=A Python-based docker to edit color palettes.
Comment[ca]=Un acoblador basant en el Python per editar paletes de colors.
Comment[ca@valencia]=Un acoblador basant en el Python per editar paletes de colors.
+Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την επεξεργασία παλετών χρώματος.
Comment[es]=Un panel basado en Python para editar paletas de colores.
Comment[gl]=Unha doca baseada en Python para editar paletas de cores.
Comment[it]=Un'area di aggancio per modificare le tavolozze di colori basata su Python.
Comment[nl]=Een op Python gebaseerde vastzetter om kleurpaletten te bewerken.
Comment[pl]=Dok oparty na pythonie do edytowania palet barw.
Comment[pt]=Uma área acoplável, feita em Python, para editar paletas de cores.
Comment[sv]=Ett Python-baserat dockningsfönster för att redigera färgpaletter.
Comment[uk]=Бічна панель для редагування палітр кольорів на основі Python.
+Comment[x-test]=xxA Python-based docker to edit color palettes.xx
Comment[zh_CN]=基于 Python 的调色板编辑坞窗
diff --git a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop
index 7ea51ec00d..953c32a9e8 100644
--- a/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/quick_settings_docker/kritapykrita_quick_settings_docker.desktop
@@ -1,30 +1,34 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=quick_settings_docker
X-Python-2-Compatible=false
Name=Quick Settings Docker
Name[ca]=Acoblador d'arranjament ràpid
Name[ca@valencia]=Acoblador d'arranjament ràpid
Name[cs]=Dok pro rychlé nastavení
+Name[el]=Προσάρτηση γρήγορων ρυθμίσεων
Name[es]=Panel de ajustes rápidos
Name[gl]=Doca de configuración rápida
Name[it]=Area di aggancio delle impostazioni rapide
Name[nl]=Docker voor snelle instellingen
Name[pl]=Dok szybkich ustawień
Name[pt]=Área de Configuração Rápida
Name[sv]=Dockningspanel med snabbinställningar
Name[uk]=Панель швидких параметрів
+Name[x-test]=xxQuick Settings Dockerxx
Name[zh_CN]=快速设置坞窗
Comment=A Python-based docker for quickly changing brush size and opacity.
Comment[ca]=Un acoblador basant en el Python per canviar ràpidament la mida del pinzell i l'opacitat.
Comment[ca@valencia]=Un acoblador basant en el Python per canviar ràpidament la mida del pinzell i l'opacitat.
+Comment[el]=Ένα εργαλείο προσάρτησης σε Python για γρήγορη αλλαγή του μεγέθους και της αδιαφάνειας του πινέλου.
Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel.
Comment[gl]=Unha doca baseada en Python para cambiar rapidamente a opacidade e o tamaño dos pinceis.
Comment[it]=Un'area di aggancio basata su Python per cambiare rapidamente la dimensione del pennello e l'opacità.
Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking.
Comment[pl]=Dok oparty na pythonie do szybkiej zmiany rozmiaru i nieprzezroczystości pędzla.
Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel.
Comment[sv]=En Python-baserad dockningspanel för att snabbt ändra penselstorlek och ogenomskinlighet.
Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля.
+Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx
Comment[zh_CN]=基于 Python 的坞窗,用于快速更改笔刷尺寸和透明度。
diff --git a/plugins/extensions/pykrita/plugin/plugins/scriptdocker/kritapykrita_scriptdocker.desktop b/plugins/extensions/pykrita/plugin/plugins/scriptdocker/kritapykrita_scriptdocker.desktop
index c29cae457b..6d8c58d377 100644
--- a/plugins/extensions/pykrita/plugin/plugins/scriptdocker/kritapykrita_scriptdocker.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/scriptdocker/kritapykrita_scriptdocker.desktop
@@ -1,30 +1,34 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=scriptdocker
X-Python-2-Compatible=false
Name=Script Docker
Name[ca]=Acoblador de scripts
Name[ca@valencia]=Acoblador de scripts
Name[cs]=Dok skriptu
+Name[el]=Προσάρτηση σεναρίων
Name[es]=Panel de guiones
Name[gl]=Doca de scripts
Name[it]=Area di aggancio degli script
Name[nl]=Vastzetten van scripts
Name[pl]=Dok skryptów
Name[pt]=Área de Programas
Name[sv]=Dockningsfönster för skript
Name[uk]=Бічна панель скриптів
+Name[x-test]=xxScript Dockerxx
Name[zh_CN]=脚本坞窗
Comment=A Python-based docker for create actions and point to Python scripts
Comment[ca]=Un acoblador basant en el Python per crear accions i apuntar a scripts del Python
Comment[ca@valencia]=Un acoblador basant en el Python per crear accions i apuntar a scripts del Python
+Comment[el]=Ένα εργαλείο προσάρτησης σε Python για τη δημιουργία ενεργειών και τη δεικτοδότηση σεναρίων σε Python
Comment[es]=Un panel basado en Python para crear y apuntar a guiones de Python
Comment[gl]=Unha doca escrita en Python para crear accións e apuntar a scripts escritos en Python.
Comment[it]=Un'area di aggancio basata su Python per creare azioni e scegliere sctipt Python
Comment[nl]=Een op Python gebaseerde vastzetter voor aanmaakacties en wijzen naar Python-scripts
Comment[pl]=Dok oparty na pythonie do tworzenia działań i punktów dla skryptów pythona
Comment[pt]=Uma área acoplável, feita em Python, para criar acções e apontar para programas em Python
Comment[sv]=Ett Python-baserat dockningsfönster för att skapa åtgärder och peka ut Python-skript
Comment[uk]=Бічна панель для створення дій і керування скриптами на основі Python.
+Comment[x-test]=xxA Python-based docker for create actions and point to Python scriptsxx
Comment[zh_CN]=基于 Python 的坞窗,用于创建动作并指向 Python 脚本
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop b/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop
index 33660f8c3c..7a68e43d4f 100644
--- a/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop
@@ -1,34 +1,38 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=scripter
X-Python-2-Compatible=false
Name=Scripter
Name[ca]=Scripter
Name[ca@valencia]=Scripter
Name[de]=Scripter
+Name[el]=Σενάρια
Name[en_GB]=Scripter
Name[es]=Guionador
Name[gl]=Executor de scripts
Name[it]=Scripter
Name[nl]=Scriptschrijver
Name[pl]=Skrypter
Name[pt]=Programador
Name[sv]=Skriptgenerator
Name[tr]=Betik yazarı
Name[uk]=Скриптер
+Name[x-test]=xxScripterxx
Name[zh_CN]=脚本编写者
Comment=Plugin to execute ad-hoc Python code
Comment[ca]=Connector per executar codi Python ad-hoc
Comment[ca@valencia]=Connector per executar codi Python ad-hoc
+Comment[el]=Πρόσθετο για την εκτέλεση συγκεκριμένου κώδικα Python
Comment[en_GB]=Plugin to execute ad-hoc Python code
Comment[es]=Complemento para ejecutar código Python a medida
Comment[gl]=Complemento para executar código de Python escrito no momento.
Comment[it]=Estensione per eseguire ad-hoc codice Python
Comment[nl]=Plug-in om ad-hoc Python code uit te voeren
Comment[pl]=Wtyczka do wykonywania kodu Pythona ad-hoc
Comment[pt]='Plugin' para executar código em Python arbitrário
Comment[sv]=Insticksprogram för att köra godtycklig Python-kod
Comment[tr]=Geçici Python kodu çalıştırmak için eklenti
Comment[uk]=Додаток для виконання апріорного коду Python
+Comment[x-test]=xxPlugin to execute ad-hoc Python codexx
Comment[zh_CN]=执行特定 Python 代码的插件
diff --git a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
index ff092c7e5a..e97632a36c 100644
--- a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
@@ -1,34 +1,38 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=selectionsbagdocker
X-Python-2-Compatible=false
Name=Selections Bag Docker
Name[ca]=Acoblador de bossa de seleccions
Name[ca@valencia]=Acoblador de bossa de seleccions
+Name[el]=Προσάρτηση σάκου επιλογών
Name[en_GB]=Selections Bag Docker
Name[es]=Panel de selecciones
Name[gl]=Doca de bolsa das seleccións
Name[it]=Area di raccolta selezioni
Name[nl]=Docker van zak met selecties
Name[pl]=Dok worka zaznaczeń
Name[pt]=Área de Selecções
Name[sv]=Dockningspanel med markeringspåse
Name[tr]=Seçim Çantası İşçisi
Name[uk]=Бічна панель позначеного
+Name[x-test]=xxSelections Bag Dockerxx
Name[zh_CN]=选区包坞窗
Comment=A docker that allow to store a list of selections
Comment[ca]=Un acoblador que permet emmagatzemar una llista de seleccions
Comment[ca@valencia]=Un acoblador que permet emmagatzemar una llista de seleccions
Comment[cs]=Dok umožňující uložit seznam výběrů
+Comment[el]=Ένα εργαλείο προσάρτησης που επιτρέπει την αποθήκευση μιας λίστας επιλογών
Comment[en_GB]=A docker that allow to store a list of selections
Comment[es]=Un panel que permite guardar una lista de selecciones
Comment[gl]=Unha doca que permite almacenar unha lista de seleccións.
Comment[it]=Un'area di aggancio che consente di memorizzare un elenco di selezioni
Comment[nl]=Een docker die een lijst met selecties kan opslaan
Comment[pl]=Dok, który umożliwia przechowywanie listy zaznaczeń
Comment[pt]=Uma área acoplável que permite guardar uma lista de selecções
Comment[sv]=En dockningspanel som gör det möjligt att lagra en lista över markeringar
Comment[tr]=Seçimlerin bir listesini saklamayı sağlayan işçi
Comment[uk]=Бічна панель, на якій можна зберігати список позначеного
+Comment[x-test]=xxA docker that allow to store a list of selectionsxx
Comment[zh_CN]=存储选区列表的坞窗
diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop
index 003a9ca41d..5d1a9879f3 100644
--- a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop
+++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop
@@ -1,34 +1,38 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=tenbrushes
X-Python-2-Compatible=false
Name=Ten Brushes
Name[ca]=Deu pinzells
Name[ca@valencia]=Deu pinzells
Name[cs]=Deset štětců
+Name[el]=Δέκα πινέλα
Name[en_GB]=Ten Brushes
Name[es]=Diez pinceles
Name[gl]=Dez pinceis
Name[it]=Dieci pennelli
Name[nl]=Tien penselen
Name[pl]=Dziesięć pędzli
Name[pt]=Dez Pincéis
Name[sv]=Tio penslar
Name[tr]=On Fırça
Name[uk]=Десять пензлів
+Name[x-test]=xxTen Brushesxx
Name[zh_CN]=十笔刷
Comment=Assign a preset to ctrl-1 to ctrl-0
Comment[ca]=Assigna una predefinició des de Ctrl-1 a Ctrl-0
Comment[ca@valencia]=Assigna una predefinició des de Ctrl-1 a Ctrl-0
+Comment[el]=Αντιστοίχιση προκαθορισμένου από ctrl-1 στο ctrl-0
Comment[en_GB]=Assign a preset to ctrl-1 to ctrl-0
Comment[es]=Asignar una preselección a Ctrl-1 hasta Ctrl-0
Comment[gl]=Asigne unha predefinición do Ctrl+1 ao Ctrl+0.
Comment[it]=Assegna una preimpostazione per ctrl-1 a ctrl-0
Comment[nl]=Een voorinstelling toekennen aan Ctrl-1 tot Ctrl-0
Comment[pl]=Przypisz nastawę do ctrl-1 lub ctrl-0
Comment[pt]=Atribuir uma predefinição de Ctrl-1 a Ctrl-0
Comment[sv]=Tilldela en förinställning för Ctrl+1 till Ctrl+0
Comment[tr]= ctrl-1 ve ctrl-0 için ayar atama
Comment[uk]=Прив’язування наборів налаштувань до скорочень від ctrl-1 до ctrl-0
+Comment[x-test]=xxAssign a preset to ctrl-1 to ctrl-0xx
Comment[zh_CN]=将预设分配给 Ctrl+1 到 Ctrl+0
diff --git a/plugins/extensions/pykrita/plugin/version_checker.h b/plugins/extensions/pykrita/plugin/version_checker.h
index bfb9c2c5fb..33a32d292a 100644
--- a/plugins/extensions/pykrita/plugin/version_checker.h
+++ b/plugins/extensions/pykrita/plugin/version_checker.h
@@ -1,279 +1,279 @@
// This file is part of PyKrita, Krita' Python scripting plugin.
//
// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) version 3.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with this library; see the file COPYING.LIB. If not, write to
// the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
// Boston, MA 02110-1301, USA.
#ifndef __VERSION_CHECKER_H__
# define __VERSION_CHECKER_H__
# include <QtCore/QString>
# include <QtCore/QStringList>
# include <QtCore/QtGlobal>
namespace PyKrita
{
/**
* \brief Class \c version
*/
class version
{
enum type {
undefined = -1
, zero = 0
};
public:
/// Default constructor
explicit version(
const int major = zero
, const int minor = zero
, const int patch = zero
)
: m_major(major)
, m_minor(minor)
, m_patch(patch) {
}
int major() const {
return m_major;
}
int minor() const {
return m_minor;
}
int patch() const {
return m_patch;
}
bool isValid() const {
return major() != undefined && minor() != undefined && patch() != undefined;
}
operator QString() const {
return QString("%1.%2.%3").arg(major()).arg(minor()).arg(patch());
}
static version fromString(const QString& version_str) {
int tmp[3] = {zero, zero, zero};
QStringList parts = version_str.split('.');
for (
unsigned long i = 0
; i < qMin(static_cast<unsigned long>(sizeof(tmp) / sizeof(int)), static_cast<unsigned long>(parts.size()))
; ++i
) {
bool ok;
const int num = parts[i].toInt(&ok);
if (ok)
tmp[i] = num;
else {
tmp[i] = undefined;
break;
}
}
return version(tmp[0], tmp[1], tmp[2]);
};
static version invalid() {
static version s_bad(undefined, undefined, undefined);
return s_bad;
}
private:
int m_major;
int m_minor;
int m_patch;
};
inline bool operator==(const version& left, const version& right)
{
return left.major() == right.major()
&& left.minor() == right.minor()
&& left.patch() == right.patch()
;
}
inline bool operator!=(const version& left, const version& right)
{
return !(left == right);
}
inline bool operator<(const version& left, const version& right)
{
return left.major() < right.major()
|| (left.major() == right.major() && left.minor() < right.minor())
|| (left.major() == right.major() && left.minor() == right.minor() && left.patch() < right.patch())
;
}
inline bool operator>(const version& left, const version& right)
{
return left.major() > right.major()
|| (left.major() == right.major() && left.minor() > right.minor())
|| (left.major() == right.major() && left.minor() == right.minor() && left.patch() > right.patch())
;
}
inline bool operator<=(const version& left, const version& right)
{
return left == right || left < right;
}
inline bool operator>=(const version& left, const version& right)
{
return left == right || left > right;
}
/**
* \brief Class \c version_checker
*/
class version_checker
{
public:
enum operation {
invalid
, undefined
, less
, less_or_equal
, greather
, greather_or_equal
, not_equal
, equal
, last__
};
/// Default constructor
explicit version_checker(const operation op = invalid)
: m_op(op) {
}
bool isValid() const {
return m_op != invalid;
}
bool isEmpty() const {
return m_op == undefined;
}
void bind_second(const version& rhs) {
m_rhs = rhs;
}
bool operator()(const version& left) {
switch (m_op) {
case less:
return left < m_rhs;
case greather:
return left > m_rhs;
case equal:
return left == m_rhs;
case not_equal:
return left != m_rhs;
case less_or_equal:
return left <= m_rhs;
case greather_or_equal:
return left >= m_rhs;
default:
Q_ASSERT(!"Sanity check");
break;
}
return false;
}
version required() const {
return m_rhs;
}
QString operationToString() const {
QString result;
switch (m_op) {
case less:
result = " < ";
break;
case greather:
result = " > ";
break;
case equal:
result = " = ";
break;
case not_equal:
result = " != ";
break;
case less_or_equal:
result = " <= ";
break;
case greather_or_equal:
result = " >= ";
break;
default:
Q_ASSERT(!"Sanity check");
break;
}
return result;
}
static version_checker fromString(const QString& version_info) {
version_checker checker(invalid);
if (version_info.isEmpty())
return checker;
bool lookup_next_char = false;
int strip_lead_pos = 0;
- switch (version_info.at(0).toAscii()) {
+ switch (version_info.at(0).toLatin1()) {
case '<':
checker.m_op = less;
lookup_next_char = true;
break;
case '>':
checker.m_op = greather;
lookup_next_char = true;
break;
case '=':
strip_lead_pos = 1;
checker.m_op = equal;
break;
default:
strip_lead_pos = 0;
checker.m_op = equal;
break;
}
if (lookup_next_char) {
- if (version_info.at(1).toAscii() == '=') {
+ if (version_info.at(1).toLatin1() == '=') {
// NOTE Shift state
checker.m_op = operation(int(checker.m_op) + 1);
strip_lead_pos = 2;
} else {
strip_lead_pos = 1;
}
}
//
QString rhs_str = version_info.mid(strip_lead_pos).trimmed();
version rhs = version::fromString(rhs_str);
if (rhs.isValid())
checker.bind_second(rhs);
else
checker.m_op = invalid;
return checker;
}
private:
operation m_op;
version m_rhs;
};
} // namespace PyKrita
#endif // __VERSION_CHECKER_H__
diff --git a/plugins/extensions/pykrita/sip/filename b/plugins/extensions/pykrita/sip/filename
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/plugins/extensions/qmic/QMic.cpp b/plugins/extensions/qmic/QMic.cpp
index ea27fbec84..ff26f78eb2 100644
--- a/plugins/extensions/qmic/QMic.cpp
+++ b/plugins/extensions/qmic/QMic.cpp
@@ -1,460 +1,460 @@
/*
* Copyright (c) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "QMic.h"
#include <QApplication>
#include <QDebug>
#include <QFileInfo>
#include <QLocalSocket>
#include <QBuffer>
#include <QByteArray>
#include <QDataStream>
#include <QProcess>
#include <QLocalServer>
#include <QVBoxLayout>
#include <QUuid>
#include <QList>
#include <QSharedPointer>
#include <QMultiMap>
#include <QSharedMemory>
#include <QMessageBox>
#include <klocalizedstring.h>
#include <kpluginfactory.h>
#include <KoDialog.h>
#include <KoColorSpaceConstants.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpace.h>
#include <KoColorModelStandardIds.h>
#include <KoColorSpaceTraits.h>
#include <KisPart.h>
#include <KisViewManager.h>
#include <kis_action.h>
#include <kis_config.h>
#include <kis_preference_set_registry.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_layer.h>
#include <kis_selection.h>
#include <kis_paint_layer.h>
#include <kis_algebra_2d.h>
#include "kis_input_output_mapper.h"
#include "kis_qmic_simple_convertor.h"
#include "kis_import_qmic_processing_visitor.h"
#include <PluginSettings.h>
#include "kis_qmic_applicator.h"
static const char ack[] = "ack";
K_PLUGIN_FACTORY_WITH_JSON(QMicFactory, "kritaqmic.json", registerPlugin<QMic>();)
QMic::QMic(QObject *parent, const QVariantList &)
: KisViewPlugin(parent)
, m_gmicApplicator(0)
{
#ifndef Q_OS_MAC
// KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
// PluginSettingsFactory* settingsFactory = new PluginSettingsFactory();
// preferenceSetRegistry->add("QMicPluginSettingsFactory", settingsFactory);
m_qmicAction = createAction("QMic");
m_qmicAction->setActivationFlags(KisAction::ACTIVE_DEVICE);
connect(m_qmicAction , SIGNAL(triggered()), this, SLOT(slotQMic()));
m_againAction = createAction("QMicAgain");
m_againAction->setActivationFlags(KisAction::ACTIVE_DEVICE);
m_againAction->setEnabled(false);
connect(m_againAction, SIGNAL(triggered()), this, SLOT(slotQMicAgain()));
m_gmicApplicator = new KisQmicApplicator();
connect(m_gmicApplicator, SIGNAL(gmicFinished(bool, int, QString)), this, SLOT(slotGmicFinished(bool, int, QString)));
#endif
}
QMic::~QMic()
{
Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) {
qDebug() << "detaching" << memorySegment->key();
memorySegment->detach();
}
qDeleteAll(m_sharedMemorySegments);
m_sharedMemorySegments.clear();
if (m_pluginProcess) {
m_pluginProcess->close();
}
delete m_gmicApplicator;
delete m_localServer;
}
void QMic::slotQMicAgain()
{
slotQMic(true);
}
void QMic::slotQMic(bool again)
{
m_qmicAction->setEnabled(false);
m_againAction->setEnabled(false);
// find the krita-gmic-qt plugin
QString pluginPath = PluginSettings::gmicQtPath();
if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists() || !QFileInfo(pluginPath).isFile()) {
QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Krita cannot find the gmic-qt plugin."));
return;
}
m_key = QUuid::createUuid().toString();
m_localServer = new QLocalServer();
m_localServer->listen(m_key);
connect(m_localServer, SIGNAL(newConnection()), SLOT(connected()));
m_pluginProcess = new QProcess(this);
m_pluginProcess->setProcessChannelMode(QProcess::ForwardedChannels);
connect(m_pluginProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(pluginFinished(int,QProcess::ExitStatus)));
connect(m_pluginProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(pluginStateChanged(QProcess::ProcessState)));
- m_pluginProcess->start(pluginPath, QStringList() << m_key << (again ? QString(" reapply") : QString::null));
+ m_pluginProcess->start(pluginPath, QStringList() << m_key << (again ? QString(" reapply") : QString()));
bool r = m_pluginProcess->waitForStarted();
while (m_pluginProcess->waitForFinished(10)) {
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
}
qDebug() << "Plugin started" << r << m_pluginProcess->state();
}
void QMic::connected()
{
qDebug() << "connected";
QLocalSocket *socket = m_localServer->nextPendingConnection();
if (!socket) { return; }
while (socket->bytesAvailable() < static_cast<int>(sizeof(quint32))) {
if (!socket->isValid()) { // stale request
return;
}
socket->waitForReadyRead(1000);
}
QDataStream ds(socket);
QByteArray msg;
quint32 remaining;
ds >> remaining;
msg.resize(remaining);
int got = 0;
char* uMsgBuf = msg.data();
// FIXME: Should use read transaction for Qt >= 5.7:
// https://doc.qt.io/qt-5/qdatastream.html#using-read-transactions
do {
got = ds.readRawData(uMsgBuf, remaining);
remaining -= got;
uMsgBuf += got;
}
while (remaining && got >= 0 && socket->waitForReadyRead(2000));
if (got < 0) {
qWarning() << "Message reception failed" << socket->errorString();
delete socket;
m_localServer->close();
delete m_localServer;
m_localServer = 0;
return;
}
QString message = QString::fromUtf8(msg);
qDebug() << "Received" << message;
// Check the message: we can get three different ones
QMultiMap<QString, QString> messageMap;
Q_FOREACH(QString line, message.split('\n', QString::SkipEmptyParts)) {
QList<QString> kv = line.split('=', QString::SkipEmptyParts);
if (kv.size() == 2) {
messageMap.insert(kv[0], kv[1]);
}
else {
qWarning() << "line" << line << "is invalid.";
}
}
if (!messageMap.contains("command")) {
qWarning() << "Message did not contain a command";
return;
}
int mode = 0;
if (messageMap.contains("mode")) {
mode = messageMap.values("mode").first().toInt();
}
QByteArray ba;
QString messageBoxWarningText;
if (messageMap.values("command").first() == "gmic_qt_get_image_size") {
KisSelectionSP selection = m_view->image()->globalSelection();
if (selection) {
QRect selectionRect = selection->selectedExactRect();
ba = QByteArray::number(selectionRect.width()) + "," + QByteArray::number(selectionRect.height());
}
else {
ba = QByteArray::number(m_view->image()->width()) + "," + QByteArray::number(m_view->image()->height());
}
}
else if (messageMap.values("command").first() == "gmic_qt_get_cropped_images") {
// Parse the message, create the shared memory segments, and create a new message to send back and waid for ack
QRectF cropRect(0.0, 0.0, 1.0, 1.0);
if (!messageMap.contains("croprect") || messageMap.values("croprect").first().split(',', QString::SkipEmptyParts).size() != 4) {
qWarning() << "gmic-qt didn't send a croprect or not a valid croprect";
}
else {
QStringList cr = messageMap.values("croprect").first().split(',', QString::SkipEmptyParts);
cropRect.setX(cr[0].toFloat());
cropRect.setY(cr[1].toFloat());
cropRect.setWidth(cr[2].toFloat());
cropRect.setHeight(cr[3].toFloat());
}
if (!prepareCroppedImages(&ba, cropRect, mode)) {
qWarning() << "Failed to prepare images for gmic-qt";
}
}
else if (messageMap.values("command").first() == "gmic_qt_output_images") {
// Parse the message. read the shared memory segments, fix up the current image and send an ack
qDebug() << "gmic_qt_output_images";
QStringList layers = messageMap.values("layer");
m_outputMode = (OutputMode)mode;
if (m_outputMode != IN_PLACE) {
messageBoxWarningText = i18n("Sorry, this output mode is not implemented yet.");
m_outputMode = IN_PLACE;
}
slotStartApplicator(layers);
}
else if (messageMap.values("command").first() == "gmic_qt_detach") {
Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) {
qDebug() << "detaching" << memorySegment->key() << memorySegment->isAttached();
if (memorySegment->isAttached()) {
if (!memorySegment->detach()) {
qDebug() << "\t" << memorySegment->error() << memorySegment->errorString();
}
}
}
qDeleteAll(m_sharedMemorySegments);
m_sharedMemorySegments.clear();
}
else {
qWarning() << "Received unknown command" << messageMap.values("command");
}
qDebug() << "Sending" << QString::fromUtf8(ba);
// HACK: Make sure QDataStream does not refuse to write!
// Proper fix: Change the above read to use read transaction
ds.resetStatus();
ds.writeBytes(ba.constData(), ba.length());
// Flush the socket because we might not return to the event loop!
if (!socket->waitForBytesWritten(2000)) {
qWarning() << "Failed to write response:" << socket->error();
}
// Wait for the ack
bool r = true;
r &= socket->waitForReadyRead(2000); // wait for ack
r &= (socket->read(qstrlen(ack)) == ack);
if (!socket->waitForDisconnected(2000)) {
qWarning() << "Remote not disconnected:" << socket->error();
// Wait again
socket->disconnectFromServer();
if (socket->waitForDisconnected(2000)) {
qWarning() << "Disconnect timed out:" << socket->error();
}
}
if (!messageBoxWarningText.isEmpty()) {
// Defer the message box to the event loop
QTimer::singleShot(0, [messageBoxWarningText]() {
QMessageBox::warning(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita"), messageBoxWarningText);
});
}
}
void QMic::pluginStateChanged(QProcess::ProcessState state)
{
qDebug() << "stateChanged" << state;
}
void QMic::pluginFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
qDebug() << "pluginFinished" << exitCode << exitStatus;
delete m_pluginProcess;
m_pluginProcess = 0;
delete m_localServer;
m_localServer = 0;
m_qmicAction->setEnabled(true);
m_againAction->setEnabled(true);
}
void QMic::slotGmicFinished(bool successfully, int milliseconds, const QString &msg)
{
qDebug() << "slotGmicFinished();" << successfully << milliseconds << msg;
if (successfully) {
m_gmicApplicator->finish();
}
else {
m_gmicApplicator->cancel();
QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("G'Mic failed, reason:") + msg);
}
}
void QMic::slotStartApplicator(QStringList gmicImages)
{
qDebug() << "slotStartApplicator();" << gmicImages;
// Create a vector of gmic images
QVector<gmic_image<float> *> images;
Q_FOREACH(const QString &image, gmicImages) {
QStringList parts = image.split(',', QString::SkipEmptyParts);
Q_ASSERT(parts.size() == 4);
QString key = parts[0];
QString layerName = QByteArray::fromHex(parts[1].toLatin1());
int spectrum = parts[2].toInt();
int width = parts[3].toInt();
int height = parts[4].toInt();
qDebug() << key << layerName << width << height;
QSharedMemory m(key);
if (!m.attach(QSharedMemory::ReadOnly)) {
qWarning() << "Could not attach to shared memory area." << m.error() << m.errorString();
}
if (m.isAttached()) {
if (!m.lock()) {
qDebug() << "Could not lock memeory segment" << m.error() << m.errorString();
}
qDebug() << "Memory segment" << key << m.size() << m.constData() << m.data();
gmic_image<float> *gimg = new gmic_image<float>();
gimg->assign(width, height, 1, spectrum);
gimg->name = layerName;
gimg->_data = new float[width * height * spectrum * sizeof(float)];
qDebug() << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << m.size();
memcpy(gimg->_data, m.constData(), width * height * spectrum * sizeof(float));
qDebug() << "created gmic image" << gimg->name << gimg->_width << gimg->_height;
if (!m.unlock()) {
qDebug() << "Could not unlock memeory segment" << m.error() << m.errorString();
}
if (!m.detach()) {
qDebug() << "Could not detach from memeory segment" << m.error() << m.errorString();
}
images.append(gimg);
}
}
qDebug() << "Got" << images.size() << "gmic images";
// Start the applicator
KUndo2MagicString actionName = kundo2_i18n("Gmic filter");
KisNodeSP rootNode = m_view->image()->root();
KisInputOutputMapper mapper(m_view->image(), m_view->activeNode());
KisNodeListSP layers = mapper.inputNodes(m_inputMode);
m_gmicApplicator->setProperties(m_view->image(), rootNode, images, actionName, layers);
m_gmicApplicator->preview();
m_gmicApplicator->finish();
}
bool QMic::prepareCroppedImages(QByteArray *message, QRectF &rc, int inputMode)
{
m_view->image()->lock();
m_inputMode = (InputLayerMode)inputMode;
qDebug() << "prepareCroppedImages()" << QString::fromUtf8(*message) << rc << inputMode;
KisInputOutputMapper mapper(m_view->image(), m_view->activeNode());
KisNodeListSP nodes = mapper.inputNodes(m_inputMode);
if (nodes->isEmpty()) {
m_view->image()->unlock();
return false;
}
for (int i = 0; i < nodes->size(); ++i) {
KisNodeSP node = nodes->at(i);
if (node && node->paintDevice()) {
QRect cropRect;
KisSelectionSP selection = m_view->image()->globalSelection();
if (selection) {
cropRect = selection->selectedExactRect();
}
else {
cropRect = m_view->image()->bounds();
}
qDebug() << "Converting node" << node->name() << cropRect;
const QRectF mappedRect = KisAlgebra2D::mapToRect(cropRect).mapRect(rc);
const QRect resultRect = mappedRect.toAlignedRect();
QSharedMemory *m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString()));
m_sharedMemorySegments.append(m);
if (!m->create(resultRect.width() * resultRect.height() * 4 * sizeof(float))) { //buf.size())) {
qWarning() << "Could not create shared memory segment" << m->error() << m->errorString();
return false;
}
m->lock();
gmic_image<float> img;
img.assign(resultRect.width(), resultRect.height(), 1, 4);
img._data = reinterpret_cast<float*>(m->data());
KisQmicSimpleConvertor::convertToGmicImageFast(node->paintDevice(), &img, resultRect);
message->append(m->key().toUtf8());
m->unlock();
message->append(",");
message->append(node->name().toUtf8().toHex());
message->append(",");
message->append(QByteArray::number(resultRect.width()));
message->append(",");
message->append(QByteArray::number(resultRect.height()));
message->append("\n");
}
}
qDebug() << QString::fromUtf8(*message);
m_view->image()->unlock();
return true;
}
#include "QMic.moc"
diff --git a/plugins/extensions/resourcemanager/dlg_create_bundle.cpp b/plugins/extensions/resourcemanager/dlg_create_bundle.cpp
index daa5cc8abf..3f2ca1ca21 100644
--- a/plugins/extensions/resourcemanager/dlg_create_bundle.cpp
+++ b/plugins/extensions/resourcemanager/dlg_create_bundle.cpp
@@ -1,438 +1,438 @@
/*
* Copyright (c) 2014 Victor Lafon metabolic.ewilan@hotmail.fr
*
* 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 "dlg_create_bundle.h"
#include "ui_wdgdlgcreatebundle.h"
#include <QProcessEnvironment>
#include <QFileInfo>
#include <QMessageBox>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <QGridLayout>
#include <QTableWidget>
#include <QPainter>
#include <KisImportExportManager.h>
#include <KoDocumentInfo.h>
#include <KoFileDialog.h>
#include <kis_icon.h>
#include <resources/KoResource.h>
#include <KoResourceServer.h>
#include <KoResourceServerProvider.h>
#include <kis_resource_server_provider.h>
#include <kis_workspace_resource.h>
#include <brushengine/kis_paintop_preset.h>
#include <kis_brush_server.h>
#include <kis_config.h>
#include "KisResourceBundle.h"
#define ICON_SIZE 48
DlgCreateBundle::DlgCreateBundle(KisResourceBundle *bundle, QWidget *parent)
: KoDialog(parent)
, m_ui(new Ui::WdgDlgCreateBundle)
, m_bundle(bundle)
{
m_page = new QWidget();
m_ui->setupUi(m_page);
setMainWidget(m_page);
setFixedSize(m_page->sizeHint());
setButtons(Ok | Cancel);
setDefaultButton(Ok);
setButtonText(Ok, i18n("Save"));
connect(m_ui->bnSelectSaveLocation, SIGNAL(clicked()), SLOT(selectSaveLocation()));
KoDocumentInfo info;
info.updateParameters();
if (bundle) {
setCaption(i18n("Edit Resource Bundle"));
m_ui->lblSaveLocation->setText(QFileInfo(bundle->filename()).absolutePath());
m_ui->editBundleName->setText(bundle->name());
m_ui->editAuthor->setText(bundle->getMeta("author"));
m_ui->editEmail->setText(bundle->getMeta("email"));
m_ui->editLicense->setText(bundle->getMeta("license"));
m_ui->editWebsite->setText(bundle->getMeta("website"));
m_ui->editDescription->document()->setPlainText(bundle->getMeta("description"));
m_ui->lblPreview->setPixmap(QPixmap::fromImage(bundle->image().scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation)));
Q_FOREACH (const QString & resType, bundle->resourceTypes()) {
if (resType == "gradients") {
Q_FOREACH (const KoResource *res, bundle->resources(resType)) {
if (res) {
m_selectedGradients << res->shortFilename();
}
}
}
else if (resType == "patterns") {
Q_FOREACH (const KoResource *res, bundle->resources(resType)) {
if (res) {
m_selectedPatterns << res->shortFilename();
}
}
}
else if (resType == "brushes") {
Q_FOREACH (const KoResource *res, bundle->resources(resType)) {
if (res) {
m_selectedBrushes << res->shortFilename();
}
}
}
else if (resType == "palettes") {
Q_FOREACH (const KoResource *res, bundle->resources(resType)) {
if (res) {
m_selectedPalettes << res->shortFilename();
}
}
}
else if (resType == "workspaces") {
Q_FOREACH (const KoResource *res, bundle->resources(resType)) {
if (res) {
m_selectedWorkspaces << res->shortFilename();
}
}
}
else if (resType == "paintoppresets") {
Q_FOREACH (const KoResource *res, bundle->resources(resType)) {
if (res) {
m_selectedPresets << res->shortFilename();
}
}
}
}
}
else {
setCaption(i18n("Create Resource Bundle"));
KisConfig cfg;
m_ui->editAuthor->setText(cfg.readEntry<QString>("BundleAuthorName", info.authorInfo("creator")));
m_ui->editEmail->setText(cfg.readEntry<QString>("BundleAuthorEmail", info.authorInfo("email")));
m_ui->editWebsite->setText(cfg.readEntry<QString>("BundleWebsite", "http://"));
m_ui->editLicense->setText(cfg.readEntry<QString>("BundleLicense", "CC-BY-SA"));
- m_ui->lblSaveLocation->setText(cfg.readEntry<QString>("BundleExportLocation", QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation)));
+ m_ui->lblSaveLocation->setText(cfg.readEntry<QString>("BundleExportLocation", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)));
}
m_ui->bnAdd->setIcon(KisIconUtils::loadIcon("arrow-right"));
connect(m_ui->bnAdd, SIGNAL(clicked()), SLOT(addSelected()));
m_ui->bnRemove->setIcon(KisIconUtils::loadIcon("arrow-left"));
connect(m_ui->bnRemove, SIGNAL(clicked()), SLOT(removeSelected()));
m_ui->cmbResourceTypes->addItem(i18n("Brushes"), QString("brushes"));
m_ui->cmbResourceTypes->addItem(i18n("Brush Presets"), QString("presets"));
m_ui->cmbResourceTypes->addItem(i18n("Gradients"), QString("gradients"));
m_ui->cmbResourceTypes->addItem(i18n("Patterns"), QString("patterns"));
m_ui->cmbResourceTypes->addItem(i18n("Palettes"), QString("palettes"));
m_ui->cmbResourceTypes->addItem(i18n("Workspaces"), QString("workspaces"));
connect(m_ui->cmbResourceTypes, SIGNAL(activated(int)), SLOT(resourceTypeSelected(int)));
m_ui->tableAvailable->setIconSize(QSize(ICON_SIZE, ICON_SIZE));
m_ui->tableAvailable->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_ui->tableSelected->setIconSize(QSize(ICON_SIZE, ICON_SIZE));
m_ui->tableSelected->setSelectionMode(QAbstractItemView::ExtendedSelection);
connect(m_ui->bnGetPreview, SIGNAL(clicked()), SLOT(getPreviewImage()));
resourceTypeSelected(0);
}
DlgCreateBundle::~DlgCreateBundle()
{
delete m_ui;
}
QString DlgCreateBundle::bundleName() const
{
return m_ui->editBundleName->text().replace(" ", "_");
}
QString DlgCreateBundle::authorName() const
{
return m_ui->editAuthor->text();
}
QString DlgCreateBundle::email() const
{
return m_ui->editEmail->text();
}
QString DlgCreateBundle::website() const
{
return m_ui->editWebsite->text();
}
QString DlgCreateBundle::license() const
{
return m_ui->editLicense->text();
}
QString DlgCreateBundle::description() const
{
return m_ui->editDescription->document()->toPlainText();
}
QString DlgCreateBundle::saveLocation() const
{
return m_ui->lblSaveLocation->text();
}
QString DlgCreateBundle::previewImage() const
{
return m_previewImage;
}
void DlgCreateBundle::accept()
{
QString name = m_ui->editBundleName->text().remove(" ");
if (name.isEmpty()) {
m_ui->editBundleName->setStyleSheet(QString(" border: 1px solid red"));
QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The resource bundle name cannot be empty."));
return;
}
else {
QFileInfo fileInfo(m_ui->lblSaveLocation->text() + "/" + name + ".bundle");
if (fileInfo.exists() && !m_bundle) {
m_ui->editBundleName->setStyleSheet("border: 1px solid red");
QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("A bundle with this name already exists."));
return;
}
else {
if (!m_bundle) {
KisConfig cfg;
cfg.writeEntry<QString>("BunleExportLocation", m_ui->lblSaveLocation->text());
cfg.writeEntry<QString>("BundleAuthorName", m_ui->editAuthor->text());
cfg.writeEntry<QString>("BundleAuthorEmail", m_ui->editEmail->text());
cfg.writeEntry<QString>("BundleWebsite", m_ui->editWebsite->text());
cfg.writeEntry<QString>("BundleLicense", m_ui->editLicense->text());
}
KoDialog::accept();
}
}
}
void DlgCreateBundle::selectSaveLocation()
{
KoFileDialog dialog(this, KoFileDialog::OpenDirectory, "resourcebundlesavelocation");
dialog.setDefaultDir(m_ui->lblSaveLocation->text());
dialog.setCaption(i18n("Select a directory to save the bundle"));
QString location = dialog.filename();
m_ui->lblSaveLocation->setText(location);
}
void DlgCreateBundle::addSelected()
{
int row = m_ui->tableAvailable->currentRow();
Q_FOREACH (QListWidgetItem *item, m_ui->tableAvailable->selectedItems()) {
m_ui->tableSelected->addItem(m_ui->tableAvailable->takeItem(m_ui->tableAvailable->row(item)));
QString resourceType = m_ui->cmbResourceTypes->itemData(m_ui->cmbResourceTypes->currentIndex()).toString();
if (resourceType == "brushes") {
m_selectedBrushes.append(item->data(Qt::UserRole).toString());
}
else if (resourceType == "presets") {
m_selectedPresets.append(item->data(Qt::UserRole).toString());
}
else if (resourceType == "gradients") {
m_selectedGradients.append(item->data(Qt::UserRole).toString());
}
else if (resourceType == "patterns") {
m_selectedPatterns.append(item->data(Qt::UserRole).toString());
}
else if (resourceType == "palettes") {
m_selectedPalettes.append(item->data(Qt::UserRole).toString());
}
else if (resourceType == "workspaces") {
m_selectedWorkspaces.append(item->data(Qt::UserRole).toString());
}
}
m_ui->tableAvailable->setCurrentRow(row);
}
void DlgCreateBundle::removeSelected()
{
int row = m_ui->tableSelected->currentRow();
Q_FOREACH (QListWidgetItem *item, m_ui->tableSelected->selectedItems()) {
m_ui->tableAvailable->addItem(m_ui->tableSelected->takeItem(m_ui->tableSelected->row(item)));
QString resourceType = m_ui->cmbResourceTypes->itemData(m_ui->cmbResourceTypes->currentIndex()).toString();
if (resourceType == "brushes") {
m_selectedBrushes.removeAll(item->data(Qt::UserRole).toString());
}
else if (resourceType == "presets") {
m_selectedPresets.removeAll(item->data(Qt::UserRole).toString());
}
else if (resourceType == "gradients") {
m_selectedGradients.removeAll(item->data(Qt::UserRole).toString());
}
else if (resourceType == "patterns") {
m_selectedPatterns.removeAll(item->data(Qt::UserRole).toString());
}
else if (resourceType == "palettes") {
m_selectedPalettes.removeAll(item->data(Qt::UserRole).toString());
}
else if (resourceType == "workspaces") {
m_selectedWorkspaces.removeAll(item->data(Qt::UserRole).toString());
}
}
m_ui->tableSelected->setCurrentRow(row);
}
QPixmap imageToIcon(const QImage &img) {
QPixmap pixmap(ICON_SIZE, ICON_SIZE);
pixmap.fill();
QImage scaled = img.scaled(ICON_SIZE, ICON_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation);
int x = (ICON_SIZE - scaled.width()) / 2;
int y = (ICON_SIZE - scaled.height()) / 2;
QPainter gc(&pixmap);
gc.drawImage(x, y, scaled);
gc.end();
return pixmap;
}
void DlgCreateBundle::resourceTypeSelected(int idx)
{
QString resourceType = m_ui->cmbResourceTypes->itemData(idx).toString();
m_ui->tableAvailable->clear();
m_ui->tableSelected->clear();
if (resourceType == "brushes") {
KisBrushResourceServer *server = KisBrushServer::instance()->brushServer();
Q_FOREACH (KisBrushSP res, server->resources()) {
QListWidgetItem *item = new QListWidgetItem(imageToIcon(res->image()), res->name());
item->setData(Qt::UserRole, res->shortFilename());
if (m_selectedBrushes.contains(res->shortFilename())) {
m_ui->tableSelected->addItem(item);
}
else {
m_ui->tableAvailable->addItem(item);
}
}
}
else if (resourceType == "presets") {
KisPaintOpPresetResourceServer* server = KisResourceServerProvider::instance()->paintOpPresetServer();
Q_FOREACH (KisPaintOpPresetSP res, server->resources()) {
QListWidgetItem *item = new QListWidgetItem(imageToIcon(res->image()), res->name());
item->setData(Qt::UserRole, res->shortFilename());
if (m_selectedPresets.contains(res->shortFilename())) {
m_ui->tableSelected->addItem(item);
}
else {
m_ui->tableAvailable->addItem(item);
}
}
}
else if (resourceType == "gradients") {
KoResourceServer<KoAbstractGradient>* server = KoResourceServerProvider::instance()->gradientServer();
Q_FOREACH (KoResource *res, server->resources()) {
if (res->filename()!="Foreground to Transparent" && res->filename()!="Foreground to Background") {
//technically we should read from the file-name whether or not the file can be opened, but this works for now. The problem is making sure that bundle-resource know where they are stored.//
//dbgKrita<<res->filename();
QListWidgetItem *item = new QListWidgetItem(imageToIcon(res->image()), res->name());
item->setData(Qt::UserRole, res->shortFilename());
if (m_selectedGradients.contains(res->shortFilename())) {
m_ui->tableSelected->addItem(item);
}
else {
m_ui->tableAvailable->addItem(item);
}
}
}
}
else if (resourceType == "patterns") {
KoResourceServer<KoPattern>* server = KoResourceServerProvider::instance()->patternServer();
Q_FOREACH (KoResource *res, server->resources()) {
QListWidgetItem *item = new QListWidgetItem(imageToIcon(res->image()), res->name());
item->setData(Qt::UserRole, res->shortFilename());
if (m_selectedPatterns.contains(res->shortFilename())) {
m_ui->tableSelected->addItem(item);
}
else {
m_ui->tableAvailable->addItem(item);
}
}
}
else if (resourceType == "palettes") {
KoResourceServer<KoColorSet>* server = KoResourceServerProvider::instance()->paletteServer();
Q_FOREACH (KoResource *res, server->resources()) {
QListWidgetItem *item = new QListWidgetItem(imageToIcon(res->image()), res->name());
item->setData(Qt::UserRole, res->shortFilename());
if (m_selectedPalettes.contains(res->shortFilename())) {
m_ui->tableSelected->addItem(item);
}
else {
m_ui->tableAvailable->addItem(item);
}
}
}
else if (resourceType == "workspaces") {
KoResourceServer<KisWorkspaceResource>* server = KisResourceServerProvider::instance()->workspaceServer();
Q_FOREACH (KoResource *res, server->resources()) {
QListWidgetItem *item = new QListWidgetItem(imageToIcon(res->image()), res->name());
item->setData(Qt::UserRole, res->shortFilename());
if (m_selectedWorkspaces.contains(res->shortFilename())) {
m_ui->tableSelected->addItem(item);
}
else {
m_ui->tableAvailable->addItem(item);
}
}
}
}
void DlgCreateBundle::getPreviewImage()
{
KoFileDialog dialog(this, KoFileDialog::OpenFile, "BundlePreviewImage");
dialog.setCaption(i18n("Select file to use as bundle icon"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import));
m_previewImage = dialog.filename();
QImage img(m_previewImage);
img = img.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation);
m_ui->lblPreview->setPixmap(QPixmap::fromImage(img));
}
diff --git a/plugins/extensions/resourcemanager/resourcemanager.cpp b/plugins/extensions/resourcemanager/resourcemanager.cpp
index 67fc7091e3..fc121c8071 100644
--- a/plugins/extensions/resourcemanager/resourcemanager.cpp
+++ b/plugins/extensions/resourcemanager/resourcemanager.cpp
@@ -1,324 +1,324 @@
/*
* resourcemanager.cc -- Part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "resourcemanager.h"
#include <QDir>
#include <QFileInfo>
#include <QTimer>
#include <QThread>
#include <QMessageBox>
#include <QGlobalStatic>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <klocalizedstring.h>
#include <KoResourcePaths.h>
#include <kpluginfactory.h>
#include <KoFileDialog.h>
#include <resources/KoResource.h>
#include <KoResourceServer.h>
#include <KoResourceServerProvider.h>
#include <kis_debug.h>
#include <kis_action.h>
#include <KisViewManager.h>
#include <kis_resource_server_provider.h>
#include <kis_workspace_resource.h>
#include <brushengine/kis_paintop_preset.h>
#include <kis_brush_server.h>
#include <kis_paintop_settings.h>
#include "dlg_bundle_manager.h"
#include "dlg_create_bundle.h"
class ResourceManager::Private {
public:
Private()
{
brushServer = KisBrushServer::instance()->brushServer(false);
paintopServer = KisResourceServerProvider::instance()->paintOpPresetServer(false);
gradientServer = KoResourceServerProvider::instance()->gradientServer(false);
patternServer = KoResourceServerProvider::instance()->patternServer(false);
paletteServer = KoResourceServerProvider::instance()->paletteServer(false);
workspaceServer = KisResourceServerProvider::instance()->workspaceServer(false);
}
KisBrushResourceServer* brushServer;
KisPaintOpPresetResourceServer * paintopServer;
KoResourceServer<KoAbstractGradient>* gradientServer;
KoResourceServer<KoPattern> *patternServer;
KoResourceServer<KoColorSet>* paletteServer;
KoResourceServer<KisWorkspaceResource>* workspaceServer;
};
K_PLUGIN_FACTORY_WITH_JSON(ResourceManagerFactory, "kritaresourcemanager.json", registerPlugin<ResourceManager>();)
ResourceManager::ResourceManager(QObject *parent, const QVariantList &)
: KisViewPlugin(parent)
, d(new Private())
{
KisAction *action = new KisAction(i18n("Import Bundles..."), this);
addAction("import_bundles", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotImportBundles()));
action = new KisAction(i18n("Import Brushes..."), this);
addAction("import_brushes", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotImportBrushes()));
action = new KisAction(i18n("Import Gradients..."), this);
addAction("import_gradients", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotImportGradients()));
action = new KisAction(i18n("Import Palettes..."), this);
addAction("import_palettes", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotImportPalettes()));
action = new KisAction(i18n("Import Patterns..."), this);
addAction("import_patterns", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotImportPatterns()));
action = new KisAction(i18n("Import Presets..."), this);
addAction("import_presets", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotImportPresets()));
action = new KisAction(i18n("Import Workspaces..."), this);
addAction("import_workspaces", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotImportWorkspaces()));
action = new KisAction(i18n("Create Resource Bundle..."), this);
addAction("create_bundle", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotCreateBundle()));
action = new KisAction(i18n("Manage Resources..."), this);
addAction("manage_bundles", action);
connect(action, SIGNAL(triggered()), this, SLOT(slotManageBundles()));
}
ResourceManager::~ResourceManager()
{
}
void ResourceManager::slotCreateBundle()
{
DlgCreateBundle dlgCreateBundle;
if (dlgCreateBundle.exec() != QDialog::Accepted) {
return;
}
saveBundle(dlgCreateBundle);
}
KisResourceBundle *ResourceManager::saveBundle(const DlgCreateBundle &dlgCreateBundle)
{
QString bundlePath = dlgCreateBundle.saveLocation() + "/" + dlgCreateBundle.bundleName() + ".bundle";
KisResourceBundle *newBundle = new KisResourceBundle(bundlePath);
newBundle->addMeta("name", dlgCreateBundle.bundleName());
newBundle->addMeta("author", dlgCreateBundle.authorName());
newBundle->addMeta("email", dlgCreateBundle.email());
newBundle->addMeta("license", dlgCreateBundle.license());
newBundle->addMeta("website", dlgCreateBundle.website());
newBundle->addMeta("description", dlgCreateBundle.description());
newBundle->setThumbnail(dlgCreateBundle.previewImage());
QStringList res = dlgCreateBundle.selectedBrushes();
Q_FOREACH (const QString &r, res) {
KoResource *res = d->brushServer->resourceByFilename(r).data();
newBundle->addResource("kis_brushes", res->filename(), d->brushServer->assignedTagsList(res), res->md5());
}
res = dlgCreateBundle.selectedGradients();
Q_FOREACH (const QString &r, res) {
KoResource *res = d->gradientServer->resourceByFilename(r);
newBundle->addResource("ko_gradients", res->filename(), d->gradientServer->assignedTagsList(res), res->md5());
}
res = dlgCreateBundle.selectedPalettes();
Q_FOREACH (const QString &r, res) {
KoResource *res = d->paletteServer->resourceByFilename(r);
newBundle->addResource("ko_palettes", res->filename(), d->paletteServer->assignedTagsList(res), res->md5());
}
res = dlgCreateBundle.selectedPatterns();
Q_FOREACH (const QString &r, res) {
KoResource *res = d->patternServer->resourceByFilename(r);
newBundle->addResource("ko_patterns", res->filename(), d->patternServer->assignedTagsList(res), res->md5());
}
res = dlgCreateBundle.selectedPresets();
Q_FOREACH (const QString &r, res) {
KisPaintOpPresetSP preset = d->paintopServer->resourceByFilename(r);
KoResource *res = preset.data();
newBundle->addResource("kis_paintoppresets", res->filename(), d->paintopServer->assignedTagsList(res), res->md5());
KisPaintOpSettingsSP settings = preset->settings();
if (settings->hasProperty("requiredBrushFile")) {
QString brushFile = settings->getString("requiredBrushFile");
KisBrush *brush = d->brushServer->resourceByFilename(brushFile).data();
if (brush) {
newBundle->addResource("kis_brushes", brushFile, d->brushServer->assignedTagsList(brush), brush->md5());
}
else {
qWarning() << "There is no brush with name" << brushFile;
}
}
}
res = dlgCreateBundle.selectedWorkspaces();
Q_FOREACH (const QString &r, res) {
KoResource *res = d->workspaceServer->resourceByFilename(r);
newBundle->addResource("kis_workspaces", res->filename(), d->workspaceServer->assignedTagsList(res), res->md5());
}
newBundle->addMeta("fileName", bundlePath);
newBundle->addMeta("created", QDate::currentDate().toString("dd/MM/yyyy"));
if (!newBundle->save()) {
QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("Could not create the new bundle."));
}
else {
newBundle->setValid(true);
if (QDir(KisResourceServerProvider::instance()->resourceBundleServer()->saveLocation()) != QDir(QFileInfo(bundlePath).path())) {
newBundle->setFilename(KisResourceServerProvider::instance()->resourceBundleServer()->saveLocation() + "/" + dlgCreateBundle.bundleName() + ".bundle");
}
if (KisResourceServerProvider::instance()->resourceBundleServer()->resourceByName(newBundle->name())) {
KisResourceServerProvider::instance()->resourceBundleServer()->removeResourceFromServer(
KisResourceServerProvider::instance()->resourceBundleServer()->resourceByName(newBundle->name()));
}
KisResourceServerProvider::instance()->resourceBundleServer()->addResource(newBundle, true);
newBundle->load();
}
return newBundle;
}
void ResourceManager::slotManageBundles()
{
DlgBundleManager* dlg = new DlgBundleManager(this, m_view->actionManager());
if (dlg->exec() != QDialog::Accepted) {
return;
}
}
QStringList ResourceManager::importResources(const QString &title, const QStringList &mimes) const
{
KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::OpenFiles, "krita_resources");
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
dialog.setCaption(title);
dialog.setMimeTypeFilters(mimes);
return dialog.filenames();
}
void ResourceManager::slotImportBrushes()
{
QStringList resources = importResources(i18n("Import Brushes"), QStringList() << "image/x-gimp-brush"
<< "image/x-gimp-x-gimp-brush-animated"
<< "image/x-adobe-brushlibrary"
<< "image/png"
<< "image/svg+xml");
Q_FOREACH (const QString &res, resources) {
d->brushServer->importResourceFile(res);
}
}
void ResourceManager::slotImportPresets()
{
QStringList resources = importResources(i18n("Import Presets"), QStringList() << "application/x-krita-paintoppreset");
Q_FOREACH (const QString &res, resources) {
d->paintopServer->importResourceFile(res);
}
}
void ResourceManager::slotImportGradients()
{
QStringList resources = importResources(i18n("Import Gradients"), QStringList() << "image/svg+xml"
<< "application/x-gimp-gradient"
<< "applicaition/x-karbon-gradient");
Q_FOREACH (const QString &res, resources) {
d->gradientServer->importResourceFile(res);
}
}
void ResourceManager::slotImportBundles()
{
QStringList resources = importResources(i18n("Import Bundles"), QStringList() << "application/x-krita-bundle");
Q_FOREACH (const QString &res, resources) {
KisResourceBundle *bundle = KisResourceServerProvider::instance()->resourceBundleServer()->createResource(res);
bundle->load();
if (bundle->valid()) {
if (!bundle->install()) {
QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Could not install the resources for bundle %1.").arg(res));
}
}
else {
QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Could not load bundle %1.").arg(res));
}
QFileInfo fi(res);
QString newFilename = KisResourceServerProvider::instance()->resourceBundleServer()->saveLocation() + fi.baseName() + bundle->defaultFileExtension();
QFileInfo fileInfo(newFilename);
int i = 1;
while (fileInfo.exists()) {
fileInfo.setFile(KisResourceServerProvider::instance()->resourceBundleServer()->saveLocation() + fi.baseName() + QString("%1").arg(i) + bundle->defaultFileExtension());
i++;
}
bundle->setFilename(fileInfo.filePath());
QFile::copy(res, newFilename);
KisResourceServerProvider::instance()->resourceBundleServer()->addResource(bundle, false);
}
}
void ResourceManager::slotImportPatterns()
{
QStringList resources = importResources(i18n("Import Patterns"), QStringList() << "image/png"
<< "image/svg+xml"
<< "application/x-gimp-pattern"
<< "image/jpeg"
<< "image/tiff"
<< "image/bmp"
<< "image/xpg");
Q_FOREACH (const QString &res, resources) {
d->patternServer->importResourceFile(res);
}
}
void ResourceManager::slotImportPalettes()
{
QStringList resources = importResources(i18n("Import Palettes"), QStringList() << "image/x-gimp-color-palette");
Q_FOREACH (const QString &res, resources) {
d->paletteServer->importResourceFile(res);
}
}
void ResourceManager::slotImportWorkspaces()
{
QStringList resources = importResources(i18n("Import Workspaces"), QStringList() << "application/x-krita-workspace");
Q_FOREACH (const QString &res, resources) {
d->workspaceServer->importResourceFile(res);
}
}
#include "resourcemanager.moc"
diff --git a/plugins/extensions/separate_channels/kis_channel_separator.cc b/plugins/extensions/separate_channels/kis_channel_separator.cc
index 32859b9ead..e5b88d88d0 100644
--- a/plugins/extensions/separate_channels/kis_channel_separator.cc
+++ b/plugins/extensions/separate_channels/kis_channel_separator.cc
@@ -1,272 +1,272 @@
/*
* This file is part of Krita
*
* Copyright (c) 2005 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
*
* ported from Gimp, Copyright (C) 1997 Eiichi Takamori <taka@ma1.seikyou.ne.jp>
* original pixelize.c for GIMP 0.54 by Tracy Scott
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_channel_separator.h"
#include <limits.h>
#include <stdlib.h>
#include <vector>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <KisImportExportManager.h>
#include <KoUpdater.h>
#include <KoFileDialog.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoChannelInfo.h>
#include <KoColorModelStandardIds.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
#include <kis_transaction.h>
#include <kis_undo_adapter.h>
#include <kis_global.h>
#include <kis_types.h>
#include "kis_iterator_ng.h"
#include <KisPart.h>
#include <KisViewManager.h>
#include <kis_paint_device.h>
#include <kis_node_manager.h>
#include <kis_node_commands_adapter.h>
#include <KisMimeDatabase.h>
KisChannelSeparator::KisChannelSeparator(KisViewManager * view)
: m_view(view)
{
}
void KisChannelSeparator::separate(KoUpdater * progressUpdater, enumSepAlphaOptions alphaOps, enumSepSource sourceOps, enumSepOutput outputOps, bool downscale, bool toColor)
{
KisImageSP image = m_view->image();
if (!image) return;
KisPaintDeviceSP src;
// Use the flattened image, if required
switch (sourceOps) {
case ALL_LAYERS:
// the content will be locked later
src = image->projection();
break;
case CURRENT_LAYER:
src = m_view->activeDevice();
break;
default:
break;
}
if (!src) return;
progressUpdater->setProgress(1);
const KoColorSpace * dstCs = 0;
quint32 numberOfChannels = src->channelCount();
const KoColorSpace * srcCs = src->colorSpace();
QList<KoChannelInfo *> channels = srcCs->channels();
vKisPaintDeviceSP layers;
QList<KoChannelInfo *>::const_iterator begin = channels.constBegin();
QList<KoChannelInfo *>::const_iterator end = channels.constEnd();
QRect rect = src->exactBounds();
image->lock();
int i = 0;
for (QList<KoChannelInfo *>::const_iterator it = begin; it != end; ++it) {
KoChannelInfo * ch = (*it);
if (ch->channelType() == KoChannelInfo::ALPHA && alphaOps != CREATE_ALPHA_SEPARATION) {
continue;
}
qint32 channelSize = ch->size();
qint32 channelPos = ch->pos();
qint32 destSize = 1;
KisPaintDeviceSP dev;
if (toColor) {
// We don't downscale if we separate to color channels
dev = new KisPaintDevice(srcCs);
} else {
if (channelSize == 1 || downscale) {
dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), 0));
} else {
dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), 0));
destSize = 2;
}
}
dstCs = dev->colorSpace();
layers.push_back(dev);
KisHLineConstIteratorSP srcIt = src->createHLineConstIteratorNG(rect.x(), rect.y(), rect.width());
KisHLineIteratorSP dstIt = dev->createHLineIteratorNG(rect.x(), rect.y(), rect.width());
for (qint32 row = 0; row < rect.height(); ++row) {
do {
if (toColor) {
dstCs->singleChannelPixel(dstIt->rawData(), srcIt->oldRawData(), channelPos);
if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) {
//dstCs->setAlpha(dstIt->rawData(), srcIt->oldRawData()[srcAlphaPos], 1);
dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1);
} else {
dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1);
}
} else {
// To grayscale
// Decide whether we need downscaling
if (channelSize == 1 && destSize == 1) {
// Both 8-bit channels
dstIt->rawData()[0] = srcIt->oldRawData()[channelPos];
if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) {
dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1);
} else {
dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1);
}
} else if (channelSize == 2 && destSize == 2) {
// Both 16-bit
dstIt->rawData()[0] = srcIt->oldRawData()[channelPos];
dstIt->rawData()[1] = srcIt->oldRawData()[channelPos + 1];
if (alphaOps == COPY_ALPHA_TO_SEPARATIONS) {
dstCs->setOpacity(dstIt->rawData(), srcCs->opacityU8(srcIt->oldRawData()), 1);
} else {
dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1);
}
} else if (channelSize != 1 && destSize == 1) {
// Downscale
memset(dstIt->rawData(), srcCs->scaleToU8(srcIt->oldRawData(), channelPos), 1);
// XXX: Do alpha
dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1);
} else if (channelSize != 2 && destSize == 2) {
// Upscale
dstIt->rawData()[0] = srcCs->scaleToU8(srcIt->oldRawData(), channelPos);
// XXX: Do alpha
dstCs->setOpacity(dstIt->rawData(), OPACITY_OPAQUE_U8, 1);
}
}
} while (dstIt->nextPixel() && srcIt->nextPixel());
dstIt->nextRow();
srcIt->nextRow();
}
++i;
progressUpdater->setProgress((i * 100) / numberOfChannels);
if (progressUpdater->interrupted()) {
break;
}
}
vKisPaintDeviceSP_it deviceIt = layers.begin();
progressUpdater->setProgress(100);
if (!progressUpdater->interrupted()) {
KisUndoAdapter * undo = image->undoAdapter();
if (outputOps == TO_LAYERS) {
undo->beginMacro(kundo2_i18n("Separate Image"));
}
// Flatten the image if required
switch (sourceOps) {
case(ALL_LAYERS):
image->flatten();
break;
default:
break;
}
KisNodeCommandsAdapter adapter(m_view);
for (QList<KoChannelInfo *>::const_iterator it = begin; it != end; ++it) {
KoChannelInfo * ch = (*it);
if (ch->channelType() == KoChannelInfo::ALPHA && alphaOps != CREATE_ALPHA_SEPARATION) {
// Don't make an separate separation of the alpha channel if the user didn't ask for it.
continue;
}
if (outputOps == TO_LAYERS) {
KisPaintLayerSP l = KisPaintLayerSP(new KisPaintLayer(image.data(), ch->name(), OPACITY_OPAQUE_U8, *deviceIt));
adapter.addNode(l.data(), image->rootLayer(), 0);
}
else {
KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::SaveFile, "OpenDocument");
dialog.setCaption(i18n("Export Layer") + '(' + ch->name() + ')');
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Export));
QUrl url = QUrl::fromUserInput(dialog.filename());
if (url.isEmpty())
return;
const QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile());
KisPaintLayerSP l = KisPaintLayerSP(new KisPaintLayer(image.data(), ch->name(), OPACITY_OPAQUE_U8, *deviceIt));
QRect r = l->exactBounds();
KisDocument *d = KisPart::instance()->createDocument();
KisImageWSP dst = KisImageWSP(new KisImage(d->createUndoStore(), r.width(), r.height(), (*deviceIt)->colorSpace(), l->name()));
d->setCurrentImage(dst);
dst->addNode(l->clone().data(), dst->rootLayer());
d->exportDocumentSync(url, mimeType.toLatin1());
delete d;
}
++deviceIt;
}
if (outputOps == TO_LAYERS) {
undo->endMacro();
}
image->unlock();
image->setModified();
}
}
diff --git a/plugins/filters/colorsfilters/kis_brightness_contrast_filter.cpp b/plugins/filters/colorsfilters/kis_brightness_contrast_filter.cpp
index d6c6d04a1a..c8f9da8d15 100644
--- a/plugins/filters/colorsfilters/kis_brightness_contrast_filter.cpp
+++ b/plugins/filters/colorsfilters/kis_brightness_contrast_filter.cpp
@@ -1,302 +1,302 @@
/*
* This file is part of Krita
*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
*
* 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_brightness_contrast_filter.h"
#include <math.h>
#include <klocalizedstring.h>
#include <QLayout>
#include <QPixmap>
#include <QPainter>
#include <QLabel>
#include <QDomDocument>
#include <QString>
#include <QStringList>
#include <QPushButton>
#include <QHBoxLayout>
#include <QColor>
#include "KoBasicHistogramProducers.h"
#include "KoColorSpace.h"
#include "KoColorTransformation.h"
#include "KoCompositeOp.h"
#include <KoToolManager.h>
#include "kis_config_widget.h"
#include "kis_bookmarked_configuration_manager.h"
#include "kis_paint_device.h"
#include "widgets/kis_curve_widget.h"
#include "kis_histogram.h"
#include "kis_painter.h"
#include <KisViewManager.h>
#include <KoColor.h>
#include <kis_canvas_resource_provider.h>
KisBrightnessContrastFilterConfiguration::KisBrightnessContrastFilterConfiguration()
: KisColorTransformationConfiguration("brightnesscontrast", 1)
{
}
KisBrightnessContrastFilterConfiguration::~KisBrightnessContrastFilterConfiguration()
{
}
void KisBrightnessContrastFilterConfiguration::fromLegacyXML(const QDomElement& root)
{
fromXML(root);
}
void KisBrightnessContrastFilterConfiguration::updateTransfer()
{
m_transfer = m_curve.uint16Transfer();
}
void KisBrightnessContrastFilterConfiguration::setCurve(const KisCubicCurve &curve)
{
m_curve = curve;
updateTransfer();
}
const QVector<quint16>& KisBrightnessContrastFilterConfiguration::transfer() const
{
return m_transfer;
}
const KisCubicCurve& KisBrightnessContrastFilterConfiguration::curve() const
{
return m_curve;
}
void KisBrightnessContrastFilterConfiguration::fromXML(const QDomElement& root)
{
KisCubicCurve curve;
int version;
version = root.attribute("version").toInt();
QDomElement e = root.firstChild().toElement();
QString attributeName;
while (!e.isNull()) {
if ((attributeName = e.attribute("name")) != "nTransfers") {
QRegExp rx("curve(\\d+)");
if (rx.indexIn(attributeName, 0) != -1) {
quint16 index = rx.cap(1).toUShort();
if (index == 0 && !e.text().isEmpty()) {
/**
* We are going to use first curve only
*/
curve.fromString(e.text());
}
}
}
e = e.nextSiblingElement();
}
setVersion(version);
setCurve(curve);
}
/**
* Inherited from KisPropertiesConfiguration
*/
//void KisPerChannelFilterConfiguration::fromXML(const QString& s)
void KisBrightnessContrastFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const
{
/**
* <params version=1>
* <param name="nTransfers">1</param>
* <param name="curve0">0,0;0.5,0.5;1,1;</param>
* </params>
*/
/* This is a constant for Brightness/Contranst filter */
const qint32 numTransfers = 1;
root.setAttribute("version", version());
QDomElement t = doc.createElement("param");
QDomText text = doc.createTextNode(QString::number(numTransfers));
t.setAttribute("name", "nTransfers");
t.appendChild(text);
root.appendChild(t);
t = doc.createElement("param");
t.setAttribute("name", "curve0");
text = doc.createTextNode(m_curve.toString());
t.appendChild(text);
root.appendChild(t);
}
/**
* Inherited from KisPropertiesConfiguration
*/
//QString KisPerChannelFilterConfiguration::toXML()
KisBrightnessContrastFilter::KisBrightnessContrastFilter()
: KisColorTransformationFilter(id(), categoryAdjust(), i18n("&Brightness/Contrast curve..."))
{
setSupportsPainting(false);
setColorSpaceIndependence(TO_LAB16);
}
KisConfigWidget * KisBrightnessContrastFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const
{
return new KisBrightnessContrastConfigWidget(parent, dev);
}
KisFilterConfigurationSP KisBrightnessContrastFilter::factoryConfiguration()
const
{
return new KisBrightnessContrastFilterConfiguration();
}
KoColorTransformation* KisBrightnessContrastFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const
{
const KisBrightnessContrastFilterConfiguration* configBC = dynamic_cast<const KisBrightnessContrastFilterConfiguration*>(config.data());
if (!configBC) return 0;
KoColorTransformation * adjustment = cs->createBrightnessContrastAdjustment(configBC->transfer().constData());
return adjustment;
}
-KisBrightnessContrastConfigWidget::KisBrightnessContrastConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WFlags f)
+KisBrightnessContrastConfigWidget::KisBrightnessContrastConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f)
: KisConfigWidget(parent, f)
{
int i;
int height;
m_page = new WdgBrightnessContrast(this);
QHBoxLayout * l = new QHBoxLayout(this);
Q_CHECK_PTR(l);
//Hide these buttons and labels as they are not implemented in 1.5
m_page->pb_more_contrast->hide();
m_page->pb_less_contrast->hide();
m_page->pb_more_brightness->hide();
m_page->pb_less_brightness->hide();
m_page->textLabelBrightness->hide();
m_page->textLabelContrast->hide();
l->addWidget(m_page, 1, Qt::AlignTop);
l->setContentsMargins(0,0,0,0);
height = 256;
connect(m_page->curveWidget, SIGNAL(modified()), SIGNAL(sigConfigurationItemChanged()));
// Create the horizontal gradient label
QPixmap hgradientpix(256, 1);
QPainter hgp(&hgradientpix);
hgp.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine));
for (i = 0; i < 256; ++i) {
hgp.setPen(QColor(i, i, i));
hgp.drawPoint(i, 0);
}
m_page->hgradient->setPixmap(hgradientpix);
// Create the vertical gradient label
QPixmap vgradientpix(1, 256);
QPainter vgp(&vgradientpix);
vgp.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine));
for (i = 0; i < 256; ++i) {
vgp.setPen(QColor(i, i, i));
vgp.drawPoint(0, 255 - i);
}
m_page->vgradient->setPixmap(vgradientpix);
KoHistogramProducer *producer = new KoGenericLabHistogramProducer();
KisHistogram histogram(dev, dev->exactBounds(), producer, LINEAR);
QPalette appPalette = QApplication::palette();
QPixmap pix(256, height);
pix.fill(QColor(appPalette.color(QPalette::Base)));
QPainter p(&pix);
p.setPen(QPen(Qt::gray, 1, Qt::SolidLine));
double highest = (double)histogram.calculations().getHighest();
qint32 bins = histogram.producer()->numberOfBins();
if (histogram.getHistogramType() == LINEAR) {
double factor = (double)height / highest;
for (i = 0; i < bins; ++i) {
p.drawLine(i, height, i, height - int(histogram.getValue(i) * factor));
}
} else {
double factor = (double)height / (double)log(highest);
for (i = 0; i < bins; ++i) {
p.drawLine(i, height, i, height - int(log((double)histogram.getValue(i)) * factor));
}
}
m_page->curveWidget->setPixmap(pix);
m_page->curveWidget->setBasePixmap(pix);
}
KisBrightnessContrastConfigWidget::~KisBrightnessContrastConfigWidget()
{
KoToolManager::instance()->switchBackRequested();
delete m_page;
}
KisPropertiesConfigurationSP KisBrightnessContrastConfigWidget::configuration() const
{
KisBrightnessContrastFilterConfiguration * cfg = new KisBrightnessContrastFilterConfiguration();
cfg->setCurve(m_page->curveWidget->curve());
return cfg;
}
void KisBrightnessContrastConfigWidget::slotDrawLine(const KoColor &color)
{
QColor colorNew = color.toQColor();
int i = (colorNew.red() + colorNew.green() + colorNew.blue())/3 ;
QPixmap pix = m_page->curveWidget->getBasePixmap();
QPainter p(&pix);
p.setPen(QPen(Qt::black, 1, Qt::SolidLine));
p.drawLine(i,0,i,255);
QString label = "x:";
label.insert(2,QString(QString::number(i)));
p.drawText(i,250,label);
m_page->curveWidget->setPixmap(pix);
}
void KisBrightnessContrastConfigWidget::setView(KisViewManager *view)
{
connect(view->resourceProvider(), SIGNAL(sigFGColorChanged(const KoColor&)), this, SLOT(slotDrawLine(const KoColor&)));
KoToolManager::instance()->switchToolTemporaryRequested("KritaSelected/KisToolColorPicker");
}
void KisBrightnessContrastConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config)
{
const KisBrightnessContrastFilterConfiguration * cfg = dynamic_cast<const KisBrightnessContrastFilterConfiguration *>(config.data());
Q_ASSERT(cfg);
m_page->curveWidget->setCurve(cfg->curve());
}
diff --git a/plugins/filters/colorsfilters/kis_brightness_contrast_filter.h b/plugins/filters/colorsfilters/kis_brightness_contrast_filter.h
index b92dddac7c..955d9cfc7c 100644
--- a/plugins/filters/colorsfilters/kis_brightness_contrast_filter.h
+++ b/plugins/filters/colorsfilters/kis_brightness_contrast_filter.h
@@ -1,116 +1,116 @@
/*
* This file is part of Krita
*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
*
* 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_BRIGHTNESS_CONTRAST_FILTER_H_
#define _KIS_BRIGHTNESS_CONTRAST_FILTER_H_
#include <QList>
#include "filter/kis_color_transformation_filter.h"
#include "kis_config_widget.h"
#include "ui_wdg_brightness_contrast.h"
#include <filter/kis_color_transformation_configuration.h>
#include <kis_selection.h>
#include <kis_paint_device.h>
#include <kis_processing_information.h>
#include <KoColor.h>
class QWidget;
class KoColorTransformation;
class WdgBrightnessContrast : public QWidget, public Ui::WdgBrightnessContrast
{
Q_OBJECT
public:
WdgBrightnessContrast(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class KisBrightnessContrastFilterConfiguration : public KisColorTransformationConfiguration
{
public:
using KisFilterConfiguration::fromXML;
using KisFilterConfiguration::toXML;
using KisFilterConfiguration::fromLegacyXML;
void fromLegacyXML(const QDomElement& root) override;
void fromXML(const QDomElement& e) override;
void toXML(QDomDocument& doc, QDomElement& root) const override;
KisBrightnessContrastFilterConfiguration();
~KisBrightnessContrastFilterConfiguration() override;
void setCurve(const KisCubicCurve &curve) override;
const QVector<quint16>& transfer() const;
const KisCubicCurve& curve() const override;
private:
void updateTransfer();
private:
KisCubicCurve m_curve;
QVector<quint16> m_transfer;
};
/**
* This class affect Intensity Y of the image
*/
class KisBrightnessContrastFilter : public KisColorTransformationFilter
{
public:
KisBrightnessContrastFilter();
public:
KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override;
static inline KoID id() {
return KoID("brightnesscontrast", i18n("Brightness / Contrast"));
}
KisFilterConfigurationSP factoryConfiguration() const override;
KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const override;
};
class KisBrightnessContrastConfigWidget : public KisConfigWidget
{
Q_OBJECT
public:
- KisBrightnessContrastConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WFlags f = 0);
+ KisBrightnessContrastConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f = 0);
~KisBrightnessContrastConfigWidget() override;
KisPropertiesConfigurationSP configuration() const override;
void setConfiguration(const KisPropertiesConfigurationSP config) override;
WdgBrightnessContrast * m_page;
void setView(KisViewManager *view) override;
public Q_SLOTS:
void slotDrawLine(const KoColor &color);
};
#endif
diff --git a/plugins/filters/colorsfilters/kis_desaturate_filter.cpp b/plugins/filters/colorsfilters/kis_desaturate_filter.cpp
index 30a48acdbf..6e3821fbae 100644
--- a/plugins/filters/colorsfilters/kis_desaturate_filter.cpp
+++ b/plugins/filters/colorsfilters/kis_desaturate_filter.cpp
@@ -1,121 +1,121 @@
/*
* This file is part of Krita
*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
*
* 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_desaturate_filter.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <QSlider>
#include <QPoint>
#include <QColor>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include "KoBasicHistogramProducers.h"
#include <KoColorSpace.h>
#include <KoColorTransformation.h>
#include <filter/kis_color_transformation_configuration.h>
#include <kis_paint_device.h>
#include <kis_processing_information.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_global.h>
#include <kis_types.h>
#include <kis_selection.h>
#include "filter/kis_filter_registry.h"
#include <kis_painter.h>
#include <KoColorSpaceConstants.h>
#include <KoCompositeOp.h>
#include <kis_iterator_ng.h>
KisDesaturateFilter::KisDesaturateFilter()
: KisColorTransformationFilter(id(), categoryAdjust(), i18n("&Desaturate..."))
{
setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U));
setSupportsPainting(true);
}
KisDesaturateFilter::~KisDesaturateFilter()
{
}
KisConfigWidget *KisDesaturateFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const
{
Q_UNUSED(dev);
return new KisDesaturateConfigWidget(parent);
}
KoColorTransformation* KisDesaturateFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const
{
QHash<QString, QVariant> params;
if (config) {
params["type"] = config->getInt("type", 0);
}
return cs->createColorTransformation("desaturate_adjustment", params);
}
KisFilterConfigurationSP KisDesaturateFilter::factoryConfiguration() const
{
KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration(id().id(), 1);
config->setProperty("type", 0);
return config;
}
-KisDesaturateConfigWidget::KisDesaturateConfigWidget(QWidget * parent, Qt::WFlags f) : KisConfigWidget(parent, f)
+KisDesaturateConfigWidget::KisDesaturateConfigWidget(QWidget * parent, Qt::WindowFlags f) : KisConfigWidget(parent, f)
{
m_page = new Ui_WdgDesaturate();
m_page->setupUi(this);
m_group = new QButtonGroup(this);
m_group->addButton(m_page->radioLightness, 0);
m_group->addButton(m_page->radioLuminosityBT709, 1);
m_group->addButton(m_page->radioLuminosityBT601, 2);
m_group->addButton(m_page->radioAverage, 3);
m_group->addButton(m_page->radioMin, 4);
m_group->addButton(m_page->radioMax, 5);
m_group->setExclusive(true);
connect(m_group, SIGNAL(buttonClicked(int)), SIGNAL(sigConfigurationItemChanged()));
}
KisDesaturateConfigWidget::~KisDesaturateConfigWidget()
{
delete m_page;
}
KisPropertiesConfigurationSP KisDesaturateConfigWidget::configuration() const
{
KisColorTransformationConfigurationSP c = new KisColorTransformationConfiguration(KisDesaturateFilter::id().id(), 0);
c->setProperty("type", m_group->checkedId());
return c;
}
void KisDesaturateConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config)
{
m_group->button(config->getInt("type", 0))->setChecked(true);
emit sigConfigurationItemChanged();
}
diff --git a/plugins/filters/colorsfilters/kis_desaturate_filter.h b/plugins/filters/colorsfilters/kis_desaturate_filter.h
index a2e058052b..59bf312011 100644
--- a/plugins/filters/colorsfilters/kis_desaturate_filter.h
+++ b/plugins/filters/colorsfilters/kis_desaturate_filter.h
@@ -1,70 +1,70 @@
/*
* This file is part of Krita
*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
*
* 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_DESATURATE_FILTER_H
#define KIS_DESATURATE_FILTER_H
#include <QObject>
#include <QVariant>
#include <kis_config_widget.h>
#include <filter/kis_color_transformation_filter.h>
#include "ui_wdg_desaturate.h"
class KoColorSpace;
class KoColorTransformation;
class KisDesaturateFilter : public KisColorTransformationFilter
{
public:
KisDesaturateFilter();
~KisDesaturateFilter() override;
public:
KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const override;
KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override;
static inline KoID id() {
return KoID("desaturate", i18n("Desaturate"));
}
KisFilterConfigurationSP factoryConfiguration() const override;
};
class KisDesaturateConfigWidget : public KisConfigWidget
{
Q_OBJECT
public:
- KisDesaturateConfigWidget(QWidget * parent, Qt::WFlags f = 0);
+ KisDesaturateConfigWidget(QWidget * parent, Qt::WindowFlags f = 0);
~KisDesaturateConfigWidget() override;
KisPropertiesConfigurationSP configuration() const override;
void setConfiguration(const KisPropertiesConfigurationSP config) override;
Ui_WdgDesaturate *m_page;
QButtonGroup *m_group;
};
#endif
diff --git a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp
index f197ce3eb9..44277ed32c 100644
--- a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp
+++ b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.cpp
@@ -1,207 +1,207 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; version 2
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_hsv_adjustment_filter.h"
#include <filter/kis_color_transformation_configuration.h>
#include <kis_selection.h>
#include <kis_paint_device.h>
#include <kis_processing_information.h>
#include <KoColorProfile.h>
namespace {
struct SliderConfig {
QString m_text;
int m_minimum;
int m_maximum;
inline void apply(QSpinBox* spinBox, QSlider* slider, QLabel* label) const
{
label->setText(m_text);
slider->setMinimum(m_minimum);
slider->setMaximum(m_maximum);
spinBox->setMinimum(m_minimum);
spinBox->setMaximum(m_maximum);
int sliderValue = slider->value();
if (sliderValue < m_minimum || sliderValue > m_maximum) {
slider->setValue((m_minimum + m_maximum) / 2);
}
}
inline double normalize(int value) const
{
return (double)value / (double)m_maximum;
}
inline void resetSlider( QSlider* slider) const
{
slider->setValue(0);
}
};
struct WidgetSlidersConfig {
SliderConfig m_sliders[3];
};
#define PERCENT_FIELD_REL(x) {x, -100, 100}
#define PERCENT_FIELD_ABS(x) {x, 0, 100}
#define DEGREES_FIELD_REL(x) {x, -180, 180}
#define DEGREES_FIELD_ABS(x) {x, 0, 360}
#define HSX_CONFIGS(x) { \
{ {DEGREES_FIELD_REL(i18n("Hue:")), PERCENT_FIELD_REL(i18n("Saturation:")), PERCENT_FIELD_REL(x)} }, \
{ {DEGREES_FIELD_ABS(i18n("Hue:")), PERCENT_FIELD_ABS(i18n("Saturation:")), PERCENT_FIELD_REL(x)} } \
}
const WidgetSlidersConfig WIDGET_CONFIGS[][2] = {
// Hue/Saturation/Value
HSX_CONFIGS(i18n("Value:")),
// Hue/Saturation/Lightness
HSX_CONFIGS(i18n("Lightness:")),
// Hue/Saturation/Intensity
HSX_CONFIGS(i18n("Intensity:")),
// Hue/Saturation/Luminosity
HSX_CONFIGS(i18n("Luma:")),
// Blue Chroma/Red Chroma/Luma
{{ {PERCENT_FIELD_REL(i18n("Yellow-Blue:")), PERCENT_FIELD_REL(i18n("Green-Red:")), PERCENT_FIELD_REL(i18n("Luma:"))} },
{ {PERCENT_FIELD_ABS(i18n("Yellow-Blue:")), PERCENT_FIELD_ABS(i18n("Green-Red:")), PERCENT_FIELD_REL(i18n("Luma:"))} }}
};
inline const WidgetSlidersConfig& getCurrentWidgetConfig(int type, bool colorize) {
return WIDGET_CONFIGS[type][colorize ? 1 : 0];
}
}
KisHSVAdjustmentFilter::KisHSVAdjustmentFilter()
: KisColorTransformationFilter(id(), categoryAdjust(), i18n("&HSV Adjustment..."))
{
setShortcut(QKeySequence(Qt::CTRL + Qt::Key_U));
setSupportsPainting(true);
}
KisConfigWidget * KisHSVAdjustmentFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const
{
Q_UNUSED(dev);
return new KisHSVConfigWidget(parent);
}
KoColorTransformation* KisHSVAdjustmentFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const
{
QHash<QString, QVariant> params;
if (config) {
int type = config->getInt("type", 1);
bool colorize = config->getBool("colorize", false);
const WidgetSlidersConfig& widgetConfig = getCurrentWidgetConfig(type, colorize);
params["h"] = widgetConfig.m_sliders[0].normalize(config->getInt("h", 0));
params["s"] = widgetConfig.m_sliders[1].normalize(config->getInt("s", 0));
params["v"] = widgetConfig.m_sliders[2].normalize(config->getInt("v", 0));
params["type"] = type;
params["colorize"] = colorize;
params["lumaRed"] = cs->lumaCoefficients()[0];
params["lumaGreen"] = cs->lumaCoefficients()[1];
params["lumaBlue"] = cs->lumaCoefficients()[2];
}
return cs->createColorTransformation("hsv_adjustment", params);
}
KisFilterConfigurationSP KisHSVAdjustmentFilter::factoryConfiguration() const
{
KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration(id().id(), 1);
config->setProperty("h", 0);
config->setProperty("s", 0);
config->setProperty("v", 0);
config->setProperty("type", 1);
config->setProperty("colorize", false);
return config;
}
-KisHSVConfigWidget::KisHSVConfigWidget(QWidget * parent, Qt::WFlags f) : KisConfigWidget(parent, f)
+KisHSVConfigWidget::KisHSVConfigWidget(QWidget * parent, Qt::WindowFlags f) : KisConfigWidget(parent, f)
{
m_page = new Ui_WdgHSVAdjustment();
m_page->setupUi(this);
connect(m_page->cmbType, SIGNAL(activated(int)), this, SLOT(configureSliderLimitsAndLabels()));
connect(m_page->chkColorize, SIGNAL(toggled(bool)), this, SLOT(configureSliderLimitsAndLabels()));
connect(m_page->reset,SIGNAL(clicked(bool)),this,SLOT(resetFilter()));
// connect horizontal sliders
connect(m_page->hueSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->saturationSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->valueSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->hueSpinBox, SIGNAL(valueChanged(int)), m_page->hueSlider, SLOT(setValue(int)));
connect(m_page->saturationSpinBox, SIGNAL(valueChanged(int)), m_page->saturationSlider, SLOT(setValue(int)));
connect(m_page->valueSpinBox, SIGNAL(valueChanged(int)), m_page->valueSlider, SLOT(setValue(int)));
connect(m_page->hueSlider, SIGNAL(valueChanged(int)), m_page->hueSpinBox, SLOT(setValue(int)));
connect(m_page->saturationSlider, SIGNAL(valueChanged(int)), m_page->saturationSpinBox, SLOT(setValue(int)));
connect(m_page->valueSlider, SIGNAL(valueChanged(int)), m_page->valueSpinBox, SLOT(setValue(int)));
}
KisHSVConfigWidget::~KisHSVConfigWidget()
{
delete m_page;
}
KisPropertiesConfigurationSP KisHSVConfigWidget::configuration() const
{
KisColorTransformationConfigurationSP c = new KisColorTransformationConfiguration(KisHSVAdjustmentFilter::id().id(), 0);
c->setProperty("h", m_page->hueSlider->value());
c->setProperty("s", m_page->saturationSlider->value());
c->setProperty("v", m_page->valueSlider->value());
c->setProperty("type", m_page->cmbType->currentIndex());
c->setProperty("colorize", m_page->chkColorize->isChecked());
return c;
}
void KisHSVConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config)
{
m_page->cmbType->setCurrentIndex(config->getInt("type", 1));
m_page->chkColorize->setChecked(config->getBool("colorize", false));
m_page->hueSlider->setValue(config->getInt("h", 0));
m_page->saturationSlider->setValue(config->getInt("s", 0));
m_page->valueSlider->setValue(config->getInt("v", 0));
configureSliderLimitsAndLabels();
}
void KisHSVConfigWidget::configureSliderLimitsAndLabels()
{
const WidgetSlidersConfig& widget = getCurrentWidgetConfig(m_page->cmbType->currentIndex(), m_page->chkColorize->isChecked());
widget.m_sliders[0].apply(m_page->hueSpinBox, m_page->hueSlider, m_page->label);
widget.m_sliders[1].apply(m_page->saturationSpinBox, m_page->saturationSlider, m_page->label_2);
widget.m_sliders[2].apply(m_page->valueSpinBox, m_page->valueSlider, m_page->label_3);
emit sigConfigurationItemChanged();
}
void KisHSVConfigWidget::resetFilter()
{
const WidgetSlidersConfig& widget = getCurrentWidgetConfig(m_page->cmbType->currentIndex(), m_page->chkColorize->isChecked());
widget.m_sliders[0].resetSlider(m_page->hueSlider);
widget.m_sliders[1].resetSlider(m_page->saturationSlider);
widget.m_sliders[2].resetSlider(m_page->valueSlider);
}
diff --git a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h
index 3afb7c14e2..2e007d42df 100644
--- a/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h
+++ b/plugins/filters/colorsfilters/kis_hsv_adjustment_filter.h
@@ -1,78 +1,78 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; version 2
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_HSV_ADJUSTMENT_FILTER_H_
#define _KIS_HSV_ADJUSTMENT_FILTER_H_
#include <QList>
#include "filter/kis_filter.h"
#include "kis_config_widget.h"
#include "ui_wdg_hsv_adjustment.h"
#include "filter/kis_color_transformation_filter.h"
class QWidget;
class KoColorTransformation;
/**
* This class affect Intensity Y of the image
*/
class KisHSVAdjustmentFilter : public KisColorTransformationFilter
{
public:
KisHSVAdjustmentFilter();
public:
KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const override;
KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override;
static inline KoID id() {
return KoID("hsvadjustment", i18n("HSV/HSL Adjustment"));
}
KisFilterConfigurationSP factoryConfiguration() const override;
};
class KisHSVConfigWidget : public KisConfigWidget
{
Q_OBJECT
public:
- KisHSVConfigWidget(QWidget * parent, Qt::WFlags f = 0);
+ KisHSVConfigWidget(QWidget * parent, Qt::WindowFlags f = 0);
~KisHSVConfigWidget() override;
KisPropertiesConfigurationSP configuration() const override;
void setConfiguration(const KisPropertiesConfigurationSP config) override;
Ui_WdgHSVAdjustment * m_page;
private Q_SLOTS:
void configureSliderLimitsAndLabels();
void resetFilter();
};
#endif
diff --git a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp b/plugins/filters/colorsfilters/kis_perchannel_filter.cpp
index dc31bfd439..a27240ffc1 100644
--- a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp
+++ b/plugins/filters/colorsfilters/kis_perchannel_filter.cpp
@@ -1,613 +1,613 @@
/*
* This file is part of Krita
*
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
*
* 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_perchannel_filter.h"
#include <Qt>
#include <QLayout>
#include <QPixmap>
#include <QPainter>
#include <QLabel>
#include <QComboBox>
#include <QDomDocument>
#include <QHBoxLayout>
#include "KoChannelInfo.h"
#include "KoBasicHistogramProducers.h"
#include "KoColorModelStandardIds.h"
#include "KoColorSpace.h"
#include "KoColorTransformation.h"
#include "KoCompositeColorTransformation.h"
#include "KoCompositeOp.h"
#include "KoID.h"
#include "kis_signals_blocker.h"
#include "kis_bookmarked_configuration_manager.h"
#include "kis_config_widget.h"
#include <filter/kis_filter_configuration.h>
#include <kis_selection.h>
#include <kis_paint_device.h>
#include <kis_processing_information.h>
#include "kis_histogram.h"
#include "kis_painter.h"
#include "widgets/kis_curve_widget.h"
QVector<VirtualChannelInfo> getVirtualChannels(const KoColorSpace *cs)
{
const bool supportsLightness =
cs->colorModelId() != LABAColorModelID &&
cs->colorModelId() != GrayAColorModelID &&
cs->colorModelId() != GrayColorModelID &&
cs->colorModelId() != AlphaColorModelID;
QVector<VirtualChannelInfo> vchannels;
QList<KoChannelInfo *> sortedChannels =
KoChannelInfo::displayOrderSorted(cs->channels());
if (supportsLightness) {
vchannels << VirtualChannelInfo(VirtualChannelInfo::ALL_COLORS, -1, 0, cs);
}
Q_FOREACH (KoChannelInfo *channel, sortedChannels) {
int pixelIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), sortedChannels);
vchannels << VirtualChannelInfo(VirtualChannelInfo::REAL, pixelIndex, channel, cs);
}
if (supportsLightness) {
vchannels << VirtualChannelInfo(VirtualChannelInfo::LIGHTNESS, -1, 0, cs);
}
return vchannels;
}
-KisPerChannelConfigWidget::KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WFlags f)
+KisPerChannelConfigWidget::KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f)
: KisConfigWidget(parent, f), m_histogram(0)
{
Q_ASSERT(dev);
m_page = new WdgPerChannel(this);
QHBoxLayout * layout = new QHBoxLayout(this);
Q_CHECK_PTR(layout);
layout->setContentsMargins(0,0,0,0);
layout->addWidget(m_page);
m_dev = dev;
m_activeVChannel = 0;
const KoColorSpace *targetColorSpace = dev->compositionSourceColorSpace();
// fill in the channel chooser, in the display order, but store the pixel index as well.
m_virtualChannels = getVirtualChannels(targetColorSpace);
const int virtualChannelCount = m_virtualChannels.size();
KisPerChannelFilterConfiguration::initDefaultCurves(m_curves,
virtualChannelCount);
for (int i = 0; i < virtualChannelCount; i++) {
const VirtualChannelInfo &info = m_virtualChannels[i];
m_page->cmbChannel->addItem(info.name(), info.pixelIndex());
m_curves[i].setName(info.name());
}
connect(m_page->cmbChannel, SIGNAL(activated(int)), this, SLOT(setActiveChannel(int)));
connect((QObject*)(m_page->chkLogarithmic), SIGNAL(toggled(bool)), this, SLOT(logHistView()));
// create the horizontal and vertical gradient labels
m_page->hgradient->setPixmap(createGradient(Qt::Horizontal));
m_page->vgradient->setPixmap(createGradient(Qt::Vertical));
// init histogram calculator
QList<QString> keys =
KoHistogramProducerFactoryRegistry::instance()->keysCompatibleWith(targetColorSpace);
if(keys.size() > 0) {
KoHistogramProducerFactory *hpf;
hpf = KoHistogramProducerFactoryRegistry::instance()->get(keys.at(0));
m_histogram = new KisHistogram(m_dev, m_dev->exactBounds(), hpf->generate(), LINEAR);
}
connect(m_page->curveWidget, SIGNAL(modified()), this, SIGNAL(sigConfigurationItemChanged()));
m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, 0, 100);
{
KisSignalsBlocker b(m_page->curveWidget);
m_page->curveWidget->setCurve(m_curves[0]);
setActiveChannel(0);
}
}
KisPerChannelConfigWidget::~KisPerChannelConfigWidget()
{
delete m_histogram;
}
inline QPixmap KisPerChannelConfigWidget::createGradient(Qt::Orientation orient /*, int invert (not used yet) */)
{
int width;
int height;
int *i, inc, col;
int x = 0, y = 0;
if (orient == Qt::Horizontal) {
i = &x; inc = 1; col = 0;
width = 256; height = 1;
} else {
i = &y; inc = -1; col = 255;
width = 1; height = 256;
}
QPixmap gradientpix(width, height);
QPainter p(&gradientpix);
p.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine));
for (; *i < 256; (*i)++, col += inc) {
p.setPen(QColor(col, col, col));
p.drawPoint(x, y);
}
return gradientpix;
}
inline QPixmap KisPerChannelConfigWidget::getHistogram()
{
int i;
int height = 256;
QPixmap pix(256, height);
bool logarithmic = m_page->chkLogarithmic->isChecked();
if (logarithmic)
m_histogram->setHistogramType(LOGARITHMIC);
else
m_histogram->setHistogramType(LINEAR);
QPalette appPalette = QApplication::palette();
pix.fill(QColor(appPalette.color(QPalette::Base)));
QPainter p(&pix);
p.setPen(QColor(appPalette.color(QPalette::Text)));
p.save();
p.setOpacity(0.2);
const VirtualChannelInfo &info = m_virtualChannels[m_activeVChannel];
if (m_histogram && info.type() == VirtualChannelInfo::REAL)
{
m_histogram->setChannel(info.pixelIndex());
double highest = (double)m_histogram->calculations().getHighest();
qint32 bins = m_histogram->producer()->numberOfBins();
if (m_histogram->getHistogramType() == LINEAR) {
double factor = (double)height / highest;
for (i = 0; i < bins; ++i) {
p.drawLine(i, height, i, height - int(m_histogram->getValue(i) * factor));
}
} else {
double factor = (double)height / (double)log(highest);
for (i = 0; i < bins; ++i) {
p.drawLine(i, height, i, height - int(log((double)m_histogram->getValue(i)) * factor));
}
}
}
p.restore();
return pix;
}
#define BITS_PER_BYTE 8
#define pwr2(p) (1<<p)
void KisPerChannelConfigWidget::setActiveChannel(int ch)
{
m_curves[m_activeVChannel] = m_page->curveWidget->curve();
m_activeVChannel = ch;
m_page->curveWidget->setCurve(m_curves[m_activeVChannel]);
m_page->curveWidget->setPixmap(getHistogram());
m_page->cmbChannel->setCurrentIndex(m_activeVChannel);
// Getting range accepted by channel
VirtualChannelInfo &currentVChannel = m_virtualChannels[m_activeVChannel];
KoChannelInfo::enumChannelValueType valueType = currentVChannel.valueType();
int order = BITS_PER_BYTE * currentVChannel.channelSize();
int maxValue = pwr2(order);
int min;
int max;
m_page->curveWidget->dropInOutControls();
switch (valueType) {
case KoChannelInfo::UINT8:
case KoChannelInfo::UINT16:
case KoChannelInfo::UINT32:
m_shift = 0;
m_scale = double(maxValue);
min = 0;
max = maxValue - 1;
break;
case KoChannelInfo::INT8:
case KoChannelInfo::INT16:
m_shift = 0.5;
m_scale = double(maxValue);
min = -maxValue / 2;
max = maxValue / 2 - 1;
break;
case KoChannelInfo::FLOAT16:
case KoChannelInfo::FLOAT32:
case KoChannelInfo::FLOAT64:
default:
m_shift = 0;
m_scale = 100.0;
//Hack Alert: should be changed to float
min = 0;
max = 100;
break;
}
m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, min, max);
}
KisPropertiesConfigurationSP KisPerChannelConfigWidget::configuration() const
{
int numChannels = m_virtualChannels.size();
KisPropertiesConfigurationSP cfg = new KisPerChannelFilterConfiguration(numChannels);
KIS_ASSERT_RECOVER(m_activeVChannel < m_curves.size()) { return cfg; }
m_curves[m_activeVChannel] = m_page->curveWidget->curve();
static_cast<KisPerChannelFilterConfiguration*>(cfg.data())->setCurves(m_curves);
return cfg;
}
void KisPerChannelConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config)
{
const KisPerChannelFilterConfiguration * cfg = dynamic_cast<const KisPerChannelFilterConfiguration *>(config.data());
if (!cfg)
return;
if (cfg->curves().size() == 0) {
/**
* HACK ALERT: our configuration factory generates
* default configuration with nTransfers==0.
* Catching it here. Just reset all the transfers.
*/
const int virtualChannelCount = m_virtualChannels.size();
KisPerChannelFilterConfiguration::initDefaultCurves(m_curves,
virtualChannelCount);
for (int i = 0; i < virtualChannelCount; i++) {
const VirtualChannelInfo &info = m_virtualChannels[i];
m_curves[i].setName(info.name());
}
} else if (cfg->curves().size() != int(m_virtualChannels.size())) {
warnKrita << "WARNING: trying to load a curve with incorrect number of channels!";
warnKrita << "WARNING: expected:" << m_virtualChannels.size();
warnKrita << "WARNING: got:" << cfg->curves().size();
return;
} else {
for (int ch = 0; ch < cfg->curves().size(); ch++)
m_curves[ch] = cfg->curves()[ch];
}
// HACK: we save the previous curve in setActiveChannel, so just copy it
m_page->curveWidget->setCurve(m_curves[m_activeVChannel]);
setActiveChannel(0);
}
KisPerChannelFilterConfiguration::KisPerChannelFilterConfiguration(int nCh)
: KisColorTransformationConfiguration("perchannel", 1)
{
initDefaultCurves(m_curves, nCh);
updateTransfers();
}
KisPerChannelFilterConfiguration::~KisPerChannelFilterConfiguration()
{
}
bool KisPerChannelFilterConfiguration::isCompatible(const KisPaintDeviceSP dev) const
{
return (int)dev->compositionSourceColorSpace()->channelCount() == m_curves.size();
}
void KisPerChannelFilterConfiguration::setCurves(QList<KisCubicCurve> &curves)
{
m_curves.clear();
m_curves = curves;
updateTransfers();
}
void KisPerChannelFilterConfiguration::initDefaultCurves(QList<KisCubicCurve> &curves, int nCh)
{
curves.clear();
for (int i = 0; i < nCh; i++) {
curves.append(KisCubicCurve());
}
}
void KisPerChannelFilterConfiguration::updateTransfers()
{
m_transfers.resize(m_curves.size());
for (int i = 0; i < m_curves.size(); i++) {
m_transfers[i] = m_curves[i].uint16Transfer();
}
}
const QVector<QVector<quint16> >&
KisPerChannelFilterConfiguration::transfers() const
{
return m_transfers;
}
const QList<KisCubicCurve>&
KisPerChannelFilterConfiguration::curves() const
{
return m_curves;
}
void KisPerChannelFilterConfiguration::fromLegacyXML(const QDomElement& root)
{
fromXML(root);
}
void KisPerChannelFilterConfiguration::fromXML(const QDomElement& root)
{
QList<KisCubicCurve> curves;
quint16 numTransfers = 0;
int version;
version = root.attribute("version").toInt();
QDomElement e = root.firstChild().toElement();
QString attributeName;
KisCubicCurve curve;
quint16 index;
while (!e.isNull()) {
if ((attributeName = e.attribute("name")) == "nTransfers") {
numTransfers = e.text().toUShort();
} else {
QRegExp rx("curve(\\d+)");
if (rx.indexIn(attributeName, 0) != -1) {
index = rx.cap(1).toUShort();
index = qMin(index, quint16(curves.count()));
if (!e.text().isEmpty()) {
curve.fromString(e.text());
}
curves.insert(index, curve);
}
}
e = e.nextSiblingElement();
}
if (!numTransfers)
return;
setVersion(version);
setCurves(curves);
}
/**
* Inherited from KisPropertiesConfiguration
*/
//void KisPerChannelFilterConfiguration::fromXML(const QString& s)
void addParamNode(QDomDocument& doc,
QDomElement& root,
const QString &name,
const QString &value)
{
QDomText text = doc.createTextNode(value);
QDomElement t = doc.createElement("param");
t.setAttribute("name", name);
t.appendChild(text);
root.appendChild(t);
}
void KisPerChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const
{
/**
* <params version=1>
* <param name="nTransfers">3</param>
* <param name="curve0">0,0;0.5,0.5;1,1;</param>
* <param name="curve1">0,0;1,1;</param>
* <param name="curve2">0,0;1,1;</param>
* </params>
*/
root.setAttribute("version", version());
QDomText text;
QDomElement t;
addParamNode(doc, root, "nTransfers", QString::number(m_curves.size()));
KisCubicCurve curve;
QString paramName;
for (int i = 0; i < m_curves.size(); ++i) {
QString name = QLatin1String("curve") + QString::number(i);
QString value = m_curves[i].toString();
addParamNode(doc, root, name, value);
}
}
/**
* Inherited from KisPropertiesConfiguration
*/
//QString KisPerChannelFilterConfiguration::toXML()
KisPerChannelFilter::KisPerChannelFilter() : KisColorTransformationFilter(id(), categoryAdjust(), i18n("&Color Adjustment curves..."))
{
setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M));
setSupportsPainting(true);
setColorSpaceIndependence(TO_LAB16);
}
KisConfigWidget * KisPerChannelFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const
{
return new KisPerChannelConfigWidget(parent, dev);
}
KisFilterConfigurationSP KisPerChannelFilter::factoryConfiguration() const
{
return new KisPerChannelFilterConfiguration(0);
}
KoColorTransformation* KisPerChannelFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const
{
const KisPerChannelFilterConfiguration* configBC =
dynamic_cast<const KisPerChannelFilterConfiguration*>(config.data()); // Somehow, this shouldn't happen
Q_ASSERT(configBC);
const QVector<QVector<quint16> > &originalTransfers = configBC->transfers();
const QList<KisCubicCurve> &originalCurves = configBC->curves();
/**
* TODO: What about the order of channels? (DK)
*
* Virtual channels are sorted in display order, does Lcms accepts
* transforms in display order? Why on Earth it works?! Is it
* documented anywhere?
*/
const QVector<VirtualChannelInfo> virtualChannels = getVirtualChannels(cs);
if (originalTransfers.size() != int(virtualChannels.size())) {
// We got an illegal number of colorchannels :(
return 0;
}
bool colorsNull = true;
bool lightnessNull = true;
bool allColorsNull = true;
int alphaIndexInReal = -1;
QVector<QVector<quint16> > realTransfers;
QVector<quint16> lightnessTransfer;
QVector<quint16> allColorsTransfer;
for (int i = 0; i < virtualChannels.size(); i++) {
if (virtualChannels[i].type() == VirtualChannelInfo::REAL) {
realTransfers << originalTransfers[i];
if (virtualChannels[i].isAlpha()) {
alphaIndexInReal = realTransfers.size() - 1;
}
if (colorsNull && !originalCurves[i].isNull()) {
colorsNull = false;
}
} else if (virtualChannels[i].type() == VirtualChannelInfo::LIGHTNESS) {
KIS_ASSERT_RECOVER_NOOP(lightnessTransfer.isEmpty());
lightnessTransfer = originalTransfers[i];
if (lightnessNull && !originalCurves[i].isNull()) {
lightnessNull = false;
}
} else if (virtualChannels[i].type() == VirtualChannelInfo::ALL_COLORS) {
KIS_ASSERT_RECOVER_NOOP(allColorsTransfer.isEmpty());
allColorsTransfer = originalTransfers[i];
if (allColorsNull && !originalCurves[i].isNull()) {
allColorsNull = false;
}
}
}
KoColorTransformation *lightnessTransform = 0;
KoColorTransformation *allColorsTransform = 0;
KoColorTransformation *colorTransform = 0;
if (!colorsNull) {
const quint16** transfers = new const quint16*[realTransfers.size()];
for(int i = 0; i < realTransfers.size(); ++i) {
transfers[i] = realTransfers[i].constData();
/**
* createPerChannelAdjustment() expects alpha channel to
* be the last channel in the list, so just it here
*/
KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal ||
alphaIndexInReal == (realTransfers.size() - 1));
}
colorTransform = cs->createPerChannelAdjustment(transfers);
delete [] transfers;
}
if (!lightnessNull) {
lightnessTransform = cs->createBrightnessContrastAdjustment(lightnessTransfer.constData());
}
if (!allColorsNull) {
const quint16** allColorsTransfers = new const quint16*[realTransfers.size()];
for(int i = 0; i < realTransfers.size(); ++i) {
allColorsTransfers[i] = (i != alphaIndexInReal) ?
allColorsTransfer.constData() : 0;
/**
* createPerChannelAdjustment() expects alpha channel to
* be the last channel in the list, so just it here
*/
KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal ||
alphaIndexInReal == (realTransfers.size() - 1));
}
allColorsTransform = cs->createPerChannelAdjustment(allColorsTransfers);
delete[] allColorsTransfers;
}
QVector<KoColorTransformation*> allTransforms;
allTransforms << colorTransform;
allTransforms << allColorsTransform;
allTransforms << lightnessTransform;
return KoCompositeColorTransformation::createOptimizedCompositeTransform(allTransforms);
}
bool KisPerChannelFilter::needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const
{
Q_UNUSED(config);
return cs->colorModelId() == AlphaColorModelID;
}
void KisPerChannelConfigWidget::logHistView()
{
m_page->curveWidget->setPixmap(getHistogram());
}
diff --git a/plugins/filters/colorsfilters/kis_perchannel_filter.h b/plugins/filters/colorsfilters/kis_perchannel_filter.h
index 4844503100..044d618621 100644
--- a/plugins/filters/colorsfilters/kis_perchannel_filter.h
+++ b/plugins/filters/colorsfilters/kis_perchannel_filter.h
@@ -1,136 +1,136 @@
/*
* This file is part of Krita
*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
*
* 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_PERCHANNEL_FILTER_H_
#define _KIS_PERCHANNEL_FILTER_H_
#include <QPair>
#include <QList>
#include <filter/kis_color_transformation_filter.h>
#include <filter/kis_color_transformation_configuration.h>
#include <kis_config_widget.h>
#include <kis_paint_device.h>
#include "ui_wdg_perchannel.h"
#include "virtual_channel_info.h"
class WdgPerChannel : public QWidget, public Ui::WdgPerChannel
{
Q_OBJECT
public:
WdgPerChannel(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class KisPerChannelFilterConfiguration
: public KisColorTransformationConfiguration
{
public:
KisPerChannelFilterConfiguration(int n);
~KisPerChannelFilterConfiguration() override;
using KisFilterConfiguration::fromXML;
using KisFilterConfiguration::toXML;
using KisFilterConfiguration::fromLegacyXML;
void fromLegacyXML(const QDomElement& root) override;
void fromXML(const QDomElement& e) override;
void toXML(QDomDocument& doc, QDomElement& root) const override;
void setCurves(QList<KisCubicCurve> &curves) override;
static inline void initDefaultCurves(QList<KisCubicCurve> &curves, int nCh);
bool isCompatible(const KisPaintDeviceSP) const override;
const QVector<QVector<quint16> >& transfers() const;
const QList<KisCubicCurve>& curves() const override;
private:
QList<KisCubicCurve> m_curves;
private:
void updateTransfers();
private:
QVector<QVector<quint16> > m_transfers;
};
/**
* This class is generic for filters that affect channel separately
*/
class KisPerChannelFilter
: public KisColorTransformationFilter
{
public:
KisPerChannelFilter();
public:
KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const override;
KisFilterConfigurationSP factoryConfiguration() const override;
KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override;
bool needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const override;
static inline KoID id() {
return KoID("perchannel", i18n("Color Adjustment"));
}
private:
};
class KisPerChannelConfigWidget : public KisConfigWidget
{
Q_OBJECT
public:
- KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WFlags f = 0);
+ KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f = 0);
~KisPerChannelConfigWidget() override;
void setConfiguration(const KisPropertiesConfigurationSP config) override;
KisPropertiesConfigurationSP configuration() const override;
private Q_SLOTS:
virtual void setActiveChannel(int ch);
void logHistView();
private:
QVector<VirtualChannelInfo> m_virtualChannels;
int m_activeVChannel;
// private routines
inline QPixmap getHistogram();
inline QPixmap createGradient(Qt::Orientation orient /*, int invert (not used now) */);
// members
WdgPerChannel * m_page;
KisPaintDeviceSP m_dev;
KisHistogram *m_histogram;
mutable QList<KisCubicCurve> m_curves;
// scales for displaying color numbers
double m_scale;
double m_shift;
};
#endif
diff --git a/plugins/filters/edgedetection/kis_edge_detection_filter.cpp b/plugins/filters/edgedetection/kis_edge_detection_filter.cpp
index 79b594a016..5775d1d166 100644
--- a/plugins/filters/edgedetection/kis_edge_detection_filter.cpp
+++ b/plugins/filters/edgedetection/kis_edge_detection_filter.cpp
@@ -1,157 +1,158 @@
/*
* Copyright (c) 2017 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_edge_detection_filter.h"
#include "kis_wdg_edge_detection.h"
#include <kis_edge_detection_kernel.h>
#include <kis_convolution_kernel.h>
#include <kis_convolution_painter.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <filter/kis_filter_configuration.h>
#include <kis_selection.h>
#include <kis_paint_device.h>
#include <kis_processing_information.h>
#include "kis_lod_transform.h"
#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <filter/kis_filter_registry.h>
K_PLUGIN_FACTORY_WITH_JSON(KritaEdgeDetectionFilterFactory, "kritaedgedetection.json", registerPlugin<KritaEdgeDetectionFilter>();)
KritaEdgeDetectionFilter::KritaEdgeDetectionFilter(QObject *parent, const QVariantList &)
: QObject(parent)
{
KisFilterRegistry::instance()->add(KisFilterSP(new KisEdgeDetectionFilter()));
}
KritaEdgeDetectionFilter::~KritaEdgeDetectionFilter()
{
}
KisEdgeDetectionFilter::KisEdgeDetectionFilter(): KisFilter(id(), categoryEdgeDetection(), i18n("&Edge Detection..."))
{
setSupportsPainting(true);
setSupportsAdjustmentLayers(true);
setSupportsLevelOfDetail(true);
setColorSpaceIndependence(FULLY_INDEPENDENT);
setShowConfigurationWidget(true);
}
void KisEdgeDetectionFilter::processImpl(KisPaintDeviceSP device, const QRect &rect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const
{
Q_ASSERT(device != 0);
KisFilterConfigurationSP configuration = config ? config : new KisFilterConfiguration(id().id(), 1);
KisLodTransformScalar t(device);
QVariant value;
configuration->getProperty("horizRadius", value);
float horizontalRadius = t.scale(value.toFloat());
configuration->getProperty("vertRadius", value);
float verticalRadius = t.scale(value.toFloat());
QBitArray channelFlags;
if (configuration) {
channelFlags = configuration->channelFlags();
}
if (channelFlags.isEmpty() || !configuration) {
channelFlags = device->colorSpace()->channelFlags();
}
KisEdgeDetectionKernel::FilterType type = KisEdgeDetectionKernel::SobolVector;
if (config->getString("type") == "prewitt") {
type = KisEdgeDetectionKernel::Prewit;
} else if (config->getString("type") == "simple") {
type = KisEdgeDetectionKernel::Simple;
}
KisEdgeDetectionKernel::FilterOutput output = KisEdgeDetectionKernel::pythagorean;
if (config->getString("output") == "xGrowth") {
output = KisEdgeDetectionKernel::xGrowth;
} else if (config->getString("output") == "xFall") {
output = KisEdgeDetectionKernel::xFall;
} else if (config->getString("output") == "yGrowth") {
output = KisEdgeDetectionKernel::yGrowth;
} else if (config->getString("output") == "yFall") {
output = KisEdgeDetectionKernel::yFall;
} else if (config->getString("output") == "radian") {
output = KisEdgeDetectionKernel::radian;
}
KisEdgeDetectionKernel::applyEdgeDetection(device,
rect,
horizontalRadius,
verticalRadius,
type,
channelFlags,
progressUpdater,
output,
config->getBool("transparency", false));
}
KisFilterConfigurationSP KisEdgeDetectionFilter::factoryConfiguration() const
{
KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1);
config->setProperty("horizRadius", 1);
config->setProperty("vertRadius", 1);
config->setProperty("type", "prewitt");
config->setProperty("output", "pythagorean");
config->setProperty("lockAspect", true);
config->setProperty("transparency", false);
return config;
}
KisConfigWidget *KisEdgeDetectionFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const
{
+ Q_UNUSED(dev);
return new KisWdgEdgeDetection(parent);
}
QRect KisEdgeDetectionFilter::neededRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const
{
KisLodTransformScalar t(lod);
QVariant value;
/**
* NOTE: integer devision by two is done on purpose,
* because the kernel size is always odd
*/
const int halfWidth = _config->getProperty("horizRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5;
const int halfHeight = _config->getProperty("vertRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5;
return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2);
}
QRect KisEdgeDetectionFilter::changedRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const
{
KisLodTransformScalar t(lod);
QVariant value;
const int halfWidth = _config->getProperty("horizRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5;
const int halfHeight = _config->getProperty("vertRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5;
return rect.adjusted( -halfWidth, -halfHeight, halfWidth, halfHeight);
}
#include "kis_edge_detection_filter.moc"
diff --git a/plugins/filters/gradientmap/gradientmap.cpp b/plugins/filters/gradientmap/gradientmap.cpp
index 983ccbfae4..e9e74cb7f4 100644
--- a/plugins/filters/gradientmap/gradientmap.cpp
+++ b/plugins/filters/gradientmap/gradientmap.cpp
@@ -1,126 +1,126 @@
/*
* This file is part of the KDE project
*
* Copyright (c) 2016 Spencer Brown <sbrown655@gmail.com>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "QObject"
#include "gradientmap.h"
#include <kpluginfactory.h>
#include <filter/kis_filter_registry.h>
#include "krita_filter_gradient_map.h"
#include "KoResourceServerProvider.h"
#include "kis_config_widget.h"
#include <KoAbstractGradient.h>
#include <KoStopGradient.h>
#include <KoGradientBackground.h>
#include <KoResourceServerAdapter.h>
K_PLUGIN_FACTORY_WITH_JSON(KritaGradientMapFactory, "kritagradientmap.json", registerPlugin<KritaGradientMap>();)
-KritaGradientMapConfigWidget::KritaGradientMapConfigWidget(QWidget *parent, KisPaintDeviceSP dev, Qt::WFlags f)
+KritaGradientMapConfigWidget::KritaGradientMapConfigWidget(QWidget *parent, KisPaintDeviceSP dev, Qt::WindowFlags f)
: KisConfigWidget(parent, f)
{
Q_UNUSED(dev)
m_page = new WdgGradientMap(this);
QHBoxLayout *l = new QHBoxLayout(this);
Q_CHECK_PTR(l);
l->addWidget(m_page);
l->setContentsMargins(0, 0, 0, 0);
KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance();
QSharedPointer<KoAbstractResourceServerAdapter> gradientResourceAdapter(
new KoResourceServerAdapter<KoAbstractGradient>(serverProvider->gradientServer()));
m_gradientChangedCompressor = new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE);
m_gradientPopUp = new KoResourcePopupAction(gradientResourceAdapter,
m_page->btnGradientChooser);
m_activeGradient = KoStopGradient::fromQGradient(dynamic_cast<KoAbstractGradient*>(gradientResourceAdapter->resources().first())->toQGradient());
m_page->gradientEditor->setGradient(m_activeGradient);
m_page->gradientEditor->setCompactMode(true);
m_page->gradientEditor->setEnabled(true);
m_page->btnGradientChooser->setDefaultAction(m_gradientPopUp);
m_page->btnGradientChooser->setPopupMode(QToolButton::InstantPopup);
connect(m_gradientPopUp, SIGNAL(resourceSelected(QSharedPointer<KoShapeBackground>)), this, SLOT(setAbstractGradientToEditor()));
connect(m_page->gradientEditor, SIGNAL(sigGradientChanged()), m_gradientChangedCompressor, SLOT(start()));
connect(m_gradientChangedCompressor, SIGNAL(timeout()), this, SIGNAL(sigConfigurationItemChanged()));
}
KritaGradientMapConfigWidget::~KritaGradientMapConfigWidget()
{
delete m_page;
}
void KritaGradientMapConfigWidget::setAbstractGradientToEditor()
{
QSharedPointer<KoGradientBackground> bg =
qSharedPointerDynamicCast<KoGradientBackground>(
m_gradientPopUp->currentBackground());
m_activeGradient = KoStopGradient::fromQGradient(bg->gradient());
m_page->gradientEditor->setGradient(m_activeGradient);
}
KisPropertiesConfigurationSP KritaGradientMapConfigWidget::configuration() const
{
KisFilterConfigurationSP cfg = new KisFilterConfiguration("gradientmap", 2);
if (m_activeGradient) {
QDomDocument doc;
QDomElement elt = doc.createElement("gradient");
m_activeGradient->toXML(doc, elt);
doc.appendChild(elt);
cfg->setProperty("gradientXML", doc.toString());
}
return cfg;
}
//-----------------------------
void KritaGradientMapConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config)
{
Q_ASSERT(config);
QDomDocument doc;
if (config->hasProperty("gradientXML")) {
doc.setContent(config->getString("gradientXML", ""));
qDebug()<<config->getString("gradientXML", "");
KoStopGradient gradient = KoStopGradient::fromXML(doc.firstChildElement());
if (gradient.stops().size()>0) {
m_activeGradient->setStops(gradient.stops());
}
}
}
void KritaGradientMapConfigWidget::setView(KisViewManager *view)
{
Q_UNUSED(view)
}
//------------------------------
KritaGradientMap::KritaGradientMap(QObject *parent, const QVariantList &)
: QObject(parent)
{
KisFilterRegistry::instance()->add(KisFilterSP(new KritaFilterGradientMap()));
}
KritaGradientMap::~KritaGradientMap()
{
}
//-----------------------------
#include "gradientmap.moc"
diff --git a/plugins/filters/gradientmap/gradientmap.h b/plugins/filters/gradientmap/gradientmap.h
index fa5fcbebdb..bcaf3eaa29 100644
--- a/plugins/filters/gradientmap/gradientmap.h
+++ b/plugins/filters/gradientmap/gradientmap.h
@@ -1,83 +1,83 @@
/*
* This file is part of Krita
*
* Copyright (c) 2016 Spencer Brown <sbrown655@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
#include "QObject"
#include "ui_wdg_gradientmap.h"
#include "kis_properties_configuration.h"
#include "filter/kis_color_transformation_configuration.h"
#include "kis_config_widget.h"
#include <KoResourcePopupAction.h>
#include <kis_signal_compressor.h>
#include <KoStopGradient.h>
class WdgGradientMap : public QWidget, public Ui::WdgGradientMap
{
Q_OBJECT
public:
WdgGradientMap(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class KritaGradientMapFilterConfiguration : public KisColorTransformationConfiguration
{
public:
KritaGradientMapFilterConfiguration();
~KritaGradientMapFilterConfiguration() override;
virtual void setGradient(const KoResource* gradient);
virtual const KoResource* gradient() const;
private:
KoResource const* m_gradient;
};
class KritaGradientMapConfigWidget : public KisConfigWidget
{
Q_OBJECT
public:
- KritaGradientMapConfigWidget(QWidget *parent, KisPaintDeviceSP dev, Qt::WFlags f = 0);
+ KritaGradientMapConfigWidget(QWidget *parent, KisPaintDeviceSP dev, Qt::WindowFlags f = 0);
~KritaGradientMapConfigWidget() override;
KisPropertiesConfigurationSP configuration() const override;
void setConfiguration(const KisPropertiesConfigurationSP config) override;
WdgGradientMap *m_page;
KoResourcePopupAction *m_gradientPopUp;
KisSignalCompressor *m_gradientChangedCompressor;
KoStopGradient *m_activeGradient;
void setView(KisViewManager *view) override;
private Q_SLOTS:
void setAbstractGradientToEditor();
};
class KritaGradientMap : public QObject
{
Q_OBJECT
public:
KritaGradientMap(QObject *parent, const QVariantList &);
~KritaGradientMap() override;
};
diff --git a/plugins/filters/indexcolors/kiswdgindexcolors.cpp b/plugins/filters/indexcolors/kiswdgindexcolors.cpp
index fd2659e8aa..51ce63d45b 100644
--- a/plugins/filters/indexcolors/kiswdgindexcolors.cpp
+++ b/plugins/filters/indexcolors/kiswdgindexcolors.cpp
@@ -1,192 +1,192 @@
/*
* Copyright 2014 Manuel Riecke <spell1337@gmail.com>
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for any purpose and without fee is hereby
* granted, provided that the above copyright notice appear in all
* copies and that both that the copyright notice and this
* permission notice and warranty disclaimer appear in supporting
* documentation, and that the name of the author not be used in
* advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* The author disclaim all warranties with regard to this
* software, including all implied warranties of merchantability
* and fitness. In no event shall the author be liable for any
* special, indirect or consequential damages or any damages
* whatsoever resulting from loss of use, data or profits, whether
* in an action of contract, negligence or other tortious action,
* arising out of or in connection with the use or performance of
* this software.
*/
#include "filter/kis_color_transformation_configuration.h"
#include "kiswdgindexcolors.h"
#include "palettegeneratorconfig.h"
#include "ui_kiswdgindexcolors.h"
#include "kis_int_parse_spin_box.h"
#include <kis_color_button.h>
-KisWdgIndexColors::KisWdgIndexColors(QWidget* parent, Qt::WFlags f, int delay): KisConfigWidget(parent, f, delay)
+KisWdgIndexColors::KisWdgIndexColors(QWidget* parent, Qt::WindowFlags f, int delay): KisConfigWidget(parent, f, delay)
{
ui = new Ui::KisWdgIndexColors;
ui->setupUi(this);
connect(ui->diagCheck, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(ui->inbetweenSpinBox, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(ui->alphaStepsSpinBox, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(ui->colorLimit, SIGNAL(valueChanged(int)), SLOT(slotColorLimitChanged(int)));
connect(ui->colorLimit, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(ui->colorLimitCheck, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(ui->luminanceSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(ui->aSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(ui->bSlider, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
}
void KisWdgIndexColors::slotColorLimitChanged(int value)
{
ui->colorLimit->setSuffix(i18ncp("suffix for a spinbox",
" color", " colors", value));
}
void KisWdgIndexColors::setup(QStringList shadesLabels, int ramps)
{
int rows = shadesLabels.length();
int collumns = ramps;
m_colorSelectors.resize(rows);
m_stepSpinners.resize(rows-1);
// Labels for the shades
for(int row = 0; row < rows; ++row)
{
QLabel* l = new QLabel(shadesLabels[row], ui->colorsBox);
ui->layoutColors->addWidget(l, row+1, 0);
m_colorSelectors[row].resize(collumns);
}
// Labels for the ramps
/*for(int col = 0; col < collumns; ++col)
{
QLabel* l = new QLabel(rampsLabels[col], ui->colorsBox);
l->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
ui->layoutColors->addWidget(l, 0, col+1);
}*/
// Step selectors for the shade gradients
for(int row = 0; row < (rows-1); ++row)
{
QLabel* l0 = new QLabel(shadesLabels[row+1]);
QLabel* l1 = new QLabel(QString::fromUtf8("↔"));
QLabel* l2 = new QLabel(shadesLabels[row]);
QSpinBox* s = new KisIntParseSpinBox();
s->setMinimum(0);
s->setMaximum(32);
s->setValue(2);
connect(s, SIGNAL(valueChanged(int)), this, SIGNAL(sigConfigurationItemChanged()));
m_stepSpinners[row] = s;
ui->layoutRowSteps->addWidget(l0, row, 0);
ui->layoutRowSteps->addWidget(l1, row, 1);
ui->layoutRowSteps->addWidget(l2, row, 2);
ui->layoutRowSteps->addWidget(s, row, 3);
}
// Color selectors
for(int y = 0; y < rows; ++y)
for(int x = 0; x < collumns; ++x)
{
KisColorButton* b = new KisColorButton;
QCheckBox* c = new QCheckBox;
c->setChecked(false);
b->setEnabled(false);
b->setMaximumWidth(50);
c->setMaximumWidth(21); // Ugh. I hope this won't be causing any issues. Trying to get rid of the unnecessary spacing after it.
connect(c, SIGNAL(toggled(bool)), b, SLOT(setEnabled(bool)));
connect(c, SIGNAL(toggled(bool)), this, SIGNAL(sigConfigurationItemChanged()));
connect(b, SIGNAL(changed(KoColor)), this, SIGNAL(sigConfigurationItemChanged()));
QHBoxLayout* cell = new QHBoxLayout();
cell->setSpacing(0);
cell->setContentsMargins(0, 0, 0, 0);
cell->addWidget(c);
cell->addWidget(b);
ui->layoutColors->addLayout(cell, 1+y, 1+x);
m_colorSelectors[y][x].button = b;
m_colorSelectors[y][x].checkbox = c;
}
}
KisPropertiesConfigurationSP KisWdgIndexColors::configuration() const
{
KisColorTransformationConfigurationSP config = new KisColorTransformationConfiguration("indexcolors", 1);
PaletteGeneratorConfig palCfg;
for(int y = 0; y < 4; ++y)
for(int x = 0; x < 4; ++x)
{
palCfg.colors[y][x] = m_colorSelectors[y][x].button->color().toQColor();
palCfg.colorsEnabled[y][x] = m_colorSelectors[y][x].button->isEnabled();
}
for(int y = 0; y < 3; ++y)
palCfg.gradientSteps[y] = m_stepSpinners[y]->value();
palCfg.diagonalGradients = ui->diagCheck->isChecked();
palCfg.inbetweenRampSteps = ui->inbetweenSpinBox->value();
IndexColorPalette pal = palCfg.generate();
ui->colorCount->setText(QString::number(pal.numColors()));
config->setProperty("paletteGen", palCfg.toByteArray());
config->setProperty("LFactor", ui->luminanceSlider->value() / 100.f);
config->setProperty("aFactor", ui->aSlider->value() / 100.f);
config->setProperty("bFactor", ui->bSlider->value() / 100.f);
config->setProperty("reduceColorsEnabled", ui->colorLimitCheck->isChecked());
config->setProperty("colorLimit", ui->colorLimit->value());
config->setProperty("alphaSteps", ui->alphaStepsSpinBox->value());
return config;
}
void KisWdgIndexColors::setConfiguration(const KisPropertiesConfigurationSP config)
{
PaletteGeneratorConfig palCfg;
palCfg.fromByteArray(config->getProperty("paletteGen").toByteArray());
ui->luminanceSlider->setValue(config->getFloat("LFactor")*100);
ui->aSlider->setValue(config->getFloat("aFactor")*100);
ui->bSlider->setValue(config->getFloat("bFactor")*100);
ui->alphaStepsSpinBox->setValue(config->getInt("alphaSteps"));
ui->colorLimitCheck->setChecked(config->getBool("reduceColorsEnabled"));
ui->colorLimit->setEnabled(config->getBool("reduceColorsEnabled"));
ui->colorLimit->setValue(config->getInt("colorLimit"));
ui->diagCheck->setChecked(palCfg.diagonalGradients);
ui->inbetweenSpinBox->setValue(palCfg.inbetweenRampSteps);
for(int y = 0; y < 4; ++y)
for(int x = 0; x < 4; ++x)
{
m_colorSelectors[y][x].checkbox->setChecked(palCfg.colorsEnabled[y][x]);
m_colorSelectors[y][x].button->setEnabled(palCfg.colorsEnabled[y][x]);
KoColor c;
c.fromQColor(palCfg.colors[y][x]);
m_colorSelectors[y][x].button->setColor(c);
}
for(int y = 0; y < 3; ++y)
m_stepSpinners[y]->setValue(palCfg.gradientSteps[y]);
IndexColorPalette pal = palCfg.generate();
ui->colorCount->setText(QString::number(pal.numColors()));
}
diff --git a/plugins/filters/indexcolors/kiswdgindexcolors.h b/plugins/filters/indexcolors/kiswdgindexcolors.h
index 902f9a73af..bb997290f3 100644
--- a/plugins/filters/indexcolors/kiswdgindexcolors.h
+++ b/plugins/filters/indexcolors/kiswdgindexcolors.h
@@ -1,59 +1,59 @@
/*
* Copyright 2014 Manuel Riecke <spell1337@gmail.com>
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for any purpose and without fee is hereby
* granted, provided that the above copyright notice appear in all
* copies and that both that the copyright notice and this
* permission notice and warranty disclaimer appear in supporting
* documentation, and that the name of the author not be used in
* advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* The author disclaim all warranties with regard to this
* software, including all implied warranties of merchantability
* and fitness. In no event shall the author be liable for any
* special, indirect or consequential damages or any damages
* whatsoever resulting from loss of use, data or profits, whether
* in an action of contract, negligence or other tortious action,
* arising out of or in connection with the use or performance of
* this software.
*/
#ifndef KISWDGINDEXCOLORS_H
#define KISWDGINDEXCOLORS_H
#include <kis_config_widget.h>
#include <QSpinBox>
class QCheckBox;
class KisColorButton;
namespace Ui
{
class KisWdgIndexColors;
}
class KisWdgIndexColors : public KisConfigWidget
{
Q_OBJECT
public:
- KisWdgIndexColors(QWidget* parent = 0, Qt::WFlags f = 0, int delay = 500);
+ KisWdgIndexColors(QWidget* parent = 0, Qt::WindowFlags f = 0, int delay = 500);
KisPropertiesConfigurationSP configuration() const override;
void setConfiguration(const KisPropertiesConfigurationSP config) override;
void setup(QStringList shadesLabels, int ramps);
private Q_SLOTS:
void slotColorLimitChanged(int value);
private:
struct ColorWidgets
{
KisColorButton* button;
QCheckBox* checkbox;
};
QVector< QVector<ColorWidgets> > m_colorSelectors;
QVector< QSpinBox* > m_stepSpinners;
Ui::KisWdgIndexColors* ui;
};
#endif // KISWDGINDEXCOLORS_H
diff --git a/plugins/filters/phongbumpmap/kis_phong_bumpmap_config_widget.cpp b/plugins/filters/phongbumpmap/kis_phong_bumpmap_config_widget.cpp
index de3399d0cd..d0011fe093 100644
--- a/plugins/filters/phongbumpmap/kis_phong_bumpmap_config_widget.cpp
+++ b/plugins/filters/phongbumpmap/kis_phong_bumpmap_config_widget.cpp
@@ -1,186 +1,186 @@
/*
* Copyright (c) 2010-2011 José Luis Vergara <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_phong_bumpmap_config_widget.h"
#include <filter/kis_filter_configuration.h>
#include <kis_size_group.h>
#include "phong_bumpmap_constants.h"
#include "KoChannelInfo.h"
#include "KoColorSpace.h"
-KisPhongBumpmapConfigWidget::KisPhongBumpmapConfigWidget(const KisPaintDeviceSP dev, QWidget *parent, Qt::WFlags f)
+KisPhongBumpmapConfigWidget::KisPhongBumpmapConfigWidget(const KisPaintDeviceSP dev, QWidget *parent, Qt::WindowFlags f)
: KisConfigWidget(parent, f)
, m_device(dev)
{
Q_ASSERT(m_device);
m_page = new KisPhongBumpmapWidget(this);
KisSizeGroup *matPropLabelsGroup = new KisSizeGroup(this);
matPropLabelsGroup->addWidget(m_page->lblAmbientReflectivity);
matPropLabelsGroup->addWidget(m_page->lblDiffuseReflectivity);
matPropLabelsGroup->addWidget(m_page->lblSpecularReflectivity);
matPropLabelsGroup->addWidget(m_page->lblSpecularShinyExp);
// Connect widgets to each other
connect(m_page->azimuthDial1, SIGNAL(valueChanged(int)), m_page->azimuthSpinBox1, SLOT(setValue(int)));
connect(m_page->azimuthDial2, SIGNAL(valueChanged(int)), m_page->azimuthSpinBox2, SLOT(setValue(int)));
connect(m_page->azimuthDial3, SIGNAL(valueChanged(int)), m_page->azimuthSpinBox3, SLOT(setValue(int)));
connect(m_page->azimuthDial4, SIGNAL(valueChanged(int)), m_page->azimuthSpinBox4, SLOT(setValue(int)));
connect(m_page->azimuthSpinBox1, SIGNAL(valueChanged(int)), m_page->azimuthDial1, SLOT(setValue(int)));
connect(m_page->azimuthSpinBox2, SIGNAL(valueChanged(int)), m_page->azimuthDial2, SLOT(setValue(int)));
connect(m_page->azimuthSpinBox3, SIGNAL(valueChanged(int)), m_page->azimuthDial3, SLOT(setValue(int)));
connect(m_page->azimuthSpinBox4, SIGNAL(valueChanged(int)), m_page->azimuthDial4, SLOT(setValue(int)));
//Let widgets warn the preview of when they are updated
connect(m_page->azimuthDial1, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->azimuthDial2, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->azimuthDial3, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->azimuthDial4, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->lightKColorCombo1, SIGNAL(currentIndexChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->lightKColorCombo2, SIGNAL(currentIndexChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->lightKColorCombo3, SIGNAL(currentIndexChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->lightKColorCombo4, SIGNAL(currentIndexChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->inclinationSpinBox1, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->inclinationSpinBox2, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->inclinationSpinBox3, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->inclinationSpinBox4, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->useNormalMap, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->diffuseReflectivityGroup, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->specularReflectivityGroup, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->ambientReflectivityKisDoubleSliderSpinBox, SIGNAL(valueChanged(qreal)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->diffuseReflectivityKisDoubleSliderSpinBox, SIGNAL(valueChanged(qreal)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->specularReflectivityKisDoubleSliderSpinBox, SIGNAL(valueChanged(qreal)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->shinynessExponentKisSliderSpinBox, SIGNAL(valueChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->heightChannelComboBox, SIGNAL(currentIndexChanged(int)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->lightSourceGroupBox1, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->lightSourceGroupBox2, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->lightSourceGroupBox3, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
connect(m_page->lightSourceGroupBox4, SIGNAL(toggled(bool)), SIGNAL(sigConfigurationItemChanged()));
QVBoxLayout *l = new QVBoxLayout(this);
Q_CHECK_PTR(l);
l->addWidget(m_page);
/* fill in the channel chooser */
QList<KoChannelInfo *> channels = m_device->colorSpace()->channels();
for (quint8 ch = 0; ch < m_device->colorSpace()->colorChannelCount(); ch++)
m_page->heightChannelComboBox->addItem(channels.at(ch)->name());
connect(m_page->useNormalMap, SIGNAL(toggled(bool)), this, SLOT(slotDisableHeightChannelCombobox(bool) ) );
}
void KisPhongBumpmapConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config)
{
if (!config) return;
QVariant tempcolor;
if (config->getBool(USE_NORMALMAP_IS_ENABLED)) {
m_page->heightChannelComboBox->setEnabled(false);
} else {
m_page->heightChannelComboBox->setEnabled(true);
}
m_page->ambientReflectivityKisDoubleSliderSpinBox->setValue( config->getDouble(PHONG_AMBIENT_REFLECTIVITY) );
m_page->diffuseReflectivityKisDoubleSliderSpinBox->setValue( config->getDouble(PHONG_DIFFUSE_REFLECTIVITY) );
m_page->specularReflectivityKisDoubleSliderSpinBox->setValue( config->getDouble(PHONG_SPECULAR_REFLECTIVITY) );
m_page->shinynessExponentKisSliderSpinBox->setValue( config->getInt(PHONG_SHINYNESS_EXPONENT) );
m_page->useNormalMap->setChecked( config->getBool(USE_NORMALMAP_IS_ENABLED) );
m_page->diffuseReflectivityGroup->setChecked( config->getBool(PHONG_DIFFUSE_REFLECTIVITY_IS_ENABLED) );
m_page->specularReflectivityGroup->setChecked( config->getBool(PHONG_SPECULAR_REFLECTIVITY_IS_ENABLED) );
// NOTE: Indexes are off by 1 simply because arrays start at 0 and the GUI naming scheme started at 1
m_page->lightSourceGroupBox1->setChecked( config->getBool(PHONG_ILLUMINANT_IS_ENABLED[0]) );
m_page->lightSourceGroupBox2->setChecked( config->getBool(PHONG_ILLUMINANT_IS_ENABLED[1]) );
m_page->lightSourceGroupBox3->setChecked( config->getBool(PHONG_ILLUMINANT_IS_ENABLED[2]) );
m_page->lightSourceGroupBox4->setChecked( config->getBool(PHONG_ILLUMINANT_IS_ENABLED[3]) );
config->getProperty(PHONG_ILLUMINANT_COLOR[0], tempcolor);
m_page->lightKColorCombo1->setColor(tempcolor.value<QColor>());
config->getProperty(PHONG_ILLUMINANT_COLOR[1], tempcolor);
m_page->lightKColorCombo2->setColor(tempcolor.value<QColor>());
config->getProperty(PHONG_ILLUMINANT_COLOR[2], tempcolor);
m_page->lightKColorCombo3->setColor(tempcolor.value<QColor>());
config->getProperty(PHONG_ILLUMINANT_COLOR[3], tempcolor);
m_page->lightKColorCombo4->setColor(tempcolor.value<QColor>());
m_page->azimuthSpinBox1->setValue( config->getDouble(PHONG_ILLUMINANT_AZIMUTH[0]) );
m_page->azimuthSpinBox2->setValue( config->getDouble(PHONG_ILLUMINANT_AZIMUTH[1]) );
m_page->azimuthSpinBox3->setValue( config->getDouble(PHONG_ILLUMINANT_AZIMUTH[2]) );
m_page->azimuthSpinBox4->setValue( config->getDouble(PHONG_ILLUMINANT_AZIMUTH[3]) );
m_page->inclinationSpinBox1->setValue( config->getDouble(PHONG_ILLUMINANT_INCLINATION[0]) );
m_page->inclinationSpinBox2->setValue( config->getDouble(PHONG_ILLUMINANT_INCLINATION[1]) );
m_page->inclinationSpinBox3->setValue( config->getDouble(PHONG_ILLUMINANT_INCLINATION[2]) );
m_page->inclinationSpinBox4->setValue( config->getDouble(PHONG_ILLUMINANT_INCLINATION[3]) );
}
KisPropertiesConfigurationSP KisPhongBumpmapConfigWidget::configuration() const
{
KisFilterConfigurationSP config = new KisFilterConfiguration("phongbumpmap", 2);
config->setProperty(PHONG_HEIGHT_CHANNEL, m_page->heightChannelComboBox->currentText());
config->setProperty(USE_NORMALMAP_IS_ENABLED, m_page->useNormalMap->isChecked());
config->setProperty(PHONG_AMBIENT_REFLECTIVITY, m_page->ambientReflectivityKisDoubleSliderSpinBox->value());
config->setProperty(PHONG_DIFFUSE_REFLECTIVITY, m_page->diffuseReflectivityKisDoubleSliderSpinBox->value());
config->setProperty(PHONG_SPECULAR_REFLECTIVITY, m_page->specularReflectivityKisDoubleSliderSpinBox->value());
config->setProperty(PHONG_SHINYNESS_EXPONENT, m_page->shinynessExponentKisSliderSpinBox->value());
config->setProperty(PHONG_DIFFUSE_REFLECTIVITY_IS_ENABLED, m_page->diffuseReflectivityGroup->isChecked());
config->setProperty(PHONG_SPECULAR_REFLECTIVITY_IS_ENABLED, m_page->specularReflectivityGroup->isChecked());
//config->setProperty(PHONG_SHINYNESS_EXPONENT_IS_ENABLED, m_page->specularReflectivityCheckBox->isChecked());
// Indexes are off by 1 simply because arrays start at 0 and the GUI naming scheme started at 1
config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[0], m_page->lightSourceGroupBox1->isChecked());
config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[1], m_page->lightSourceGroupBox2->isChecked());
config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[2], m_page->lightSourceGroupBox3->isChecked());
config->setProperty(PHONG_ILLUMINANT_IS_ENABLED[3], m_page->lightSourceGroupBox4->isChecked());
config->setProperty(PHONG_ILLUMINANT_COLOR[0], m_page->lightKColorCombo1->color());
config->setProperty(PHONG_ILLUMINANT_COLOR[1], m_page->lightKColorCombo2->color());
config->setProperty(PHONG_ILLUMINANT_COLOR[2], m_page->lightKColorCombo3->color());
config->setProperty(PHONG_ILLUMINANT_COLOR[3], m_page->lightKColorCombo4->color());
config->setProperty(PHONG_ILLUMINANT_AZIMUTH[0], m_page->azimuthSpinBox1->value());
config->setProperty(PHONG_ILLUMINANT_AZIMUTH[1], m_page->azimuthSpinBox2->value());
config->setProperty(PHONG_ILLUMINANT_AZIMUTH[2], m_page->azimuthSpinBox3->value());
config->setProperty(PHONG_ILLUMINANT_AZIMUTH[3], m_page->azimuthSpinBox4->value());
config->setProperty(PHONG_ILLUMINANT_INCLINATION[0], m_page->inclinationSpinBox1->value());
config->setProperty(PHONG_ILLUMINANT_INCLINATION[1], m_page->inclinationSpinBox2->value());
config->setProperty(PHONG_ILLUMINANT_INCLINATION[2], m_page->inclinationSpinBox3->value());
config->setProperty(PHONG_ILLUMINANT_INCLINATION[3], m_page->inclinationSpinBox4->value());
// Read configuration
/*
QMap<QString, QVariant> rofl = QMap<QString, QVariant>(config->getProperties());
QMap<QString, QVariant>::const_iterator i;
for (i = rofl.constBegin(); i != rofl.constEnd(); ++i)
dbgKrita << i.key() << ":" << i.value();
*/
return config;
}
void KisPhongBumpmapConfigWidget::slotDisableHeightChannelCombobox(bool normalmapchecked) {
if (normalmapchecked) {
m_page->heightChannelComboBox->setEnabled(false);
} else {
m_page->heightChannelComboBox->setEnabled(true);
}
}
diff --git a/plugins/filters/phongbumpmap/kis_phong_bumpmap_config_widget.h b/plugins/filters/phongbumpmap/kis_phong_bumpmap_config_widget.h
index aed4fe678b..7367ca4b5d 100644
--- a/plugins/filters/phongbumpmap/kis_phong_bumpmap_config_widget.h
+++ b/plugins/filters/phongbumpmap/kis_phong_bumpmap_config_widget.h
@@ -1,66 +1,66 @@
/*
* Copyright (c) 2010-2011 José Luis Vergara <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PHONG_BUMPMAP_CONFIG_WIDGET_H
#define KIS_PHONG_BUMPMAP_CONFIG_WIDGET_H
#include "ui_wdgphongbumpmap.h"
#include "kis_paint_device.h"
#include "kis_config_widget.h"
#include "kis_image.h"
class KisPhongBumpmapWidget : public QWidget, public Ui::WdgPhongBumpmap
{
Q_OBJECT
public:
KisPhongBumpmapWidget(QWidget *parent) : QWidget(parent)
{
setupUi(this);
ambientReflectivityKisDoubleSliderSpinBox -> setRange(0, 1, 2);
diffuseReflectivityKisDoubleSliderSpinBox -> setRange(0, 1, 2);
specularReflectivityKisDoubleSliderSpinBox -> setRange(0, 1, 2);
shinynessExponentKisSliderSpinBox -> setRange(1, 200);
ambientReflectivityKisDoubleSliderSpinBox -> setValue(0.1);
diffuseReflectivityKisDoubleSliderSpinBox -> setValue(0.5);
specularReflectivityKisDoubleSliderSpinBox -> setValue(0.5);
shinynessExponentKisSliderSpinBox -> setValue(40);
}
};
class KisPhongBumpmapConfigWidget : public KisConfigWidget
{
Q_OBJECT
public:
- KisPhongBumpmapConfigWidget(const KisPaintDeviceSP dev, QWidget *parent, Qt::WFlags f = 0);
+ KisPhongBumpmapConfigWidget(const KisPaintDeviceSP dev, QWidget *parent, Qt::WindowFlags f = 0);
~KisPhongBumpmapConfigWidget() override {}
void setConfiguration(const KisPropertiesConfigurationSP config) override;
KisPropertiesConfigurationSP configuration() const override;
KisPhongBumpmapWidget *m_page;
private:
KisPaintDeviceSP m_device;
private Q_SLOTS:
void slotDisableHeightChannelCombobox(bool normalmapchecked);
};
#endif //KIS_PHONG_BUMPMAP_CONFIG_WIDGET_H
diff --git a/plugins/flake/textshape/CMakeLists.txt b/plugins/flake/textshape/CMakeLists.txt
index c0999ed2fc..bd4eca24d8 100644
--- a/plugins/flake/textshape/CMakeLists.txt
+++ b/plugins/flake/textshape/CMakeLists.txt
@@ -1,160 +1,177 @@
project( textPlugin)
+
+if (${Qt5_VERSION} VERSION_GREATER "5.8.0" )
+ remove_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900)
+elseif(${Qt5_VERSION} VERSION_GREATER "5.7.0" )
+ remove_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50800)
+elseif(${Qt5_VERSION} VERSION_GREATER "5.6.0" )
+ remove_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50700)
+else()
+ remove_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50600)
+endif()
+
+add_definitions(
+ -DQT_DISABLE_DEPRECATED_BEFORE=0
+)
+
add_subdirectory( textlayout )
add_subdirectory( kotext )
+
+
set ( textshape_SRCS
TextPlugin.cpp
TextShape.cpp
TextShapeFactory.cpp
TextTool.cpp
TextEditingPluginContainer.cpp
TextToolFactory.cpp
ShrinkToFitShapeContainer.cpp
SimpleRootAreaProvider.cpp
AnnotationTextShape.cpp
AnnotationTextShapeFactory.cpp
ChangeTracker.cpp
ReviewTool.cpp
ReviewToolFactory.cpp
TextChanges.cpp
TextChange.cpp
FontSizeAction.cpp
FontFamilyAction.cpp
ReferencesTool.cpp
ReferencesToolFactory.cpp
# dialogs/StylesWidget.cpp
# dialogs/SpecialButton.cpp
dialogs/StylesCombo.cpp
dialogs/StylesComboPreview.cpp
dialogs/DockerStylesComboModel.cpp
dialogs/SimpleCharacterWidget.cpp
dialogs/SimpleParagraphWidget.cpp
dialogs/SimpleTableWidget.cpp
dialogs/SimpleInsertWidget.cpp
dialogs/LinkInsertionDialog.cpp
dialogs/SimpleTableOfContentsWidget.cpp
dialogs/SimpleCitationBibliographyWidget.cpp
dialogs/SimpleLinksWidget.cpp
dialogs/SimpleSpellCheckingWidget.cpp
dialogs/CitationInsertionDialog.cpp
dialogs/InsertBibliographyDialog.cpp
dialogs/SimpleFootEndNotesWidget.cpp
dialogs/NotesConfigurationDialog.cpp
dialogs/SimpleCaptionsWidget.cpp
dialogs/ParagraphLayout.cpp
dialogs/ParagraphIndentSpacing.cpp
dialogs/ParagraphDecorations.cpp
dialogs/ParagraphBulletsNumbers.cpp
dialogs/ParagraphSettingsDialog.cpp
dialogs/ParagraphDropCaps.cpp
dialogs/ListsSpinBox.cpp
dialogs/StylesModel.cpp
dialogs/StylesManagerModel.cpp
dialogs/StylesSortFilterProxyModel.cpp
dialogs/AbstractStylesModel.cpp
dialogs/StylesFilteredModelBase.cpp
dialogs/ValidParentStylesProxyModel.cpp
dialogs/StylesDelegate.cpp
dialogs/StyleManager.cpp
dialogs/StyleManagerDialog.cpp
dialogs/ParagraphGeneral.cpp
dialogs/CharacterGeneral.cpp
dialogs/CharacterHighlighting.cpp
dialogs/InsertCharacter.cpp
dialogs/FontDia.cpp
dialogs/FontDecorations.cpp
dialogs/LanguageTab.cpp
dialogs/FormattingPreview.cpp
dialogs/StyleManagerWelcome.cpp
dialogs/TableDialog.cpp
dialogs/QuickTableButton.cpp
dialogs/FormattingButton.cpp
dialogs/ChangeConfigureDialog.cpp
dialogs/AcceptRejectChangeDialog.cpp
dialogs/TrackedChangeModel.cpp
dialogs/TrackedChangeManager.cpp
dialogs/BibliographyConfigureDialog.cpp
dialogs/TableOfContentsConfigure.cpp
dialogs/TableOfContentsStyleConfigure.cpp
dialogs/TableOfContentsStyleModel.cpp
dialogs/TableOfContentsStyleDelegate.cpp
dialogs/TableOfContentsPreview.cpp
dialogs/TableOfContentsEntryDelegate.cpp
dialogs/TableOfContentsEntryModel.cpp
dialogs/TableOfContentsTemplate.cpp
dialogs/BibliographyTemplate.cpp
dialogs/BibliographyPreview.cpp
dialogs/ListLevelChooser.cpp
dialogs/SimpleAnnotationWidget.cpp
dialogs/ManageBookmarkDialog.cpp
dialogs/SectionFormatDialog.cpp
dialogs/SectionsSplitDialog.cpp
commands/ChangeListLevelCommand.cpp
commands/ShowChangesCommand.cpp
commands/AcceptChangeCommand.cpp
commands/RejectChangeCommand.cpp
commands/AutoResizeCommand.cpp
)
ki18n_wrap_ui(textshape_SRCS
dialogs/SimpleCharacterWidget.ui
dialogs/SimpleParagraphWidget.ui
dialogs/SimpleTableWidget.ui
dialogs/SimpleInsertWidget.ui
dialogs/SimpleTableOfContentsWidget.ui
dialogs/SimpleCitationBibliographyWidget.ui
dialogs/SimpleSpellCheckingWidget.ui
dialogs/CitationInsertionDialog.ui
dialogs/InsertBibliographyDialog.ui
dialogs/SimpleFootEndNotesWidget.ui
dialogs/NotesConfigurationDialog.ui
dialogs/SimpleCaptionsWidget.ui
dialogs/StylesWidget.ui
dialogs/ParagraphLayout.ui
dialogs/ParagraphIndentSpacing.ui
dialogs/ParagraphDecorations.ui
dialogs/ParagraphBulletsNumbers.ui
dialogs/ParagraphDropCaps.ui
dialogs/StyleManager.ui
dialogs/CharacterGeneral.ui
dialogs/CharacterHighlighting.ui
dialogs/StyleManagerWelcome.ui
dialogs/TableDialog.ui
dialogs/BibliographyConfigureDialog.ui
dialogs/TableOfContentsConfigure.ui
dialogs/SimpleLinksWidget.ui
dialogs/TableOfContentsStyleConfigure.ui
dialogs/SimpleAnnotationWidget.ui
dialogs/FontDecorations.ui
dialogs/LanguageTab.ui
dialogs/ChangeConfigureDialog.ui
dialogs/AcceptRejectChangeDialog.ui
dialogs/TrackedChangeManager.ui
dialogs/LinkInsertionDialog.ui
dialogs/ManageBookmark.ui
dialogs/SectionFormatDialog.ui
dialogs/SectionsSplitDialog.ui
)
qt5_add_resources(textshape_SRCS textshape.qrc)
add_library(krita_shape_text MODULE ${textshape_SRCS})
target_link_libraries(krita_shape_text kritatext kritatextlayout kritawidgetutils kritawidgets Qt5::Network KF5::Completion KF5::ItemViews KF5::WidgetsAddons
)
if( SHOULD_BUILD_FEATURE_RDF )
target_link_libraries(krita_shape_text ${SOPRANO_LIBRARIES})
endif()
install(TARGETS krita_shape_text DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/plugins/flake/vectorshape/VectorShape.cpp b/plugins/flake/vectorshape/VectorShape.cpp
index 2a80a5cb20..0d847d54ed 100644
--- a/plugins/flake/vectorshape/VectorShape.cpp
+++ b/plugins/flake/vectorshape/VectorShape.cpp
@@ -1,498 +1,490 @@
/* This file is part of the KDE project
*
* Copyright (C) 2009 - 2011 Inge Wallin <inge@lysator.liu.se>
* Copyright (C) 2011 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
// Own
#include "VectorShape.h"
// Posix
#include <math.h>
// Qt
#include <QFontDatabase>
#include <QPen>
#include <QPainter>
#include <QBuffer>
#include <QDataStream>
#include <QMutexLocker>
#include <QThreadPool>
#include <QSvgRenderer>
// Calligra
#include "KoUnit.h"
#include "KoStore.h"
#include "KoXmlNS.h"
#include "KoXmlReader.h"
#include "KoXmlWriter.h"
#include <KoEmbeddedDocumentSaver.h>
#include <KoShapeLoadingContext.h>
#include <KoOdfLoadingContext.h>
#include <KoShapeSavingContext.h>
#include <KoViewConverter.h>
// Wmf support
#include "WmfPainterBackend.h"
// Vector shape
#include "EmfParser.h"
#include "EmfOutputPainterStrategy.h"
#include "EmfOutputDebugStrategy.h"
#include "SvmParser.h"
#include "SvmPainterBackend.h"
#include "kis_painting_tweaks.h"
// Comment out to get uncached painting, which is good for debugging
//#define VECTORSHAPE_PAINT_UNCACHED
// Comment out to get unthreaded painting, which is good for debugging
//#define VECTORSHAPE_PAINT_UNTHREADED
VectorShape::VectorShape()
: KoFrameShape(KoXmlNS::draw, "image")
, m_type(VectorTypeNone)
, m_isRendering(false)
{
setShapeId(VectorShape_SHAPEID);
// Default size of the shape.
KoShape::setSize(QSizeF(CM_TO_POINT(8), CM_TO_POINT(5)));
m_cache.setMaxCost(3);
}
VectorShape::~VectorShape()
{
// Wait for the render-thread to finish before the shape is allowed to be
// destroyed so we can make sure to prevent crashes or unwanted
// side-effects. Maybe as alternate we could just kill the render-thread...
QMutexLocker locker(&m_mutex);
}
// Methods specific to the vector shape.
QByteArray VectorShape::compressedContents() const
{
return m_contents;
}
VectorShape::VectorType VectorShape::vectorType() const
{
return m_type;
}
void VectorShape::setCompressedContents(const QByteArray &newContents, VectorType vectorType)
{
QMutexLocker locker(&m_mutex);
m_contents = newContents;
m_type = vectorType;
m_cache.clear();
update();
}
RenderThread::RenderThread(const QByteArray &contents, VectorShape::VectorType type,
const QSizeF &size, const QSize &boundingSize, qreal zoomX, qreal zoomY)
: QObject(), QRunnable(),
m_contents(contents), m_type(type),
m_size(size), m_boundingSize(boundingSize), m_zoomX(zoomX), m_zoomY(zoomY)
{
setAutoDelete(true);
}
RenderThread::~RenderThread()
{
}
void RenderThread::run()
{
QImage *image = new QImage(m_boundingSize, QImage::Format_ARGB32);
image->fill(0);
QPainter painter;
if (!painter.begin(image)) {
//kWarning(31000) << "Failed to create image-cache";
delete image;
image = 0;
} else {
painter.scale(m_zoomX, m_zoomY);
draw(painter);
painter.end();
}
emit finished(m_boundingSize, image);
}
void RenderThread::draw(QPainter &painter)
{
// If the data is uninitialized, e.g. because loading failed, draw the null shape.
if (m_contents.isEmpty()) {
drawNull(painter);
return;
}
// Actually draw the contents
switch (m_type) {
case VectorShape::VectorTypeWmf:
drawWmf(painter);
break;
case VectorShape::VectorTypeEmf:
drawEmf(painter);
break;
case VectorShape::VectorTypeSvm:
drawSvm(painter);
break;
case VectorShape::VectorTypeSvg:
drawSvg(painter);
break;
case VectorShape::VectorTypeNone:
default:
drawNull(painter);
}
}
void RenderThread::drawNull(QPainter &painter) const
{
QRectF rect(QPointF(0, 0), m_size);
painter.save();
// Draw a simple cross in a rectangle just to indicate that there is something here.
painter.setPen(QPen(QColor(172, 196, 206)));
painter.drawRect(rect);
painter.drawLine(rect.topLeft(), rect.bottomRight());
painter.drawLine(rect.bottomLeft(), rect.topRight());
painter.restore();
}
void RenderThread::drawWmf(QPainter &painter) const
{
Libwmf::WmfPainterBackend wmfPainter(&painter, m_size);
if (!wmfPainter.load(m_contents)) {
drawNull(painter);
return;
}
painter.save();
// Actually paint the WMF.
wmfPainter.play();
painter.restore();
}
void RenderThread::drawEmf(QPainter &painter) const
{
// FIXME: Make emfOutput use QSizeF
QSize shapeSizeInt(m_size.width(), m_size.height());
//kDebug(31000) << "-------------------------------------------";
//kDebug(31000) << "size: " << shapeSizeInt << m_size;
//kDebug(31000) << "position: " << position();
//kDebug(31000) << "-------------------------------------------";
Libemf::Parser emfParser;
#if 1 // Set to 0 to get debug output
// Create a new painter output strategy. Last param = true means keep aspect ratio.
Libemf::OutputPainterStrategy emfPaintOutput(painter, shapeSizeInt, true);
emfParser.setOutput(&emfPaintOutput);
#else
Libemf::OutputDebugStrategy emfDebugOutput;
emfParser.setOutput(&emfDebugOutput);
#endif
emfParser.load(m_contents);
}
void RenderThread::drawSvm(QPainter &painter) const
{
QSize shapeSizeInt(m_size.width(), m_size.height());
Libsvm::SvmParser svmParser;
// Create a new painter backend.
Libsvm::SvmPainterBackend svmPaintOutput(&painter, shapeSizeInt);
svmParser.setBackend(&svmPaintOutput);
svmParser.parse(m_contents);
}
void RenderThread::drawSvg(QPainter &painter) const
{
QSvgRenderer renderer(m_contents);
renderer.render(&painter, QRectF(0, 0, m_size.width(), m_size.height()));
}
void VectorShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &)
{
#ifdef VECTORSHAPE_PAINT_UNCACHED
bool useCache = false;
#else
bool useCache = true;
#endif
-#ifdef VECTORSHAPE_PAINT_UNTHREADED
- bool asynchronous = false;
-#else
- // Since the backends may use QPainter::drawText we need to make sure to only
- // use threads if the font-backend supports that what is in most cases.
- bool asynchronous = QFontDatabase::supportsThreadedFontRendering();
-#endif
-
- QImage *cache = render(converter, asynchronous, useCache);
+ QImage *cache = render(converter, true, useCache);
if (cache) { // paint cached image
Q_ASSERT(!cache->isNull());
QVector<QRect> clipRects = KisPaintingTweaks::safeClipRegion(painter).rects();
foreach (const QRect &rc, clipRects) {
painter.drawImage(rc.topLeft(), *cache, rc);
}
}
}
void VectorShape::renderFinished(QSize boundingSize, QImage *image)
{
if (image) {
m_cache.insert(boundingSize.height(), image);
update();
}
m_isRendering = false;
}
// ----------------------------------------------------------------
// Loading and Saving
void VectorShape::saveOdf(KoShapeSavingContext &context) const
{
QMutexLocker locker(&m_mutex);
KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver();
KoXmlWriter &xmlWriter = context.xmlWriter();
QString fileName = fileSaver.getFilename("VectorImages/Image");
QByteArray mimeType;
switch (m_type) {
case VectorTypeWmf:
mimeType = "image/x-wmf";
break;
case VectorTypeEmf:
mimeType = "image/x-emf";
break;
case VectorTypeSvm:
mimeType = "image/x-svm"; // mimetype as used inside LO/AOO
break;
case VectorTypeSvg:
mimeType = "image/svg+xml";
default:
// FIXME: What here?
mimeType = "application/x-what";
break;
}
xmlWriter.startElement("draw:frame");
saveOdfAttributes(context, OdfAllAttributes);
fileSaver.embedFile(xmlWriter, "draw:image", fileName, mimeType, qUncompress(m_contents));
xmlWriter.endElement(); // draw:frame
}
bool VectorShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
//kDebug(31000) <<"Loading ODF frame in the vector shape. Element = " << element.tagName();
loadOdfAttributes(element, context, OdfAllAttributes);
return loadOdfFrame(element, context);
}
inline static int read32(const char *buffer, const int offset)
{
// little endian
int result = (int) buffer[offset];
result |= (int) buffer[offset + 1] << 8;
result |= (int) buffer[offset + 2] << 16;
result |= (int) buffer[offset + 3] << 24;
return result;
}
// Load the actual contents within the vector shape.
bool VectorShape::loadOdfFrameElement(const KoXmlElement &element,
KoShapeLoadingContext &context)
{
//kDebug(31000) <<"Loading ODF element: " << element.tagName();
QMutexLocker locker(&m_mutex);
// Get the reference to the vector file. If there is no href, then just return.
const QString href = element.attribute("href");
if (href.isEmpty()) {
return false;
}
// Try to open the embedded file.
KoStore *store = context.odfLoadingContext().store();
bool result = store->open(href);
if (!result) {
return false;
}
int size = store->size();
if (size < 88) {
store->close();
return false;
}
m_contents = store->read(size);
store->close();
if (m_contents.count() < size) {
//kDebug(31000) << "Too few bytes read: " << m_contents.count() << " instead of " << size;
return false;
}
// Try to recognize the type. We should do this before the
// compression below, because that's a semi-expensive operation.
m_type = vectorType(m_contents);
// Return false if we didn't manage to identify the type.
if (m_type == VectorTypeNone) {
return false;
}
// Compress for biiiig memory savings.
m_contents = qCompress(m_contents);
return true;
}
void VectorShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const
{
render(converter, asynchronous, true);
}
QImage *VectorShape::render(const KoViewConverter &converter, bool asynchronous, bool useCache) const
{
QRectF rect = converter.documentToView(boundingRect());
int id = rect.size().toSize().height();
QImage *cache = useCache ? m_cache[id] : 0;
if (!cache || cache->isNull()) { // recreate the cached image
cache = 0;
if (!m_isRendering) {
m_isRendering = true;
qreal zoomX, zoomY;
converter.zoom(&zoomX, &zoomY);
QMutexLocker locker(&m_mutex);
const QByteArray uncompressedContents =
m_type != VectorShape::VectorTypeNone ? qUncompress(m_contents) : QByteArray();
RenderThread *t = new RenderThread(uncompressedContents, m_type, size(), rect.size().toSize(), zoomX, zoomY);
connect(t, SIGNAL(finished(QSize,QImage*)), this, SLOT(renderFinished(QSize,QImage*)));
if (asynchronous) { // render and paint the image threaded
QThreadPool::globalInstance()->start(t);
} else { // non-threaded rendering and painting of the image
t->run();
cache = m_cache[id];
}
}
}
return cache;
}
VectorShape::VectorType VectorShape::vectorType(const QByteArray &newContents)
{
VectorType vectorType;
if (isWmf(newContents)) {
vectorType = VectorShape::VectorTypeWmf;
} else if (isEmf(newContents)) {
vectorType = VectorShape::VectorTypeEmf;
} else if (isSvm(newContents)) {
vectorType = VectorShape::VectorTypeSvm;
} else if (isSvg(newContents)) {
vectorType = VectorShape::VectorTypeSvg;
} else {
vectorType = VectorShape::VectorTypeNone;
}
return vectorType;
}
bool VectorShape::isWmf(const QByteArray &bytes)
{
//kDebug(31000) << "Check for WMF";
const char *data = bytes.constData();
const int size = bytes.count();
if (size < 10) {
return false;
}
// This is how the 'file' command identifies a WMF.
if (data[0] == '\327' && data[1] == '\315' && data[2] == '\306' && data[3] == '\232') {
// FIXME: Is this a compressed wmf? Check it up.
//kDebug(31000) << "WMF identified: header 1";
return true;
}
if (data[0] == '\002' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000') {
//kDebug(31000) << "WMF identified: header 2";
return true;
}
if (data[0] == '\001' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000') {
//kDebug(31000) << "WMF identified: header 3";
return true;
}
return false;
}
bool VectorShape::isEmf(const QByteArray &bytes)
{
//kDebug(31000) << "Check for EMF";
const char *data = bytes.constData();
const int size = bytes.count();
// This is how the 'file' command identifies an EMF.
// 1. Check type
qint32 mark = read32(data, 0);
if (mark != 0x00000001) {
//kDebug(31000) << "Not an EMF: mark = " << mark << " instead of 0x00000001";
return false;
}
// 2. An EMF has the string " EMF" at the start + offset 40.
if (size > 44
&& data[40] == ' ' && data[41] == 'E' && data[42] == 'M' && data[43] == 'F') {
//kDebug(31000) << "EMF identified";
return true;
}
return false;
}
bool VectorShape::isSvm(const QByteArray &bytes)
{
//kDebug(31000) << "Check for SVM";
// Check the SVM signature.
if (bytes.startsWith("VCLMTF")) {
//kDebug(31000) << "SVM identified";
return true;
}
return false;
}
bool VectorShape::isSvg(const QByteArray &bytes)
{
//kDebug(31000) << "Check for SVG";
return (bytes.contains("svg"));
}
diff --git a/plugins/flake/vectorshape/VectorTool.cpp b/plugins/flake/vectorshape/VectorTool.cpp
index 026cfffc4a..8b3aff4cdb 100644
--- a/plugins/flake/vectorshape/VectorTool.cpp
+++ b/plugins/flake/vectorshape/VectorTool.cpp
@@ -1,117 +1,117 @@
/* This file is part of the KDE project
Copyright 2007 Montel Laurent <montel@kde.org>
Copyright 2011 Boudewijn Rempt <boud@valdyas.org>
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 "VectorTool.h"
#include "VectorShape.h"
#include "ChangeVectorDataCommand.h"
#include <QToolButton>
#include <QGridLayout>
-#include <QDesktopServices>
+#include <QStandardPaths>
#include <klocalizedstring.h>
#include <KoFileDialog.h>
#include <KoIcon.h>
#include <KoCanvasBase.h>
#include <KoImageCollection.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoPointerEvent.h>
VectorTool::VectorTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_shape(0)
{
}
void VectorTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
KoToolBase::activate(activation, shapes);
foreach (KoShape *shape, shapes) {
m_shape = dynamic_cast<VectorShape *>(shape);
if (m_shape) {
break;
}
}
if (!m_shape) {
emit done();
return;
}
useCursor(Qt::ArrowCursor);
}
void VectorTool::deactivate()
{
m_shape = 0;
KoToolBase::deactivate();
}
QWidget *VectorTool::createOptionWidget()
{
QWidget *optionWidget = new QWidget();
QGridLayout *layout = new QGridLayout(optionWidget);
QToolButton *button = 0;
button = new QToolButton(optionWidget);
button->setIcon(koIcon("document-open"));
button->setToolTip(i18n("Open Vector Image (EMF/WMF/SVM)"));
layout->addWidget(button, 0, 0);
connect(button, SIGNAL(clicked(bool)), this, SLOT(changeUrlPressed()));
return optionWidget;
}
void VectorTool::changeUrlPressed()
{
if (m_shape == 0) {
return;
}
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setCaption(i18n("Select a Vector Image"));
- dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
+ dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(QString("image/x-emf,image/x-wmf,image/x-svm,image/svg+xml").split(','));
QString fn = dialog.filename();
if (!fn.isEmpty()) {
QFile f(fn);
if (f.exists()) {
f.open(QFile::ReadOnly);
QByteArray ba = f.readAll();
f.close();
if (!ba.isEmpty()) {
const VectorShape::VectorType vectorType = VectorShape::vectorType(ba);
ChangeVectorDataCommand *cmd = new ChangeVectorDataCommand(m_shape, qCompress(ba), vectorType);
canvas()->addCommand(cmd);
}
}
}
}
void VectorTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
if (canvas()->shapeManager()->shapeAt(event->point) != m_shape) {
event->ignore(); // allow the event to be used by another
return;
}
changeUrlPressed();
}
diff --git a/plugins/impex/exr/krita_exr.desktop b/plugins/impex/exr/krita_exr.desktop
index 984b34b858..4890260c34 100644
--- a/plugins/impex/exr/krita_exr.desktop
+++ b/plugins/impex/exr/krita_exr.desktop
@@ -1,121 +1,122 @@
[Desktop Entry]
Categories=Qt;KDE;Office;Graphics;
Exec=krita %u
GenericName=Application for Drawing and Handling of Images
GenericName[bg]=Приложение за рисуване и обработка на изображения
GenericName[bs]=Aplikacija za crtanje i upravljanje slikom
GenericName[ca]=Aplicació per a dibuix i modificació d'imatges
GenericName[ca@valencia]=Aplicació per a dibuix i modificació d'imatges
GenericName[da]=Tegne- og billedbehandlingsprogram
GenericName[de]=Programm zum Zeichnen und Bearbeiten von Bildern
GenericName[el]=Εφαρμογή για επεξεργασία και χειρισμό εικόνων
GenericName[en_GB]=Application for Drawing and Handling of Images
GenericName[eo]=Aplikaĵo por Desegnado kaj Mastrumado de Bildoj
GenericName[es]=Aplicación para dibujo y manipulación de imágenes
GenericName[et]=Joonistamise ja pilditöötluse rakendus
GenericName[eu]=Irudiak marrazteko eta manipulatzeko aplikazioa
GenericName[fa]=کاربرد برای ترسیم و به کار بردن تصاویر
GenericName[fi]=Ohjelma kuvien piirtämiseen ja käsittelyyn
GenericName[fr]=Application pour dessiner et manipuler des images
GenericName[fy]=Aplikaasje om ôfbyldings mei te tekenjen en te bewurkjen
GenericName[ga]=Feidhmchlár le haghaidh Líníochta agus Láimhseála Íomhánna
GenericName[gl]=Aplicativo de debuxo e edición de imaxes
GenericName[he]=יישום לצביעה וניהול תמונות
GenericName[hi]=छवियों को ड्रा करने तथा उन्हें प्रबन्धित करने का अनुप्रयोग
GenericName[hne]=फोटू मन ल ड्रा करे अउ ओ मन ल प्रबन्धित करे के अनुपरयोग
GenericName[hu]=Rajzoló és képkezelő
GenericName[is]=Teikni og myndvinnsluforrit
GenericName[it]=Applicazione di disegno e gestione di immagini
GenericName[ja]=描画と画像操作のためのアプリケーション
GenericName[kk]=Кескінді салу және өңдеу бағдарламасы
GenericName[ko]=그림 그리기 및 처리 프로그램
GenericName[lv]=Programma zīmēšanai un attēlu apstrādei
GenericName[nb]=Program for tegning og bildehåndtering
GenericName[nds]=Programm för't Teken un Bildhanteren
GenericName[ne]=रेखाचित्र बनाउन र छविको ह्यान्डल गर्नका लागि अनुप्रयोग
GenericName[nl]=Toepassing om afbeeldingen te tekenen en te bewerken
GenericName[pl]=Program do rysowania i obróbki obrazów
GenericName[pt]=Aplicação de Desenho e Manipulação de Imagens
GenericName[pt_BR]=Aplicativo de desenho e manipulação de imagens
GenericName[ru]=Приложение для рисования и редактирования изображений
GenericName[sk]=Aplikácia na kresnenie a manilupáciu s obrázkami
GenericName[sl]=Program za risanje in rokovanje s slikami
GenericName[sv]=Program för att rita och hantera bilder
GenericName[ta]=பிம்பங்களை கையாளுதல் மற்றும் வரைதலுக்கான பயன்னாடு
GenericName[tr]=Çizim ve Resim İşleme Uygulaması
GenericName[uk]=Програма для малювання і обробки зображень
GenericName[uz]=Rasm chizish dasturi
GenericName[uz@cyrillic]=Расм чизиш дастури
GenericName[wa]=Programe po dessiner et apougnî des imådjes
+GenericName[x-test]=xxApplication for Drawing and Handling of Imagesxx
GenericName[zh_CN]=绘制和处理图像的应用程序
GenericName[zh_TW]=影像繪製與處理應用程式
Icon=calligrakrita
MimeType=image/exr;
Name=Krita
Name[af]=Krita
Name[bg]=Krita
Name[br]=Krita
Name[bs]=Krita
Name[ca]=Krita
Name[ca@valencia]=Krita
Name[cs]=Krita
Name[cy]=Krita
Name[da]=Krita
Name[de]=Krita
Name[el]=Krita
Name[en_GB]=Krita
Name[eo]=Krita
Name[es]=Krita
Name[et]=Krita
Name[eu]=Krita
Name[fi]=Krita
Name[fr]=Krita
Name[fy]=Krita
Name[ga]=Krita
Name[gl]=Krita
Name[he]=Krita
Name[hi]=केरिता
Name[hne]=केरिता
Name[hr]=Krita
Name[hu]=Krita
Name[ia]=Krita
Name[is]=Krita
Name[it]=Krita
Name[ja]=Krita
Name[kk]=Krita
Name[ko]=Krita
Name[lt]=Krita
Name[lv]=Krita
Name[mr]=क्रिटा
Name[ms]=Krita
Name[nb]=Krita
Name[nds]=Krita
Name[ne]=क्रिता
Name[nl]=Krita
Name[pl]=Krita
Name[pt]=Krita
Name[pt_BR]=Krita
Name[ro]=Krita
Name[ru]=Krita
Name[se]=Krita
Name[sk]=Krita
Name[sl]=Krita
Name[sv]=Krita
Name[ta]=கிரிட்டா
Name[tg]=Krita
Name[tr]=Krita
Name[ug]=Krita
Name[uk]=Krita
Name[uz]=Krita
Name[uz@cyrillic]=Krita
Name[wa]=Krita
Name[xh]=Krita
Name[x-test]=xxKritaxx
Name[zh_CN]=Krita
Name[zh_TW]=繪圖_Krita
StartupNotify=true
Terminal=false
Type=Application
X-KDE-SubstituteUID=false
X-KDE-Username=
NoDisplay=true
diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp
index 79b5976bfa..8724b36ae2 100644
--- a/plugins/impex/kra/kra_converter.cpp
+++ b/plugins/impex/kra/kra_converter.cpp
@@ -1,353 +1,352 @@
/*
* Copyright (C) 2016 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kra_converter.h"
#include <QApplication>
#include <QFileInfo>
#include <QScopedPointer>
#include <QUrl>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoColorSpaceRegistry.h>
#include <KoDocumentInfo.h>
#include <KoXmlWriter.h>
#include <KoXmlReader.h>
#include <KritaVersionWrapper.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_png_converter.h>
#include <KisDocument.h>
static const char CURRENT_DTD_VERSION[] = "2.0";
KraConverter::KraConverter(KisDocument *doc)
: m_doc(doc)
, m_image(doc->savingImage())
{
}
KraConverter::~KraConverter()
{
delete m_store;
delete m_kraSaver;
delete m_kraLoader;
}
KisImageBuilder_Result KraConverter::buildImage(QIODevice *io)
{
m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip);
if (m_store->bad()) {
m_doc->setErrorMessage(i18n("Not a valid Krita file"));
return KisImageBuilder_RESULT_FAILURE;
}
bool success;
{
if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml)
KoXmlDocument doc;
bool ok = oldLoadAndParse(m_store, "root", doc);
if (ok)
ok = loadXML(doc, m_store);
if (!ok) {
return KisImageBuilder_RESULT_FAILURE;
}
} else {
errUI << "ERROR: No maindoc.xml" << endl;
m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'."));
return KisImageBuilder_RESULT_FAILURE;
}
if (m_store->hasFile("documentinfo.xml")) {
KoXmlDocument doc;
if (oldLoadAndParse(m_store, "documentinfo.xml", doc)) {
m_doc->documentInfo()->load(doc);
}
}
success = completeLoading(m_store);
}
return success ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE;
}
KisImageSP KraConverter::image()
{
return m_image;
}
vKisNodeSP KraConverter::activeNodes()
{
return m_activeNodes;
}
QList<KisPaintingAssistantSP> KraConverter::assistants()
{
return m_assistants;
}
KisImageBuilder_Result KraConverter::buildFile(QIODevice *io)
{
m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip);
if (m_store->bad()) {
m_doc->setErrorMessage(i18n("Could not create the file for saving"));
return KisImageBuilder_RESULT_FAILURE;
}
bool result = false;
m_kraSaver = new KisKraSaver(m_doc);
result = saveRootDocuments(m_store);
if (!result) {
return KisImageBuilder_RESULT_FAILURE;
}
result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true);
if (!result) {
qWarning() << "saving key frames failed";
}
result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving());
if (!result) {
qWarning() << "saving binary data failed";
}
if (!m_store->finalize()) {
return KisImageBuilder_RESULT_FAILURE;
}
if (!m_kraSaver->errorMessages().isEmpty()) {
m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n"));
return KisImageBuilder_RESULT_FAILURE;
}
return KisImageBuilder_RESULT_OK;
}
bool KraConverter::saveRootDocuments(KoStore *store)
{
dbgFile << "Saving root";
if (store->open("root")) {
KoStoreDevice dev(store);
if (!saveToStream(&dev) || !store->close()) {
dbgUI << "saveToStream failed";
return false;
}
} else {
m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml")));
return false;
}
bool success = false;
if (store->open("documentinfo.xml")) {
QDomDocument doc = KisDocument::createDomDocument("document-info"
/*DTD name*/, "document-info" /*tag name*/, "1.1");
doc = m_doc->documentInfo()->save(doc);
KoStoreDevice dev(store);
QByteArray s = doc.toByteArray(); // this is already Utf8!
success = dev.write(s.data(), s.size());
store->close();
}
if (store->open("preview.png")) {
// ### TODO: missing error checking (The partition could be full!)
savePreview(store);
(void)store->close();
}
dbgUI << "Saving done of url:" << m_doc->url().toLocalFile();
// Success
return success;
}
bool KraConverter::saveToStream(QIODevice *dev)
{
QDomDocument doc = createDomDocument();
// Save to buffer
QByteArray s = doc.toByteArray(); // utf8 already
dev->open(QIODevice::WriteOnly);
int nwritten = dev->write(s.data(), s.size());
if (nwritten != (int)s.size()) {
warnUI << "wrote " << nwritten << "- expected" << s.size();
}
return nwritten == (int)s.size();
}
QDomDocument KraConverter::createDomDocument()
{
QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION);
QDomElement root = doc.documentElement();
root.setAttribute("editor", "Krita");
root.setAttribute("syntaxVersion", "2");
root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false));
root.appendChild(m_kraSaver->saveXML(doc, m_image));
if (!m_kraSaver->errorMessages().isEmpty()) {
m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n"));
}
return doc;
}
bool KraConverter::savePreview(KoStore *store)
{
QPixmap pix = m_doc->generatePreview(QSize(256, 256));
QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly));
if (preview.size() == QSize(0,0)) {
QSize newSize = m_doc->savingImage()->bounds().size();
newSize.scale(QSize(256, 256), Qt::KeepAspectRatio);
preview = QImage(newSize, QImage::Format_ARGB32);
preview.fill(QColor(0, 0, 0, 0));
}
KoStoreDevice io(store);
if (!io.open(QIODevice::WriteOnly)) {
return false;
}
bool ret = preview.save(&io, "PNG");
io.close();
return ret;
}
bool KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc)
{
//dbgUI <<"Trying to open" << filename;
if (!store->open(filename)) {
warnUI << "Entry " << filename << " not found!";
m_doc->setErrorMessage(i18n("Could not find %1", filename));
return false;
}
// Error variables for QDomDocument::setContent
QString errorMsg;
int errorLine, errorColumn;
bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn);
store->close();
if (!ok) {
errUI << "Parsing error in " << filename << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4"
, filename , errorLine, errorColumn ,
- QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0,
- QCoreApplication::UnicodeUTF8)));
+ QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0)));
return false;
}
dbgUI << "File" << filename << " loaded and parsed";
return true;
}
bool KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store)
{
Q_UNUSED(store);
KoXmlElement root;
KoXmlNode node;
if (doc.doctype().name() != "DOC") {
m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted"));
return false;
}
root = doc.documentElement();
int syntaxVersion = root.attribute("syntaxVersion", "3").toInt();
if (syntaxVersion > 2) {
m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion));
return false;
}
if (!root.hasChildNodes()) {
m_doc->setErrorMessage(i18n("The file has no layers."));
return false;
}
m_kraLoader = new KisKraLoader(m_doc, syntaxVersion);
// Legacy from the multi-image .kra file period.
for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) {
if (node.isElement()) {
if (node.nodeName() == "IMAGE") {
KoXmlElement elem = node.toElement();
if (!(m_image = m_kraLoader->loadXML(elem))) {
if (m_kraLoader->errorMessages().isEmpty()) {
m_doc->setErrorMessage(i18n("Unknown error."));
}
else {
m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n"));
}
return false;
}
return true;
}
else {
if (m_kraLoader->errorMessages().isEmpty()) {
m_doc->setErrorMessage(i18n("The file does not contain an image."));
}
return false;
}
}
}
return false;
}
bool KraConverter::completeLoading(KoStore* store)
{
if (!m_image) {
if (m_kraLoader->errorMessages().isEmpty()) {
m_doc->setErrorMessage(i18n("Unknown error."));
}
else {
m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n"));
}
return false;
}
m_image->blockUpdates();
m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true);
m_image->unblockUpdates();
bool retval = true;
if (!m_kraLoader->warningMessages().isEmpty()) {
m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n"));
retval = true;
}
if (retval) {
m_activeNodes = m_kraLoader->selectedNodes();
m_assistants = m_kraLoader->assistants();
}
return retval;
}
void KraConverter::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp
index 3ad6aba6f2..2ed38dcf05 100644
--- a/plugins/impex/libkra/kis_kra_loader.cpp
+++ b/plugins/impex/libkra/kis_kra_loader.cpp
@@ -1,1154 +1,1155 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2007
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_kra_loader.h"
#include <QApplication>
#include <QStringList>
#include <QMessageBox>
#include <QUrl>
#include <QBuffer>
#include <KoStore.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpaceEngine.h>
#include <KoColorProfile.h>
#include <KoDocumentInfo.h>
#include <KoFileDialog.h>
#include <KisImportExportManager.h>
#include <KoXmlReader.h>
#include <KoStoreDevice.h>
#include <filter/kis_filter.h>
#include <filter/kis_filter_registry.h>
#include <generator/kis_generator.h>
#include <generator/kis_generator_layer.h>
#include <generator/kis_generator_registry.h>
#include <kis_adjustment_layer.h>
#include <kis_annotation.h>
#include <kis_base_node.h>
#include <kis_clone_layer.h>
#include <kis_debug.h>
#include <kis_assert.h>
#include <kis_external_layer_iface.h>
#include <kis_filter_mask.h>
#include <kis_transform_mask.h>
#include "lazybrush/kis_colorize_mask.h"
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_name_server.h>
#include <kis_paint_layer.h>
#include <kis_selection.h>
#include <kis_selection_mask.h>
#include <kis_shape_layer.h>
#include <kis_transparency_mask.h>
#include <kis_layer_composition.h>
#include <kis_file_layer.h>
#include <kis_psd_layer_style.h>
#include <kis_psd_layer_style_resource.h>
#include "kis_resource_server_provider.h"
#include "kis_keyframe_channel.h"
#include <kis_filter_configuration.h>
#include "KisDocument.h"
#include "kis_config.h"
#include "kis_kra_tags.h"
#include "kis_kra_utils.h"
#include "kis_kra_load_visitor.h"
#include "kis_dom_utils.h"
#include "kis_image_animation_interface.h"
#include "kis_time_range.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_config.h"
#include "KisProofingConfiguration.h"
#include "kis_layer_properties_icons.h"
#include "kis_node_view_color_scheme.h"
/*
Color model id comparison through the ages:
2.4 2.5 2.6 ideal
ALPHA ALPHA ALPHA ALPHAU8
CMYK CMYK CMYK CMYKAU8
CMYKAF32 CMYKAF32
CMYKA16 CMYKAU16 CMYKAU16
GRAYA GRAYA GRAYA GRAYAU8
GrayF32 GRAYAF32 GRAYAF32
GRAYA16 GRAYAU16 GRAYAU16
LABA LABA LABA LABAU16
LABAF32 LABAF32
LABAU8 LABAU8
RGBA RGBA RGBA RGBAU8
RGBA16 RGBA16 RGBA16 RGBAU16
RgbAF32 RGBAF32 RGBAF32
RgbAF16 RgbAF16 RGBAF16
XYZA16 XYZA16 XYZA16 XYZAU16
XYZA8 XYZA8 XYZAU8
XyzAF16 XyzAF16 XYZAF16
XyzAF32 XYZAF32 XYZAF32
YCbCrA YCBCRA8 YCBCRA8 YCBCRAU8
YCbCrAU16 YCBCRAU16 YCBCRAU16
YCBCRF32 YCBCRF32
*/
using namespace KRA;
struct KisKraLoader::Private
{
public:
KisDocument* document;
QString imageName; // used to be stored in the image, is now in the documentInfo block
QString imageComment; // used to be stored in the image, is now in the documentInfo block
QMap<KisNode*, QString> layerFilenames; // temp storage during loading
int syntaxVersion; // version of the fileformat we are loading
vKisNodeSP selectedNodes; // the nodes that were active when saving the document.
QMap<QString, QString> assistantsFilenames;
QList<KisPaintingAssistantSP> assistants;
QMap<KisNode*, QString> keyframeFilenames;
QStringList errorMessages;
QStringList warningMessages;
};
void convertColorSpaceNames(QString &colorspacename, QString &profileProductName) {
if (colorspacename == "Grayscale + Alpha") {
colorspacename = "GRAYA";
profileProductName.clear();
}
else if (colorspacename == "RgbAF32") {
colorspacename = "RGBAF32";
profileProductName.clear();
}
else if (colorspacename == "RgbAF16") {
colorspacename = "RGBAF16";
profileProductName.clear();
}
else if (colorspacename == "CMYKA16") {
colorspacename = "CMYKAU16";
}
else if (colorspacename == "GrayF32") {
colorspacename = "GRAYAF32";
profileProductName.clear();
}
else if (colorspacename == "GRAYA16") {
colorspacename = "GRAYAU16";
}
else if (colorspacename == "XyzAF16") {
colorspacename = "XYZAF16";
profileProductName.clear();
}
else if (colorspacename == "XyzAF32") {
colorspacename = "XYZAF32";
profileProductName.clear();
}
else if (colorspacename == "YCbCrA") {
colorspacename = "YCBCRA8";
}
else if (colorspacename == "YCbCrAU16") {
colorspacename = "YCBCRAU16";
}
}
KisKraLoader::KisKraLoader(KisDocument * document, int syntaxVersion)
: m_d(new Private())
{
m_d->document = document;
m_d->syntaxVersion = syntaxVersion;
}
KisKraLoader::~KisKraLoader()
{
delete m_d;
}
KisImageSP KisKraLoader::loadXML(const KoXmlElement& element)
{
QString attr;
KisImageSP image = 0;
QString name;
qint32 width;
qint32 height;
QString profileProductName;
double xres;
double yres;
QString colorspacename;
const KoColorSpace * cs;
if ((attr = element.attribute(MIME)) == NATIVE_MIMETYPE) {
if ((m_d->imageName = element.attribute(NAME)).isNull()) {
m_d->errorMessages << i18n("Image does not have a name.");
return KisImageSP(0);
}
if ((attr = element.attribute(WIDTH)).isNull()) {
m_d->errorMessages << i18n("Image does not specify a width.");
return KisImageSP(0);
}
width = KisDomUtils::toInt(attr);
if ((attr = element.attribute(HEIGHT)).isNull()) {
m_d->errorMessages << i18n("Image does not specify a height.");
return KisImageSP(0);
}
height = KisDomUtils::toInt(attr);
m_d->imageComment = element.attribute(DESCRIPTION);
xres = 100.0 / 72.0;
if (!(attr = element.attribute(X_RESOLUTION)).isNull()) {
qreal value = KisDomUtils::toDouble(attr);
if (value > 1.0) {
xres = value / 72.0;
}
}
yres = 100.0 / 72.0;
if (!(attr = element.attribute(Y_RESOLUTION)).isNull()) {
qreal value = KisDomUtils::toDouble(attr);
if (value > 1.0) {
yres = value / 72.0;
}
}
if ((colorspacename = element.attribute(COLORSPACE_NAME)).isNull()) {
// An old file: take a reasonable default.
// Krita didn't support anything else in those
// days anyway.
colorspacename = "RGBA";
}
profileProductName = element.attribute(PROFILE);
// A hack for an old colorspacename
convertColorSpaceNames(colorspacename, profileProductName);
QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id();
QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id();
if (profileProductName.isNull()) {
// no mention of profile so get default profile";
cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, "");
} else {
cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, profileProductName);
}
if (cs == 0) {
// try once more without the profile
cs = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, "");
if (cs == 0) {
m_d->errorMessages << i18n("Image specifies an unsupported color model: %1.", colorspacename);
return KisImageSP(0);
}
}
KisImageConfig cfgImage;
KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration();
if (!(attr = element.attribute(PROOFINGPROFILENAME)).isNull()) {
proofingConfig->proofingProfile = attr;
}
if (!(attr = element.attribute(PROOFINGMODEL)).isNull()) {
proofingConfig->proofingModel = attr;
}
if (!(attr = element.attribute(PROOFINGDEPTH)).isNull()) {
proofingConfig->proofingDepth = attr;
}
if (!(attr = element.attribute(PROOFINGINTENT)).isNull()) {
proofingConfig->intent = (KoColorConversionTransformation::Intent) KisDomUtils::toInt(attr);
}
if (!(attr = element.attribute(PROOFINGADAPTATIONSTATE)).isNull()) {
proofingConfig->adaptationState = KisDomUtils::toDouble(attr);
}
if (m_d->document) {
image = new KisImage(m_d->document->createUndoStore(), width, height, cs, name);
}
else {
image = new KisImage(0, width, height, cs, name);
}
image->setResolution(xres, yres);
loadNodes(element, image, const_cast<KisGroupLayer*>(image->rootLayer().data()));
KoXmlNode child;
for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) {
KoXmlElement e = child.toElement();
if(e.tagName() == CANVASPROJECTIONCOLOR) {
if (e.hasAttribute(COLORBYTEDATA)) {
QByteArray colorData = QByteArray::fromBase64(e.attribute(COLORBYTEDATA).toLatin1());
KoColor color((const quint8*)colorData.data(), image->colorSpace());
image->setDefaultProjectionColor(color);
}
}
if(e.tagName()== PROOFINGWARNINGCOLOR) {
QDomDocument dom;
KoXml::asQDomElement(dom, e);
QDomElement eq = dom.firstChildElement();
proofingConfig->warningColor = KoColor::fromXML(eq.firstChildElement(), Integer8BitsColorDepthID.id());
}
if (e.tagName().toLower() == "animation") {
loadAnimationMetadata(e, image);
}
}
image->setProofingConfiguration(proofingConfig);
for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) {
KoXmlElement e = child.toElement();
if(e.tagName() == "compositions") {
loadCompositions(e, image);
}
}
}
KoXmlNode child;
for (child = element.lastChild(); !child.isNull(); child = child.previousSibling()) {
KoXmlElement e = child.toElement();
if (e.tagName() == "grid") {
loadGrid(e);
} else if (e.tagName() == "guides") {
loadGuides(e);
} else if (e.tagName() == "assistants") {
loadAssistantsList(e);
} else if (e.tagName() == "audio") {
loadAudio(e, image);
}
}
return image;
}
void KisKraLoader::loadBinaryData(KoStore * store, KisImageSP image, const QString & uri, bool external)
{
// icc profile: if present, this overrides the profile product name loaded in loadXML.
QString location = external ? QString() : uri;
location += m_d->imageName + ICC_PATH;
if (store->hasFile(location)) {
if (store->open(location)) {
QByteArray data; data.resize(store->size());
bool res = (store->read(data.data(), store->size()) > -1);
store->close();
if (res) {
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(image->colorSpace()->colorModelId().id(), image->colorSpace()->colorDepthId().id(), data);
if (profile && profile->valid()) {
res = image->assignImageProfile(profile);
}
if (!res) {
const QString defaultProfileId = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(image->colorSpace()->id());
profile = KoColorSpaceRegistry::instance()->profileByName(defaultProfileId);
Q_ASSERT(profile && profile->valid());
image->assignImageProfile(profile);
}
}
}
}
//load the embed proofing profile, it only needs to be loaded into Krita, not assigned.
location = external ? QString() : uri;
location += m_d->imageName + ICC_PROOFING_PATH;
if (store->hasFile(location)) {
if (store->open(location)) {
QByteArray proofingData;
proofingData.resize(store->size());
bool proofingProfileRes = (store->read(proofingData.data(), store->size())>-1);
store->close();
- if (proofingProfileRes)
- {
- const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(image->proofingConfiguration()->proofingModel, image->proofingConfiguration()->proofingDepth, proofingData);
- if (proofingProfile->valid()){
- //if (KoColorSpaceEngineRegistry::instance()->get("icc")) {
- // KoColorSpaceEngineRegistry::instance()->get("icc")->addProfile(proofingProfile->fileName());
- //}
+ KisProofingConfigurationSP proofingConfig = image->proofingConfiguration();
+ if (!proofingConfig) {
+ proofingConfig = KisImageConfig().defaultProofingconfiguration();
+ }
+
+ if (proofingProfileRes) {
+ const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->createColorProfile(proofingConfig->proofingModel, proofingConfig->proofingDepth, proofingData);
+ if (proofingProfile->valid()){
KoColorSpaceRegistry::instance()->addProfile(proofingProfile);
}
}
}
}
// Load the layers data: if there is a profile associated with a layer it will be set now.
KisKraLoadVisitor visitor(image, store, m_d->layerFilenames, m_d->keyframeFilenames, m_d->imageName, m_d->syntaxVersion);
if (external) {
visitor.setExternalUri(uri);
}
image->rootLayer()->accept(visitor);
if (!visitor.errorMessages().isEmpty()) {
m_d->errorMessages.append(visitor.errorMessages());
}
if (!visitor.warningMessages().isEmpty()) {
m_d->warningMessages.append(visitor.warningMessages());
}
// annotations
// exif
location = external ? QString() : uri;
location += m_d->imageName + EXIF_PATH;
if (store->hasFile(location)) {
QByteArray data;
store->open(location);
data = store->read(store->size());
store->close();
image->addAnnotation(KisAnnotationSP(new KisAnnotation("exif", "", data)));
}
// layer styles
location = external ? QString() : uri;
location += m_d->imageName + LAYER_STYLES_PATH;
if (store->hasFile(location)) {
KisPSDLayerStyleCollectionResource *collection =
new KisPSDLayerStyleCollectionResource("Embedded Styles.asl");
collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_d->imageName));
KIS_ASSERT_RECOVER_NOOP(!collection->valid());
store->open(location);
{
KoStoreDevice device(store);
device.open(QIODevice::ReadOnly);
/**
* ASL loading code cannot work with non-sequential IO devices,
* so convert the device beforehand!
*/
QByteArray buf = device.readAll();
QBuffer raDevice(&buf);
raDevice.open(QIODevice::ReadOnly);
collection->loadFromDevice(&raDevice);
}
store->close();
if (collection->valid()) {
KoResourceServer<KisPSDLayerStyleCollectionResource> *server = KisResourceServerProvider::instance()->layerStyleCollectionServer();
server->addResource(collection, false);
collection->assignAllLayerStyles(image->root());
} else {
warnKrita << "WARNING: Couldn't load layer styles library from .kra!";
delete collection;
}
}
if (m_d->document && m_d->document->documentInfo()->aboutInfo("title").isNull())
m_d->document->documentInfo()->setAboutInfo("title", m_d->imageName);
if (m_d->document && m_d->document->documentInfo()->aboutInfo("comment").isNull())
m_d->document->documentInfo()->setAboutInfo("comment", m_d->imageComment);
loadAssistants(store, uri, external);
}
vKisNodeSP KisKraLoader::selectedNodes() const
{
return m_d->selectedNodes;
}
QList<KisPaintingAssistantSP> KisKraLoader::assistants() const
{
return m_d->assistants;
}
QStringList KisKraLoader::errorMessages() const
{
return m_d->errorMessages;
}
QStringList KisKraLoader::warningMessages() const
{
return m_d->warningMessages;
}
void KisKraLoader::loadAssistants(KoStore *store, const QString &uri, bool external)
{
QString file_path;
QString location;
QMap<int ,KisPaintingAssistantHandleSP> handleMap;
KisPaintingAssistant* assistant = 0;
QMap<QString,QString>::const_iterator loadedAssistant = m_d->assistantsFilenames.constBegin();
while (loadedAssistant != m_d->assistantsFilenames.constEnd()){
const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(loadedAssistant.value());
if (factory) {
assistant = factory->createPaintingAssistant();
location = external ? QString() : uri;
location += m_d->imageName + ASSISTANTS_PATH;
file_path = location + loadedAssistant.key();
assistant->loadXml(store, handleMap, file_path);
//If an assistant has too few handles than it should according to it's own setup, just don't load it//
if (assistant->handles().size()==assistant->numHandles()){
m_d->assistants.append(toQShared(assistant));
}
}
loadedAssistant++;
}
}
void KisKraLoader::loadAnimationMetadata(const KoXmlElement &element, KisImageSP image)
{
QDomDocument qDom;
KoXml::asQDomElement(qDom, element);
QDomElement qElement = qDom.firstChildElement();
float framerate;
KisTimeRange range;
int currentTime;
KisImageAnimationInterface *animation = image->animationInterface();
if (KisDomUtils::loadValue(qElement, "framerate", &framerate)) {
animation->setFramerate(framerate);
}
if (KisDomUtils::loadValue(qElement, "range", &range)) {
animation->setFullClipRange(range);
}
if (KisDomUtils::loadValue(qElement, "currentTime", &currentTime)) {
animation->switchCurrentTimeAsync(currentTime);
}
}
KisNodeSP KisKraLoader::loadNodes(const KoXmlElement& element, KisImageSP image, KisNodeSP parent)
{
KoXmlNode node = element.firstChild();
KoXmlNode child;
if (!node.isNull()) {
if (node.isElement()) {
if (node.nodeName().toUpper() == LAYERS.toUpper() || node.nodeName().toUpper() == MASKS.toUpper()) {
for (child = node.lastChild(); !child.isNull(); child = child.previousSibling()) {
KisNodeSP node = loadNode(child.toElement(), image, parent);
if (node) {
image->nextLayerName(); // Make sure the nameserver is current with the number of nodes.
image->addNode(node, parent);
if (node->inherits("KisLayer") && KoXml::childNodesCount(child) > 0) {
loadNodes(child.toElement(), image, node);
}
}
}
}
}
}
return parent;
}
KisNodeSP KisKraLoader::loadNode(const KoXmlElement& element, KisImageSP image, KisNodeSP parent)
{
// Nota bene: If you add new properties to layers, you should
// ALWAYS define a default value in case the property is not
// present in the layer definition: this helps a LOT with backward
// compatibility.
QString name = element.attribute(NAME, "No Name");
QUuid id = QUuid(element.attribute(UUID, QUuid().toString()));
qint32 x = element.attribute(X, "0").toInt();
qint32 y = element.attribute(Y, "0").toInt();
qint32 opacity = element.attribute(OPACITY, QString::number(OPACITY_OPAQUE_U8)).toInt();
if (opacity < OPACITY_TRANSPARENT_U8) opacity = OPACITY_TRANSPARENT_U8;
if (opacity > OPACITY_OPAQUE_U8) opacity = OPACITY_OPAQUE_U8;
const KoColorSpace* colorSpace = 0;
if ((element.attribute(COLORSPACE_NAME)).isNull()) {
dbgFile << "No attribute color space for layer: " << name;
colorSpace = image->colorSpace();
}
else {
QString colorspacename = element.attribute(COLORSPACE_NAME);
QString profileProductName;
convertColorSpaceNames(colorspacename, profileProductName);
QString colorspaceModel = KoColorSpaceRegistry::instance()->colorSpaceColorModelId(colorspacename).id();
QString colorspaceDepth = KoColorSpaceRegistry::instance()->colorSpaceColorDepthId(colorspacename).id();
dbgFile << "Searching color space: " << colorspacename << colorspaceModel << colorspaceDepth << " for layer: " << name;
// use default profile - it will be replaced later in completeLoading
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorspaceModel, colorspaceDepth, "");
dbgFile << "found colorspace" << colorSpace;
if (!colorSpace) {
m_d->warningMessages << i18n("Layer %1 specifies an unsupported color model: %2.", name, colorspacename);
return 0;
}
}
const bool visible = element.attribute(VISIBLE, "1") == "0" ? false : true;
const bool locked = element.attribute(LOCKED, "0") == "0" ? false : true;
const bool collapsed = element.attribute(COLLAPSED, "0") == "0" ? false : true;
int colorLabelIndex = element.attribute(COLOR_LABEL, "0").toInt();
QVector<QColor> labels = KisNodeViewColorScheme::instance()->allColorLabels();
if (colorLabelIndex >= labels.size()) {
colorLabelIndex = labels.size() - 1;
}
// Now find out the layer type and do specific handling
QString nodeType;
if (m_d->syntaxVersion == 1) {
nodeType = element.attribute("layertype");
if (nodeType.isEmpty()) {
nodeType = PAINT_LAYER;
}
}
else {
nodeType = element.attribute(NODE_TYPE);
}
if (nodeType.isEmpty()) {
m_d->warningMessages << i18n("Layer %1 has an unsupported type.", name);
return 0;
}
KisNodeSP node = 0;
if (nodeType == PAINT_LAYER)
node = loadPaintLayer(element, image, name, colorSpace, opacity);
else if (nodeType == GROUP_LAYER)
node = loadGroupLayer(element, image, name, colorSpace, opacity);
else if (nodeType == ADJUSTMENT_LAYER)
node = loadAdjustmentLayer(element, image, name, colorSpace, opacity);
else if (nodeType == SHAPE_LAYER)
node = loadShapeLayer(element, image, name, colorSpace, opacity);
else if (nodeType == GENERATOR_LAYER)
node = loadGeneratorLayer(element, image, name, colorSpace, opacity);
else if (nodeType == CLONE_LAYER)
node = loadCloneLayer(element, image, name, colorSpace, opacity);
else if (nodeType == FILTER_MASK)
node = loadFilterMask(element, parent);
else if (nodeType == TRANSFORM_MASK)
node = loadTransformMask(element, parent);
else if (nodeType == TRANSPARENCY_MASK)
node = loadTransparencyMask(element, parent);
else if (nodeType == SELECTION_MASK)
node = loadSelectionMask(image, element, parent);
else if (nodeType == COLORIZE_MASK)
node = loadColorizeMask(image, element, parent, colorSpace);
else if (nodeType == FILE_LAYER) {
node = loadFileLayer(element, image, name, opacity);
}
else {
m_d->warningMessages << i18n("Layer %1 has an unsupported type: %2.", name, nodeType);
return 0;
}
// Loading the node went wrong. Return empty node and leave to
// upstream to complain to the user
if (!node) {
m_d->warningMessages << i18n("Failure loading layer %1 of type: %2.", name, nodeType);
return 0;
}
node->setVisible(visible, true);
node->setUserLocked(locked);
node->setCollapsed(collapsed);
node->setColorLabelIndex(colorLabelIndex);
node->setX(x);
node->setY(y);
node->setName(name);
if (! id.isNull()) // if no uuid in file, new one has been generated already
node->setUuid(id);
if (node->inherits("KisLayer") || node->inherits("KisColorizeMask")) {
QString compositeOpName = element.attribute(COMPOSITE_OP, "normal");
node->setCompositeOpId(compositeOpName);
}
if (node->inherits("KisLayer")) {
KisLayer* layer = qobject_cast<KisLayer*>(node.data());
QBitArray channelFlags = stringToFlags(element.attribute(CHANNEL_FLAGS, ""), colorSpace->channelCount());
layer->setChannelFlags(channelFlags);
if (element.hasAttribute(LAYER_STYLE_UUID)) {
QString uuidString = element.attribute(LAYER_STYLE_UUID);
QUuid uuid(uuidString);
if (!uuid.isNull()) {
KisPSDLayerStyleSP dumbLayerStyle(new KisPSDLayerStyle());
dumbLayerStyle->setUuid(uuid);
layer->setLayerStyle(dumbLayerStyle);
} else {
warnKrita << "WARNING: Layer style for layer" << layer->name() << "contains invalid UUID" << uuidString;
}
}
}
if (node->inherits("KisGroupLayer")) {
if (element.hasAttribute(PASS_THROUGH_MODE)) {
bool value = element.attribute(PASS_THROUGH_MODE, "0") != "0";
KisGroupLayer *group = qobject_cast<KisGroupLayer*>(node.data());
group->setPassThroughMode(value);
}
}
const bool timelineEnabled = element.attribute(VISIBLE_IN_TIMELINE, "0") == "0" ? false : true;
node->setUseInTimeline(timelineEnabled);
if (node->inherits("KisPaintLayer")) {
KisPaintLayer* layer = qobject_cast<KisPaintLayer*>(node.data());
QBitArray channelLockFlags = stringToFlags(element.attribute(CHANNEL_LOCK_FLAGS, ""), colorSpace->channelCount());
layer->setChannelLockFlags(channelLockFlags);
bool onionEnabled = element.attribute(ONION_SKIN_ENABLED, "0") == "0" ? false : true;
layer->setOnionSkinEnabled(onionEnabled);
}
if (element.attribute(FILE_NAME).isNull()) {
m_d->layerFilenames[node.data()] = name;
}
else {
m_d->layerFilenames[node.data()] = element.attribute(FILE_NAME);
}
if (element.hasAttribute("selected") && element.attribute("selected") == "true") {
m_d->selectedNodes.append(node);
}
if (element.hasAttribute(KEYFRAME_FILE)) {
m_d->keyframeFilenames.insert(node.data(), element.attribute(KEYFRAME_FILE));
}
return node;
}
KisNodeSP KisKraLoader::loadPaintLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
Q_UNUSED(element);
KisPaintLayer* layer;
layer = new KisPaintLayer(image, name, opacity, cs);
Q_CHECK_PTR(layer);
return layer;
}
KisNodeSP KisKraLoader::loadFileLayer(const KoXmlElement& element, KisImageSP image, const QString& name, quint32 opacity)
{
QString filename = element.attribute("source", QString());
if (filename.isNull()) return 0;
bool scale = (element.attribute("scale", "true") == "true");
int scalingMethod = element.attribute("scalingmethod", "-1").toInt();
if (scalingMethod < 0) {
if (scale) {
scalingMethod = KisFileLayer::ToImagePPI;
}
else {
scalingMethod = KisFileLayer::None;
}
}
QString documentPath;
if (m_d->document) {
documentPath = m_d->document->url().toLocalFile();
}
QFileInfo info(documentPath);
QString basePath = info.absolutePath();
QString fullPath = QDir(basePath).filePath(QDir::cleanPath(filename));
// Entering the event loop to show the messagebox will delete the image, so up the ref by one
image->ref();
if (!QFileInfo(fullPath).exists()) {
qApp->setOverrideCursor(Qt::ArrowCursor);
QString msg = i18nc(
"@info",
"The file associated to a file layer with the name \"%1\" is not found.\n\n"
"Expected path:\n"
"%2\n\n"
"Do you want to locate it manually?", name, fullPath);
int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (result == QMessageBox::Yes) {
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import));
dialog.setDefaultDir(basePath);
QString url = dialog.filename();
if (!QFileInfo(basePath).exists()) {
filename = url;
} else {
QDir d(basePath);
filename = d.relativeFilePath(url);
}
}
qApp->restoreOverrideCursor();
}
KisLayer *layer = new KisFileLayer(image, basePath, filename, (KisFileLayer::ScalingMethod)scalingMethod, name, opacity);
Q_CHECK_PTR(layer);
return layer;
}
KisNodeSP KisKraLoader::loadGroupLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
Q_UNUSED(element);
Q_UNUSED(cs);
QString attr;
KisGroupLayer* layer;
layer = new KisGroupLayer(image, name, opacity);
Q_CHECK_PTR(layer);
return layer;
}
KisNodeSP KisKraLoader::loadAdjustmentLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
// XXX: do something with filterversion?
Q_UNUSED(cs);
QString attr;
KisAdjustmentLayer* layer;
QString filtername;
if ((filtername = element.attribute(FILTER_NAME)).isNull()) {
// XXX: Invalid adjustmentlayer! We should warn about it!
warnFile << "No filter in adjustment layer";
return 0;
}
KisFilterSP f = KisFilterRegistry::instance()->value(filtername);
if (!f) {
warnFile << "No filter for filtername" << filtername << "";
return 0; // XXX: We don't have this filter. We should warn about it!
}
KisFilterConfigurationSP kfc = f->defaultConfiguration();
// We'll load the configuration and the selection later.
layer = new KisAdjustmentLayer(image, name, kfc, 0);
Q_CHECK_PTR(layer);
layer->setOpacity(opacity);
return layer;
}
KisNodeSP KisKraLoader::loadShapeLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
Q_UNUSED(element);
Q_UNUSED(cs);
QString attr;
KoShapeBasedDocumentBase * shapeController = 0;
if (m_d->document) {
shapeController = m_d->document->shapeController();
}
KisShapeLayer* layer = new KisShapeLayer(shapeController, image, name, opacity);
Q_CHECK_PTR(layer);
return layer;
}
KisNodeSP KisKraLoader::loadGeneratorLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
Q_UNUSED(cs);
// XXX: do something with generator version?
KisGeneratorLayer* layer;
QString generatorname = element.attribute(GENERATOR_NAME);
if (generatorname.isNull()) {
// XXX: Invalid generator layer! We should warn about it!
warnFile << "No generator in generator layer";
return 0;
}
KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorname);
if (!generator) {
warnFile << "No generator for generatorname" << generatorname << "";
return 0; // XXX: We don't have this generator. We should warn about it!
}
KisFilterConfigurationSP kgc = generator->defaultConfiguration();
// We'll load the configuration and the selection later.
layer = new KisGeneratorLayer(image, name, kgc, 0);
Q_CHECK_PTR(layer);
layer->setOpacity(opacity);
return layer;
}
KisNodeSP KisKraLoader::loadCloneLayer(const KoXmlElement& element, KisImageSP image,
const QString& name, const KoColorSpace* cs, quint32 opacity)
{
Q_UNUSED(cs);
KisCloneLayerSP layer = new KisCloneLayer(0, image, name, opacity);
KisCloneInfo info;
if (! (element.attribute(CLONE_FROM_UUID)).isNull()) {
info = KisCloneInfo(QUuid(element.attribute(CLONE_FROM_UUID)));
} else {
if ((element.attribute(CLONE_FROM)).isNull()) {
return 0;
} else {
info = KisCloneInfo(element.attribute(CLONE_FROM));
}
}
layer->setCopyFromInfo(info);
if ((element.attribute(CLONE_TYPE)).isNull()) {
return 0;
} else {
layer->setCopyType((CopyLayerType) element.attribute(CLONE_TYPE).toInt());
}
return layer;
}
KisNodeSP KisKraLoader::loadFilterMask(const KoXmlElement& element, KisNodeSP parent)
{
Q_UNUSED(parent);
QString attr;
KisFilterMask* mask;
QString filtername;
// XXX: should we check the version?
if ((filtername = element.attribute(FILTER_NAME)).isNull()) {
// XXX: Invalid filter layer! We should warn about it!
warnFile << "No filter in filter layer";
return 0;
}
KisFilterSP f = KisFilterRegistry::instance()->value(filtername);
if (!f) {
warnFile << "No filter for filtername" << filtername << "";
return 0; // XXX: We don't have this filter. We should warn about it!
}
KisFilterConfigurationSP kfc = f->defaultConfiguration();
// We'll load the configuration and the selection later.
mask = new KisFilterMask();
mask->setFilter(kfc);
Q_CHECK_PTR(mask);
return mask;
}
KisNodeSP KisKraLoader::loadTransformMask(const KoXmlElement& element, KisNodeSP parent)
{
Q_UNUSED(element);
Q_UNUSED(parent);
KisTransformMask* mask;
/**
* We'll load the transform configuration later on a stage
* of binary data loading
*/
mask = new KisTransformMask();
Q_CHECK_PTR(mask);
return mask;
}
KisNodeSP KisKraLoader::loadTransparencyMask(const KoXmlElement& element, KisNodeSP parent)
{
Q_UNUSED(element);
Q_UNUSED(parent);
KisTransparencyMask* mask = new KisTransparencyMask();
Q_CHECK_PTR(mask);
return mask;
}
KisNodeSP KisKraLoader::loadSelectionMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent)
{
Q_UNUSED(parent);
KisSelectionMaskSP mask = new KisSelectionMask(image);
bool active = element.attribute(ACTIVE, "1") == "0" ? false : true;
mask->setActive(active);
Q_CHECK_PTR(mask);
return mask;
}
KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& element, KisNodeSP parent, const KoColorSpace *colorSpace)
{
Q_UNUSED(parent);
KisColorizeMaskSP mask = new KisColorizeMask();
bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true;
bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true;
KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image);
KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image);
delete mask->setColorSpace(colorSpace);
mask->setImage(image);
return mask;
}
void KisKraLoader::loadCompositions(const KoXmlElement& elem, KisImageSP image)
{
KoXmlNode child;
for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) {
KoXmlElement e = child.toElement();
QString name = e.attribute("name");
bool exportEnabled = e.attribute("exportEnabled", "1") == "0" ? false : true;
KisLayerCompositionSP composition(new KisLayerComposition(image, name));
composition->setExportEnabled(exportEnabled);
KoXmlNode value;
for (value = child.lastChild(); !value.isNull(); value = value.previousSibling()) {
KoXmlElement e = value.toElement();
QUuid uuid(e.attribute("uuid"));
bool visible = e.attribute("visible", "1") == "0" ? false : true;
composition->setVisible(uuid, visible);
bool collapsed = e.attribute("collapsed", "1") == "0" ? false : true;
composition->setCollapsed(uuid, collapsed);
}
image->addComposition(composition);
}
}
void KisKraLoader::loadAssistantsList(const KoXmlElement &elem)
{
KoXmlNode child;
int count = 0;
for (child = elem.firstChild(); !child.isNull(); child = child.nextSibling()) {
KoXmlElement e = child.toElement();
QString type = e.attribute("type");
QString file_name = e.attribute("filename");
m_d->assistantsFilenames.insert(file_name,type);
count++;
}
}
void KisKraLoader::loadGrid(const KoXmlElement& elem)
{
QDomDocument dom;
KoXml::asQDomElement(dom, elem);
QDomElement domElement = dom.firstChildElement();
KisGridConfig config;
config.loadDynamicDataFromXml(domElement);
config.loadStaticData();
m_d->document->setGridConfig(config);
}
void KisKraLoader::loadGuides(const KoXmlElement& elem)
{
QDomDocument dom;
KoXml::asQDomElement(dom, elem);
QDomElement domElement = dom.firstChildElement();
KisGuidesConfig guides;
guides.loadFromXml(domElement);
m_d->document->setGuidesConfig(guides);
}
void KisKraLoader::loadAudio(const KoXmlElement& elem, KisImageSP image)
{
QDomDocument dom;
KoXml::asQDomElement(dom, elem);
QDomElement qElement = dom.firstChildElement();
QString fileName;
if (KisDomUtils::loadValue(qElement, "masterChannelPath", &fileName)) {
fileName = QDir::toNativeSeparators(fileName);
QDir baseDirectory = QFileInfo(m_d->document->localFilePath()).absoluteDir();
fileName = baseDirectory.absoluteFilePath(fileName);
QFileInfo info(fileName);
if (!info.exists()) {
qApp->setOverrideCursor(Qt::ArrowCursor);
QString msg = i18nc(
"@info",
"Audio channel file \"%1\" doesn't exist!\n\n"
"Expected path:\n"
"%2\n\n"
"Do you want to locate it manually?", info.fileName(), info.absoluteFilePath());
int result = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (result == QMessageBox::Yes) {
info.setFile(KisImportExportManager::askForAudioFileName(info.absolutePath(), 0));
}
qApp->restoreOverrideCursor();
}
if (info.exists()) {
image->animationInterface()->setAudioChannelFileName(info.absoluteFilePath());
}
}
bool audioMuted = false;
if (KisDomUtils::loadValue(qElement, "audioMuted", &audioMuted)) {
image->animationInterface()->setAudioMuted(audioMuted);
}
qreal audioVolume = 0.5;
if (KisDomUtils::loadValue(qElement, "audioVolume", &audioVolume)) {
image->animationInterface()->setAudioVolume(audioVolume);
}
}
diff --git a/plugins/impex/libkra/kis_kra_saver.cpp b/plugins/impex/libkra/kis_kra_saver.cpp
index 18802e4d2d..857f9beb98 100644
--- a/plugins/impex/libkra/kis_kra_saver.cpp
+++ b/plugins/impex/libkra/kis_kra_saver.cpp
@@ -1,452 +1,452 @@
/* This file is part of the KDE project
* Copyright 2008 (C) Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_kra_saver.h"
#include "kis_kra_tags.h"
#include "kis_kra_save_visitor.h"
#include "kis_kra_savexml_visitor.h"
#include <QDomDocument>
#include <QDomElement>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QBuffer>
#include <KoDocumentInfo.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpace.h>
#include <KoColorProfile.h>
#include <KoColor.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <kis_annotation.h>
#include <kis_image.h>
#include <kis_image_animation_interface.h>
#include <kis_group_layer.h>
#include <kis_layer.h>
#include <kis_adjustment_layer.h>
#include <kis_layer_composition.h>
#include <kis_painting_assistants_decoration.h>
#include <kis_psd_layer_style_resource.h>
#include "kis_png_converter.h"
#include "kis_keyframe_channel.h"
#include <kis_time_range.h>
#include "KisDocument.h"
#include <string>
#include "kis_dom_utils.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "KisProofingConfiguration.h"
#include <QFileInfo>
#include <QDir>
using namespace KRA;
struct KisKraSaver::Private
{
public:
KisDocument* doc;
QMap<const KisNode*, QString> nodeFileNames;
QMap<const KisNode*, QString> keyframeFilenames;
QString imageName;
QStringList errorMessages;
};
KisKraSaver::KisKraSaver(KisDocument* document)
: m_d(new Private)
{
m_d->doc = document;
m_d->imageName = m_d->doc->documentInfo()->aboutInfo("title");
if (m_d->imageName.isEmpty()) {
m_d->imageName = i18n("Unnamed");
}
}
KisKraSaver::~KisKraSaver()
{
delete m_d;
}
QDomElement KisKraSaver::saveXML(QDomDocument& doc, KisImageSP image)
{
QDomElement imageElement = doc.createElement("IMAGE"); // Legacy!
Q_ASSERT(image);
imageElement.setAttribute(NAME, m_d->imageName);
imageElement.setAttribute(MIME, NATIVE_MIMETYPE);
imageElement.setAttribute(WIDTH, KisDomUtils::toString(image->width()));
imageElement.setAttribute(HEIGHT, KisDomUtils::toString(image->height()));
imageElement.setAttribute(COLORSPACE_NAME, image->colorSpace()->id());
imageElement.setAttribute(DESCRIPTION, m_d->doc->documentInfo()->aboutInfo("comment"));
// XXX: Save profile as blob inside the image, instead of the product name.
if (image->profile() && image->profile()-> valid()) {
imageElement.setAttribute(PROFILE, image->profile()->name());
}
imageElement.setAttribute(X_RESOLUTION, KisDomUtils::toString(image->xRes()*72.0));
imageElement.setAttribute(Y_RESOLUTION, KisDomUtils::toString(image->yRes()*72.0));
//now the proofing options:
- imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile));
- imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel));
- imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth));
- imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent));
- imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState));
+ if (image->proofingConfiguration()) {
+ imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile));
+ imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel));
+ imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth));
+ imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent));
+ imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState));
+ }
quint32 count = 1; // We don't save the root layer, but it does count
KisSaveXmlVisitor visitor(doc, imageElement, count, m_d->doc->url().toLocalFile(), true);
visitor.setSelectedNodes({m_d->doc->preActivatedNode()});
image->rootLayer()->accept(visitor);
m_d->errorMessages.append(visitor.errorMessages());
m_d->nodeFileNames = visitor.nodeFileNames();
m_d->keyframeFilenames = visitor.keyframeFileNames();
saveBackgroundColor(doc, imageElement, image);
saveWarningColor(doc, imageElement, image);
saveCompositions(doc, imageElement, image);
saveAssistantsList(doc, imageElement);
saveGrid(doc,imageElement);
saveGuides(doc,imageElement);
saveAudio(doc,imageElement);
QDomElement animationElement = doc.createElement("animation");
KisDomUtils::saveValue(&animationElement, "framerate", image->animationInterface()->framerate());
KisDomUtils::saveValue(&animationElement, "range", image->animationInterface()->fullClipRange());
KisDomUtils::saveValue(&animationElement, "currentTime", image->animationInterface()->currentUITime());
imageElement.appendChild(animationElement);
return imageElement;
}
bool KisKraSaver::saveKeyframes(KoStore *store, const QString &uri, bool external)
{
QMap<const KisNode*, QString>::iterator it;
for (it = m_d->keyframeFilenames.begin(); it != m_d->keyframeFilenames.end(); it++) {
const KisNode *node = it.key();
QString filename = it.value();
QString location =
(external ? QString() : uri)
+ m_d->imageName + LAYER_PATH + filename;
if (!saveNodeKeyframes(store, location, node)) {
return false;
}
}
return true;
}
bool KisKraSaver::saveNodeKeyframes(KoStore *store, QString location, const KisNode *node)
{
QDomDocument doc = KisDocument::createDomDocument("krita-keyframes", "keyframes", "1.0");
QDomElement root = doc.documentElement();
KisKeyframeChannel *channel;
Q_FOREACH (channel, node->keyframeChannels()) {
QDomElement element = channel->toXML(doc, m_d->nodeFileNames[node]);
root.appendChild(element);
}
if (store->open(location)) {
QByteArray xml = doc.toByteArray();
store->write(xml);
store->close();
} else {
m_d->errorMessages << i18n("could not save keyframes");
return false;
}
return true;
}
bool KisKraSaver::saveBinaryData(KoStore* store, KisImageSP image, const QString &uri, bool external, bool autosave)
{
QString location;
// Save the layers data
KisKraSaveVisitor visitor(store, m_d->imageName, m_d->nodeFileNames);
if (external)
visitor.setExternalUri(uri);
image->rootLayer()->accept(visitor);
m_d->errorMessages.append(visitor.errorMessages());
if (!m_d->errorMessages.isEmpty()) {
return false;
}
// saving annotations
// XXX this only saves EXIF and ICC info. This would probably need
// a redesign of the dtd of the krita file to do this more generally correct
// e.g. have <ANNOTATION> tags or so.
KisAnnotationSP annotation = image->annotation("exif");
if (annotation) {
location = external ? QString() : uri;
location += m_d->imageName + EXIF_PATH;
if (store->open(location)) {
store->write(annotation->annotation());
store->close();
}
}
if (image->profile()) {
const KoColorProfile *profile = image->profile();
KisAnnotationSP annotation;
if (profile) {
QByteArray profileRawData = profile->rawData();
if (!profileRawData.isEmpty()) {
if (profile->type() == "icc") {
annotation = new KisAnnotation(ICC, profile->name(), profile->rawData());
} else {
annotation = new KisAnnotation(PROFILE, profile->name(), profile->rawData());
}
}
}
if (annotation) {
location = external ? QString() : uri;
location += m_d->imageName + ICC_PATH;
if (store->open(location)) {
store->write(annotation->annotation());
store->close();
}
}
}
//This'll embed the profile used for proofing into the kra file.
if (image->proofingConfiguration()) {
const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->profileByName(image->proofingConfiguration()->proofingProfile);
if (proofingProfile && proofingProfile->valid()) {
QByteArray proofingProfileRaw = proofingProfile->rawData();
if (!proofingProfileRaw.isEmpty()) {
annotation = new KisAnnotation(ICCPROOFINGPROFILE, proofingProfile->name(), proofingProfile->rawData());
}
}
if (annotation) {
location = external ? QString() : uri;
location += m_d->imageName + ICC_PROOFING_PATH;
if (store->open(location)) {
store->write(annotation->annotation());
store->close();
}
}
}
{
KisPSDLayerStyleCollectionResource collection("not-nexists.asl");
KIS_ASSERT_RECOVER_NOOP(!collection.valid());
collection.collectAllLayerStyles(image->root());
if (collection.valid()) {
location = external ? QString() : uri;
location += m_d->imageName + LAYER_STYLES_PATH;
if (store->open(location)) {
QBuffer aslBuffer;
aslBuffer.open(QIODevice::WriteOnly);
collection.saveToDevice(&aslBuffer);
aslBuffer.close();
store->write(aslBuffer.buffer());
store->close();
}
}
}
if (!autosave) {
KisPaintDeviceSP dev = image->projection();
KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store);
}
saveAssistants(store, uri,external);
return true;
}
QStringList KisKraSaver::errorMessages() const
{
return m_d->errorMessages;
}
void KisKraSaver::saveBackgroundColor(QDomDocument& doc, QDomElement& element, KisImageSP image)
{
QDomElement e = doc.createElement(CANVASPROJECTIONCOLOR);
KoColor color = image->defaultProjectionColor();
QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize());
e.setAttribute(COLORBYTEDATA, QString(colorData.toBase64()));
element.appendChild(e);
}
void KisKraSaver::saveWarningColor(QDomDocument& doc, QDomElement& element, KisImageSP image)
{
if (image->proofingConfiguration()) {
QDomElement e = doc.createElement(PROOFINGWARNINGCOLOR);
KoColor color = image->proofingConfiguration()->warningColor;
color.toXML(doc, e);
- //QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize());
- //e.setAttribute("ColorData", QString(colorData.toBase64()));
element.appendChild(e);
}
}
void KisKraSaver::saveCompositions(QDomDocument& doc, QDomElement& element, KisImageSP image)
{
if (!image->compositions().isEmpty()) {
QDomElement e = doc.createElement("compositions");
Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) {
composition->save(doc, e);
}
element.appendChild(e);
}
}
bool KisKraSaver::saveAssistants(KoStore* store, QString uri, bool external)
{
QString location;
QMap<QString, int> assistantcounters;
QByteArray data;
QList<KisPaintingAssistantSP> assistants = m_d->doc->assistants();
QMap<KisPaintingAssistantHandleSP, int> handlemap;
if (!assistants.isEmpty()) {
Q_FOREACH (KisPaintingAssistantSP assist, assistants){
if (!assistantcounters.contains(assist->id())){
assistantcounters.insert(assist->id(),0);
}
location = external ? QString() : uri;
location += m_d->imageName + ASSISTANTS_PATH;
location += QString(assist->id()+"%1.assistant").arg(assistantcounters[assist->id()]);
data = assist->saveXml(handlemap);
store->open(location);
store->write(data);
store->close();
assistantcounters[assist->id()]++;
}
}
return true;
}
bool KisKraSaver::saveAssistantsList(QDomDocument& doc, QDomElement& element)
{
int count_ellipse = 0, count_perspective = 0, count_ruler = 0, count_vanishingpoint = 0,count_infiniteruler = 0, count_parallelruler = 0, count_concentricellipse = 0, count_fisheyepoint = 0, count_spline = 0;
QList<KisPaintingAssistantSP> assistants = m_d->doc->assistants();
if (!assistants.isEmpty()) {
QDomElement assistantsElement = doc.createElement("assistants");
Q_FOREACH (KisPaintingAssistantSP assist, assistants){
if (assist->id() == "ellipse"){
assist->saveXmlList(doc, assistantsElement, count_ellipse);
count_ellipse++;
}
else if (assist->id() == "spline"){
assist->saveXmlList(doc, assistantsElement, count_spline);
count_spline++;
}
else if (assist->id() == "perspective"){
assist->saveXmlList(doc, assistantsElement, count_perspective);
count_perspective++;
}
else if (assist->id() == "vanishing point"){
assist->saveXmlList(doc, assistantsElement, count_vanishingpoint);
count_vanishingpoint++;
}
else if (assist->id() == "infinite ruler"){
assist->saveXmlList(doc, assistantsElement, count_infiniteruler);
count_infiniteruler++;
}
else if (assist->id() == "parallel ruler"){
assist->saveXmlList(doc, assistantsElement, count_parallelruler);
count_parallelruler++;
}
else if (assist->id() == "concentric ellipse"){
assist->saveXmlList(doc, assistantsElement, count_concentricellipse);
count_concentricellipse++;
}
else if (assist->id() == "fisheye-point"){
assist->saveXmlList(doc, assistantsElement, count_fisheyepoint);
count_fisheyepoint++;
}
else if (assist->id() == "ruler"){
assist->saveXmlList(doc, assistantsElement, count_ruler);
count_ruler++;
}
}
element.appendChild(assistantsElement);
}
return true;
}
bool KisKraSaver::saveGrid(QDomDocument& doc, QDomElement& element)
{
KisGridConfig config = m_d->doc->gridConfig();
if (!config.isDefault()) {
QDomElement gridElement = config.saveDynamicDataToXml(doc, "grid");
element.appendChild(gridElement);
}
return true;
}
bool KisKraSaver::saveGuides(QDomDocument& doc, QDomElement& element)
{
KisGuidesConfig guides = m_d->doc->guidesConfig();
if (guides.hasGuides()) {
QDomElement guidesElement = guides.saveToXml(doc, "guides");
element.appendChild(guidesElement);
}
return true;
}
bool KisKraSaver::saveAudio(QDomDocument& doc, QDomElement& element)
{
const KisImageAnimationInterface *interface = m_d->doc->image()->animationInterface();
QString fileName = interface->audioChannelFileName();
if (fileName.isEmpty()) return true;
if (!QFileInfo::exists(fileName)) {
m_d->errorMessages << i18n("Audio channel file %1 doesn't exist!", fileName);
return false;
}
const QDir documentDir = QFileInfo(m_d->doc->localFilePath()).absoluteDir();
KIS_ASSERT_RECOVER_RETURN_VALUE(documentDir.exists(), false);
fileName = documentDir.relativeFilePath(fileName);
fileName = QDir::fromNativeSeparators(fileName);
KIS_ASSERT_RECOVER_RETURN_VALUE(!fileName.isEmpty(), false);
QDomElement audioElement = doc.createElement("audio");
KisDomUtils::saveValue(&audioElement, "masterChannelPath", fileName);
KisDomUtils::saveValue(&audioElement, "audioMuted", interface->isAudioMuted());
KisDomUtils::saveValue(&audioElement, "audioVolume", interface->audioVolume());
element.appendChild(audioElement);
return true;
}
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp
index 7ed4cb4b59..8157920dbf 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp
@@ -1,1324 +1,1324 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
* <a href="http://www.digikam.org">http://www.digikam.org</a>
*
* @date 2006-09-13
* @brief LibRaw settings widgets
*
* @author Copyright (C) 2006-2015 by Gilles Caulier
* <a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
* @author Copyright (C) 2006-2011 by Marcel Wiesweg
* <a href="mailto:marcel dot wiesweg at gmx dot de">marcel dot wiesweg at gmx dot de</a>
* @author Copyright (C) 2007-2008 by Guillaume Castagnino
* <a href="mailto:casta at xwing dot info">casta at xwing dot info</a>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dcrawsettingswidget.h"
// C++ includes
#include <cmath>
// Qt includes
#include <QCheckBox>
#include <QLabel>
#include <QWhatsThis>
#include <QGridLayout>
#include <QApplication>
#include <QStyle>
#include <QIcon>
-#include <QDesktopServices>
+#include <QStandardPaths>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "kdcraw.h"
#include "rnuminput.h"
#include "rcombobox.h"
#include "rexpanderbox.h"
#include "libkdcraw_debug.h"
#include <kis_icon_utils.h>
namespace KDcrawIface
{
class Q_DECL_HIDDEN DcrawSettingsWidget::Private
{
public:
Private()
{
autoBrightnessBox = 0;
sixteenBitsImage = 0;
fourColorCheckBox = 0;
brightnessLabel = 0;
brightnessSpinBox = 0;
blackPointCheckBox = 0;
blackPointSpinBox = 0;
whitePointCheckBox = 0;
whitePointSpinBox = 0;
whiteBalanceComboBox = 0;
whiteBalanceLabel = 0;
customWhiteBalanceSpinBox = 0;
customWhiteBalanceLabel = 0;
customWhiteBalanceGreenSpinBox = 0;
customWhiteBalanceGreenLabel = 0;
unclipColorLabel = 0;
dontStretchPixelsCheckBox = 0;
RAWQualityComboBox = 0;
RAWQualityLabel = 0;
noiseReductionComboBox = 0;
NRSpinBox1 = 0;
NRSpinBox2 = 0;
NRLabel1 = 0;
NRLabel2 = 0;
enableCACorrectionBox = 0;
autoCACorrectionBox = 0;
caRedMultSpinBox = 0;
caBlueMultSpinBox = 0;
caRedMultLabel = 0;
caBlueMultLabel = 0;
unclipColorComboBox = 0;
reconstructLabel = 0;
reconstructSpinBox = 0;
outputColorSpaceLabel = 0;
outputColorSpaceComboBox = 0;
demosaicingSettings = 0;
whiteBalanceSettings = 0;
correctionsSettings = 0;
colormanSettings = 0;
medianFilterPassesSpinBox = 0;
medianFilterPassesLabel = 0;
inIccUrlEdit = 0;
outIccUrlEdit = 0;
inputColorSpaceLabel = 0;
inputColorSpaceComboBox = 0;
fixColorsHighlightsBox = 0;
refineInterpolationBox = 0;
noiseReductionLabel = 0;
expoCorrectionBox = 0;
expoCorrectionShiftSpinBox = 0;
expoCorrectionHighlightSpinBox = 0;
expoCorrectionShiftLabel = 0;
expoCorrectionHighlightLabel = 0;
}
/** Convert Exposure correction shift E.V value from GUI to Linear value needs by libraw decoder.
*/
double shiftExpoFromEvToLinear(double ev) const
{
// From GUI : -2.0EV => 0.25
// +3.0EV => 8.00
return (1.55*ev + 3.35);
}
/** Convert Exposure correction shift Linear value from liraw decoder to E.V value needs by GUI.
*/
double shiftExpoFromLinearToEv(double lin) const
{
// From GUI : 0.25 => -2.0EV
// 8.00 => +3.0EV
return ((lin-3.35) / 1.55);
}
public:
QWidget* demosaicingSettings;
QWidget* whiteBalanceSettings;
QWidget* correctionsSettings;
QWidget* colormanSettings;
QLabel* whiteBalanceLabel;
QLabel* customWhiteBalanceLabel;
QLabel* customWhiteBalanceGreenLabel;
QLabel* brightnessLabel;
QLabel* RAWQualityLabel;
QLabel* NRLabel1;
QLabel* NRLabel2;
QLabel* caRedMultLabel;
QLabel* caBlueMultLabel;
QLabel* unclipColorLabel;
QLabel* reconstructLabel;
QLabel* inputColorSpaceLabel;
QLabel* outputColorSpaceLabel;
QLabel* medianFilterPassesLabel;
QLabel* noiseReductionLabel;
QLabel* expoCorrectionShiftLabel;
QLabel* expoCorrectionHighlightLabel;
QCheckBox* blackPointCheckBox;
QCheckBox* whitePointCheckBox;
QCheckBox* sixteenBitsImage;
QCheckBox* autoBrightnessBox;
QCheckBox* fourColorCheckBox;
QCheckBox* dontStretchPixelsCheckBox;
QCheckBox* enableCACorrectionBox;
QCheckBox* autoCACorrectionBox;
QCheckBox* fixColorsHighlightsBox;
QCheckBox* refineInterpolationBox;
QCheckBox* expoCorrectionBox;
RFileSelector* inIccUrlEdit;
RFileSelector* outIccUrlEdit;
RComboBox* noiseReductionComboBox;
RComboBox* whiteBalanceComboBox;
RComboBox* RAWQualityComboBox;
RComboBox* unclipColorComboBox;
RComboBox* inputColorSpaceComboBox;
RComboBox* outputColorSpaceComboBox;
RIntNumInput* customWhiteBalanceSpinBox;
RIntNumInput* reconstructSpinBox;
RIntNumInput* blackPointSpinBox;
RIntNumInput* whitePointSpinBox;
RIntNumInput* NRSpinBox1;
RIntNumInput* NRSpinBox2;
RIntNumInput* medianFilterPassesSpinBox;
RDoubleNumInput* customWhiteBalanceGreenSpinBox;
RDoubleNumInput* caRedMultSpinBox;
RDoubleNumInput* caBlueMultSpinBox;
RDoubleNumInput* brightnessSpinBox;
RDoubleNumInput* expoCorrectionShiftSpinBox;
RDoubleNumInput* expoCorrectionHighlightSpinBox;
};
DcrawSettingsWidget::DcrawSettingsWidget(QWidget* const parent, int advSettings)
: RExpanderBox(parent), d(new Private)
{
setup(advSettings);
}
void DcrawSettingsWidget::setup(int advSettings)
{
setObjectName( QLatin1String("DCRawSettings Expander" ));
// ---------------------------------------------------------------
// DEMOSAICING Settings panel
d->demosaicingSettings = new QWidget(this);
QGridLayout* const demosaicingLayout = new QGridLayout(d->demosaicingSettings);
int line = 0;
d->sixteenBitsImage = new QCheckBox(i18nc("@option:check", "16 bits color depth"), d->demosaicingSettings);
d->sixteenBitsImage->setToolTip(i18nc("@info:whatsthis", "<p>If enabled, all RAW files will "
"be decoded in 16-bit color depth using a linear gamma curve. To "
"prevent dark picture rendering in the editor, it is recommended to "
"use Color Management in this mode.</p>"
"<p>If disabled, all RAW files will be decoded in 8-bit color "
"depth with a BT.709 gamma curve and a 99th-percentile white point. "
"This mode is faster than 16-bit decoding.</p>"));
demosaicingLayout->addWidget(d->sixteenBitsImage, 0, 0, 1, 2);
if (advSettings & SIXTEENBITS)
{
d->sixteenBitsImage->show();
line = 1;
}
else
{
d->sixteenBitsImage->hide();
}
d->fourColorCheckBox = new QCheckBox(i18nc("@option:check", "Interpolate RGB as four colors"), d->demosaicingSettings);
d->fourColorCheckBox->setToolTip(i18nc("@info:whatsthis", "<title>Interpolate RGB as four "
"colors</title>"
"<p>The default is to assume that all green pixels are the same. "
"If even-row green pixels are more sensitive to ultraviolet light "
"than odd-row this difference causes a mesh pattern in the output; "
"using this option solves this problem with minimal loss of detail.</p>"
"<p>To resume, this option blurs the image a little, but it "
"eliminates false 2x2 mesh patterns with VNG quality method or "
"mazes with AHD quality method.</p>"));
demosaicingLayout->addWidget(d->fourColorCheckBox, line, 0, 1, line == 0 ? 2 : 3);
line++;
QLabel* const dcrawVersion = new QLabel(d->demosaicingSettings);
dcrawVersion->setAlignment(Qt::AlignRight);
dcrawVersion->setToolTip(i18nc("@info:tooltip", "Visit LibRaw project website"));
dcrawVersion->setOpenExternalLinks(true);
dcrawVersion->setTextFormat(Qt::RichText);
dcrawVersion->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
dcrawVersion->setText(QString::fromLatin1("<a href=\"%1\">%2</a>")
.arg(QLatin1String("http://www.libraw.org"))
.arg(QString::fromLatin1("libraw %1").arg(KDcraw::librawVersion())));
demosaicingLayout->addWidget(dcrawVersion, 0, 2, 1, 1);
d->dontStretchPixelsCheckBox = new QCheckBox(i18nc("@option:check", "Do not stretch or rotate pixels"), d->demosaicingSettings);
d->dontStretchPixelsCheckBox->setToolTip(i18nc("@info:whatsthis",
"<title>Do not stretch or rotate pixels</title>"
"<p>For Fuji Super CCD cameras, show the image tilted 45 degrees. "
"For cameras with non-square pixels, do not stretch the image to "
"its correct aspect ratio. In any case, this option guarantees that "
"each output pixel corresponds to one RAW pixel.</p>"));
demosaicingLayout->addWidget(d->dontStretchPixelsCheckBox, line, 0, 1, 3);
line++;
d->RAWQualityLabel = new QLabel(i18nc("@label:listbox", "Quality:"), d->demosaicingSettings);
d->RAWQualityComboBox = new RComboBox(d->demosaicingSettings);
// Original dcraw demosaicing methods
d->RAWQualityComboBox->insertItem(RawDecodingSettings::BILINEAR, i18nc("@item:inlistbox Quality", "Bilinear"));
d->RAWQualityComboBox->insertItem(RawDecodingSettings::VNG, i18nc("@item:inlistbox Quality", "VNG"));
d->RAWQualityComboBox->insertItem(RawDecodingSettings::PPG, i18nc("@item:inlistbox Quality", "PPG"));
d->RAWQualityComboBox->insertItem(RawDecodingSettings::AHD, i18nc("@item:inlistbox Quality", "AHD"));
// Extended demosaicing method from GPL2 pack
d->RAWQualityComboBox->insertItem(RawDecodingSettings::DCB, i18nc("@item:inlistbox Quality", "DCB"));
d->RAWQualityComboBox->insertItem(RawDecodingSettings::PL_AHD, i18nc("@item:inlistbox Quality", "AHD v2"));
d->RAWQualityComboBox->insertItem(RawDecodingSettings::AFD, i18nc("@item:inlistbox Quality", "AFD"));
d->RAWQualityComboBox->insertItem(RawDecodingSettings::VCD, i18nc("@item:inlistbox Quality", "VCD"));
d->RAWQualityComboBox->insertItem(RawDecodingSettings::VCD_AHD, i18nc("@item:inlistbox Quality", "VCD & AHD"));
d->RAWQualityComboBox->insertItem(RawDecodingSettings::LMMSE, i18nc("@item:inlistbox Quality", "LMMSE"));
// Extended demosaicing method from GPL3 pack
d->RAWQualityComboBox->insertItem(RawDecodingSettings::AMAZE, i18nc("@item:inlistbox Quality", "AMaZE"));
// If Libraw do not support GPL2 pack, disable entries relevant.
if (!KDcraw::librawUseGPL2DemosaicPack())
{
for (int i=RawDecodingSettings::DCB ; i <=RawDecodingSettings::LMMSE ; ++i)
d->RAWQualityComboBox->combo()->setItemData(i, false, Qt::UserRole-1);
}
// If Libraw do not support GPL3 pack, disable entries relevant.
if (!KDcraw::librawUseGPL3DemosaicPack())
{
d->RAWQualityComboBox->combo()->setItemData(RawDecodingSettings::AMAZE, false, Qt::UserRole-1);
}
d->RAWQualityComboBox->setDefaultIndex(RawDecodingSettings::BILINEAR);
d->RAWQualityComboBox->setCurrentIndex(RawDecodingSettings::BILINEAR);
d->RAWQualityComboBox->setToolTip(i18nc("@info:whatsthis", "<title>Quality (interpolation)</title>"
"<p>Select here the demosaicing method to use when decoding RAW "
"images. A demosaicing algorithm is a digital image process used to "
"interpolate a complete image from the partial raw data received "
"from the color-filtered image sensor, internal to many digital "
"cameras, in form of a matrix of colored pixels. Also known as CFA "
"interpolation or color reconstruction, another common spelling is "
"demosaicing. The following methods are available for demosaicing "
"RAW images:</p>"
// Original dcraw demosaicing methods
"<p><ul><li><emphasis strong='true'>Bilinear</emphasis>: use "
"high-speed but low-quality bilinear interpolation (default - for "
"slow computers). In this method, the red value of a non-red pixel "
"is computed as the average of the adjacent red pixels, and similarly "
"for blue and green.</li>"
"<li><emphasis strong='true'>VNG</emphasis>: use Variable Number "
"of Gradients interpolation. This method computes gradients near "
"the pixel of interest and uses the lower gradients (representing "
"smoother and more similar parts of the image) to make an estimate.</li>"
"<li><emphasis strong='true'>PPG</emphasis>: use Patterned-Pixel-"
"Grouping interpolation. Pixel Grouping uses assumptions about "
"natural scenery in making estimates. It has fewer color artifacts "
"on natural images than the Variable Number of Gradients method.</li>"
"<li><emphasis strong='true'>AHD</emphasis>: use Adaptive "
"Homogeneity-Directed interpolation. This method selects the "
"direction of interpolation so as to maximize a homogeneity metric, "
"thus typically minimizing color artifacts.</li>"
// Extended demosaicing method
"<li><emphasis strong='true'>DCB</emphasis>: DCB interpolation from "
"linuxphoto.org project.</li>"
"<li><emphasis strong='true'>AHD v2</emphasis>: modified AHD "
"interpolation using Variance of Color Differences method.</li>"
"<li><emphasis strong='true'>AFD</emphasis>: Adaptive Filtered "
"Demosaicing interpolation through 5 pass median filter from PerfectRaw "
"project.</li>"
"<li><emphasis strong='true'>VCD</emphasis>: Variance of Color "
"Differences interpolation.</li>"
"<li><emphasis strong='true'>VCD & AHD</emphasis>: Mixed demosaicing "
"between VCD and AHD.</li>"
"<li><emphasis strong='true'>LMMSE</emphasis>: color demosaicing via "
"directional linear minimum mean-square error estimation interpolation "
"from PerfectRaw.</li>"
"<li><emphasis strong='true'>AMaZE</emphasis>: Aliasing Minimization "
"interpolation and Zipper Elimination to apply color aberration removal "
"from RawTherapee project.</li></ul></p>"
"<p>Note: some methods can be unavailable if RAW decoder have been built "
"without extension packs.</p>"));
demosaicingLayout->addWidget(d->RAWQualityLabel, line, 0, 1, 1);
demosaicingLayout->addWidget(d->RAWQualityComboBox, line, 1, 1, 2);
line++;
d->medianFilterPassesSpinBox = new RIntNumInput(d->demosaicingSettings);
d->medianFilterPassesSpinBox->setRange(0, 10, 1);
d->medianFilterPassesSpinBox->setDefaultValue(0);
d->medianFilterPassesLabel = new QLabel(i18nc("@label:slider", "Pass:"), d->whiteBalanceSettings);
d->medianFilterPassesSpinBox->setToolTip( i18nc("@info:whatsthis", "<title>Pass</title>"
"<p>Set here the passes used by the median filter applied after "
"interpolation to Red-Green and Blue-Green channels.</p>"
"<p>This setting is only available for specific Quality options: "
"<emphasis strong='true'>Bilinear</emphasis>, <emphasis strong='true'>"
"VNG</emphasis>, <emphasis strong='true'>PPG</emphasis>, "
"<emphasis strong='true'>AHD</emphasis>, <emphasis strong='true'>"
"DCB</emphasis>, and <emphasis strong='true'>VCD & AHD</emphasis>.</p>"));
demosaicingLayout->addWidget(d->medianFilterPassesLabel, line, 0, 1, 1);
demosaicingLayout->addWidget(d->medianFilterPassesSpinBox, line, 1, 1, 2);
line++;
d->refineInterpolationBox = new QCheckBox(i18nc("@option:check", "Refine interpolation"), d->demosaicingSettings);
d->refineInterpolationBox->setToolTip(i18nc("@info:whatsthis", "<title>Refine interpolation</title>"
"<p>This setting is available only for few Quality options:</p>"
"<p><ul><li><emphasis strong='true'>DCB</emphasis>: turn on "
"the enhance interpolated colors filter.</li>"
"<li><emphasis strong='true'>VCD & AHD</emphasis>: turn on the "
"enhanced effective color interpolation (EECI) refine to improve "
"sharpness.</li></ul></p>"));
demosaicingLayout->addWidget(d->refineInterpolationBox, line, 0, 1, 2);
// If Libraw do not support GPL2 pack, disable options relevant.
if (!KDcraw::librawUseGPL2DemosaicPack())
{
d->medianFilterPassesLabel->setEnabled(false);
d->medianFilterPassesSpinBox->setEnabled(false);
d->refineInterpolationBox->setEnabled(false);
}
addItem(d->demosaicingSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Demosaicing"), QString("demosaicing"), true);
// ---------------------------------------------------------------
// WHITE BALANCE Settings Panel
d->whiteBalanceSettings = new QWidget(this);
QGridLayout* const whiteBalanceLayout = new QGridLayout(d->whiteBalanceSettings);
d->whiteBalanceLabel = new QLabel(i18nc("@label:listbox", "Method:"), d->whiteBalanceSettings);
d->whiteBalanceComboBox = new RComboBox(d->whiteBalanceSettings);
d->whiteBalanceComboBox->insertItem(RawDecodingSettings::NONE, i18nc("@item:inlistbox", "Default D65"));
d->whiteBalanceComboBox->insertItem(RawDecodingSettings::CAMERA, i18nc("@item:inlistbox", "Camera"));
d->whiteBalanceComboBox->insertItem(RawDecodingSettings::AUTO, i18nc("@item:inlistbox set while balance automatically", "Automatic"));
d->whiteBalanceComboBox->insertItem(RawDecodingSettings::CUSTOM, i18nc("@item:inlistbox set white balance manually", "Manual"));
d->whiteBalanceComboBox->setDefaultIndex(RawDecodingSettings::CAMERA);
d->whiteBalanceComboBox->setToolTip(i18nc("@info:whatsthis", "<title>White Balance</title>"
"<p>Configure the raw white balance:</p>"
"<p><ul><li><emphasis strong='true'>Default D65</emphasis>: "
"Use a standard daylight D65 white balance.</li>"
"<li><emphasis strong='true'>Camera</emphasis>: Use the white "
"balance specified by the camera. If not available, reverts to "
"default neutral white balance.</li>"
"<li><emphasis strong='true'>Automatic</emphasis>: Calculates an "
"automatic white balance averaging the entire image.</li>"
"<li><emphasis strong='true'>Manual</emphasis>: Set a custom "
"temperature and green level values.</li></ul></p>"));
d->customWhiteBalanceSpinBox = new RIntNumInput(d->whiteBalanceSettings);
d->customWhiteBalanceSpinBox->setRange(2000, 12000, 10);
d->customWhiteBalanceSpinBox->setDefaultValue(6500);
d->customWhiteBalanceLabel = new QLabel(i18nc("@label:slider", "T(K):"), d->whiteBalanceSettings);
d->customWhiteBalanceSpinBox->setToolTip( i18nc("@info:whatsthis", "<title>Temperature</title>"
"<p>Set here the color temperature in Kelvin.</p>"));
d->customWhiteBalanceGreenSpinBox = new RDoubleNumInput(d->whiteBalanceSettings);
d->customWhiteBalanceGreenSpinBox->setDecimals(2);
d->customWhiteBalanceGreenSpinBox->setRange(0.2, 2.5, 0.01);
d->customWhiteBalanceGreenSpinBox->setDefaultValue(1.0);
d->customWhiteBalanceGreenLabel = new QLabel(i18nc("@label:slider Green component", "Green:"), d->whiteBalanceSettings);
d->customWhiteBalanceGreenSpinBox->setToolTip(i18nc("@info:whatsthis", "<p>Set here the "
"green component to set magenta color cast removal level.</p>"));
d->unclipColorLabel = new QLabel(i18nc("@label:listbox", "Highlights:"), d->whiteBalanceSettings);
d->unclipColorComboBox = new RComboBox(d->whiteBalanceSettings);
d->unclipColorComboBox->insertItem(0, i18nc("@item:inlistbox", "Solid white"));
d->unclipColorComboBox->insertItem(1, i18nc("@item:inlistbox", "Unclip"));
d->unclipColorComboBox->insertItem(2, i18nc("@item:inlistbox", "Blend"));
d->unclipColorComboBox->insertItem(3, i18nc("@item:inlistbox", "Rebuild"));
d->unclipColorComboBox->setDefaultIndex(0);
d->unclipColorComboBox->setToolTip(i18nc("@info:whatsthis", "<title>Highlights</title>"
"<p>Select here the highlight clipping method:</p>"
"<p><ul><li><emphasis strong='true'>Solid white</emphasis>: "
"clip all highlights to solid white</li>"
"<li><emphasis strong='true'>Unclip</emphasis>: leave highlights "
"unclipped in various shades of pink</li>"
"<li><emphasis strong='true'>Blend</emphasis>:Blend clipped and "
"unclipped values together for a gradual fade to white</li>"
"<li><emphasis strong='true'>Rebuild</emphasis>: reconstruct "
"highlights using a level value</li></ul></p>"));
d->reconstructLabel = new QLabel(i18nc("@label:slider Highlight reconstruct level", "Level:"), d->whiteBalanceSettings);
d->reconstructSpinBox = new RIntNumInput(d->whiteBalanceSettings);
d->reconstructSpinBox->setRange(0, 6, 1);
d->reconstructSpinBox->setDefaultValue(0);
d->reconstructSpinBox->setToolTip(i18nc("@info:whatsthis", "<title>Level</title>"
"<p>Specify the reconstruct highlight level. Low values favor "
"whites and high values favor colors.</p>"));
d->expoCorrectionBox = new QCheckBox(i18nc("@option:check", "Exposure Correction (E.V)"), d->whiteBalanceSettings);
d->expoCorrectionBox->setToolTip(i18nc("@info:whatsthis", "<p>Turn on the exposure "
"correction before interpolation.</p>"));
d->expoCorrectionShiftLabel = new QLabel(i18nc("@label:slider", "Linear Shift:"), d->whiteBalanceSettings);
d->expoCorrectionShiftSpinBox = new RDoubleNumInput(d->whiteBalanceSettings);
d->expoCorrectionShiftSpinBox->setDecimals(2);
d->expoCorrectionShiftSpinBox->setRange(-2.0, 3.0, 0.01);
d->expoCorrectionShiftSpinBox->setDefaultValue(0.0);
d->expoCorrectionShiftSpinBox->setToolTip(i18nc("@info:whatsthis", "<title>Shift</title>"
"<p>Linear Shift of exposure correction before interpolation in E.V</p>"));
d->expoCorrectionHighlightLabel = new QLabel(i18nc("@label:slider", "Highlight:"), d->whiteBalanceSettings);
d->expoCorrectionHighlightSpinBox = new RDoubleNumInput(d->whiteBalanceSettings);
d->expoCorrectionHighlightSpinBox->setDecimals(2);
d->expoCorrectionHighlightSpinBox->setRange(0.0, 1.0, 0.01);
d->expoCorrectionHighlightSpinBox->setDefaultValue(0.0);
d->expoCorrectionHighlightSpinBox->setToolTip(i18nc("@info:whatsthis", "<title>Highlight</title>"
"<p>Amount of highlight preservation for exposure correction "
"before interpolation in E.V. Only take effect if Shift Correction is > 1.0 E.V</p>"));
d->fixColorsHighlightsBox = new QCheckBox(i18nc("@option:check", "Correct false colors in highlights"), d->whiteBalanceSettings);
d->fixColorsHighlightsBox->setToolTip(i18nc("@info:whatsthis", "<p>If enabled, images with "
"overblown channels are processed much more accurately, without "
"'pink clouds' (and blue highlights under tungsten lamps).</p>"));
d->autoBrightnessBox = new QCheckBox(i18nc("@option:check", "Auto Brightness"), d->whiteBalanceSettings);
d->autoBrightnessBox->setToolTip(i18nc("@info:whatsthis", "<p>If disable, use a fixed white level "
"and ignore the image histogram to adjust brightness.</p>"));
d->brightnessLabel = new QLabel(i18nc("@label:slider", "Brightness:"), d->whiteBalanceSettings);
d->brightnessSpinBox = new RDoubleNumInput(d->whiteBalanceSettings);
d->brightnessSpinBox->setDecimals(2);
d->brightnessSpinBox->setRange(0.0, 10.0, 0.01);
d->brightnessSpinBox->setDefaultValue(1.0);
d->brightnessSpinBox->setToolTip(i18nc("@info:whatsthis", "<title>Brightness</title>"
"<p>Specify the brightness level of output image. The default "
"value is 1.0 (works in 8-bit mode only).</p>"));
if (! (advSettings & POSTPROCESSING))
{
d->brightnessLabel->hide();
d->brightnessSpinBox->hide();
}
d->blackPointCheckBox = new QCheckBox(i18nc("@option:check Black point", "Black:"), d->whiteBalanceSettings);
d->blackPointCheckBox->setToolTip(i18nc("@info:whatsthis", "<title>Black point</title>"
"<p>Use a specific black point value to decode RAW pictures. If "
"you set this option to off, the Black Point value will be "
"automatically computed.</p>"));
d->blackPointSpinBox = new RIntNumInput(d->whiteBalanceSettings);
d->blackPointSpinBox->setRange(0, 1000, 1);
d->blackPointSpinBox->setDefaultValue(0);
d->blackPointSpinBox->setToolTip(i18nc("@info:whatsthis", "<title>Black point value</title>"
"<p>Specify specific black point value of the output image.</p>"));
d->whitePointCheckBox = new QCheckBox(i18nc("@option:check White point", "White:"), d->whiteBalanceSettings);
d->whitePointCheckBox->setToolTip(i18nc("@info:whatsthis", "<title>White point</title>"
"<p>Use a specific white point value to decode RAW pictures. If "
"you set this option to off, the White Point value will be "
"automatically computed.</p>"));
d->whitePointSpinBox = new RIntNumInput(d->whiteBalanceSettings);
d->whitePointSpinBox->setRange(0, 20000, 1);
d->whitePointSpinBox->setDefaultValue(0);
d->whitePointSpinBox->setToolTip(i18nc("@info:whatsthis", "<title>White point value</title>"
"<p>Specify specific white point value of the output image.</p>"));
if (! (advSettings & BLACKWHITEPOINTS))
{
d->blackPointCheckBox->hide();
d->blackPointSpinBox->hide();
d->whitePointCheckBox->hide();
d->whitePointSpinBox->hide();
}
whiteBalanceLayout->addWidget(d->whiteBalanceLabel, 0, 0, 1, 1);
whiteBalanceLayout->addWidget(d->whiteBalanceComboBox, 0, 1, 1, 2);
whiteBalanceLayout->addWidget(d->customWhiteBalanceLabel, 1, 0, 1, 1);
whiteBalanceLayout->addWidget(d->customWhiteBalanceSpinBox, 1, 1, 1, 2);
whiteBalanceLayout->addWidget(d->customWhiteBalanceGreenLabel, 2, 0, 1, 1);
whiteBalanceLayout->addWidget(d->customWhiteBalanceGreenSpinBox, 2, 1, 1, 2);
whiteBalanceLayout->addWidget(d->unclipColorLabel, 3, 0, 1, 1);
whiteBalanceLayout->addWidget(d->unclipColorComboBox, 3, 1, 1, 2);
whiteBalanceLayout->addWidget(d->reconstructLabel, 4, 0, 1, 1);
whiteBalanceLayout->addWidget(d->reconstructSpinBox, 4, 1, 1, 2);
whiteBalanceLayout->addWidget(d->expoCorrectionBox, 5, 0, 1, 2);
whiteBalanceLayout->addWidget(d->expoCorrectionShiftLabel, 6, 0, 1, 1);
whiteBalanceLayout->addWidget(d->expoCorrectionShiftSpinBox, 6, 1, 1, 2);
whiteBalanceLayout->addWidget(d->expoCorrectionHighlightLabel, 7, 0, 1, 1);
whiteBalanceLayout->addWidget(d->expoCorrectionHighlightSpinBox, 7, 1, 1, 2);
whiteBalanceLayout->addWidget(d->fixColorsHighlightsBox, 8, 0, 1, 3);
whiteBalanceLayout->addWidget(d->autoBrightnessBox, 9, 0, 1, 3);
whiteBalanceLayout->addWidget(d->brightnessLabel, 10, 0, 1, 1);
whiteBalanceLayout->addWidget(d->brightnessSpinBox, 10, 1, 1, 2);
whiteBalanceLayout->addWidget(d->blackPointCheckBox, 11, 0, 1, 1);
whiteBalanceLayout->addWidget(d->blackPointSpinBox, 11, 1, 1, 2);
whiteBalanceLayout->addWidget(d->whitePointCheckBox, 12, 0, 1, 1);
whiteBalanceLayout->addWidget(d->whitePointSpinBox, 12, 1, 1, 2);
whiteBalanceLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
whiteBalanceLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
addItem(d->whiteBalanceSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "White Balance"), QString("whitebalance"), true);
// ---------------------------------------------------------------
// CORRECTIONS Settings panel
d->correctionsSettings = new QWidget(this);
QGridLayout* const correctionsLayout = new QGridLayout(d->correctionsSettings);
d->noiseReductionLabel = new QLabel(i18nc("@label:listbox", "Noise reduction:"), d->correctionsSettings);
d->noiseReductionComboBox = new RComboBox(d->colormanSettings);
d->noiseReductionComboBox->insertItem(RawDecodingSettings::NONR, i18nc("@item:inlistbox Noise Reduction", "None"));
d->noiseReductionComboBox->insertItem(RawDecodingSettings::WAVELETSNR, i18nc("@item:inlistbox Noise Reduction", "Wavelets"));
d->noiseReductionComboBox->insertItem(RawDecodingSettings::FBDDNR, i18nc("@item:inlistbox Noise Reduction", "FBDD"));
d->noiseReductionComboBox->insertItem(RawDecodingSettings::LINENR, i18nc("@item:inlistbox Noise Reduction", "CFA Line Denoise"));
d->noiseReductionComboBox->insertItem(RawDecodingSettings::IMPULSENR, i18nc("@item:inlistbox Noise Reduction", "Impulse Denoise"));
d->noiseReductionComboBox->setDefaultIndex(RawDecodingSettings::NONR);
d->noiseReductionComboBox->setToolTip(i18nc("@info:whatsthis", "<title>Noise Reduction</title>"
"<p>Select here the noise reduction method to apply during RAW "
"decoding.</p>"
"<p><ul><li><emphasis strong='true'>None</emphasis>: no "
"noise reduction.</li>"
"<li><emphasis strong='true'>Wavelets</emphasis>: wavelets "
"correction to erase noise while preserving real detail. It's "
"applied after interpolation.</li>"
"<li><emphasis strong='true'>FBDD</emphasis>: Fake Before "
"Demosaicing Denoising noise reduction. It's applied before "
"interpolation.</li>"
"<li><emphasis strong='true'>CFA Line Denoise</emphasis>: Banding "
"noise suppression. It's applied after interpolation.</li>"
"<li><emphasis strong='true'>Impulse Denoise</emphasis>: Impulse "
"noise suppression. It's applied after interpolation.</li></ul></p>"));
d->NRSpinBox1 = new RIntNumInput(d->correctionsSettings);
d->NRSpinBox1->setRange(100, 1000, 1);
d->NRSpinBox1->setDefaultValue(100);
d->NRLabel1 = new QLabel(d->correctionsSettings);
d->NRSpinBox2 = new RIntNumInput(d->correctionsSettings);
d->NRSpinBox2->setRange(100, 1000, 1);
d->NRSpinBox2->setDefaultValue(100);
d->NRLabel2 = new QLabel(d->correctionsSettings);
d->enableCACorrectionBox = new QCheckBox(i18nc("@option:check", "Enable Chromatic Aberration correction"), d->correctionsSettings);
d->enableCACorrectionBox->setToolTip(i18nc("@info:whatsthis", "<title>Enable Chromatic "
"Aberration correction</title>"
"<p>Enlarge the raw red-green and blue-yellow axis by the given "
"factors (automatic by default).</p>"));
d->autoCACorrectionBox = new QCheckBox(i18nc("@option:check", "Automatic color axis adjustments"), d->correctionsSettings);
d->autoCACorrectionBox->setToolTip(i18nc("@info:whatsthis", "<title>Automatic Chromatic "
"Aberration correction</title>"
"<p>If this option is turned on, it will try to shift image "
"channels slightly and evaluate Chromatic Aberration change. Note "
"that if you shot blue-red pattern, the method may fail. In this "
"case, disable this option and tune manually color factors.</p>"));
d->caRedMultLabel = new QLabel(i18nc("@label:slider", "Red-Green:"), d->correctionsSettings);
d->caRedMultSpinBox = new RDoubleNumInput(d->correctionsSettings);
d->caRedMultSpinBox->setDecimals(1);
d->caRedMultSpinBox->setRange(-4.0, 4.0, 0.1);
d->caRedMultSpinBox->setDefaultValue(0.0);
d->caRedMultSpinBox->setToolTip(i18nc("@info:whatsthis", "<title>Red-Green multiplier</title>"
"<p>Set here the amount of correction on red-green axis</p>"));
d->caBlueMultLabel = new QLabel(i18nc("@label:slider", "Blue-Yellow:"), d->correctionsSettings);
d->caBlueMultSpinBox = new RDoubleNumInput(d->correctionsSettings);
d->caBlueMultSpinBox->setDecimals(1);
d->caBlueMultSpinBox->setRange(-4.0, 4.0, 0.1);
d->caBlueMultSpinBox->setDefaultValue(0.0);
d->caBlueMultSpinBox->setToolTip(i18nc("@info:whatsthis", "<title>Blue-Yellow multiplier</title>"
"<p>Set here the amount of correction on blue-yellow axis</p>"));
correctionsLayout->addWidget(d->noiseReductionLabel, 0, 0, 1, 1);
correctionsLayout->addWidget(d->noiseReductionComboBox, 0, 1, 1, 2);
correctionsLayout->addWidget(d->NRLabel1, 1, 0, 1, 1);
correctionsLayout->addWidget(d->NRSpinBox1, 1, 1, 1, 2);
correctionsLayout->addWidget(d->NRLabel2, 2, 0, 1, 1);
correctionsLayout->addWidget(d->NRSpinBox2, 2, 1, 1, 2);
correctionsLayout->addWidget(d->enableCACorrectionBox, 3, 0, 1, 3);
correctionsLayout->addWidget(d->autoCACorrectionBox, 4, 0, 1, 3);
correctionsLayout->addWidget(d->caRedMultLabel, 5, 0, 1, 1);
correctionsLayout->addWidget(d->caRedMultSpinBox, 5, 1, 1, 2);
correctionsLayout->addWidget(d->caBlueMultLabel, 6, 0, 1, 1);
correctionsLayout->addWidget(d->caBlueMultSpinBox, 6, 1, 1, 2);
correctionsLayout->setRowStretch(7, 10);
correctionsLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
correctionsLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
addItem(d->correctionsSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Corrections"), QString("corrections"), false);
// ---------------------------------------------------------------
// COLOR MANAGEMENT Settings panel
d->colormanSettings = new QWidget(this);
QGridLayout* const colormanLayout = new QGridLayout(d->colormanSettings);
d->inputColorSpaceLabel = new QLabel(i18nc("@label:listbox", "Camera Profile:"), d->colormanSettings);
d->inputColorSpaceComboBox = new RComboBox(d->colormanSettings);
d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::NOINPUTCS, i18nc("@item:inlistbox Camera Profile", "None"));
d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::EMBEDDED, i18nc("@item:inlistbox Camera Profile", "Embedded"));
d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::CUSTOMINPUTCS, i18nc("@item:inlistbox Camera Profile", "Custom"));
d->inputColorSpaceComboBox->setDefaultIndex(RawDecodingSettings::NOINPUTCS);
d->inputColorSpaceComboBox->setToolTip(i18nc("@info:whatsthis", "<title>Camera Profile</title>"
"<p>Select here the input color space used to decode RAW data.</p>"
"<p><ul><li><emphasis strong='true'>None</emphasis>: no "
"input color profile is used during RAW decoding.</li>"
"<li><emphasis strong='true'>Embedded</emphasis>: use embedded "
"color profile from RAW file, if it exists.</li>"
"<li><emphasis strong='true'>Custom</emphasis>: use a custom "
"input color space profile.</li></ul></p>"));
d->inIccUrlEdit = new RFileSelector(d->colormanSettings);
d->inIccUrlEdit->setFileDlgMode(QFileDialog::ExistingFile);
d->inIccUrlEdit->setFileDlgFilter(i18n("ICC Files (*.icc; *.icm)"));
d->outputColorSpaceLabel = new QLabel(i18nc("@label:listbox", "Workspace:"), d->colormanSettings);
d->outputColorSpaceComboBox = new RComboBox( d->colormanSettings );
d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::RAWCOLOR, i18nc("@item:inlistbox Workspace", "Raw (no profile)"));
d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::SRGB, i18nc("@item:inlistbox Workspace", "sRGB"));
d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::ADOBERGB, i18nc("@item:inlistbox Workspace", "Adobe RGB"));
d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::WIDEGAMMUT, i18nc("@item:inlistbox Workspace", "Wide Gamut"));
d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::PROPHOTO, i18nc("@item:inlistbox Workspace", "Pro-Photo"));
d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::CUSTOMOUTPUTCS, i18nc("@item:inlistbox Workspace", "Custom"));
d->outputColorSpaceComboBox->setDefaultIndex(RawDecodingSettings::SRGB);
d->outputColorSpaceComboBox->setToolTip(i18nc("@info:whatsthis", "<title>Workspace</title>"
"<p>Select here the output color space used to decode RAW data.</p>"
"<p><ul><li><emphasis strong='true'>Raw (linear)</emphasis>: "
"in this mode, no output color space is used during RAW decoding.</li>"
"<li><emphasis strong='true'>sRGB</emphasis>: this is an RGB "
"color space, created cooperatively by Hewlett-Packard and "
"Microsoft. It is the best choice for images destined for the Web "
"and portrait photography.</li>"
"<li><emphasis strong='true'>Adobe RGB</emphasis>: this color "
"space is an extended RGB color space, developed by Adobe. It is "
"used for photography applications such as advertising and fine "
"art.</li>"
"<li><emphasis strong='true'>Wide Gamut</emphasis>: this color "
"space is an expanded version of the Adobe RGB color space.</li>"
"<li><emphasis strong='true'>Pro-Photo</emphasis>: this color "
"space is an RGB color space, developed by Kodak, that offers an "
"especially large gamut designed for use with photographic outputs "
"in mind.</li>"
"<li><emphasis strong='true'>Custom</emphasis>: use a custom "
"output color space profile.</li></ul></p>"));
d->outIccUrlEdit = new RFileSelector(d->colormanSettings);
d->outIccUrlEdit->setFileDlgMode(QFileDialog::ExistingFile);
d->outIccUrlEdit->setFileDlgFilter(i18n("ICC Files (*.icc; *.icm)"));
colormanLayout->addWidget(d->inputColorSpaceLabel, 0, 0, 1, 1);
colormanLayout->addWidget(d->inputColorSpaceComboBox, 0, 1, 1, 2);
colormanLayout->addWidget(d->inIccUrlEdit, 1, 0, 1, 3);
colormanLayout->addWidget(d->outputColorSpaceLabel, 2, 0, 1, 1);
colormanLayout->addWidget(d->outputColorSpaceComboBox, 2, 1, 1, 2);
colormanLayout->addWidget(d->outIccUrlEdit, 3, 0, 1, 3);
colormanLayout->setRowStretch(4, 10);
colormanLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
colormanLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
addItem(d->colormanSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Color Management"), QString("colormanagement"), false);
if (! (advSettings & COLORSPACE))
removeItem(COLORMANAGEMENT);
addStretch();
// ---------------------------------------------------------------
connect(d->unclipColorComboBox, static_cast<void (RComboBox::*)(int)>(&RComboBox::activated),
this, &DcrawSettingsWidget::slotUnclipColorActivated);
connect(d->whiteBalanceComboBox, static_cast<void (RComboBox::*)(int)>(&RComboBox::activated),
this, &DcrawSettingsWidget::slotWhiteBalanceToggled);
connect(d->noiseReductionComboBox, static_cast<void (RComboBox::*)(int)>(&RComboBox::activated),
this, &DcrawSettingsWidget::slotNoiseReductionChanged);
connect(d->enableCACorrectionBox, &QCheckBox::toggled,
this, &DcrawSettingsWidget::slotCACorrectionToggled);
connect(d->autoCACorrectionBox, &QCheckBox::toggled,
this, &DcrawSettingsWidget::slotAutoCAToggled);
connect(d->blackPointCheckBox, SIGNAL(toggled(bool)),
d->blackPointSpinBox, SLOT(setEnabled(bool)));
connect(d->whitePointCheckBox, SIGNAL(toggled(bool)),
d->whitePointSpinBox, SLOT(setEnabled(bool)));
connect(d->sixteenBitsImage, &QCheckBox::toggled,
this, &DcrawSettingsWidget::slotsixteenBitsImageToggled);
connect(d->inputColorSpaceComboBox, static_cast<void (RComboBox::*)(int)>(&RComboBox::activated),
this, &DcrawSettingsWidget::slotInputColorSpaceChanged);
connect(d->outputColorSpaceComboBox, static_cast<void (RComboBox::*)(int)>(&RComboBox::activated),
this, &DcrawSettingsWidget::slotOutputColorSpaceChanged);
connect(d->expoCorrectionBox, &QCheckBox::toggled,
this, &DcrawSettingsWidget::slotExposureCorrectionToggled);
connect(d->expoCorrectionShiftSpinBox, &RDoubleNumInput::valueChanged,
this, &DcrawSettingsWidget::slotExpoCorrectionShiftChanged);
// Wrapper to emit signal when something is changed in settings.
connect(d->inIccUrlEdit->lineEdit(), &QLineEdit::textChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->outIccUrlEdit->lineEdit(), &QLineEdit::textChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->whiteBalanceComboBox, static_cast<void (RComboBox::*)(int)>(&RComboBox::activated),
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->RAWQualityComboBox, static_cast<void (RComboBox::*)(int)>(&RComboBox::activated),
this, &DcrawSettingsWidget::slotRAWQualityChanged);
connect(d->unclipColorComboBox, static_cast<void (RComboBox::*)(int)>(&RComboBox::activated),
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->inputColorSpaceComboBox, static_cast<void (RComboBox::*)(int)>(&RComboBox::activated),
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->outputColorSpaceComboBox, static_cast<void (RComboBox::*)(int)>(&RComboBox::activated),
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->blackPointCheckBox, &QCheckBox::toggled,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->whitePointCheckBox, &QCheckBox::toggled,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->sixteenBitsImage, &QCheckBox::toggled,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->fixColorsHighlightsBox, &QCheckBox::toggled,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->autoBrightnessBox, &QCheckBox::toggled,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->fourColorCheckBox, &QCheckBox::toggled,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->dontStretchPixelsCheckBox, &QCheckBox::toggled,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->refineInterpolationBox, &QCheckBox::toggled,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->customWhiteBalanceSpinBox, &RIntNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->reconstructSpinBox, &RIntNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->blackPointSpinBox, &RIntNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->whitePointSpinBox, &RIntNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->NRSpinBox1, &RIntNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->NRSpinBox2, &RIntNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->medianFilterPassesSpinBox, &RIntNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->customWhiteBalanceGreenSpinBox, &RDoubleNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->caRedMultSpinBox, &RDoubleNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->caBlueMultSpinBox, &RDoubleNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->brightnessSpinBox, &RDoubleNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
connect(d->expoCorrectionHighlightSpinBox, &RDoubleNumInput::valueChanged,
this, &DcrawSettingsWidget::signalSettingsChanged);
}
DcrawSettingsWidget::~DcrawSettingsWidget()
{
delete d;
}
void DcrawSettingsWidget::updateMinimumWidth()
{
int width = 0;
for (int i = 0; i < count(); i++)
{
if (widget(i)->width() > width)
width = widget(i)->width();
}
setMinimumWidth(width);
}
RFileSelector* DcrawSettingsWidget::inputProfileUrlEdit() const
{
return d->inIccUrlEdit;
}
RFileSelector* DcrawSettingsWidget::outputProfileUrlEdit() const
{
return d->outIccUrlEdit;
}
void DcrawSettingsWidget::resetToDefault()
{
setSettings(RawDecodingSettings());
}
void DcrawSettingsWidget::slotsixteenBitsImageToggled(bool b)
{
setEnabledBrightnessSettings(!b);
emit signalSixteenBitsImageToggled(d->sixteenBitsImage->isChecked());
}
void DcrawSettingsWidget::slotWhiteBalanceToggled(int v)
{
if (v == 3)
{
d->customWhiteBalanceSpinBox->setEnabled(true);
d->customWhiteBalanceGreenSpinBox->setEnabled(true);
d->customWhiteBalanceLabel->setEnabled(true);
d->customWhiteBalanceGreenLabel->setEnabled(true);
}
else
{
d->customWhiteBalanceSpinBox->setEnabled(false);
d->customWhiteBalanceGreenSpinBox->setEnabled(false);
d->customWhiteBalanceLabel->setEnabled(false);
d->customWhiteBalanceGreenLabel->setEnabled(false);
}
}
void DcrawSettingsWidget::slotUnclipColorActivated(int v)
{
if (v == 3) // Reconstruct Highlight method
{
d->reconstructLabel->setEnabled(true);
d->reconstructSpinBox->setEnabled(true);
}
else
{
d->reconstructLabel->setEnabled(false);
d->reconstructSpinBox->setEnabled(false);
}
}
void DcrawSettingsWidget::slotNoiseReductionChanged(int item)
{
d->NRSpinBox1->setEnabled(true);
d->NRLabel1->setEnabled(true);
d->NRSpinBox2->setEnabled(true);
d->NRLabel2->setEnabled(true);
d->NRLabel1->setText(i18nc("@label", "Threshold:"));
d->NRSpinBox1->setToolTip(i18nc("@info:whatsthis", "<title>Threshold</title>"
"<p>Set here the noise reduction threshold value to use.</p>"));
switch(item)
{
case RawDecodingSettings::WAVELETSNR:
case RawDecodingSettings::FBDDNR:
case RawDecodingSettings::LINENR:
d->NRSpinBox2->setVisible(false);
d->NRLabel2->setVisible(false);
break;
case RawDecodingSettings::IMPULSENR:
d->NRLabel1->setText(i18nc("@label", "Luminance:"));
d->NRSpinBox1->setToolTip(i18nc("@info:whatsthis", "<title>Luminance</title>"
"<p>Amount of Luminance impulse noise reduction.</p>"));
d->NRLabel2->setText(i18nc("@label", "Chrominance:"));
d->NRSpinBox2->setToolTip(i18nc("@info:whatsthis", "<title>Chrominance</title>"
"<p>Amount of Chrominance impulse noise reduction.</p>"));
d->NRSpinBox2->setVisible(true);
d->NRLabel2->setVisible(true);
break;
default:
d->NRSpinBox1->setEnabled(false);
d->NRLabel1->setEnabled(false);
d->NRSpinBox2->setEnabled(false);
d->NRLabel2->setEnabled(false);
d->NRSpinBox2->setVisible(false);
d->NRLabel2->setVisible(false);
break;
}
emit signalSettingsChanged();
}
void DcrawSettingsWidget::slotCACorrectionToggled(bool b)
{
d->autoCACorrectionBox->setEnabled(b);
slotAutoCAToggled(d->autoCACorrectionBox->isChecked());
}
void DcrawSettingsWidget::slotAutoCAToggled(bool b)
{
if (b)
{
d->caRedMultSpinBox->setValue(0.0);
d->caBlueMultSpinBox->setValue(0.0);
}
bool mult = (!b) && (d->autoCACorrectionBox->isEnabled());
d->caRedMultSpinBox->setEnabled(mult);
d->caBlueMultSpinBox->setEnabled(mult);
d->caRedMultLabel->setEnabled(mult);
d->caBlueMultLabel->setEnabled(mult);
emit signalSettingsChanged();
}
void DcrawSettingsWidget::slotExposureCorrectionToggled(bool b)
{
d->expoCorrectionShiftLabel->setEnabled(b);
d->expoCorrectionShiftSpinBox->setEnabled(b);
d->expoCorrectionHighlightLabel->setEnabled(b);
d->expoCorrectionHighlightSpinBox->setEnabled(b);
slotExpoCorrectionShiftChanged(d->expoCorrectionShiftSpinBox->value());
}
void DcrawSettingsWidget::slotExpoCorrectionShiftChanged(double ev)
{
// Only enable Highligh exposure correction if Shift correction is >= 1.0, else this settings do not take effect.
bool b = (ev >= 1.0);
d->expoCorrectionHighlightLabel->setEnabled(b);
d->expoCorrectionHighlightSpinBox->setEnabled(b);
emit signalSettingsChanged();
}
void DcrawSettingsWidget::slotInputColorSpaceChanged(int item)
{
d->inIccUrlEdit->setEnabled(item == RawDecodingSettings::CUSTOMINPUTCS);
}
void DcrawSettingsWidget::slotOutputColorSpaceChanged(int item)
{
d->outIccUrlEdit->setEnabled(item == RawDecodingSettings::CUSTOMOUTPUTCS);
}
void DcrawSettingsWidget::slotRAWQualityChanged(int quality)
{
switch(quality)
{
case RawDecodingSettings::DCB:
case RawDecodingSettings::VCD_AHD:
// These options can be only available if Libraw use GPL2 pack.
d->medianFilterPassesLabel->setEnabled(KDcraw::librawUseGPL2DemosaicPack());
d->medianFilterPassesSpinBox->setEnabled(KDcraw::librawUseGPL2DemosaicPack());
d->refineInterpolationBox->setEnabled(KDcraw::librawUseGPL2DemosaicPack());
break;
case RawDecodingSettings::PL_AHD:
case RawDecodingSettings::AFD:
case RawDecodingSettings::VCD:
case RawDecodingSettings::LMMSE:
case RawDecodingSettings::AMAZE:
d->medianFilterPassesLabel->setEnabled(false);
d->medianFilterPassesSpinBox->setEnabled(false);
d->refineInterpolationBox->setEnabled(false);
break;
default: // BILINEAR, VNG, PPG, AHD
d->medianFilterPassesLabel->setEnabled(true);
d->medianFilterPassesSpinBox->setEnabled(true);
d->refineInterpolationBox->setEnabled(false);
break;
}
emit signalSettingsChanged();
}
void DcrawSettingsWidget::setEnabledBrightnessSettings(bool b)
{
d->brightnessLabel->setEnabled(b);
d->brightnessSpinBox->setEnabled(b);
}
bool DcrawSettingsWidget::brightnessSettingsIsEnabled() const
{
return d->brightnessSpinBox->isEnabled();
}
void DcrawSettingsWidget::setSettings(const RawDecodingSettings& settings)
{
d->sixteenBitsImage->setChecked(settings.sixteenBitsImage);
switch(settings.whiteBalance)
{
case RawDecodingSettings::CAMERA:
d->whiteBalanceComboBox->setCurrentIndex(1);
break;
case RawDecodingSettings::AUTO:
d->whiteBalanceComboBox->setCurrentIndex(2);
break;
case RawDecodingSettings::CUSTOM:
d->whiteBalanceComboBox->setCurrentIndex(3);
break;
default:
d->whiteBalanceComboBox->setCurrentIndex(0);
break;
}
slotWhiteBalanceToggled(d->whiteBalanceComboBox->currentIndex());
d->customWhiteBalanceSpinBox->setValue(settings.customWhiteBalance);
d->customWhiteBalanceGreenSpinBox->setValue(settings.customWhiteBalanceGreen);
d->fourColorCheckBox->setChecked(settings.RGBInterpolate4Colors);
d->autoBrightnessBox->setChecked(settings.autoBrightness);
d->fixColorsHighlightsBox->setChecked(settings.fixColorsHighlights);
switch(settings.unclipColors)
{
case 0:
d->unclipColorComboBox->setCurrentIndex(0);
break;
case 1:
d->unclipColorComboBox->setCurrentIndex(1);
break;
case 2:
d->unclipColorComboBox->setCurrentIndex(2);
break;
default: // Reconstruct Highlight method
d->unclipColorComboBox->setCurrentIndex(3);
d->reconstructSpinBox->setValue(settings.unclipColors-3);
break;
}
slotUnclipColorActivated(d->unclipColorComboBox->currentIndex());
d->dontStretchPixelsCheckBox->setChecked(settings.DontStretchPixels);
d->brightnessSpinBox->setValue(settings.brightness);
d->blackPointCheckBox->setChecked(settings.enableBlackPoint);
d->blackPointSpinBox->setEnabled(settings.enableBlackPoint);
d->blackPointSpinBox->setValue(settings.blackPoint);
d->whitePointCheckBox->setChecked(settings.enableWhitePoint);
d->whitePointSpinBox->setEnabled(settings.enableWhitePoint);
d->whitePointSpinBox->setValue(settings.whitePoint);
int q = settings.RAWQuality;
// If Libraw do not support GPL2 pack, reset to BILINEAR.
if (!KDcraw::librawUseGPL2DemosaicPack())
{
for (int i=RawDecodingSettings::DCB ; i <=RawDecodingSettings::LMMSE ; ++i)
{
if (q == i)
{
q = RawDecodingSettings::BILINEAR;
qCDebug(LIBKDCRAW_LOG) << "Libraw GPL2 pack not available. Raw quality set to Bilinear";
break;
}
}
}
// If Libraw do not support GPL3 pack, reset to BILINEAR.
if (!KDcraw::librawUseGPL3DemosaicPack() && (q == RawDecodingSettings::AMAZE))
{
q = RawDecodingSettings::BILINEAR;
qCDebug(LIBKDCRAW_LOG) << "Libraw GPL3 pack not available. Raw quality set to Bilinear";
}
d->RAWQualityComboBox->setCurrentIndex(q);
switch(q)
{
case RawDecodingSettings::DCB:
d->medianFilterPassesSpinBox->setValue(settings.dcbIterations);
d->refineInterpolationBox->setChecked(settings.dcbEnhanceFl);
break;
case RawDecodingSettings::VCD_AHD:
d->medianFilterPassesSpinBox->setValue(settings.eeciRefine);
d->refineInterpolationBox->setChecked(settings.eeciRefine);
break;
default:
d->medianFilterPassesSpinBox->setValue(settings.medianFilterPasses);
d->refineInterpolationBox->setChecked(false); // option not used.
break;
}
slotRAWQualityChanged(q);
d->inputColorSpaceComboBox->setCurrentIndex((int)settings.inputColorSpace);
slotInputColorSpaceChanged((int)settings.inputColorSpace);
d->outputColorSpaceComboBox->setCurrentIndex((int)settings.outputColorSpace);
slotOutputColorSpaceChanged((int)settings.outputColorSpace);
d->noiseReductionComboBox->setCurrentIndex(settings.NRType);
slotNoiseReductionChanged(settings.NRType);
d->NRSpinBox1->setValue(settings.NRThreshold);
d->NRSpinBox2->setValue(settings.NRChroThreshold);
d->enableCACorrectionBox->setChecked(settings.enableCACorrection);
d->caRedMultSpinBox->setValue(settings.caMultiplier[0]);
d->caBlueMultSpinBox->setValue(settings.caMultiplier[1]);
d->autoCACorrectionBox->setChecked((settings.caMultiplier[0] == 0.0) && (settings.caMultiplier[1] == 0.0));
slotCACorrectionToggled(settings.enableCACorrection);
d->expoCorrectionBox->setChecked(settings.expoCorrection);
slotExposureCorrectionToggled(settings.expoCorrection);
d->expoCorrectionShiftSpinBox->setValue(d->shiftExpoFromLinearToEv(settings.expoCorrectionShift));
d->expoCorrectionHighlightSpinBox->setValue(settings.expoCorrectionHighlight);
d->inIccUrlEdit->lineEdit()->setText(settings.inputProfile);
d->outIccUrlEdit->lineEdit()->setText(settings.outputProfile);
}
RawDecodingSettings DcrawSettingsWidget::settings() const
{
RawDecodingSettings prm;
prm.sixteenBitsImage = d->sixteenBitsImage->isChecked();
switch(d->whiteBalanceComboBox->currentIndex())
{
case 1:
prm.whiteBalance = RawDecodingSettings::CAMERA;
break;
case 2:
prm.whiteBalance = RawDecodingSettings::AUTO;
break;
case 3:
prm.whiteBalance = RawDecodingSettings::CUSTOM;
break;
default:
prm.whiteBalance = RawDecodingSettings::NONE;
break;
}
prm.customWhiteBalance = d->customWhiteBalanceSpinBox->value();
prm.customWhiteBalanceGreen = d->customWhiteBalanceGreenSpinBox->value();
prm.RGBInterpolate4Colors = d->fourColorCheckBox->isChecked();
prm.autoBrightness = d->autoBrightnessBox->isChecked();
prm.fixColorsHighlights = d->fixColorsHighlightsBox->isChecked();
switch(d->unclipColorComboBox->currentIndex())
{
case 0:
prm.unclipColors = 0;
break;
case 1:
prm.unclipColors = 1;
break;
case 2:
prm.unclipColors = 2;
break;
default: // Reconstruct Highlight method
prm.unclipColors = d->reconstructSpinBox->value()+3;
break;
}
prm.DontStretchPixels = d->dontStretchPixelsCheckBox->isChecked();
prm.brightness = d->brightnessSpinBox->value();
prm.enableBlackPoint = d->blackPointCheckBox->isChecked();
prm.blackPoint = d->blackPointSpinBox->value();
prm.enableWhitePoint = d->whitePointCheckBox->isChecked();
prm.whitePoint = d->whitePointSpinBox->value();
prm.RAWQuality = (RawDecodingSettings::DecodingQuality)d->RAWQualityComboBox->currentIndex();
switch(prm.RAWQuality)
{
case RawDecodingSettings::DCB:
prm.dcbIterations = d->medianFilterPassesSpinBox->value();
prm.dcbEnhanceFl = d->refineInterpolationBox->isChecked();
break;
case RawDecodingSettings::VCD_AHD:
prm.esMedPasses = d->medianFilterPassesSpinBox->value();
prm.eeciRefine = d->refineInterpolationBox->isChecked();
break;
default:
prm.medianFilterPasses = d->medianFilterPassesSpinBox->value();
break;
}
prm.NRType = (RawDecodingSettings::NoiseReduction)d->noiseReductionComboBox->currentIndex();
switch (prm.NRType)
{
case RawDecodingSettings::NONR:
{
prm.NRThreshold = 0;
prm.NRChroThreshold = 0;
break;
}
case RawDecodingSettings::WAVELETSNR:
case RawDecodingSettings::FBDDNR:
case RawDecodingSettings::LINENR:
{
prm.NRThreshold = d->NRSpinBox1->value();
prm.NRChroThreshold = 0;
break;
}
default: // IMPULSENR
{
prm.NRThreshold = d->NRSpinBox1->value();
prm.NRChroThreshold = d->NRSpinBox2->value();
break;
}
}
prm.enableCACorrection = d->enableCACorrectionBox->isChecked();
prm.caMultiplier[0] = d->caRedMultSpinBox->value();
prm.caMultiplier[1] = d->caBlueMultSpinBox->value();
prm.expoCorrection = d->expoCorrectionBox->isChecked();
prm.expoCorrectionShift = d->shiftExpoFromEvToLinear(d->expoCorrectionShiftSpinBox->value());
prm.expoCorrectionHighlight = d->expoCorrectionHighlightSpinBox->value();
prm.inputColorSpace = (RawDecodingSettings::InputColorSpace)(d->inputColorSpaceComboBox->currentIndex());
prm.outputColorSpace = (RawDecodingSettings::OutputColorSpace)(d->outputColorSpaceComboBox->currentIndex());
prm.inputProfile = d->inIccUrlEdit->lineEdit()->text();
prm.outputProfile = d->outIccUrlEdit->lineEdit()->text();
return prm;
}
void DcrawSettingsWidget::writeSettings(KConfigGroup& group)
{
RawDecodingSettings prm = settings();
prm.writeSettings(group);
RExpanderBox::writeSettings(group);
}
void DcrawSettingsWidget::readSettings(KConfigGroup& group)
{
RawDecodingSettings prm;
prm.readSettings(group);
setSettings(prm);
RExpanderBox::readSettings(group);
}
} // NameSpace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp
index 38fdc2b2b7..1c761f8563 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp
@@ -1,690 +1,694 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
* <a href="http://www.digikam.org">http://www.digikam.org</a>
*
* @date 2008-10-09
* @brief internal private container for KDcraw
*
* @author Copyright (C) 2008-2015 by Gilles Caulier
* <a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "kdcraw.h"
#include "kdcraw_p.h"
// Qt includes
#include <QString>
#include <QFile>
// Local includes
#include "libkdcraw_debug.h"
namespace KDcrawIface
{
int callbackForLibRaw(void* data, enum LibRaw_progress p, int iteration, int expected)
{
if (data)
{
KDcraw::Private* const d = static_cast<KDcraw::Private*>(data);
if (d)
{
return d->progressCallback(p, iteration, expected);
}
}
return 0;
}
// --------------------------------------------------------------------------------------------------
KDcraw::Private::Private(KDcraw* const p)
{
m_progress = 0.0;
m_parent = p;
}
KDcraw::Private::~Private()
{
}
void KDcraw::Private::createPPMHeader(QByteArray& imgData, libraw_processed_image_t* const img)
{
QString header = QString("P%1\n%2 %3\n%4\n").arg(img->colors == 3 ? "6" : "5")
.arg(img->width)
.arg(img->height)
.arg((1 << img->bits)-1);
imgData.append(header.toLatin1());
imgData.append(QByteArray((const char*)img->data, (int)img->data_size));
}
int KDcraw::Private::progressCallback(enum LibRaw_progress p, int iteration, int expected)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw progress: " << libraw_strprogress(p) << " pass "
<< iteration << " of " << expected;
// post a little change in progress indicator to show raw processor activity.
setProgress(progressValue()+0.01);
// Clean processing termination by user...
if (m_parent->checkToCancelWaitingData())
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw process terminaison invoked...";
m_parent->m_cancel = true;
m_progress = 0.0;
return 1;
}
// Return 0 to continue processing...
return 0;
}
void KDcraw::Private::setProgress(double value)
{
m_progress = value;
m_parent->setWaitingDataProgress(m_progress);
}
double KDcraw::Private::progressValue() const
{
return m_progress;
}
void KDcraw::Private::fillIndentifyInfo(LibRaw* const raw, DcrawInfoContainer& identify)
{
+#if QT_VERSION >= 0x050900
+ identify.dateTime.setSecsSinceEpoch(raw->imgdata.other.timestamp);
+#else
identify.dateTime.setTime_t(raw->imgdata.other.timestamp);
+#endif
identify.make = QString(raw->imgdata.idata.make);
identify.model = QString(raw->imgdata.idata.model);
identify.owner = QString(raw->imgdata.other.artist);
identify.DNGVersion = QString::number(raw->imgdata.idata.dng_version);
identify.sensitivity = raw->imgdata.other.iso_speed;
identify.exposureTime = raw->imgdata.other.shutter;
identify.aperture = raw->imgdata.other.aperture;
identify.focalLength = raw->imgdata.other.focal_len;
identify.imageSize = QSize(raw->imgdata.sizes.width, raw->imgdata.sizes.height);
identify.fullSize = QSize(raw->imgdata.sizes.raw_width, raw->imgdata.sizes.raw_height);
identify.outputSize = QSize(raw->imgdata.sizes.iwidth, raw->imgdata.sizes.iheight);
identify.thumbSize = QSize(raw->imgdata.thumbnail.twidth, raw->imgdata.thumbnail.theight);
identify.topMargin = raw->imgdata.sizes.top_margin;
identify.leftMargin = raw->imgdata.sizes.left_margin;
identify.hasIccProfile = raw->imgdata.color.profile ? true : false;
identify.isDecodable = true;
identify.pixelAspectRatio = raw->imgdata.sizes.pixel_aspect;
identify.rawColors = raw->imgdata.idata.colors;
identify.rawImages = raw->imgdata.idata.raw_count;
identify.blackPoint = raw->imgdata.color.black;
for (int ch = 0; ch < 4; ch++)
{
identify.blackPointCh[ch] = raw->imgdata.color.cblack[ch];
}
identify.whitePoint = raw->imgdata.color.maximum;
identify.orientation = (DcrawInfoContainer::ImageOrientation)raw->imgdata.sizes.flip;
memcpy(&identify.cameraColorMatrix1, &raw->imgdata.color.cmatrix, sizeof(raw->imgdata.color.cmatrix));
memcpy(&identify.cameraColorMatrix2, &raw->imgdata.color.rgb_cam, sizeof(raw->imgdata.color.rgb_cam));
memcpy(&identify.cameraXYZMatrix, &raw->imgdata.color.cam_xyz, sizeof(raw->imgdata.color.cam_xyz));
if (raw->imgdata.idata.filters)
{
if (!raw->imgdata.idata.cdesc[3])
{
raw->imgdata.idata.cdesc[3] = 'G';
}
for (int i=0; i < 16; i++)
{
identify.filterPattern.append(raw->imgdata.idata.cdesc[raw->COLOR(i >> 1,i & 1)]);
}
identify.colorKeys = raw->imgdata.idata.cdesc;
}
for(int c = 0 ; c < raw->imgdata.idata.colors ; c++)
{
identify.daylightMult[c] = raw->imgdata.color.pre_mul[c];
}
if (raw->imgdata.color.cam_mul[0] > 0)
{
for(int c = 0 ; c < 4 ; c++)
{
identify.cameraMult[c] = raw->imgdata.color.cam_mul[c];
}
}
}
bool KDcraw::Private::loadFromLibraw(const QString& filePath, QByteArray& imageData,
int& width, int& height, int& rgbmax)
{
m_parent->m_cancel = false;
LibRaw raw;
// Set progress call back function.
raw.set_progress_handler(callbackForLibRaw, this);
QByteArray deadpixelPath = QFile::encodeName(m_parent->m_rawDecodingSettings.deadPixelMap);
QByteArray cameraProfile = QFile::encodeName(m_parent->m_rawDecodingSettings.inputProfile);
QByteArray outputProfile = QFile::encodeName(m_parent->m_rawDecodingSettings.outputProfile);
if (!m_parent->m_rawDecodingSettings.autoBrightness)
{
// Use a fixed white level, ignoring the image histogram.
raw.imgdata.params.no_auto_bright = 1;
}
if (m_parent->m_rawDecodingSettings.sixteenBitsImage)
{
// (-4) 16bit ppm output
raw.imgdata.params.output_bps = 16;
}
if (m_parent->m_rawDecodingSettings.halfSizeColorImage)
{
// (-h) Half-size color image (3x faster than -q).
raw.imgdata.params.half_size = 1;
}
if (m_parent->m_rawDecodingSettings.RGBInterpolate4Colors)
{
// (-f) Interpolate RGB as four colors.
raw.imgdata.params.four_color_rgb = 1;
}
if (m_parent->m_rawDecodingSettings.DontStretchPixels)
{
// (-j) Do not stretch the image to its correct aspect ratio.
raw.imgdata.params.use_fuji_rotate = 1;
}
// (-H) Unclip highlight color.
raw.imgdata.params.highlight = m_parent->m_rawDecodingSettings.unclipColors;
if (m_parent->m_rawDecodingSettings.brightness != 1.0)
{
// (-b) Set Brightness value.
raw.imgdata.params.bright = m_parent->m_rawDecodingSettings.brightness;
}
if (m_parent->m_rawDecodingSettings.enableBlackPoint)
{
// (-k) Set Black Point value.
raw.imgdata.params.user_black = m_parent->m_rawDecodingSettings.blackPoint;
}
if (m_parent->m_rawDecodingSettings.enableWhitePoint)
{
// (-S) Set White Point value (saturation).
raw.imgdata.params.user_sat = m_parent->m_rawDecodingSettings.whitePoint;
}
if (m_parent->m_rawDecodingSettings.medianFilterPasses > 0)
{
// (-m) After interpolation, clean up color artifacts by repeatedly applying a 3x3 median filter to the R-G and B-G channels.
raw.imgdata.params.med_passes = m_parent->m_rawDecodingSettings.medianFilterPasses;
}
if (!m_parent->m_rawDecodingSettings.deadPixelMap.isEmpty())
{
// (-P) Read the dead pixel list from this file.
raw.imgdata.params.bad_pixels = deadpixelPath.data();
}
switch (m_parent->m_rawDecodingSettings.whiteBalance)
{
case RawDecodingSettings::NONE:
{
break;
}
case RawDecodingSettings::CAMERA:
{
// (-w) Use camera white balance, if possible.
raw.imgdata.params.use_camera_wb = 1;
break;
}
case RawDecodingSettings::AUTO:
{
// (-a) Use automatic white balance.
raw.imgdata.params.use_auto_wb = 1;
break;
}
case RawDecodingSettings::CUSTOM:
{
/* Convert between Temperature and RGB.
*/
double T;
double RGB[3];
double xD, yD, X, Y, Z;
DcrawInfoContainer identify;
T = m_parent->m_rawDecodingSettings.customWhiteBalance;
/* Here starts the code picked and adapted from ufraw (0.12.1)
to convert Temperature + green multiplier to RGB multipliers
*/
/* Convert between Temperature and RGB.
* Base on information from http://www.brucelindbloom.com/
* The fit for D-illuminant between 4000K and 12000K are from CIE
* The generalization to 2000K < T < 4000K and the blackbody fits
* are my own and should be taken with a grain of salt.
*/
const double XYZ_to_RGB[3][3] = {
{ 3.24071, -0.969258, 0.0556352 },
{-1.53726, 1.87599, -0.203996 },
{-0.498571, 0.0415557, 1.05707 }
};
// Fit for CIE Daylight illuminant
if (T <= 4000)
{
xD = 0.27475e9/(T*T*T) - 0.98598e6/(T*T) + 1.17444e3/T + 0.145986;
}
else if (T <= 7000)
{
xD = -4.6070e9/(T*T*T) + 2.9678e6/(T*T) + 0.09911e3/T + 0.244063;
}
else
{
xD = -2.0064e9/(T*T*T) + 1.9018e6/(T*T) + 0.24748e3/T + 0.237040;
}
yD = -3*xD*xD + 2.87*xD - 0.275;
X = xD/yD;
Y = 1;
Z = (1-xD-yD)/yD;
RGB[0] = X*XYZ_to_RGB[0][0] + Y*XYZ_to_RGB[1][0] + Z*XYZ_to_RGB[2][0];
RGB[1] = X*XYZ_to_RGB[0][1] + Y*XYZ_to_RGB[1][1] + Z*XYZ_to_RGB[2][1];
RGB[2] = X*XYZ_to_RGB[0][2] + Y*XYZ_to_RGB[1][2] + Z*XYZ_to_RGB[2][2];
/* End of the code picked to ufraw
*/
RGB[1] = RGB[1] / m_parent->m_rawDecodingSettings.customWhiteBalanceGreen;
/* By default, decraw override his default D65 WB
We need to keep it as a basis : if not, colors with some
DSLR will have a high dominant of color that will lead to
a completely wrong WB
*/
if (rawFileIdentify(identify, filePath))
{
RGB[0] = identify.daylightMult[0] / RGB[0];
RGB[1] = identify.daylightMult[1] / RGB[1];
RGB[2] = identify.daylightMult[2] / RGB[2];
}
else
{
RGB[0] = 1.0 / RGB[0];
RGB[1] = 1.0 / RGB[1];
RGB[2] = 1.0 / RGB[2];
qCDebug(LIBKDCRAW_LOG) << "Warning: cannot get daylight multipliers";
}
// (-r) set Raw Color Balance Multipliers.
raw.imgdata.params.user_mul[0] = RGB[0];
raw.imgdata.params.user_mul[1] = RGB[1];
raw.imgdata.params.user_mul[2] = RGB[2];
raw.imgdata.params.user_mul[3] = RGB[1];
break;
}
case RawDecodingSettings::AERA:
{
// (-A) Calculate the white balance by averaging a rectangular area from image.
raw.imgdata.params.greybox[0] = m_parent->m_rawDecodingSettings.whiteBalanceArea.left();
raw.imgdata.params.greybox[1] = m_parent->m_rawDecodingSettings.whiteBalanceArea.top();
raw.imgdata.params.greybox[2] = m_parent->m_rawDecodingSettings.whiteBalanceArea.width();
raw.imgdata.params.greybox[3] = m_parent->m_rawDecodingSettings.whiteBalanceArea.height();
break;
}
}
// (-q) Use an interpolation method.
raw.imgdata.params.user_qual = m_parent->m_rawDecodingSettings.RAWQuality;
switch (m_parent->m_rawDecodingSettings.NRType)
{
case RawDecodingSettings::WAVELETSNR:
{
// (-n) Use wavelets to erase noise while preserving real detail.
raw.imgdata.params.threshold = m_parent->m_rawDecodingSettings.NRThreshold;
break;
}
case RawDecodingSettings::FBDDNR:
{
// (100 - 1000) => (1 - 10) conversion
raw.imgdata.params.fbdd_noiserd = lround(m_parent->m_rawDecodingSettings.NRThreshold / 100.0);
break;
}
case RawDecodingSettings::LINENR:
{
// (100 - 1000) => (0.001 - 0.02) conversion.
raw.imgdata.params.linenoise = m_parent->m_rawDecodingSettings.NRThreshold * 2.11E-5 + 0.00111111;
raw.imgdata.params.cfaline = true;
break;
}
case RawDecodingSettings::IMPULSENR:
{
// (100 - 1000) => (0.005 - 0.05) conversion.
raw.imgdata.params.lclean = m_parent->m_rawDecodingSettings.NRThreshold * 5E-5;
raw.imgdata.params.cclean = m_parent->m_rawDecodingSettings.NRChroThreshold * 5E-5;
raw.imgdata.params.cfa_clean = true;
break;
}
default: // No Noise Reduction
{
raw.imgdata.params.threshold = 0;
raw.imgdata.params.fbdd_noiserd = 0;
raw.imgdata.params.linenoise = 0;
raw.imgdata.params.cfaline = false;
raw.imgdata.params.lclean = 0;
raw.imgdata.params.cclean = 0;
raw.imgdata.params.cfa_clean = false;
break;
}
}
// Chromatic aberration correction.
raw.imgdata.params.ca_correc = m_parent->m_rawDecodingSettings.enableCACorrection;
raw.imgdata.params.cared = m_parent->m_rawDecodingSettings.caMultiplier[0];
raw.imgdata.params.cablue = m_parent->m_rawDecodingSettings.caMultiplier[1];
// Exposure Correction before interpolation.
raw.imgdata.params.exp_correc = m_parent->m_rawDecodingSettings.expoCorrection;
raw.imgdata.params.exp_shift = m_parent->m_rawDecodingSettings.expoCorrectionShift;
raw.imgdata.params.exp_preser = m_parent->m_rawDecodingSettings.expoCorrectionHighlight;
switch (m_parent->m_rawDecodingSettings.inputColorSpace)
{
case RawDecodingSettings::EMBEDDED:
{
// (-p embed) Use input profile from RAW file to define the camera's raw colorspace.
raw.imgdata.params.camera_profile = (char*)"embed";
break;
}
case RawDecodingSettings::CUSTOMINPUTCS:
{
if (!m_parent->m_rawDecodingSettings.inputProfile.isEmpty())
{
// (-p) Use input profile file to define the camera's raw colorspace.
raw.imgdata.params.camera_profile = cameraProfile.data();
}
break;
}
default:
{
// No input profile
break;
}
}
switch (m_parent->m_rawDecodingSettings.outputColorSpace)
{
case RawDecodingSettings::CUSTOMOUTPUTCS:
{
if (!m_parent->m_rawDecodingSettings.outputProfile.isEmpty())
{
// (-o) Use ICC profile file to define the output colorspace.
raw.imgdata.params.output_profile = outputProfile.data();
}
break;
}
default:
{
// (-o) Define the output colorspace.
raw.imgdata.params.output_color = m_parent->m_rawDecodingSettings.outputColorSpace;
break;
}
}
//-- Extended demosaicing settings ----------------------------------------------------------
raw.imgdata.params.dcb_iterations = m_parent->m_rawDecodingSettings.dcbIterations;
raw.imgdata.params.dcb_enhance_fl = m_parent->m_rawDecodingSettings.dcbEnhanceFl;
raw.imgdata.params.eeci_refine = m_parent->m_rawDecodingSettings.eeciRefine;
raw.imgdata.params.es_med_passes = m_parent->m_rawDecodingSettings.esMedPasses;
//-------------------------------------------------------------------------------------------
setProgress(0.1);
qCDebug(LIBKDCRAW_LOG) << filePath;
qCDebug(LIBKDCRAW_LOG) << m_parent->m_rawDecodingSettings;
int ret = raw.open_file((const char*)(QFile::encodeName(filePath)).constData());
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret);
raw.recycle();
return false;
}
if (m_parent->m_cancel)
{
raw.recycle();
return false;
}
setProgress(0.2);
ret = raw.unpack();
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run unpack: " << libraw_strerror(ret);
raw.recycle();
return false;
}
if (m_parent->m_cancel)
{
raw.recycle();
return false;
}
setProgress(0.25);
if (m_parent->m_rawDecodingSettings.fixColorsHighlights)
{
qCDebug(LIBKDCRAW_LOG) << "Applying LibRaw highlights adjustments";
// 1.0 is fallback to default value
raw.imgdata.params.adjust_maximum_thr = 1.0;
}
else
{
qCDebug(LIBKDCRAW_LOG) << "Disabling LibRaw highlights adjustments";
// 0.0 disables this feature
raw.imgdata.params.adjust_maximum_thr = 0.0;
}
ret = raw.dcraw_process();
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_process: " << libraw_strerror(ret);
raw.recycle();
return false;
}
if (m_parent->m_cancel)
{
raw.recycle();
return false;
}
setProgress(0.3);
libraw_processed_image_t* img = raw.dcraw_make_mem_image(&ret);
if(!img)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_make_mem_image: " << libraw_strerror(ret);
raw.recycle();
return false;
}
if (m_parent->m_cancel)
{
// Clear memory allocation. Introduced with LibRaw 0.11.0
raw.dcraw_clear_mem(img);
raw.recycle();
return false;
}
setProgress(0.35);
width = img->width;
height = img->height;
rgbmax = (1 << img->bits)-1;
if (img->colors == 3)
{
imageData = QByteArray((const char*)img->data, (int)img->data_size);
}
else
{
// img->colors == 1 (Grayscale) : convert to RGB
imageData = QByteArray();
for (int i = 0 ; i < (int)img->data_size ; ++i)
{
for (int j = 0 ; j < 3 ; ++j)
{
imageData.append(img->data[i]);
}
}
}
// Clear memory allocation. Introduced with LibRaw 0.11.0
raw.dcraw_clear_mem(img);
raw.recycle();
if (m_parent->m_cancel)
{
return false;
}
setProgress(0.4);
qCDebug(LIBKDCRAW_LOG) << "LibRaw: data info: width=" << width
<< " height=" << height
<< " rgbmax=" << rgbmax;
return true;
}
bool KDcraw::Private::loadEmbeddedPreview(QByteArray& imgData, LibRaw& raw)
{
int ret = raw.unpack_thumb();
if (ret != LIBRAW_SUCCESS)
{
raw.recycle();
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run unpack_thumb: " << libraw_strerror(ret);
raw.recycle();
return false;
}
libraw_processed_image_t* const thumb = raw.dcraw_make_mem_thumb(&ret);
if(!thumb)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_make_mem_thumb: " << libraw_strerror(ret);
raw.recycle();
return false;
}
if(thumb->type == LIBRAW_IMAGE_BITMAP)
{
createPPMHeader(imgData, thumb);
}
else
{
imgData = QByteArray((const char*)thumb->data, (int)thumb->data_size);
}
// Clear memory allocation. Introduced with LibRaw 0.11.0
raw.dcraw_clear_mem(thumb);
raw.recycle();
if ( imgData.isEmpty() )
{
qCDebug(LIBKDCRAW_LOG) << "Failed to load JPEG thumb from LibRaw!";
return false;
}
return true;
}
bool KDcraw::Private::loadHalfPreview(QImage& image, LibRaw& raw)
{
raw.imgdata.params.use_auto_wb = 1; // Use automatic white balance.
raw.imgdata.params.use_camera_wb = 1; // Use camera white balance, if possible.
raw.imgdata.params.half_size = 1; // Half-size color image (3x faster than -q).
QByteArray imgData;
int ret = raw.unpack();
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run unpack: " << libraw_strerror(ret);
raw.recycle();
return false;
}
ret = raw.dcraw_process();
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_process: " << libraw_strerror(ret);
raw.recycle();
return false;
}
libraw_processed_image_t* halfImg = raw.dcraw_make_mem_image(&ret);
if(!halfImg)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_make_mem_image: " << libraw_strerror(ret);
raw.recycle();
return false;
}
Private::createPPMHeader(imgData, halfImg);
// Clear memory allocation. Introduced with LibRaw 0.11.0
raw.dcraw_clear_mem(halfImg);
raw.recycle();
if ( imgData.isEmpty() )
{
qCDebug(LIBKDCRAW_LOG) << "Failed to load half preview from LibRaw!";
return false;
}
if (!image.loadFromData(imgData))
{
qCDebug(LIBKDCRAW_LOG) << "Failed to load PPM data from LibRaw!";
return false;
}
return true;
}
} // namespace KDcrawIface
diff --git a/plugins/paintops/CMakeLists.txt b/plugins/paintops/CMakeLists.txt
index 8fe2cc7786..57e8151a61 100644
--- a/plugins/paintops/CMakeLists.txt
+++ b/plugins/paintops/CMakeLists.txt
@@ -1,20 +1,19 @@
include_directories(libpaintop)
add_subdirectory( libpaintop )
add_subdirectory( defaultpresets )
add_subdirectory( defaultpaintops )
add_subdirectory( hairy )
-add_subdirectory( chalk )
add_subdirectory( dynadraw )
add_subdirectory( deform )
add_subdirectory( curvebrush )
add_subdirectory( spray )
add_subdirectory( filterop )
add_subdirectory( experiment )
add_subdirectory( particle )
add_subdirectory( gridbrush )
add_subdirectory( hatching)
add_subdirectory( sketch )
add_subdirectory( colorsmudge )
add_subdirectory( roundmarker )
add_subdirectory( tangentnormal )
diff --git a/plugins/paintops/chalk/CMakeLists.txt b/plugins/paintops/chalk/CMakeLists.txt
deleted file mode 100644
index ba2afefcf3..0000000000
--- a/plugins/paintops/chalk/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-set(kritachalkpaintop_SOURCES
- chalk_paintop_plugin.cpp
- kis_chalk_paintop.cpp
- kis_chalkop_option.cpp
- kis_chalk_paintop_settings.cpp
- kis_chalk_paintop_settings_widget.cpp
- chalk_brush.cpp
- )
-
-ki18n_wrap_ui(kritachalkpaintop_SOURCES wdgchalkoptions.ui )
-
-add_library(kritachalkpaintop MODULE ${kritachalkpaintop_SOURCES})
-
-target_link_libraries(kritachalkpaintop kritalibpaintop)
-
-install(TARGETS kritachalkpaintop DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
-install( FILES
- krita-chalk.png DESTINATION ${DATA_INSTALL_DIR}/krita/images)
diff --git a/plugins/paintops/chalk/chalk_brush.cpp b/plugins/paintops/chalk/chalk_brush.cpp
deleted file mode 100644
index e9e0420cf0..0000000000
--- a/plugins/paintops/chalk/chalk_brush.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (c) 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include "chalk_brush.h"
-
-#include "kis_global.h"
-
-#include <KoColor.h>
-#include <KoColorSpace.h>
-#include <KoColorTransformation.h>
-
-#include <QVariant>
-#include <QHash>
-
-#include "kis_random_accessor_ng.h"
-#include <cmath>
-#include <ctime>
-
-
-ChalkBrush::ChalkBrush(const ChalkProperties* properties, KoColorTransformation* transformation)
-{
- m_transfo = transformation;
- if (m_transfo) {
- m_transfo->setParameter(m_transfo->parameterId("h"), 0.0);
- m_saturationId = m_transfo->parameterId("s"); // cache for later usage
- m_transfo->setParameter(m_transfo->parameterId("v"), 0.0);
- m_transfo->setParameter(3, 1);//sets the type to hsv.
- m_transfo->setParameter(4, false);//sets the colorize to none.
- }
- else {
- m_saturationId = -1;
- }
-
-
- m_counter = 0;
- m_properties = properties;
-}
-
-
-ChalkBrush::~ChalkBrush()
-{
- delete m_transfo;
-}
-
-
-void ChalkBrush::paint(KisPaintDeviceSP dev, qreal x, qreal y, const KoColor &color, qreal additionalScale)
-{
- m_inkColor = color;
- m_counter++;
-
- qint32 pixelSize = dev->colorSpace()->pixelSize();
- KisRandomAccessorSP accessor = dev->createRandomAccessorNG((int)x, (int)y);
-
- qreal result;
- if (m_properties->inkDepletion) {
- //count decrementing of saturation and opacity
- result = log((qreal)m_counter);
- result = -(result * 10) / 100.0;
-
- if (m_properties->useSaturation) {
- if (m_transfo) {
- m_transfo->setParameter(m_saturationId, 1.0f + result);
- m_transfo->transform(m_inkColor.data(), m_inkColor.data(), 1);
- }
-
- }
-
- if (m_properties->useOpacity) {
- qreal opacity = (1.0f + result);
- m_inkColor.setOpacity(opacity);
- }
- }
-
- int pixelX, pixelY;
- const int radius = m_properties->radius * additionalScale;
- const int radiusSquared = pow2(radius);
- double dirtThreshold = 0.5;
-
-
- for (int by = -radius; by <= radius; by++) {
- int bySquared = by * by;
- for (int bx = -radius; bx <= radius; bx++) {
- // let's call that noise from ground to chalk :)
- if (((bx * bx + bySquared) > radiusSquared) ||
- m_randomSource.generateNormalized() < dirtThreshold) {
- continue;
- }
-
- pixelX = qRound(x + bx);
- pixelY = qRound(y + by);
-
- accessor->moveTo(pixelX, pixelY);
- memcpy(accessor->rawData(), m_inkColor.data(), pixelSize);
- }
- }
-}
-
diff --git a/plugins/paintops/chalk/chalk_paintop_plugin.cpp b/plugins/paintops/chalk/chalk_paintop_plugin.cpp
deleted file mode 100644
index f3948920d1..0000000000
--- a/plugins/paintops/chalk/chalk_paintop_plugin.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2008 Lukáš Tvrdý (lukast.dev@gmail.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-#include "chalk_paintop_plugin.h"
-
-
-#include <klocalizedstring.h>
-
-#include <kis_debug.h>
-#include <kpluginfactory.h>
-
-#include <brushengine/kis_paintop_registry.h>
-
-
-#include "kis_chalk_paintop.h"
-#include "kis_simple_paintop_factory.h"
-
-#include "kis_global.h"
-
-K_PLUGIN_FACTORY_WITH_JSON(ChalkPaintOpPluginFactory, "kritachalkpaintop.json", registerPlugin<ChalkPaintOpPlugin>();)
-
-
-ChalkPaintOpPlugin::ChalkPaintOpPlugin(QObject *parent, const QVariantList &)
- : QObject(parent)
-{
- KisPaintOpRegistry *r = KisPaintOpRegistry::instance();
- r->add(new KisSimplePaintOpFactory<KisChalkPaintOp, KisChalkPaintOpSettings, KisChalkPaintOpSettingsWidget>("chalkbrush", i18n("Chalk"), KisPaintOpFactory::categoryStable(), "krita-chalk.png"));
-
-}
-
-ChalkPaintOpPlugin::~ChalkPaintOpPlugin()
-{
-}
-
-#include "chalk_paintop_plugin.moc"
diff --git a/plugins/paintops/chalk/kis_chalk_paintop.cpp b/plugins/paintops/chalk/kis_chalk_paintop.cpp
deleted file mode 100644
index 5c8158fcf1..0000000000
--- a/plugins/paintops/chalk/kis_chalk_paintop.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (c) 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include "kis_chalk_paintop.h"
-#include "kis_chalk_paintop_settings.h"
-
-#include <cmath>
-#include <QRect>
-
-#include <KoColor.h>
-#include <KoColorSpace.h>
-
-#include <kis_image.h>
-#include <kis_debug.h>
-
-#include <kis_global.h>
-#include <kis_paint_device.h>
-#include <kis_painter.h>
-#include <kis_types.h>
-#include <brushengine/kis_paintop.h>
-#include <brushengine/kis_paint_information.h>
-
-#include <kis_pressure_opacity_option.h>
-#include <kis_lod_transform.h>
-#include <kis_paintop_plugin_utils.h>
-
-
-KisChalkPaintOp::KisChalkPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image)
- : KisPaintOp(painter)
-{
- Q_UNUSED(image);
- Q_UNUSED(node);
- m_airbrushOption.readOptionSetting(settings);
- m_opacityOption.readOptionSetting(settings);
- m_rateOption.readOptionSetting(settings);
- m_opacityOption.resetAllSensors();
- m_rateOption.resetAllSensors();
-
- m_properties.readOptionSetting(settings);
-
- KoColorTransformation* transfo = 0;
- if (m_properties.inkDepletion && m_properties.useSaturation) {
- transfo = painter->device()->compositionSourceColorSpace()->createColorTransformation("hsv_adjustment", QHash<QString, QVariant>());
- }
- m_chalkBrush = new ChalkBrush(&m_properties, transfo);
-}
-
-KisChalkPaintOp::~KisChalkPaintOp()
-{
- delete m_chalkBrush;
-}
-
-KisSpacingInformation KisChalkPaintOp::paintAt(const KisPaintInformation& info)
-{
- if (!painter()) return KisSpacingInformation(1.0);
-
- if (!m_dab) {
- m_dab = source()->createCompositionSourceDevice();
- }
- else {
- m_dab->clear();
- }
-
- qreal x1, y1;
-
- x1 = info.pos().x();
- y1 = info.pos().y();
-
- const qreal additionalScale = KisLodTransform::lodToScale(painter()->device());
-
- quint8 origOpacity = m_opacityOption.apply(painter(), info);
- m_chalkBrush->paint(m_dab, x1, y1, painter()->paintColor(), additionalScale);
-
- QRect rc = m_dab->extent();
-
- painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height());
- painter()->renderMirrorMask(rc, m_dab);
- painter()->setOpacity(origOpacity);
-
- return updateSpacingImpl(info);
-}
-
-KisSpacingInformation KisChalkPaintOp::updateSpacingImpl(const KisPaintInformation &info) const
-{
- return KisPaintOpPluginUtils::effectiveSpacing(1.0, 1.0, true, 0.0, false, 1.0, false, 1.0, 1.0,
- &m_airbrushOption, nullptr, info);
-}
-
-KisTimingInformation KisChalkPaintOp::updateTimingImpl(const KisPaintInformation &info) const
-{
- return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info);
-}
diff --git a/plugins/paintops/chalk/kis_chalk_paintop.h b/plugins/paintops/chalk/kis_chalk_paintop.h
deleted file mode 100644
index 2cec5ce181..0000000000
--- a/plugins/paintops/chalk/kis_chalk_paintop.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (c) 2008 Boudewijn Rempt <boud@valdyas.org>
- * Copyright (c) 2008, 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef KIS_CHALK_PAINTOP_H_
-#define KIS_CHALK_PAINTOP_H_
-
-#include <brushengine/kis_paintop.h>
-#include <kis_types.h>
-
-#include "chalk_brush.h"
-#include "kis_chalk_paintop_settings.h"
-#include "kis_airbrush_option.h"
-#include "kis_pressure_rate_option.h"
-
-class KisPainter;
-
-class KisChalkPaintOp : public KisPaintOp
-{
-
-public:
-
- KisChalkPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image);
- ~KisChalkPaintOp() override;
-
-protected:
-
- KisSpacingInformation paintAt(const KisPaintInformation& info) override;
-
- KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override;
-
- KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override;
-
-private:
- KisPaintDeviceSP m_dab;
- ChalkBrush * m_chalkBrush;
- KisAirbrushOption m_airbrushOption;
- KisPressureOpacityOption m_opacityOption;
- KisPressureRateOption m_rateOption;
- ChalkProperties m_properties;
-};
-
-#endif // KIS_CHALK_PAINTOP_H_
diff --git a/plugins/paintops/chalk/kis_chalk_paintop_settings.cpp b/plugins/paintops/chalk/kis_chalk_paintop_settings.cpp
deleted file mode 100644
index 2409d03618..0000000000
--- a/plugins/paintops/chalk/kis_chalk_paintop_settings.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2008 Lukáš Tvrdý <lukast.dev@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include "kis_chalk_paintop_settings.h"
-
-#include <kis_chalkop_option.h>
-
-#include <kis_paint_action_type_option.h>
-#include <kis_airbrush_option.h>
-
-KisChalkPaintOpSettings::KisChalkPaintOpSettings()
-{
-}
-
-bool KisChalkPaintOpSettings::paintIncremental()
-{
- return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP;
-}
-
-QPainterPath KisChalkPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode)
-{
- QPainterPath path;
- if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) {
- qreal size = getInt(CHALK_RADIUS) * 2 + 1;
- path = ellipseOutline(size, size, 1.0, 0.0);
-
- if (mode == CursorTiltOutline) {
- path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), size * 0.5, 3.0));
- }
-
- path.translate(info.pos());
- }
- return path;
-}
-
-void KisChalkPaintOpSettings::setPaintOpSize(qreal value)
-{
- ChalkProperties properties;
- properties.readOptionSetting(this);
- properties.radius = qRound(0.5 * value);
- properties.writeOptionSetting(this);
-}
-
-qreal KisChalkPaintOpSettings::paintOpSize() const
-{
- ChalkProperties properties;
- properties.readOptionSetting(this);
- return properties.radius * 2;
-}
diff --git a/plugins/paintops/chalk/kis_chalk_paintop_settings.h b/plugins/paintops/chalk/kis_chalk_paintop_settings.h
deleted file mode 100644
index 3096f50727..0000000000
--- a/plugins/paintops/chalk/kis_chalk_paintop_settings.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2008,2009 Lukáš Tvrdý <lukast.dev@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef KIS_CHALK_PAINTOP_SETTINGS_H_
-#define KIS_CHALK_PAINTOP_SETTINGS_H_
-
-#include <brushengine/kis_paintop_settings.h>
-#include <kis_types.h>
-
-#include "kis_chalk_paintop_settings_widget.h"
-
-#include <kis_pressure_opacity_option.h>
-
-
-class KisChalkPaintOpSettings : public KisPaintOpSettings
-{
-
-public:
- KisChalkPaintOpSettings();
- ~KisChalkPaintOpSettings() override {}
-
- QPainterPath brushOutline(const KisPaintInformation &info, OutlineMode mode) override;
-
- void setPaintOpSize(qreal value) override;
- qreal paintOpSize() const override;
-
- bool paintIncremental() override;
-};
-
-#endif
diff --git a/plugins/paintops/chalk/kis_chalk_paintop_settings_widget.cpp b/plugins/paintops/chalk/kis_chalk_paintop_settings_widget.cpp
deleted file mode 100644
index d57d7c8ee7..0000000000
--- a/plugins/paintops/chalk/kis_chalk_paintop_settings_widget.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2008 Lukáš Tvrdý <lukast.dev@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include "kis_chalk_paintop_settings_widget.h"
-
-#include "kis_chalkop_option.h"
-#include "kis_chalk_paintop_settings.h"
-
-#include <kis_curve_option_widget.h>
-#include <kis_pressure_opacity_option.h>
-#include <kis_pressure_rate_option.h>
-
-#include <kis_paintop_settings_widget.h>
-#include <kis_paint_action_type_option.h>
-#include <kis_airbrush_option.h>
-
-KisChalkPaintOpSettingsWidget:: KisChalkPaintOpSettingsWidget(QWidget* parent)
- : KisPaintOpSettingsWidget(parent)
-{
- m_chalkOption = new KisChalkOpOption();
- addPaintOpOption(m_chalkOption, i18n("Brush size"));
- addPaintOpOption(new KisCurveOptionWidget(new KisPressureOpacityOption(), i18n("Transparent"), i18n("Opaque")), i18n("Opacity"));
- addPaintOpOption(new KisAirbrushOption(false), i18n("Airbrush"));
- addPaintOpOption(new KisCurveOptionWidget(new KisPressureRateOption(), i18n("0%"), i18n("100%")), i18n("Rate"));
- addPaintOpOption(new KisPaintActionTypeOption(), i18n("Painting Mode"));
-}
-
-KisChalkPaintOpSettingsWidget::~ KisChalkPaintOpSettingsWidget()
-{
-}
-
-KisPropertiesConfigurationSP KisChalkPaintOpSettingsWidget::configuration() const
-{
- KisChalkPaintOpSettings* config = new KisChalkPaintOpSettings();
- config->setOptionsWidget(const_cast<KisChalkPaintOpSettingsWidget*>(this));
- config->setProperty("paintop", "chalkbrush"); // XXX: make this a const id string
- writeConfiguration(config);
- return config;
-}
diff --git a/plugins/paintops/chalk/kis_chalkop_option.cpp b/plugins/paintops/chalk/kis_chalkop_option.cpp
deleted file mode 100644
index 546c96f042..0000000000
--- a/plugins/paintops/chalk/kis_chalkop_option.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (c) 2008,2010 Lukáš Tvrdý <lukast.dev@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-#include "kis_chalkop_option.h"
-
-#include "ui_wdgchalkoptions.h"
-
-class KisChalkOpOptionsWidget: public QWidget, public Ui::WdgChalkOptions
-{
-public:
- KisChalkOpOptionsWidget(QWidget *parent = 0)
- : QWidget(parent) {
- setupUi(this);
- }
-};
-
-KisChalkOpOption::KisChalkOpOption()
- : KisPaintOpOption(KisPaintOpOption::GENERAL, false)
-{
- m_checkable = false;
- m_options = new KisChalkOpOptionsWidget();
- m_options->hide();
-
- setObjectName("KisChalkOpOption");
-
- // initialize values
- m_options->radiusSpinBox->setRange(0, 400);
- m_options->radiusSpinBox->setValue(5);
- m_options->radiusSpinBox->setSuffix(i18n(" px"));
-
- connect(m_options->radiusSpinBox, SIGNAL(valueChanged(int)), SLOT(emitSettingChanged()));
- connect(m_options->inkDepletionCHBox, SIGNAL(clicked(bool)), SLOT(emitSettingChanged()));
- connect(m_options->opacity, SIGNAL(clicked(bool)), SLOT(emitSettingChanged()));
- connect(m_options->saturation, SIGNAL(clicked(bool)), SLOT(emitSettingChanged()));
- setConfigurationPage(m_options);
-}
-
-KisChalkOpOption::~KisChalkOpOption()
-{
- // delete m_options;
-}
-
-int KisChalkOpOption::radius() const
-{
- return m_options->radiusSpinBox->value();
-}
-
-
-void KisChalkOpOption::setRadius(int radius) const
-{
- m_options->radiusSpinBox->setValue(radius);
-}
-
-
-
-bool KisChalkOpOption::inkDepletion() const
-{
- return m_options->inkDepletionCHBox->isChecked();
-}
-
-
-
-bool KisChalkOpOption::opacity() const
-{
- return m_options->opacity->isChecked();
-}
-
-
-bool KisChalkOpOption::saturation() const
-{
- return m_options->saturation->isChecked();
-}
-
-
-void KisChalkOpOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const
-{
- setting->setProperty(CHALK_RADIUS, radius());
- setting->setProperty(CHALK_INK_DEPLETION, inkDepletion());
- setting->setProperty(CHALK_USE_OPACITY, opacity());
- setting->setProperty(CHALK_USE_SATURATION, saturation());
-}
-
-void KisChalkOpOption::readOptionSetting(const KisPropertiesConfigurationSP setting)
-{
- m_options->radiusSpinBox->setValue(setting->getInt(CHALK_RADIUS));
- m_options->inkDepletionCHBox->setChecked(setting->getBool(CHALK_INK_DEPLETION));
- m_options->opacity->setChecked(setting->getBool(CHALK_USE_OPACITY));
- m_options->saturation->setChecked(setting->getBool(CHALK_USE_SATURATION));
-}
-
-
diff --git a/plugins/paintops/chalk/kis_chalkop_option.h b/plugins/paintops/chalk/kis_chalkop_option.h
deleted file mode 100644
index 98a123dea1..0000000000
--- a/plugins/paintops/chalk/kis_chalkop_option.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (c) 2008,2010 Lukáš Tvrdý <lukast.dev@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-#ifndef KIS_CHALKOP_OPTION_H
-#define KIS_CHALKOP_OPTION_H
-
-#include <kis_paintop_option.h>
-
-const QString CHALK_RADIUS = "Chalk/radius";
-const QString CHALK_INK_DEPLETION = "Chalk/inkDepletion";
-const QString CHALK_USE_OPACITY = "Chalk/opacity";
-const QString CHALK_USE_SATURATION = "Chalk/saturation";
-
-class KisChalkOpOptionsWidget;
-
-class KisChalkOpOption : public KisPaintOpOption
-{
-public:
- KisChalkOpOption();
- ~KisChalkOpOption() override;
-
- void setRadius(int radius) const;
- int radius() const;
-
- bool inkDepletion() const;
- bool saturation() const;
- bool opacity() const;
-
- void writeOptionSetting(KisPropertiesConfigurationSP setting) const override;
- void readOptionSetting(const KisPropertiesConfigurationSP setting) override;
-
-
-private:
-
- KisChalkOpOptionsWidget * m_options;
-
-};
-
-class ChalkProperties : public KisBaseOption
-{
-public:
- int radius;
- bool inkDepletion;
- bool useOpacity;
- bool useSaturation;
-
- void readOptionSettingImpl(const KisPropertiesConfiguration *settings) override {
- radius = settings->getInt(CHALK_RADIUS);
- inkDepletion = settings->getBool(CHALK_INK_DEPLETION);
- useOpacity = settings->getBool(CHALK_USE_OPACITY);
- useSaturation = settings->getBool(CHALK_USE_SATURATION);
- }
-
- void writeOptionSettingImpl(KisPropertiesConfiguration* settings) const override {
- settings->setProperty(CHALK_RADIUS, radius);
- settings->setProperty(CHALK_INK_DEPLETION, inkDepletion);
- settings->setProperty(CHALK_USE_OPACITY, useOpacity);
- settings->setProperty(CHALK_USE_SATURATION, useSaturation);
- }
-};
-
-#endif
diff --git a/plugins/paintops/chalk/krita-chalk.png b/plugins/paintops/chalk/krita-chalk.png
deleted file mode 100644
index fd4d0fa896..0000000000
Binary files a/plugins/paintops/chalk/krita-chalk.png and /dev/null differ
diff --git a/plugins/paintops/chalk/kritachalkpaintop.json b/plugins/paintops/chalk/kritachalkpaintop.json
deleted file mode 100644
index 3ede7370ac..0000000000
--- a/plugins/paintops/chalk/kritachalkpaintop.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "Id": "Chalk brush",
- "Type": "Service",
- "X-KDE-Library": "kritachalkpaintop",
- "X-KDE-ServiceTypes": [
- "Krita/Paintop"
- ],
- "X-Krita-Version": "28"
-}
diff --git a/plugins/paintops/chalk/wdgchalkoptions.ui b/plugins/paintops/chalk/wdgchalkoptions.ui
deleted file mode 100644
index d6e11ed6fa..0000000000
--- a/plugins/paintops/chalk/wdgchalkoptions.ui
+++ /dev/null
@@ -1,169 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>WdgChalkOptions</class>
- <widget class="QWidget" name="WdgChalkOptions">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>330</width>
- <height>192</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>210</horstretch>
- <verstretch>60</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>330</width>
- <height>60</height>
- </size>
- </property>
- <widget class="QWidget" name="layoutWidget">
- <property name="geometry">
- <rect>
- <x>1</x>
- <y>11</y>
- <width>321</width>
- <height>131</height>
- </rect>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QLabel" name="radiusLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Brush Radius:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="KisSliderSpinBox" name="radiusSpinBox"/>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QCheckBox" name="inkDepletionCHBox">
- <property name="text">
- <string>Ink depletion</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="1">
- <widget class="QCheckBox" name="opacity">
- <property name="text">
- <string>Opacity decrease</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QCheckBox" name="saturation">
- <property name="text">
- <string>Saturation decrease</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="2" column="0">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </widget>
- <customwidgets>
- <customwidget>
- <class>KisSliderSpinBox</class>
- <extends>QWidget</extends>
- <header>kis_slider_spin_box.h</header>
- </customwidget>
- </customwidgets>
- <resources/>
- <connections>
- <connection>
- <sender>inkDepletionCHBox</sender>
- <signal>toggled(bool)</signal>
- <receiver>opacity</receiver>
- <slot>setEnabled(bool)</slot>
- <hints>
- <hint type="sourcelabel">
- <x>36</x>
- <y>70</y>
- </hint>
- <hint type="destinationlabel">
- <x>66</x>
- <y>102</y>
- </hint>
- </hints>
- </connection>
- <connection>
- <sender>inkDepletionCHBox</sender>
- <signal>toggled(bool)</signal>
- <receiver>saturation</receiver>
- <slot>setEnabled(bool)</slot>
- <hints>
- <hint type="sourcelabel">
- <x>18</x>
- <y>77</y>
- </hint>
- <hint type="destinationlabel">
- <x>74</x>
- <y>129</y>
- </hint>
- </hints>
- </connection>
- </connections>
-</ui>
diff --git a/plugins/paintops/colorsmudge/kis_smudge_radius_option.cpp b/plugins/paintops/colorsmudge/kis_smudge_radius_option.cpp
index e54355941d..b390c704c7 100644
--- a/plugins/paintops/colorsmudge/kis_smudge_radius_option.cpp
+++ b/plugins/paintops/colorsmudge/kis_smudge_radius_option.cpp
@@ -1,163 +1,163 @@
/*
* Copyright (C) 2014 Mohit Goyal <mohit.bits2011@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_smudge_radius_option.h"
#include <klocalizedstring.h>
#include <kis_painter.h>
#include <widgets/kis_curve_widget.h>
#include "kis_paint_device.h"
#include "KoPointerEvent.h"
#include "KoCanvasBase.h"
#include "kis_random_accessor_ng.h"
#include "KoColor.h"
#include <resources/KoColorSet.h>
#include <KoChannelInfo.h>
#include <KoMixColorsOp.h>
#include <kis_cross_device_color_picker.h>
#include <KoColor.h>
class KisRandomConstAccessorNG;
KisSmudgeRadiusOption::KisSmudgeRadiusOption():
KisRateOption("SmudgeRadius", KisPaintOpOption::GENERAL, true)
{
setValueRange(0.0,300.0);
}
void KisSmudgeRadiusOption::apply(KisPainter& painter,
const KisPaintInformation& info,
qreal diameter,
qreal posx,
qreal posy,
KisPaintDeviceSP dev) const
{
if (!isChecked()) return;
qreal sliderValue = computeSizeLikeValue(info);
int smudgeRadius = ((sliderValue * diameter) * 0.5) / 100.0;
KoColor color = painter.paintColor();
if (smudgeRadius == 1) {
dev->pixel(posx, posy, &color);
painter.setPaintColor(color);
} else {
const KoColorSpace* cs = dev->colorSpace();
int pixelSize = cs->pixelSize();
quint8* data = new quint8[pixelSize];
static quint8** pixels = new quint8*[2];
- qint16* weights = new qint16[2];
+ qint16 weights[2];
pixels[1] = new quint8[pixelSize];
pixels[0] = new quint8[pixelSize];
int loop_increment = 1;
if(smudgeRadius >= 8)
{
loop_increment = (2*smudgeRadius)/16;
}
int i = 0;
int k = 0;
int j = 0;
KisRandomConstAccessorSP accessor = dev->createRandomConstAccessorNG(0, 0);
KisCrossDeviceColorPickerInt colorPicker(painter.device(), color);
colorPicker.pickColor(posx, posy, color.data());
for (int y = 0; y <= smudgeRadius; y = y + loop_increment) {
for (int x = 0; x <= smudgeRadius; x = x + loop_increment) {
for(j = 0;j < 2;j++)
{
if(j == 1)
{
y = y*(-1);
}
for(k = 0;k < 2;k++)
{
if(k == 1)
{
x = x*(-1);
}
accessor->moveTo(posx + x, posy + y);
memcpy(pixels[1], accessor->rawDataConst(), pixelSize);
if(i == 0)
{
memcpy(pixels[0],accessor->rawDataConst(),pixelSize);
}
if (x == 0 && y == 0) {
// Because the sum of the weights must be 255,
// we cheat a bit, and weigh the center pixel differently in order
// to sum to 255 in total
// It's -(counts -1), because we'll add the center one implicitly
// through that calculation
weights[1] = (255 - ((i + 1) * (255 /(i+2) )) );
} else {
weights[1] = 255 /(i+2);
}
i++;
if (i>smudgeRadius){i=0;}
weights[0] = 255 - weights[1];
const quint8** cpixels = const_cast<const quint8**>(pixels);
cs->mixColorsOp()->mixColors(cpixels, weights,2, data);
memcpy(pixels[0],data,pixelSize);
}
x = x*(-1);
}
y = y*(-1);
}
}
KoColor color = KoColor(pixels[0],cs);
painter.setPaintColor(color);
for (int l = 0; l < 2; l++){
delete[] pixels[l];
}
// delete[] pixels;
delete[] data;
}
}
void KisSmudgeRadiusOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const
{
KisCurveOption::writeOptionSetting(setting);
}
void KisSmudgeRadiusOption::readOptionSetting(const KisPropertiesConfigurationSP setting)
{
KisCurveOption::readOptionSetting(setting);
}
diff --git a/plugins/paintops/defaultpaintops/CMakeLists.txt b/plugins/paintops/defaultpaintops/CMakeLists.txt
index 0e06523a94..68ac300940 100644
--- a/plugins/paintops/defaultpaintops/CMakeLists.txt
+++ b/plugins/paintops/defaultpaintops/CMakeLists.txt
@@ -1,28 +1,35 @@
add_subdirectory(brush/tests)
-include_directories(brush)
-include_directories(duplicate)
-
+include_directories(brush
+ duplicate
+ ${CMAKE_CURRENT_BINARY_DIR})
set(kritadefaultpaintops_SOURCES
defaultpaintops_plugin.cc
brush/kis_brushop.cpp
+ brush/KisBrushOpResources.cpp
+ brush/KisBrushOpSettings.cpp
brush/kis_brushop_settings_widget.cpp
- duplicate/kis_duplicateop.cpp
+ brush/KisDabRenderingQueue.cpp
+ brush/KisDabRenderingQueueCache.cpp
+ brush/KisDabRenderingJob.cpp
+ brush/KisDabRenderingExecutor.cpp
+ duplicate/kis_duplicateop.cpp
duplicate/kis_duplicateop_settings.cpp
duplicate/kis_duplicateop_settings_widget.cpp
duplicate/kis_duplicateop_option.cpp
)
ki18n_wrap_ui(kritadefaultpaintops_SOURCES duplicate/wdgduplicateop.ui )
add_library(kritadefaultpaintops MODULE ${kritadefaultpaintops_SOURCES})
+generate_export_header(kritadefaultpaintops BASE_NAME kritadefaultpaintops EXPORT_MACRO_NAME KRITADEFAULTPAINTOPS_EXPORT)
target_link_libraries(kritadefaultpaintops kritalibpaintop)
install(TARGETS kritadefaultpaintops DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( FILES
krita-paintbrush.png
krita-eraser.png
krita-duplicate.png
DESTINATION ${DATA_INSTALL_DIR}/krita/images)
diff --git a/plugins/paintops/defaultpaintops/brush/KisBrushOpResources.cpp b/plugins/paintops/defaultpaintops/brush/KisBrushOpResources.cpp
new file mode 100644
index 0000000000..5da1c8ea48
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/KisBrushOpResources.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisBrushOpResources.h"
+
+#include <KoColorSpace.h>
+#include <KoColorTransformation.h>
+
+#include "kis_color_source.h"
+#include "kis_pressure_mix_option.h"
+#include "kis_pressure_darken_option.h"
+#include "kis_pressure_hsv_option.h"
+#include "kis_color_source_option.h"
+#include "kis_pressure_sharpness_option.h"
+#include "kis_texture_option.h"
+#include "kis_painter.h"
+#include "kis_paintop_settings.h"
+
+struct KisBrushOpResources::Private
+{
+ QList<KisPressureHSVOption*> hsvOptions;
+ KoColorTransformation *hsvTransformation = 0;
+ KisPressureMixOption mixOption;
+ KisPressureDarkenOption darkenOption;
+};
+
+
+KisBrushOpResources::KisBrushOpResources(const KisPaintOpSettingsSP settings, KisPainter *painter)
+ : m_d(new Private)
+{
+ KisColorSourceOption colorSourceOption;
+ colorSourceOption.readOptionSetting(settings);
+ colorSource.reset(colorSourceOption.createColorSource(painter));
+
+ sharpnessOption.reset(new KisPressureSharpnessOption());
+ sharpnessOption->readOptionSetting(settings);
+ sharpnessOption->resetAllSensors();
+
+ textureOption.reset(new KisTextureProperties(painter->device()->defaultBounds()->currentLevelOfDetail()));
+ textureOption->fillProperties(settings);
+
+ m_d->hsvOptions.append(KisPressureHSVOption::createHueOption());
+ m_d->hsvOptions.append(KisPressureHSVOption::createSaturationOption());
+ m_d->hsvOptions.append(KisPressureHSVOption::createValueOption());
+
+ Q_FOREACH (KisPressureHSVOption * option, m_d->hsvOptions) {
+ option->readOptionSetting(settings);
+ option->resetAllSensors();
+ if (option->isChecked() && !m_d->hsvTransformation) {
+ m_d->hsvTransformation = painter->backgroundColor().colorSpace()->createColorTransformation("hsv_adjustment", QHash<QString, QVariant>());
+ }
+ }
+
+ m_d->darkenOption.readOptionSetting(settings);
+ m_d->mixOption.readOptionSetting(settings);
+
+ m_d->darkenOption.resetAllSensors();
+ m_d->mixOption.resetAllSensors();
+}
+
+KisBrushOpResources::~KisBrushOpResources()
+{
+ qDeleteAll(m_d->hsvOptions);
+ delete m_d->hsvTransformation;
+}
+
+void KisBrushOpResources::syncResourcesToSeqNo(int seqNo, const KisPaintInformation &info)
+{
+ colorSource->selectColor(m_d->mixOption.apply(info), info);
+ m_d->darkenOption.apply(colorSource.data(), info);
+
+ if (m_d->hsvTransformation) {
+ Q_FOREACH (KisPressureHSVOption * option, m_d->hsvOptions) {
+ option->apply(m_d->hsvTransformation, info);
+ }
+ colorSource->applyColorTransformation(m_d->hsvTransformation);
+ }
+
+ KisDabCacheUtils::DabRenderingResources::syncResourcesToSeqNo(seqNo, info);
+}
diff --git a/libs/ui/opengl/kis_opengl_canvas_debugger.h b/plugins/paintops/defaultpaintops/brush/KisBrushOpResources.h
similarity index 62%
copy from libs/ui/opengl/kis_opengl_canvas_debugger.h
copy to plugins/paintops/defaultpaintops/brush/KisBrushOpResources.h
index 8d7e605e96..dcd610bf0e 100644
--- a/libs/ui/opengl/kis_opengl_canvas_debugger.h
+++ b/plugins/paintops/defaultpaintops/brush/KisBrushOpResources.h
@@ -1,45 +1,43 @@
/*
- * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef __KIS_OPENGL_CANVAS_DEBUGGER_H
-#define __KIS_OPENGL_CANVAS_DEBUGGER_H
+#ifndef KISBRUSHOPRESOURCES_H
+#define KISBRUSHOPRESOURCES_H
+
+#include "KisDabCacheUtils.h"
#include <QScopedPointer>
+class KisPainter;
+class KisPaintInformation;
-class KisOpenglCanvasDebugger
+class KisBrushOpResources : public KisDabCacheUtils::DabRenderingResources
{
public:
- KisOpenglCanvasDebugger();
- ~KisOpenglCanvasDebugger();
-
- static KisOpenglCanvasDebugger* instance();
-
- bool showFpsOnCanvas() const;
+ KisBrushOpResources(const KisPaintOpSettingsSP settings, KisPainter *painter);
+ ~KisBrushOpResources() override;
- void nofityPaintRequested();
- void nofitySyncStatus(bool value);
- qreal accumulatedFps();
+ void syncResourcesToSeqNo(int seqNo, const KisPaintInformation &info) override;
private:
struct Private;
const QScopedPointer<Private> m_d;
};
-#endif /* __KIS_OPENGL_CANVAS_DEBUGGER_H */
+#endif // KISBRUSHOPRESOURCES_H
diff --git a/libs/image/kis_projection_updates_filter.cpp b/plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.cpp
similarity index 64%
copy from libs/image/kis_projection_updates_filter.cpp
copy to plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.cpp
index e1493ec367..f7573c09a5 100644
--- a/libs/image/kis_projection_updates_filter.cpp
+++ b/plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.cpp
@@ -1,36 +1,25 @@
/*
- * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "kis_projection_updates_filter.h"
+#include "KisBrushOpSettings.h"
-#include <QtGlobal>
-#include <QRect>
-
-KisProjectionUpdatesFilter::~KisProjectionUpdatesFilter()
-{
-}
-
-bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache)
+bool KisBrushOpSettings::needsAsynchronousUpdates() const
{
- Q_UNUSED(image);
- Q_UNUSED(node);
- Q_UNUSED(rect);
- Q_UNUSED(resetAnimationCache);
return true;
}
diff --git a/plugins/paintops/chalk/chalk_paintop_plugin.h b/plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.h
similarity index 63%
copy from plugins/paintops/chalk/chalk_paintop_plugin.h
copy to plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.h
index 0d78033afd..f4dcd7ed66 100644
--- a/plugins/paintops/chalk/chalk_paintop_plugin.h
+++ b/plugins/paintops/defaultpaintops/brush/KisBrushOpSettings.h
@@ -1,36 +1,31 @@
/*
- * Copyright (c) 2008 Lukáš Tvrdý (lukast.dev@gmail.com)
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef CHALK_PAINTOP_PLUGIN_H_
-#define CHALK_PAINTOP_PLUGIN_H_
+#ifndef KISBRUSHOPSETTINGS_H
+#define KISBRUSHOPSETTINGS_H
-#include <QObject>
-#include <QVariant>
+#include "kis_brush_based_paintop_settings.h"
-/**
- * A plugin wrapper that adds the paintop factories to the paintop registry.
- */
-class ChalkPaintOpPlugin : public QObject
+
+class KisBrushOpSettings : public KisBrushBasedPaintOpSettings
{
- Q_OBJECT
public:
- ChalkPaintOpPlugin(QObject *parent, const QVariantList &);
- ~ChalkPaintOpPlugin() override;
+ bool needsAsynchronousUpdates() const;
};
-#endif // CHALK_PAINTOP_PLUGIN_H_
+#endif // KISBRUSHOPSETTINGS_H
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp
new file mode 100644
index 0000000000..6ede283678
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisDabRenderingExecutor.h"
+
+#include "KisDabRenderingQueue.h"
+#include "KisDabRenderingQueueCache.h"
+#include "KisDabRenderingJob.h"
+#include "KisRenderedDab.h"
+#include "KisRunnableStrokeJobsInterface.h"
+#include "KisRunnableStrokeJobData.h"
+#include <tool/strokes/FreehandStrokeRunnableJobDataWithUpdate.h>
+
+struct KisDabRenderingExecutor::Private
+{
+ QScopedPointer<KisDabRenderingQueue> renderingQueue;
+ KisRunnableStrokeJobsInterface *runnableJobsInterface;
+};
+
+KisDabRenderingExecutor::KisDabRenderingExecutor(const KoColorSpace *cs,
+ KisDabCacheUtils::ResourcesFactory resourcesFactory,
+ KisRunnableStrokeJobsInterface *runnableJobsInterface,
+ KisPressureMirrorOption *mirrorOption,
+ KisPrecisionOption *precisionOption)
+ : m_d(new Private)
+{
+ m_d->runnableJobsInterface = runnableJobsInterface;
+
+ m_d->renderingQueue.reset(
+ new KisDabRenderingQueue(cs, resourcesFactory));
+
+ KisDabRenderingQueueCache *cache = new KisDabRenderingQueueCache();
+ cache->setMirrorPostprocessing(mirrorOption);
+ cache->setPrecisionOption(precisionOption);
+
+ m_d->renderingQueue->setCacheInterface(cache);
+}
+
+KisDabRenderingExecutor::~KisDabRenderingExecutor()
+{
+}
+
+void KisDabRenderingExecutor::addDab(const KisDabCacheUtils::DabRequestInfo &request,
+ qreal opacity, qreal flow)
+{
+ KisDabRenderingJobSP job = m_d->renderingQueue->addDab(request, opacity, flow);
+ if (job) {
+ m_d->runnableJobsInterface->addRunnableJob(
+ new FreehandStrokeRunnableJobDataWithUpdate(
+ new KisDabRenderingJobRunner(job, m_d->renderingQueue.data(), m_d->runnableJobsInterface),
+ KisStrokeJobData::CONCURRENT));
+ }
+}
+
+QList<KisRenderedDab> KisDabRenderingExecutor::takeReadyDabs(bool returnMutableDabs)
+{
+ return m_d->renderingQueue->takeReadyDabs(returnMutableDabs);
+}
+
+bool KisDabRenderingExecutor::hasPreparedDabs() const
+{
+ return m_d->renderingQueue->hasPreparedDabs();
+}
+
+int KisDabRenderingExecutor::averageDabRenderingTime() const
+{
+ return m_d->renderingQueue->averageExecutionTime();
+}
+
+int KisDabRenderingExecutor::averageDabSize() const
+{
+ return m_d->renderingQueue->averageDabSize();
+}
+
+void KisDabRenderingExecutor::recyclePaintDevicesForCache(const QVector<KisFixedPaintDeviceSP> devices)
+{
+ m_d->renderingQueue->recyclePaintDevicesForCache(devices);
+}
+
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h
new file mode 100644
index 0000000000..95b8317be1
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingExecutor.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISDABRENDERINGEXECUTOR_H
+#define KISDABRENDERINGEXECUTOR_H
+
+#include "kritadefaultpaintops_export.h"
+
+#include <QScopedPointer>
+
+#include <QList>
+class KisRenderedDab;
+
+#include "KisDabCacheUtils.h"
+
+class KisPressureMirrorOption;
+class KisPrecisionOption;
+class KisRunnableStrokeJobsInterface;
+
+
+class KRITADEFAULTPAINTOPS_EXPORT KisDabRenderingExecutor
+{
+public:
+ KisDabRenderingExecutor(const KoColorSpace *cs,
+ KisDabCacheUtils::ResourcesFactory resourcesFactory,
+ KisRunnableStrokeJobsInterface *runnableJobsInterface,
+ KisPressureMirrorOption *mirrorOption = 0,
+ KisPrecisionOption *precisionOption = 0);
+ ~KisDabRenderingExecutor();
+
+ void addDab(const KisDabCacheUtils::DabRequestInfo &request,
+ qreal opacity, qreal flow);
+
+ QList<KisRenderedDab> takeReadyDabs(bool returnMutableDabs = false);
+
+ bool hasPreparedDabs() const;
+
+ int averageDabRenderingTime() const; // usecs
+ int averageDabSize() const;
+
+ void recyclePaintDevicesForCache(const QVector<KisFixedPaintDeviceSP> devices);
+
+private:
+ KisDabRenderingExecutor(const KisDabRenderingExecutor &rhs) = delete;
+
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KISDABRENDERINGEXECUTOR_H
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.cpp b/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.cpp
new file mode 100644
index 0000000000..586dbdee2f
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisDabRenderingJob.h"
+
+#include <QElapsedTimer>
+
+#include <KisRunnableStrokeJobsInterface.h>
+#include <KisRunnableStrokeJobData.h>
+
+#include "KisDabCacheUtils.h"
+#include "KisDabRenderingQueue.h"
+
+#include <tool/strokes/FreehandStrokeRunnableJobDataWithUpdate.h>
+
+
+KisDabRenderingJob::KisDabRenderingJob()
+{
+}
+
+KisDabRenderingJob::KisDabRenderingJob(int _seqNo, KisDabCacheUtils::DabGenerationInfo _generationInfo, KisDabRenderingJob::JobType _type)
+ : seqNo(_seqNo),
+ generationInfo(_generationInfo),
+ type(_type)
+{
+}
+
+KisDabRenderingJob::KisDabRenderingJob(const KisDabRenderingJob &rhs)
+ : seqNo(rhs.seqNo),
+ generationInfo(rhs.generationInfo),
+ type(rhs.type),
+ originalDevice(rhs.originalDevice),
+ postprocessedDevice(rhs.postprocessedDevice),
+ status(rhs.status),
+ opacity(rhs.opacity),
+ flow(rhs.flow)
+{
+}
+
+KisDabRenderingJob &KisDabRenderingJob::operator=(const KisDabRenderingJob &rhs)
+{
+ seqNo = rhs.seqNo;
+ generationInfo = rhs.generationInfo;
+ type = rhs.type;
+ originalDevice = rhs.originalDevice;
+ postprocessedDevice = rhs.postprocessedDevice;
+ status = rhs.status;
+ opacity = rhs.opacity;
+ flow = rhs.flow;
+
+ return *this;
+}
+
+QPoint KisDabRenderingJob::dstDabOffset() const
+{
+ return generationInfo.dstDabRect.topLeft();
+}
+
+
+
+KisDabRenderingJobRunner::KisDabRenderingJobRunner(KisDabRenderingJobSP job,
+ KisDabRenderingQueue *parentQueue,
+ KisRunnableStrokeJobsInterface *runnableJobsInterface)
+ : m_job(job),
+ m_parentQueue(parentQueue),
+ m_runnableJobsInterface(runnableJobsInterface)
+{
+}
+
+KisDabRenderingJobRunner::~KisDabRenderingJobRunner()
+{
+}
+
+int KisDabRenderingJobRunner::executeOneJob(KisDabRenderingJob *job,
+ KisDabCacheUtils::DabRenderingResources *resources,
+ KisDabRenderingQueue *parentQueue)
+{
+ using namespace KisDabCacheUtils;
+
+ KIS_SAFE_ASSERT_RECOVER_NOOP(job->type == KisDabRenderingJob::Dab ||
+ job->type == KisDabRenderingJob::Postprocess);
+
+ QElapsedTimer executionTime;
+ executionTime.start();
+
+ resources->syncResourcesToSeqNo(job->seqNo, job->generationInfo.info);
+
+ if (job->type == KisDabRenderingJob::Dab) {
+ // TODO: thing about better interface for the reverse queue link
+ job->originalDevice = parentQueue->fetchCachedPaintDevce();
+
+ generateDab(job->generationInfo, resources, &job->originalDevice);
+ }
+
+ // by now the original device should be already prepared
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(job->originalDevice, 0);
+
+ if (job->type == KisDabRenderingJob::Dab ||
+ job->type == KisDabRenderingJob::Postprocess) {
+
+ if (job->generationInfo.needsPostprocessing) {
+ // TODO: cache postprocessed device
+
+ if (!job->postprocessedDevice ||
+ *job->originalDevice->colorSpace() != *job->postprocessedDevice->colorSpace()) {
+
+ job->postprocessedDevice = parentQueue->fetchCachedPaintDevce();
+ *job->postprocessedDevice = *job->originalDevice;
+ } else {
+ *job->postprocessedDevice = *job->originalDevice;
+ }
+
+ postProcessDab(job->postprocessedDevice,
+ job->generationInfo.dstDabRect.topLeft(),
+ job->generationInfo.info,
+ resources);
+ } else {
+ job->postprocessedDevice = job->originalDevice;
+ }
+ }
+
+ return executionTime.nsecsElapsed() / 1000;
+}
+
+void KisDabRenderingJobRunner::run()
+{
+ int executionTime = 0;
+
+ KisDabCacheUtils::DabRenderingResources *resources = m_parentQueue->fetchResourcesFromCache();
+
+ executionTime = executeOneJob(m_job.data(), resources, m_parentQueue);
+ QList<KisDabRenderingJobSP> jobs = m_parentQueue->notifyJobFinished(m_job->seqNo, executionTime);
+
+ while (!jobs.isEmpty()) {
+ QVector<KisRunnableStrokeJobData*> dataList;
+
+ // start all-but-the-first jobs asynchronously
+ for (int i = 1; i < jobs.size(); i++) {
+ dataList.append(new FreehandStrokeRunnableJobDataWithUpdate(
+ new KisDabRenderingJobRunner(jobs[i], m_parentQueue, m_runnableJobsInterface),
+ KisStrokeJobData::CONCURRENT));
+ }
+
+ m_runnableJobsInterface->addRunnableJobs(dataList);
+
+
+ // execute the first job in the current thread
+ KisDabRenderingJobSP job = jobs.first();
+ executionTime = executeOneJob(job.data(), resources, m_parentQueue);
+ jobs = m_parentQueue->notifyJobFinished(job->seqNo, executionTime);
+ }
+
+ m_parentQueue->putResourcesToCache(resources);
+}
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.h b/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.h
new file mode 100644
index 0000000000..83955a8c3a
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingJob.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISDABRENDERINGJOB_H
+#define KISDABRENDERINGJOB_H
+
+#include <QRunnable>
+#include <KisDabCacheUtils.h>
+#include <kis_fixed_paint_device.h>
+#include <kis_types.h>
+#include "kritadefaultpaintops_export.h"
+
+class KisDabRenderingQueue;
+class KisRunnableStrokeJobsInterface;
+
+class KRITADEFAULTPAINTOPS_EXPORT KisDabRenderingJob
+{
+public:
+ enum JobType {
+ Dab,
+ Postprocess,
+ Copy
+ };
+
+ enum Status {
+ New,
+ Running,
+ Completed
+ };
+
+public:
+ KisDabRenderingJob();
+ KisDabRenderingJob(int _seqNo,
+ KisDabCacheUtils::DabGenerationInfo _generationInfo,
+ JobType _type);
+ KisDabRenderingJob(const KisDabRenderingJob &rhs);
+ KisDabRenderingJob& operator=(const KisDabRenderingJob &rhs);
+
+ QPoint dstDabOffset() const;
+
+ int seqNo = -1;
+ KisDabCacheUtils::DabGenerationInfo generationInfo;
+ JobType type = Dab;
+ KisFixedPaintDeviceSP originalDevice;
+ KisFixedPaintDeviceSP postprocessedDevice;
+
+ // high-level members, not directly related to job execution itself
+ Status status = New;
+
+ qreal opacity = OPACITY_OPAQUE_F;
+ qreal flow = OPACITY_OPAQUE_F;
+};
+
+#include <QSharedPointer>
+typedef QSharedPointer<KisDabRenderingJob> KisDabRenderingJobSP;
+
+class KRITADEFAULTPAINTOPS_EXPORT KisDabRenderingJobRunner : public QRunnable
+{
+public:
+ KisDabRenderingJobRunner(KisDabRenderingJobSP job,
+ KisDabRenderingQueue *parentQueue,
+ KisRunnableStrokeJobsInterface *runnableJobsInterface);
+ ~KisDabRenderingJobRunner();
+
+ void run() override;
+
+ static int executeOneJob(KisDabRenderingJob *job, KisDabCacheUtils::DabRenderingResources *resources, KisDabRenderingQueue *parentQueue);
+
+private:
+ KisDabRenderingJobSP m_job;
+ KisDabRenderingQueue *m_parentQueue = 0;
+ KisRunnableStrokeJobsInterface *m_runnableJobsInterface = 0;
+};
+
+
+#endif // KISDABRENDERINGJOB_H
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp
new file mode 100644
index 0000000000..26d1d01383
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.cpp
@@ -0,0 +1,459 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisDabRenderingQueue.h"
+
+#include "KisDabRenderingJob.h"
+#include "KisRenderedDab.h"
+#include "kis_painter.h"
+
+#include <QSet>
+#include <QMutex>
+#include <QMutexLocker>
+#include <KisRollingMeanAccumulatorWrapper.h>
+
+#include "kis_algebra_2d.h"
+
+struct KisDabRenderingQueue::Private
+{
+ struct DumbCacheInterface : public CacheInterface {
+ void getDabType(bool hasDabInCache,
+ KisDabCacheUtils::DabRenderingResources *resources,
+ const KisDabCacheUtils::DabRequestInfo &request,
+ /* out */
+ KisDabCacheUtils::DabGenerationInfo *di,
+ bool *shouldUseCache) override
+ {
+ Q_UNUSED(hasDabInCache);
+ Q_UNUSED(resources);
+ Q_UNUSED(request);
+
+ di->needsPostprocessing = false;
+ *shouldUseCache = false;
+ }
+
+ bool hasSeparateOriginal(KisDabCacheUtils::DabRenderingResources *resources) const override
+ {
+ Q_UNUSED(resources);
+ return false;
+ }
+
+ };
+
+ Private(const KoColorSpace *_colorSpace,
+ KisDabCacheUtils::ResourcesFactory _resourcesFactory)
+ : cacheInterface(new DumbCacheInterface),
+ colorSpace(_colorSpace),
+ resourcesFactory(_resourcesFactory),
+ avgExecutionTime(50),
+ avgDabSize(50)
+ {
+ KIS_SAFE_ASSERT_RECOVER_NOOP(resourcesFactory);
+ }
+
+ ~Private() {
+ qDeleteAll(cachedResources);
+ cachedResources.clear();
+ }
+
+ QList<KisDabRenderingJobSP> jobs;
+ int nextSeqNoToUse = 0;
+ int lastPaintedJob = -1;
+ int lastDabJobInQueue = -1;
+ QScopedPointer<CacheInterface> cacheInterface;
+ const KoColorSpace *colorSpace;
+ qreal averageOpacity = 0.0;
+
+ KisDabCacheUtils::ResourcesFactory resourcesFactory;
+
+ QList<KisDabCacheUtils::DabRenderingResources*> cachedResources;
+ QSet<KisFixedPaintDeviceSP> cachedPaintDevices;
+
+ QMutex mutex;
+
+ KisRollingMeanAccumulatorWrapper avgExecutionTime;
+ KisRollingMeanAccumulatorWrapper avgDabSize;
+
+ int calculateLastDabJobIndex(int startSearchIndex);
+ void cleanPaintedDabs();
+ bool dabsHaveSeparateOriginal();
+
+ KisDabCacheUtils::DabRenderingResources* fetchResourcesFromCache();
+ void putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources);
+};
+
+
+KisDabRenderingQueue::KisDabRenderingQueue(const KoColorSpace *cs,
+ KisDabCacheUtils::ResourcesFactory resourcesFactory)
+ : m_d(new Private(cs, resourcesFactory))
+{
+}
+
+KisDabRenderingQueue::~KisDabRenderingQueue()
+{
+}
+
+int KisDabRenderingQueue::Private::calculateLastDabJobIndex(int startSearchIndex)
+{
+ if (startSearchIndex < 0) {
+ startSearchIndex = jobs.size() - 1;
+ }
+
+ // try to use cached value
+ if (startSearchIndex >= lastDabJobInQueue) {
+ return lastDabJobInQueue;
+ }
+
+ // if we are below the cached value, just iterate through the dabs
+ // (which is extremely(!) slow)
+ for (int i = startSearchIndex; i >= 0; i--) {
+ if (jobs[i]->type == KisDabRenderingJob::Dab) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+KisDabRenderingJobSP KisDabRenderingQueue::addDab(const KisDabCacheUtils::DabRequestInfo &request,
+ qreal opacity, qreal flow)
+{
+ QMutexLocker l(&m_d->mutex);
+
+ const int seqNo = m_d->nextSeqNoToUse++;
+
+ KisDabCacheUtils::DabRenderingResources *resources = m_d->fetchResourcesFromCache();
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(resources, KisDabRenderingJobSP());
+
+ // We should sync the cached brush into the current seqNo
+ resources->syncResourcesToSeqNo(seqNo, request.info);
+
+ const int lastDabJobIndex = m_d->lastDabJobInQueue;
+
+ KisDabRenderingJobSP job(new KisDabRenderingJob());
+
+ bool shouldUseCache = false;
+ m_d->cacheInterface->getDabType(lastDabJobIndex >= 0,
+ resources,
+ request,
+ &job->generationInfo,
+ &shouldUseCache);
+
+ m_d->putResourcesToCache(resources);
+ resources = 0;
+
+ // TODO: initialize via c-tor
+ job->seqNo = seqNo;
+ job->type =
+ !shouldUseCache ? KisDabRenderingJob::Dab :
+ job->generationInfo.needsPostprocessing ? KisDabRenderingJob::Postprocess :
+ KisDabRenderingJob::Copy;
+ job->opacity = opacity;
+ job->flow = flow;
+
+
+ if (job->type == KisDabRenderingJob::Dab) {
+ job->status = KisDabRenderingJob::Running;
+ } else if (job->type == KisDabRenderingJob::Postprocess ||
+ job->type == KisDabRenderingJob::Copy) {
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastDabJobIndex >= 0, KisDabRenderingJobSP());
+
+ if (m_d->jobs[lastDabJobIndex]->status == KisDabRenderingJob::Completed) {
+ if (job->type == KisDabRenderingJob::Postprocess) {
+ job->status = KisDabRenderingJob::Running;
+ job->originalDevice = m_d->jobs[lastDabJobIndex]->originalDevice;
+ } else if (job->type == KisDabRenderingJob::Copy) {
+ job->status = KisDabRenderingJob::Completed;
+ job->originalDevice = m_d->jobs[lastDabJobIndex]->originalDevice;
+ job->postprocessedDevice = m_d->jobs[lastDabJobIndex]->postprocessedDevice;
+ }
+ }
+ }
+
+ m_d->jobs.append(job);
+
+ KisDabRenderingJobSP jobToRun;
+ if (job->status == KisDabRenderingJob::Running) {
+ jobToRun = job;
+ }
+
+ if (job->type == KisDabRenderingJob::Dab) {
+ m_d->lastDabJobInQueue = m_d->jobs.size() - 1;
+ m_d->cleanPaintedDabs();
+ }
+
+ // collect some statistics about the dab
+ m_d->avgDabSize(KisAlgebra2D::maxDimension(job->generationInfo.dstDabRect));
+
+ return jobToRun;
+}
+
+QList<KisDabRenderingJobSP> KisDabRenderingQueue::notifyJobFinished(int seqNo, int usecsTime)
+{
+ QMutexLocker l(&m_d->mutex);
+
+ QList<KisDabRenderingJobSP> dependentJobs;
+
+ /**
+ * Here we use binary search for locating the necessary original dab
+ */
+ auto finishedJobIt =
+ std::lower_bound(m_d->jobs.begin(), m_d->jobs.end(), seqNo,
+ [] (KisDabRenderingJobSP job, int seqNo) {
+ return job->seqNo < seqNo;
+ });
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(finishedJobIt != m_d->jobs.end(), dependentJobs);
+ KisDabRenderingJobSP finishedJob = *finishedJobIt;
+
+ KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->status == KisDabRenderingJob::Running);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->seqNo == seqNo);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->originalDevice);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(finishedJob->postprocessedDevice);
+
+ finishedJob->status = KisDabRenderingJob::Completed;
+
+ if (finishedJob->type == KisDabRenderingJob::Dab) {
+ for (auto it = finishedJobIt + 1; it != m_d->jobs.end(); ++it) {
+ KisDabRenderingJobSP j = *it;
+
+ // next dab job closes the chain
+ if (j->type == KisDabRenderingJob::Dab) break;
+
+ // the non 'dab'-type job couldn't have
+ // been started before the source ob was completed
+ KIS_SAFE_ASSERT_RECOVER_BREAK(j->status == KisDabRenderingJob::New);
+
+ if (j->type == KisDabRenderingJob::Copy) {
+
+ j->originalDevice = finishedJob->originalDevice;
+ j->postprocessedDevice = finishedJob->postprocessedDevice;
+ j->status = KisDabRenderingJob::Completed;
+
+ } else if (j->type == KisDabRenderingJob::Postprocess) {
+
+ j->originalDevice = finishedJob->originalDevice;
+ j->status = KisDabRenderingJob::Running;
+ dependentJobs << j;
+ }
+ }
+ }
+
+ if (usecsTime >= 0) {
+ m_d->avgExecutionTime(usecsTime);
+ }
+
+ return dependentJobs;
+}
+
+void KisDabRenderingQueue::Private::cleanPaintedDabs()
+{
+ const int nextToBePainted = lastPaintedJob + 1;
+ const int lastSourceJob = calculateLastDabJobIndex(qMin(nextToBePainted, jobs.size() - 1));
+
+ if (lastPaintedJob >= 0) {
+ int numRemovedJobs = 0;
+ int numRemovedJobsBeforeLastSource = 0;
+
+ auto it = jobs.begin();
+ for (int i = 0; i <= lastPaintedJob; i++) {
+ KisDabRenderingJobSP job = *it;
+
+ if (i < lastSourceJob || job->type != KisDabRenderingJob::Dab){
+
+ // cache unique 'original' devices
+ if (job->type == KisDabRenderingJob::Dab &&
+ job->postprocessedDevice != job->originalDevice) {
+ cachedPaintDevices << job->originalDevice;
+ job->originalDevice = 0;
+ }
+
+ it = jobs.erase(it);
+ numRemovedJobs++;
+ if (i < lastSourceJob) {
+ numRemovedJobsBeforeLastSource++;
+ }
+
+ } else {
+ ++it;
+ }
+ }
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN(jobs.size() > 0);
+
+ lastPaintedJob -= numRemovedJobs;
+ lastDabJobInQueue -= numRemovedJobsBeforeLastSource;
+ }
+}
+
+QList<KisRenderedDab> KisDabRenderingQueue::takeReadyDabs(bool returnMutableDabs)
+{
+ QMutexLocker l(&m_d->mutex);
+
+ QList<KisRenderedDab> renderedDabs;
+ if (m_d->jobs.isEmpty()) return renderedDabs;
+
+ KIS_SAFE_ASSERT_RECOVER_NOOP(
+ m_d->jobs.isEmpty() ||
+ m_d->jobs.first()->type == KisDabRenderingJob::Dab);
+
+ const int copyJobAfterInclusive =
+ returnMutableDabs && !m_d->dabsHaveSeparateOriginal() ?
+ m_d->lastDabJobInQueue :
+ std::numeric_limits<int>::max();
+
+ for (int i = 0; i < m_d->jobs.size(); i++) {
+ KisDabRenderingJobSP j = m_d->jobs[i];
+
+ if (j->status != KisDabRenderingJob::Completed) break;
+
+ if (i <= m_d->lastPaintedJob) continue;
+
+ KisRenderedDab dab;
+ KisFixedPaintDeviceSP resultDevice = j->postprocessedDevice;
+
+ if (i >= copyJobAfterInclusive) {
+ resultDevice = new KisFixedPaintDevice(*resultDevice);
+ }
+
+ dab.device = resultDevice;
+ dab.offset = j->dstDabOffset();
+ dab.opacity = j->opacity;
+ dab.flow = j->flow;
+
+ m_d->averageOpacity = KisPainter::blendAverageOpacity(j->opacity, m_d->averageOpacity);
+ dab.averageOpacity = m_d->averageOpacity;
+
+
+ renderedDabs.append(dab);
+
+ m_d->lastPaintedJob = i;
+ }
+
+ m_d->cleanPaintedDabs();
+ return renderedDabs;
+}
+
+bool KisDabRenderingQueue::hasPreparedDabs() const
+{
+ QMutexLocker l(&m_d->mutex);
+
+ const int nextToBePainted = m_d->lastPaintedJob + 1;
+
+ return
+ nextToBePainted >= 0 &&
+ nextToBePainted < m_d->jobs.size() &&
+ m_d->jobs[nextToBePainted]->status == KisDabRenderingJob::Completed;
+}
+
+void KisDabRenderingQueue::setCacheInterface(KisDabRenderingQueue::CacheInterface *interface)
+{
+ m_d->cacheInterface.reset(interface);
+}
+
+KisFixedPaintDeviceSP KisDabRenderingQueue::fetchCachedPaintDevce()
+{
+ QMutexLocker l(&m_d->mutex);
+
+ KisFixedPaintDeviceSP result;
+
+ if (m_d->cachedPaintDevices.isEmpty()) {
+ result = new KisFixedPaintDevice(m_d->colorSpace);
+ } else {
+ // there is no difference from which side to take elements from QSet
+ auto it = m_d->cachedPaintDevices.begin();
+ result = *it;
+ m_d->cachedPaintDevices.erase(it);
+ }
+
+ return result;
+}
+
+void KisDabRenderingQueue::recyclePaintDevicesForCache(const QVector<KisFixedPaintDeviceSP> devices)
+{
+ QMutexLocker l(&m_d->mutex);
+
+ Q_FOREACH (KisFixedPaintDeviceSP device, devices) {
+ // the set automatically checks if the device is unique in the set
+ m_d->cachedPaintDevices << device;
+ }
+}
+
+int KisDabRenderingQueue::averageExecutionTime() const
+{
+ QMutexLocker l(&m_d->mutex);
+ return qRound(m_d->avgExecutionTime.rollingMean());
+}
+
+int KisDabRenderingQueue::averageDabSize() const
+{
+ QMutexLocker l(&m_d->mutex);
+ return qRound(m_d->avgDabSize.rollingMean());
+}
+
+bool KisDabRenderingQueue::Private::dabsHaveSeparateOriginal()
+{
+ KisDabCacheUtils::DabRenderingResources *resources = fetchResourcesFromCache();
+
+ const bool result = cacheInterface->hasSeparateOriginal(resources);
+
+ putResourcesToCache(resources);
+
+ return result;
+}
+
+KisDabCacheUtils::DabRenderingResources *KisDabRenderingQueue::Private::fetchResourcesFromCache()
+{
+ KisDabCacheUtils::DabRenderingResources *resources = 0;
+
+ // fetch/create a temporary resources object
+ if (!cachedResources.isEmpty()) {
+ resources = cachedResources.takeLast();
+ } else {
+ resources = resourcesFactory();
+ }
+
+ return resources;
+}
+
+void KisDabRenderingQueue::Private::putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources)
+{
+ cachedResources << resources;
+}
+
+KisDabCacheUtils::DabRenderingResources *KisDabRenderingQueue::fetchResourcesFromCache()
+{
+ // TODO: make a separate lock for that
+ QMutexLocker l(&m_d->mutex);
+ return m_d->fetchResourcesFromCache();
+}
+
+void KisDabRenderingQueue::putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources)
+{
+ QMutexLocker l(&m_d->mutex);
+ m_d->putResourcesToCache(resources);
+}
+
+int KisDabRenderingQueue::testingGetQueueSize() const
+{
+ QMutexLocker l(&m_d->mutex);
+
+ return m_d->jobs.size();
+}
+
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h
new file mode 100644
index 0000000000..a9933396d1
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueue.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISDABRENDERINGQUEUE_H
+#define KISDABRENDERINGQUEUE_H
+
+#include <QScopedPointer>
+
+#include "kritadefaultpaintops_export.h"
+
+#include <QList>
+class KisDabRenderingJob;
+class KisRenderedDab;
+
+#include "KisDabCacheUtils.h"
+
+class KRITADEFAULTPAINTOPS_EXPORT KisDabRenderingQueue
+{
+public:
+ struct CacheInterface {
+ virtual ~CacheInterface() {}
+ virtual void getDabType(bool hasDabInCache,
+ KisDabCacheUtils::DabRenderingResources *resources,
+ const KisDabCacheUtils::DabRequestInfo &request,
+ /* out */
+ KisDabCacheUtils::DabGenerationInfo *di,
+ bool *shouldUseCache) = 0;
+
+ virtual bool hasSeparateOriginal(KisDabCacheUtils::DabRenderingResources *resources) const = 0;
+ };
+
+
+public:
+ KisDabRenderingQueue(const KoColorSpace *cs, KisDabCacheUtils::ResourcesFactory resourcesFactory);
+ ~KisDabRenderingQueue();
+
+ KisDabRenderingJobSP addDab(const KisDabCacheUtils::DabRequestInfo &request,
+ qreal opacity, qreal flow);
+
+ QList<KisDabRenderingJobSP> notifyJobFinished(int seqNo, int usecsTime = -1);
+
+ QList<KisRenderedDab> takeReadyDabs(bool returnMutableDabs = false);
+
+ bool hasPreparedDabs() const;
+
+ void setCacheInterface(CacheInterface *interface);
+
+ KisFixedPaintDeviceSP fetchCachedPaintDevce();
+ void recyclePaintDevicesForCache(const QVector<KisFixedPaintDeviceSP> devices);
+
+ void putResourcesToCache(KisDabCacheUtils::DabRenderingResources *resources);
+ KisDabCacheUtils::DabRenderingResources* fetchResourcesFromCache();
+
+ int averageExecutionTime() const;
+ int averageDabSize() const;
+
+ int testingGetQueueSize() const;
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KISDABRENDERINGQUEUE_H
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueueCache.cpp b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueueCache.cpp
new file mode 100644
index 0000000000..d000938171
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueueCache.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisDabRenderingQueueCache.h"
+
+struct KisDabRenderingQueueCache::Private
+{
+ Private()
+ {
+ }
+};
+
+KisDabRenderingQueueCache::KisDabRenderingQueueCache()
+ : m_d(new Private())
+{
+}
+
+KisDabRenderingQueueCache::~KisDabRenderingQueueCache()
+{
+}
+
+void KisDabRenderingQueueCache::getDabType(bool hasDabInCache, KisDabCacheUtils::DabRenderingResources *resources, const KisDabCacheUtils::DabRequestInfo &request, KisDabCacheUtils::DabGenerationInfo *di, bool *shouldUseCache)
+{
+ fetchDabGenerationInfo(hasDabInCache, resources, request, di, shouldUseCache);
+}
+
+bool KisDabRenderingQueueCache::hasSeparateOriginal(KisDabCacheUtils::DabRenderingResources *resources) const
+{
+ return needSeparateOriginal(resources->textureOption.data(), resources->sharpnessOption.data());
+}
diff --git a/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueueCache.h b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueueCache.h
new file mode 100644
index 0000000000..151a0eb096
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/KisDabRenderingQueueCache.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISDABRENDERINGQUEUECACHE_H
+#define KISDABRENDERINGQUEUECACHE_H
+
+#include "KisDabRenderingQueue.h"
+#include "kis_dab_cache_base.h"
+
+#include "kritadefaultpaintops_export.h"
+
+class KisPressureMirrorOption;
+class KisPrecisionOption;
+class KisPressureSharpnessOption;
+
+class KRITADEFAULTPAINTOPS_EXPORT KisDabRenderingQueueCache : public KisDabRenderingQueue::CacheInterface, public KisDabCacheBase
+{
+public:
+
+public:
+ KisDabRenderingQueueCache();
+ ~KisDabRenderingQueueCache();
+
+ void getDabType(bool hasDabInCache,
+ KisDabCacheUtils::DabRenderingResources *resources,
+ const KisDabCacheUtils::DabRequestInfo &request,
+ /* out */
+ KisDabCacheUtils::DabGenerationInfo *di,
+ bool *shouldUseCache) override;
+
+ bool hasSeparateOriginal(KisDabCacheUtils::DabRenderingResources *resources) const override;
+
+private:
+ struct Private;
+ QScopedPointer<Private> m_d;
+};
+
+#endif // KISDABRENDERINGQUEUECACHE_H
diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
index dbf10dac83..0e400706e2 100644
--- a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
+++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
@@ -1,209 +1,353 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004-2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Clarence Dang <dang@kde.org>
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_brushop.h"
#include <QRect>
#include <kis_image.h>
#include <kis_vec.h>
#include <kis_debug.h>
#include <KoColorTransformation.h>
#include <KoColor.h>
#include <kis_brush.h>
#include <kis_global.h>
#include <kis_paint_device.h>
#include <kis_painter.h>
#include <kis_brush_based_paintop_settings.h>
-#include <kis_color_source.h>
-#include <kis_pressure_sharpness_option.h>
-#include <kis_fixed_paint_device.h>
#include <kis_lod_transform.h>
#include <kis_paintop_plugin_utils.h>
+#include "krita_utils.h"
+#include <QtConcurrent>
+#include "kis_algebra_2d.h"
+#include <KisDabRenderingExecutor.h>
+#include <KisDabCacheUtils.h>
+#include <KisRenderedDab.h>
+#include "KisBrushOpResources.h"
+#include <KisRunnableStrokeJobData.h>
+#include <KisRunnableStrokeJobsInterface.h>
+
+#include <QSharedPointer>
+#include <QThread>
+#include "kis_image_config.h"
KisBrushOp::KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image)
: KisBrushBasedPaintOp(settings, painter)
, m_opacityOption(node)
- , m_hsvTransformation(0)
+ , m_avgSpacing(50)
+ , m_avgNumDabs(50)
+ , m_idealNumRects(KisImageConfig().maxNumberOfThreads())
{
Q_UNUSED(image);
Q_ASSERT(settings);
- KisColorSourceOption colorSourceOption;
- colorSourceOption.readOptionSetting(settings);
- m_colorSource = colorSourceOption.createColorSource(painter);
-
- m_hsvOptions.append(KisPressureHSVOption::createHueOption());
- m_hsvOptions.append(KisPressureHSVOption::createSaturationOption());
- m_hsvOptions.append(KisPressureHSVOption::createValueOption());
-
- Q_FOREACH (KisPressureHSVOption * option, m_hsvOptions) {
- option->readOptionSetting(settings);
- option->resetAllSensors();
- if (option->isChecked() && !m_hsvTransformation) {
- m_hsvTransformation = painter->backgroundColor().colorSpace()->createColorTransformation("hsv_adjustment", QHash<QString, QVariant>());
- }
- }
+ /**
+ * We do our own threading here, so we need to forbid the brushes
+ * to do threading internally
+ */
+ m_brush->setThreadingAllowed(false);
m_airbrushOption.readOptionSetting(settings);
m_opacityOption.readOptionSetting(settings);
m_flowOption.readOptionSetting(settings);
m_sizeOption.readOptionSetting(settings);
m_ratioOption.readOptionSetting(settings);
m_spacingOption.readOptionSetting(settings);
m_rateOption.readOptionSetting(settings);
m_softnessOption.readOptionSetting(settings);
- m_sharpnessOption.readOptionSetting(settings);
- m_darkenOption.readOptionSetting(settings);
m_rotationOption.readOptionSetting(settings);
- m_mixOption.readOptionSetting(settings);
m_scatterOption.readOptionSetting(settings);
+ m_sharpnessOption.readOptionSetting(settings);
m_opacityOption.resetAllSensors();
m_flowOption.resetAllSensors();
m_sizeOption.resetAllSensors();
m_ratioOption.resetAllSensors();
m_rateOption.resetAllSensors();
m_softnessOption.resetAllSensors();
m_sharpnessOption.resetAllSensors();
- m_darkenOption.resetAllSensors();
m_rotationOption.resetAllSensors();
m_scatterOption.resetAllSensors();
+ m_sharpnessOption.resetAllSensors();
- m_dabCache->setSharpnessPostprocessing(&m_sharpnessOption);
m_rotationOption.applyFanCornersInfo(this);
+
+ KisBrushSP baseBrush = m_brush;
+ auto resourcesFactory =
+ [baseBrush, settings, painter] () {
+ KisDabCacheUtils::DabRenderingResources *resources =
+ new KisBrushOpResources(settings, painter);
+ resources->brush = baseBrush->clone();
+
+ return resources;
+ };
+
+
+ m_dabExecutor.reset(
+ new KisDabRenderingExecutor(
+ painter->device()->compositionSourceColorSpace(),
+ resourcesFactory,
+ painter->runnableStrokeJobsInterface(),
+ &m_mirrorOption,
+ &m_precisionOption));
}
KisBrushOp::~KisBrushOp()
{
- qDeleteAll(m_hsvOptions);
- delete m_colorSource;
- delete m_hsvTransformation;
}
KisSpacingInformation KisBrushOp::paintAt(const KisPaintInformation& info)
{
if (!painter()->device()) return KisSpacingInformation(1.0);
KisBrushSP brush = m_brush;
Q_ASSERT(brush);
if (!brush)
return KisSpacingInformation(1.0);
if (!brush->canPaintFor(info))
return KisSpacingInformation(1.0);
qreal scale = m_sizeOption.apply(info);
scale *= KisLodTransform::lodToScale(painter()->device());
if (checkSizeTooSmall(scale)) return KisSpacingInformation();
qreal rotation = m_rotationOption.apply(info);
qreal ratio = m_ratioOption.apply(info);
- KisPaintDeviceSP device = painter()->device();
-
-
KisDabShape shape(scale, ratio, rotation);
QPointF cursorPos =
m_scatterOption.apply(info,
brush->maskWidth(shape, 0, 0, info),
brush->maskHeight(shape, 0, 0, info));
- quint8 origOpacity = painter()->opacity();
-
m_opacityOption.setFlow(m_flowOption.apply(info));
- m_opacityOption.apply(painter(), info);
- m_colorSource->selectColor(m_mixOption.apply(info), info);
- m_darkenOption.apply(m_colorSource, info);
- if (m_hsvTransformation) {
- Q_FOREACH (KisPressureHSVOption * option, m_hsvOptions) {
- option->apply(m_hsvTransformation, info);
- }
- m_colorSource->applyColorTransformation(m_hsvTransformation);
+ quint8 dabOpacity = OPACITY_OPAQUE_U8;
+ quint8 dabFlow = OPACITY_OPAQUE_U8;
+
+ m_opacityOption.apply(info, &dabOpacity, &dabFlow);
+
+ KisDabCacheUtils::DabRequestInfo request(painter()->paintColor(),
+ cursorPos,
+ shape,
+ info,
+ m_softnessOption.apply(info));
+
+ m_dabExecutor->addDab(request, qreal(dabOpacity) / 255.0, qreal(dabFlow) / 255.0);
+
+
+ KisSpacingInformation spacingInfo =
+ effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info);
+
+ // gather statistics about dabs
+ m_avgSpacing(spacingInfo.scalarApprox());
+
+ return spacingInfo;
+}
+
+struct KisBrushOp::UpdateSharedState
+{
+ // rendering data
+ KisPainter *painter = 0;
+ QList<KisRenderedDab> dabsQueue;
+
+ // speed metrics
+ QVector<QPointF> dabPoints;
+ QElapsedTimer dabRenderingTimer;
+
+ // final report
+ QVector<QRect> allDirtyRects;
+};
+
+void KisBrushOp::addMirroringJobs(Qt::Orientation direction,
+ QVector<QRect> &rects,
+ UpdateSharedStateSP state,
+ QVector<KisRunnableStrokeJobData*> &jobs)
+{
+ jobs.append(new KisRunnableStrokeJobData(0, KisStrokeJobData::SEQUENTIAL));
+
+ for (KisRenderedDab &dab : state->dabsQueue) {
+ jobs.append(
+ new KisRunnableStrokeJobData(
+ [state, &dab, direction] () {
+ state->painter->mirrorDab(direction, &dab);
+ },
+ KisStrokeJobData::CONCURRENT));
}
- QRect dabRect;
- KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(device->compositionSourceColorSpace(),
- m_colorSource,
- cursorPos,
- shape,
- info,
- m_softnessOption.apply(info),
- &dabRect);
-
- // sanity check for the size calculation code
- if (dab->bounds().size() != dabRect.size()) {
- warnKrita << "KisBrushOp: dab bounds is not dab rect. See bug 327156" << dab->bounds().size() << dabRect.size();
+ jobs.append(new KisRunnableStrokeJobData(0, KisStrokeJobData::SEQUENTIAL));
+
+ for (QRect &rc : rects) {
+ state->painter->mirrorRect(direction, &rc);
+
+ jobs.append(
+ new KisRunnableStrokeJobData(
+ [rc, state] () {
+ state->painter->bltFixed(rc, state->dabsQueue);
+ },
+ KisStrokeJobData::CONCURRENT));
}
- painter()->bltFixed(dabRect.topLeft(), dab, dab->bounds());
+ state->allDirtyRects.append(rects);
+}
- painter()->renderMirrorMaskSafe(dabRect,
- dab,
- !m_dabCache->needSeparateOriginal());
- painter()->setOpacity(origOpacity);
+int KisBrushOp::doAsyncronousUpdate(QVector<KisRunnableStrokeJobData*> &jobs)
+{
+ if (!m_updateSharedState && m_dabExecutor->hasPreparedDabs()) {
- return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info);
+ m_updateSharedState = toQShared(new UpdateSharedState());
+ UpdateSharedStateSP state = m_updateSharedState;
+
+ state->painter = painter();
+
+ state->dabsQueue = m_dabExecutor->takeReadyDabs(painter()->hasMirroring());
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!state->dabsQueue.isEmpty(),
+ m_currentUpdatePeriod);
+
+ const int diameter = m_dabExecutor->averageDabSize();
+ const qreal spacing = m_avgSpacing.rollingMean();
+
+ const int idealNumRects = m_idealNumRects;
+ QVector<QRect> rects =
+ KisPaintOpUtils::splitDabsIntoRects(state->dabsQueue,
+ idealNumRects, diameter, spacing);
+
+ state->allDirtyRects = rects;
+
+ Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) {
+ state->dabPoints.append(dab.realBounds().center());
+ }
+
+ state->dabRenderingTimer.start();
+
+ Q_FOREACH (const QRect &rc, rects) {
+ jobs.append(
+ new KisRunnableStrokeJobData(
+ [rc, state] () {
+ state->painter->bltFixed(rc, state->dabsQueue);
+ },
+ KisStrokeJobData::CONCURRENT));
+ }
+
+ /**
+ * After the dab has been rendered once, we should mirror it either one
+ * (h __or__ v) or three (h __and__ v) times. This sequence of 'if's achives
+ * the goal without any extra copying. Please note that it has __no__ 'else'
+ * branches, which is done intentionally!
+ */
+ if (state->painter->hasHorizontalMirroring()) {
+ addMirroringJobs(Qt::Horizontal, rects, state, jobs);
+ }
+
+ if (state->painter->hasVerticalMirroring()) {
+ addMirroringJobs(Qt::Vertical, rects, state, jobs);
+ }
+
+ if (state->painter->hasHorizontalMirroring() && state->painter->hasVerticalMirroring()) {
+ addMirroringJobs(Qt::Horizontal, rects, state, jobs);
+ }
+
+ jobs.append(
+ new KisRunnableStrokeJobData(
+ [state, this] () {
+ Q_FOREACH(const QRect &rc, state->allDirtyRects) {
+ state->painter->addDirtyRect(rc);
+ }
+
+ state->painter->setAverageOpacity(state->dabsQueue.last().averageOpacity);
+
+ const int updateRenderingTime = state->dabRenderingTimer.elapsed();
+ const int dabRenderingTime = m_dabExecutor->averageDabRenderingTime() / 1000;
+ m_avgNumDabs(state->dabsQueue.size());
+
+ QVector<KisFixedPaintDeviceSP> recycledDevices;
+ for (auto it = state->dabsQueue.begin(); it != state->dabsQueue.end(); ++it) {
+ // we don't need to check for uniqueness, it is done by the queue
+ recycledDevices << it->device;
+ it->device.clear();
+ }
+ m_dabExecutor->recyclePaintDevicesForCache(recycledDevices);
+
+
+
+ const int approxDabRenderingTime = qreal(dabRenderingTime) / m_idealNumRects * m_avgNumDabs.rollingMean();
+
+ m_currentUpdatePeriod = qBound(20, int(1.5 * (approxDabRenderingTime + updateRenderingTime)), 100);
+
+
+ { // debug chunk
+// const int updateRenderingTime = state->dabRenderingTimer.nsecsElapsed() / 1000;
+// const int dabRenderingTime = m_dabExecutor->averageDabRenderingTime();
+// ENTER_FUNCTION() << ppVar(state->allDirtyRects.size()) << ppVar(state->dabsQueue.size()) << ppVar(dabRenderingTime) << ppVar(updateRenderingTime);
+// ENTER_FUNCTION() << ppVar(m_currentUpdatePeriod);
+ }
+
+ m_updateSharedState.clear();
+ },
+ KisStrokeJobData::SEQUENTIAL));
+ }
+
+ return m_currentUpdatePeriod;
}
KisSpacingInformation KisBrushOp::updateSpacingImpl(const KisPaintInformation &info) const
{
const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device());
qreal rotation = m_rotationOption.apply(info);
return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info);
}
KisTimingInformation KisBrushOp::updateTimingImpl(const KisPaintInformation &info) const
{
return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info);
}
void KisBrushOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance)
{
if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) {
if (!m_lineCacheDevice) {
m_lineCacheDevice = source()->createCompositionSourceDevice();
}
else {
m_lineCacheDevice->clear();
}
KisPainter p(m_lineCacheDevice);
p.setPaintColor(painter()->paintColor());
p.drawDDALine(pi1.pos(), pi2.pos());
QRect rc = m_lineCacheDevice->extent();
painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height());
//fixes Bug 338011
painter()->renderMirrorMask(rc, m_lineCacheDevice);
}
else {
KisPaintOp::paintLine(pi1, pi2, currentDistance);
}
}
diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.h b/plugins/paintops/defaultpaintops/brush/kis_brushop.h
index 51e7b52fe0..390f84e4cc 100644
--- a/plugins/paintops/defaultpaintops/brush/kis_brushop.h
+++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.h
@@ -1,87 +1,103 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004-2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Clarence Dang <dang@kde.org>
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
*
* 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_BRUSHOP_H_
#define KIS_BRUSHOP_H_
#include "kis_brush_based_paintop.h"
#include <kis_airbrush_option.h>
-#include <kis_pressure_darken_option.h>
#include <kis_pressure_flow_opacity_option.h>
#include <kis_pressure_size_option.h>
#include <kis_pressure_ratio_option.h>
#include <kis_pressure_flow_option.h>
#include <kis_pressure_rotation_option.h>
-#include <kis_pressure_mix_option.h>
-#include <kis_pressure_hsv_option.h>
#include <kis_pressure_scatter_option.h>
#include <kis_pressure_softness_option.h>
#include <kis_pressure_sharpness_option.h>
-#include <kis_color_source_option.h>
#include <kis_pressure_spacing_option.h>
#include <kis_pressure_rate_option.h>
#include <kis_brush_based_paintop_settings.h>
+#include <KisRollingMeanAccumulatorWrapper.h>
+
+#include <QElapsedTimer>
+
class KisPainter;
class KisColorSource;
-
+class KisDabRenderingExecutor;
+class KisRenderedDab;
+class KisRunnableStrokeJobData;
class KisBrushOp : public KisBrushBasedPaintOp
{
public:
KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image);
~KisBrushOp() override;
void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override;
+ int doAsyncronousUpdate(QVector<KisRunnableStrokeJobData *> &jobs) override;
+
protected:
KisSpacingInformation paintAt(const KisPaintInformation& info) override;
KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override;
KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override;
+ struct UpdateSharedState;
+ typedef QSharedPointer<UpdateSharedState> UpdateSharedStateSP;
+
+ void addMirroringJobs(Qt::Orientation direction,
+ QVector<QRect> &rects,
+ UpdateSharedStateSP state,
+ QVector<KisRunnableStrokeJobData*> &jobs);
+
+ UpdateSharedStateSP m_updateSharedState;
+
+
private:
- KisColorSource *m_colorSource;
KisAirbrushOption m_airbrushOption;
KisPressureSizeOption m_sizeOption;
KisPressureRatioOption m_ratioOption;
KisPressureSpacingOption m_spacingOption;
KisPressureRateOption m_rateOption;
KisPressureFlowOption m_flowOption;
KisFlowOpacityOption m_opacityOption;
KisPressureSoftnessOption m_softnessOption;
KisPressureSharpnessOption m_sharpnessOption;
- KisPressureDarkenOption m_darkenOption;
KisPressureRotationOption m_rotationOption;
- KisPressureMixOption m_mixOption;
KisPressureScatterOption m_scatterOption;
- QList<KisPressureHSVOption*> m_hsvOptions;
- KoColorTransformation *m_hsvTransformation;
KisPaintDeviceSP m_lineCacheDevice;
- KisPaintDeviceSP m_colorSourceDevice;
+
+ QScopedPointer<KisDabRenderingExecutor> m_dabExecutor;
+ qreal m_currentUpdatePeriod = 20.0;
+ KisRollingMeanAccumulatorWrapper m_avgSpacing;
+ KisRollingMeanAccumulatorWrapper m_avgNumDabs;
+
+ const int m_idealNumRects;
};
#endif // KIS_BRUSHOP_H_
diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
index bbe667d467..e71b11a68c 100644
--- a/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
+++ b/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp
@@ -1,99 +1,99 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004-2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Clarence Dang <dang@kde.org>
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
*
* 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_brushop_settings_widget.h"
-#include <kis_brush_based_paintop_settings.h>
+#include <KisBrushOpSettings.h>
#include <kis_pressure_darken_option.h>
#include <kis_pressure_opacity_option.h>
#include <kis_pressure_flow_option.h>
#include <kis_pressure_size_option.h>
#include <kis_pressure_ratio_option.h>
#include <kis_paint_action_type_option.h>
#include <kis_pressure_rotation_option.h>
#include <kis_pressure_mix_option.h>
#include <kis_curve_option_widget.h>
#include <kis_pressure_hsv_option.h>
#include <kis_airbrush_option.h>
#include <kis_pressure_scatter_option_widget.h>
#include <kis_pressure_softness_option.h>
#include <kis_pressure_sharpness_option_widget.h>
#include <kis_color_source_option_widget.h>
#include <kis_compositeop_option.h>
#include <kis_pressure_flow_opacity_option_widget.h>
#include <kis_pressure_spacing_option_widget.h>
#include <kis_pressure_rate_option.h>
#include "kis_texture_option.h"
#include "kis_curve_option_widget.h"
#include <kis_pressure_mirror_option_widget.h>
#include "kis_pressure_texture_strength_option.h"
KisBrushOpSettingsWidget::KisBrushOpSettingsWidget(QWidget* parent)
: KisBrushBasedPaintopOptionWidget(parent)
{
setObjectName("brush option widget");
setPrecisionEnabled(true);
// Brush tip options
addPaintOpOption(new KisCompositeOpOption(true), i18n("Blending Mode"));
addPaintOpOption(new KisFlowOpacityOptionWidget(), i18n("Opacity"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureFlowOption(), i18n("0%"), i18n("100%")), i18n("Flow"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureSizeOption(), i18n("0%"), i18n("100%")), i18n("Size"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureRatioOption(), i18n("0%"), i18n("100%")), i18n("Ratio"));
addPaintOpOption(new KisPressureSpacingOptionWidget(), i18n("Spacing"));
addPaintOpOption(new KisPressureMirrorOptionWidget(), i18n("Mirror"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureSoftnessOption(), i18n("Soft"), i18n("Hard")), i18n("Softness"));
addPaintOpOption(new KisPressureSharpnessOptionWidget(), i18n("Sharpness"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureRotationOption(), i18n("-180°"), i18n("180°")), i18n("Rotation"));
addPaintOpOption(new KisPressureScatterOptionWidget(), i18n("Scatter"));
// Colors options
addPaintOpOption(new KisColorSourceOptionWidget(), i18n("Source"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureDarkenOption(), i18n("0.0"), i18n("1.0")), i18n("Darken"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureMixOption(), i18n("Foreground"), i18n("Background")), i18n("Mix"));
addPaintOpOption(new KisCurveOptionWidget(KisPressureHSVOption::createHueOption(), KisPressureHSVOption::hueMinLabel(), KisPressureHSVOption::huemaxLabel()), i18n("Hue"));
addPaintOpOption(new KisCurveOptionWidget(KisPressureHSVOption::createSaturationOption(), KisPressureHSVOption::saturationMinLabel(), KisPressureHSVOption::saturationmaxLabel()), i18n("Saturation"));
addPaintOpOption(new KisCurveOptionWidget(KisPressureHSVOption::createValueOption(), KisPressureHSVOption::valueMinLabel(), KisPressureHSVOption::valuemaxLabel()), i18n("Value"));
addPaintOpOption(new KisAirbrushOption(false), i18n("Airbrush"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureRateOption(), i18n("0%"), i18n("100%")), i18n("Rate"));
addPaintOpOption(new KisPaintActionTypeOption(), i18n("Painting Mode"));
addPaintOpOption(new KisTextureOption(), i18n("Pattern"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureTextureStrengthOption(), i18n("Weak"), i18n("Strong")), i18n("Strength"));
}
KisBrushOpSettingsWidget::~KisBrushOpSettingsWidget()
{
}
KisPropertiesConfigurationSP KisBrushOpSettingsWidget::configuration() const
{
- KisBrushBasedPaintOpSettingsSP config = new KisBrushBasedPaintOpSettings();
+ KisBrushBasedPaintOpSettingsSP config = new KisBrushOpSettings();
config->setOptionsWidget(const_cast<KisBrushOpSettingsWidget*>(this));
config->setProperty("paintop", "paintbrush"); // XXX: make this a const id string
writeConfiguration(config);
return config;
}
diff --git a/plugins/paintops/defaultpaintops/brush/tests/CMakeLists.txt b/plugins/paintops/defaultpaintops/brush/tests/CMakeLists.txt
index ed22adf8e7..d2d4500c5e 100644
--- a/plugins/paintops/defaultpaintops/brush/tests/CMakeLists.txt
+++ b/plugins/paintops/defaultpaintops/brush/tests/CMakeLists.txt
@@ -1,14 +1,20 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests )
include(KritaAddBrokenUnitTest)
macro_add_unittest_definitions()
include(ECMAddTests)
+ecm_add_test(KisDabRenderingQueueTest.cpp
+ TEST_NAME KisDabRenderingQueueTest
+ LINK_LIBRARIES kritadefaultpaintops kritalibpaintop kritaimage Qt5::Test)
+
+
+
krita_add_broken_unit_test(kis_brushop_test.cpp ../../../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME krita-plugins-KisBrushOpTest
LINK_LIBRARIES kritaimage kritaui kritalibpaintop Qt5::Test)
diff --git a/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp b/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp
new file mode 100644
index 0000000000..c72f7603b4
--- /dev/null
+++ b/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.cpp
@@ -0,0 +1,541 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisDabRenderingQueueTest.h"
+
+#include <QTest>
+#include <KoColorSpace.h>
+#include <KoColorSpaceRegistry.h>
+
+#include <../KisDabRenderingQueue.h>
+#include <../KisRenderedDab.h>
+#include <../KisDabRenderingJob.h>
+
+struct SurrogateCacheInterface : public KisDabRenderingQueue::CacheInterface
+{
+ void getDabType(bool hasDabInCache,
+ KisDabCacheUtils::DabRenderingResources *resources,
+ const KisDabCacheUtils::DabRequestInfo &request,
+ /* out */
+ KisDabCacheUtils::DabGenerationInfo *di,
+ bool *shouldUseCache) override
+ {
+ Q_UNUSED(resources);
+ Q_UNUSED(request);
+
+ if (!hasDabInCache || typeOverride == KisDabRenderingJob::Dab) {
+ di->needsPostprocessing = false;
+ *shouldUseCache = false;
+ } else if (typeOverride == KisDabRenderingJob::Copy) {
+ di->needsPostprocessing = false;
+ *shouldUseCache = true;
+ } else if (typeOverride == KisDabRenderingJob::Postprocess) {
+ di->needsPostprocessing = true;
+ *shouldUseCache = true;
+ }
+
+ di->info = request.info;
+ }
+
+ bool hasSeparateOriginal(KisDabCacheUtils::DabRenderingResources *resources) const override {
+ Q_UNUSED(resources);
+ return typeOverride == KisDabRenderingJob::Postprocess;
+ }
+
+ KisDabRenderingJob::JobType typeOverride = KisDabRenderingJob::Dab;
+};
+
+#include <kis_mask_generator.h>
+#include "kis_auto_brush.h"
+
+KisDabCacheUtils::DabRenderingResources *testResourcesFactory()
+{
+ KisDabCacheUtils::DabRenderingResources *resources =
+ new KisDabCacheUtils::DabRenderingResources();
+
+ KisCircleMaskGenerator* circle = new KisCircleMaskGenerator(10, 1.0, 1.0, 1.0, 2, false);
+ KisBrushSP brush = new KisAutoBrush(circle, 0.0, 0.0);
+ resources->brush = brush;
+
+ return resources;
+}
+
+void KisDabRenderingQueueTest::testCachedDabs()
+{
+ const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
+
+ SurrogateCacheInterface *cacheInterface = new SurrogateCacheInterface();
+
+ KisDabRenderingQueue queue(cs, testResourcesFactory);
+ queue.setCacheInterface(cacheInterface);
+
+ KoColor color;
+ QPointF pos1(10,10);
+ QPointF pos2(20,20);
+ KisDabShape shape;
+ KisPaintInformation pi1(pos1);
+ KisPaintInformation pi2(pos2);
+
+ KisDabCacheUtils::DabRequestInfo request1(color, pos1, shape, pi1, 1.0);
+ KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0);
+
+ cacheInterface->typeOverride = KisDabRenderingJob::Dab;
+ KisDabRenderingJobSP job0 = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+
+ QVERIFY(job0);
+ QCOMPARE(job0->seqNo, 0);
+ QCOMPARE(job0->generationInfo.info.pos(), request1.info.pos());
+ QCOMPARE(job0->type, KisDabRenderingJob::Dab);
+ QVERIFY(!job0->originalDevice);
+ QVERIFY(!job0->postprocessedDevice);
+
+ cacheInterface->typeOverride = KisDabRenderingJob::Dab;
+ KisDabRenderingJobSP job1 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+
+ QVERIFY(job1);
+ QCOMPARE(job1->seqNo, 1);
+ QCOMPARE(job1->generationInfo.info.pos(), request2.info.pos());
+ QCOMPARE(job1->type, KisDabRenderingJob::Dab);
+ QVERIFY(!job1->originalDevice);
+ QVERIFY(!job1->postprocessedDevice);
+
+ cacheInterface->typeOverride = KisDabRenderingJob::Copy;
+ KisDabRenderingJobSP job2 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+ QVERIFY(!job2);
+
+ cacheInterface->typeOverride = KisDabRenderingJob::Copy;
+ KisDabRenderingJobSP job3 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+ QVERIFY(!job3);
+
+ // we only added the dabs, but we haven't completed them yet
+ QVERIFY(!queue.hasPreparedDabs());
+ QCOMPARE(queue.testingGetQueueSize(), 4);
+
+ QList<KisDabRenderingJobSP > jobs;
+ QList<KisRenderedDab> renderedDabs;
+
+
+ {
+ // we've completed job0
+ job0->originalDevice = new KisFixedPaintDevice(cs);
+ job0->postprocessedDevice = job0->originalDevice;
+
+ jobs = queue.notifyJobFinished(job0->seqNo);
+ QVERIFY(jobs.isEmpty());
+
+ // now we should have at least one job in prepared state
+ QVERIFY(queue.hasPreparedDabs());
+
+ // take the prepared dabs
+ renderedDabs = queue.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 1);
+
+ // the list should be empty again
+ QVERIFY(!queue.hasPreparedDabs());
+ QCOMPARE(queue.testingGetQueueSize(), 3);
+ }
+
+ {
+ // we've completed job1
+ job1->originalDevice = new KisFixedPaintDevice(cs);
+ job1->postprocessedDevice = job1->originalDevice;
+
+ jobs = queue.notifyJobFinished(job1->seqNo);
+ QVERIFY(jobs.isEmpty());
+
+ // now we should have at least one job in prepared state
+ QVERIFY(queue.hasPreparedDabs());
+
+ // take the prepared dabs
+ renderedDabs = queue.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 3);
+
+ // since they are copies, they should be the same
+ QCOMPARE(renderedDabs[1].device, renderedDabs[0].device);
+ QCOMPARE(renderedDabs[2].device, renderedDabs[0].device);
+
+ // the list should be empty again
+ QVERIFY(!queue.hasPreparedDabs());
+
+ // we delete all the painted jobs except the latest 'dab' job
+ QCOMPARE(queue.testingGetQueueSize(), 1);
+ }
+
+ {
+ // add one more cached job and take it
+ cacheInterface->typeOverride = KisDabRenderingJob::Copy;
+ KisDabRenderingJobSP job = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+ QVERIFY(!job);
+
+ // now we should have at least one job in prepared state
+ QVERIFY(queue.hasPreparedDabs());
+
+ // take the prepared dabs
+ renderedDabs = queue.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 1);
+
+ // the list should be empty again
+ QVERIFY(!queue.hasPreparedDabs());
+
+ // we delete all the painted jobs except the latest 'dab' job
+ QCOMPARE(queue.testingGetQueueSize(), 1);
+ }
+
+ {
+ // add a 'dab' job and complete it
+
+ cacheInterface->typeOverride = KisDabRenderingJob::Dab;
+ KisDabRenderingJobSP job = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+
+ QVERIFY(job);
+ QCOMPARE(job->seqNo, 5);
+ QCOMPARE(job->generationInfo.info.pos(), request1.info.pos());
+ QCOMPARE(job->type, KisDabRenderingJob::Dab);
+ QVERIFY(!job->originalDevice);
+ QVERIFY(!job->postprocessedDevice);
+
+ // now the queue can be cleared from the completed dabs!
+ QCOMPARE(queue.testingGetQueueSize(), 1);
+
+ job->originalDevice = new KisFixedPaintDevice(cs);
+ job->postprocessedDevice = job->originalDevice;
+
+ jobs = queue.notifyJobFinished(job->seqNo);
+ QVERIFY(jobs.isEmpty());
+
+ // now we should have at least one job in prepared state
+ QVERIFY(queue.hasPreparedDabs());
+
+ // take the prepared dabs
+ renderedDabs = queue.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 1);
+
+ // the list should be empty again
+ QVERIFY(!queue.hasPreparedDabs());
+
+ // we do not delete the queue of jobs until the next 'dab'
+ // job arrives
+ QCOMPARE(queue.testingGetQueueSize(), 1);
+ }
+
+}
+
+void KisDabRenderingQueueTest::testPostprocessedDabs()
+{
+ const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
+
+ SurrogateCacheInterface *cacheInterface = new SurrogateCacheInterface();
+
+ KisDabRenderingQueue queue(cs, testResourcesFactory);
+ queue.setCacheInterface(cacheInterface);
+
+ KoColor color;
+ QPointF pos1(10,10);
+ QPointF pos2(20,20);
+ KisDabShape shape;
+ KisPaintInformation pi1(pos1);
+ KisPaintInformation pi2(pos2);
+
+ KisDabCacheUtils::DabRequestInfo request1(color, pos1, shape, pi1, 1.0);
+ KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0);
+
+ cacheInterface->typeOverride = KisDabRenderingJob::Dab;
+ KisDabRenderingJobSP job0 = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+
+ QVERIFY(job0);
+ QCOMPARE(job0->seqNo, 0);
+ QCOMPARE(job0->generationInfo.info.pos(), request1.info.pos());
+ QCOMPARE(job0->type, KisDabRenderingJob::Dab);
+ QVERIFY(!job0->originalDevice);
+ QVERIFY(!job0->postprocessedDevice);
+
+ cacheInterface->typeOverride = KisDabRenderingJob::Dab;
+ KisDabRenderingJobSP job1 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+
+ QVERIFY(job1);
+ QCOMPARE(job1->seqNo, 1);
+ QCOMPARE(job1->generationInfo.info.pos(), request2.info.pos());
+ QCOMPARE(job1->type, KisDabRenderingJob::Dab);
+ QVERIFY(!job1->originalDevice);
+ QVERIFY(!job1->postprocessedDevice);
+
+ cacheInterface->typeOverride = KisDabRenderingJob::Postprocess;
+ KisDabRenderingJobSP job2 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+ QVERIFY(!job2);
+
+ cacheInterface->typeOverride = KisDabRenderingJob::Postprocess;
+ KisDabRenderingJobSP job3 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+ QVERIFY(!job3);
+
+ // we only added the dabs, but we haven't completed them yet
+ QVERIFY(!queue.hasPreparedDabs());
+ QCOMPARE(queue.testingGetQueueSize(), 4);
+
+ QList<KisDabRenderingJobSP > jobs;
+ QList<KisRenderedDab> renderedDabs;
+
+
+ {
+ // we've completed job0
+ job0->originalDevice = new KisFixedPaintDevice(cs);
+ job0->postprocessedDevice = job0->originalDevice;
+
+ jobs = queue.notifyJobFinished(job0->seqNo);
+ QVERIFY(jobs.isEmpty());
+
+ // now we should have at least one job in prepared state
+ QVERIFY(queue.hasPreparedDabs());
+
+ // take the prepared dabs
+ renderedDabs = queue.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 1);
+
+ // the list should be empty again
+ QVERIFY(!queue.hasPreparedDabs());
+ QCOMPARE(queue.testingGetQueueSize(), 3);
+ }
+
+ {
+ // we've completed job1
+ job1->originalDevice = new KisFixedPaintDevice(cs);
+ job1->postprocessedDevice = job1->originalDevice;
+
+ jobs = queue.notifyJobFinished(job1->seqNo);
+ QCOMPARE(jobs.size(), 2);
+
+ QCOMPARE(jobs[0]->seqNo, 2);
+ QCOMPARE(jobs[1]->seqNo, 3);
+
+ QVERIFY(jobs[0]->originalDevice);
+ QVERIFY(!jobs[0]->postprocessedDevice);
+
+ QVERIFY(jobs[1]->originalDevice);
+ QVERIFY(!jobs[1]->postprocessedDevice);
+
+ // pretend we have created a postprocessed device
+ jobs[0]->postprocessedDevice = new KisFixedPaintDevice(cs);
+ jobs[1]->postprocessedDevice = new KisFixedPaintDevice(cs);
+
+ // now we should have at least one job in prepared state
+ QVERIFY(queue.hasPreparedDabs());
+
+ // take the prepared dabs
+ renderedDabs = queue.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 1);
+
+ // the list should be empty again
+ QVERIFY(!queue.hasPreparedDabs());
+
+
+ // return back two postprocessed dabs
+ QList<KisDabRenderingJobSP > emptyJobs;
+ emptyJobs = queue.notifyJobFinished(jobs[0]->seqNo);
+ QVERIFY(emptyJobs.isEmpty());
+
+ emptyJobs = queue.notifyJobFinished(jobs[1]->seqNo);
+ QVERIFY(emptyJobs.isEmpty());
+
+
+ // now we should have at least one job in prepared state
+ QVERIFY(queue.hasPreparedDabs());
+
+ // take the prepared dabs
+ renderedDabs = queue.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 2);
+
+ // the list should be empty again
+ QVERIFY(!queue.hasPreparedDabs());
+
+ // we delete all the painted jobs except the latest 'dab' job
+ QCOMPARE(queue.testingGetQueueSize(), 1);
+ }
+
+ {
+ // add one more postprocessed job and take it
+ cacheInterface->typeOverride = KisDabRenderingJob::Postprocess;
+ KisDabRenderingJobSP job = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+
+ QVERIFY(job);
+ QCOMPARE(job->seqNo, 4);
+ QCOMPARE(job->generationInfo.info.pos(), request2.info.pos());
+ ENTER_FUNCTION() << ppVar(job->type);
+
+ QCOMPARE(job->type, KisDabRenderingJob::Postprocess);
+ QVERIFY(job->originalDevice);
+ QVERIFY(!job->postprocessedDevice);
+
+ // the list should still be empty
+ QVERIFY(!queue.hasPreparedDabs());
+
+ // pretend we have created a postprocessed device
+ job->postprocessedDevice = new KisFixedPaintDevice(cs);
+
+ // return back the postprocessed dab
+ QList<KisDabRenderingJobSP > emptyJobs;
+ emptyJobs = queue.notifyJobFinished(job->seqNo);
+ QVERIFY(emptyJobs.isEmpty());
+
+ // now we should have at least one job in prepared state
+ QVERIFY(queue.hasPreparedDabs());
+
+ // take the prepared dabs
+ renderedDabs = queue.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 1);
+
+ // the list should be empty again
+ QVERIFY(!queue.hasPreparedDabs());
+
+ // we delete all the painted jobs except the latest 'dab' job
+ QCOMPARE(queue.testingGetQueueSize(), 1);
+ }
+
+ {
+ // add a 'dab' job and complete it. That will clear the queue!
+
+ cacheInterface->typeOverride = KisDabRenderingJob::Dab;
+ KisDabRenderingJobSP job = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+
+ QVERIFY(job);
+ QCOMPARE(job->seqNo, 5);
+ QCOMPARE(job->generationInfo.info.pos(), request1.info.pos());
+ QCOMPARE(job->type, KisDabRenderingJob::Dab);
+ QVERIFY(!job->originalDevice);
+ QVERIFY(!job->postprocessedDevice);
+
+ // now the queue can be cleared from the completed dabs!
+ QCOMPARE(queue.testingGetQueueSize(), 1);
+
+ job->originalDevice = new KisFixedPaintDevice(cs);
+ job->postprocessedDevice = job->originalDevice;
+
+ jobs = queue.notifyJobFinished(job->seqNo);
+ QVERIFY(jobs.isEmpty());
+
+ // now we should have at least one job in prepared state
+ QVERIFY(queue.hasPreparedDabs());
+
+ // take the prepared dabs
+ renderedDabs = queue.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 1);
+
+ // the list should be empty again
+ QVERIFY(!queue.hasPreparedDabs());
+
+ // we do not delete the queue of jobs until the next 'dab'
+ // job arrives
+ QCOMPARE(queue.testingGetQueueSize(), 1);
+ }
+
+}
+
+#include <../KisDabRenderingQueueCache.h>
+
+void KisDabRenderingQueueTest::testRunningJobs()
+{
+ const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
+
+ KisDabRenderingQueueCache *cacheInterface = new KisDabRenderingQueueCache();
+ // we do *not* initialize any options yet!
+
+ KisDabRenderingQueue queue(cs, testResourcesFactory);
+ queue.setCacheInterface(cacheInterface);
+
+
+ KoColor color(Qt::red, cs);
+ QPointF pos1(10,10);
+ QPointF pos2(20,20);
+ KisDabShape shape;
+ KisPaintInformation pi1(pos1);
+ KisPaintInformation pi2(pos2);
+
+ KisDabCacheUtils::DabRequestInfo request1(color, pos1, shape, pi1, 1.0);
+ KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0);
+
+ KisDabRenderingJobSP job0 = queue.addDab(request1, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+
+ QVERIFY(job0);
+ QCOMPARE(job0->seqNo, 0);
+ QCOMPARE(job0->generationInfo.info.pos(), request1.info.pos());
+ QCOMPARE(job0->type, KisDabRenderingJob::Dab);
+
+ QVERIFY(!job0->originalDevice);
+ QVERIFY(!job0->postprocessedDevice);
+
+ KisDabRenderingJobRunner runner(job0, &queue, 0);
+ runner.run();
+
+ QVERIFY(job0->originalDevice);
+ QVERIFY(job0->postprocessedDevice);
+ QCOMPARE(job0->originalDevice, job0->postprocessedDevice);
+
+ QVERIFY(!job0->originalDevice->bounds().isEmpty());
+
+ KisDabRenderingJobSP job1 = queue.addDab(request2, OPACITY_OPAQUE_F, OPACITY_OPAQUE_F);
+ QVERIFY(!job1);
+
+ QList<KisRenderedDab> renderedDabs = queue.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 2);
+
+ // we did the caching
+ QVERIFY(renderedDabs[0].device == renderedDabs[1].device);
+
+ QCOMPARE(renderedDabs[0].offset, QPoint(5,5));
+ QCOMPARE(renderedDabs[1].offset, QPoint(15,15));
+}
+
+#include "../KisDabRenderingExecutor.h"
+#include "KisFakeRunnableStrokeJobsExecutor.h"
+
+void KisDabRenderingQueueTest::testExecutor()
+{
+ const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
+
+ QScopedPointer<KisRunnableStrokeJobsInterface> runner(new KisFakeRunnableStrokeJobsExecutor());
+
+ KisDabRenderingExecutor executor(cs, testResourcesFactory, runner.data());
+
+ KoColor color(Qt::red, cs);
+ QPointF pos1(10,10);
+ QPointF pos2(20,20);
+ KisDabShape shape;
+ KisPaintInformation pi1(pos1);
+ KisPaintInformation pi2(pos2);
+
+ KisDabCacheUtils::DabRequestInfo request1(color, pos1, shape, pi1, 1.0);
+ KisDabCacheUtils::DabRequestInfo request2(color, pos2, shape, pi2, 1.0);
+
+ executor.addDab(request1, 0.5, 0.25);
+ executor.addDab(request2, 0.125, 1.0);
+
+ QList<KisRenderedDab> renderedDabs = executor.takeReadyDabs();
+ QCOMPARE(renderedDabs.size(), 2);
+
+ // we did the caching
+ QVERIFY(renderedDabs[0].device == renderedDabs[1].device);
+
+ QCOMPARE(renderedDabs[0].offset, QPoint(5,5));
+ QCOMPARE(renderedDabs[1].offset, QPoint(15,15));
+
+ QCOMPARE(renderedDabs[0].opacity, 0.5);
+ QCOMPARE(renderedDabs[0].flow, 0.25);
+ QCOMPARE(renderedDabs[1].opacity, 0.125);
+ QCOMPARE(renderedDabs[1].flow, 1.0);
+
+}
+
+QTEST_MAIN(KisDabRenderingQueueTest)
diff --git a/plugins/paintops/chalk/chalk_paintop_plugin.h b/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.h
similarity index 65%
rename from plugins/paintops/chalk/chalk_paintop_plugin.h
rename to plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.h
index 0d78033afd..fa3f66d076 100644
--- a/plugins/paintops/chalk/chalk_paintop_plugin.h
+++ b/plugins/paintops/defaultpaintops/brush/tests/KisDabRenderingQueueTest.h
@@ -1,36 +1,35 @@
/*
- * Copyright (c) 2008 Lukáš Tvrdý (lukast.dev@gmail.com)
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef CHALK_PAINTOP_PLUGIN_H_
-#define CHALK_PAINTOP_PLUGIN_H_
+#ifndef KISDABRENDERINGQUEUETEST_H
+#define KISDABRENDERINGQUEUETEST_H
#include <QObject>
-#include <QVariant>
-/**
- * A plugin wrapper that adds the paintop factories to the paintop registry.
- */
-class ChalkPaintOpPlugin : public QObject
+class KisDabRenderingQueueTest : public QObject
{
Q_OBJECT
-public:
- ChalkPaintOpPlugin(QObject *parent, const QVariantList &);
- ~ChalkPaintOpPlugin() override;
+private Q_SLOTS:
+ void testCachedDabs();
+ void testPostprocessedDabs();
+ void testRunningJobs();
+
+ void testExecutor();
};
-#endif // CHALK_PAINTOP_PLUGIN_H_
+#endif // KISDABRENDERINGQUEUETEST_H
diff --git a/plugins/paintops/defaultpaintops/defaultpaintops_plugin.cc b/plugins/paintops/defaultpaintops/defaultpaintops_plugin.cc
index b50be936f0..03d5bde99d 100644
--- a/plugins/paintops/defaultpaintops/defaultpaintops_plugin.cc
+++ b/plugins/paintops/defaultpaintops/defaultpaintops_plugin.cc
@@ -1,57 +1,57 @@
/*
* defaultpaintops_plugin.cc -- Part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "defaultpaintops_plugin.h"
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <KoCompositeOpRegistry.h>
#include "kis_simple_paintop_factory.h"
#include "kis_brushop.h"
#include "kis_brushop_settings_widget.h"
#include "kis_duplicateop.h"
#include "kis_duplicateop_settings.h"
#include "kis_global.h"
#include <brushengine/kis_paintop_registry.h>
-#include "kis_brush_based_paintop_settings.h"
+#include "KisBrushOpSettings.h"
#include "kis_brush_server.h"
#include "kis_duplicateop_settings_widget.h"
K_PLUGIN_FACTORY_WITH_JSON(DefaultPaintOpsPluginFactory, "kritadefaultpaintops.json", registerPlugin<DefaultPaintOpsPlugin>();)
DefaultPaintOpsPlugin::DefaultPaintOpsPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KisPaintOpRegistry *r = KisPaintOpRegistry::instance();
- r->add(new KisSimplePaintOpFactory<KisBrushOp, KisBrushBasedPaintOpSettings, KisBrushOpSettingsWidget>("paintbrush", i18nc("Pixel paintbrush", "Pixel"), KisPaintOpFactory::categoryStable(), "krita-paintbrush.png", QString(), QStringList(), 1));
+ r->add(new KisSimplePaintOpFactory<KisBrushOp, KisBrushOpSettings, KisBrushOpSettingsWidget>("paintbrush", i18nc("Pixel paintbrush", "Pixel"), KisPaintOpFactory::categoryStable(), "krita-paintbrush.png", QString(), QStringList(), 1));
r->add(new KisSimplePaintOpFactory<KisDuplicateOp, KisDuplicateOpSettings, KisDuplicateOpSettingsWidget>("duplicate", i18nc("clone paintbrush (previously \"Duplicate\")", "Clone"), KisPaintOpFactory::categoryStable(), "krita-duplicate.png", QString(), QStringList(COMPOSITE_COPY), 15));
KisBrushServer::instance();
}
DefaultPaintOpsPlugin::~DefaultPaintOpsPlugin()
{
}
#include "defaultpaintops_plugin.moc"
diff --git a/plugins/paintops/deform/deform_brush.cpp b/plugins/paintops/deform/deform_brush.cpp
index 569c720df9..c1b4f1951a 100644
--- a/plugins/paintops/deform/deform_brush.cpp
+++ b/plugins/paintops/deform/deform_brush.cpp
@@ -1,294 +1,291 @@
/*
* Copyright (c) 2008,2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "deform_brush.h"
#include "kis_painter.h"
#include "kis_fixed_paint_device.h"
#include <KoColor.h>
#include <KoColorSpace.h>
#include <QRect>
#include <kis_types.h>
#include <kis_iterator_ng.h>
#include <kis_cross_device_color_picker.h>
#include <cmath>
#include <ctime>
#include <KoColorSpaceRegistry.h>
const qreal degToRad = M_PI / 180.0;
DeformBrush::DeformBrush()
{
m_firstPaint = false;
m_counter = 1;
m_deformAction = 0;
}
DeformBrush::~DeformBrush()
{
delete m_deformAction;
}
void DeformBrush::initDeformAction()
{
DeformModes mode = DeformModes(m_properties->deform_action - 1);
switch (mode) {
case GROW:
case SHRINK: {
m_deformAction = new DeformScale();
break;
}
case SWIRL_CW:
case SWIRL_CCW: {
m_deformAction = new DeformRotation();
break;
}
case MOVE: {
m_deformAction = new DeformMove();
static_cast<DeformMove*>(m_deformAction)->setFactor(m_properties->deform_amount);
break;
}
case LENS_IN:
case LENS_OUT: {
m_deformAction = new DeformLens();
static_cast<DeformLens*>(m_deformAction)->setLensFactor(m_properties->deform_amount, 0.0);
static_cast<DeformLens*>(m_deformAction)->setMode(mode == LENS_OUT);
break;
}
case DEFORM_COLOR: {
m_deformAction = new DeformColor();
static_cast<DeformColor*>(m_deformAction)->setFactor(m_properties->deform_amount);
break;
}
default: {
m_deformAction = new DeformBase();
break;
}
}
}
bool DeformBrush::setupAction(
DeformModes mode, const QPointF& pos, QTransform const& rotation)
{
switch (mode) {
case GROW:
case SHRINK: {
// grow or shrink, the sign decide
qreal sign = (mode == GROW) ? 1.0 : -1.0;
qreal factor;
if (m_properties->deform_use_counter) {
factor = (1.0 + sign * (m_counter * m_counter / 100.0));
}
else {
factor = (1.0 + sign * (m_properties->deform_amount));
}
dynamic_cast<DeformScale*>(m_deformAction)->setFactor(factor);
break;
}
case SWIRL_CW:
case SWIRL_CCW: {
// CW or CCW, the sign decide
qreal sign = (mode == SWIRL_CW) ? 1.0 : -1.0;
qreal factor;
if (m_properties->deform_use_counter) {
factor = m_counter * sign * degToRad;
}
else {
factor = (360 * m_properties->deform_amount * 0.5) * sign * degToRad;
}
dynamic_cast<DeformRotation*>(m_deformAction)->setAlpha(factor);
break;
}
case MOVE: {
if (m_firstPaint == false) {
m_prevX = pos.x();
m_prevY = pos.y();
static_cast<DeformMove*>(m_deformAction)->setDistance(0.0, 0.0);
m_firstPaint = true;
return false;
}
else {
qreal xDistance = pos.x() - m_prevX;
qreal yDistance = pos.y() - m_prevY;
rotation.map(xDistance, yDistance, &xDistance, &yDistance);
static_cast<DeformMove*>(m_deformAction)->setDistance(xDistance, yDistance);
m_prevX = pos.x();
m_prevY = pos.y();
}
break;
}
case LENS_IN:
case LENS_OUT: {
static_cast<DeformLens*>(m_deformAction)->setMaxDistance(m_sizeProperties->brush_diameter * 0.5, m_sizeProperties->brush_diameter * 0.5);
break;
}
case DEFORM_COLOR: {
// no run-time setup
break;
}
default: {
break;
}
}
return true;
}
KisFixedPaintDeviceSP DeformBrush::paintMask(KisFixedPaintDeviceSP dab,
KisPaintDeviceSP layer,
qreal scale,
qreal rotation,
QPointF pos, qreal subPixelX, qreal subPixelY, int dabX, int dabY)
{
KisFixedPaintDeviceSP mask = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
KisCrossDeviceColorPicker colorPicker(layer, dab);
qreal fWidth = maskWidth(scale);
qreal fHeight = maskHeight(scale);
int dstWidth = qRound(m_maskRect.width());
int dstHeight = qRound(m_maskRect.height());
// clear
if (dab->bounds().width() != dstWidth || dab->bounds().height() != dstHeight) {
dab->setRect(m_maskRect.toRect());
- dab->initialize();
- }
- else {
- dab->clear(m_maskRect.toRect());
+ dab->lazyGrowBufferWithoutInitialization();
}
qreal const centerX = dstWidth * 0.5 + subPixelX;
qreal const centerY = dstHeight * 0.5 + subPixelY;
qreal const majorAxis = 2.0 / fWidth;
qreal const minorAxis = 2.0 / fHeight;
qreal distance;
QTransform forwardRotationMatrix;
forwardRotationMatrix.rotateRadians(-rotation);
QTransform reverseRotationMatrix;
reverseRotationMatrix.rotateRadians(rotation);
// if can't paint, stop
if (!setupAction(DeformModes(m_properties->deform_action - 1),
pos, forwardRotationMatrix))
{
return 0;
}
mask->setRect(dab->bounds());
- mask->initialize();
+ mask->lazyGrowBufferWithoutInitialization();
quint8* maskPointer = mask->data();
qint8 maskPixelSize = mask->pixelSize();
quint8* dabPointer = dab->data();
int dabPixelSize = dab->colorSpace()->pixelSize();
for (int y = 0; y < dstHeight; y++) {
for (int x = 0; x < dstWidth; x++) {
qreal maskX = x - centerX;
qreal maskY = y - centerY;
forwardRotationMatrix.map(maskX, maskY, &maskX, &maskY);
distance = norme(maskX * majorAxis, maskY * minorAxis);
if (distance > 1.0) {
// leave there OPACITY TRANSPARENT pixel (default pixel)
colorPicker.pickOldColor(x + dabX, y + dabY, dabPointer);
dabPointer += dabPixelSize;
*maskPointer = OPACITY_TRANSPARENT_U8;
maskPointer += maskPixelSize;
continue;
}
if (m_sizeProperties->brush_density != 1.0) {
if (m_sizeProperties->brush_density < drand48()) {
dabPointer += dabPixelSize;
*maskPointer = OPACITY_TRANSPARENT_U8;
maskPointer += maskPixelSize;
continue;
}
}
m_deformAction->transform(&maskX, &maskY, distance);
reverseRotationMatrix.map(maskX, maskY, &maskX, &maskY);
maskX += pos.x();
maskY += pos.y();
if (!m_properties->deform_use_bilinear) {
maskX = qRound(maskX);
maskY = qRound(maskY);
}
if (m_properties->deform_use_old_data) {
colorPicker.pickOldColor(maskX, maskY, dabPointer);
}
else {
colorPicker.pickColor(maskX, maskY, dabPointer);
}
dabPointer += dabPixelSize;
*maskPointer = OPACITY_OPAQUE_U8;
maskPointer += maskPixelSize;
}
}
m_counter++;
return mask;
}
void DeformBrush::debugColor(const quint8* data, KoColorSpace * cs)
{
QColor rgbcolor;
cs->toQColor(data, &rgbcolor);
dbgPlugins << "RGBA: ("
<< rgbcolor.red()
<< ", " << rgbcolor.green()
<< ", " << rgbcolor.blue()
<< ", " << rgbcolor.alpha() << ")";
}
QPointF DeformBrush::hotSpot(qreal scale, qreal rotation)
{
qreal fWidth = maskWidth(scale);
qreal fHeight = maskHeight(scale);
QTransform m;
m.reset();
m.rotateRadians(rotation);
m_maskRect = QRect(0, 0, fWidth, fHeight);
m_maskRect.translate(-m_maskRect.center());
m_maskRect = m.mapRect(m_maskRect);
m_maskRect.translate(-m_maskRect.topLeft());
return m_maskRect.center();
}
diff --git a/plugins/paintops/libpaintop/CMakeLists.txt b/plugins/paintops/libpaintop/CMakeLists.txt
index 7bd94aecc1..ea0af1519b 100644
--- a/plugins/paintops/libpaintop/CMakeLists.txt
+++ b/plugins/paintops/libpaintop/CMakeLists.txt
@@ -1,100 +1,104 @@
set(kritalibpaintop_LIB_SRCS
kis_airbrush_option.cpp
kis_auto_brush_widget.cpp
kis_spacing_selection_widget.cpp
kis_bidirectional_mixing_option.cpp
kis_bidirectional_mixing_option_widget.cpp
kis_brush_based_paintop.cpp
kis_brush_chooser.cpp
kis_brush_option_widget.cpp
kis_brush_option.cpp
kis_brush_selection_widget.cpp
kis_color_option.cpp
kis_color_source.cpp
kis_color_source_option.cpp
kis_color_source_option_widget.cpp
kis_curve_option.cpp
kis_curve_option_widget.cpp
kis_curve_option_uniform_property.cpp
kis_custom_brush_widget.cpp
kis_clipboard_brush_widget.cpp
kis_dynamic_sensor.cc
+ KisDabCacheUtils.cpp
+ kis_dab_cache_base.cpp
kis_dab_cache.cpp
kis_filter_option.cpp
kis_multi_sensors_model_p.cpp
kis_multi_sensors_selector.cpp
kis_paint_action_type_option.cpp
kis_precision_option.cpp
kis_pressure_darken_option.cpp
kis_pressure_hsv_option.cpp
kis_pressure_opacity_option.cpp
kis_pressure_flow_option.cpp
kis_pressure_mirror_option.cpp
kis_pressure_scatter_option.cpp
kis_pressure_scatter_option_widget.cpp
kis_pressure_sharpness_option.cpp
kis_pressure_sharpness_option_widget.cpp
kis_pressure_mirror_option_widget.cpp
kis_pressure_rotation_option.cpp
kis_pressure_size_option.cpp
kis_pressure_spacing_option.cpp
kis_pressure_rate_option.cpp
kis_pressure_softness_option.cpp
kis_pressure_mix_option.cpp
kis_pressure_gradient_option.cpp
kis_pressure_flow_opacity_option.cpp
kis_pressure_flow_opacity_option_widget.cpp
kis_pressure_spacing_option_widget.cpp
kis_pressure_ratio_option.cpp
kis_current_outline_fetcher.cpp
kis_text_brush_chooser.cpp
kis_brush_based_paintop_options_widget.cpp
kis_brush_based_paintop_settings.cpp
kis_compositeop_option.cpp
kis_texture_option.cpp
+ kis_texture_chooser.cpp
kis_pressure_texture_strength_option.cpp
kis_embedded_pattern_manager.cpp
sensors/kis_dynamic_sensors.cc
sensors/kis_dynamic_sensor_drawing_angle.cpp
sensors/kis_dynamic_sensor_distance.cc
sensors/kis_dynamic_sensor_time.cc
sensors/kis_dynamic_sensor_fade.cpp
sensors/kis_dynamic_sensor_fuzzy.cpp
)
ki18n_wrap_ui(kritalibpaintop_LIB_SRCS
forms/wdgautobrush.ui
forms/wdgBrushSizeOptions.ui
forms/wdgcurveoption.ui
forms/wdgcustombrush.ui
forms/wdgclipboardbrush.ui
forms/wdgtextbrush.ui
forms/wdgincremental.ui
forms/wdgmultisensorsselector.ui
forms/wdgairbrush.ui
forms/wdgfilteroption.ui
forms/wdgcoloroptions.ui
forms/wdgbrushchooser.ui
forms/wdgpredefinedbrushchooser.ui
+ forms/wdgtexturechooser.ui
forms/wdgCompositeOpOption.ui
forms/wdgflowopacityoption.ui
sensors/SensorDistanceConfiguration.ui
sensors/SensorTimeConfiguration.ui
sensors/SensorFadeConfiguration.ui
)
add_library(kritalibpaintop SHARED ${kritalibpaintop_LIB_SRCS} )
generate_export_header(kritalibpaintop BASE_NAME kritapaintop EXPORT_MACRO_NAME PAINTOP_EXPORT)
target_link_libraries(kritalibpaintop kritaui kritalibbrush kritawidgetutils)
target_link_libraries(kritalibpaintop LINK_INTERFACE_LIBRARIES kritaui kritalibbrush)
set_target_properties(kritalibpaintop PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritalibpaintop ${INSTALL_TARGETS_DEFAULT_ARGS})
add_subdirectory(tests)
diff --git a/plugins/paintops/libpaintop/KisDabCacheUtils.cpp b/plugins/paintops/libpaintop/KisDabCacheUtils.cpp
new file mode 100644
index 0000000000..a17bec115f
--- /dev/null
+++ b/plugins/paintops/libpaintop/KisDabCacheUtils.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisDabCacheUtils.h"
+
+#include "kis_brush.h"
+#include "kis_paint_device.h"
+#include "kis_fixed_paint_device.h"
+#include "kis_color_source.h"
+
+#include <kis_pressure_sharpness_option.h>
+#include <kis_texture_option.h>
+
+#include <kundo2command.h>
+
+namespace KisDabCacheUtils
+{
+
+DabRenderingResources::DabRenderingResources()
+{
+}
+
+DabRenderingResources::~DabRenderingResources()
+{
+}
+
+void DabRenderingResources::syncResourcesToSeqNo(int seqNo, const KisPaintInformation &info)
+{
+ brush->prepareForSeqNo(info, seqNo);
+}
+
+QRect correctDabRectWhenFetchedFromCache(const QRect &dabRect,
+ const QSize &realDabSize)
+{
+ int diffX = (realDabSize.width() - dabRect.width()) / 2;
+ int diffY = (realDabSize.height() - dabRect.height()) / 2;
+
+ return QRect(dabRect.x() - diffX, dabRect.y() - diffY,
+ realDabSize.width() , realDabSize.height());
+}
+
+
+void generateDab(const DabGenerationInfo &di, DabRenderingResources *resources, KisFixedPaintDeviceSP *dab)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(*dab);
+ const KoColorSpace *cs = (*dab)->colorSpace();
+
+
+ if (resources->brush->brushType() == IMAGE || resources->brush->brushType() == PIPE_IMAGE) {
+ *dab = resources->brush->paintDevice(cs, di.shape, di.info,
+ di.subPixel.x(),
+ di.subPixel.y());
+ } else if (di.solidColorFill) {
+ resources->brush->mask(*dab,
+ di.paintColor,
+ di.shape,
+ di.info,
+ di.subPixel.x(), di.subPixel.y(),
+ di.softnessFactor);
+ }
+ else {
+ if (!resources->colorSourceDevice ||
+ *cs != *resources->colorSourceDevice->colorSpace()) {
+
+ resources->colorSourceDevice = new KisPaintDevice(cs);
+ }
+ else {
+ resources->colorSourceDevice->clear();
+ }
+
+ QRect maskRect(QPoint(), di.dstDabRect.size());
+ resources->colorSource->colorize(resources->colorSourceDevice, maskRect, di.info.pos().toPoint());
+ delete resources->colorSourceDevice->convertTo(cs);
+
+ resources->brush->mask(*dab, resources->colorSourceDevice,
+ di.shape,
+ di.info,
+ di.subPixel.x(), di.subPixel.y(),
+ di.softnessFactor);
+ }
+
+ if (!di.mirrorProperties.isEmpty()) {
+ (*dab)->mirror(di.mirrorProperties.horizontalMirror,
+ di.mirrorProperties.verticalMirror);
+ }
+}
+
+void postProcessDab(KisFixedPaintDeviceSP dab,
+ const QPoint &dabTopLeft,
+ const KisPaintInformation& info,
+ DabRenderingResources *resources)
+{
+ if (resources->sharpnessOption) {
+ resources->sharpnessOption->applyThreshold(dab);
+ }
+
+ if (resources->textureOption) {
+ resources->textureOption->apply(dab, dabTopLeft, info);
+ }
+}
+
+}
+
diff --git a/plugins/paintops/libpaintop/KisDabCacheUtils.h b/plugins/paintops/libpaintop/KisDabCacheUtils.h
new file mode 100644
index 0000000000..6e694a08d3
--- /dev/null
+++ b/plugins/paintops/libpaintop/KisDabCacheUtils.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISDABCACHEUTILS_H
+#define KISDABCACHEUTILS_H
+
+#include <QRect>
+#include <QSize>
+
+#include "kis_types.h"
+
+#include <kis_pressure_mirror_option.h>
+#include "kis_dab_shape.h"
+
+#include "kritapaintop_export.h"
+#include <functional>
+
+class KisBrush;
+typedef KisSharedPtr<KisBrush> KisBrushSP;
+
+class KisColorSource;
+class KisPressureSharpnessOption;
+class KisTextureProperties;
+
+
+namespace KisDabCacheUtils
+{
+
+struct PAINTOP_EXPORT DabRenderingResources
+{
+ DabRenderingResources();
+ virtual ~DabRenderingResources();
+
+ virtual void syncResourcesToSeqNo(int seqNo, const KisPaintInformation &info);
+
+ KisBrushSP brush;
+ QScopedPointer<KisColorSource> colorSource;
+
+ QScopedPointer<KisPressureSharpnessOption> sharpnessOption;
+ QScopedPointer<KisTextureProperties> textureOption;
+
+ KisPaintDeviceSP colorSourceDevice;
+
+private:
+ DabRenderingResources(const DabRenderingResources &rhs) = delete;
+};
+
+typedef std::function<DabRenderingResources*()> ResourcesFactory;
+
+struct PAINTOP_EXPORT DabRequestInfo
+{
+ DabRequestInfo(const KoColor &_color,
+ const QPointF &_cursorPoint,
+ const KisDabShape &_shape,
+ const KisPaintInformation &_info,
+ qreal _softnessFactor)
+ : color(_color),
+ cursorPoint(_cursorPoint),
+ shape(_shape),
+ info(_info),
+ softnessFactor(_softnessFactor)
+ {
+ }
+
+ const KoColor &color;
+ const QPointF &cursorPoint;
+ const KisDabShape &shape;
+ const KisPaintInformation &info;
+ const qreal softnessFactor;
+
+private:
+ DabRequestInfo(const DabRequestInfo &rhs);
+};
+
+struct PAINTOP_EXPORT DabGenerationInfo
+{
+ MirrorProperties mirrorProperties;
+ KisDabShape shape;
+ QRect dstDabRect;
+ QPointF subPixel;
+ bool solidColorFill = true;
+ KoColor paintColor;
+ KisPaintInformation info;
+ qreal softnessFactor = 1.0;
+
+ bool needsPostprocessing = false;
+};
+
+PAINTOP_EXPORT QRect correctDabRectWhenFetchedFromCache(const QRect &dabRect,
+ const QSize &realDabSize);
+
+PAINTOP_EXPORT void generateDab(const DabGenerationInfo &di,
+ DabRenderingResources *resources,
+ KisFixedPaintDeviceSP *dab);
+
+PAINTOP_EXPORT void postProcessDab(KisFixedPaintDeviceSP dab,
+ const QPoint &dabTopLeft,
+ const KisPaintInformation& info,
+ DabRenderingResources *resources);
+
+}
+
+template<class T> class QSharedPointer;
+class KisDabRenderingJob;
+typedef QSharedPointer<KisDabRenderingJob> KisDabRenderingJobSP;
+
+#endif // KISDABCACHEUTILS_H
diff --git a/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui b/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui
index fecb0fa2dd..b6909ef5ae 100644
--- a/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui
+++ b/plugins/paintops/libpaintop/forms/wdgbrushchooser.ui
@@ -1,184 +1,184 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgBrushChooser</class>
<widget class="QWidget" name="WdgBrushChooser">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>537</width>
- <height>300</height>
+ <width>504</width>
+ <height>270</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0">
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<layout class="QHBoxLayout" name="brushChooserButtonLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="settingsFrame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
- <enum>QFrame::Raised</enum>
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
</property>
</widget>
</item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2"/>
- </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="autoPrecisionCheckBox">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="toolTip">
<string>Use to set the size from which the Automatic Precision Setting should begin. The Precision will remain 5 before this value.</string>
</property>
<property name="text">
<string>Starting Brush Size:</string>
</property>
</widget>
</item>
<item>
<widget class="KisDoubleParseSpinBox" name="sizeToStartFromSpinBox">
<property name="suffix">
<string> px</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>This determines every interval after which the precision should change. For example: if the delta value is set to be 15.00, after every 15 pts change in the size of brush, the precision will change.</string>
</property>
<property name="text">
<string>Delta :</string>
</property>
</widget>
</item>
<item>
<widget class="KisDoubleParseSpinBox" name="deltaValueSpinBox">
<property name="suffix">
<string> px</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblPrecisionValue">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblPrecision">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Precision:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="KisSliderSpinBox" name="sliderPrecision" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisDoubleParseSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>kis_double_parse_spin_box.h</header>
</customwidget>
<customwidget>
<class>KisSliderSpinBox</class>
<extends>QWidget</extends>
<header>kis_slider_spin_box.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/paintops/libpaintop/forms/wdgcurveoption.ui b/plugins/paintops/libpaintop/forms/wdgcurveoption.ui
index e63e268f6b..6893388587 100644
--- a/plugins/paintops/libpaintop/forms/wdgcurveoption.ui
+++ b/plugins/paintops/libpaintop/forms/wdgcurveoption.ui
@@ -1,383 +1,499 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgCurveOption</class>
<widget class="QWidget" name="WdgCurveOption">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>634</width>
- <height>444</height>
+ <width>486</width>
+ <height>464</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="bottomMargin">
<number>15</number>
</property>
<item>
<widget class="QLabel" name="strengthLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Strength:</string>
</property>
</widget>
</item>
<item>
<widget class="KisDoubleSliderSpinBox" name="slider" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
- <widget class="QCheckBox" name="checkBoxUseCurve">
- <property name="text">
- <string>Enable Pen Settings</string>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="checkBoxUseCurve">
+ <property name="minimumSize">
+ <size>
+ <width>190</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Enable Pen Settings</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="linearCurveButton">
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="revLinearButton">
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="sCurveButton">
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="reverseSCurveButton">
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="uCurveButton">
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="revUCurveButton">
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="jCurveButton">
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="lCurveButton">
+ <property name="maximumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+S</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>2</width>
+ <height>2</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="KisMultiSensorsSelector" name="sensorSelector" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
- <width>180</width>
- <height>360</height>
+ <width>190</width>
+ <height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>160</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="3">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>2</width>
+ <height>2</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="1">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>2</width>
+ <height>2</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="checkBoxUseSameCurve">
+ <property name="text">
+ <string>Share curve across all settings</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
<item row="0" column="1">
<layout class="QGridLayout" name="gridLayout">
- <item row="2" column="0">
- <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item row="3" column="2">
+ <layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QLabel" name="label_xmin">
+ <widget class="QLabel" name="label_ymax">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string comment="KDE::DoNotExtract">TextLabel</string>
</property>
</widget>
</item>
<item>
- <spacer name="horizontalSpacer">
+ <spacer name="verticalSpacer_3">
<property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
- <height>20</height>
+ <width>10</width>
+ <height>10</height>
</size>
</property>
</spacer>
</item>
<item>
- <widget class="QLabel" name="label_xmax">
+ <widget class="QLabel" name="label_ymin">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string comment="KDE::DoNotExtract">TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
- <item row="1" column="0">
- <widget class="KisCurveWidget" name="curveWidget" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
- <horstretch>1</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>230</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <layout class="QVBoxLayout" name="verticalLayout">
+ <item row="4" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
- <widget class="QLabel" name="label_ymax">
+ <widget class="QLabel" name="label_xmin">
<property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string comment="KDE::DoNotExtract">TextLabel</string>
</property>
</widget>
</item>
<item>
- <spacer name="verticalSpacer_3">
+ <spacer name="horizontalSpacer">
<property name="orientation">
- <enum>Qt::Vertical</enum>
+ <enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>20</width>
- <height>40</height>
+ <width>10</width>
+ <height>10</height>
</size>
</property>
</spacer>
</item>
<item>
- <widget class="QLabel" name="label_ymin">
+ <widget class="QLabel" name="label_xmax">
<property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string comment="KDE::DoNotExtract">TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
- <item row="0" column="0">
- <widget class="QCheckBox" name="checkBoxUseSameCurve">
- <property name="text">
- <string>Share curve across all settings</string>
+ <item row="3" column="1">
+ <widget class="KisCurveWidget" name="curveWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="checked">
- <bool>true</bool>
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>200</height>
+ </size>
</property>
</widget>
</item>
</layout>
</item>
- <item row="0" column="3">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
<item row="2" column="1">
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="1">
- <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
- <widget class="QPushButton" name="linearCurveButton">
- <property name="text">
- <string/>
- </property>
- <property name="iconSize">
- <size>
- <width>16</width>
- <height>16</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="revLinearButton">
- <property name="text">
- <string/>
- </property>
- <property name="iconSize">
- <size>
- <width>16</width>
- <height>16</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="sCurveButton">
- <property name="text">
- <string/>
- </property>
- <property name="iconSize">
- <size>
- <width>16</width>
- <height>16</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="reverseSCurveButton">
- <property name="text">
- <string/>
- </property>
- <property name="iconSize">
- <size>
- <width>16</width>
- <height>16</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="uCurveButton">
- <property name="text">
- <string/>
- </property>
- <property name="iconSize">
- <size>
- <width>16</width>
- <height>16</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="revUCurveButton">
- <property name="text">
- <string/>
- </property>
- <property name="iconSize">
- <size>
- <width>16</width>
- <height>16</height>
- </size>
+ <widget class="QLabel" name="label">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;
+&lt;p&gt;Curve calculation mode changes how 2 or more curve works together&lt;br/&gt;&lt;/p&gt;
+&lt;p&gt; multiply (default): all values from curves multiplies &lt;/p&gt;&lt;p&gt;(0.8 pressure) * (0.5 speed) = 0.4 &lt;br/&gt;&lt;/p&gt;
+&lt;p&gt; addition: all values from curves adds&lt;/p&gt;&lt;p&gt;(0.6 pressure) + (0.3 speed) = 0.9&lt;br/&gt;&lt;/p&gt;
+&lt;p&gt; maximum value&lt;/p&gt;&lt;p&gt;(0.7 pressure), (0.3 speed) = 0.7&lt;br/&gt;&lt;/p&gt;
+&lt;p&gt; minimum value &lt;/p&gt;&lt;p&gt;(0.7 pressure), (0.3 speed) = 0.3&lt;br/&gt;&lt;/p&gt;
+&lt;p&gt; difference between min and max values&lt;/p&gt;&lt;p&gt;(0.8 pressure), (0.3 speed), (0.6 fade) = 0.5&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="jCurveButton">
<property name="text">
- <string/>
- </property>
- <property name="iconSize">
- <size>
- <width>16</width>
- <height>16</height>
- </size>
+ <string>Curves calculation mode:</string>
</property>
</widget>
</item>
<item>
- <widget class="QPushButton" name="lCurveButton">
- <property name="text">
- <string/>
+ <widget class="QComboBox" name="curveMode">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;
+&lt;p&gt;Curve calculation mode changes how 2 or more curve works together&lt;br/&gt;&lt;/p&gt;
+&lt;p&gt; multiply (default): all values from curves multiplies &lt;/p&gt;&lt;p&gt;(0.8 pressure) * (0.5 speed) = 0.4 &lt;br/&gt;&lt;/p&gt;
+&lt;p&gt; addition: all values from curves adds&lt;/p&gt;&lt;p&gt;(0.6 pressure) + (0.3 speed) = 0.9&lt;br/&gt;&lt;/p&gt;
+&lt;p&gt; maximum value&lt;/p&gt;&lt;p&gt;(0.7 pressure), (0.3 speed) = 0.7&lt;br/&gt;&lt;/p&gt;
+&lt;p&gt; minimum value &lt;/p&gt;&lt;p&gt;(0.7 pressure), (0.3 speed) = 0.3&lt;br/&gt;&lt;/p&gt;
+&lt;p&gt; difference between min and max values&lt;/p&gt;&lt;p&gt;(0.8 pressure), (0.3 speed), (0.6 fade) = 0.5&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
- <property name="iconSize">
- <size>
- <width>16</width>
- <height>16</height>
- </size>
- </property>
- <property name="shortcut">
- <string>Ctrl+S</string>
+ <property name="editable">
+ <bool>false</bool>
</property>
+ <item>
+ <property name="text">
+ <string>multiply</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>addition</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>maximum</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>minimum</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>difference</string>
+ </property>
+ </item>
</widget>
</item>
- <item>
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
+ <zorder>curveWidget</zorder>
</widget>
<customwidgets>
<customwidget>
<class>KisDoubleSliderSpinBox</class>
<extends>QWidget</extends>
<header>kis_slider_spin_box.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KisMultiSensorsSelector</class>
<extends>QWidget</extends>
<header>kis_multi_sensors_selector.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KisCurveWidget</class>
<extends>QWidget</extends>
<header location="global">widgets/kis_curve_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui b/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui
index 5a3d8b86bb..6cd5492881 100644
--- a/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui
+++ b/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui
@@ -1,241 +1,256 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgPredefinedBrushChooser</class>
<widget class="QWidget" name="WdgPredefinedBrushChooser">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>646</width>
<height>325</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="presetsLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
- <number>5</number>
+ <number>0</number>
</property>
<property name="topMargin">
- <number>5</number>
+ <number>0</number>
</property>
<property name="rightMargin">
- <number>5</number>
+ <number>0</number>
</property>
<property name="bottomMargin">
- <number>5</number>
+ <number>0</number>
</property>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="addPresetButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Import</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stampButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Stamp</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clipboardButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Clipboard</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteBrushTipButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="brushTipNameLabel">
<property name="font">
<font>
<pointsize>12</pointsize>
<italic>false</italic>
</font>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string>Current Brush Tip</string>
</property>
<property name="margin">
<number>2</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="brushDetailsLabel">
<property name="text">
<string>Brush Details</string>
</property>
<property name="margin">
<number>2</number>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>15</number>
</property>
<property name="verticalSpacing">
<number>10</number>
</property>
<item row="1" column="0">
<widget class="QLabel" name="brushRotationLabel">
<property name="text">
<string>Rotation:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="brushSpacingLabel">
<property name="text">
<string>Spacing:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisDoubleSliderSpinBox" name="brushRotationSpinBox" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="brushSizeLabel">
<property name="text">
<string>Size:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="KisSpacingSelectionWidget" name="brushSpacingSelectionWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisDoubleSliderSpinBox" name="brushSizeSpinBox" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="useColorAsMaskCheckbox">
<property name="text">
<string>Use Color as Mask</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="resetBrushButton">
<property name="text">
<string>Reset Predefined Tip</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisDoubleSliderSpinBox</class>
<extends>QWidget</extends>
<header>kis_slider_spin_box.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KisSpacingSelectionWidget</class>
<extends>QWidget</extends>
<header>kis_spacing_selection_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/paintops/libpaintop/forms/wdgtextbrush.ui b/plugins/paintops/libpaintop/forms/wdgtextbrush.ui
index 58d5e7adc5..9a8e91a6dd 100644
--- a/plugins/paintops/libpaintop/forms/wdgtextbrush.ui
+++ b/plugins/paintops/libpaintop/forms/wdgtextbrush.ui
@@ -1,159 +1,208 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KisWdgTextBrush</class>
<widget class="QWidget" name="KisWdgTextBrush">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>458</width>
- <height>262</height>
+ <width>381</width>
+ <height>182</height>
</rect>
</property>
- <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0,0">
- <item row="0" column="0" rowspan="2">
- <widget class="QLabel" name="labelText">
- <property name="text">
- <string>Text:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="1" rowspan="2" colspan="3">
- <widget class="QLineEdit" name="lineEdit">
- <property name="minimumSize">
- <size>
- <width>120</width>
- <height>0</height>
- </size>
- </property>
- <property name="font">
- <font>
- <weight>50</weight>
- <italic>false</italic>
- <bold>false</bold>
- <underline>false</underline>
- <strikeout>false</strikeout>
- </font>
- </property>
- <property name="text">
- <string>The Quick Brown Fox Jumps Over The Lazy Dog</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Font:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLabel" name="lblFont">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>350</width>
- <height>150</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>350</width>
- <height>150</height>
- </size>
- </property>
- <property name="frameShape">
- <enum>QFrame::StyledPanel</enum>
- </property>
- <property name="text">
- <string>--</string>
- </property>
- <property name="scaledContents">
- <bool>true</bool>
- </property>
- </widget>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Font:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="margin">
+ <number>10</number>
+ </property>
+ <property name="indent">
+ <number>5</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="lblFont">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>180</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>16777</width>
+ <height>40</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::Box</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="text">
+ <string>--</string>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="indent">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="bnFont">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="shortcut">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
- <item row="5" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Spacing:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>10</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelText">
+ <property name="text">
+ <string>Text:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="lineEdit">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>50</weight>
+ <italic>false</italic>
+ <bold>false</bold>
+ <underline>false</underline>
+ <strikeout>false</strikeout>
+ </font>
+ </property>
+ <property name="text">
+ <string>The Quick Brown Fox Jumps Over The Lazy Dog</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Spacing:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="KisDoubleSliderSpinBox" name="inputSpacing" native="true"/>
+ </item>
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="pipeModeChbox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>use only one letter at a time</string>
+ </property>
+ <property name="text">
+ <string>Pipe mode</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
- <item row="6" column="1" colspan="2">
+ <item>
<spacer name="spacer2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>20</width>
- <height>31</height>
+ <width>30</width>
+ <height>21</height>
</size>
</property>
</spacer>
</item>
- <item row="2" column="2">
- <widget class="QToolButton" name="bnFont">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>...</string>
- </property>
- <property name="shortcut">
- <string/>
- </property>
- </widget>
- </item>
- <item row="5" column="1" colspan="2">
- <widget class="KisDoubleSliderSpinBox" name="inputSpacing"/>
- </item>
- <item row="3" column="1">
- <widget class="QCheckBox" name="pipeModeChbox">
- <property name="toolTip">
- <string>use only one letter at a time</string>
- </property>
- <property name="text">
- <string>Pipe mode</string>
- </property>
- </widget>
- </item>
</layout>
+ <zorder>horizontalLayoutWidget</zorder>
+ <zorder>horizontalLayoutWidget</zorder>
+ <zorder>spacer2</zorder>
+ <zorder>gridLayoutWidget</zorder>
</widget>
<customwidgets>
- <customwidget>
- <class>QLineEdit</class>
- <extends>QLineEdit</extends>
- <header>klineedit.h</header>
- </customwidget>
<customwidget>
<class>KisDoubleSliderSpinBox</class>
<extends>QWidget</extends>
<header>kis_slider_spin_box.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/paintops/libpaintop/forms/wdgtexturechooser.ui b/plugins/paintops/libpaintop/forms/wdgtexturechooser.ui
new file mode 100644
index 0000000000..1540c4272d
--- /dev/null
+++ b/plugins/paintops/libpaintop/forms/wdgtexturechooser.ui
@@ -0,0 +1,341 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>KisWdgTextureChooser</class>
+ <widget class="QWidget" name="KisWdgTextureChooser">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>548</width>
+ <height>391</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="textureTab">
+ <attribute name="title">
+ <string>Texture</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="KisPatternChooser" name="textureSelectorWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="textureOptionsTab">
+ <attribute name="title">
+ <string>Options</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="3" column="0">
+ <widget class="QLabel" name="lblAngle">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Contrast:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="KisDoubleSliderSpinBox" name="brightnessSlider" native="true"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="KisMultipliersDoubleSliderSpinBox" name="scaleSlider" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="lblSpacing_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Cutoff</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="lblRatio">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Brightness:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="KisDoubleSliderSpinBox" name="contrastSlider" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="lblSpacing">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Cutoff Policy:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblDensity">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Texturing Mode:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QComboBox" name="cmbCutoffPolicy">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cmbTexturingMode">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblDiameter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Scale:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="KisGradientSlider" name="cutoffSlider" native="true">
+ <property name="text" stdset="0">
+ <string>PushButton</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblSpikes">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Horizontal Offset:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="KisSliderSpinBox" name="offsetSliderY" native="true"/>
+ </item>
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="chkInvert">
+ <property name="toolTip">
+ <string>The border of the brush will be smoothed to avoid aliasing</string>
+ </property>
+ <property name="text">
+ <string>Invert Pattern</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="KisSliderSpinBox" name="offsetSliderX" native="true"/>
+ </item>
+ <item row="1" column="2">
+ <widget class="QCheckBox" name="randomOffsetY">
+ <property name="text">
+ <string>Random Offset</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblRandomness">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Vertical Offset:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QCheckBox" name="randomOffsetX">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Random Offset</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>KisDoubleSliderSpinBox</class>
+ <extends>QWidget</extends>
+ <header>kis_slider_spin_box.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>KisSliderSpinBox</class>
+ <extends>QWidget</extends>
+ <header location="global">kis_slider_spin_box.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>KisPatternChooser</class>
+ <extends>QFrame</extends>
+ <header location="global">kis_pattern_chooser.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>KisGradientSlider</class>
+ <extends>QWidget</extends>
+ <header>kis_gradient_slider.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>KisMultipliersDoubleSliderSpinBox</class>
+ <extends>QWidget</extends>
+ <header>kis_multipliers_double_slider_spinbox.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/paintops/libpaintop/kis_airbrush_option.cpp b/plugins/paintops/libpaintop/kis_airbrush_option.cpp
index 96e527a757..496c5c55ea 100644
--- a/plugins/paintops/libpaintop/kis_airbrush_option.cpp
+++ b/plugins/paintops/libpaintop/kis_airbrush_option.cpp
@@ -1,139 +1,139 @@
/*
* Copyright (c) 2008 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_airbrush_option.h"
#include "kis_paintop_settings.h"
#include <klocalizedstring.h>
#include <QWidget>
#include <QRadioButton>
#include "ui_wdgairbrush.h"
const qreal MINIMUM_RATE = 1.0;
const qreal MAXIMUM_RATE = 1000.0;
const int RATE_NUM_DECIMALS = 2;
const qreal RATE_EXPONENT_RATIO = 2.0;
const qreal RATE_SINGLE_STEP = 1.0;
const qreal DEFAULT_RATE = 20.0;
class KisAirbrushWidget: public QWidget, public Ui::WdgAirbrush
{
public:
KisAirbrushWidget(QWidget *parent = 0, bool canIgnoreSpacing = true)
: QWidget(parent) {
setupUi(this);
sliderRate->setRange(MINIMUM_RATE, MAXIMUM_RATE, RATE_NUM_DECIMALS);
sliderRate->setExponentRatio(RATE_EXPONENT_RATIO);
sliderRate->setSingleStep(RATE_SINGLE_STEP);
sliderRate->setValue(DEFAULT_RATE);
checkBoxIgnoreSpacing->setVisible(canIgnoreSpacing);
checkBoxIgnoreSpacing->setEnabled(canIgnoreSpacing);
}
};
struct KisAirbrushOption::Private {
public:
bool ignoreSpacing;
// We store the airbrush interval (in milliseconds) instead of the rate because the interval is
// likely to be accessed more often.
qreal airbrushInterval;
- KisAirbrushWidget *configPage;
+ QScopedPointer<KisAirbrushWidget> configPage;
};
KisAirbrushOption::KisAirbrushOption(bool enabled, bool canIgnoreSpacing)
: KisPaintOpOption(KisPaintOpOption::COLOR, enabled)
, m_d(new Private())
{
setObjectName("KisAirbrushOption");
// Initialize GUI.
m_checkable = true;
- m_d->configPage = new KisAirbrushWidget(nullptr, canIgnoreSpacing);
+ m_d->configPage.reset(new KisAirbrushWidget(nullptr, canIgnoreSpacing));
connect(m_d->configPage->sliderRate, SIGNAL(valueChanged(qreal)), SLOT(slotIntervalChanged()));
connect(m_d->configPage->checkBoxIgnoreSpacing, SIGNAL(toggled(bool)),
SLOT(slotIgnoreSpacingChanged()));
- setConfigurationPage(m_d->configPage);
+ setConfigurationPage(m_d->configPage.data());
// Read initial configuration from the GUI.
updateIgnoreSpacing();
updateInterval();
}
KisAirbrushOption::~KisAirbrushOption()
{
delete m_d;
}
void KisAirbrushOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const
{
KIS_SAFE_ASSERT_RECOVER (m_d->airbrushInterval > 0.0) {
m_d->airbrushInterval = 1.0;
}
setting->setProperty(AIRBRUSH_ENABLED, isChecked());
setting->setProperty(AIRBRUSH_RATE, 1000.0 / m_d->airbrushInterval);
setting->setProperty(AIRBRUSH_IGNORE_SPACING, m_d->ignoreSpacing);
}
void KisAirbrushOption::readOptionSetting(const KisPropertiesConfigurationSP setting)
{
setChecked(setting->getBool(AIRBRUSH_ENABLED));
// Update settings in the widget. The widget's signals should cause the changes to be propagated
// to this->m_d as well.
m_d->configPage->sliderRate->setValue(setting->getDouble(AIRBRUSH_RATE, DEFAULT_RATE));
m_d->configPage->checkBoxIgnoreSpacing->setChecked(setting->getBool(AIRBRUSH_IGNORE_SPACING,
false));
}
qreal KisAirbrushOption::airbrushInterval() const
{
return m_d->airbrushInterval;
}
bool KisAirbrushOption::ignoreSpacing() const
{
return m_d->ignoreSpacing;
}
void KisAirbrushOption::slotIntervalChanged()
{
updateInterval();
emitSettingChanged();
}
void KisAirbrushOption::slotIgnoreSpacingChanged()
{
updateIgnoreSpacing();
emitSettingChanged();
}
void KisAirbrushOption::updateInterval()
{
// Get rate in dabs per second, then convert to interval in milliseconds.
qreal rate = m_d->configPage->sliderRate->value();
KIS_SAFE_ASSERT_RECOVER(rate > 0.0) {
rate = 1.0;
}
m_d->airbrushInterval = 1000.0 / rate;
}
void KisAirbrushOption::updateIgnoreSpacing()
{
m_d->ignoreSpacing = m_d->configPage->checkBoxIgnoreSpacing->isChecked();
}
diff --git a/plugins/paintops/libpaintop/kis_bidirectional_mixing_option.cpp b/plugins/paintops/libpaintop/kis_bidirectional_mixing_option.cpp
index 80afa13347..10d2b7aef7 100644
--- a/plugins/paintops/libpaintop/kis_bidirectional_mixing_option.cpp
+++ b/plugins/paintops/libpaintop/kis_bidirectional_mixing_option.cpp
@@ -1,131 +1,131 @@
/*
* Copyright (c) 2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2008 Emanuele Tamponi <emanuele@valinor.it>
*
* 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_bidirectional_mixing_option.h"
#include <klocalizedstring.h>
#include <QLabel>
#include <QVector>
#include <QRect>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include <KoColor.h>
#include <kis_painter.h>
#include <kis_fixed_paint_device.h>
#include <kis_paint_device.h>
#include <kis_properties_configuration.h>
#include "kis_bidirectional_mixing_option_widget.h"
#include <kis_iterator_ng.h>
KisBidirectionalMixingOption::KisBidirectionalMixingOption()
: m_mixingEnabled(false)
{
}
KisBidirectionalMixingOption::~KisBidirectionalMixingOption()
{
}
void KisBidirectionalMixingOption::apply(KisPaintDeviceSP dab, KisPaintDeviceSP device, KisPainter* painter, qint32 sx, qint32 sy, qint32 sw, qint32 sh, quint8 pressure, const QRect& dstRect)
{
if (!m_mixingEnabled) return;
const KoColorSpace *cs = dab->colorSpace();
KisPaintDeviceSP canvas = new KisPaintDevice(cs);
KisPainter p(canvas);
p.setCompositeOp(COMPOSITE_COPY);
p.bitBlt(sx, sy, device, dstRect.x(), dstRect.y(), sw, sh);
int count = cs->channelCount();
QRect srcRect(sx, sy, sw, sh);
KisSequentialConstIterator cit(canvas, srcRect);
KisSequentialIterator dit(dab, srcRect);
QVector<float> cc(count), dc(count);
do {
if (cs->opacityU8(dit.rawData()) > 10 && cs->opacityU8(cit.rawDataConst()) > 10) {
cs->normalisedChannelsValue(cit.rawDataConst(), cc);
cs->normalisedChannelsValue(dit.rawData(), dc);
for (int i = 0; i < count; i++) {
dc[i] = (1.0 - 0.4 * pressure) * cc[i] + 0.4 * pressure * dc[i];
}
cs->fromNormalisedChannelsValue(dit.rawData(), dc);
if (dit.x() == (int)(sw / 2) && dit.y() == (int)(sh / 2)) {
painter->setPaintColor(KoColor(dit.rawData(), cs));
}
}
dit.nextPixel();
} while(cit.nextPixel());
}
void KisBidirectionalMixingOption::applyFixed(KisFixedPaintDeviceSP dab, KisPaintDeviceSP device, KisPainter* painter, qint32 sx, qint32 sy, qint32 sw, qint32 sh, quint8 pressure, const QRect& dstRect)
{
Q_UNUSED(sx);
Q_UNUSED(sy);
if (!m_mixingEnabled) return;
KisFixedPaintDevice canvas(device->colorSpace());
canvas.setRect(QRect(dstRect.x(), dstRect.y(), sw, sh));
- canvas.initialize();
+ canvas.lazyGrowBufferWithoutInitialization();
device->readBytes(canvas.data(), canvas.bounds());
const KoColorSpace* cs = dab->colorSpace();
int channelCount = cs->channelCount();
quint8* dabPointer = dab->data();
quint8* canvasPointer = canvas.data();
QVector<float> cc(channelCount);
QVector<float> dc(channelCount);
for (int y = 0; y < sh; y++) {
for (int x = 0; x < sw; x++) {
if (cs->opacityU8(dabPointer) > 10 && cs->opacityU8(canvasPointer) > 10) {
cs->normalisedChannelsValue(canvasPointer, cc);
cs->normalisedChannelsValue(dabPointer, dc);
for (int i = 0; i < channelCount ; i++) {
dc[i] = (1.0 - 0.4 * pressure) * cc[i] + 0.4 * pressure * dc[i];
}
cs->fromNormalisedChannelsValue(dabPointer, dc);
if (x == (int)(sw / 2) && y == (int)(sh / 2)) {
painter->setPaintColor(KoColor(dabPointer, cs));
}
}
}
dabPointer += dab->pixelSize();
canvasPointer += canvas.pixelSize();
}
}
void KisBidirectionalMixingOption::readOptionSetting(const KisPropertiesConfigurationSP setting)
{
m_mixingEnabled = setting->getBool(BIDIRECTIONAL_MIXING_ENABLED, false);
}
diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop.h b/plugins/paintops/libpaintop/kis_brush_based_paintop.h
index 305fbf3ecd..3d0ee64c48 100644
--- a/plugins/paintops/libpaintop/kis_brush_based_paintop.h
+++ b/plugins/paintops/libpaintop/kis_brush_based_paintop.h
@@ -1,101 +1,103 @@
/*
* Copyright (c) 2008 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_BRUSH_BASED_PAINTOP_H
#define KIS_BRUSH_BASED_PAINTOP_H
#include "kritapaintop_export.h"
#include <brushengine/kis_paintop.h>
#include "kis_dab_cache.h"
#include "kis_brush.h"
#include "kis_texture_option.h"
#include "kis_precision_option.h"
#include "kis_airbrush_option.h"
#include "kis_pressure_mirror_option.h"
#include <kis_threaded_text_rendering_workaround.h>
class KisPropertiesConfiguration;
class KisPressureSpacingOption;
class KisPressureRateOption;
class KisDabCache;
/// Internal
class TextBrushInitializationWorkaround
{
public:
TextBrushInitializationWorkaround();
~TextBrushInitializationWorkaround();
static TextBrushInitializationWorkaround* instance();
void preinitialize(KisPropertiesConfigurationSP settings);
KisBrushSP tryGetBrush(const KisPropertiesConfigurationSP settings);
private:
KisBrushSP m_brush;
KisPropertiesConfigurationSP m_settings;
};
/**
* This is a base class for paintops that use a KisBrush or derived
* brush to paint with. This is mainly important for the spacing
* generation.
*/
class PAINTOP_EXPORT KisBrushBasedPaintOp : public KisPaintOp
{
public:
KisBrushBasedPaintOp(const KisPropertiesConfigurationSP settings, KisPainter* painter);
~KisBrushBasedPaintOp() override;
bool checkSizeTooSmall(qreal scale);
KisSpacingInformation effectiveSpacing(qreal scale) const;
KisSpacingInformation effectiveSpacing(qreal scale, qreal rotation, const KisPaintInformation &pi) const;
KisSpacingInformation effectiveSpacing(qreal scale, qreal rotation, const KisPressureSpacingOption &spacingOption, const KisPaintInformation &pi) const;
KisSpacingInformation effectiveSpacing(qreal scale,
qreal rotation,
const KisAirbrushOption *airbrushOption,
const KisPressureSpacingOption *spacingOption,
const KisPaintInformation &pi) const;
///Reimplemented, false if brush is 0
bool canPaint() const override;
#ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
typedef int needs_preinitialization;
static void preinitializeOpStatically(KisPaintOpSettingsSP settings);
#endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
private:
KisSpacingInformation effectiveSpacing(qreal dabWidth, qreal dabHeight, qreal extraScale, bool isotropicSpacing, qreal rotation, bool axesFlipped) const;
protected: // XXX: make private!
KisDabCache *m_dabCache;
KisBrushSP m_brush;
private:
KisTextureProperties m_textureProperties;
+
+protected:
KisPressureMirrorOption m_mirrorOption;
KisPrecisionOption m_precisionOption;
};
#endif
diff --git a/plugins/paintops/libpaintop/kis_curve_option.cpp b/plugins/paintops/libpaintop/kis_curve_option.cpp
index 97d5e68929..de28f62d07 100644
--- a/plugins/paintops/libpaintop/kis_curve_option.cpp
+++ b/plugins/paintops/libpaintop/kis_curve_option.cpp
@@ -1,390 +1,434 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_curve_option.h"
#include <QDomNode>
KisCurveOption::KisCurveOption(const QString& name, KisPaintOpOption::PaintopCategory category,
bool checked, qreal value, qreal min, qreal max)
: m_name(name)
, m_category(category)
, m_checkable(true)
, m_checked(checked)
, m_useCurve(true)
, m_useSameCurve(true)
, m_separateCurveValue(false)
+ , m_curveMode(0)
{
Q_FOREACH (const DynamicSensorType sensorType, KisDynamicSensor::sensorsTypes()) {
- KisDynamicSensorSP sensor = KisDynamicSensor::type2Sensor(sensorType);
+ KisDynamicSensorSP sensor = KisDynamicSensor::type2Sensor(sensorType, m_name);
sensor->setActive(false);
replaceSensor(sensor);
}
m_sensorMap[PRESSURE]->setActive(true);
setValueRange(min, max);
setValue(value);
}
KisCurveOption::~KisCurveOption()
{
}
const QString& KisCurveOption::name() const
{
return m_name;
}
KisPaintOpOption::PaintopCategory KisCurveOption::category() const
{
return m_category;
}
qreal KisCurveOption::minValue() const
{
return m_minValue;
}
qreal KisCurveOption::maxValue() const
{
return m_maxValue;
}
qreal KisCurveOption::value() const
{
return m_value;
}
void KisCurveOption::resetAllSensors()
{
Q_FOREACH (KisDynamicSensorSP sensor, m_sensorMap.values()) {
if (sensor->isActive()) {
sensor->reset();
}
}
}
void KisCurveOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const
{
if (m_checkable) {
setting->setProperty("Pressure" + m_name, isChecked());
}
if (activeSensors().size() == 1) {
setting->setProperty(m_name + "Sensor", activeSensors().first()->toXML());
}
else {
QDomDocument doc = QDomDocument("params");
QDomElement root = doc.createElement("params");
doc.appendChild(root);
root.setAttribute("id", "sensorslist");
Q_FOREACH (KisDynamicSensorSP sensor, activeSensors()) {
QDomElement childelt = doc.createElement("ChildSensor");
sensor->toXML(doc, childelt);
root.appendChild(childelt);
}
setting->setProperty(m_name + "Sensor", doc.toString());
}
setting->setProperty(m_name + "UseCurve", m_useCurve);
setting->setProperty(m_name + "UseSameCurve", m_useSameCurve);
setting->setProperty(m_name + "Value", m_value);
+ setting->setProperty(m_name + "curveMode", m_curveMode);
}
void KisCurveOption::readOptionSetting(KisPropertiesConfigurationSP setting)
{
m_curveCache.clear();
readNamedOptionSetting(m_name, setting);
}
void KisCurveOption::lodLimitations(KisPaintopLodLimitations *l) const
{
Q_UNUSED(l);
}
void KisCurveOption::readNamedOptionSetting(const QString& prefix, const KisPropertiesConfigurationSP setting)
{
if (!setting) return;
//dbgKrita << "readNamedOptionSetting" << prefix;
setting->dump();
if (m_checkable) {
setChecked(setting->getBool("Pressure" + prefix, false));
}
//dbgKrita << "\tPressure" + prefix << isChecked();
m_sensorMap.clear();
// Replace all sensors with the inactive defaults
Q_FOREACH (const DynamicSensorType sensorType, KisDynamicSensor::sensorsTypes()) {
- replaceSensor(KisDynamicSensor::type2Sensor(sensorType));
+ replaceSensor(KisDynamicSensor::type2Sensor(sensorType, m_name));
}
QString sensorDefinition = setting->getString(prefix + "Sensor");
if (!sensorDefinition.contains("sensorslist")) {
- KisDynamicSensorSP s = KisDynamicSensor::createFromXML(sensorDefinition);
+ KisDynamicSensorSP s = KisDynamicSensor::createFromXML(sensorDefinition, m_name);
if (s) {
replaceSensor(s);
s->setActive(true);
//dbgKrita << "\tsingle sensor" << s::id(s->sensorType()) << s->isActive() << "added";
}
}
else {
QDomDocument doc;
doc.setContent(sensorDefinition);
QDomElement elt = doc.documentElement();
QDomNode node = elt.firstChild();
while (!node.isNull()) {
if (node.isElement()) {
QDomElement childelt = node.toElement();
if (childelt.tagName() == "ChildSensor") {
- KisDynamicSensorSP s = KisDynamicSensor::createFromXML(childelt);
+ KisDynamicSensorSP s = KisDynamicSensor::createFromXML(childelt, m_name);
if (s) {
replaceSensor(s);
s->setActive(true);
//dbgKrita << "\tchild sensor" << s::id(s->sensorType()) << s->isActive() << "added";
}
}
}
node = node.nextSibling();
}
}
// Only load the old curve format if the curve wasn't saved by the sensor
// This will give every sensor the same curve.
//dbgKrita << ">>>>>>>>>>>" << prefix + "Sensor" << setting->getString(prefix + "Sensor");
if (!setting->getString(prefix + "Sensor").contains("curve")) {
//dbgKrita << "\told format";
if (setting->getBool("Custom" + prefix, false)) {
Q_FOREACH (KisDynamicSensorSP s, m_sensorMap.values()) {
s->setCurve(setting->getCubicCurve("Curve" + prefix));
}
}
}
// At least one sensor needs to be active
if (activeSensors().size() == 0) {
m_sensorMap[PRESSURE]->setActive(true);
}
m_value = setting->getDouble(m_name + "Value", m_maxValue);
//dbgKrita << "\t" + m_name + "Value" << m_value;
m_useCurve = setting->getBool(m_name + "UseCurve", true);
//dbgKrita << "\t" + m_name + "UseCurve" << m_useSameCurve;
m_useSameCurve = setting->getBool(m_name + "UseSameCurve", true);
//dbgKrita << "\t" + m_name + "UseSameCurve" << m_useSameCurve;
+ m_curveMode = setting->getInt(m_name + "curveMode");
//dbgKrita << "-----------------";
}
void KisCurveOption::replaceSensor(KisDynamicSensorSP s)
{
Q_ASSERT(s);
m_sensorMap[s->sensorType()] = s;
}
KisDynamicSensorSP KisCurveOption::sensor(DynamicSensorType sensorType, bool active) const
{
if (m_sensorMap.contains(sensorType)) {
if (!active) {
return m_sensorMap[sensorType];
}
else {
if (m_sensorMap[sensorType]->isActive()) {
return m_sensorMap[sensorType];
}
}
}
return 0;
}
bool KisCurveOption::isRandom() const
{
return bool(sensor(FUZZY_PER_DAB, true)) ||
bool(sensor(FUZZY_PER_STROKE, true));
}
bool KisCurveOption::isCurveUsed() const
{
return m_useCurve;
}
bool KisCurveOption::isSameCurveUsed() const
{
return m_useSameCurve;
}
+int KisCurveOption::getCurveMode() const
+{
+ return m_curveMode;
+}
+
void KisCurveOption::setSeparateCurveValue(bool separateCurveValue)
{
m_separateCurveValue = separateCurveValue;
}
bool KisCurveOption::isCheckable()
{
return m_checkable;
}
bool KisCurveOption::isChecked() const
{
return m_checked;
}
void KisCurveOption::setChecked(bool checked)
{
m_checked = checked;
}
void KisCurveOption::setCurveUsed(bool useCurve)
{
m_useCurve = useCurve;
}
+void KisCurveOption::setCurveMode(int mode)
+{
+ m_curveMode = mode;
+}
+
void KisCurveOption::setCurve(DynamicSensorType sensorType, bool useSameCurve, const KisCubicCurve &curve)
{
// No switch in state, don't mess with the cache
if (useSameCurve == m_useSameCurve) {
if (useSameCurve) {
Q_FOREACH (KisDynamicSensorSP s, m_sensorMap.values()) {
s->setCurve(curve);
}
}
else {
KisDynamicSensorSP s = sensor(sensorType, false);
if (s) {
s->setCurve(curve);
}
}
}
else {
// moving from not use same curve to use same curve: backup the custom curves
if (!m_useSameCurve && useSameCurve) {
// Copy the custom curves to the cache and set the new curve on all sensors, active or not
m_curveCache.clear();
Q_FOREACH (KisDynamicSensorSP s, m_sensorMap.values()) {
m_curveCache[s->sensorType()] = s->curve();
s->setCurve(curve);
}
}
else { //if (m_useSameCurve && !useSameCurve)
// Restore the cached curves
KisDynamicSensorSP s = 0;
Q_FOREACH (DynamicSensorType sensorType, m_curveCache.keys()) {
if (m_sensorMap.contains(sensorType)) {
s = m_sensorMap[sensorType];
}
else {
- s = KisDynamicSensor::type2Sensor(sensorType);
+ s = KisDynamicSensor::type2Sensor(sensorType, m_name);
}
s->setCurve(m_curveCache[sensorType]);
m_sensorMap[sensorType] = s;
}
s = 0;
// And set the current sensor to the current curve
if (!m_sensorMap.contains(sensorType)) {
- s = KisDynamicSensor::type2Sensor(sensorType);
+ s = KisDynamicSensor::type2Sensor(sensorType, m_name);
}
if (s) {
s->setCurve(curve);
s->setCurve(m_curveCache[sensorType]);
}
}
m_useSameCurve = useSameCurve;
}
}
void KisCurveOption::setValueRange(qreal min, qreal max)
{
m_minValue = qMin(min, max);
m_maxValue = qMax(min, max);
}
void KisCurveOption::setValue(qreal value)
{
m_value = qBound(m_minValue, value, m_maxValue);
}
KisCurveOption::ValueComponents KisCurveOption::computeValueComponents(const KisPaintInformation& info) const
{
ValueComponents components;
if (m_useCurve) {
QMap<DynamicSensorType, KisDynamicSensorSP>::const_iterator i;
+ QList<double> sensorValues;
for (i = m_sensorMap.constBegin(); i != m_sensorMap.constEnd(); ++i) {
KisDynamicSensorSP s(i.value());
if (s->isActive()) {
if (s->isAdditive()) {
components.additive += s->parameter(info);
components.hasAdditive = true;
} else if (s->isAbsoluteRotation()) {
components.absoluteOffset = s->parameter(info);
components.hasAbsoluteOffset =true;
} else {
- components.scaling *= s->parameter(info);
+ sensorValues << s->parameter(info);
components.hasScaling = true;
}
}
}
+
+ if (sensorValues.count() == 1) {
+ components.scaling = sensorValues.first();
+ } else {
+
+ if (m_curveMode == 1){ // add
+ components.scaling = 0;
+ double i;
+ foreach (i, sensorValues) {
+ components.scaling += i;
+ }
+ } else if (m_curveMode == 2){ //max
+ components.scaling = *std::max_element(sensorValues.begin(), sensorValues.end());
+
+ } else if (m_curveMode == 3){ //min
+ components.scaling = *std::min_element(sensorValues.begin(), sensorValues.end());
+
+ } else if (m_curveMode == 4){ //difference
+ double max = *std::max_element(sensorValues.begin(), sensorValues.end());
+ double min = *std::min_element(sensorValues.begin(), sensorValues.end());
+ components.scaling = max-min;
+
+ } else { //multuply - default
+ double i;
+ foreach (i, sensorValues) {
+ components.scaling *= i;
+ }
+ }
+ }
+
}
if (!m_separateCurveValue) {
components.constant = m_value;
}
components.minSizeLikeValue = m_minValue;
components.maxSizeLikeValue = m_maxValue;
return components;
}
qreal KisCurveOption::computeSizeLikeValue(const KisPaintInformation& info) const
{
const ValueComponents components = computeValueComponents(info);
return components.sizeLikeValue();
}
qreal KisCurveOption::computeRotationLikeValue(const KisPaintInformation& info, qreal baseValue, bool absoluteAxesFlipped) const
{
const ValueComponents components = computeValueComponents(info);
return components.rotationLikeValue(baseValue, absoluteAxesFlipped);
}
QList<KisDynamicSensorSP> KisCurveOption::sensors()
{
//dbgKrita << "ID" << name() << "has" << m_sensorMap.count() << "Sensors of which" << sensorList.count() << "are active.";
return m_sensorMap.values();
}
QList<KisDynamicSensorSP> KisCurveOption::activeSensors() const
{
QList<KisDynamicSensorSP> sensorList;
Q_FOREACH (KisDynamicSensorSP sensor, m_sensorMap.values()) {
if (sensor->isActive()) {
sensorList << sensor;
}
}
//dbgKrita << "ID" << name() << "has" << m_sensorMap.count() << "Sensors of which" << sensorList.count() << "are active.";
return sensorList;
}
diff --git a/plugins/paintops/libpaintop/kis_curve_option.h b/plugins/paintops/libpaintop/kis_curve_option.h
index d73837d991..d173ba7597 100644
--- a/plugins/paintops/libpaintop/kis_curve_option.h
+++ b/plugins/paintops/libpaintop/kis_curve_option.h
@@ -1,199 +1,204 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CURVE_OPTION_H
#define KIS_CURVE_OPTION_H
#include <QObject>
#include <QVector>
#include "kis_paintop_option.h"
#include "kis_global.h"
#include "kis_paintop_option.h"
#include <brushengine/kis_paint_information.h>
#include "kritapaintop_export.h"
#include "kis_dynamic_sensor.h"
class KisDynamicSensor;
/**
* KisCurveOption is the base class for paintop options that are
* defined through one or more curves.
*
* Note: it is NOT a KisPaintOpOption, even though the API is pretty similar!
*
*/
class PAINTOP_EXPORT KisCurveOption
{
public:
KisCurveOption(const QString& name,
KisPaintOpOption::PaintopCategory category,
bool checked,
qreal value = 1.0,
qreal min = 0.0,
qreal max = 1.0);
virtual ~KisCurveOption();
virtual void writeOptionSetting(KisPropertiesConfigurationSP setting) const;
virtual void readOptionSetting(KisPropertiesConfigurationSP setting);
virtual void lodLimitations(KisPaintopLodLimitations *l) const;
const QString& name() const;
KisPaintOpOption::PaintopCategory category() const;
qreal minValue() const;
qreal maxValue() const;
qreal value() const;
void resetAllSensors();
KisDynamicSensorSP sensor(DynamicSensorType sensorType, bool active) const;
void replaceSensor(KisDynamicSensorSP sensor);
QList<KisDynamicSensorSP> sensors();
QList<KisDynamicSensorSP> activeSensors() const;
bool isCheckable();
bool isChecked() const;
bool isCurveUsed() const;
bool isSameCurveUsed() const;
bool isRandom() const;
+ int getCurveMode() const;
+
void setSeparateCurveValue(bool separateCurveValue);
void setChecked(bool checked);
void setCurveUsed(bool useCurve);
void setCurve(DynamicSensorType sensorType, bool useSameCurve, const KisCubicCurve &curve);
void setValue(qreal value);
+ void setCurveMode(int mode);
struct ValueComponents {
ValueComponents()
: constant(1.0),
scaling(1.0),
additive(0.0),
absoluteOffset(0.0),
hasAbsoluteOffset(false),
hasScaling(false),
hasAdditive(false)
{
}
qreal constant;
qreal scaling;
qreal additive;
qreal absoluteOffset;
bool hasAbsoluteOffset;
bool hasScaling;
bool hasAdditive;
qreal minSizeLikeValue;
qreal maxSizeLikeValue;
/**
* @param normalizedBaseAngle canvas rotation alngle normalized to range [0; 1]
* @param absoluteAxesFlipped true if underlying image coordinate system is flipped (horiz. mirror != vert. mirror)
*/
qreal rotationLikeValue(qreal normalizedBaseAngle, bool absoluteAxesFlipped) const {
const qreal offset =
!hasAbsoluteOffset ? normalizedBaseAngle :
absoluteAxesFlipped ? 1.0 - absoluteOffset :
absoluteOffset;
const qreal realScalingPart = hasScaling ? KisDynamicSensor::scalingToAdditive(scaling) : 0.0;
const qreal realAdditivePart = hasAdditive ? additive : 0;
qreal value = wrapInRange(2 * offset + constant * realScalingPart + realAdditivePart, -1.0, 1.0);
if (qIsNaN(value)) {
qWarning() << "rotationLikeValue returns NaN!" << normalizedBaseAngle << absoluteAxesFlipped;
value = 0;
}
return value;
}
qreal sizeLikeValue() const {
const qreal offset =
hasAbsoluteOffset ? absoluteOffset : 1.0;
const qreal realScalingPart = hasScaling ? scaling : 1.0;
const qreal realAdditivePart = hasAdditive ? KisDynamicSensor::additiveToScaling(additive) : 1.0;
return qBound(minSizeLikeValue,
constant * offset * realScalingPart * realAdditivePart,
maxSizeLikeValue);
}
private:
static inline qreal wrapInRange(qreal x, qreal min, qreal max) {
const qreal range = max - min;
x -= min;
if (x < 0.0) {
x = range + fmod(x, range);
}
if (x > range) {
x = fmod(x, range);
}
return x + min;
}
};
/**
* Uses the curves set on the sensors to compute a single
* double value that can control the parameters of a brush.
*
* This value is derives from the falues stored in
* ValuesComponents opject.
*/
ValueComponents computeValueComponents(const KisPaintInformation& info) const;
qreal computeSizeLikeValue(const KisPaintInformation &info) const;
qreal computeRotationLikeValue(const KisPaintInformation& info, qreal baseValue, bool absoluteAxesFlipped) const;
protected:
void setValueRange(qreal min, qreal max);
/**
* Read the option using the prefix in argument
*/
void readNamedOptionSetting(const QString& prefix, const KisPropertiesConfigurationSP setting);
QString m_name;
KisPaintOpOption::PaintopCategory m_category;
bool m_checkable;
bool m_checked;
bool m_useCurve;
bool m_useSameCurve;
bool m_separateCurveValue;
+ int m_curveMode;
+
QMap<DynamicSensorType, KisDynamicSensorSP> m_sensorMap;
QMap<DynamicSensorType, KisCubicCurve> m_curveCache;
private:
qreal m_value;
qreal m_minValue;
qreal m_maxValue;
};
#endif
diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp b/plugins/paintops/libpaintop/kis_curve_option_widget.cpp
index 640ca9fada..0b6e2e5824 100644
--- a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp
+++ b/plugins/paintops/libpaintop/kis_curve_option_widget.cpp
@@ -1,293 +1,301 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2009 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_curve_option_widget.h"
#include "ui_wdgcurveoption.h"
#include "widgets/kis_curve_widget.h"
#include "kis_dynamic_sensor.h"
#include "kis_global.h"
#include "kis_curve_option.h"
#include "kis_signals_blocker.h"
#include "kis_icon_utils.h"
inline void setLabel(QLabel* label, const KisCurveLabel& curve_label)
{
if (curve_label.icon().isNull()) {
label->setText(curve_label.name());
}
else {
label->setPixmap(QPixmap::fromImage(curve_label.icon()));
}
}
KisCurveOptionWidget::KisCurveOptionWidget(KisCurveOption* curveOption, const QString &minLabel, const QString &maxLabel, bool hideSlider)
: KisPaintOpOption(curveOption->category(), curveOption->isChecked())
, m_widget(new QWidget)
, m_curveOptionWidget(new Ui_WdgCurveOption())
, m_curveOption(curveOption)
{
setObjectName("KisCurveOptionWidget");
m_curveOptionWidget->setupUi(m_widget);
setConfigurationPage(m_widget);
m_curveOptionWidget->sensorSelector->setCurveOption(curveOption);
updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted());
updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted());
connect(m_curveOptionWidget->curveWidget, SIGNAL(modified()), this, SLOT(transferCurve()));
connect(m_curveOptionWidget->sensorSelector, SIGNAL(parametersChanged()), SLOT(emitSettingChanged()));
connect(m_curveOptionWidget->sensorSelector, SIGNAL(parametersChanged()), SLOT(updateLabelsOfCurrentSensor()));
connect(m_curveOptionWidget->sensorSelector, SIGNAL(highlightedSensorChanged(KisDynamicSensorSP )), SLOT(updateSensorCurveLabels(KisDynamicSensorSP )));
connect(m_curveOptionWidget->sensorSelector, SIGNAL(highlightedSensorChanged(KisDynamicSensorSP )), SLOT(updateCurve(KisDynamicSensorSP )));
connect(m_curveOptionWidget->checkBoxUseSameCurve, SIGNAL(stateChanged(int)), SLOT(transferCurve()));
// set all the icons for the curve preset shapes
updateThemedIcons();
// various curve preset buttons with predefined curves
connect(m_curveOptionWidget->linearCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveLinear()));
connect(m_curveOptionWidget->revLinearButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveReverseLinear()));
connect(m_curveOptionWidget->jCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveJShape()));
connect(m_curveOptionWidget->lCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveLShape()));
connect(m_curveOptionWidget->sCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveSShape()));
connect(m_curveOptionWidget->reverseSCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveReverseSShape()));
connect(m_curveOptionWidget->uCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveUShape()));
connect(m_curveOptionWidget->revUCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveArchShape()));
m_curveOptionWidget->label_ymin->setText(minLabel);
m_curveOptionWidget->label_ymax->setText(maxLabel);
m_curveOptionWidget->slider->setRange(curveOption->minValue(), curveOption->maxValue(), 2);
m_curveOptionWidget->slider->setValue(curveOption->value());
if (hideSlider) {
m_curveOptionWidget->slider->hide();
m_curveOptionWidget->strengthLabel->hide();
}
connect(m_curveOptionWidget->checkBoxUseCurve, SIGNAL(stateChanged(int)) , SLOT(updateValues()));
+ connect(m_curveOptionWidget->curveMode, SIGNAL(currentIndexChanged(int)), SLOT(updateMode()));
connect(m_curveOptionWidget->slider, SIGNAL(valueChanged(qreal)), SLOT(updateValues()));
}
KisCurveOptionWidget::~KisCurveOptionWidget()
{
delete m_curveOption;
delete m_curveOptionWidget;
}
void KisCurveOptionWidget::writeOptionSetting(KisPropertiesConfigurationSP setting) const
{
m_curveOption->writeOptionSetting(setting);
}
void KisCurveOptionWidget::readOptionSetting(const KisPropertiesConfigurationSP setting)
{
setting->dump();
m_curveOption->readOptionSetting(setting);
m_curveOptionWidget->checkBoxUseCurve->setChecked(m_curveOption->isCurveUsed());
m_curveOptionWidget->slider->setValue(m_curveOption->value());
m_curveOptionWidget->checkBoxUseSameCurve->setChecked(m_curveOption->isSameCurveUsed());
+ m_curveOptionWidget->curveMode->setCurrentIndex(m_curveOption->getCurveMode());
disableWidgets(!m_curveOption->isCurveUsed());
m_curveOptionWidget->sensorSelector->reload();
m_curveOptionWidget->sensorSelector->setCurrent(m_curveOption->activeSensors().first());
updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted());
updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted());
}
void KisCurveOptionWidget::lodLimitations(KisPaintopLodLimitations *l) const
{
m_curveOption->lodLimitations(l);
}
bool KisCurveOptionWidget::isCheckable() const
{
return m_curveOption->isCheckable();
}
bool KisCurveOptionWidget::isChecked() const
{
return m_curveOption->isChecked();
}
void KisCurveOptionWidget::setChecked(bool checked)
{
m_curveOption->setChecked(checked);
}
KisCurveOption* KisCurveOptionWidget::curveOption()
{
return m_curveOption;
}
QWidget* KisCurveOptionWidget::curveWidget()
{
return m_widget;
}
void KisCurveOptionWidget::transferCurve()
{
m_curveOptionWidget->sensorSelector->setCurrentCurve(m_curveOptionWidget->curveWidget->curve(), m_curveOptionWidget->checkBoxUseSameCurve->isChecked());
emitSettingChanged();
}
void KisCurveOptionWidget::updateSensorCurveLabels(KisDynamicSensorSP sensor)
{
if (sensor) {
m_curveOptionWidget->label_xmin->setText(KisDynamicSensor::minimumLabel(sensor->sensorType()));
m_curveOptionWidget->label_xmax->setText(KisDynamicSensor::maximumLabel(sensor->sensorType(), sensor->length()));
}
}
void KisCurveOptionWidget::updateCurve(KisDynamicSensorSP sensor)
{
if (sensor) {
bool blockSignal = m_curveOptionWidget->curveWidget->blockSignals(true);
m_curveOptionWidget->curveWidget->setCurve(sensor->curve());
m_curveOptionWidget->curveWidget->blockSignals(blockSignal);
}
}
void KisCurveOptionWidget::updateLabelsOfCurrentSensor()
{
updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted());
updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted());
}
void KisCurveOptionWidget::updateValues()
{
m_curveOption->setValue(m_curveOptionWidget->slider->value());
m_curveOption->setCurveUsed(m_curveOptionWidget->checkBoxUseCurve->isChecked());
disableWidgets(!m_curveOptionWidget->checkBoxUseCurve->isChecked());
emitSettingChanged();
}
+void KisCurveOptionWidget::updateMode()
+{
+ m_curveOption->setCurveMode(m_curveOptionWidget->curveMode->currentIndex());
+ emitSettingChanged();
+}
+
void KisCurveOptionWidget::changeCurveLinear()
{
QList<QPointF> points;
points.push_back(QPointF(0,0));
points.push_back(QPointF(1,1));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveReverseLinear()
{
QList<QPointF> points;
points.push_back(QPointF(0,1));
points.push_back(QPointF(1,0));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveSShape()
{
QList<QPointF> points;
points.push_back(QPointF(0,0));
points.push_back(QPointF(0.25,0.1));
points.push_back(QPointF(0.75,0.9));
points.push_back(QPointF(1, 1));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveReverseSShape()
{
QList<QPointF> points;
points.push_back(QPointF(0,1));
points.push_back(QPointF(0.25,0.9));
points.push_back(QPointF(0.75,0.1));
points.push_back(QPointF(1,0));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveJShape()
{
QList<QPointF> points;
points.push_back(QPointF(0,0));
points.push_back(QPointF(0.35,0.1));
points.push_back(QPointF(1,1));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveLShape()
{
QList<QPointF> points;
points.push_back(QPointF(0,1));
points.push_back(QPointF(0.25,0.48));
points.push_back(QPointF(1,0));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveUShape()
{
QList<QPointF> points;
points.push_back(QPointF(0,1));
points.push_back(QPointF(0.5,0));
points.push_back(QPointF(1,1));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::changeCurveArchShape()
{
QList<QPointF> points;
points.push_back(QPointF(0,0));
points.push_back(QPointF(0.5,1));
points.push_back(QPointF(1,0));
m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points));
}
void KisCurveOptionWidget::disableWidgets(bool disable)
{
m_curveOptionWidget->checkBoxUseSameCurve->setDisabled(disable);
m_curveOptionWidget->curveWidget->setDisabled(disable);
m_curveOptionWidget->sensorSelector->setDisabled(disable);
m_curveOptionWidget->label_xmax->setDisabled(disable);
m_curveOptionWidget->label_xmin->setDisabled(disable);
m_curveOptionWidget->label_ymax->setDisabled(disable);
m_curveOptionWidget->label_ymin->setDisabled(disable);
}
void KisCurveOptionWidget::updateThemedIcons()
{
// set all the icons for the curve preset shapes
m_curveOptionWidget->linearCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-linear"));
m_curveOptionWidget->revLinearButton->setIcon(KisIconUtils::loadIcon("curve-preset-linear-reverse"));
m_curveOptionWidget->jCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-j"));
m_curveOptionWidget->lCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-l"));
m_curveOptionWidget->sCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-s"));
m_curveOptionWidget->reverseSCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-s-reverse"));
m_curveOptionWidget->uCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-u"));
m_curveOptionWidget->revUCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-arch"));
}
diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.h b/plugins/paintops/libpaintop/kis_curve_option_widget.h
index 45bea06a09..eae9d1be19 100644
--- a/plugins/paintops/libpaintop/kis_curve_option_widget.h
+++ b/plugins/paintops/libpaintop/kis_curve_option_widget.h
@@ -1,81 +1,84 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2009 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CURVE_OPTION_WIDGET_H
#define KIS_CURVE_OPTION_WIDGET_H
#include <kis_paintop_option.h>
class Ui_WdgCurveOption;
class KisCurveOption;
+class QComboBox;
#include <kis_dynamic_sensor.h>
class PAINTOP_EXPORT KisCurveOptionWidget : public KisPaintOpOption
{
Q_OBJECT
public:
KisCurveOptionWidget(KisCurveOption* curveOption, const QString &minLabel, const QString &maxLabel, bool hideSlider = false);
~KisCurveOptionWidget() override;
void writeOptionSetting(KisPropertiesConfigurationSP setting) const override;
void readOptionSetting(const KisPropertiesConfigurationSP setting) override;
void lodLimitations(KisPaintopLodLimitations *l) const override;
bool isCheckable() const override;
bool isChecked() const override;
void setChecked(bool checked) override;
void show();
protected:
KisCurveOption* curveOption();
QWidget* curveWidget();
private Q_SLOTS:
void transferCurve();
void updateSensorCurveLabels(KisDynamicSensorSP sensor);
void updateCurve(KisDynamicSensorSP sensor);
void updateValues();
+ void updateMode();
void updateLabelsOfCurrentSensor();
void disableWidgets(bool disable);
void updateThemedIcons();
// curve shape preset buttons
void changeCurveLinear();
void changeCurveReverseLinear();
void changeCurveSShape();
void changeCurveReverseSShape();
void changeCurveJShape();
void changeCurveLShape();
void changeCurveUShape();
void changeCurveArchShape();
private:
QWidget* m_widget;
Ui_WdgCurveOption* m_curveOptionWidget;
+ QComboBox* m_curveMode;
KisCurveOption* m_curveOption;
};
#endif // KIS_CURVE_OPTION_WIDGET_H
diff --git a/plugins/paintops/libpaintop/kis_dab_cache.cpp b/plugins/paintops/libpaintop/kis_dab_cache.cpp
index ba03c0388a..7104688eb1 100644
--- a/plugins/paintops/libpaintop/kis_dab_cache.cpp
+++ b/plugins/paintops/libpaintop/kis_dab_cache.cpp
@@ -1,421 +1,213 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dab_cache.h"
#include <KoColor.h>
-#include "kis_color_source.h"
#include "kis_paint_device.h"
#include "kis_brush.h"
-#include <kis_pressure_mirror_option.h>
-#include <kis_pressure_sharpness_option.h>
-#include <kis_texture_option.h>
-#include <kis_precision_option.h>
#include <kis_fixed_paint_device.h>
-#include <brushengine/kis_paintop.h>
+#include "kis_color_source.h"
+#include "kis_pressure_sharpness_option.h"
+#include "kis_texture_option.h"
#include <kundo2command.h>
-struct PrecisionValues {
- qreal angle;
- qreal sizeFrac;
- qreal subPixel;
- qreal softnessFactor;
-};
-
-const qreal eps = 1e-6;
-static const PrecisionValues precisionLevels[] = {
- {M_PI / 180, 0.05, 1, 0.01},
- {M_PI / 180, 0.01, 1, 0.01},
- {M_PI / 180, 0, 1, 0.01},
- {M_PI / 180, 0, 0.5, 0.01},
- {eps, 0, eps, eps}
-};
-
-struct KisDabCache::SavedDabParameters {
- KoColor color;
- qreal angle;
- int width;
- int height;
- qreal subPixelX;
- qreal subPixelY;
- qreal softnessFactor;
- int index;
- MirrorProperties mirrorProperties;
-
- bool compare(const SavedDabParameters &rhs, int precisionLevel) const {
- const PrecisionValues &prec = precisionLevels[precisionLevel];
-
- return color == rhs.color &&
- qAbs(angle - rhs.angle) <= prec.angle &&
- qAbs(width - rhs.width) <= (int)(prec.sizeFrac * width) &&
- qAbs(height - rhs.height) <= (int)(prec.sizeFrac * height) &&
- qAbs(subPixelX - rhs.subPixelX) <= prec.subPixel &&
- qAbs(subPixelY - rhs.subPixelY) <= prec.subPixel &&
- qAbs(softnessFactor - rhs.softnessFactor) <= prec.softnessFactor &&
- index == rhs.index &&
- mirrorProperties.horizontalMirror == rhs.mirrorProperties.horizontalMirror &&
- mirrorProperties.verticalMirror == rhs.mirrorProperties.verticalMirror;
- }
-};
-
struct KisDabCache::Private {
Private(KisBrushSP brush)
- : brush(brush),
- mirrorOption(0),
- sharpnessOption(0),
- textureOption(0),
- precisionOption(0),
- subPixelPrecisionDisabled(false),
- cachedDabParameters(new SavedDabParameters)
+ : brush(brush)
{}
+
KisFixedPaintDeviceSP dab;
KisFixedPaintDeviceSP dabOriginal;
KisBrushSP brush;
KisPaintDeviceSP colorSourceDevice;
- KisPressureMirrorOption *mirrorOption;
- KisPressureSharpnessOption *sharpnessOption;
- KisTextureProperties *textureOption;
- KisPrecisionOption *precisionOption;
- bool subPixelPrecisionDisabled;
-
- SavedDabParameters *cachedDabParameters;
+ KisPressureSharpnessOption *sharpnessOption = 0;
+ KisTextureProperties *textureOption = 0;
};
KisDabCache::KisDabCache(KisBrushSP brush)
: m_d(new Private(brush))
{
}
KisDabCache::~KisDabCache()
{
- delete m_d->cachedDabParameters;
delete m_d;
}
-void KisDabCache::setMirrorPostprocessing(KisPressureMirrorOption *option)
-{
- m_d->mirrorOption = option;
-}
-
void KisDabCache::setSharpnessPostprocessing(KisPressureSharpnessOption *option)
{
m_d->sharpnessOption = option;
}
void KisDabCache::setTexturePostprocessing(KisTextureProperties *option)
{
m_d->textureOption = option;
}
-void KisDabCache::setPrecisionOption(KisPrecisionOption *option)
+bool KisDabCache::needSeparateOriginal() const
{
- m_d->precisionOption = option;
+ return KisDabCacheBase::needSeparateOriginal(m_d->textureOption, m_d->sharpnessOption);
}
-void KisDabCache::disableSubpixelPrecision()
-{
- m_d->subPixelPrecisionDisabled = true;
-}
-
-inline KisDabCache::SavedDabParameters
-KisDabCache::getDabParameters(const KoColor& color,
- KisDabShape const& shape,
- const KisPaintInformation& info,
- double subPixelX, double subPixelY,
- qreal softnessFactor,
- MirrorProperties mirrorProperties)
-{
- SavedDabParameters params;
-
- params.color = color;
- params.angle = shape.rotation();
- params.width = m_d->brush->maskWidth(shape, subPixelX, subPixelY, info);
- params.height = m_d->brush->maskHeight(shape, subPixelX, subPixelY, info);
- params.subPixelX = subPixelX;
- params.subPixelY = subPixelY;
- params.softnessFactor = softnessFactor;
- params.index = m_d->brush->brushIndex(info);
- params.mirrorProperties = mirrorProperties;
-
- return params;
-}
KisFixedPaintDeviceSP KisDabCache::fetchDab(const KoColorSpace *cs,
- const KisColorSource *colorSource,
+ KisColorSource *colorSource,
const QPointF &cursorPoint,
KisDabShape const& shape,
const KisPaintInformation& info,
qreal softnessFactor,
QRect *dstDabRect)
{
return fetchDabCommon(cs, colorSource, KoColor(),
cursorPoint,
shape,
info,
softnessFactor,
dstDabRect);
}
KisFixedPaintDeviceSP KisDabCache::fetchDab(const KoColorSpace *cs,
const KoColor& color,
const QPointF &cursorPoint,
KisDabShape const& shape,
const KisPaintInformation& info,
qreal softnessFactor,
QRect *dstDabRect)
{
return fetchDabCommon(cs, 0, color,
cursorPoint,
shape,
info,
softnessFactor,
dstDabRect);
}
-bool KisDabCache::needSeparateOriginal()
-{
- return (m_d->textureOption && m_d->textureOption->m_enabled) ||
- (m_d->sharpnessOption && m_d->sharpnessOption->isChecked());
-}
-
-struct KisDabCache::DabPosition {
- DabPosition(const QRect &_rect,
- const QPointF &_subPixel,
- qreal _realAngle)
- : rect(_rect),
- subPixel(_subPixel),
- realAngle(_realAngle) {
- }
-
- QRect rect;
- QPointF subPixel;
- qreal realAngle;
-};
-
inline
-QRect KisDabCache::correctDabRectWhenFetchedFromCache(const QRect &dabRect,
- const QSize &realDabSize)
-{
- int diffX = (realDabSize.width() - dabRect.width()) / 2;
- int diffY = (realDabSize.height() - dabRect.height()) / 2;
-
- return QRect(dabRect.x() - diffX, dabRect.y() - diffY,
- realDabSize.width() , realDabSize.height());
-}
-
-inline
-KisFixedPaintDeviceSP KisDabCache::tryFetchFromCache(const SavedDabParameters &params,
- const KisPaintInformation& info,
- QRect *dstDabRect)
+KisFixedPaintDeviceSP KisDabCache::fetchFromCache(KisDabCacheUtils::DabRenderingResources *resources,
+ const KisPaintInformation& info,
+ QRect *dstDabRect)
{
- int precisionLevel = m_d->precisionOption ? m_d->precisionOption->precisionLevel() - 1 : 3;
-
- if (!params.compare(*m_d->cachedDabParameters, precisionLevel)) {
- return 0;
- }
-
if (needSeparateOriginal()) {
*m_d->dab = *m_d->dabOriginal;
- *dstDabRect = correctDabRectWhenFetchedFromCache(*dstDabRect, m_d->dab->bounds().size());
- postProcessDab(m_d->dab, dstDabRect->topLeft(), info);
+ *dstDabRect = KisDabCacheUtils::correctDabRectWhenFetchedFromCache(*dstDabRect, m_d->dab->bounds().size());
+ KisDabCacheUtils::postProcessDab(m_d->dab, dstDabRect->topLeft(), info, resources);
}
else {
- *dstDabRect = correctDabRectWhenFetchedFromCache(*dstDabRect, m_d->dab->bounds().size());
+ *dstDabRect = KisDabCacheUtils::correctDabRectWhenFetchedFromCache(*dstDabRect, m_d->dab->bounds().size());
}
- m_d->brush->notifyCachedDabPainted(info);
+ resources->brush->notifyCachedDabPainted(info);
return m_d->dab;
}
-qreal positiveFraction(qreal x) {
- qint32 unused = 0;
- qreal fraction = 0.0;
- KisPaintOp::splitCoordinate(x, &unused, &fraction);
-
- return fraction;
-}
-
-inline
-KisDabCache::DabPosition
-KisDabCache::calculateDabRect(const QPointF &cursorPoint,
- KisDabShape shape,
- const KisPaintInformation& info,
- const MirrorProperties &mirrorProperties)
+/**
+ * A special hack class that allows creation of temporary object with resources
+ * without taking ownershop over the option classes
+ */
+struct TemporaryResourcesWithoutOwning : public KisDabCacheUtils::DabRenderingResources
{
- qint32 x = 0, y = 0;
- qreal subPixelX = 0.0, subPixelY = 0.0;
-
- if (mirrorProperties.coordinateSystemFlipped) {
- shape = KisDabShape(shape.scale(), shape.ratio(), 2 * M_PI - shape.rotation());
+ ~TemporaryResourcesWithoutOwning() override {
+ // we do not own these resources, so just
+ // release them before destruction
+ colorSource.take();
+ sharpnessOption.take();
+ textureOption.take();
}
-
- QPointF hotSpot = m_d->brush->hotSpot(shape, info);
- QPointF pt = cursorPoint - hotSpot;
-
- if (m_d->sharpnessOption) {
- m_d->sharpnessOption->apply(info, pt, x, y, subPixelX, subPixelY);
- }
- else {
- KisPaintOp::splitCoordinate(pt.x(), &x, &subPixelX);
- KisPaintOp::splitCoordinate(pt.y(), &y, &subPixelY);
- }
-
- if (m_d->subPixelPrecisionDisabled) {
- subPixelX = 0;
- subPixelY = 0;
- }
-
- if (qIsNaN(subPixelX)) {
- subPixelX = 0;
- }
-
- if (qIsNaN(subPixelY)) {
- subPixelY = 0;
- }
-
- int width = m_d->brush->maskWidth(shape, subPixelX, subPixelY, info);
- int height = m_d->brush->maskHeight(shape, subPixelX, subPixelY, info);
-
- if (mirrorProperties.horizontalMirror) {
- subPixelX = positiveFraction(-(cursorPoint.x() + hotSpot.x()));
- width = m_d->brush->maskWidth(shape, subPixelX, subPixelY, info);
- x = qRound(cursorPoint.x() + subPixelX + hotSpot.x()) - width;
- }
-
- if (mirrorProperties.verticalMirror) {
- subPixelY = positiveFraction(-(cursorPoint.y() + hotSpot.y()));
- height = m_d->brush->maskHeight(shape, subPixelX, subPixelY, info);
- y = qRound(cursorPoint.y() + subPixelY + hotSpot.y()) - height;
- }
-
- return DabPosition(QRect(x, y, width, height),
- QPointF(subPixelX, subPixelY),
- shape.rotation());
-}
+};
inline
KisFixedPaintDeviceSP KisDabCache::fetchDabCommon(const KoColorSpace *cs,
- const KisColorSource *colorSource,
+ KisColorSource *colorSource,
const KoColor& color,
const QPointF &cursorPoint,
KisDabShape shape,
const KisPaintInformation& info,
qreal softnessFactor,
QRect *dstDabRect)
{
Q_ASSERT(dstDabRect);
- MirrorProperties mirrorProperties;
- if (m_d->mirrorOption) {
- mirrorProperties = m_d->mirrorOption->apply(info);
+ bool hasDabInCache = true;
+
+ if (!m_d->dab || *m_d->dab->colorSpace() != *cs) {
+ m_d->dab = new KisFixedPaintDevice(cs);
+ hasDabInCache = false;
}
- DabPosition position = calculateDabRect(cursorPoint,
- shape,
- info,
- mirrorProperties);
- shape = KisDabShape(shape.scale(), shape.ratio(), position.realAngle);
- *dstDabRect = position.rect;
+ using namespace KisDabCacheUtils;
- bool cachingIsPossible = !colorSource || colorSource->isUniformColor();
- KoColor paintColor = colorSource && colorSource->isUniformColor() ?
- colorSource->uniformColor() : color;
+ // 1. Calculate new dab parameters and whether we can reuse the cache
- SavedDabParameters newParams = getDabParameters(paintColor,
- shape, info,
- position.subPixel.x(),
- position.subPixel.y(),
- softnessFactor,
- mirrorProperties);
+ TemporaryResourcesWithoutOwning resources;
+ resources.brush = m_d->brush;
+ resources.colorSourceDevice = m_d->colorSourceDevice;
- if (!m_d->dab || *m_d->dab->colorSpace() != *cs) {
- m_d->dab = new KisFixedPaintDevice(cs);
- }
- else if (cachingIsPossible) {
- KisFixedPaintDeviceSP cachedDab =
- tryFetchFromCache(newParams, info, dstDabRect);
+ // NOTE: we use a special subclass of resources that will NOT
+ // delete options on destruction!
+ resources.colorSource.reset(colorSource);
+ resources.sharpnessOption.reset(m_d->sharpnessOption);
+ resources.textureOption.reset(m_d->textureOption);
- if (cachedDab) return cachedDab;
- }
- if (m_d->brush->brushType() == IMAGE || m_d->brush->brushType() == PIPE_IMAGE) {
- m_d->dab = m_d->brush->paintDevice(cs, shape, info,
- position.subPixel.x(),
- position.subPixel.y());
- }
- else if (cachingIsPossible) {
- *m_d->cachedDabParameters = newParams;
- m_d->brush->mask(m_d->dab, paintColor, shape,
- info,
- position.subPixel.x(), position.subPixel.y(),
- softnessFactor);
- }
- else {
- if (!m_d->colorSourceDevice || *cs != *m_d->colorSourceDevice->colorSpace()) {
- m_d->colorSourceDevice = new KisPaintDevice(cs);
- }
- else {
- m_d->colorSourceDevice->clear();
- }
+ DabGenerationInfo di;
+ bool shouldUseCache = false;
- QRect maskRect(QPoint(), position.rect.size());
- colorSource->colorize(m_d->colorSourceDevice, maskRect, info.pos().toPoint());
- delete m_d->colorSourceDevice->convertTo(cs);
+ fetchDabGenerationInfo(hasDabInCache,
+ &resources,
+ DabRequestInfo(
+ color,
+ cursorPoint,
+ shape,
+ info,
+ softnessFactor),
+ &di,
+ &shouldUseCache);
+
+ *dstDabRect = di.dstDabRect;
- m_d->brush->mask(m_d->dab, m_d->colorSourceDevice, shape,
- info,
- position.subPixel.x(), position.subPixel.y(),
- softnessFactor);
- }
- if (!mirrorProperties.isEmpty()) {
- m_d->dab->mirror(mirrorProperties.horizontalMirror,
- mirrorProperties.verticalMirror);
+ // 2. Try return a saved dab from the cache
+
+ if (shouldUseCache) {
+ return fetchFromCache(&resources, info, dstDabRect);
}
- if (needSeparateOriginal()) {
+ // 3. Generate new dab
+
+ generateDab(di, &resources, &m_d->dab);
+
+ // 4. Do postprocessing
+ if (di.needsPostprocessing) {
if (!m_d->dabOriginal || *cs != *m_d->dabOriginal->colorSpace()) {
m_d->dabOriginal = new KisFixedPaintDevice(cs);
}
*m_d->dabOriginal = *m_d->dab;
- }
-
- postProcessDab(m_d->dab, position.rect.topLeft(), info);
-
- return m_d->dab;
-}
-void KisDabCache::postProcessDab(KisFixedPaintDeviceSP dab,
- const QPoint &dabTopLeft,
- const KisPaintInformation& info)
-{
- if (m_d->sharpnessOption) {
- m_d->sharpnessOption->applyThreshold(dab);
+ postProcessDab(m_d->dab, di.dstDabRect.topLeft(), info, &resources);
}
- if (m_d->textureOption) {
- m_d->textureOption->apply(dab, dabTopLeft, info);
- }
+ return m_d->dab;
}
diff --git a/plugins/paintops/libpaintop/kis_dab_cache.h b/plugins/paintops/libpaintop/kis_dab_cache.h
index 442543e475..0a7c610ad6 100644
--- a/plugins/paintops/libpaintop/kis_dab_cache.h
+++ b/plugins/paintops/libpaintop/kis_dab_cache.h
@@ -1,134 +1,103 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_DAB_CACHE_H
#define __KIS_DAB_CACHE_H
#include "kritapaintop_export.h"
+
+#include "kis_dab_cache_base.h"
+
#include "kis_brush.h"
class KisColorSource;
class KisPressureSharpnessOption;
class KisTextureProperties;
class KisPressureMirrorOption;
class KisPrecisionOption;
struct MirrorProperties;
/**
* @brief The KisDabCache class provides caching for dabs into the brush paintop
*
* This class adds caching of the dabs to the paintop system of Krita.
* Such cache makes the execution of the benchmarks up to 2 times faster.
* Subjectively, the real painting becomes much faster, especially with
* huge brushes. Artists report up to 20% speed gain while painting.
*
* Of course, such caching makes the painting a bit less precise: we need
* to tolerate subpixel differences to allow the cache to work. Sometimes
* small difference in the size of a dab can also be acceptable. That is
* why I introduced levels of precision. They are graded from 1 to 5: from
* the fastest and less precise to the slowest, but with the best quality.
* You can see the slider in the paintop settings dialog. The ToolTip text
* explains which features of the brush are sacrificed on each precision
* level.
*
* The texturing and mirroring problems are solved.
*/
-class PAINTOP_EXPORT KisDabCache
+class PAINTOP_EXPORT KisDabCache : public KisDabCacheBase
{
public:
KisDabCache(KisBrushSP brush);
~KisDabCache();
- void setMirrorPostprocessing(KisPressureMirrorOption *option);
- void setSharpnessPostprocessing(KisPressureSharpnessOption *option);
- void setTexturePostprocessing(KisTextureProperties *option);
- void setPrecisionOption(KisPrecisionOption *option);
-
- /**
- * Disables handling of the subPixelX and subPixelY values, this
- * is needed at least for the Color Smudge paint op, which reads
- * aligned areas from image, so additional offsets generated by
- * the subpixel precision should be avoided
- */
- void disableSubpixelPrecision();
-
- bool needSeparateOriginal();
-
KisFixedPaintDeviceSP fetchDab(const KoColorSpace *cs,
- const KisColorSource *colorSource,
+ KisColorSource *colorSource,
const QPointF &cursorPoint,
KisDabShape const&,
const KisPaintInformation& info,
qreal softnessFactor,
QRect *dstDabRect);
KisFixedPaintDeviceSP fetchDab(const KoColorSpace *cs,
const KoColor& color,
const QPointF &cursorPoint,
KisDabShape const&,
const KisPaintInformation& info,
qreal softnessFactor,
QRect *dstDabRect);
+ void setSharpnessPostprocessing(KisPressureSharpnessOption *option);
+ void setTexturePostprocessing(KisTextureProperties *option);
+
+ bool needSeparateOriginal() const;
private:
- struct SavedDabParameters;
- struct DabPosition;
-private:
- inline SavedDabParameters getDabParameters(const KoColor& color,
- KisDabShape const&,
- const KisPaintInformation& info,
- double subPixelX, double subPixelY,
- qreal softnessFactor,
- MirrorProperties mirrorProperties);
- inline KisDabCache::DabPosition
- calculateDabRect(const QPointF &cursorPoint,
- KisDabShape,
- const KisPaintInformation& info,
- const MirrorProperties &mirrorProperties);
-
- inline
- QRect correctDabRectWhenFetchedFromCache(const QRect &dabRect,
- const QSize &realDabSize);
-
- inline KisFixedPaintDeviceSP tryFetchFromCache(const SavedDabParameters &params,
- const KisPaintInformation& info,
- QRect *dstDabRect);
+
+ inline KisFixedPaintDeviceSP fetchFromCache(KisDabCacheUtils::DabRenderingResources *resources, const KisPaintInformation& info,
+ QRect *dstDabRect);
inline KisFixedPaintDeviceSP fetchDabCommon(const KoColorSpace *cs,
- const KisColorSource *colorSource,
+ KisColorSource *colorSource,
const KoColor& color,
const QPointF &cursorPoint,
KisDabShape,
const KisPaintInformation& info,
qreal softnessFactor,
QRect *dstDabRect);
- void postProcessDab(KisFixedPaintDeviceSP dab,
- const QPoint &dabTopLeft,
- const KisPaintInformation& info);
-
private:
struct Private;
Private * const m_d;
};
#endif /* __KIS_DAB_CACHE_H */
diff --git a/plugins/paintops/libpaintop/kis_dab_cache_base.cpp b/plugins/paintops/libpaintop/kis_dab_cache_base.cpp
new file mode 100644
index 0000000000..9fb631abae
--- /dev/null
+++ b/plugins/paintops/libpaintop/kis_dab_cache_base.cpp
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "kis_dab_cache_base.h"
+
+#include <KoColor.h>
+#include "kis_color_source.h"
+#include "kis_paint_device.h"
+#include "kis_brush.h"
+#include <kis_pressure_mirror_option.h>
+#include <kis_pressure_sharpness_option.h>
+#include <kis_texture_option.h>
+#include <kis_precision_option.h>
+#include <kis_fixed_paint_device.h>
+#include <brushengine/kis_paintop.h>
+
+#include <kundo2command.h>
+
+struct PrecisionValues {
+ qreal angle;
+ qreal sizeFrac;
+ qreal subPixel;
+ qreal softnessFactor;
+};
+
+const qreal eps = 1e-6;
+static const PrecisionValues precisionLevels[] = {
+ {M_PI / 180, 0.05, 1, 0.01},
+ {M_PI / 180, 0.01, 1, 0.01},
+ {M_PI / 180, 0, 1, 0.01},
+ {M_PI / 180, 0, 0.5, 0.01},
+ {eps, 0, eps, eps}
+};
+
+struct KisDabCacheBase::SavedDabParameters {
+ KoColor color;
+ qreal angle;
+ int width;
+ int height;
+ qreal subPixelX;
+ qreal subPixelY;
+ qreal softnessFactor;
+ int index;
+ MirrorProperties mirrorProperties;
+
+ bool compare(const SavedDabParameters &rhs, int precisionLevel) const {
+ const PrecisionValues &prec = precisionLevels[precisionLevel];
+
+ return color == rhs.color &&
+ qAbs(angle - rhs.angle) <= prec.angle &&
+ qAbs(width - rhs.width) <= (int)(prec.sizeFrac * width) &&
+ qAbs(height - rhs.height) <= (int)(prec.sizeFrac * height) &&
+ qAbs(subPixelX - rhs.subPixelX) <= prec.subPixel &&
+ qAbs(subPixelY - rhs.subPixelY) <= prec.subPixel &&
+ qAbs(softnessFactor - rhs.softnessFactor) <= prec.softnessFactor &&
+ index == rhs.index &&
+ mirrorProperties.horizontalMirror == rhs.mirrorProperties.horizontalMirror &&
+ mirrorProperties.verticalMirror == rhs.mirrorProperties.verticalMirror;
+ }
+};
+
+struct KisDabCacheBase::Private {
+
+ Private()
+ : mirrorOption(0),
+ precisionOption(0),
+ subPixelPrecisionDisabled(false)
+ {}
+
+ KisPressureMirrorOption *mirrorOption;
+ KisPrecisionOption *precisionOption;
+ bool subPixelPrecisionDisabled;
+
+ SavedDabParameters lastSavedDabParameters;
+
+ static qreal positiveFraction(qreal x);
+};
+
+
+
+KisDabCacheBase::KisDabCacheBase()
+ : m_d(new Private())
+{
+}
+
+KisDabCacheBase::~KisDabCacheBase()
+{
+ delete m_d;
+}
+
+void KisDabCacheBase::setMirrorPostprocessing(KisPressureMirrorOption *option)
+{
+ m_d->mirrorOption = option;
+}
+
+void KisDabCacheBase::setPrecisionOption(KisPrecisionOption *option)
+{
+ m_d->precisionOption = option;
+}
+
+void KisDabCacheBase::disableSubpixelPrecision()
+{
+ m_d->subPixelPrecisionDisabled = true;
+}
+
+inline KisDabCacheBase::SavedDabParameters
+KisDabCacheBase::getDabParameters(KisBrushSP brush,
+ const KoColor& color,
+ KisDabShape const& shape,
+ const KisPaintInformation& info,
+ double subPixelX, double subPixelY,
+ qreal softnessFactor,
+ MirrorProperties mirrorProperties)
+{
+ SavedDabParameters params;
+
+ params.color = color;
+ params.angle = shape.rotation();
+ params.width = brush->maskWidth(shape, subPixelX, subPixelY, info);
+ params.height = brush->maskHeight(shape, subPixelX, subPixelY, info);
+ params.subPixelX = subPixelX;
+ params.subPixelY = subPixelY;
+ params.softnessFactor = softnessFactor;
+ params.index = brush->brushIndex(info);
+ params.mirrorProperties = mirrorProperties;
+
+ return params;
+}
+
+bool KisDabCacheBase::needSeparateOriginal(KisTextureProperties *textureOption,
+ KisPressureSharpnessOption *sharpnessOption) const
+{
+ return (textureOption && textureOption->m_enabled) ||
+ (sharpnessOption && sharpnessOption->isChecked());
+}
+
+struct KisDabCacheBase::DabPosition {
+ DabPosition(const QRect &_rect,
+ const QPointF &_subPixel,
+ qreal _realAngle)
+ : rect(_rect),
+ subPixel(_subPixel),
+ realAngle(_realAngle) {
+ }
+
+ QRect rect;
+ QPointF subPixel;
+ qreal realAngle;
+};
+
+qreal KisDabCacheBase::Private::positiveFraction(qreal x) {
+ qint32 unused = 0;
+ qreal fraction = 0.0;
+ KisPaintOp::splitCoordinate(x, &unused, &fraction);
+
+ return fraction;
+}
+
+inline
+KisDabCacheBase::DabPosition
+KisDabCacheBase::calculateDabRect(KisBrushSP brush,
+ const QPointF &cursorPoint,
+ KisDabShape shape,
+ const KisPaintInformation& info,
+ const MirrorProperties &mirrorProperties,
+ KisPressureSharpnessOption *sharpnessOption)
+{
+ qint32 x = 0, y = 0;
+ qreal subPixelX = 0.0, subPixelY = 0.0;
+
+ if (mirrorProperties.coordinateSystemFlipped) {
+ shape = KisDabShape(shape.scale(), shape.ratio(), 2 * M_PI - shape.rotation());
+ }
+
+ QPointF hotSpot = brush->hotSpot(shape, info);
+ QPointF pt = cursorPoint - hotSpot;
+
+ if (sharpnessOption) {
+ sharpnessOption->apply(info, pt, x, y, subPixelX, subPixelY);
+ }
+ else {
+ KisPaintOp::splitCoordinate(pt.x(), &x, &subPixelX);
+ KisPaintOp::splitCoordinate(pt.y(), &y, &subPixelY);
+ }
+
+ if (m_d->subPixelPrecisionDisabled) {
+ subPixelX = 0;
+ subPixelY = 0;
+ }
+
+ if (qIsNaN(subPixelX)) {
+ subPixelX = 0;
+ }
+
+ if (qIsNaN(subPixelY)) {
+ subPixelY = 0;
+ }
+
+ int width = brush->maskWidth(shape, subPixelX, subPixelY, info);
+ int height = brush->maskHeight(shape, subPixelX, subPixelY, info);
+
+ if (mirrorProperties.horizontalMirror) {
+ subPixelX = Private::positiveFraction(-(cursorPoint.x() + hotSpot.x()));
+ width = brush->maskWidth(shape, subPixelX, subPixelY, info);
+ x = qRound(cursorPoint.x() + subPixelX + hotSpot.x()) - width;
+ }
+
+ if (mirrorProperties.verticalMirror) {
+ subPixelY = Private::positiveFraction(-(cursorPoint.y() + hotSpot.y()));
+ height = brush->maskHeight(shape, subPixelX, subPixelY, info);
+ y = qRound(cursorPoint.y() + subPixelY + hotSpot.y()) - height;
+ }
+
+ return DabPosition(QRect(x, y, width, height),
+ QPointF(subPixelX, subPixelY),
+ shape.rotation());
+}
+
+void KisDabCacheBase::fetchDabGenerationInfo(bool hasDabInCache,
+ KisDabCacheUtils::DabRenderingResources *resources,
+ const KisDabCacheUtils::DabRequestInfo &request,
+ KisDabCacheUtils::DabGenerationInfo *di,
+ bool *shouldUseCache)
+{
+ di->info = request.info;
+ di->softnessFactor = request.softnessFactor;
+
+ if (m_d->mirrorOption) {
+ di->mirrorProperties = m_d->mirrorOption->apply(request.info);
+ }
+
+ DabPosition position = calculateDabRect(resources->brush,
+ request.cursorPoint,
+ request.shape,
+ request.info,
+ di->mirrorProperties,
+ resources->sharpnessOption.data());
+ di->shape = KisDabShape(request.shape.scale(), request.shape.ratio(), position.realAngle);
+ di->dstDabRect = position.rect;
+ di->subPixel = position.subPixel;
+
+ di->solidColorFill = !resources->colorSource || resources->colorSource->isUniformColor();
+ di->paintColor = resources->colorSource && resources->colorSource->isUniformColor() ?
+ resources->colorSource->uniformColor() : request.color;
+
+ SavedDabParameters newParams = getDabParameters(resources->brush,
+ di->paintColor,
+ di->shape,
+ di->info,
+ di->subPixel.x(),
+ di->subPixel.y(),
+ di->softnessFactor,
+ di->mirrorProperties);
+
+ const int precisionLevel = m_d->precisionOption ? m_d->precisionOption->precisionLevel() - 1 : 3;
+ *shouldUseCache = hasDabInCache && di->solidColorFill &&
+ newParams.compare(m_d->lastSavedDabParameters, precisionLevel);
+
+ if (!*shouldUseCache) {
+ m_d->lastSavedDabParameters = newParams;
+ }
+
+ di->needsPostprocessing = needSeparateOriginal(resources->textureOption.data(), resources->sharpnessOption.data());
+}
+
diff --git a/plugins/paintops/libpaintop/kis_dab_cache_base.h b/plugins/paintops/libpaintop/kis_dab_cache_base.h
new file mode 100644
index 0000000000..77b9f7f91c
--- /dev/null
+++ b/plugins/paintops/libpaintop/kis_dab_cache_base.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_DAB_CACHE_BASE_H
+#define __KIS_DAB_CACHE_BASE_H
+
+#include "kritapaintop_export.h"
+#include "kis_brush.h"
+
+#include "KisDabCacheUtils.h"
+
+class KisColorSource;
+class KisPressureSharpnessOption;
+class KisTextureProperties;
+class KisPressureMirrorOption;
+class KisPrecisionOption;
+struct MirrorProperties;
+
+
+/**
+ * @brief The KisDabCacheBase class provides caching for dabs into the brush paintop
+ *
+ * This class adds caching of the dabs to the paintop system of Krita.
+ * Such cache makes the execution of the benchmarks up to 2 times faster.
+ * Subjectively, the real painting becomes much faster, especially with
+ * huge brushes. Artists report up to 20% speed gain while painting.
+ *
+ * Of course, such caching makes the painting a bit less precise: we need
+ * to tolerate subpixel differences to allow the cache to work. Sometimes
+ * small difference in the size of a dab can also be acceptable. That is
+ * why I introduced levels of precision. They are graded from 1 to 5: from
+ * the fastest and less precise to the slowest, but with the best quality.
+ * You can see the slider in the paintop settings dialog. The ToolTip text
+ * explains which features of the brush are sacrificed on each precision
+ * level.
+ *
+ * The texturing and mirroring problems are solved.
+ */
+class PAINTOP_EXPORT KisDabCacheBase
+{
+public:
+ KisDabCacheBase();
+ ~KisDabCacheBase();
+
+ void setMirrorPostprocessing(KisPressureMirrorOption *option);
+ void setPrecisionOption(KisPrecisionOption *option);
+
+ /**
+ * Disables handling of the subPixelX and subPixelY values, this
+ * is needed at least for the Color Smudge paint op, which reads
+ * aligned areas from image, so additional offsets generated by
+ * the subpixel precision should be avoided
+ */
+ void disableSubpixelPrecision();
+
+ /**
+ * Return true if the dab needs postprocesing by special options
+ * like 'texture' or 'sharpness'
+ */
+ bool needSeparateOriginal(KisTextureProperties *textureOption,
+ KisPressureSharpnessOption *sharpnessOption) const;
+
+protected:
+ /**
+ * Fetches all the necessary information for dab generation and
+ * tells if the caller must should reuse the preciously returned dab. *
+ * Please note that KisDabCacheBase has an internal state, that keeps the
+ * parameters of the previously generated (on a cache-miss) dab. This function
+ * automatically updates this state when 'shouldUseCache == false'. Therefore, the
+ * caller *must* generate the dab if and only if when 'shouldUseCache == false'.
+ * Othewise the internal state will become inconsistent.
+ *
+ * @param hasDabInCache shows if the caller has something in its cache
+ * @param resources rendering resources available for this dab
+ * @param color current painting color
+ * @param cursorPoint cursor point at which the dab should be painted
+ * @param shape dab shape requested by the caller. It will be modified before
+ * generation to accomodate the mirroring and rotation options.
+ * @param info painting info associated with the dab
+ * @param softnessFactor softness factor
+ * @param di (OUT) calculated dab generation information
+ * @param shouldUseCache (OUT) shows whether the caller *must* use cache or not
+ */
+ void fetchDabGenerationInfo(bool hasDabInCache,
+ KisDabCacheUtils::DabRenderingResources *resources,
+ const KisDabCacheUtils::DabRequestInfo &request,
+ /* out */
+ KisDabCacheUtils::DabGenerationInfo *di,
+ bool *shouldUseCache);
+
+private:
+ struct SavedDabParameters;
+ struct DabPosition;
+private:
+ inline SavedDabParameters getDabParameters(KisBrushSP brush, const KoColor& color,
+ KisDabShape const&,
+ const KisPaintInformation& info,
+ double subPixelX, double subPixelY,
+ qreal softnessFactor,
+ MirrorProperties mirrorProperties);
+
+ inline KisDabCacheBase::DabPosition
+ calculateDabRect(KisBrushSP brush, const QPointF &cursorPoint,
+ KisDabShape,
+ const KisPaintInformation& info,
+ const MirrorProperties &mirrorProperties, KisPressureSharpnessOption *sharpnessOption);
+
+private:
+ struct Private;
+ Private * const m_d;
+};
+
+#endif /* __KIS_DAB_CACHE_BASE_H */
diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensor.cc b/plugins/paintops/libpaintop/kis_dynamic_sensor.cc
index 09c18c5215..6e48cc5d4f 100644
--- a/plugins/paintops/libpaintop/kis_dynamic_sensor.cc
+++ b/plugins/paintops/libpaintop/kis_dynamic_sensor.cc
@@ -1,477 +1,477 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensor.h"
#include <QDomElement>
#include "kis_algebra_2d.h"
#include "sensors/kis_dynamic_sensors.h"
#include "sensors/kis_dynamic_sensor_distance.h"
#include "sensors/kis_dynamic_sensor_drawing_angle.h"
#include "sensors/kis_dynamic_sensor_time.h"
#include "sensors/kis_dynamic_sensor_fade.h"
#include "sensors/kis_dynamic_sensor_fuzzy.h"
KisDynamicSensor::KisDynamicSensor(DynamicSensorType type)
: m_length(-1)
, m_type(type)
, m_customCurve(false)
, m_active(false)
{
}
KisDynamicSensor::~KisDynamicSensor()
{
}
QWidget* KisDynamicSensor::createConfigurationWidget(QWidget* parent, QWidget*)
{
Q_UNUSED(parent);
return 0;
}
void KisDynamicSensor::reset()
{
}
-KisDynamicSensorSP KisDynamicSensor::id2Sensor(const KoID& id)
+KisDynamicSensorSP KisDynamicSensor::id2Sensor(const KoID& id, const QString &parentOptionName)
{
if (id.id() == PressureId.id()) {
return new KisDynamicSensorPressure();
}
else if (id.id() == PressureInId.id()) {
return new KisDynamicSensorPressureIn();
}
else if (id.id() == XTiltId.id()) {
return new KisDynamicSensorXTilt();
}
else if (id.id() == YTiltId.id()) {
return new KisDynamicSensorYTilt();
}
else if (id.id() == TiltDirectionId.id()) {
return new KisDynamicSensorTiltDirection();
}
else if (id.id() == TiltElevationId.id()) {
return new KisDynamicSensorTiltElevation();
}
else if (id.id() == SpeedId.id()) {
return new KisDynamicSensorSpeed();
}
else if (id.id() == DrawingAngleId.id()) {
return new KisDynamicSensorDrawingAngle();
}
else if (id.id() == RotationId.id()) {
return new KisDynamicSensorRotation();
}
else if (id.id() == DistanceId.id()) {
return new KisDynamicSensorDistance();
}
else if (id.id() == TimeId.id()) {
return new KisDynamicSensorTime();
}
else if (id.id() == FuzzyPerDabId.id()) {
- return new KisDynamicSensorFuzzy(false);
+ return new KisDynamicSensorFuzzy(false, parentOptionName);
}
else if (id.id() == FuzzyPerStrokeId.id()) {
- return new KisDynamicSensorFuzzy(true);
+ return new KisDynamicSensorFuzzy(true, parentOptionName);
}
else if (id.id() == FadeId.id()) {
return new KisDynamicSensorFade();
}
else if (id.id() == PerspectiveId.id()) {
return new KisDynamicSensorPerspective();
}
else if (id.id() == TangentialPressureId.id()) {
return new KisDynamicSensorTangentialPressure();
}
dbgPlugins << "Unknown transform parameter :" << id.id();
return 0;
}
DynamicSensorType KisDynamicSensor::id2Type(const KoID &id)
{
if (id.id() == PressureId.id()) {
return PRESSURE;
}
else if (id.id() == PressureInId.id()) {
return PRESSURE_IN;
}
else if (id.id() == XTiltId.id()) {
return XTILT;
}
else if (id.id() == YTiltId.id()) {
return YTILT;
}
else if (id.id() == TiltDirectionId.id()) {
return TILT_DIRECTION;
}
else if (id.id() == TiltElevationId.id()) {
return TILT_ELEVATATION;
}
else if (id.id() == SpeedId.id()) {
return SPEED;
}
else if (id.id() == DrawingAngleId.id()) {
return ANGLE;
}
else if (id.id() == RotationId.id()) {
return ROTATION;
}
else if (id.id() == DistanceId.id()) {
return DISTANCE;
}
else if (id.id() == TimeId.id()) {
return TIME;
}
else if (id.id() == FuzzyPerDabId.id()) {
return FUZZY_PER_DAB;
}
else if (id.id() == FuzzyPerStrokeId.id()) {
return FUZZY_PER_STROKE;
}
else if (id.id() == FadeId.id()) {
return FADE;
}
else if (id.id() == PerspectiveId.id()) {
return PERSPECTIVE;
}
else if (id.id() == TangentialPressureId.id()) {
return TANGENTIAL_PRESSURE;
}
return UNKNOWN;
}
-KisDynamicSensorSP KisDynamicSensor::type2Sensor(DynamicSensorType sensorType)
+KisDynamicSensorSP KisDynamicSensor::type2Sensor(DynamicSensorType sensorType, const QString &parentOptionName)
{
switch (sensorType) {
case FUZZY_PER_DAB:
- return new KisDynamicSensorFuzzy(false);
+ return new KisDynamicSensorFuzzy(false, parentOptionName);
case FUZZY_PER_STROKE:
- return new KisDynamicSensorFuzzy(true);
+ return new KisDynamicSensorFuzzy(true, parentOptionName);
case SPEED:
return new KisDynamicSensorSpeed();
case FADE:
return new KisDynamicSensorFade();
case DISTANCE:
return new KisDynamicSensorDistance();
case TIME:
return new KisDynamicSensorTime();
case ANGLE:
return new KisDynamicSensorDrawingAngle();
case ROTATION:
return new KisDynamicSensorRotation();
case PRESSURE:
return new KisDynamicSensorPressure();
case XTILT:
return new KisDynamicSensorXTilt();
case YTILT:
return new KisDynamicSensorYTilt();
case TILT_DIRECTION:
return new KisDynamicSensorTiltDirection();
case TILT_ELEVATATION:
return new KisDynamicSensorTiltElevation();
case PERSPECTIVE:
return new KisDynamicSensorPerspective();
case TANGENTIAL_PRESSURE:
return new KisDynamicSensorTangentialPressure();
case PRESSURE_IN:
return new KisDynamicSensorPressureIn();
default:
return 0;
}
}
QString KisDynamicSensor::minimumLabel(DynamicSensorType sensorType)
{
switch (sensorType) {
case FUZZY_PER_DAB:
case FUZZY_PER_STROKE:
return QString();
case FADE:
return i18n("0");
case DISTANCE:
return i18n("0 px");
case TIME:
return i18n("0 s");
case ANGLE:
return i18n("0°");
case SPEED:
return i18n("Slow");
case ROTATION:
return i18n("0°");
case PRESSURE:
return i18n("Low");
case XTILT:
return i18n("-30°");
case YTILT:
return i18n("-30°");
case TILT_DIRECTION:
return i18n("0°");
case TILT_ELEVATATION:
return i18n("90°");
case PERSPECTIVE:
return i18n("Far");
case TANGENTIAL_PRESSURE:
case PRESSURE_IN:
return i18n("Low");
default:
return i18n("0.0");
}
}
QString KisDynamicSensor::maximumLabel(DynamicSensorType sensorType, int max)
{
switch (sensorType) {
case FUZZY_PER_DAB:
case FUZZY_PER_STROKE:
return QString();
case FADE:
if (max < 0)
return i18n("1000");
else
return i18n("%1", max);
case DISTANCE:
if (max < 0)
return i18n("30 px");
else
return i18n("%1 px", max);
case TIME:
if (max < 0)
return i18n("3 s");
else
return i18n("%1 s", max / 1000);
case ANGLE:
return i18n("360°");
case SPEED:
return i18n("Fast");
case ROTATION:
return i18n("360°");
case PRESSURE:
return i18n("High");
case XTILT:
return i18n("30°");
case YTILT:
return i18n("30°");
case TILT_DIRECTION:
return i18n("360°");
case TILT_ELEVATATION:
return i18n("0°");
case PERSPECTIVE:
return i18n("Near");
case TANGENTIAL_PRESSURE:
case PRESSURE_IN:
return i18n("High");
default:
return i18n("1.0");
};
}
-KisDynamicSensorSP KisDynamicSensor::createFromXML(const QString& s)
+KisDynamicSensorSP KisDynamicSensor::createFromXML(const QString& s, const QString &parentOptionName)
{
QDomDocument doc;
doc.setContent(s);
QDomElement e = doc.documentElement();
- return createFromXML(e);
+ return createFromXML(e, parentOptionName);
}
-KisDynamicSensorSP KisDynamicSensor::createFromXML(const QDomElement& e)
+KisDynamicSensorSP KisDynamicSensor::createFromXML(const QDomElement& e, const QString &parentOptionName)
{
QString id = e.attribute("id", "");
- KisDynamicSensorSP sensor = id2Sensor(id);
+ KisDynamicSensorSP sensor = id2Sensor(id, parentOptionName);
if (sensor) {
sensor->fromXML(e);
}
return sensor;
}
QList<KoID> KisDynamicSensor::sensorsIds()
{
QList<KoID> ids;
ids << PressureId
<< PressureInId
<< XTiltId
<< YTiltId
<< TiltDirectionId
<< TiltElevationId
<< SpeedId
<< DrawingAngleId
<< RotationId
<< DistanceId
<< TimeId
<< FuzzyPerDabId
<< FuzzyPerStrokeId
<< FadeId
<< PerspectiveId
<< TangentialPressureId;
return ids;
}
QList<DynamicSensorType> KisDynamicSensor::sensorsTypes()
{
QList<DynamicSensorType> sensorTypes;
sensorTypes
<< PRESSURE
<< PRESSURE_IN
<< XTILT
<< YTILT
<< TILT_DIRECTION
<< TILT_ELEVATATION
<< SPEED
<< ANGLE
<< ROTATION
<< DISTANCE
<< TIME
<< FUZZY_PER_DAB
<< FUZZY_PER_STROKE
<< FADE
<< PERSPECTIVE
<< TANGENTIAL_PRESSURE;
return sensorTypes;
}
QString KisDynamicSensor::id(DynamicSensorType sensorType)
{
switch (sensorType) {
case FUZZY_PER_DAB:
return "fuzzy";
case FUZZY_PER_STROKE:
return "fuzzystroke";
case FADE:
return "fade";
case DISTANCE:
return "distance";
case TIME:
return "time";
case ANGLE:
return "drawingangle";
case SPEED:
return "speed";
case ROTATION:
return "rotation";
case PRESSURE:
return "pressure";
case XTILT:
return "xtilt";
case YTILT:
return "ytilt";
case TILT_DIRECTION:
return "ascension";
case TILT_ELEVATATION:
return "declination";
case PERSPECTIVE:
return "perspective";
case TANGENTIAL_PRESSURE:
return "tangentialpressure";
case PRESSURE_IN:
return "pressurein";
case SENSORS_LIST:
return "sensorslist";
default:
return QString();
};
}
void KisDynamicSensor::toXML(QDomDocument& doc, QDomElement& elt) const
{
elt.setAttribute("id", id(sensorType()));
if (m_customCurve) {
QDomElement curve_elt = doc.createElement("curve");
QDomText text = doc.createTextNode(m_curve.toString());
curve_elt.appendChild(text);
elt.appendChild(curve_elt);
}
}
void KisDynamicSensor::fromXML(const QDomElement& e)
{
Q_ASSERT(e.attribute("id", "") == id(sensorType()));
m_customCurve = false;
QDomElement curve_elt = e.firstChildElement("curve");
if (!curve_elt.isNull()) {
m_customCurve = true;
m_curve.fromString(curve_elt.text());
}
}
qreal KisDynamicSensor::parameter(const KisPaintInformation& info)
{
const qreal val = value(info);
if (m_customCurve) {
qreal scaledVal = isAdditive() ? additiveToScaling(val) : val;
int offset = qRound(256.0 * qAbs(scaledVal));
qreal newValue = m_curve.floatTransfer(257)[qBound(0, offset, 256)];
scaledVal = KisAlgebra2D::copysign(newValue, scaledVal);
return isAdditive() ? scalingToAdditive(scaledVal) : scaledVal;
}
else {
return val;
}
}
void KisDynamicSensor::setCurve(const KisCubicCurve& curve)
{
m_customCurve = true;
m_curve = curve;
}
const KisCubicCurve& KisDynamicSensor::curve() const
{
return m_curve;
}
void KisDynamicSensor::removeCurve()
{
m_customCurve = false;
}
bool KisDynamicSensor::hasCustomCurve() const
{
return m_customCurve;
}
bool KisDynamicSensor::dependsOnCanvasRotation() const
{
return true;
}
bool KisDynamicSensor::isAdditive() const
{
return false;
}
bool KisDynamicSensor::isAbsoluteRotation() const
{
return false;
}
void KisDynamicSensor::setActive(bool active)
{
m_active = active;
}
bool KisDynamicSensor::isActive() const
{
return m_active;
}
diff --git a/plugins/paintops/libpaintop/kis_dynamic_sensor.h b/plugins/paintops/libpaintop/kis_dynamic_sensor.h
index b852f0a44e..ae26dbb1e2 100644
--- a/plugins/paintops/libpaintop/kis_dynamic_sensor.h
+++ b/plugins/paintops/libpaintop/kis_dynamic_sensor.h
@@ -1,217 +1,217 @@
/*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_SENSOR_H_
#define _KIS_DYNAMIC_SENSOR_H_
#include <kritapaintop_export.h>
#include <QObject>
#include <KoID.h>
#include <klocalizedstring.h>
#include "kis_serializable_configuration.h"
#include "kis_curve_label.h"
#include <kis_cubic_curve.h>
#include <kis_shared_ptr.h>
#include <kis_shared.h>
class QWidget;
class KisPaintInformation;
const KoID FuzzyPerDabId("fuzzy", ki18n("Fuzzy Dab")); ///< generate a random number
const KoID FuzzyPerStrokeId("fuzzystroke", ki18n("Fuzzy Stroke")); ///< generate a random number
const KoID SpeedId("speed", ki18n("Speed")); ///< generate a number depending on the speed of the cursor
const KoID FadeId("fade", ki18n("Fade")); ///< generate a number that increase every time you call it (e.g. per dab)
const KoID DistanceId("distance", ki18n("Distance")); ///< generate a number that increase with distance
const KoID TimeId("time", ki18n("Time")); ///< generate a number that increase with time
const KoID DrawingAngleId("drawingangle", ki18n("Drawing angle")); ///< number depending on the angle
const KoID RotationId("rotation", ki18n("Rotation")); ///< rotation coming from the device
const KoID PressureId("pressure", ki18n("Pressure")); ///< number depending on the pressure
const KoID PressureInId("pressurein", ki18n("PressureIn")); ///< number depending on the pressure
const KoID XTiltId("xtilt", ki18n("X-Tilt")); ///< number depending on X-tilt
const KoID YTiltId("ytilt", ki18n("Y-Tilt")); ///< number depending on Y-tilt
/**
* "TiltDirection" and "TiltElevation" parameters are written to
* preset files as "ascension" and "declination" to keep backward
* compatibility with older presets from the days when they were called
* differently.
*/
const KoID TiltDirectionId("ascension", ki18n("Tilt direction")); /// < number depending on the X and Y tilt, tilt direction is 0 when stylus nib points to you and changes clockwise from -180 to +180.
const KoID TiltElevationId("declination", ki18n("Tilt elevation")); /// < tilt elevation is 90 when stylus is perpendicular to tablet and 0 when it's parallel to tablet
const KoID PerspectiveId("perspective", ki18n("Perspective")); ///< number depending on the distance on the perspective grid
const KoID TangentialPressureId("tangentialpressure", ki18n("Tangential pressure")); ///< the wheel on an airbrush device
const KoID SensorsListId("sensorslist", "SHOULD NOT APPEAR IN THE UI !"); ///< this a non user-visible sensor that can store a list of other sensors, and multiply their output
class KisDynamicSensor;
typedef KisSharedPtr<KisDynamicSensor> KisDynamicSensorSP;
enum DynamicSensorType {
FUZZY_PER_DAB,
FUZZY_PER_STROKE,
SPEED,
FADE,
DISTANCE,
TIME,
ANGLE,
ROTATION,
PRESSURE,
XTILT,
YTILT,
TILT_DIRECTION,
TILT_ELEVATATION,
PERSPECTIVE,
TANGENTIAL_PRESSURE,
SENSORS_LIST,
PRESSURE_IN,
UNKNOWN = 255
};
/**
* Sensors are used to extract from KisPaintInformation a single
* double value which can be used to control the parameters of
* a brush.
*/
class PAINTOP_EXPORT KisDynamicSensor : public KisSerializableConfiguration
{
public:
enum ParameterSign {
NegativeParameter = -1,
UnSignedParameter = 0,
PositiveParameter = 1
};
protected:
KisDynamicSensor(DynamicSensorType type);
public:
~KisDynamicSensor() override;
/**
* @return the value of this sensor for the given KisPaintInformation
*/
qreal parameter(const KisPaintInformation& info);
/**
* This function is call before beginning a stroke to reset the sensor.
* Default implementation does nothing.
*/
virtual void reset();
/**
* @param selector is a \ref QWidget that countains a signal called "parametersChanged()"
*/
virtual QWidget* createConfigurationWidget(QWidget* parent, QWidget* selector);
/**
* Creates a sensor from its identifiant.
*/
- static KisDynamicSensorSP id2Sensor(const KoID& id);
- static KisDynamicSensorSP id2Sensor(const QString& s) {
- return id2Sensor(KoID(s));
+ static KisDynamicSensorSP id2Sensor(const KoID& id, const QString &parentOptionName);
+ static KisDynamicSensorSP id2Sensor(const QString& s, const QString &parentOptionName) {
+ return id2Sensor(KoID(s), parentOptionName);
}
static DynamicSensorType id2Type(const KoID& id);
static DynamicSensorType id2Type(const QString& s) {
return id2Type(KoID(s));
}
/**
* type2Sensor creates a new sensor for the give type
*/
- static KisDynamicSensorSP type2Sensor(DynamicSensorType sensorType);
+ static KisDynamicSensorSP type2Sensor(DynamicSensorType sensorType, const QString &parentOptionName);
static QString minimumLabel(DynamicSensorType sensorType);
static QString maximumLabel(DynamicSensorType sensorType, int max = -1);
- static KisDynamicSensorSP createFromXML(const QString&);
- static KisDynamicSensorSP createFromXML(const QDomElement&);
+ static KisDynamicSensorSP createFromXML(const QString&, const QString &parentOptionName);
+ static KisDynamicSensorSP createFromXML(const QDomElement&, const QString &parentOptionName);
/**
* @return the list of sensors
*/
static QList<KoID> sensorsIds();
static QList<DynamicSensorType> sensorsTypes();
/**
* @return the identifiant of this sensor
*/
static QString id(DynamicSensorType sensorType);
using KisSerializableConfiguration::fromXML;
using KisSerializableConfiguration::toXML;
void toXML(QDomDocument&, QDomElement&) const override;
void fromXML(const QDomElement&) override;
void setCurve(const KisCubicCurve& curve);
const KisCubicCurve& curve() const;
void removeCurve();
bool hasCustomCurve() const;
void setActive(bool active);
bool isActive() const;
virtual bool dependsOnCanvasRotation() const;
virtual bool isAdditive() const;
virtual bool isAbsoluteRotation() const;
inline DynamicSensorType sensorType() const { return m_type; }
/**
* @return the currently set length or -1 if not relevant
*/
int length() { return m_length; }
public:
static inline qreal scalingToAdditive(qreal x) {
return -1.0 + 2.0 * x;
}
static inline qreal additiveToScaling(qreal x) {
return 0.5 * (1.0 + x);
}
protected:
virtual qreal value(const KisPaintInformation& info) = 0;
int m_length;
private:
Q_DISABLE_COPY(KisDynamicSensor)
DynamicSensorType m_type;
bool m_customCurve;
KisCubicCurve m_curve;
bool m_active;
};
#endif
diff --git a/plugins/paintops/libpaintop/kis_multi_sensors_model_p.cpp b/plugins/paintops/libpaintop/kis_multi_sensors_model_p.cpp
index f7cd7237e2..7defecc556 100644
--- a/plugins/paintops/libpaintop/kis_multi_sensors_model_p.cpp
+++ b/plugins/paintops/libpaintop/kis_multi_sensors_model_p.cpp
@@ -1,125 +1,124 @@
/*
* Copyright (c) 2011 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_multi_sensors_model_p.h"
#include "kis_dynamic_sensor.h"
#include "kis_curve_option.h"
KisMultiSensorsModel::KisMultiSensorsModel(QObject* parent)
: QAbstractListModel(parent)
, m_curveOption(0)
{
}
KisMultiSensorsModel::~KisMultiSensorsModel()
{
}
void KisMultiSensorsModel::setCurveOption(KisCurveOption *curveOption)
{
beginResetModel();
m_curveOption = curveOption;
endResetModel();
}
int KisMultiSensorsModel::rowCount(const QModelIndex &/*parent*/) const
{
if (m_curveOption) {
return m_curveOption->sensors().size();
}
else {
return 0;
}
}
QVariant KisMultiSensorsModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();
if (role == Qt::DisplayRole) {
return KisDynamicSensor::sensorsIds()[index.row()].name();
}
else if (role == Qt::CheckStateRole) {
QString selectedSensorId = KisDynamicSensor::sensorsIds()[index.row()].id();
KisDynamicSensorSP sensor = m_curveOption->sensor(KisDynamicSensor::id2Type(selectedSensorId), false);
if (sensor) {
//dbgKrita << sensor->id() << sensor->isActive();
return QVariant(sensor->isActive() ? Qt::Checked : Qt::Unchecked);
}
else {
return QVariant(Qt::Unchecked);
}
}
return QVariant();
}
bool KisMultiSensorsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
bool result = false;
if (role == Qt::CheckStateRole) {
bool checked = (value.toInt() == Qt::Checked);
if (checked || m_curveOption->activeSensors().size() != 1) { // Don't uncheck the last sensor (but why not?)
KisDynamicSensorSP sensor = m_curveOption->sensor(KisDynamicSensor::id2Type(KisDynamicSensor::sensorsIds()[index.row()].id()), false);
if (!sensor) {
- sensor = KisDynamicSensor::id2Sensor(KisDynamicSensor::sensorsIds()[index.row()].id());
+ sensor = KisDynamicSensor::id2Sensor(KisDynamicSensor::sensorsIds()[index.row()].id(), "NOT_VALID_NAME");
m_curveOption->replaceSensor(sensor);
}
sensor->setActive(checked);
emit(parametersChanged());
result = true;
}
}
return result;
}
Qt::ItemFlags KisMultiSensorsModel::flags(const QModelIndex & /*index */) const
{
return Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled;
}
KisDynamicSensorSP KisMultiSensorsModel::getSensor(const QModelIndex& index)
{
if (!index.isValid()) return 0;
QString id = KisDynamicSensor::sensorsIds()[index.row()].id();
return m_curveOption->sensor(KisDynamicSensor::id2Type(id), false);
}
void KisMultiSensorsModel::setCurrentCurve(const QModelIndex& currentIndex, const KisCubicCurve& curve, bool useSameCurve)
{
if (!currentIndex.isValid()) return;
QString selectedSensorId = KisDynamicSensor::sensorsIds()[currentIndex.row()].id();
m_curveOption->setCurve(KisDynamicSensor::id2Type(selectedSensorId), useSameCurve, curve);
}
QModelIndex KisMultiSensorsModel::sensorIndex(KisDynamicSensorSP arg1)
{
return index(KisDynamicSensor::sensorsIds().indexOf(KoID(KisDynamicSensor::id(arg1->sensorType()))));
}
void KisMultiSensorsModel::resetCurveOption()
{
beginResetModel();
- reset();
endResetModel();
}
diff --git a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp b/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp
index c05e72927e..007f585323 100644
--- a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp
+++ b/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.cpp
@@ -1,91 +1,103 @@
/*
* Copyright (c) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_pressure_flow_opacity_option.h"
#include "kis_paint_action_type_option.h"
#include <klocalizedstring.h>
#include <kis_painter.h>
#include <brushengine/kis_paint_information.h>
#include <kis_indirect_painting_support.h>
#include <kis_node.h>
#include <widgets/kis_curve_widget.h>
KisFlowOpacityOption::KisFlowOpacityOption(KisNodeSP currentNode)
: KisCurveOption("Opacity", KisPaintOpOption::GENERAL, true, 1.0, 0.0, 1.0)
, m_flow(1.0)
{
setCurveUsed(true);
setSeparateCurveValue(true);
m_checkable = false;
m_nodeHasIndirectPaintingSupport =
currentNode &&
dynamic_cast<KisIndirectPaintingSupport*>(currentNode.data());
}
void KisFlowOpacityOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const
{
KisCurveOption::writeOptionSetting(setting);
setting->setProperty("FlowValue", m_flow);
}
void KisFlowOpacityOption::readOptionSetting(const KisPropertiesConfigurationSP setting)
{
KisCurveOption::readOptionSetting(setting);
setFlow(setting->getDouble("FlowValue", 1.0));
m_paintActionType = setting->getInt("PaintOpAction", BUILDUP);
}
qreal KisFlowOpacityOption::getFlow() const
{
return m_flow;
}
qreal KisFlowOpacityOption::getStaticOpacity() const
{
return value();
}
qreal KisFlowOpacityOption::getDynamicOpacity(const KisPaintInformation& info) const
{
return computeSizeLikeValue(info);
}
void KisFlowOpacityOption::setFlow(qreal flow)
{
m_flow = qBound(qreal(0), flow, qreal(1));
}
void KisFlowOpacityOption::setOpacity(qreal opacity)
{
setValue(opacity);
}
void KisFlowOpacityOption::apply(KisPainter* painter, const KisPaintInformation& info)
{
- if (m_paintActionType == WASH && m_nodeHasIndirectPaintingSupport)
- painter->setOpacityUpdateAverage(quint8(getDynamicOpacity(info) * 255.0));
- else
- painter->setOpacityUpdateAverage(quint8(getStaticOpacity() * getDynamicOpacity(info) * 255.0));
+ quint8 opacity = OPACITY_OPAQUE_U8;
+ quint8 flow = OPACITY_OPAQUE_U8;
- painter->setFlow(quint8(getFlow() * 255.0));
+ apply(info, &opacity, &flow);
+
+ painter->setOpacityUpdateAverage(opacity);
+ painter->setFlow(flow);
+}
+
+void KisFlowOpacityOption::apply(const KisPaintInformation &info, quint8 *opacity, quint8 *flow)
+{
+ if (m_paintActionType == WASH && m_nodeHasIndirectPaintingSupport) {
+ *opacity = quint8(getDynamicOpacity(info) * 255.0);
+ } else {
+ *opacity = quint8(getStaticOpacity() * getDynamicOpacity(info) * 255.0);
+ }
+
+ *flow = quint8(getFlow() * 255.0);
}
diff --git a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h b/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h
index f822372fe6..bb52cf6ec9 100644
--- a/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h
+++ b/plugins/paintops/libpaintop/kis_pressure_flow_opacity_option.h
@@ -1,54 +1,55 @@
/*
* Copyright (c) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PRESSURE_FLOW_OPACITY_OPTION_H
#define KIS_PRESSURE_FLOW_OPACITY_OPTION_H
#include "kis_curve_option.h"
#include <kritapaintop_export.h>
#include <kis_types.h>
#include <brushengine/kis_paintop_settings.h>
class KisPaintInformation;
class KisPainter;
class PAINTOP_EXPORT KisFlowOpacityOption: public KisCurveOption
{
public:
KisFlowOpacityOption(KisNodeSP currentNode);
~KisFlowOpacityOption() override { }
void writeOptionSetting(KisPropertiesConfigurationSP setting) const override;
void readOptionSetting(const KisPropertiesConfigurationSP setting) override;
void setFlow(qreal flow);
void setOpacity(qreal opacity);
void apply(KisPainter* painter, const KisPaintInformation& info);
+ void apply(const KisPaintInformation& info, quint8 *opacity, quint8 *flow);
qreal getFlow() const;
qreal getStaticOpacity() const;
qreal getDynamicOpacity(const KisPaintInformation& info) const;
protected:
qreal m_flow;
int m_paintActionType;
bool m_nodeHasIndirectPaintingSupport;
};
#endif //KIS_PRESSURE_FLOW_OPACITY_OPTION_H
diff --git a/plugins/paintops/libpaintop/kis_texture_chooser.cpp b/plugins/paintops/libpaintop/kis_texture_chooser.cpp
new file mode 100644
index 0000000000..e3dbc1562e
--- /dev/null
+++ b/plugins/paintops/libpaintop/kis_texture_chooser.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "kis_texture_chooser.h"
+#include "kis_texture_option.h"
+
+KisTextureChooser::KisTextureChooser(QWidget *parent)
+ : QWidget(parent)
+{
+ setupUi(this);
+
+ textureSelectorWidget->setGrayscalePreview(true);
+ textureSelectorWidget->setCurrentItem(0, 0);
+
+ scaleSlider->setRange(0.0, 2.0, 2);
+ scaleSlider->setValue(1.0);
+ scaleSlider->addMultiplier(0.1);
+ scaleSlider->addMultiplier(2);
+ scaleSlider->addMultiplier(10);
+
+ brightnessSlider->setRange(-1.0, 1.0, 2);
+ brightnessSlider->setValue(0.0);
+ brightnessSlider->setToolTip(i18n("Makes texture lighter or darker"));
+
+ contrastSlider->setRange(0.0, 2.0, 2);
+ contrastSlider->setValue(1.0);
+
+ offsetSliderX->setSuffix(i18n(" px"));
+
+ offsetSliderY->setSuffix(i18n(" px"));
+
+
+ QStringList texturingModes;
+ texturingModes << i18n("Multiply") << i18n("Subtract");
+ cmbTexturingMode->addItems(texturingModes);
+ cmbTexturingMode->setCurrentIndex(KisTextureProperties::SUBTRACT);
+
+ QStringList cutOffPolicies;
+ cutOffPolicies << i18n("Cut Off Disabled") << i18n("Cut Off Brush") << i18n("Cut Off Pattern");
+ cmbCutoffPolicy->addItems(cutOffPolicies);
+
+
+ cutoffSlider->setMinimumSize(256, 30);
+ cutoffSlider->enableGamma(false);
+ cutoffSlider->setToolTip(i18n("When pattern texture values are outside the range specified"
+ " by the slider, the cut-off policy will be applied."));
+
+ chkInvert->setChecked(false);
+}
+
+KisTextureChooser::~KisTextureChooser()
+{
+
+}
diff --git a/plugins/paintops/libpaintop/kis_texture_chooser.h b/plugins/paintops/libpaintop/kis_texture_chooser.h
new file mode 100644
index 0000000000..2fb870ccb8
--- /dev/null
+++ b/plugins/paintops/libpaintop/kis_texture_chooser.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2017 Scott Petrovic <scottpetrovic@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _KIS_TEXTURE_CHOOSER_H_
+#define _KIS_TEXTURE_CHOOSER_H_
+
+#include "ui_wdgtexturechooser.h"
+
+class KisTextureChooser : public QWidget, public Ui::KisWdgTextureChooser
+{
+ Q_OBJECT
+
+public:
+ KisTextureChooser(QWidget *parent = 0);
+ ~KisTextureChooser();
+};
+
+#endif
diff --git a/plugins/paintops/libpaintop/kis_texture_option.cpp b/plugins/paintops/libpaintop/kis_texture_option.cpp
index 9b39049bfb..97586937c5 100644
--- a/plugins/paintops/libpaintop/kis_texture_option.cpp
+++ b/plugins/paintops/libpaintop/kis_texture_option.cpp
@@ -1,450 +1,349 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2012
* Copyright (C) Mohit Goyal <mohit.bits2011@gmail.com>, (C) 2014
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_texture_option.h"
#include <QWidget>
#include <QString>
#include <QByteArray>
#include <QCheckBox>
#include <QBuffer>
#include <QFormLayout>
#include <QLabel>
#include <QComboBox>
#include <QTransform>
#include <QPainter>
#include <QBoxLayout>
#include <klocalizedstring.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <kis_resource_server_provider.h>
#include <kis_pattern_chooser.h>
#include <kis_slider_spin_box.h>
#include <kis_multipliers_double_slider_spinbox.h>
#include <resources/KoPattern.h>
#include <kis_paint_device.h>
#include <kis_fill_painter.h>
#include <kis_painter.h>
#include <kis_iterator_ng.h>
#include <kis_fixed_paint_device.h>
#include <kis_gradient_slider.h>
#include "kis_embedded_pattern_manager.h"
#include "kis_algebra_2d.h"
#include "kis_lod_transform.h"
#include <brushengine/kis_paintop_lod_limitations.h>
-
-
+#include "kis_texture_chooser.h"
#include <time.h>
-class KisTextureOptionWidget : public QWidget
-{
-public:
-
- KisTextureOptionWidget(QWidget *parent = 0)
- : QWidget(parent) {
- QFormLayout *formLayout = new QFormLayout(this);
- formLayout->setMargin(0);
-
- chooser = new KisPatternChooser(this);
- chooser->setGrayscalePreview(true);
- chooser->setMaximumHeight(250);
- chooser->setCurrentItem(0, 0);
- formLayout->addRow(chooser);
-
- scaleSlider = new KisMultipliersDoubleSliderSpinBox(this);
- scaleSlider->setRange(0.0, 2.0, 2);
- scaleSlider->setValue(1.0);
- scaleSlider->addMultiplier(0.1);
- scaleSlider->addMultiplier(2);
- scaleSlider->addMultiplier(10);
-
- formLayout->addRow(i18n("Scale:"), scaleSlider);
-
- brightnessSlider = new KisDoubleSliderSpinBox(this);
- brightnessSlider->setRange(-1.0, 1.0, 2);
- brightnessSlider->setValue(0.0);
- brightnessSlider->setToolTip(i18n("Makes texture lighter or darker"));
- brightnessSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
-
- formLayout->addRow(i18n("Brightness:"), brightnessSlider);
-
- contrastSlider = new KisDoubleSliderSpinBox(this);
- contrastSlider->setRange(0.0, 2.0, 2);
- contrastSlider->setValue(1.0);
- contrastSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
-
- formLayout->addRow(i18n("Contrast:"), contrastSlider);
-
-
- QBoxLayout *offsetLayoutX = new QBoxLayout(QBoxLayout::LeftToRight);
- offsetSliderX = new KisSliderSpinBox(this);
- offsetSliderX->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
- offsetSliderX->setSuffix(i18n(" px"));
- randomOffsetX = new QCheckBox(i18n("Random Offset"),this);
- offsetLayoutX->addWidget(offsetSliderX,1,0);
- offsetLayoutX->addWidget(randomOffsetX,0,0);
- formLayout->addRow(i18n("Horizontal Offset:"), offsetLayoutX);
-
-
- QBoxLayout *offsetLayoutY = new QBoxLayout(QBoxLayout::LeftToRight);
- offsetSliderY = new KisSliderSpinBox(this);
- offsetSliderY->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
- offsetSliderY->setSuffix(i18n(" px"));
- randomOffsetY = new QCheckBox(i18n("Random Offset"),this);
- offsetLayoutY->addWidget(offsetSliderY,1,0);
- offsetLayoutY->addWidget(randomOffsetY,0,0);
- formLayout->addRow(i18n("Vertical Offset:"), offsetLayoutY);
-
- cmbTexturingMode = new QComboBox(this);
- cmbTexturingMode->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
- QStringList texturingModes;
- texturingModes << i18n("Multiply") << i18n("Subtract");
- cmbTexturingMode->addItems(texturingModes);
- formLayout->addRow(i18n("Texturing Mode:"), cmbTexturingMode);
- cmbTexturingMode->setCurrentIndex(KisTextureProperties::SUBTRACT);
-
- cmbCutoffPolicy = new QComboBox(this);
- cmbCutoffPolicy->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
- QStringList cutOffPolicies;
- cutOffPolicies << i18n("Cut Off Disabled") << i18n("Cut Off Brush") << i18n("Cut Off Pattern");
- cmbCutoffPolicy->addItems(cutOffPolicies);
- formLayout->addRow(i18n("Cutoff Policy:"), cmbCutoffPolicy);
-
- cutoffSlider = new KisGradientSlider(this);
- cutoffSlider->setMinimumSize(256, 30);
- cutoffSlider->enableGamma(false);
- cutoffSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
- cutoffSlider->setToolTip(i18n("When pattern texture values are outside the range specified"
- " by the slider, the cut-off policy will be applied."));
- formLayout->addRow(i18n("Cutoff:"), cutoffSlider);
-
- chkInvert = new QCheckBox(this);
- chkInvert->setChecked(false);
- formLayout->addRow(i18n("Invert Pattern:"), chkInvert);
-
- setLayout(formLayout);
- }
- KisPatternChooser *chooser;
- KisMultipliersDoubleSliderSpinBox *scaleSlider;
- KisDoubleSliderSpinBox *brightnessSlider;
- KisDoubleSliderSpinBox *contrastSlider;
- KisSliderSpinBox *offsetSliderX;
- QCheckBox *randomOffsetX;
- KisSliderSpinBox *offsetSliderY;
- QCheckBox *randomOffsetY;
- QComboBox *cmbTexturingMode;
- KisGradientSlider *cutoffSlider;
- QComboBox *cmbCutoffPolicy;
- QCheckBox *chkInvert;
-};
KisTextureOption::KisTextureOption()
: KisPaintOpOption(KisPaintOpOption::TEXTURE, true)
+ , m_textureOptions(new KisTextureChooser())
{
setObjectName("KisTextureOption");
+ setConfigurationPage(m_textureOptions);
+
+ connect(m_textureOptions->textureSelectorWidget, SIGNAL(resourceSelected(KoResource*)), SLOT(resetGUI(KoResource*)));
+ connect(m_textureOptions->textureSelectorWidget, SIGNAL(resourceSelected(KoResource*)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->scaleSlider, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->brightnessSlider, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->contrastSlider, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->offsetSliderX, SIGNAL(valueChanged(int)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->randomOffsetX, SIGNAL(toggled(bool)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->randomOffsetY, SIGNAL(toggled(bool)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->offsetSliderY, SIGNAL(valueChanged(int)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->cmbTexturingMode, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->cmbCutoffPolicy, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->cutoffSlider, SIGNAL(sigModifiedBlack(int)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->cutoffSlider, SIGNAL(sigModifiedWhite(int)), SLOT(emitSettingChanged()));
+ connect(m_textureOptions->chkInvert, SIGNAL(toggled(bool)), SLOT(emitSettingChanged()));
+ resetGUI(m_textureOptions->textureSelectorWidget->currentResource());
- setChecked(false);
- m_optionWidget = new KisTextureOptionWidget;
- m_optionWidget->hide();
- setConfigurationPage(m_optionWidget);
-
- connect(m_optionWidget->chooser, SIGNAL(resourceSelected(KoResource*)), SLOT(resetGUI(KoResource*)));
- connect(m_optionWidget->chooser, SIGNAL(resourceSelected(KoResource*)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->scaleSlider, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->brightnessSlider, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->contrastSlider, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->offsetSliderX, SIGNAL(valueChanged(int)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->randomOffsetX, SIGNAL(toggled(bool)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->randomOffsetY, SIGNAL(toggled(bool)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->offsetSliderY, SIGNAL(valueChanged(int)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->cmbTexturingMode, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->cmbCutoffPolicy, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->cutoffSlider, SIGNAL(sigModifiedBlack(int)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->cutoffSlider, SIGNAL(sigModifiedWhite(int)), SLOT(emitSettingChanged()));
- connect(m_optionWidget->chkInvert, SIGNAL(toggled(bool)), SLOT(emitSettingChanged()));
- resetGUI(m_optionWidget->chooser->currentResource());
}
KisTextureOption::~KisTextureOption()
{
- delete m_optionWidget;
+ delete m_textureOptions;
}
void KisTextureOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const
{
- m_optionWidget->chooser->blockSignals(true); // Checking
- if (!m_optionWidget->chooser->currentResource()) return;
- KoPattern *pattern = static_cast<KoPattern*>(m_optionWidget->chooser->currentResource());
- m_optionWidget->chooser->blockSignals(false); // Checking
+ m_textureOptions->textureSelectorWidget->blockSignals(true); // Checking
+ if (!m_textureOptions->textureSelectorWidget->currentResource()) return;
+ KoPattern *pattern = static_cast<KoPattern*>(m_textureOptions->textureSelectorWidget->currentResource());
+ m_textureOptions->textureSelectorWidget->blockSignals(false); // Checking
if (!pattern) return;
setting->setProperty("Texture/Pattern/Enabled", isChecked());
if (!isChecked()) {
return;
}
- qreal scale = m_optionWidget->scaleSlider->value();
+ qreal scale = m_textureOptions->scaleSlider->value();
- qreal brightness = m_optionWidget->brightnessSlider->value();
+ qreal brightness = m_textureOptions->brightnessSlider->value();
- qreal contrast = m_optionWidget->contrastSlider->value();
+ qreal contrast = m_textureOptions->contrastSlider->value();
- int offsetX = m_optionWidget->offsetSliderX->value();
- if (m_optionWidget ->randomOffsetX->isChecked()) {
+ int offsetX = m_textureOptions->offsetSliderX->value();
+ if (m_textureOptions ->randomOffsetX->isChecked()) {
- m_optionWidget->offsetSliderX ->setEnabled(false);
- m_optionWidget->offsetSliderX ->blockSignals(true);
- m_optionWidget->offsetSliderX ->setValue(offsetX);
- m_optionWidget->offsetSliderX ->blockSignals(false);
+ m_textureOptions->offsetSliderX ->setEnabled(false);
+ m_textureOptions->offsetSliderX ->blockSignals(true);
+ m_textureOptions->offsetSliderX ->setValue(offsetX);
+ m_textureOptions->offsetSliderX ->blockSignals(false);
}
else {
- m_optionWidget->offsetSliderX ->setEnabled(true);
+ m_textureOptions->offsetSliderX ->setEnabled(true);
}
- int offsetY = m_optionWidget->offsetSliderY->value();
- if (m_optionWidget ->randomOffsetY->isChecked()) {
+ int offsetY = m_textureOptions->offsetSliderY->value();
+ if (m_textureOptions ->randomOffsetY->isChecked()) {
- m_optionWidget->offsetSliderY ->setEnabled(false);
- m_optionWidget->offsetSliderY ->blockSignals(true);
- m_optionWidget->offsetSliderY ->setValue(offsetY);
- m_optionWidget->offsetSliderY ->blockSignals(false);
+ m_textureOptions->offsetSliderY ->setEnabled(false);
+ m_textureOptions->offsetSliderY ->blockSignals(true);
+ m_textureOptions->offsetSliderY ->setValue(offsetY);
+ m_textureOptions->offsetSliderY ->blockSignals(false);
}
else {
- m_optionWidget->offsetSliderY ->setEnabled(true);
+ m_textureOptions->offsetSliderY ->setEnabled(true);
}
- int texturingMode = m_optionWidget->cmbTexturingMode->currentIndex();
- bool invert = (m_optionWidget->chkInvert->checkState() == Qt::Checked);
+ int texturingMode = m_textureOptions->cmbTexturingMode->currentIndex();
+ bool invert = (m_textureOptions->chkInvert->checkState() == Qt::Checked);
setting->setProperty("Texture/Pattern/Scale", scale);
setting->setProperty("Texture/Pattern/Brightness", brightness);
setting->setProperty("Texture/Pattern/Contrast", contrast);
setting->setProperty("Texture/Pattern/OffsetX", offsetX);
setting->setProperty("Texture/Pattern/OffsetY", offsetY);
setting->setProperty("Texture/Pattern/TexturingMode", texturingMode);
- setting->setProperty("Texture/Pattern/CutoffLeft", m_optionWidget->cutoffSlider->black());
- setting->setProperty("Texture/Pattern/CutoffRight", m_optionWidget->cutoffSlider->white());
- setting->setProperty("Texture/Pattern/CutoffPolicy", m_optionWidget->cmbCutoffPolicy->currentIndex());
+ setting->setProperty("Texture/Pattern/CutoffLeft", m_textureOptions->cutoffSlider->black());
+ setting->setProperty("Texture/Pattern/CutoffRight", m_textureOptions->cutoffSlider->white());
+ setting->setProperty("Texture/Pattern/CutoffPolicy", m_textureOptions->cmbCutoffPolicy->currentIndex());
setting->setProperty("Texture/Pattern/Invert", invert);
- setting->setProperty("Texture/Pattern/MaximumOffsetX",m_optionWidget->offsetSliderX ->maximum());
- setting->setProperty("Texture/Pattern/MaximumOffsetY",m_optionWidget->offsetSliderY ->maximum());
- setting->setProperty("Texture/Pattern/isRandomOffsetX",m_optionWidget ->randomOffsetX ->isChecked());
- setting->setProperty("Texture/Pattern/isRandomOffsetY",m_optionWidget ->randomOffsetY ->isChecked());
+ setting->setProperty("Texture/Pattern/MaximumOffsetX",m_textureOptions->offsetSliderX ->maximum());
+ setting->setProperty("Texture/Pattern/MaximumOffsetY",m_textureOptions->offsetSliderY ->maximum());
+ setting->setProperty("Texture/Pattern/isRandomOffsetX",m_textureOptions ->randomOffsetX ->isChecked());
+ setting->setProperty("Texture/Pattern/isRandomOffsetY",m_textureOptions ->randomOffsetY ->isChecked());
KisEmbeddedPatternManager::saveEmbeddedPattern(setting, pattern);
}
void KisTextureOption::readOptionSetting(const KisPropertiesConfigurationSP setting)
{
+
setChecked(setting->getBool("Texture/Pattern/Enabled"));
if (!isChecked()) {
return;
}
KoPattern *pattern = KisEmbeddedPatternManager::loadEmbeddedPattern(setting);
if (!pattern) {
- pattern = static_cast<KoPattern*>(m_optionWidget->chooser->currentResource());
+ pattern = static_cast<KoPattern*>(m_textureOptions->textureSelectorWidget->currentResource());
}
- m_optionWidget->chooser->setCurrentPattern(pattern);
-
- m_optionWidget->scaleSlider->setValue(setting->getDouble("Texture/Pattern/Scale", 1.0));
- m_optionWidget->brightnessSlider->setValue(setting->getDouble("Texture/Pattern/Brightness"));
- m_optionWidget->contrastSlider->setValue(setting->getDouble("Texture/Pattern/Contrast", 1.0));
- m_optionWidget->offsetSliderX->setValue(setting->getInt("Texture/Pattern/OffsetX"));
- m_optionWidget->offsetSliderY->setValue(setting->getInt("Texture/Pattern/OffsetY"));
- m_optionWidget->randomOffsetX->setChecked(setting->getBool("Texture/Pattern/isRandomOffsetX"));
- m_optionWidget->randomOffsetY->setChecked(setting->getBool("Texture/Pattern/isRandomOffsetY"));
- m_optionWidget->cmbTexturingMode->setCurrentIndex(setting->getInt("Texture/Pattern/TexturingMode", KisTextureProperties::MULTIPLY));
- m_optionWidget->cmbCutoffPolicy->setCurrentIndex(setting->getInt("Texture/Pattern/CutoffPolicy"));
- m_optionWidget->cutoffSlider->slotModifyBlack(setting->getInt("Texture/Pattern/CutoffLeft", 0));
- m_optionWidget->cutoffSlider->slotModifyWhite(setting->getInt("Texture/Pattern/CutoffRight", 255));
- m_optionWidget->chkInvert->setChecked(setting->getBool("Texture/Pattern/Invert"));
+ m_textureOptions->textureSelectorWidget->setCurrentPattern(pattern);
+
+ m_textureOptions->scaleSlider->setValue(setting->getDouble("Texture/Pattern/Scale", 1.0));
+ m_textureOptions->brightnessSlider->setValue(setting->getDouble("Texture/Pattern/Brightness"));
+ m_textureOptions->contrastSlider->setValue(setting->getDouble("Texture/Pattern/Contrast", 1.0));
+ m_textureOptions->offsetSliderX->setValue(setting->getInt("Texture/Pattern/OffsetX"));
+ m_textureOptions->offsetSliderY->setValue(setting->getInt("Texture/Pattern/OffsetY"));
+ m_textureOptions->randomOffsetX->setChecked(setting->getBool("Texture/Pattern/isRandomOffsetX"));
+ m_textureOptions->randomOffsetY->setChecked(setting->getBool("Texture/Pattern/isRandomOffsetY"));
+ m_textureOptions->cmbTexturingMode->setCurrentIndex(setting->getInt("Texture/Pattern/TexturingMode", KisTextureProperties::MULTIPLY));
+ m_textureOptions->cmbCutoffPolicy->setCurrentIndex(setting->getInt("Texture/Pattern/CutoffPolicy"));
+ m_textureOptions->cutoffSlider->slotModifyBlack(setting->getInt("Texture/Pattern/CutoffLeft", 0));
+ m_textureOptions->cutoffSlider->slotModifyWhite(setting->getInt("Texture/Pattern/CutoffRight", 255));
+ m_textureOptions->chkInvert->setChecked(setting->getBool("Texture/Pattern/Invert"));
+
}
void KisTextureOption::lodLimitations(KisPaintopLodLimitations *l) const
{
l->limitations << KoID("texture-pattern", i18nc("PaintOp instant preview limitation", "Texture->Pattern (low quality preview)"));
}
void KisTextureOption::resetGUI(KoResource* res)
{
KoPattern *pattern = static_cast<KoPattern *>(res);
if (!pattern) return;
- m_optionWidget->offsetSliderX->setRange(0, pattern->pattern().width() / 2);
- m_optionWidget->offsetSliderY->setRange(0, pattern->pattern().height() / 2);
+ m_textureOptions->offsetSliderX->setRange(0, pattern->pattern().width() / 2);
+ m_textureOptions->offsetSliderY->setRange(0, pattern->pattern().height() / 2);
}
KisTextureProperties::KisTextureProperties(int levelOfDetail)
: m_pattern(0),
m_levelOfDetail(levelOfDetail)
{
}
void KisTextureProperties::recalculateMask()
{
if (!m_pattern) return;
m_mask = 0;
QImage mask = m_pattern->pattern();
if ((mask.format() != QImage::Format_RGB32) |
(mask.format() != QImage::Format_ARGB32)) {
mask = mask.convertToFormat(QImage::Format_ARGB32);
}
qreal scale = m_scale * KisLodTransform::lodToScale(m_levelOfDetail);
if (!qFuzzyCompare(scale, 0.0)) {
QTransform tf;
tf.scale(scale, scale);
QRect rc = KisAlgebra2D::ensureRectNotSmaller(tf.mapRect(mask.rect()), QSize(2,2));
mask = mask.scaled(rc.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
const QRgb* pixel = reinterpret_cast<const QRgb*>(mask.constBits());
int width = mask.width();
int height = mask.height();
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8();
m_mask = new KisPaintDevice(cs);
KisHLineIteratorSP iter = m_mask->createHLineIteratorNG(0, 0, width);
for (int row = 0; row < height; ++row) {
for (int col = 0; col < width; ++col) {
const QRgb currentPixel = pixel[row * width + col];
const int red = qRed(currentPixel);
const int green = qGreen(currentPixel);
const int blue = qBlue(currentPixel);
float alpha = qAlpha(currentPixel) / 255.0;
const int grayValue = (red * 11 + green * 16 + blue * 5) / 32;
float maskValue = (grayValue / 255.0) * alpha + (1 - alpha);
maskValue = maskValue - m_brightness;
maskValue = ((maskValue - 0.5)*m_contrast)+0.5;
if (maskValue > 1.0) {maskValue = 1;}
else if (maskValue < 0) {maskValue = 0;}
if (m_invert) {
maskValue = 1 - maskValue;
}
if (m_cutoffPolicy == 1 && (maskValue < (m_cutoffLeft / 255.0) || maskValue > (m_cutoffRight / 255.0))) {
// mask out the dab if it's outside the pattern's cuttoff points
maskValue = OPACITY_TRANSPARENT_F;
}
else if (m_cutoffPolicy == 2 && (maskValue < (m_cutoffLeft / 255.0) || maskValue > (m_cutoffRight / 255.0))) {
maskValue = OPACITY_OPAQUE_F;
}
cs->setOpacity(iter->rawData(), maskValue, 1);
iter->nextPixel();
}
iter->nextRow();
}
m_maskBounds = QRect(0, 0, width, height);
}
void KisTextureProperties::fillProperties(const KisPropertiesConfigurationSP setting)
{
+
if (!setting->hasProperty("Texture/Pattern/PatternMD5")) {
m_enabled = false;
return;
}
m_pattern = KisEmbeddedPatternManager::loadEmbeddedPattern(setting);
if (!m_pattern) {
warnKrita << "WARNING: Couldn't load the pattern for a stroke";
m_enabled = false;
return;
}
m_enabled = setting->getBool("Texture/Pattern/Enabled", false);
m_scale = setting->getDouble("Texture/Pattern/Scale", 1.0);
m_brightness = setting->getDouble("Texture/Pattern/Brightness");
m_contrast = setting->getDouble("Texture/Pattern/Contrast", 1.0);
m_offsetX = setting->getInt("Texture/Pattern/OffsetX");
m_offsetY = setting->getInt("Texture/Pattern/OffsetY");
m_texturingMode = (TexturingMode) setting->getInt("Texture/Pattern/TexturingMode", MULTIPLY);
m_invert = setting->getBool("Texture/Pattern/Invert");
m_cutoffLeft = setting->getInt("Texture/Pattern/CutoffLeft", 0);
m_cutoffRight = setting->getInt("Texture/Pattern/CutoffRight", 255);
m_cutoffPolicy = setting->getInt("Texture/Pattern/CutoffPolicy", 0);
m_strengthOption.readOptionSetting(setting);
m_strengthOption.resetAllSensors();
recalculateMask();
}
void KisTextureProperties::apply(KisFixedPaintDeviceSP dab, const QPoint &offset, const KisPaintInformation & info)
{
if (!m_enabled) return;
KisPaintDeviceSP fillDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
QRect rect = dab->bounds();
int x = offset.x() % m_maskBounds.width() - m_offsetX;
int y = offset.y() % m_maskBounds.height() - m_offsetY;
KisFillPainter fillPainter(fillDevice);
fillPainter.fillRect(x - 1, y - 1, rect.width() + 2, rect.height() + 2, m_mask, m_maskBounds);
fillPainter.end();
qreal pressure = m_strengthOption.apply(info);
quint8 *dabData = dab->data();
KisHLineIteratorSP iter = fillDevice->createHLineIteratorNG(x, y, rect.width());
for (int row = 0; row < rect.height(); ++row) {
for (int col = 0; col < rect.width(); ++col) {
if (m_texturingMode == MULTIPLY) {
dab->colorSpace()->multiplyAlpha(dabData, quint8(*iter->oldRawData() * pressure), 1);
}
else {
int pressureOffset = (1.0 - pressure) * 255;
qint16 maskA = *iter->oldRawData() + pressureOffset;
quint8 dabA = dab->colorSpace()->opacityU8(dabData);
dabA = qMax(0, (qint16)dabA - maskA);
dab->colorSpace()->setOpacity(dabData, dabA, 1);
}
iter->nextPixel();
dabData += dab->pixelSize();
}
iter->nextRow();
}
}
diff --git a/plugins/paintops/libpaintop/kis_texture_option.h b/plugins/paintops/libpaintop/kis_texture_option.h
index 6c70b8c82b..b37c0f33d0 100644
--- a/plugins/paintops/libpaintop/kis_texture_option.h
+++ b/plugins/paintops/libpaintop/kis_texture_option.h
@@ -1,105 +1,106 @@
/* This file is part of the KDE project
* Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2012
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TEXTURE_OPTION_H
#define KIS_TEXTURE_OPTION_H
#include <kritapaintop_export.h>
#include <kis_paint_device.h>
#include <kis_types.h>
#include "kis_paintop_option.h"
#include "kis_pressure_texture_strength_option.h"
#include <QRect>
+class KisTextureChooser;
class KisTextureOptionWidget;
class KoPattern;
class KoResource;
class KisPropertiesConfiguration;
class KisPaintopLodLimitations;
class PAINTOP_EXPORT KisTextureOption : public KisPaintOpOption
{
Q_OBJECT
public:
explicit KisTextureOption();
~KisTextureOption() override;
public Q_SLOTS:
void writeOptionSetting(KisPropertiesConfigurationSP setting) const override;
void readOptionSetting(const KisPropertiesConfigurationSP setting) override;
void lodLimitations(KisPaintopLodLimitations *l) const override;
private Q_SLOTS:
void resetGUI(KoResource*); /// called when a new pattern is selected
private:
- KisTextureOptionWidget *m_optionWidget;
-
+ /// UI Widget that stores all the texture options
+ KisTextureChooser* m_textureOptions;
};
class PAINTOP_EXPORT KisTextureProperties
{
public:
KisTextureProperties(int levelOfDetail);
enum TexturingMode {
MULTIPLY,
SUBTRACT
};
bool m_enabled;
/**
* @brief apply combine the texture map with the dab
* @param dab the colored, final representation of the dab, after mirroring and everything.
* @param offset the position of the dab on the image. used to calculate the position of the mask pattern
*/
void apply(KisFixedPaintDeviceSP dab, const QPoint& offset, const KisPaintInformation & info);
void fillProperties(const KisPropertiesConfigurationSP setting);
private:
qreal m_scale;
int m_offsetX;
int m_offsetY;
qreal m_brightness;
qreal m_contrast;
TexturingMode m_texturingMode;
bool m_invert;
KoPattern *m_pattern;
int m_cutoffLeft;
int m_cutoffRight;
int m_cutoffPolicy;
int m_levelOfDetail;
private:
KisPressureTextureStrengthOption m_strengthOption;
QRect m_maskBounds; // this can be different from the extent if we mask out too many pixels in a big mask!
KisPaintDeviceSP m_mask;
void recalculateMask();
};
#endif // KIS_TEXTURE_OPTION_H
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.cc b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.cc
index 193bbbc474..02347e68dc 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.cc
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.cc
@@ -1,90 +1,84 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensor_distance.h"
#include <kis_debug.h>
#include <QDomElement>
#include "ui_SensorDistanceConfiguration.h"
#include <brushengine/kis_paint_information.h>
KisDynamicSensorDistance::KisDynamicSensorDistance()
: KisDynamicSensor(DISTANCE)
- , m_measuredDistance(0.0)
, m_periodic(true)
{
setLength(30);
}
qreal KisDynamicSensorDistance::value(const KisPaintInformation& pi)
{
if (pi.isHoveringMode()) return 1.0;
- m_measuredDistance += pi.drawingDistance();
- m_measuredDistance = m_periodic ?
- fmod(m_measuredDistance, m_length) :
- qMin(m_measuredDistance, (qreal)m_length);
+ const qreal distance =
+ m_periodic ?
+ fmod(pi.totalStrokeLength(), m_length) :
+ qMin(pi.totalStrokeLength(), (qreal)m_length);
- return m_measuredDistance / m_length;
-}
-
-void KisDynamicSensorDistance::reset()
-{
- m_measuredDistance = 0;
+ return distance / m_length;
}
void KisDynamicSensorDistance::setPeriodic(bool periodic)
{
m_periodic = periodic;
}
void KisDynamicSensorDistance::setLength(int length)
{
m_length = length;
}
QWidget* KisDynamicSensorDistance::createConfigurationWidget(QWidget* parent, QWidget* ss)
{
QWidget* wdg = new QWidget(parent);
Ui_SensorDistanceConfiguration stc;
stc.setupUi(wdg);
stc.checkBoxRepeat->setChecked(m_periodic);
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), SLOT(setPeriodic(bool)));
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), ss, SIGNAL(parametersChanged()));
stc.spinBoxLength->setValue(m_length);
connect(stc.spinBoxLength, SIGNAL(valueChanged(int)), SLOT(setLength(int)));
connect(stc.spinBoxLength, SIGNAL(valueChanged(int)), ss, SIGNAL(parametersChanged()));
return wdg;
}
void KisDynamicSensorDistance::toXML(QDomDocument& doc, QDomElement& e) const
{
KisDynamicSensor::toXML(doc, e);
e.setAttribute("periodic", m_periodic);
e.setAttribute("length", m_length);
}
void KisDynamicSensorDistance::fromXML(const QDomElement& e)
{
KisDynamicSensor::fromXML(e);
m_periodic = e.attribute("periodic", "0").toInt();
m_length = e.attribute("length", "30").toInt();
}
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.h b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.h
index 89ba405370..7cc1b7093a 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.h
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_distance.h
@@ -1,47 +1,45 @@
/*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_SENSOR_DISTANCE_H_
#define _KIS_DYNAMIC_SENSOR_DISTANCE_H_
#include "kis_dynamic_sensor.h"
//
class KisDynamicSensorDistance : public QObject, public KisDynamicSensor
{
Q_OBJECT
public:
using KisSerializableConfiguration::fromXML;
using KisSerializableConfiguration::toXML;
KisDynamicSensorDistance();
~KisDynamicSensorDistance() override { }
qreal value(const KisPaintInformation&) override;
- void reset() override;
QWidget* createConfigurationWidget(QWidget* parent, QWidget*) override;
public Q_SLOTS:
virtual void setPeriodic(bool periodic);
virtual void setLength(int length);
void toXML(QDomDocument&, QDomElement&) const override;
void fromXML(const QDomElement&) override;
private:
- qreal m_measuredDistance;
bool m_periodic;
};
#endif
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.cpp b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.cpp
index f8f2e00bcc..b276a91ad2 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.cpp
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.cpp
@@ -1,189 +1,178 @@
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensor_drawing_angle.h"
#include <QDomElement>
#include <brushengine/kis_paint_information.h>
#include <QCheckBox>
#include <QLabel>
#include <QHBoxLayout>
#include <kis_slider_spin_box.h>
KisDynamicSensorDrawingAngle::KisDynamicSensorDrawingAngle()
: KisDynamicSensor(ANGLE),
m_fanCornersEnabled(false),
m_fanCornersStep(30),
m_angleOffset(0),
- m_dabIndex(0),
m_lockedAngle(0),
m_lockedAngleMode(false)
{
}
void KisDynamicSensorDrawingAngle::reset()
{
- m_dabIndex = 0;
}
qreal KisDynamicSensorDrawingAngle::value(const KisPaintInformation& info)
{
/* so that we are in 0.0..1.0 */
- qreal ret = 0.5 + info.drawingAngle() / (2.0 * M_PI) + m_angleOffset/360.0;
+ qreal ret = 0.5 + info.drawingAngle(m_lockedAngleMode) / (2.0 * M_PI) + m_angleOffset/360.0;
// check if m_angleOffset pushed us out of bounds
if (ret > 1.0)
ret -= 1.0;
- if (!info.isHoveringMode() && m_lockedAngleMode) {
- if (!m_dabIndex) {
- info.lockCurrentDrawingAngle(1.0);
- } else {
- info.lockCurrentDrawingAngle(0.5);
- }
- m_dabIndex++;
- }
-
return ret;
}
bool KisDynamicSensorDrawingAngle::dependsOnCanvasRotation() const
{
return false;
}
bool KisDynamicSensorDrawingAngle::isAbsoluteRotation() const
{
return true;
}
void KisDynamicSensorDrawingAngle::updateGUI()
{
const bool fanEnabled = !m_chkLockedMode->isChecked();
m_chkFanCorners->setEnabled(fanEnabled);
m_intFanCornersStep->setEnabled(fanEnabled);
}
QWidget* KisDynamicSensorDrawingAngle::createConfigurationWidget(QWidget* parent, QWidget *ss)
{
QWidget *w = new QWidget(parent);
m_chkLockedMode = new QCheckBox(i18n("Lock"), w);
m_chkLockedMode->setChecked(m_lockedAngleMode);
connect(m_chkLockedMode, SIGNAL(stateChanged(int)), SLOT(setLockedAngleMode(int)));
connect(m_chkLockedMode, SIGNAL(stateChanged(int)), SLOT(updateGUI()));
m_chkFanCorners = new QCheckBox(i18n("Fan Corners"), w);
connect(m_chkFanCorners, SIGNAL(stateChanged(int)), SLOT(setFanCornersEnabled(int)));
m_chkFanCorners->setChecked(m_fanCornersEnabled);
m_intFanCornersStep = new KisSliderSpinBox(w);
m_intFanCornersStep->setRange(5, 90);
m_intFanCornersStep->setSingleStep(1);
m_intFanCornersStep->setSuffix(i18n("°"));
connect(m_intFanCornersStep, SIGNAL(valueChanged(int)), SLOT(setFanCornersStep(int)));
m_intFanCornersStep->setValue(m_fanCornersStep);
KisSliderSpinBox *angleOffset = new KisSliderSpinBox(w);
angleOffset->setRange(0, 359);
angleOffset->setSingleStep(1);
angleOffset->setSuffix(i18n("°"));
connect(angleOffset, SIGNAL(valueChanged(int)), SLOT(setAngleOffset(int)));
angleOffset->setValue(m_angleOffset);
QVBoxLayout* l = new QVBoxLayout(w);
l->addWidget(m_chkLockedMode);
l->addWidget(m_chkFanCorners);
l->addWidget(m_intFanCornersStep);
l->addWidget(new QLabel(i18n("Angle Offset")));
l->addWidget(angleOffset);
updateGUI();
connect(angleOffset, SIGNAL(valueChanged(int)), ss, SIGNAL(parametersChanged()));
connect(m_chkLockedMode, SIGNAL(stateChanged(int)), ss, SIGNAL(parametersChanged()));
connect(m_chkFanCorners, SIGNAL(stateChanged(int)), ss, SIGNAL(parametersChanged()));
connect(m_intFanCornersStep, SIGNAL(valueChanged(int)), ss, SIGNAL(parametersChanged()));
w->setLayout(l);
return w;
}
bool KisDynamicSensorDrawingAngle::fanCornersEnabled() const
{
return m_fanCornersEnabled && !m_lockedAngleMode;
}
int KisDynamicSensorDrawingAngle::fanCornersStep() const
{
return m_fanCornersStep;
}
int KisDynamicSensorDrawingAngle::angleOffset() const
{
return m_angleOffset;
}
void KisDynamicSensorDrawingAngle::setFanCornersEnabled(int state)
{
m_fanCornersEnabled = state;
}
void KisDynamicSensorDrawingAngle::setFanCornersStep(int angle)
{
m_fanCornersStep = angle;
}
void KisDynamicSensorDrawingAngle::setLockedAngleMode(int value)
{
m_lockedAngleMode = value;
}
void KisDynamicSensorDrawingAngle::setAngleOffset(int angle)
{
Q_ASSERT(angle >= 0 && angle < 360);//dont include 360
m_angleOffset = angle;
}
void KisDynamicSensorDrawingAngle::toXML(QDomDocument &doc, QDomElement &e) const
{
KisDynamicSensor::toXML(doc, e);
e.setAttribute("fanCornersEnabled", m_fanCornersEnabled);
e.setAttribute("fanCornersStep", m_fanCornersStep);
e.setAttribute("angleOffset", m_angleOffset);
e.setAttribute("lockedAngleMode", m_lockedAngleMode);
}
void KisDynamicSensorDrawingAngle::fromXML(const QDomElement &e)
{
KisDynamicSensor::fromXML(e);
m_fanCornersEnabled = e.attribute("fanCornersEnabled", "0").toInt();
m_fanCornersStep = e.attribute("fanCornersStep", "30").toInt();
m_angleOffset = e.attribute("angleOffset", "0").toInt();
m_lockedAngleMode = e.attribute("lockedAngleMode", "0").toInt();
}
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.h b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.h
index a21b71ace2..8c6fcb7970 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.h
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_drawing_angle.h
@@ -1,71 +1,70 @@
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_DYNAMIC_SENSOR_DRAWING_ANGLE_H
#define __KIS_DYNAMIC_SENSOR_DRAWING_ANGLE_H
#include "kis_dynamic_sensor.h"
class QCheckBox;
class KisSliderSpinBox;
class KisDynamicSensorDrawingAngle : public QObject, public KisDynamicSensor
{
Q_OBJECT
public:
KisDynamicSensorDrawingAngle();
qreal value(const KisPaintInformation& info) override;
bool dependsOnCanvasRotation() const override;
bool isAbsoluteRotation() const override;
QWidget* createConfigurationWidget(QWidget* parent, QWidget*) override;
using KisSerializableConfiguration::fromXML;
using KisSerializableConfiguration::toXML;
void toXML(QDomDocument&, QDomElement&) const override;
void fromXML(const QDomElement&) override;
bool fanCornersEnabled() const;
int fanCornersStep() const;
int angleOffset() const;
void reset() override;
public Q_SLOTS:
void setFanCornersEnabled(int state);
void setFanCornersStep(int angle);
void setAngleOffset(int angle);
void setLockedAngleMode(int value);
void updateGUI();
private:
bool m_fanCornersEnabled;
int m_fanCornersStep;
int m_angleOffset; // in degrees
- int m_dabIndex;
qreal m_lockedAngle;
bool m_lockedAngleMode;
QCheckBox *m_chkLockedMode;
QCheckBox *m_chkFanCorners;
KisSliderSpinBox *m_intFanCornersStep;
};
#endif /* __KIS_DYNAMIC_SENSOR_DRAWING_ANGLE_H */
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp
index d8dd1dd7a9..8eeff9ae21 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp
@@ -1,102 +1,89 @@
/*
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensor_fade.h"
#include <kis_debug.h>
#include <QDomElement>
#include "ui_SensorFadeConfiguration.h"
#include <brushengine/kis_paint_information.h>
static const int DEFAULT_LENGTH = 1000;
KisDynamicSensorFade::KisDynamicSensorFade()
: KisDynamicSensor(FADE)
- , m_counter(0)
, m_periodic(false)
{
setLength(DEFAULT_LENGTH);
}
qreal KisDynamicSensorFade::value(const KisPaintInformation& pi)
{
if (pi.isHoveringMode()) return 1.0;
- if (m_counter > m_length) {
- if (m_periodic) {
- reset();
- }
- else {
- m_counter = m_length;
- }
- }
+ const int currentValue =
+ m_periodic ?
+ pi.currentDabSeqNo() % m_length :
+ qMin(pi.currentDabSeqNo(), m_length);
- qreal result = m_counter / qreal(m_length);
- m_counter++;
-
- return result;
-}
-
-void KisDynamicSensorFade::reset()
-{
- m_counter = 0;
+ return qreal(currentValue) / m_length;
}
void KisDynamicSensorFade::setPeriodic(bool periodic)
{
m_periodic = periodic;
}
void KisDynamicSensorFade::setLength(int length)
{
m_length = length;
}
QWidget* KisDynamicSensorFade::createConfigurationWidget(QWidget* parent, QWidget* ss)
{
QWidget* wdg = new QWidget(parent);
Ui_SensorFadeConfiguration stc;
stc.setupUi(wdg);
stc.checkBoxRepeat->setChecked(m_periodic);
stc.spinBoxLength->setSuffix(i18n(" px"));
stc.spinBoxLength->setExponentRatio(3.0);
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), SLOT(setPeriodic(bool)));
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), ss, SIGNAL(parametersChanged()));
stc.spinBoxLength->setValue(m_length);
connect(stc.spinBoxLength, SIGNAL(valueChanged(int)), SLOT(setLength(int)));
connect(stc.spinBoxLength, SIGNAL(valueChanged(int)), ss, SIGNAL(parametersChanged()));
return wdg;
}
void KisDynamicSensorFade::toXML(QDomDocument& doc, QDomElement& e) const
{
KisDynamicSensor::toXML(doc, e);
e.setAttribute("periodic", m_periodic);
e.setAttribute("length", m_length);
}
void KisDynamicSensorFade::fromXML(const QDomElement& e)
{
KisDynamicSensor::fromXML(e);
m_periodic = e.attribute("periodic", "0").toInt();
m_length = e.attribute("length", QString::number(DEFAULT_LENGTH)).toInt();
}
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.h b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.h
index abfaf1b844..4384b93d89 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.h
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.h
@@ -1,48 +1,46 @@
/*
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_SENSOR_FADE_H_
#define _KIS_DYNAMIC_SENSOR_FADE_H_
#include "kis_dynamic_sensor.h"
class KisDynamicSensorFade : public QObject, public KisDynamicSensor
{
Q_OBJECT
public:
using KisSerializableConfiguration::fromXML;
using KisSerializableConfiguration::toXML;
KisDynamicSensorFade();
~KisDynamicSensorFade() override { }
qreal value(const KisPaintInformation&) override;
- void reset() override;
QWidget* createConfigurationWidget(QWidget* parent, QWidget*) override;
public Q_SLOTS:
virtual void setPeriodic(bool periodic);
virtual void setLength(int length);
void toXML(QDomDocument&, QDomElement&) const override;
void fromXML(const QDomElement&) override;
private:
- int m_counter;
bool m_periodic;
};
#endif
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.cpp b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.cpp
index 44b087aacb..7d3344bc2d 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.cpp
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.cpp
@@ -1,71 +1,64 @@
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
* Copyright (c) 2014 Mohit Goyal <mohit.bits2011@gmail.com>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensor_fuzzy.h"
#include <QDomElement>
#include <brushengine/kis_paint_information.h>
#include <QCheckBox>
#include <QHBoxLayout>
-KisDynamicSensorFuzzy::KisDynamicSensorFuzzy(bool fuzzyPerStroke)
+KisDynamicSensorFuzzy::KisDynamicSensorFuzzy(bool fuzzyPerStroke, const QString &parentOptionName)
: KisDynamicSensor(fuzzyPerStroke ? FUZZY_PER_STROKE : FUZZY_PER_DAB),
m_fuzzyPerStroke(fuzzyPerStroke),
- m_isInitialized(false),
- m_savedValue(0.0)
+ m_perStrokeRandomSourceKey(parentOptionName + "FuzzyStroke")
{
}
void KisDynamicSensorFuzzy::reset()
{
- m_isInitialized = false;
}
bool KisDynamicSensorFuzzy::isAdditive() const
{
return true;
}
qreal KisDynamicSensorFuzzy::value(const KisPaintInformation &info)
{
- if (m_fuzzyPerStroke && m_isInitialized) {
- return m_savedValue;
- }
-
qreal result = 0.0;
if (!info.isHoveringMode()) {
- result = info.randomSource()->generateNormalized();
+ result = m_fuzzyPerStroke ?
+ info.perStrokeRandomSource()->generateNormalized(m_perStrokeRandomSourceKey) :
+ info.randomSource()->generateNormalized();
result = 2.0 * result - 1.0;
-
- m_isInitialized = true;
- m_savedValue = result;
}
return result;
}
bool KisDynamicSensorFuzzy::dependsOnCanvasRotation() const
{
return false;
}
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.h b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.h
index 90222639f2..efd37753c4 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.h
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fuzzy.h
@@ -1,53 +1,52 @@
/*
* Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
* Copyright (c) 2014 Mohit Goyal <mohit.bits2011@gmail.com>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_DYNAMIC_SENSOR_FUZZY_H
#define KIS_DYNAMIC_SENSOR_FUZZY_H
#include "kis_dynamic_sensor.h"
#include <brushengine/kis_paint_information.h>
#include <brushengine/kis_paintop.h>
#include <KoID.h>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QDomElement>
class KisDynamicSensorFuzzy : public QObject, public KisDynamicSensor
{
Q_OBJECT
public:
bool dependsOnCanvasRotation() const override;
bool isAdditive() const override;
- KisDynamicSensorFuzzy(bool fuzzyPerStroke = false);
+ KisDynamicSensorFuzzy(bool fuzzyPerStroke, const QString &parentOptionName);
~KisDynamicSensorFuzzy() override {}
qreal value(const KisPaintInformation &info) override;
void reset() override;
private:
const bool m_fuzzyPerStroke;
- bool m_isInitialized;
- qreal m_savedValue;
+ QString m_perStrokeRandomSourceKey;
};
#endif // KIS_DYNAMIC_SENSOR_FUZZY_H
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc
index afba5a47c1..9c9a4d9fad 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc
@@ -1,112 +1,94 @@
/*
* Copyright (c) 2007,2010 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dynamic_sensor_time.h"
#include <kis_debug.h>
#include <QDomElement>
#include "ui_SensorTimeConfiguration.h"
#include <brushengine/kis_paint_information.h>
KisDynamicSensorTime::KisDynamicSensorTime()
: KisDynamicSensor(TIME)
- , m_time(0)
, m_periodic(true)
- , m_lastTime(0)
{
setLength(3);
}
qreal KisDynamicSensorTime::value(const KisPaintInformation& pi)
{
if (pi.isHoveringMode()) return 1.0;
- qreal curtime = pi.currentTime();
-
- if (curtime >= m_lastTime) {
- m_time += curtime - m_lastTime;
- } else {
- // safely handle the situation when currentTime() < m_lastTime
- m_time = 0;
- }
-
- m_lastTime = curtime;
-
- if (m_time > m_length) {
- if (m_periodic) {
- m_time = m_time % m_length;
- }
- else {
- m_time = m_length;
- }
- }
- return m_time / qreal(m_length);
+ const qreal currentTime =
+ m_periodic ?
+ std::fmod(pi.currentTime(), m_length) :
+ qMin(pi.currentTime(), qreal(m_length));
+
+ return currentTime / qreal(m_length);
}
void KisDynamicSensorTime::reset()
{
- m_lastTime = 0;
- m_time = 0;
}
void KisDynamicSensorTime::setPeriodic(bool periodic)
{
m_periodic = periodic;
}
void KisDynamicSensorTime::setLength(qreal length)
{
m_length = (int)(length * 1000); // convert to milliseconds
}
QWidget* KisDynamicSensorTime::createConfigurationWidget(QWidget* parent, QWidget* ss)
{
QWidget* wdg = new QWidget(parent);
Ui_SensorTimeConfiguration stc;
stc.setupUi(wdg);
stc.checkBoxRepeat->setChecked(m_periodic);
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), SLOT(setPeriodic(bool)));
connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), ss, SIGNAL(parametersChanged()));
stc.spinBoxDuration->setRange(0.02, 10.0, 2);
stc.spinBoxDuration->setSuffix(i18n(" s"));
stc.spinBoxDuration->setValue(m_length / 1000);
connect(stc.spinBoxDuration, SIGNAL(valueChanged(qreal)), SLOT(setLength(qreal)));
connect(stc.spinBoxDuration, SIGNAL(valueChanged(qreal)), ss, SIGNAL(parametersChanged()));
return wdg;
}
void KisDynamicSensorTime::toXML(QDomDocument& doc, QDomElement& e) const
{
KisDynamicSensor::toXML(doc, e);
e.setAttribute("periodic", m_periodic);
e.setAttribute("duration", m_length);
}
void KisDynamicSensorTime::fromXML(const QDomElement& e)
{
KisDynamicSensor::fromXML(e);
m_periodic = e.attribute("periodic", "0").toInt();
m_length = e.attribute("duration", "30").toInt();
}
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h
index acfcd3e1d4..82e458cdc0 100644
--- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h
+++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h
@@ -1,49 +1,47 @@
/*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DYNAMIC_SENSOR_TIME_H_
#define _KIS_DYNAMIC_SENSOR_TIME_H_
#include "kis_dynamic_sensor.h"
//
class KisDynamicSensorTime : public QObject, public KisDynamicSensor
{
Q_OBJECT
public:
using KisSerializableConfiguration::fromXML;
using KisSerializableConfiguration::toXML;
KisDynamicSensorTime();
~KisDynamicSensorTime() override { }
qreal value(const KisPaintInformation&) override;
void reset() override;
QWidget* createConfigurationWidget(QWidget* parent, QWidget*) override;
public Q_SLOTS:
virtual void setPeriodic(bool periodic);
virtual void setLength(qreal length);
void toXML(QDomDocument&, QDomElement&) const override;
void fromXML(const QDomElement&) override;
private:
- int m_time;
bool m_periodic;
- int m_lastTime;
};
#endif
diff --git a/plugins/paintops/libpaintop/tests/kis_sensors_test.cpp b/plugins/paintops/libpaintop/tests/kis_sensors_test.cpp
index b02b26d70e..94c7bc55b8 100644
--- a/plugins/paintops/libpaintop/tests/kis_sensors_test.cpp
+++ b/plugins/paintops/libpaintop/tests/kis_sensors_test.cpp
@@ -1,51 +1,51 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* 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_sensors_test.h"
#include <kis_dynamic_sensor.h>
#include <QTest>
KisSensorsTest::KisSensorsTest()
{
paintInformations.append(KisPaintInformation(QPointF(0, 0)));
paintInformations.append(KisPaintInformation(QPointF(0, 1)));
paintInformations.append(KisPaintInformation(QPointF(1, 2)));
paintInformations.append(KisPaintInformation(QPointF(2, 2)));
paintInformations.append(KisPaintInformation(QPointF(3, 1)));
paintInformations.append(KisPaintInformation(QPointF(3, 0)));
paintInformations.append(KisPaintInformation(QPointF(2, -1)));
paintInformations.append(KisPaintInformation(QPointF(1, -1)));
}
void KisSensorsTest::testDrawingAngle()
{
- KisDynamicSensorSP sensor = KisDynamicSensor::id2Sensor(DrawingAngleId);
+ KisDynamicSensorSP sensor = KisDynamicSensor::id2Sensor(DrawingAngleId, "testname");
testBound(sensor);
}
void KisSensorsTest::testBound(KisDynamicSensorSP sensor)
{
Q_FOREACH (const KisPaintInformation & pi, paintInformations) {
double v = sensor->parameter(pi);
QVERIFY(v >= 0.0);
QVERIFY(v <= 1.0);
}
}
QTEST_MAIN(KisSensorsTest)
diff --git a/plugins/tools/basictools/kis_tool_gradient.cc b/plugins/tools/basictools/kis_tool_gradient.cc
index d18aad3f94..0354591780 100644
--- a/plugins/tools/basictools/kis_tool_gradient.cc
+++ b/plugins/tools/basictools/kis_tool_gradient.cc
@@ -1,299 +1,304 @@
/*
* kis_tool_gradient.cc - part of Krita
*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2003 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004-2007 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_gradient.h"
#include <cfloat>
#include <QApplication>
#include <QPainter>
#include <QLabel>
#include <QLayout>
#include <QCheckBox>
#include <kis_transaction.h>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <kcombobox.h>
#include <KoPointerEvent.h>
#include <KoCanvasBase.h>
#include <KoViewConverter.h>
#include <KoUpdater.h>
#include <KoProgressUpdater.h>
#include <kis_gradient_painter.h>
#include <kis_painter.h>
#include <kis_canvas_resource_provider.h>
#include <kis_layer.h>
#include <kis_selection.h>
#include <kis_paint_layer.h>
#include <canvas/kis_canvas2.h>
#include <KisViewManager.h>
#include <widgets/kis_cmb_composite.h>
#include <widgets/kis_double_widget.h>
#include <widgets/kis_slider_spin_box.h>
#include <kis_cursor.h>
#include <kis_config.h>
#include "kis_resources_snapshot.h"
KisToolGradient::KisToolGradient(KoCanvasBase * canvas)
: KisToolPaint(canvas, KisCursor::load("tool_gradient_cursor.png", 6, 6))
{
setObjectName("tool_gradient");
m_startPos = QPointF(0, 0);
m_endPos = QPointF(0, 0);
m_reverse = false;
m_shape = KisGradientPainter::GradientShapeLinear;
m_repeat = KisGradientPainter::GradientRepeatNone;
m_antiAliasThreshold = 0.2;
}
KisToolGradient::~KisToolGradient()
{
}
void KisToolGradient::resetCursorStyle()
{
KisToolPaint::resetCursorStyle();
overrideCursorIfNotEditable();
}
void KisToolGradient::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
KisToolPaint::activate(toolActivation, shapes);
m_configGroup = KSharedConfig::openConfig()->group(toolId());
}
void KisToolGradient::paint(QPainter &painter, const KoViewConverter &converter)
{
if (mode() == KisTool::PAINT_MODE && m_startPos != m_endPos) {
qreal sx, sy;
converter.zoom(&sx, &sy);
painter.scale(sx / currentImage()->xRes(), sy / currentImage()->yRes());
paintLine(painter);
}
}
void KisToolGradient::beginPrimaryAction(KoPointerEvent *event)
{
if (!nodeEditable()) {
event->ignore();
return;
}
setMode(KisTool::PAINT_MODE);
m_startPos = convertToPixelCoordAndSnap(event, QPointF(), false);
m_endPos = m_startPos;
}
void KisToolGradient::continuePrimaryAction(KoPointerEvent *event)
{
/**
* TODO: The gradient tool is still not in strokes, so the end of
* its action can call processEvent(), which would result in
* nested event hadler calls. Please uncomment this line
* when the tool is ported to strokes.
*/
//CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
QPointF pos = convertToPixelCoordAndSnap(event, QPointF(), false);
QRectF bound(m_startPos, m_endPos);
canvas()->updateCanvas(convertToPt(bound.normalized()));
if (event->modifiers() == Qt::ShiftModifier) {
m_endPos = straightLine(pos);
} else {
m_endPos = pos;
}
bound.setTopLeft(m_startPos);
bound.setBottomRight(m_endPos);
canvas()->updateCanvas(convertToPt(bound.normalized()));
}
void KisToolGradient::endPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
setMode(KisTool::HOVER_MODE);
if (!currentNode() || !blockUntilOperationsFinished())
return;
if (m_startPos == m_endPos) {
return;
}
KisPaintDeviceSP device;
KisImageSP image = this->image();
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image, currentNode(), this->canvas()->resourceManager());
if (image && (device = resources->currentNode()->paintDevice())) {
QApplication::setOverrideCursor(Qt::BusyCursor);
KUndo2MagicString actionName = kundo2_i18n("Gradient");
KisUndoAdapter *undoAdapter = image->undoAdapter();
undoAdapter->beginMacro(actionName);
KisGradientPainter painter(device, resources->activeSelection());
resources->setupPainter(&painter);
painter.beginTransaction();
KisCanvas2 * canvas = dynamic_cast<KisCanvas2 *>(this->canvas());
KoUpdaterPtr updater = canvas->viewManager()->createUnthreadedUpdater(i18nc("@info:progress", "Gradient..."));
painter.setProgress(updater);
painter.setGradientShape(m_shape);
painter.paintGradient(m_startPos, m_endPos, m_repeat, m_antiAliasThreshold, m_reverse, 0, 0, image->width(), image->height());
painter.endTransaction(undoAdapter);
undoAdapter->endMacro();
QApplication::restoreOverrideCursor();
currentNode()->setDirty();
notifyModified();
}
canvas()->updateCanvas(convertToPt(currentImage()->bounds()));
}
QPointF KisToolGradient::straightLine(QPointF point)
{
QPointF comparison = point - m_startPos;
QPointF result;
if (fabs(comparison.x()) > fabs(comparison.y())) {
result.setX(point.x());
result.setY(m_startPos.y());
} else {
result.setX(m_startPos.x());
result.setY(point.y());
}
return result;
}
void KisToolGradient::paintLine(QPainter& gc)
{
if (canvas()) {
QPen old = gc.pen();
QPen pen(Qt::SolidLine);
gc.setPen(pen);
gc.drawLine(m_startPos, m_endPos);
gc.setPen(old);
}
}
QWidget* KisToolGradient::createOptionWidget()
{
QWidget *widget = KisToolPaint::createOptionWidget();
Q_CHECK_PTR(widget);
widget->setObjectName(toolId() + " option widget");
// Make sure to create the connections last after everything is set up. The initialized values
// won't be loaded from the configuration file if you add the widget before the connection
m_lbShape = new QLabel(i18n("Shape:"), widget);
m_cmbShape = new KComboBox(widget);
m_cmbShape->setObjectName("shape_combo");
m_cmbShape->addItem(i18nc("the gradient will be drawn linearly", "Linear"));
m_cmbShape->addItem(i18nc("the gradient will be drawn bilinearly", "Bi-Linear"));
m_cmbShape->addItem(i18nc("the gradient will be drawn radially", "Radial"));
m_cmbShape->addItem(i18nc("the gradient will be drawn in a square around a centre", "Square"));
m_cmbShape->addItem(i18nc("the gradient will be drawn as an assymmetric cone", "Conical"));
m_cmbShape->addItem(i18nc("the gradient will be drawn as a symmetric cone", "Conical Symmetric"));
m_cmbShape->addItem(i18nc("the gradient will be drawn in a selection outline", "Shaped"));
addOptionWidgetOption(m_cmbShape, m_lbShape);
connect(m_cmbShape, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetShape(int)));
m_lbRepeat = new QLabel(i18n("Repeat:"), widget);
m_cmbRepeat = new KComboBox(widget);
m_cmbRepeat->setObjectName("repeat_combo");
m_cmbRepeat->addItem(i18nc("The gradient will not repeat", "None"));
m_cmbRepeat->addItem(i18nc("The gradient will repeat forwards", "Forwards"));
m_cmbRepeat->addItem(i18nc("The gradient will repeat alternatingly", "Alternating"));
addOptionWidgetOption(m_cmbRepeat, m_lbRepeat);
connect(m_cmbRepeat, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetRepeat(int)));
m_lbAntiAliasThreshold = new QLabel(i18n("Anti-alias threshold:"), widget);
m_slAntiAliasThreshold = new KisDoubleSliderSpinBox(widget);
m_slAntiAliasThreshold->setObjectName("threshold_slider");
m_slAntiAliasThreshold->setRange(0, 1, 3);
addOptionWidgetOption(m_slAntiAliasThreshold, m_lbAntiAliasThreshold);
connect(m_slAntiAliasThreshold, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAntiAliasThreshold(qreal)));
m_ckReverse = new QCheckBox(i18nc("the gradient will be drawn with the color order reversed", "Reverse"), widget);
m_ckReverse->setObjectName("reverse_check");
connect(m_ckReverse, SIGNAL(toggled(bool)), this, SLOT(slotSetReverse(bool)));
addOptionWidgetOption(m_ckReverse);
widget->setFixedHeight(widget->sizeHint().height());
// load configuration settings into widget (updating UI will update internal variables from signals/slots)
m_ckReverse->setChecked((bool)m_configGroup.readEntry("reverse", false));
m_cmbShape->setCurrentIndex((int)m_configGroup.readEntry("shape", 0));
m_cmbRepeat->setCurrentIndex((int)m_configGroup.readEntry("repeat", 0));
m_slAntiAliasThreshold->setValue((qreal)m_configGroup.readEntry("antialiasThreshold", 0.0));
return widget;
}
void KisToolGradient::slotSetShape(int shape)
{
m_shape = static_cast<KisGradientPainter::enumGradientShape>(shape);
m_configGroup.writeEntry("shape", shape);
}
void KisToolGradient::slotSetRepeat(int repeat)
{
m_repeat = static_cast<KisGradientPainter::enumGradientRepeat>(repeat);
m_configGroup.writeEntry("repeat", repeat);
}
void KisToolGradient::slotSetReverse(bool state)
{
m_reverse = state;
m_configGroup.writeEntry("reverse", state);
}
void KisToolGradient::slotSetAntiAliasThreshold(qreal value)
{
m_antiAliasThreshold = value;
m_configGroup.writeEntry("antialiasThreshold", value);
}
+void KisToolGradient::setOpacity(qreal opacity)
+{
+ m_opacity = opacity;
+}
+
diff --git a/plugins/tools/basictools/kis_tool_gradient.h b/plugins/tools/basictools/kis_tool_gradient.h
index ab68fd864d..700297d57b 100644
--- a/plugins/tools/basictools/kis_tool_gradient.h
+++ b/plugins/tools/basictools/kis_tool_gradient.h
@@ -1,131 +1,131 @@
/*
* kis_tool_line.h - part of Krayon
*
* Copyright (c) 2000 John Califf <jcaliff@comuzone.net>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TOOL_GRADIENT_H_
#define KIS_TOOL_GRADIENT_H_
#include <QKeySequence>
#include <KoToolFactoryBase.h>
#include <kis_tool_paint.h>
#include <kis_global.h>
#include <kis_types.h>
#include <kis_gradient_painter.h>
#include <flake/kis_node_shape.h>
#include <kis_icon.h>
#include <kconfig.h>
#include <kconfiggroup.h>
class QLabel;
class QPoint;
class QWidget;
class QCheckBox;
class KComboBox;
class KisDoubleSliderSpinBox;
class KisToolGradient : public KisToolPaint
{
Q_OBJECT
public:
KisToolGradient(KoCanvasBase * canvas);
~KisToolGradient() override;
void beginPrimaryAction(KoPointerEvent *event) override;
void continuePrimaryAction(KoPointerEvent *event) override;
void endPrimaryAction(KoPointerEvent *event) override;
void paint(QPainter &painter, const KoViewConverter &converter) override;
QWidget* createOptionWidget() override;
public Q_SLOTS:
void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes) override;
+private Q_SLOTS:
void slotSetShape(int);
void slotSetRepeat(int);
void slotSetReverse(bool);
void slotSetAntiAliasThreshold(qreal);
-
+ void setOpacity(qreal opacity);
protected Q_SLOTS:
void resetCursorStyle() override;
private Q_SLOTS:
void areaDone(const QRect & rc) {
currentNode()->setDirty(rc); // Starts computing the projection for the area we've done.
}
private:
void paintLine(QPainter& gc);
QPointF straightLine(QPointF point);
QPointF m_startPos;
QPointF m_endPos;
KisGradientPainter::enumGradientShape m_shape;
KisGradientPainter::enumGradientRepeat m_repeat;
bool m_reverse;
double m_antiAliasThreshold;
QLabel *m_lbShape;
QLabel *m_lbRepeat;
QCheckBox *m_ckReverse;
KComboBox *m_cmbShape;
KComboBox *m_cmbRepeat;
QLabel *m_lbAntiAliasThreshold;
KisDoubleSliderSpinBox *m_slAntiAliasThreshold;
KConfigGroup m_configGroup;
-
};
class KisToolGradientFactory : public KoToolFactoryBase
{
public:
KisToolGradientFactory()
: KoToolFactoryBase("KritaFill/KisToolGradient") {
setToolTip(i18n("Gradient Tool"));
setSection(TOOL_TYPE_FILL);
setIconName(koIconNameCStr("krita_tool_gradient"));
setShortcut(QKeySequence(Qt::Key_G));
setPriority(1);
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
}
~KisToolGradientFactory() override {}
KoToolBase * createTool(KoCanvasBase *canvas) override {
return new KisToolGradient(canvas);
}
};
#endif //KIS_TOOL_GRADIENT_H_
diff --git a/plugins/tools/basictools/strokes/move_stroke_strategy.cpp b/plugins/tools/basictools/strokes/move_stroke_strategy.cpp
index 33841c61de..8cb4958ed4 100644
--- a/plugins/tools/basictools/strokes/move_stroke_strategy.cpp
+++ b/plugins/tools/basictools/strokes/move_stroke_strategy.cpp
@@ -1,228 +1,230 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "move_stroke_strategy.h"
#include <klocalizedstring.h>
#include "kis_image_interfaces.h"
#include "kis_node.h"
#include "commands_new/kis_update_command.h"
#include "commands_new/kis_node_move_command2.h"
#include "kis_layer_utils.h"
#include "krita_utils.h"
MoveStrokeStrategy::MoveStrokeStrategy(KisNodeList nodes,
KisUpdatesFacade *updatesFacade,
KisStrokeUndoFacade *undoFacade)
: KisStrokeStrategyUndoCommandBased(kundo2_i18n("Move"), false, undoFacade),
m_nodes(),
m_updatesFacade(updatesFacade),
m_updatesEnabled(true)
{
m_nodes = KisLayerUtils::sortAndFilterMergableInternalNodes(nodes, true);
KritaUtils::filterContainer<KisNodeList>(m_nodes,
[this](KisNodeSP node) {
return
!KisLayerUtils::checkIsCloneOf(node, m_nodes) &&
node->isEditable();
});
Q_FOREACH(KisNodeSP subtree, m_nodes) {
KisLayerUtils::recursiveApplyNodes(
subtree,
[this](KisNodeSP node) {
if (KisLayerUtils::checkIsCloneOf(node, m_nodes) ||
!node->isEditable()) {
m_blacklistedNodes.insert(node);
}
});
}
setSupportsWrapAroundMode(true);
}
MoveStrokeStrategy::MoveStrokeStrategy(const MoveStrokeStrategy &rhs)
: KisStrokeStrategyUndoCommandBased(rhs),
m_nodes(rhs.m_nodes),
m_blacklistedNodes(rhs.m_blacklistedNodes),
m_updatesFacade(rhs.m_updatesFacade),
m_finalOffset(rhs.m_finalOffset),
m_dirtyRect(rhs.m_dirtyRect),
m_dirtyRects(rhs.m_dirtyRects),
m_updatesEnabled(rhs.m_updatesEnabled)
{
}
void MoveStrokeStrategy::saveInitialNodeOffsets(KisNodeSP node)
{
if (!m_blacklistedNodes.contains(node)) {
m_initialNodeOffsets.insert(node, QPoint(node->x(), node->y()));
}
KisNodeSP child = node->firstChild();
while(child) {
saveInitialNodeOffsets(child);
child = child->nextSibling();
}
}
void MoveStrokeStrategy::initStrokeCallback()
{
Q_FOREACH(KisNodeSP node, m_nodes) {
saveInitialNodeOffsets(node);
}
KisStrokeStrategyUndoCommandBased::initStrokeCallback();
}
void MoveStrokeStrategy::finishStrokeCallback()
{
Q_FOREACH (KisNodeSP node, m_nodes) {
KUndo2Command *updateCommand =
new KisUpdateCommand(node, m_dirtyRects[node], m_updatesFacade, true);
addMoveCommands(node, updateCommand);
notifyCommandDone(KUndo2CommandSP(updateCommand),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
}
if (!m_updatesEnabled) {
Q_FOREACH (KisNodeSP node, m_nodes) {
m_updatesFacade->refreshGraphAsync(node, m_dirtyRects[node]);
}
}
KisStrokeStrategyUndoCommandBased::finishStrokeCallback();
}
void MoveStrokeStrategy::cancelStrokeCallback()
{
if (!m_nodes.isEmpty()) {
// FIXME: make cancel job exclusive instead
m_updatesFacade->blockUpdates();
moveAndUpdate(QPoint());
m_updatesFacade->unblockUpdates();
}
KisStrokeStrategyUndoCommandBased::cancelStrokeCallback();
}
void MoveStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
{
Data *d = dynamic_cast<Data*>(data);
if(!m_nodes.isEmpty() && d) {
moveAndUpdate(d->offset);
/**
* NOTE: we do not care about threading here, because
* all our jobs are declared sequential
*/
m_finalOffset = d->offset;
}
else {
KisStrokeStrategyUndoCommandBased::doStrokeCallback(data);
}
}
void MoveStrokeStrategy::moveAndUpdate(QPoint offset)
{
Q_FOREACH (KisNodeSP node, m_nodes) {
QRect dirtyRect = moveNode(node, offset);
m_dirtyRects[node] |= dirtyRect;
if (m_updatesEnabled) {
m_updatesFacade->refreshGraphAsync(node, dirtyRect);
}
}
}
QRect MoveStrokeStrategy::moveNode(KisNodeSP node, QPoint offset)
{
QRect dirtyRect;
if (!m_blacklistedNodes.contains(node)) {
dirtyRect = node->extent();
QPoint newOffset = m_initialNodeOffsets[node] + offset;
/**
* Some layers, e.g. clones need an update to change extent(), so
* calculate the dirty rect manually
*/
QPoint currentOffset(node->x(), node->y());
dirtyRect |= dirtyRect.translated(newOffset - currentOffset);
node->setX(newOffset.x());
node->setY(newOffset.y());
KisNodeMoveCommand2::tryNotifySelection(node);
}
KisNodeSP child = node->firstChild();
while(child) {
dirtyRect |= moveNode(child, offset);
child = child->nextSibling();
}
return dirtyRect;
}
void MoveStrokeStrategy::addMoveCommands(KisNodeSP node, KUndo2Command *parent)
{
if (!m_blacklistedNodes.contains(node)) {
QPoint nodeOffset(node->x(), node->y());
new KisNodeMoveCommand2(node, nodeOffset - m_finalOffset, nodeOffset, parent);
}
KisNodeSP child = node->firstChild();
while(child) {
addMoveCommands(child, parent);
child = child->nextSibling();
}
}
void MoveStrokeStrategy::setUpdatesEnabled(bool value)
{
m_updatesEnabled = value;
}
bool checkSupportsLodMoves(KisNodeSP subtree)
{
return
!KisLayerUtils::recursiveFindNode(
subtree,
[](KisNodeSP node) -> bool {
return !node->supportsLodMoves();
});
}
KisStrokeStrategy* MoveStrokeStrategy::createLodClone(int levelOfDetail)
{
+ Q_UNUSED(levelOfDetail);
+
Q_FOREACH (KisNodeSP node, m_nodes) {
if (!checkSupportsLodMoves(node)) return 0;
}
MoveStrokeStrategy *clone = new MoveStrokeStrategy(*this);
this->setUpdatesEnabled(false);
return clone;
}
diff --git a/plugins/tools/karbonplugins/filtereffects/ColorMatrixEffectConfigWidget.cpp b/plugins/tools/karbonplugins/filtereffects/ColorMatrixEffectConfigWidget.cpp
index b1880cd462..a97c78abe7 100644
--- a/plugins/tools/karbonplugins/filtereffects/ColorMatrixEffectConfigWidget.cpp
+++ b/plugins/tools/karbonplugins/filtereffects/ColorMatrixEffectConfigWidget.cpp
@@ -1,181 +1,181 @@
/* This file is part of the KDE project
* Copyright (c) 2009-2010 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "ColorMatrixEffectConfigWidget.h"
#include "ColorMatrixEffect.h"
#include "KoFilterEffect.h"
#include "MatrixDataModel.h"
#include <QSpinBox>
#include <kcombobox.h>
#include <klocalizedstring.h>
#include <QGridLayout>
#include <QLabel>
#include <QStackedWidget>
#include <QTableView>
#include <QHeaderView>
#include "kis_double_parse_spin_box.h"
ColorMatrixEffectConfigWidget::ColorMatrixEffectConfigWidget(QWidget *parent)
: KoFilterEffectConfigWidgetBase(parent)
, m_effect(0)
{
QGridLayout *g = new QGridLayout(this);
m_type = new KComboBox(this);
m_type->addItem(i18n("Apply color matrix"));
m_type->addItem(i18n("Saturate colors"));
m_type->addItem(i18n("Rotate hue"));
m_type->addItem(i18n("Luminance to alpha"));
g->addWidget(m_type, 0, 0);
m_stack = new QStackedWidget(this);
m_stack->setContentsMargins(0, 0, 0, 0);
g->addWidget(m_stack, 1, 0);
m_matrixModel = new MatrixDataModel(this);
QTableView *matrixWidget = new QTableView(m_stack);
matrixWidget->setModel(m_matrixModel);
matrixWidget->horizontalHeader()->hide();
- matrixWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
+ matrixWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
matrixWidget->verticalHeader()->hide();
- matrixWidget->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
+ matrixWidget->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
m_stack->addWidget(matrixWidget);
QWidget *saturateWidget = new QWidget(m_stack);
QGridLayout *saturateLayout = new QGridLayout(saturateWidget);
saturateLayout->addWidget(new QLabel(i18n("Saturate value"), saturateWidget), 0, 0);
m_saturate = new KisDoubleParseSpinBox(saturateWidget);
m_saturate->setRange(0.0, 1.0);
m_saturate->setSingleStep(0.05);
saturateLayout->addWidget(m_saturate, 0, 1);
saturateLayout->addItem(new QSpacerItem(0, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 1, 0);
saturateWidget->setLayout(saturateLayout);
m_stack->addWidget(saturateWidget);
QWidget *hueRotateWidget = new QWidget(m_stack);
QGridLayout *hueRotateLayout = new QGridLayout(hueRotateWidget);
hueRotateLayout->addWidget(new QLabel(i18n("Angle"), hueRotateWidget), 0, 0);
m_hueRotate = new KisDoubleParseSpinBox(hueRotateWidget);
m_hueRotate->setRange(0.0, 360.0);
m_hueRotate->setSingleStep(1.0);
hueRotateLayout->addWidget(m_hueRotate, 0, 1);
hueRotateLayout->addItem(new QSpacerItem(0, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 1, 0);
hueRotateWidget->setLayout(hueRotateLayout);
m_stack->addWidget(hueRotateWidget);
QWidget *luminanceWidget = new QWidget(m_stack);
m_stack->addWidget(luminanceWidget);
setLayout(g);
connect(m_type, SIGNAL(currentIndexChanged(int)), m_stack, SLOT(setCurrentIndex(int)));
connect(m_type, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged(int)));
connect(m_saturate, SIGNAL(valueChanged(double)), this, SLOT(saturateChanged(double)));
connect(m_hueRotate, SIGNAL(valueChanged(double)), this, SLOT(hueRotateChanged(double)));
connect(m_matrixModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(matrixChanged()));
}
bool ColorMatrixEffectConfigWidget::editFilterEffect(KoFilterEffect *filterEffect)
{
m_effect = dynamic_cast<ColorMatrixEffect *>(filterEffect);
if (!m_effect) {
return false;
}
m_type->blockSignals(true);
switch (m_effect->type()) {
case ColorMatrixEffect::Matrix:
m_type->setCurrentIndex(0);
m_matrixModel->setMatrix(m_effect->colorMatrix(), m_effect->colorMatrixRowCount(), m_effect->colorMatrixColumnCount());
break;
case ColorMatrixEffect::Saturate:
m_type->setCurrentIndex(1);
m_saturate->blockSignals(true);
m_saturate->setValue(m_effect->saturate());
m_saturate->blockSignals(false);
break;
case ColorMatrixEffect::HueRotate:
m_type->setCurrentIndex(2);
m_hueRotate->blockSignals(true);
m_hueRotate->setValue(m_effect->hueRotate());
m_hueRotate->blockSignals(false);
break;
case ColorMatrixEffect::LuminanceAlpha:
m_type->setCurrentIndex(3);
break;
}
m_type->blockSignals(false);
m_stack->setCurrentIndex(m_type->currentIndex());
return true;
}
void ColorMatrixEffectConfigWidget::matrixChanged()
{
if (!m_effect) {
return;
}
m_effect->setColorMatrix(m_matrixModel->matrix());
emit filterChanged();
}
void ColorMatrixEffectConfigWidget::saturateChanged(double saturate)
{
if (!m_effect) {
return;
}
m_effect->setSaturate(saturate);
emit filterChanged();
}
void ColorMatrixEffectConfigWidget::hueRotateChanged(double angle)
{
if (!m_effect) {
return;
}
m_effect->setHueRotate(angle);
emit filterChanged();
}
void ColorMatrixEffectConfigWidget::typeChanged(int index)
{
if (!m_effect) {
return;
}
if (index == ColorMatrixEffect::Matrix) {
m_effect->setColorMatrix(m_matrixModel->matrix());
} else if (index == ColorMatrixEffect::Saturate) {
m_effect->setSaturate(m_saturate->value());
} else if (index == ColorMatrixEffect::HueRotate) {
m_effect->setHueRotate(m_hueRotate->value());
} else {
m_effect->setLuminanceAlpha();
}
emit filterChanged();
}
diff --git a/plugins/tools/karbonplugins/filtereffects/ConvolveMatrixEffectConfigWidget.cpp b/plugins/tools/karbonplugins/filtereffects/ConvolveMatrixEffectConfigWidget.cpp
index 6bacd30a5a..7d8a7e8c2f 100644
--- a/plugins/tools/karbonplugins/filtereffects/ConvolveMatrixEffectConfigWidget.cpp
+++ b/plugins/tools/karbonplugins/filtereffects/ConvolveMatrixEffectConfigWidget.cpp
@@ -1,262 +1,262 @@
/* This file is part of the KDE project
* Copyright (c) 2010 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "ConvolveMatrixEffectConfigWidget.h"
#include "ConvolveMatrixEffect.h"
#include "KoFilterEffect.h"
#include "MatrixDataModel.h"
#include <klocalizedstring.h>
#include <kcombobox.h>
#include <QDialog>
#include <QGridLayout>
#include <QLabel>
#include <QDoubleSpinBox>
#include <QPushButton>
#include <QCheckBox>
#include <QTableView>
#include <QHeaderView>
#include <KConfigGroup>
#include "kis_double_parse_spin_box.h"
#include "kis_int_parse_spin_box.h"
ConvolveMatrixEffectConfigWidget::ConvolveMatrixEffectConfigWidget(QWidget *parent)
: KoFilterEffectConfigWidgetBase(parent)
, m_effect(0)
{
QGridLayout *g = new QGridLayout(this);
m_edgeMode = new KComboBox(this);
m_edgeMode->addItem(i18n("Duplicate"));
m_edgeMode->addItem(i18n("Wrap"));
m_edgeMode->addItem(i18n("None"));
g->addWidget(new QLabel(i18n("Edge mode:"), this), 0, 0);
g->addWidget(m_edgeMode, 0, 1, 1, 3);
m_orderX = new KisIntParseSpinBox(this);
m_orderX->setRange(1, 30);
m_orderY = new KisIntParseSpinBox(this);
m_orderY->setRange(1, 30);
g->addWidget(new QLabel(i18n("Kernel size:"), this), 1, 0);
g->addWidget(m_orderX, 1, 1);
g->addWidget(new QLabel("X", this), 1, 2, Qt::AlignHCenter);
g->addWidget(m_orderY, 1, 3);
m_targetX = new KisIntParseSpinBox(this);
m_targetX->setRange(0, 30);
m_targetY = new KisIntParseSpinBox(this);
m_targetY->setRange(0, 30);
g->addWidget(new QLabel(i18n("Target point:"), this), 2, 0);
g->addWidget(m_targetX, 2, 1);
g->addWidget(new QLabel("X", this), 2, 2, Qt::AlignHCenter);
g->addWidget(m_targetY, 2, 3);
m_divisor = new KisDoubleParseSpinBox(this);
m_bias = new KisDoubleParseSpinBox(this);
g->addWidget(new QLabel(i18n("Divisor:"), this), 3, 0);
g->addWidget(m_divisor, 3, 1);
g->addWidget(new QLabel(i18n("Bias:"), this), 3, 2);
g->addWidget(m_bias, 3, 3);
m_preserveAlpha = new QCheckBox(i18n("Preserve alpha"), this);
g->addWidget(m_preserveAlpha, 4, 1, 1, 3);
QPushButton *kernelButton = new QPushButton(i18n("Edit kernel"), this);
g->addWidget(kernelButton, 5, 0, 1, 4);
setLayout(g);
connect(m_edgeMode, SIGNAL(currentIndexChanged(int)), this, SLOT(edgeModeChanged(int)));
connect(m_orderX, SIGNAL(valueChanged(int)), this, SLOT(orderChanged(int)));
connect(m_orderY, SIGNAL(valueChanged(int)), this, SLOT(orderChanged(int)));
connect(m_targetX, SIGNAL(valueChanged(int)), this, SLOT(targetChanged(int)));
connect(m_targetY, SIGNAL(valueChanged(int)), this, SLOT(targetChanged(int)));
connect(m_divisor, SIGNAL(valueChanged(double)), this, SLOT(divisorChanged(double)));
connect(m_bias, SIGNAL(valueChanged(double)), this, SLOT(biasChanged(double)));
connect(kernelButton, SIGNAL(clicked(bool)), this, SLOT(editKernel()));
connect(m_preserveAlpha, SIGNAL(toggled(bool)), this, SLOT(preserveAlphaChanged(bool)));
m_matrixModel = new MatrixDataModel(this);
}
bool ConvolveMatrixEffectConfigWidget::editFilterEffect(KoFilterEffect *filterEffect)
{
m_effect = dynamic_cast<ConvolveMatrixEffect *>(filterEffect);
if (!m_effect) {
return false;
}
m_edgeMode->blockSignals(true);
m_edgeMode->setCurrentIndex(m_effect->edgeMode());
m_edgeMode->blockSignals(false);
m_orderX->blockSignals(true);
m_orderX->setValue(m_effect->order().x());
m_orderX->blockSignals(false);
m_orderY->blockSignals(true);
m_orderY->setValue(m_effect->order().y());
m_orderY->blockSignals(false);
m_targetX->blockSignals(true);
m_targetX->setMaximum(m_orderX->value());
m_targetX->setValue(m_effect->target().x() + 1);
m_targetX->blockSignals(false);
m_targetY->blockSignals(true);
m_targetY->setMaximum(m_orderY->value());
m_targetY->setValue(m_effect->target().y() + 1);
m_targetY->blockSignals(false);
m_divisor->blockSignals(true);
m_divisor->setValue(m_effect->divisor());
m_divisor->blockSignals(false);
m_bias->blockSignals(true);
m_bias->setValue(m_effect->bias());
m_bias->blockSignals(false);
m_preserveAlpha->blockSignals(true);
m_preserveAlpha->setChecked(m_effect->isPreserveAlphaEnabled());
m_preserveAlpha->blockSignals(false);
return true;
}
void ConvolveMatrixEffectConfigWidget::edgeModeChanged(int id)
{
if (!m_effect) {
return;
}
switch (id) {
case ConvolveMatrixEffect::Duplicate:
m_effect->setEdgeMode(ConvolveMatrixEffect::Duplicate);
break;
case ConvolveMatrixEffect::Wrap:
m_effect->setEdgeMode(ConvolveMatrixEffect::Wrap);
break;
case ConvolveMatrixEffect::None:
m_effect->setEdgeMode(ConvolveMatrixEffect::None);
break;
}
emit filterChanged();
}
void ConvolveMatrixEffectConfigWidget::orderChanged(int)
{
if (!m_effect) {
return;
}
QPoint newOrder(m_orderX->value(), m_orderY->value());
QPoint oldOrder = m_effect->order();
if (newOrder != oldOrder) {
m_effect->setOrder(newOrder);
emit filterChanged();
}
m_targetX->setMaximum(newOrder.x());
m_targetY->setMaximum(newOrder.y());
}
void ConvolveMatrixEffectConfigWidget::targetChanged(int)
{
if (!m_effect) {
return;
}
QPoint newTarget(m_targetX->value() - 1, m_targetY->value() - 1);
QPoint oldTarget = m_effect->target();
if (newTarget != oldTarget) {
m_effect->setTarget(newTarget);
emit filterChanged();
}
}
void ConvolveMatrixEffectConfigWidget::divisorChanged(double divisor)
{
if (!m_effect) {
return;
}
if (divisor != m_effect->divisor()) {
m_effect->setDivisor(divisor);
emit filterChanged();
}
}
void ConvolveMatrixEffectConfigWidget::biasChanged(double bias)
{
if (!m_effect) {
return;
}
if (bias != m_effect->bias()) {
m_effect->setBias(bias);
emit filterChanged();
}
}
void ConvolveMatrixEffectConfigWidget::preserveAlphaChanged(bool checked)
{
if (!m_effect) {
return;
}
m_effect->enablePreserveAlpha(checked);
emit filterChanged();
}
void ConvolveMatrixEffectConfigWidget::editKernel()
{
if (!m_effect) {
return;
}
QVector<qreal> oldKernel = m_effect->kernel();
QPoint kernelSize = m_effect->order();
m_matrixModel->setMatrix(oldKernel, kernelSize.y(), kernelSize.x());
connect(m_matrixModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(kernelChanged()));
QPointer<QDialog> dlg = new QDialog(this);
QTableView *table = new QTableView(dlg);
table->setModel(m_matrixModel);
table->horizontalHeader()->hide();
- table->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
+ table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
table->verticalHeader()->hide();
- table->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
+ table->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
QVBoxLayout *mainLayout = new QVBoxLayout;
dlg->setLayout(mainLayout);
mainLayout->addWidget(table);
if (dlg->exec() == QDialog::Accepted) {
m_effect->setKernel(m_matrixModel->matrix());
emit filterChanged();
} else {
m_effect->setKernel(oldKernel);
}
delete dlg;
disconnect(m_matrixModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(kernelChanged()));
}
void ConvolveMatrixEffectConfigWidget::kernelChanged()
{
if (!m_effect) {
return;
}
m_effect->setKernel(m_matrixModel->matrix());
emit filterChanged();
}
diff --git a/plugins/tools/karbonplugins/filtereffects/MatrixDataModel.cpp b/plugins/tools/karbonplugins/filtereffects/MatrixDataModel.cpp
index ace47513cf..6c0a699657 100644
--- a/plugins/tools/karbonplugins/filtereffects/MatrixDataModel.cpp
+++ b/plugins/tools/karbonplugins/filtereffects/MatrixDataModel.cpp
@@ -1,86 +1,87 @@
/* This file is part of the KDE project
* Copyright (c) 2010 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "MatrixDataModel.h"
#include "kis_num_parser.h"
MatrixDataModel::MatrixDataModel(QObject *parent)
: QAbstractTableModel(parent)
, m_rows(0)
, m_cols(0)
{
}
void MatrixDataModel::setMatrix(const QVector<qreal> &matrix, int rows, int cols)
{
m_matrix = matrix;
m_rows = rows;
m_cols = cols;
Q_ASSERT(m_rows);
Q_ASSERT(m_cols);
Q_ASSERT(m_matrix.count() == m_rows * m_cols);
- reset();
+ beginResetModel();
+ endResetModel();
}
QVector<qreal> MatrixDataModel::matrix() const
{
return m_matrix;
}
int MatrixDataModel::rowCount(const QModelIndex &/*parent*/) const
{
return m_rows;
}
int MatrixDataModel::columnCount(const QModelIndex &/*parent*/) const
{
return m_cols;
}
QVariant MatrixDataModel::data(const QModelIndex &index, int role) const
{
int element = index.row() * m_cols + index.column();
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
return QVariant(QString("%1").arg(m_matrix[element], 2));
break;
default:
return QVariant();
}
}
bool MatrixDataModel::setData(const QModelIndex &index, const QVariant &value, int /*role*/)
{
int element = index.row() * m_cols + index.column();
bool valid = false;
qreal elementValue = KisNumericParser::parseSimpleMathExpr(value.toString(), &valid);
if (!valid) {
return false;
}
m_matrix[element] = elementValue;
emit dataChanged(index, index);
return true;
}
Qt::ItemFlags MatrixDataModel::flags(const QModelIndex &/*index*/) const
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
diff --git a/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectSceneItems.cpp b/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectSceneItems.cpp
index 871a4f8003..b633cc4ef5 100644
--- a/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectSceneItems.cpp
+++ b/plugins/tools/karbonplugins/tools/filterEffectTool/FilterEffectSceneItems.cpp
@@ -1,321 +1,321 @@
/* This file is part of the KDE project
* Copyright (c) 2009 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "FilterEffectSceneItems.h"
#include "KoFilterEffect.h"
#include <QPen>
#include <QBrush>
#include <QFont>
#include <QDrag>
#include <QWidget>
const QSizeF ConnectorSize = QSize(20, 20);
const qreal ItemWidth = 15 * ConnectorSize.height();
const qreal FontSize = 0.8 * ConnectorSize.height();
ConnectorItem::ConnectorItem(ConnectorType type, int index, QGraphicsItem *parent)
: QGraphicsEllipseItem(parent)
, m_type(type)
, m_index(index)
{
if (m_type == Output) {
setBrush(QBrush(Qt::red));
} else if (m_type == Input) {
setBrush(QBrush(Qt::green));
}
setAcceptDrops(true);
setRect(QRectF(QPointF(), ConnectorSize));
}
void ConnectorItem::setCenter(const QPointF &position)
{
QRectF r = rect();
r.moveCenter(position);
setRect(r);
}
ConnectorItem::ConnectorType ConnectorItem::connectorType()
{
return m_type;
}
int ConnectorItem::connectorIndex() const
{
return m_index;
}
KoFilterEffect *ConnectorItem::effect() const
{
if (!parentItem()) {
return 0;
}
EffectItemBase *effectItem = dynamic_cast<EffectItemBase *>(parentItem());
if (!effectItem) {
return 0;
}
return effectItem->effect();
}
ConnectorMimeData::ConnectorMimeData(ConnectorItem *connector)
: m_connector(connector)
{
}
ConnectorItem *ConnectorMimeData::connector() const
{
return m_connector;
}
EffectItemBase::EffectItemBase(KoFilterEffect *effect)
: QGraphicsRectItem(0), m_effect(effect)
{
setZValue(1);
setFlags(QGraphicsItem::ItemIsSelectable);
setAcceptDrops(true);
setHandlesChildEvents(true);
}
void EffectItemBase::createText(const QString &text)
{
QGraphicsSimpleTextItem *textItem = new QGraphicsSimpleTextItem(text, this);
QFont font = textItem->font();
font.setPointSize(FontSize);
textItem->setFont(font);
QRectF textBox = textItem->boundingRect();
QPointF offset = rect().center() - textBox.center();
- textItem->translate(offset.x(), offset.y());
+ setTransform(QTransform::fromTranslate(offset.x(), offset.y()), true);
}
void EffectItemBase::createOutput(const QPointF &position, const QString &name)
{
ConnectorItem *connector = new ConnectorItem(ConnectorItem::Output, 0, this);
connector->setCenter(position);
m_outputPosition = position;
m_outputName = name;
}
void EffectItemBase::createInput(const QPointF &position)
{
int inputCount = m_inputPositions.count();
ConnectorItem *connector = new ConnectorItem(ConnectorItem::Input, inputCount, this);
connector->setCenter(position);
m_inputPositions.append(position);
}
QPointF EffectItemBase::outputPosition() const
{
return m_outputPosition;
}
QPointF EffectItemBase::inputPosition(int index) const
{
if (index < 0 || index >= m_inputPositions.count()) {
return QPointF();
}
return m_inputPositions[index];
}
QString EffectItemBase::outputName() const
{
return m_outputName;
}
QSizeF EffectItemBase::connectorSize() const
{
return ConnectorSize;
}
KoFilterEffect *EffectItemBase::effect() const
{
return m_effect;
}
void EffectItemBase::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
ConnectorItem *connector = connectorAtPosition(event->scenePos());
if (!connector) {
return;
}
ConnectorMimeData *data = new ConnectorMimeData(connector);
QDrag *drag = new QDrag(event->widget());
drag->setMimeData(data);
drag->start();
}
void EffectItemBase::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
{
event->ignore();
ConnectorItem *targetItem = connectorAtPosition(event->scenePos());
if (!targetItem) {
return;
}
const ConnectorMimeData *data = dynamic_cast<const ConnectorMimeData *>(event->mimeData());
if (!data) {
return;
}
ConnectorItem *sourceItem = data->connector();
int sourceItemType = sourceItem->connectorType();
int targetItemType = targetItem->connectorType();
if (sourceItemType == targetItemType) {
return;
}
// do not accept connection within single effect item
if (sourceItem->parentItem() == targetItem->parentItem()) {
return;
}
if (sourceItemType == ConnectorItem::Input) {
// we can only connect input with output above
if (sourceItem->scenePos().y() < targetItem->scenePos().y()) {
return;
}
}
if (sourceItemType == ConnectorItem::Output) {
// we can only connect output with input below
if (sourceItem->scenePos().y() > targetItem->scenePos().y()) {
return;
}
}
event->accept();
}
void EffectItemBase::dropEvent(QGraphicsSceneDragDropEvent *event)
{
ConnectorItem *connector = connectorAtPosition(event->scenePos());
if (!connector) {
return;
}
const ConnectorMimeData *data = dynamic_cast<const ConnectorMimeData *>(event->mimeData());
if (!data) {
return;
}
}
ConnectorItem *EffectItemBase::connectorAtPosition(const QPointF &scenePosition)
{
Q_FOREACH (QGraphicsItem *childItem, childItems()) {
ConnectorItem *connector = dynamic_cast<ConnectorItem *>(childItem);
if (!connector) {
continue;
}
if (connector->contains(connector->mapFromScene(scenePosition))) {
return connector;
}
}
return 0;
}
DefaultInputItem::DefaultInputItem(const QString &name, KoFilterEffect *effect)
: EffectItemBase(effect), m_name(name)
{
setRect(0, 0, ItemWidth, 2 * ConnectorSize.height());
createOutput(QPointF(ItemWidth, 0.5 * rect().height()), name);
createText(name);
QLinearGradient g(QPointF(0, 0), QPointF(1, 1));
g.setCoordinateMode(QGradient::ObjectBoundingMode);
g.setColorAt(0, Qt::white);
g.setColorAt(1, QColor(255, 168, 88));
setBrush(QBrush(g));
}
EffectItem::EffectItem(KoFilterEffect *effect)
: EffectItemBase(effect)
{
Q_ASSERT(effect);
QRectF circle(QPointF(), ConnectorSize);
QPointF position(ItemWidth, ConnectorSize.height());
// create input connectors
int requiredInputCount = effect->requiredInputCount();
int usedInputCount = qMax(requiredInputCount, effect->inputs().count());
for (int i = 0; i < usedInputCount; ++i) {
createInput(position);
position.ry() += 1.5 * ConnectorSize.height();
}
// create a new input connector when maximal input count in not reached yet
if (usedInputCount < effect->maximalInputCount()) {
createInput(position);
position.ry() += 1.5 * ConnectorSize.height();
}
// create output connector
position.ry() += 0.5 * ConnectorSize.height();
createOutput(position, effect->output());
setRect(0, 0, ItemWidth, position.y() + ConnectorSize.height());
createText(effect->id());
QLinearGradient g(QPointF(0, 0), QPointF(1, 1));
g.setCoordinateMode(QGradient::ObjectBoundingMode);
g.setColorAt(0, Qt::white);
g.setColorAt(1, QColor(0, 192, 192));
setBrush(QBrush(g));
}
ConnectionItem::ConnectionItem(EffectItemBase *source, EffectItemBase *target, int targetInput)
: QGraphicsPathItem(0)
, m_source(source)
, m_target(target)
, m_targetInput(targetInput)
{
setPen(QPen(Qt::black));
}
EffectItemBase *ConnectionItem::sourceItem() const
{
return m_source;
}
EffectItemBase *ConnectionItem::targetItem() const
{
return m_target;
}
int ConnectionItem::targetInput() const
{
return m_targetInput;
}
void ConnectionItem::setSourceItem(EffectItemBase *source)
{
m_source = source;
}
void ConnectionItem::setTargetItem(EffectItemBase *target, int targetInput)
{
m_target = target;
m_targetInput = targetInput;
}
diff --git a/plugins/tools/selectiontools/kis_tool_select_elliptical.cc b/plugins/tools/selectiontools/kis_tool_select_elliptical.cc
index fd00f98fe9..df26677de9 100644
--- a/plugins/tools/selectiontools/kis_tool_select_elliptical.cc
+++ b/plugins/tools/selectiontools/kis_tool_select_elliptical.cc
@@ -1,101 +1,100 @@
/*
* kis_tool_select_elliptical.cc -- part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_select_elliptical.h"
#include <QVBoxLayout>
#include "kis_painter.h"
#include <brushengine/kis_paintop_registry.h>
#include "kis_selection_options.h"
#include "kis_canvas2.h"
#include "kis_pixel_selection.h"
#include "kis_selection_tool_helper.h"
#include "kis_shape_tool_helper.h"
#include "KisViewManager.h"
#include "kis_selection_manager.h"
__KisToolSelectEllipticalLocal::__KisToolSelectEllipticalLocal(KoCanvasBase *canvas)
: KisToolEllipseBase(canvas, KisToolEllipseBase::SELECT,
KisCursor::load("tool_elliptical_selection_cursor.png", 6, 6))
{
setObjectName("tool_select_elliptical");
}
void __KisToolSelectEllipticalLocal::finishRect(const QRectF &rect)
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kisCanvas);
KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Ellipse"));
if (helper.tryDeselectCurrentSelection(pixelToView(rect), selectionAction())) {
return;
}
if (selectionMode() == PIXEL_SELECTION) {
KisPixelSelectionSP tmpSel = new KisPixelSelection();
KisPainter painter(tmpSel);
painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace()));
- painter.setPaintOpPreset(currentPaintOpPreset(), currentNode(), currentImage());
painter.setAntiAliasPolygonFill(antiAliasSelection());
painter.setFillStyle(KisPainter::FillStyleForegroundColor);
painter.setStrokeStyle(KisPainter::StrokeStyleNone);
painter.paintEllipse(rect);
QPainterPath cache;
cache.addEllipse(rect);
tmpSel->setOutlineCache(cache);
helper.selectPixelSelection(tmpSel, selectionAction());
} else {
QRectF ptRect = convertToPt(rect);
KoShape* shape = KisShapeToolHelper::createEllipseShape(ptRect);
helper.addSelectionShape(shape);
}
}
KisToolSelectElliptical::KisToolSelectElliptical(KoCanvasBase *canvas):
KisToolSelectEllipticalTemplate(canvas, i18n("Elliptical Selection"))
{
connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged,
this, &KisToolSelectElliptical::setSelectionAction);
}
void KisToolSelectElliptical::setSelectionAction(int action)
{
changeSelectionAction(action);
}
QMenu* KisToolSelectElliptical::popupActionsMenu()
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kisCanvas);
return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas);
}
diff --git a/plugins/tools/selectiontools/kis_tool_select_outline.cc b/plugins/tools/selectiontools/kis_tool_select_outline.cc
index e33fa4f738..9bd049a44e 100644
--- a/plugins/tools/selectiontools/kis_tool_select_outline.cc
+++ b/plugins/tools/selectiontools/kis_tool_select_outline.cc
@@ -1,273 +1,272 @@
/*
* kis_tool_select_freehand.h - part of Krayon^WKrita
*
* Copyright (c) 2000 John Califf <jcaliff@compuzone.net>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_select_outline.h"
#include <QApplication>
#include <QPainter>
#include <QWidget>
#include <QPainterPath>
#include <QLayout>
#include <QVBoxLayout>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoPointerEvent.h>
#include <KoShapeController.h>
#include <KoPathShape.h>
#include <KoColorSpace.h>
#include <KoCompositeOp.h>
#include <KoViewConverter.h>
#include <kis_layer.h>
#include <kis_selection_options.h>
#include <kis_cursor.h>
#include <kis_image.h>
#include "kis_painter.h"
#include <brushengine/kis_paintop_registry.h>
#include "canvas/kis_canvas2.h"
#include "kis_pixel_selection.h"
#include "kis_selection_tool_helper.h"
#include "kis_algebra_2d.h"
#define FEEDBACK_LINE_WIDTH 2
KisToolSelectOutline::KisToolSelectOutline(KoCanvasBase * canvas)
: KisToolSelect(canvas,
KisCursor::load("tool_outline_selection_cursor.png", 5, 5),
i18n("Outline Selection")),
m_continuedMode(false)
{
connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged,
this, &KisToolSelectOutline::setSelectionAction);
}
KisToolSelectOutline::~KisToolSelectOutline()
{
}
void KisToolSelectOutline::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Control) {
m_continuedMode = true;
}
KisToolSelect::keyPressEvent(event);
}
void KisToolSelectOutline::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);
}
void KisToolSelectOutline::mouseMoveEvent(KoPointerEvent *event)
{
m_lastCursorPos = convertToPixelCoord(event);
if (m_continuedMode && mode() != PAINT_MODE) {
updateContinuedMode();
}
}
void KisToolSelectOutline::beginPrimaryAction(KoPointerEvent *event)
{
KisToolSelectBase::beginPrimaryAction(event);
if (!selectionEditable()) {
event->ignore();
return;
}
setMode(KisTool::PAINT_MODE);
if (m_continuedMode && !m_points.isEmpty()) {
m_paintPath.lineTo(pixelToView(convertToPixelCoord(event)));
} else {
m_paintPath.moveTo(pixelToView(convertToPixelCoord(event)));
}
m_points.append(convertToPixelCoord(event));
}
void KisToolSelectOutline::continuePrimaryAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
KisToolSelectBase::continuePrimaryAction(event);
QPointF point = convertToPixelCoord(event);
m_paintPath.lineTo(pixelToView(point));
m_points.append(point);
updateFeedback();
}
void KisToolSelectOutline::endPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
KisToolSelectBase::endPrimaryAction(event);
setMode(KisTool::HOVER_MODE);
if (!m_continuedMode) {
finishSelectionAction();
}
}
void KisToolSelectOutline::finishSelectionAction()
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas);
kisCanvas->updateCanvas();
QRectF boundingViewRect =
pixelToView(KisAlgebra2D::accumulateBounds(m_points));
KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select by Outline"));
if (m_points.count() > 2 &&
!helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction())) {
QApplication::setOverrideCursor(KisCursor::waitCursor());
if (selectionMode() == PIXEL_SELECTION) {
KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
KisPainter painter(tmpSel);
painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace()));
- painter.setPaintOpPreset(currentPaintOpPreset(), currentNode(), currentImage());
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);
}
QApplication::restoreOverrideCursor();
}
m_points.clear();
m_paintPath = QPainterPath();
}
void KisToolSelectOutline::paint(QPainter& gc, const KoViewConverter &converter)
{
Q_UNUSED(converter);
if ((mode() == KisTool::PAINT_MODE || m_continuedMode) &&
!m_points.isEmpty()) {
QPainterPath outline = m_paintPath;
if (m_continuedMode && mode() != KisTool::PAINT_MODE) {
outline.lineTo(pixelToView(m_lastCursorPos));
}
paintToolOutline(&gc, outline);
}
}
void KisToolSelectOutline::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 KisToolSelectOutline::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 KisToolSelectOutline::deactivate()
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas);
kisCanvas->updateCanvas();
m_continuedMode = false;
KisTool::deactivate();
}
void KisToolSelectOutline::setSelectionAction(int action)
{
changeSelectionAction(action);
}
QMenu* KisToolSelectOutline::popupActionsMenu()
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kisCanvas);
return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas);
}
diff --git a/plugins/tools/selectiontools/kis_tool_select_polygonal.cc b/plugins/tools/selectiontools/kis_tool_select_polygonal.cc
index 46363277b1..5ceb25760c 100644
--- a/plugins/tools/selectiontools/kis_tool_select_polygonal.cc
+++ b/plugins/tools/selectiontools/kis_tool_select_polygonal.cc
@@ -1,113 +1,112 @@
/*
* kis_tool_select_polygonal.h - part of Krayon^WKrita
*
* Copyright (c) 2000 John Califf <jcaliff@compuzone.net>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_select_polygonal.h"
#include <KoPathShape.h>
#include "kis_painter.h"
#include <brushengine/kis_paintop_registry.h>
#include "kis_selection_options.h"
#include "kis_canvas2.h"
#include "kis_pixel_selection.h"
#include "kis_selection_tool_helper.h"
#include "kis_shape_tool_helper.h"
#include "KisViewManager.h"
#include "kis_selection_manager.h"
__KisToolSelectPolygonalLocal::__KisToolSelectPolygonalLocal(KoCanvasBase *canvas)
: KisToolPolylineBase(canvas, KisToolPolylineBase::SELECT,
KisCursor::load("tool_polygonal_selection_cursor.png", 6, 6))
{
setObjectName("tool_select_polygonal");
}
void __KisToolSelectPolygonalLocal::finishPolyline(const QVector<QPointF> &points)
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kisCanvas);
if (!kisCanvas)
return;
KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select Polygon"));
if (selectionMode() == PIXEL_SELECTION) {
KisPixelSelectionSP tmpSel = new KisPixelSelection();
KisPainter painter(tmpSel);
painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace()));
- painter.setPaintOpPreset(currentPaintOpPreset(), currentNode(), currentImage());
painter.setAntiAliasPolygonFill(antiAliasSelection());
painter.setFillStyle(KisPainter::FillStyleForegroundColor);
painter.setStrokeStyle(KisPainter::StrokeStyleNone);
painter.paintPolygon(points);
QPainterPath cache;
cache.addPolygon(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(points[0]));
for (int i = 1; i < points.count(); i++)
path->lineTo(resolutionMatrix.map(points[i]));
path->close();
path->normalize();
helper.addSelectionShape(path);
}
}
KisToolSelectPolygonal::KisToolSelectPolygonal(KoCanvasBase *canvas):
KisToolSelectBase<__KisToolSelectPolygonalLocal>(canvas, i18n("Polygonal Selection"))
{
connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged,
this, &KisToolSelectPolygonal::setSelectionAction);
}
void KisToolSelectPolygonal::setSelectionAction(int action)
{
changeSelectionAction(action);
}
QMenu* KisToolSelectPolygonal::popupActionsMenu()
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kisCanvas);
return KisSelectionToolHelper::getSelectionContextMenu(kisCanvas);
}
diff --git a/plugins/tools/tool_transform2/kis_liquify_paint_helper.cpp b/plugins/tools/tool_transform2/kis_liquify_paint_helper.cpp
index 3f6a8a9009..82bcf34f7b 100644
--- a/plugins/tools/tool_transform2/kis_liquify_paint_helper.cpp
+++ b/plugins/tools/tool_transform2/kis_liquify_paint_helper.cpp
@@ -1,142 +1,142 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_liquify_paint_helper.h"
#include "kis_algebra_2d.h"
#include "KoPointerEvent.h"
#include <brushengine/kis_paint_information.h>
#include "kis_painting_information_builder.h"
#include "kis_liquify_transform_worker.h"
#include <brushengine/kis_paintop_utils.h>
#include "kis_coordinates_converter.h"
#include "kis_liquify_paintop.h"
#include "kis_liquify_properties.h"
struct KisLiquifyPaintHelper::Private
{
Private(const KisCoordinatesConverter *_converter)
: converter(_converter),
infoBuilder(new KisConverterPaintingInformationBuilder(converter)),
hasPaintedAtLeastOnce(false)
{
}
KisPaintInformation previousPaintInfo;
QScopedPointer<KisLiquifyPaintop> paintOp;
KisDistanceInformation currentDistance;
const KisCoordinatesConverter *converter;
QScopedPointer<KisPaintingInformationBuilder> infoBuilder;
QTime strokeTime;
bool hasPaintedAtLeastOnce;
KisDistanceInformation previousDistanceInfo;
KisPaintOpUtils::PositionHistory lastOutlinePos;
void updatePreviousPaintInfo(const KisPaintInformation &info);
};
KisLiquifyPaintHelper::KisLiquifyPaintHelper(const KisCoordinatesConverter *converter)
: m_d(new Private(converter))
{
}
KisLiquifyPaintHelper::~KisLiquifyPaintHelper()
{
}
void KisLiquifyPaintHelper::Private::updatePreviousPaintInfo(const KisPaintInformation &info)
{
QPointF prevPos = lastOutlinePos.pushThroughHistory(info.pos());
qreal angle = KisAlgebra2D::directionBetweenPoints(prevPos, info.pos(), 0);
previousDistanceInfo =
- KisDistanceInformation(prevPos, 0, angle);
+ KisDistanceInformation(prevPos, angle);
previousPaintInfo = info;
}
QPainterPath KisLiquifyPaintHelper::brushOutline(const KisLiquifyProperties &props)
{
KisPaintInformation::DistanceInformationRegistrar registrar =
m_d->previousPaintInfo.registerDistanceInformation(&m_d->previousDistanceInfo);
return KisLiquifyPaintop::brushOutline(props, m_d->previousPaintInfo);
}
void KisLiquifyPaintHelper::configurePaintOp(const KisLiquifyProperties &props,
KisLiquifyTransformWorker *worker)
{
m_d->paintOp.reset(new KisLiquifyPaintop(props, worker));
}
void KisLiquifyPaintHelper::startPaint(KoPointerEvent *event, const KoCanvasResourceManager *manager)
{
KIS_ASSERT_RECOVER_RETURN(m_d->paintOp);
m_d->strokeTime.start();
KisPaintInformation pi =
m_d->infoBuilder->startStroke(event, m_d->strokeTime.elapsed(), manager);
m_d->updatePreviousPaintInfo(pi);
m_d->hasPaintedAtLeastOnce = false;
}
void KisLiquifyPaintHelper::continuePaint(KoPointerEvent *event)
{
KIS_ASSERT_RECOVER_RETURN(m_d->paintOp);
KisPaintInformation pi =
m_d->infoBuilder->continueStroke(event, m_d->strokeTime.elapsed());
KisPaintOpUtils::paintLine(*m_d->paintOp.data(),
m_d->previousPaintInfo,
pi,
&m_d->currentDistance,
false, false);
m_d->updatePreviousPaintInfo(pi);
m_d->hasPaintedAtLeastOnce = true;
}
bool KisLiquifyPaintHelper::endPaint(KoPointerEvent *event)
{
KIS_ASSERT_RECOVER(m_d->paintOp) { return false; }
if (!m_d->hasPaintedAtLeastOnce) {
KisPaintInformation pi =
m_d->infoBuilder->continueStroke(event, m_d->strokeTime.elapsed());
pi.paintAt(*m_d->paintOp.data(), &m_d->previousDistanceInfo);
}
m_d->paintOp.reset();
return !m_d->hasPaintedAtLeastOnce;
}
void KisLiquifyPaintHelper::hoverPaint(KoPointerEvent *event)
{
QPointF imagePoint = m_d->converter->documentToImage(event->pos());
KisPaintInformation pi = m_d->infoBuilder->hover(imagePoint, event);
m_d->updatePreviousPaintInfo(pi);
}
diff --git a/sdk/tests/KisRectsCollisionsTracker.h b/sdk/tests/KisRectsCollisionsTracker.h
new file mode 100644
index 0000000000..b2af80fde3
--- /dev/null
+++ b/sdk/tests/KisRectsCollisionsTracker.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KISRECTSCOLLISIONSTRACKER_H
+#define KISRECTSCOLLISIONSTRACKER_H
+
+#include <QList>
+#include <QRect>
+#include <QMutex>
+#include <QMutexLocker>
+
+#include "kis_assert.h"
+
+
+class KisRectsCollisionsTracker
+{
+public:
+
+ void startAccessingRect(const QRect &rc) {
+ QMutexLocker l(&m_mutex);
+
+ checkUniqueAccessImpl(rc, "start");
+ m_rectsInProgress.append(rc);
+ }
+
+ void endAccessingRect(const QRect &rc) {
+ QMutexLocker l(&m_mutex);
+ const bool result = m_rectsInProgress.removeOne(rc);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(result);
+ checkUniqueAccessImpl(rc, "end");
+ }
+
+private:
+
+ bool checkUniqueAccessImpl(const QRect &rect, const QString &tag) {
+
+ Q_FOREACH (const QRect &rc, m_rectsInProgress) {
+ if (rc != rect && rect.intersects(rc)) {
+ ENTER_FUNCTION() << "FAIL: concurrect access from" << rect << "to" << rc << tag;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+private:
+ QList<QRect> m_rectsInProgress;
+ QMutex m_mutex;
+};
+
+#endif // KISRECTSCOLLISIONSTRACKER_H
diff --git a/sdk/tests/stroke_testing_utils.cpp b/sdk/tests/stroke_testing_utils.cpp
index bc47272732..19eb4e475e 100644
--- a/sdk/tests/stroke_testing_utils.cpp
+++ b/sdk/tests/stroke_testing_utils.cpp
@@ -1,350 +1,362 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "stroke_testing_utils.h"
#include <QtTest>
#include <QDir>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoCompositeOpRegistry.h>
#include <brushengine/kis_paintop_preset.h>
#include <resources/KoPattern.h>
#include "kis_canvas_resource_provider.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "kis_paint_layer.h"
#include "kis_group_layer.h"
+#include <KisViewManager.h>
#include "testutil.h"
KisImageSP utils::createImage(KisUndoStore *undoStore, const QSize &imageSize) {
QRect imageRect(0,0,imageSize.width(),imageSize.height());
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "stroke test");
KisPaintLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8);
KisPaintLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8);
KisPaintLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8);
KisPaintLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE_U8);
KisPaintLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE_U8);
image->lock();
image->addNode(paintLayer1);
image->addNode(paintLayer2);
image->addNode(paintLayer3);
image->addNode(paintLayer4);
image->addNode(paintLayer5);
image->unlock();
return image;
}
KoCanvasResourceManager* utils::createResourceManager(KisImageWSP image,
KisNodeSP node,
const QString &presetFileName)
{
KoCanvasResourceManager *manager = new KoCanvasResourceManager();
+ KisViewManager::initializeResourceManager(manager);
QVariant i;
i.setValue(KoColor(Qt::black, image->colorSpace()));
manager->setResource(KoCanvasResourceManager::ForegroundColor, i);
i.setValue(KoColor(Qt::white, image->colorSpace()));
manager->setResource(KoCanvasResourceManager::BackgroundColor, i);
i.setValue(static_cast<void*>(0));
manager->setResource(KisCanvasResourceProvider::CurrentPattern, i);
manager->setResource(KisCanvasResourceProvider::CurrentGradient, i);
manager->setResource(KisCanvasResourceProvider::CurrentGeneratorConfiguration, i);
if(!node) {
node = image->root();
while(node && !dynamic_cast<KisPaintLayer*>(node.data())) {
node = node->firstChild();
}
Q_ASSERT(node && dynamic_cast<KisPaintLayer*>(node.data()));
}
i.setValue(node);
manager->setResource(KisCanvasResourceProvider::CurrentKritaNode, i);
KisPaintOpPresetSP preset;
if (!presetFileName.isEmpty()) {
QString fullFileName = TestUtil::fetchDataFileLazy(presetFileName);
preset = new KisPaintOpPreset(fullFileName);
bool presetValid = preset->load();
Q_ASSERT(presetValid); Q_UNUSED(presetValid);
i.setValue(preset);
manager->setResource(KisCanvasResourceProvider::CurrentPaintOpPreset, i);
}
i.setValue(COMPOSITE_OVER);
manager->setResource(KisCanvasResourceProvider::CurrentCompositeOp, i);
i.setValue(false);
manager->setResource(KisCanvasResourceProvider::MirrorHorizontal, i);
i.setValue(false);
manager->setResource(KisCanvasResourceProvider::MirrorVertical, i);
i.setValue(1.0);
manager->setResource(KisCanvasResourceProvider::Opacity, i);
i.setValue(1.0);
manager->setResource(KisCanvasResourceProvider::HdrExposure, i);
return manager;
}
utils::StrokeTester::StrokeTester(const QString &name, const QSize &imageSize, const QString &presetFilename)
: m_name(name),
m_imageSize(imageSize),
m_presetFilename(presetFilename),
m_numIterations(1),
m_baseFuzziness(1)
{
}
utils::StrokeTester::~StrokeTester()
{
}
void utils::StrokeTester::setNumIterations(int value)
{
m_numIterations = value;
}
void utils::StrokeTester::setBaseFuzziness(int value)
{
m_baseFuzziness = value;
}
void utils::StrokeTester::testSimpleStroke()
{
testOneStroke(false, true, false, true);
}
+int utils::StrokeTester::lastStrokeTime() const
+{
+ return m_strokeTime;
+}
+
void utils::StrokeTester::test()
{
testOneStroke(false, false, false);
testOneStroke(false, true, false);
testOneStroke(true, false, false);
testOneStroke(true, true, false);
// The same but with updates (compare against projection)
testOneStroke(false, false, false, true);
testOneStroke(false, true, false, true);
testOneStroke(true, false, false, true);
testOneStroke(true, true, false, true);
// The same, but with an external layer
testOneStroke(false, false, true);
testOneStroke(false, true, true);
testOneStroke(true, false, true);
testOneStroke(true, true, true);
}
void utils::StrokeTester::benchmark()
{
// not cancelled, indirect painting, internal, no updates, no qimage
doStroke(false, true, false, false, false);
}
void utils::StrokeTester::testOneStroke(bool cancelled,
bool indirectPainting,
bool externalLayer,
bool testUpdates)
{
QString testName = formatTestName(m_name,
cancelled,
indirectPainting,
externalLayer);
dbgKrita << "Testcase:" << testName
<< "(comare against " << (testUpdates ? "projection" : "layer") << ")";
QImage resultImage;
resultImage = doStroke(cancelled, indirectPainting, externalLayer, testUpdates);
QImage refImage;
refImage.load(referenceFile(testName));
QPoint temp;
if(!TestUtil::compareQImages(temp, refImage, resultImage, m_baseFuzziness, m_baseFuzziness)) {
refImage.save(dumpReferenceFile(testName));
resultImage.save(resultFile(testName));
QFAIL("Images do not coincide");
}
}
QString utils::StrokeTester::formatTestName(const QString &baseName,
bool cancelled,
bool indirectPainting,
bool externalLayer)
{
QString result = baseName;
result += "_" + m_presetFilename;
result += indirectPainting ? "_indirect" : "_incremental";
result += cancelled ? "_cancelled" : "_finished";
result += externalLayer ? "_external" : "_internal";
return result;
}
QString utils::StrokeTester::referenceFile(const QString &testName)
{
QString path =
QString(FILES_DATA_DIR) + QDir::separator() +
m_name + QDir::separator();
path += testName;
path += ".png";
return path;
}
QString utils::StrokeTester::dumpReferenceFile(const QString &testName)
{
QString path = QString(FILES_OUTPUT_DIR) + QDir::separator();
path += testName;
path += "_expected";
path += ".png";
return path;
}
QString utils::StrokeTester::resultFile(const QString &testName)
{
QString path = QString(FILES_OUTPUT_DIR) + QDir::separator();
path += testName;
path += ".png";
return path;
}
QImage utils::StrokeTester::doStroke(bool cancelled,
bool indirectPainting,
bool externalLayer,
bool testUpdates,
bool needQImage)
{
KisImageSP image = utils::createImage(0, m_imageSize);
KoCanvasResourceManager *manager = utils::createResourceManager(image, 0, m_presetFilename);
KisNodeSP currentNode;
for (int i = 0; i < m_numIterations; i++) {
modifyResourceManager(manager, image, i);
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image,
image->rootLayer()->firstChild(),
manager);
if(externalLayer) {
KisNodeSP externalNode = new KisPaintLayer(0, "extlyr", OPACITY_OPAQUE_U8, image->colorSpace());
resources->setCurrentNode(externalNode);
Q_ASSERT(resources->currentNode() == externalNode);
}
initImage(image, resources->currentNode(), i);
+ QElapsedTimer strokeTime;
+ strokeTime.start();
+
KisStrokeStrategy *stroke = createStroke(indirectPainting, resources, image);
m_strokeId = image->startStroke(stroke);
addPaintingJobs(image, resources, i);
if(!cancelled) {
image->endStroke(m_strokeId);
}
else {
image->cancelStroke(m_strokeId);
}
image->waitForDone();
+
+ m_strokeTime = strokeTime.elapsed();
currentNode = resources->currentNode();
}
beforeCheckingResult(image, currentNode);
QImage resultImage;
if(needQImage) {
KisPaintDeviceSP device = testUpdates ?
image->projection() :
currentNode->paintDevice();
resultImage = device->convertToQImage(0, 0, 0, image->width(), image->height());
}
image = 0;
delete manager;
return resultImage;
}
void utils::StrokeTester::modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image, int iteration)
{
Q_UNUSED(iteration);
modifyResourceManager(manager, image);
}
void utils::StrokeTester::modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image)
{
Q_UNUSED(manager);
Q_UNUSED(image);
}
void utils::StrokeTester::initImage(KisImageWSP image, KisNodeSP activeNode, int iteration)
{
Q_UNUSED(iteration);
initImage(image, activeNode);
}
void utils::StrokeTester::initImage(KisImageWSP image, KisNodeSP activeNode)
{
Q_UNUSED(image);
Q_UNUSED(activeNode);
}
void utils::StrokeTester::addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources,
int iteration)
{
Q_UNUSED(iteration);
addPaintingJobs(image, resources);
}
void utils::StrokeTester::addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources)
{
Q_UNUSED(image);
Q_UNUSED(resources);
}
void utils::StrokeTester::beforeCheckingResult(KisImageWSP image, KisNodeSP activeNode)
{
Q_UNUSED(image);
Q_UNUSED(activeNode);
}
diff --git a/sdk/tests/stroke_testing_utils.h b/sdk/tests/stroke_testing_utils.h
index 27fba8f358..de702524b0 100644
--- a/sdk/tests/stroke_testing_utils.h
+++ b/sdk/tests/stroke_testing_utils.h
@@ -1,107 +1,110 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __STROKE_TESTING_UTILS_H
#define __STROKE_TESTING_UTILS_H
#include <QString>
#include <KoCanvasResourceManager.h>
#include "kis_node.h"
#include "kis_types.h"
#include "kis_stroke_strategy.h"
#include "kis_resources_snapshot.h"
class KisUndoStore;
namespace utils {
KisImageSP createImage(KisUndoStore *undoStore, const QSize &imageSize);
KoCanvasResourceManager* createResourceManager(KisImageWSP image,
KisNodeSP node = 0,
const QString &presetFileName = "autobrush_300px.kpp");
class StrokeTester
{
public:
StrokeTester(const QString &name, const QSize &imageSize, const QString &presetFileName = "autobrush_300px.kpp");
virtual ~StrokeTester();
void testSimpleStroke();
void test();
void benchmark();
void setNumIterations(int value);
void setBaseFuzziness(int value);
+ int lastStrokeTime() const;
+
protected:
KisStrokeId strokeId() {
return m_strokeId;
}
virtual void modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image, int iteration);
virtual void initImage(KisImageWSP image, KisNodeSP activeNode, int iteration);
// overload
virtual void modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image);
// overload
virtual void initImage(KisImageWSP image, KisNodeSP activeNode);
virtual void beforeCheckingResult(KisImageWSP image, KisNodeSP activeNode);
virtual KisStrokeStrategy* createStroke(bool indirectPainting,
KisResourcesSnapshotSP resources,
KisImageWSP image) = 0;
virtual void addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources,
int iteration);
// overload
virtual void addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources);
private:
void testOneStroke(bool cancelled, bool indirectPainting,
bool externalLayer, bool testUpdates = false);
QImage doStroke(bool cancelled, bool indirectPainting,
bool externalLayer, bool testUpdates = false,
bool needQImage = true);
QString formatTestName(const QString &baseName, bool cancelled,
bool indirectPainting, bool externalLayer);
QString referenceFile(const QString &testName);
QString dumpReferenceFile(const QString &testName);
QString resultFile(const QString &testName);
private:
KisStrokeId m_strokeId;
QString m_name;
QSize m_imageSize;
QString m_presetFilename;
int m_numIterations;
int m_baseFuzziness;
+ int m_strokeTime = 0;
};
}
#endif /* __STROKE_TESTING_UTILS_H */
diff --git a/sdk/tests/testutil.h b/sdk/tests/testutil.h
index fc19b75cf3..3970c73949 100644
--- a/sdk/tests/testutil.h
+++ b/sdk/tests/testutil.h
@@ -1,427 +1,492 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TEST_UTIL
#define TEST_UTIL
#include <QProcessEnvironment>
#include <QList>
#include <QTime>
#include <QDir>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KoProgressProxy.h>
#include <kis_paint_device.h>
#include <kis_node.h>
#include <kis_undo_adapter.h>
#include "kis_node_graph_listener.h"
#include "kis_iterator_ng.h"
#include "kis_image.h"
#include "testing_nodes.h"
#ifndef FILES_DATA_DIR
#define FILES_DATA_DIR "."
#endif
#ifndef FILES_DEFAULT_DATA_DIR
#define FILES_DEFAULT_DATA_DIR "."
#endif
#include "qimage_test_util.h"
/**
* Routines that are useful for writing efficient tests
*/
namespace TestUtil
{
inline KisNodeSP findNode(KisNodeSP root, const QString &name) {
if(root->name() == name) return root;
KisNodeSP child = root->firstChild();
while (child) {
if((root = findNode(child, name))) return root;
child = child->nextSibling();
}
return KisNodeSP();
}
inline void dumpNodeStack(KisNodeSP node, QString prefix = QString("\t"))
{
qDebug() << node->name();
KisNodeSP child = node->firstChild();
while (child) {
if (child->childCount() > 0) {
dumpNodeStack(child, prefix + "\t");
} else {
qDebug() << prefix << child->name();
}
child = child->nextSibling();
}
}
class TestProgressBar : public KoProgressProxy {
public:
TestProgressBar()
: m_min(0), m_max(0), m_value(0)
{}
int maximum() const override {
return m_max;
}
void setValue(int value) override {
m_value = value;
}
void setRange(int min, int max) override {
m_min = min;
m_max = max;
}
void setFormat(const QString &format) override {
m_format = format;
}
void setAutoNestedName(const QString &name) {
m_autoNestedName = name;
KoProgressProxy::setAutoNestedName(name);
}
int min() { return m_min; }
int max() { return m_max; }
int value() { return m_value; }
QString format() { return m_format; }
QString autoNestedName() { return m_autoNestedName; }
private:
int m_min;
int m_max;
int m_value;
QString m_format;
QString m_autoNestedName;
};
inline bool comparePaintDevices(QPoint & pt, const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2)
{
// QTime t;
// t.start();
QRect rc1 = dev1->exactBounds();
QRect rc2 = dev2->exactBounds();
if (rc1 != rc2) {
pt.setX(-1);
pt.setY(-1);
}
KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width());
KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width());
int pixelSize = dev1->pixelSize();
for (int y = 0; y < rc1.height(); ++y) {
do {
if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0)
return false;
} while (iter1->nextPixel() && iter2->nextPixel());
iter1->nextRow();
iter2->nextRow();
}
// qDebug() << "comparePaintDevices time elapsed:" << t.elapsed();
return true;
}
template <typename channel_type>
inline bool comparePaintDevicesClever(const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2, channel_type alphaThreshold = 0)
{
QRect rc1 = dev1->exactBounds();
QRect rc2 = dev2->exactBounds();
if (rc1 != rc2) {
qDebug() << "Devices have different size" << ppVar(rc1) << ppVar(rc2);
return false;
}
KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width());
KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width());
int pixelSize = dev1->pixelSize();
for (int y = 0; y < rc1.height(); ++y) {
do {
if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) {
const channel_type* p1 = reinterpret_cast<const channel_type*>(iter1->oldRawData());
const channel_type* p2 = reinterpret_cast<const channel_type*>(iter2->oldRawData());
if (p1[3] < alphaThreshold && p2[3] < alphaThreshold) continue;
qDebug() << "Failed compare paint devices:" << iter1->x() << iter1->y();
qDebug() << "src:" << p1[0] << p1[1] << p1[2] << p1[3];
qDebug() << "dst:" << p2[0] << p2[1] << p2[2] << p2[3];
return false;
}
} while (iter1->nextPixel() && iter2->nextPixel());
iter1->nextRow();
iter2->nextRow();
}
return true;
}
#ifdef FILES_OUTPUT_DIR
struct ExternalImageChecker
{
ExternalImageChecker(const QString &prefix, const QString &testName)
: m_prefix(prefix),
m_testName(testName),
m_success(true),
m_maxFailingPixels(100),
m_fuzzy(1)
{
}
void setMaxFailingPixels(int value) {
m_maxFailingPixels = value;
}
void setFuzzy(int fuzzy){
m_fuzzy = fuzzy;
}
bool testPassed() const {
return m_success;
}
inline bool checkDevice(KisPaintDeviceSP device, KisImageSP image, const QString &caseName) {
bool result =
checkQImageExternal(device->convertToQImage(0, image->bounds()),
m_testName,
m_prefix,
caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels);
m_success &= result;
return result;
}
inline bool checkImage(KisImageSP image, const QString &testName) {
bool result = checkDevice(image->projection(), image, testName);
m_success &= result;
return result;
}
private:
QString m_prefix;
QString m_testName;
bool m_success;
int m_maxFailingPixels;
int m_fuzzy;
};
#endif
inline quint8 alphaDevicePixel(KisPaintDeviceSP dev, qint32 x, qint32 y)
{
KisHLineConstIteratorSP iter = dev->createHLineConstIteratorNG(x, y, 1);
const quint8 *pix = iter->oldRawData();
return *pix;
}
inline void alphaDeviceSetPixel(KisPaintDeviceSP dev, qint32 x, qint32 y, quint8 s)
{
KisHLineIteratorSP iter = dev->createHLineIteratorNG(x, y, 1);
quint8 *pix = iter->rawData();
*pix = s;
}
inline bool checkAlphaDeviceFilledWithPixel(KisPaintDeviceSP dev, const QRect &rc, quint8 expected)
{
KisHLineIteratorSP it = dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
for (int y = rc.y(); y < rc.y() + rc.height(); y++) {
for (int x = rc.x(); x < rc.x() + rc.width(); x++) {
if(*((quint8*)it->rawData()) != expected) {
errKrita << "At point:" << x << y;
errKrita << "Expected pixel:" << expected;
errKrita << "Actual pixel: " << *((quint8*)it->rawData());
return false;
}
it->nextPixel();
}
it->nextRow();
}
return true;
}
class TestNode : public DefaultNode
{
Q_OBJECT
public:
KisNodeSP clone() const override {
return KisNodeSP(new TestNode(*this));
}
};
class TestGraphListener : public KisNodeGraphListener
{
public:
void aboutToAddANode(KisNode *parent, int index) override {
KisNodeGraphListener::aboutToAddANode(parent, index);
beforeInsertRow = true;
}
void nodeHasBeenAdded(KisNode *parent, int index) override {
KisNodeGraphListener::nodeHasBeenAdded(parent, index);
afterInsertRow = true;
}
void aboutToRemoveANode(KisNode *parent, int index) override {
KisNodeGraphListener::aboutToRemoveANode(parent, index);
beforeRemoveRow = true;
}
void nodeHasBeenRemoved(KisNode *parent, int index) override {
KisNodeGraphListener::nodeHasBeenRemoved(parent, index);
afterRemoveRow = true;
}
void aboutToMoveNode(KisNode *parent, int oldIndex, int newIndex) override {
KisNodeGraphListener::aboutToMoveNode(parent, oldIndex, newIndex);
beforeMove = true;
}
void nodeHasBeenMoved(KisNode *parent, int oldIndex, int newIndex) override {
KisNodeGraphListener::nodeHasBeenMoved(parent, oldIndex, newIndex);
afterMove = true;
}
bool beforeInsertRow;
bool afterInsertRow;
bool beforeRemoveRow;
bool afterRemoveRow;
bool beforeMove;
bool afterMove;
void resetBools() {
beforeRemoveRow = false;
afterRemoveRow = false;
beforeInsertRow = false;
afterInsertRow = false;
beforeMove = false;
afterMove = false;
}
};
}
#include <kis_paint_layer.h>
#include <kis_image.h>
#include "kis_undo_stores.h"
namespace TestUtil {
struct MaskParent
{
MaskParent(const QRect &_imageRect = QRect(0,0,512,512))
: imageRect(_imageRect) {
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
undoStore = new KisSurrogateUndoStore();
image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "test image");
layer = KisPaintLayerSP(new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8));
image->addNode(KisNodeSP(layer.data()));
}
KisSurrogateUndoStore *undoStore;
const QRect imageRect;
KisImageSP image;
KisPaintLayerSP layer;
};
}
namespace TestUtil {
class MeasureAvgPortion
{
public:
MeasureAvgPortion(int period)
: m_period(period),
m_val(0),
m_total(0),
m_cycles(0)
{
}
~MeasureAvgPortion() {
printValues(true);
}
void addVal(int x) {
m_val += x;
}
void addTotal(int x) {
m_total += x;
m_cycles++;
printValues();
}
private:
void printValues(bool force = false) {
if (m_cycles > m_period || force) {
qDebug() << "Val / Total:" << qreal(m_val) / qreal(m_total);
qDebug() << "Avg. Val: " << qreal(m_val) / m_cycles;
qDebug() << "Avg. Total: " << qreal(m_total) / m_cycles;
qDebug() << ppVar(m_val) << ppVar(m_total) << ppVar(m_cycles);
m_val = 0;
m_total = 0;
m_cycles = 0;
}
}
private:
int m_period;
qint64 m_val;
qint64 m_total;
qint64 m_cycles;
};
+struct MeasureDistributionStats {
+ MeasureDistributionStats(int numBins, const QString &name = QString())
+ : m_numBins(numBins),
+ m_name(name)
+ {
+ reset();
+ }
+
+ void reset() {
+ m_values.clear();
+ m_values.resize(m_numBins);
+ }
+
+ void addValue(int value) {
+ addValue(value, 1);
+ }
+
+ void addValue(int value, int increment) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(value >= 0);
+
+ if (value >= m_numBins) {
+ m_values[m_numBins - 1] += increment;
+ } else {
+ m_values[value] += increment;
+ }
+ }
+
+ void print() {
+ qCritical() << "============= Stats ==============";
+
+ if (!m_name.isEmpty()) {
+ qCritical() << "Name:" << m_name;
+ }
+
+ int total = 0;
+
+ for (int i = 0; i < m_numBins; i++) {
+ total += m_values[i];
+ }
+
+ for (int i = 0; i < m_numBins; i++) {
+ if (!m_values[i]) continue;
+
+ const QString lastMarker = i == m_numBins - 1 ? "> " : " ";
+
+ const QString line =
+ QString(" %1%2: %3 (%4%)")
+ .arg(lastMarker)
+ .arg(i, 3)
+ .arg(m_values[i], 5)
+ .arg(qreal(m_values[i]) / total * 100.0, 7, 'g', 2);
+
+ qCritical() << qPrintable(line);
+ }
+ qCritical() << "---- ----";
+ qCritical() << qPrintable(QString("Total: %1").arg(total));
+ qCritical() << "==================================";
+ }
+
+private:
+ QVector<int> m_values;
+ int m_numBins = 0;
+ QString m_name;
+};
+
QStringList getHierarchy(KisNodeSP root, const QString &prefix = "");
bool checkHierarchy(KisNodeSP root, const QStringList &expected);
}
#endif