No OneTemporary

File Metadata

Created
Wed, May 22, 3:01 AM
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt
index 07777a2ff2..e00d7a0833 100644
--- a/3rdparty/CMakeLists.txt
+++ b/3rdparty/CMakeLists.txt
@@ -1,229 +1,229 @@
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()
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 "Environment variable 'WindowsSdkDir' not set! Please set it to path of Windows 10 SDK or disable QT_ENABLE_DYNAMIC_OPENGL")
endif ()
endif ()
endif (MINGW)
if (WIN32)
option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON)
endif ()
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 ()
else ()
message(WARNING "Linker Security Flags not enabled!")
endif ()
endif ()
if (DEFINED EP_PREFIX)
set_directory_properties(PROPERTIES EP_PREFIX ${EP_PREFIX})
endif ()
if (MSVC)
message(FATAL_ERROR "Krita cannot be built with MSVC. See the README.md file!")
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 (ANDROID)
set (GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DANDROID_PLATFORM=${ANDROID_PLATFORM} -DANDROID_ABI=${ANDROID_ABI})
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 ()
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)
if (ENABLE_PYTHON_2)
message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.")
else(ENABLE_PYTHON_2)
- find_package(PythonInterp 3.6 EXACT)
- find_package(PythonLibs 3.6 EXACT)
+ find_package(PythonInterp 3.8 EXACT)
+ find_package(PythonLibs 3.8 EXACT)
endif()
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 ()
else (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND)
message(FATAL_ERROR "Python requirements not met. To disable Python deps, set ENABLE_PYTHON_DEPS to OFF.")
endif ()
endif ()
endif ()
# this list must be dependency-ordered
if (ENABLE_PYTHON_DEPS OR NOT MINGW)
add_subdirectory( ext_python )
endif ()
if (MINGW)
add_subdirectory( ext_patch )
add_subdirectory( ext_png2ico )
endif ()
add_subdirectory( ext_lzma )
add_subdirectory( ext_iconv )
add_subdirectory( ext_gettext )
add_subdirectory( ext_zlib )
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_openssl )
add_subdirectory( ext_vc )
add_subdirectory( ext_gsl )
add_subdirectory( ext_fftw3 )
add_subdirectory( ext_ocio )
add_subdirectory( ext_openjpeg )
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 ()
if (MINGW)
add_subdirectory( ext_drmingw )
# add_subdirectory( ext_ffmpeg )
endif ()
if (NOT APPLE)
add_subdirectory( ext_gmic )
endif ()
if(UNIX)
add_subdirectory( ext_pkgconfig )
add_subdirectory( ext_heif )
endif()
add_subdirectory(ext_giflib)
add_subdirectory(ext_quazip)
diff --git a/3rdparty/README.md b/3rdparty/README.md
index 25675c7f48..0e0f5db025 100644
--- a/3rdparty/README.md
+++ b/3rdparty/README.md
@@ -1,269 +1,271 @@
# CMake external projects to build krita's dependencies on Linux, Windows or OSX
If you need to build Krita's dependencies for the following reasons:
* you develop on Windows and aren't using build-tools/windows/build.cmd or KDE's craft
* you develop on OSX and aren't using the scripts in krita/packaging/osx or Homebrew
* you want to build a generic, distro-agnostic version of Krita for Linux and aren't using the scripts in packaging/linux/appimage
* you develop on Linux, but some dependencies aren't available for your distribution and aren't using the scripts in packaging/linux/appimage
and you know what you're doing, you can use the following guide to build
the dependencies that Krita needs.
If you develop on Linux and your distribution has all dependencies available,
YOU DO NOT NEED THIS GUIDE AND YOU SHOULD STOP READING NOW
Otherwise you risk major confusion.
## Prerequisites
Note: on all operating systems the entire procedure is done in a terminal window.
1. git: https://git-scm.com/downloads. Make sure git is in your path
2. CMake 3.3.2 or later: https://cmake.org/download/. Make sure cmake is in your path.
* CMake 3.9 does not build Krita properly at the moment, please use 3.8 or 3.10 instead.
3. Make sure you have a compiler:
* Linux: gcc, minimum version 4.8
* OSX: clang, you need to install xcode for this
* Windows: mingw-w64 7.3 (by mingw-builds): https://sourceforge.net/projects/mingw-w64/
* The Files can be found under "Toolchains targetting Win64/Win32"/"Personal Builds".
* For threading, select posix.
* For exceptions, select seh (64-bit) or dwarf (32-bit).
* Install mingw to something like C:\mingw; the full path must not contain any spaces.
* Make sure mingw's bin folder is in your path. It might be a good
idea to create a batch file which sets the path and start cmd.
* MSVC is *not* supported at the moment.
4. On Windows, you will also need a release of Python 3.6 (*not* 3.7 or any other versions): https://www.python.org. Make sure to have that version of python.exe in your path. This version of Python will be used for two things: to configure Qt and to build the Python scripting module. Make sure that this version of Python comes first in your path. Do not set PYTHONHOME or PYTHONPATH.
* Make sure that your Python will have the correct architecture for the version you are trying to build. If building for 32-bit target, you need the 32-bit release of Python.
5. On Windows, if you want to compile Qt with ANGLE support, you will need to install Windows 10 SDK and have 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 a bare BUILDROOT with $BUILDROOT or %BUILDROOT% whenever you copy and paste a command, depending on your operating system.
2. Checkout krita in BUILDROOT
```
cd BUILDROOT
git clone git://anongit.kde.org/krita.git
```
3. Create the build directory
```
mkdir BUILDROOT/b
```
4. Create the downloads directory
```
mkdir BUILDROOT/d
```
5. Create the install directory
```
mkdir BUILDROOT/i
```
## Prepare the externals build
1. Enter the BUILDROOT/b directory
2. Run cmake:
* Linux:
```
export PATH=$BUILDROOT/i/bin:$PATH
export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python)
cmake ../krita/3rdparty \
-DINSTALL_ROOT=$BUILDROOT/i \
-DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \
-DCMAKE_INSTALL_PREFIX=BUILDROOT/i
```
* OSX:
```
export PATH=$BUILDROOT/i/bin:$PATH
export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python)
cmake ../krita/3rdparty/ \
-DCMAKE_INSTALL_PREFIX=$BUILDROOT/i \
-DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \
-DINSTALL_ROOT=$BUILDROOT/i
```
* Windows 32-bit / 64-bit:
Note that the cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d.
```
set PATH=%BUILDROOT%\i\bin\;%BUILDROOT%\i\lib;%PATH%
cmake ..\krita\3rdparty -DEXTERNALS_DOWNLOAD_DIR=/dev/d -DINSTALL_ROOT=/dev/i -G "MinGW Makefiles"
```
- If you want to build Qt and some other dependencies with parallel jobs, add
`-DSUBMAKE_JOBS=<n>` to this 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). For other jobs,
you might need to manually add a -- -j N option, where N is the number of jobs.
- If you don't have Windows 10 SDK and don't want to build Qt with ANGLE, add
`-DQT_ENABLE_DYNAMIC_OPENGL=OFF` to the CMake command line args.
3. Build the packages:
On Windows:
```
cmake --build . --config RelWithDebInfo --target ext_patch
cmake --build . --config RelWithDebInfo --target ext_png2ico
```
On OSX and Windows:
```
cmake --build . --config RelWithDebInfo --target ext_gettext
cmake --build . --config RelWithDebInfo --target ext_openssl
```
On all operating systems:
```
cmake --build . --config RelWithDebInfo --target ext_qt
cmake --build . --config RelWithDebInfo --target ext_zlib
cmake --build . --config RelWithDebInfo --target ext_boost
Note about boost: check if the headers are installed into i/include/boost, but not into i/include/boost-1.61/boost
cmake --build . --config RelWithDebInfo --target ext_eigen3
cmake --build . --config RelWithDebInfo --target ext_exiv2
cmake --build . --config RelWithDebInfo --target ext_fftw3
cmake --build . --config RelWithDebInfo --target ext_ilmbase
cmake --build . --config RelWithDebInfo --target ext_jpeg
cmake --build . --config RelWithDebInfo --target ext_lcms2
cmake --build . --config RelWithDebInfo --target ext_ocio
cmake --build . --config RelWithDebInfo --target ext_openexr
```
OSX Note: You need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again.
```
install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable
install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups
```
On All operating systems
```
cmake --build . --config RelWithDebInfo --target ext_png
cmake --build . --config RelWithDebInfo --target ext_tiff
cmake --build . --config RelWithDebInfo --target ext_gsl
cmake --build . --config RelWithDebInfo --target ext_vc
cmake --build . --config RelWithDebInfo --target ext_libraw
cmake --build . --config RelWithDebInfo --target ext_giflib
cmake --build . --config RelWithDebInfo --target ext_openjpeg
+ cmake --build . --config RelWithDebInfo --target ext_quazip
+ ```
On Linux (if you want to build your own SIP and PyQt instead of the system one)
```
cmake --build . --config RelWithDebInfo --target ext_sip
cmake --build . --config RelWithDebInfo --target ext_pyqt
```
On Windows
```
cmake --build . --config RelWithDebInfo --target ext_freetype
cmake --build . --config RelWithDebInfo --target ext_poppler
```
On Linux
```
cmake --build . --config RelWithDebInfo --target ext_kcrash
```
On Windows (if you want to include DrMingw for dumping backtrace on crash)
```
cmake --build . --config RelWithDebInfo --target ext_drmingw
```
On Windows (if you want to include Python scripting)
```
cmake --build . --config RelWithDebInfo --target ext_python
cmake --build . --config RelWithDebInfo --target ext_sip
cmake --build . --config RelWithDebInfo --target ext_pyqt
```
On Windows and Linux (if you want to include gmic-qt)
```
cmake --build . --config RelWithDebInfo --target ext_gmic
```
Linux Note: poppler should be buildable on Linux as well with a home-built freetype
and fontconfig, but I don't know how to make fontconfig find freetype, and on
Linux, fontconfig is needed for poppler. Poppler is needed for PDF import.
OSX Note: In order to build fontconfig on macOS, you need to have pkg-config installed.
You probably need homebrew for that... See http://macappstore.org/pkg-config/ .
archives from: files.kde.org/krita/build/dependencies:
On Windows and OSX
```
cmake --build . --config RelWithDebInfo --target ext_kwindowsystem
```
## Build Krita
1. Make a krita build directory:
mkdir BUILDROOT/build
2. Enter the BUILDROOT/build
3. Configure the build:
On Windows
```
cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1
```
On Linux
```
cmake ../krita -DCMAKE_INSTALL_PREFIX=BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo
# Troubleshooting: if you built your own SIP and CMake fails to find it, please set
# the following environment variable to the SIP installation directory:
export PYTHONPATH=$BUILDROOT/i/sip/
# If you also have KIO installed in the system, don't forget to disable it by bassing to cmake:
# cmake -DCMAKE_DISABLE_FIND_PACKAGE_KF5KIO=true .
```
On OSX
```
cmake ../krita -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DBUNDLE_INSTALL_DIR=$BUILDROOT/i/bin -DCMAKE_BUILD_TYPE=RelWithDebInfo
```
4. Run the build:
On Linux and OSX
```
make
make install
```
On Windows (replace 4 with the number of jobs to run in parallel)
```
cmake --build . --target install -- -j4
```
6. Run krita:
On Linux
```
BUILDROOT/i/bin/krita
```
On Windows
```
BUILDROOT\i\bin\krita.exe
```
On OSX
```
BUILDROOT/i/bin/krita.app/Contents/MacOS/krita
```
## Packaging a Windows Build
If you want to create a stripped down version of Krita to distribute, after building everything just run the packaging/windows/package-complete.cmd script.
That script will copy the necessary files into the specified folder and leave out developer related files. After the script runs there will be two new ZIP files that contain a small portable version of Krita and a separate portable debug version.
diff --git a/3rdparty/ext_eigen3/CMakeLists.txt b/3rdparty/ext_eigen3/CMakeLists.txt
index f32af14560..2c2d16f346 100644
--- a/3rdparty/ext_eigen3/CMakeLists.txt
+++ b/3rdparty/ext_eigen3/CMakeLists.txt
@@ -1,14 +1,14 @@
SET(EXTPREFIX_eigen3 "${EXTPREFIX}" )
ExternalProject_Add( ext_eigen3
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
# eigen 3.3.4: bitbucket does weird things when downloading.
- URL http://files.kde.org/krita/build/dependencies/eigen-eigen-5a0156e40feb.tar.gz
- URL_MD5 1a47e78efe365a97de0c022d127607c3
+ URL http://files.kde.org/krita/build/dependencies/eigen-3.3.7.tar.bz2
+ URL_MD5 05b1f7511c93980c385ebe11bd3c93fa
INSTALL_DIR ${EXTPREFIX_eigen3}
PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/dart.diff
COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/no_tests.diff
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_eigen3} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE}
UPDATE_COMMAND ""
)
diff --git a/3rdparty/ext_eigen3/dart.diff b/3rdparty/ext_eigen3/dart.diff
index 15770fbe18..4df1bdf519 100644
--- a/3rdparty/ext_eigen3/dart.diff
+++ b/3rdparty/ext_eigen3/dart.diff
@@ -1,24 +1,24 @@
diff --git a/cmake/EigenConfigureTesting.cmake b/cmake/EigenConfigureTesting.cmake
-index 2b11d83..8cf56ff 100644
+index 3a82439..4c28246 100644
--- a/cmake/EigenConfigureTesting.cmake
+++ b/cmake/EigenConfigureTesting.cmake
-@@ -26,19 +26,6 @@ include(CTest)
-
- set(EIGEN_TEST_BUILD_FLAGS " " CACHE STRING "Options passed to the build command of unit tests")
+@@ -21,19 +21,6 @@ set(EIGEN_TEST_BUILD_FLAGS "" CACHE STRING "Options passed to the build command
+ set(EIGEN_DASHBOARD_BUILD_TARGET "buildtests" CACHE STRING "Target to be built in dashboard mode, default is buildtests")
+ set(EIGEN_CTEST_ERROR_EXCEPTION "" CACHE STRING "Regular expression for build error messages to be filtered out")
-# Overwrite default DartConfiguration.tcl such that ctest can build our unit tests.
-# Recall that our unit tests are not in the "all" target, so we have to explicitely ask ctest to build our custom 'buildtests' target.
-# At this stage, we can also add custom flags to the build tool through the user defined EIGEN_TEST_BUILD_FLAGS variable.
-file(READ "${CMAKE_CURRENT_BINARY_DIR}/DartConfiguration.tcl" EIGEN_DART_CONFIG_FILE)
-# try to grab the default flags
-string(REGEX MATCH "MakeCommand:.*-- (.*)\nDefaultCTestConfigurationType" EIGEN_DUMMY ${EIGEN_DART_CONFIG_FILE})
-if(NOT CMAKE_MATCH_1)
-string(REGEX MATCH "MakeCommand:.*[^c]make (.*)\nDefaultCTestConfigurationType" EIGEN_DUMMY ${EIGEN_DART_CONFIG_FILE})
-endif()
--string(REGEX REPLACE "MakeCommand:.*DefaultCTestConfigurationType" "MakeCommand: ${CMAKE_COMMAND} --build . --target buildtests --config \"\${CTEST_CONFIGURATION_TYPE}\" -- ${CMAKE_MATCH_1} ${EIGEN_TEST_BUILD_FLAGS}\nDefaultCTestConfigurationType"
+-string(REGEX REPLACE "MakeCommand:.*DefaultCTestConfigurationType" "MakeCommand: ${CMAKE_COMMAND} --build . --target ${EIGEN_DASHBOARD_BUILD_TARGET} --config \"\${CTEST_CONFIGURATION_TYPE}\" -- ${CMAKE_MATCH_1} ${EIGEN_TEST_BUILD_FLAGS}\nDefaultCTestConfigurationType"
- EIGEN_DART_CONFIG_FILE2 ${EIGEN_DART_CONFIG_FILE})
-file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/DartConfiguration.tcl" ${EIGEN_DART_CONFIG_FILE2})
-
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CTestCustom.cmake.in ${CMAKE_BINARY_DIR}/CTestCustom.cmake)
# some documentation of this function would be nice
diff --git a/3rdparty/ext_eigen3/no_tests.diff b/3rdparty/ext_eigen3/no_tests.diff
index d943dab6d5..d20f5a0110 100644
--- a/3rdparty/ext_eigen3/no_tests.diff
+++ b/3rdparty/ext_eigen3/no_tests.diff
@@ -1,35 +1,13 @@
-commit 09b46c2a9acb0b7cb52968599499cb19fa5d8904
-Author: Boudewijn Rempt <boud@valdyas.org>
-Date: Fri Jan 5 16:09:39 2018 +0100
-
- Disable tests: they need blas
-
diff --git a/CMakeLists.txt b/CMakeLists.txt
-index f584002..dcb461b 100644
+index 2bfb6d5..81bffbe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
-@@ -418,24 +418,6 @@ add_subdirectory(doc EXCLUDE_FROM_ALL)
+@@ -435,7 +435,7 @@ add_subdirectory(Eigen)
- include(EigenConfigureTesting)
+ add_subdirectory(doc EXCLUDE_FROM_ALL)
+
+-option(BUILD_TESTING "Enable creation of Eigen tests." ON)
++option(BUILD_TESTING "Enable creation of Eigen tests." OFF)
+ if(BUILD_TESTING)
+ include(EigenConfigureTesting)
--# fixme, not sure this line is still needed:
--enable_testing() # must be called from the root CMakeLists, see man page
--
--
--if(EIGEN_LEAVE_TEST_IN_ALL_TARGET)
-- add_subdirectory(test) # can't do EXCLUDE_FROM_ALL here, breaks CTest
--else()
-- add_subdirectory(test EXCLUDE_FROM_ALL)
--endif()
--
--if(EIGEN_LEAVE_TEST_IN_ALL_TARGET)
-- add_subdirectory(blas)
-- add_subdirectory(lapack)
--else()
-- add_subdirectory(blas EXCLUDE_FROM_ALL)
-- add_subdirectory(lapack EXCLUDE_FROM_ALL)
--endif()
--
- # add SYCL
- option(EIGEN_TEST_SYCL "Add Sycl support." OFF)
- if(EIGEN_TEST_SYCL)
diff --git a/3rdparty/ext_pyqt/CMakeLists.txt b/3rdparty/ext_pyqt/CMakeLists.txt
index 3bfb292799..d33510a2c8 100644
--- a/3rdparty/ext_pyqt/CMakeLists.txt
+++ b/3rdparty/ext_pyqt/CMakeLists.txt
@@ -1,63 +1,63 @@
SET(PREFIX_ext_pyqt "${EXTPREFIX}" )
if (UNIX)
SET(PYTHON_EXECUTABLE_PATH ${PREFIX_ext_pyqt}/bin/python3)
if(NOT EXISTS ${PYTHON_EXECUTABLE_PATH})
message("WARNING: using system python3!")
SET(PYTHON_EXECUTABLE_PATH python3)
endif()
list(APPEND _PYQT_conf
--confirm-license
--qmake ${PREFIX_ext_pyqt}/bin/qmake
--sip ${PREFIX_ext_pyqt}/bin/sip
--sip-incdir ${PREFIX_ext_pyqt}/include
--sipdir ${PREFIX_ext_pyqt}/share/sip
)
ExternalProject_Add( ext_pyqt
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
URL https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.12.1/PyQt5_gpl-5.12.1.tar.gz
URL_MD5 67508b652098d2e05c4c2b5baeb170cc
CONFIGURE_COMMAND ${PYTHON_EXECUTABLE_PATH} <SOURCE_DIR>/configure.py ${_PYQT_conf}
BUILD_COMMAND make
INSTALL_COMMAND make install
BUILD_IN_SOURCE 1
UPDATE_COMMAND ""
)
elseif(MINGW)
list(APPEND _PYQT_conf
--confirm-license
- --target-py-version 3.6
+ --target-py-version 3.8
--bindir ${PREFIX_ext_pyqt}/bin
--qt ${PREFIX_ext_pyqt}
--sip ${PREFIX_ext_pyqt}/bin/sip.exe
--sip-incdir ${PREFIX_ext_pyqt}/include
--spec win32-g++
--verbose
--sipdir ${PREFIX_ext_pyqt}/share/sip
--destdir ${PREFIX_ext_pyqt}/lib/krita-python-libs
--stubsdir ${PREFIX_ext_pyqt}/lib/krita-python-libs/PyQt5
--no-qml-plugin --no-python-dbus --no-qsci-api --no-tools
--disable QtSql --disable QtTest --disable QtWinExtras --disable QtHelp
)
ExternalProject_Add( ext_pyqt
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
URL https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.12.1/PyQt5_gpl-5.12.1.zip
URL_MD5 0b2912828a4d59e13d86decdce1687e6
PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/pyqt-configure-fix.patch
CONFIGURE_COMMAND ${PYTHON_EXECUTABLE} <SOURCE_DIR>/configure.py ${_PYQT_conf}
BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} CXXFLAGS=-D_hypot=hypot LDFLAGS=${SECURITY_SHARED_LINKER_FLAGS}
INSTALL_COMMAND mingw32-make install
BUILD_IN_SOURCE 1
UPDATE_COMMAND ""
)
endif()
diff --git a/3rdparty/ext_python/CMakeLists.txt b/3rdparty/ext_python/CMakeLists.txt
index 6496c12368..e1bc541725 100644
--- a/3rdparty/ext_python/CMakeLists.txt
+++ b/3rdparty/ext_python/CMakeLists.txt
@@ -1,77 +1,76 @@
SET(PREFIX_ext_python "${EXTPREFIX}" )
if (UNIX)
if (APPLE)
- set(PYTHON_VERSION "3.5")
+ set(PYTHON_VERSION "3.8")
ExternalProject_Add( ext_python
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
- URL http://files.kde.org/krita/build/dependencies/Python-3.5.2.tar.gz
- URL_MD5 ea334d398990037a4b8be324bd475c83
+ URL https://www.python.org/ftp/python/3.8.1/Python-3.8.1.tar.xz
+ URL_MD5 b3fb85fd479c0bf950c626ef80cacb57
- PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/pyport_osx.diff
- COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/osx_fixappinstall.diff
+ PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/osx_fixappinstall.diff
- CONFIGURE_COMMAND <SOURCE_DIR>/configure MACOSX_DEPLOYMENT_TARGET=10.11 -prefix=${PREFIX_ext_python} ${GLOBAL_AUTOMAKE_PROFILE}
- --with-cxx-main=/usr/bin/g++ --without-ensurepip --disable-tests --without-test --without-tests --enable-framework=${PREFIX_ext_python}/lib
+ CONFIGURE_COMMAND <SOURCE_DIR>/configure MACOSX_DEPLOYMENT_TARGET=10.13 --prefix=${PREFIX_ext_python} ${GLOBAL_AUTOMAKE_PROFILE}
+ --without-ensurepip --enable-framework=${PREFIX_ext_python}/lib --enable-optimizations
BUILD_COMMAND make
INSTALL_COMMAND make install
COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_python}/bin/python3 ${PREFIX_ext_python}/bin/python
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/sitecustomize.py ${PREFIX_ext_python}/lib/Python.framework/Versions/Current/lib/python${PYTHON_VERSION}/
COMMAND ${CMAKE_COMMAND} -E create_symlink ./lib/python${PYTHON_VERSION}/site-packages ${PREFIX_ext_python}/lib/Python.framework/Versions/Current/site-packages
# CMake FindPythonLib can't find framework libraries, lack of maintainer for Python
COMMAND find ${PREFIX_ext_python}/lib/Python.framework/Versions/Current/lib -type l -d 1
| grep -o "[^/]*$"
| xargs -I FILE ${CMAKE_COMMAND} -E create_symlink ./Python.framework/Python ${PREFIX_ext_python}/lib/FILE
UPDATE_COMMAND ""
ALWAYS 0
)
else()
ExternalProject_Add( ext_python
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
- URL http://files.kde.org/krita/build/dependencies/Python-3.5.2.tar.gz
- URL_MD5 ea334d398990037a4b8be324bd475c83
+ URL https://www.python.org/ftp/python/3.8.1/Python-3.8.1.tar.xz
+ URL_MD5 b3fb85fd479c0bf950c626ef80cacb57
- CONFIGURE_COMMAND <SOURCE_DIR>/configure --prefix=${PREFIX_ext_python} ${GLOBAL_AUTOMAKE_PROFILE} --enable-shared
+ CONFIGURE_COMMAND <SOURCE_DIR>/configure --prefix=${PREFIX_ext_python} ${GLOBAL_AUTOMAKE_PROFILE} --enable-shared
BUILD_COMMAND make
INSTALL_COMMAND make install
COMMAND ${CMAKE_COMMAND} -E copy ${PREFIX_ext_python}/bin/python3 ${PREFIX_ext_python}/bin/python
UPDATE_COMMAND ""
ALWAYS 0
)
endif()
elseif(MINGW)
if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
ExternalProject_Add( ext_python
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
- URL https://www.python.org/ftp/python/3.6.2/python-3.6.2-embed-amd64.zip
- URL_MD5 0fdfe9f79e0991815d6fc1712871c17f
+ URL https://www.python.org/ftp/python/3.8.0/python-3.8.0-embed-amd64.zip
+ URL_MD5 99cca948512b53fb165084787143ef19
INSTALL_DIR ${PREFIX_ext_python}
CONFIGURE_COMMAND ""
BUILD_COMMAND ${CMAKE_COMMAND} -E echo deploying python3 64-bit binary
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory <SOURCE_DIR>/ ${PREFIX_ext_python}/python
COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/python3.dll ${PREFIX_ext_python}/bin
- COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/python36.dll ${PREFIX_ext_python}/bin
+ COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/python38.dll ${PREFIX_ext_python}/bin
COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/vcruntime140.dll ${PREFIX_ext_python}/bin
UPDATE_COMMAND ""
)
else("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
ExternalProject_Add( ext_python
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
- URL https://www.python.org/ftp/python/3.6.2/python-3.6.2-embed-win32.zip
- URL_MD5 2ca4768fdbadf6e670e97857bfab83e8
+ URL https://www.python.org/ftp/python/3.8.0/python-3.8.0-embed-win32.zip
+ URL_MD5 2ec3abf05f3f1046e0dbd1ca5c74ce88
INSTALL_DIR ${PREFIX_ext_python}
CONFIGURE_COMMAND ""
BUILD_COMMAND ${CMAKE_COMMAND} -E echo deploying python3 32-bit binary
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory <SOURCE_DIR>/ ${PREFIX_ext_python}/python
COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/python3.dll ${PREFIX_ext_python}/bin
- COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/python36.dll ${PREFIX_ext_python}/bin
+ COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/python38.dll ${PREFIX_ext_python}/bin
COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/vcruntime140.dll ${PREFIX_ext_python}/bin
UPDATE_COMMAND ""
)
endif("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
endif()
diff --git a/3rdparty/ext_python/osx_fixappinstall.diff b/3rdparty/ext_python/osx_fixappinstall.diff
index 183705be00..ca82901b03 100644
--- a/3rdparty/ext_python/osx_fixappinstall.diff
+++ b/3rdparty/ext_python/osx_fixappinstall.diff
@@ -1,49 +1,47 @@
diff --git a/configure b/configure
-old mode 100755
-new mode 100644
-index c892a99..97c8f0d
+index 2a933cd..4dfb4fb 100755
--- a/configure
+++ b/configure
-@@ -3207,7 +3207,7 @@ if test "${enable_framework+set}" = set; then :
+@@ -3149,7 +3149,7 @@ if test "${enable_framework+set}" = set; then :
FRAMEWORKINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkinstallunixtools"
FRAMEWORKALTINSTALLLAST="frameworkinstallmaclib frameworkinstallapps frameworkaltinstallunixtools"
FRAMEWORKPYTHONW="frameworkpythonw"
- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+ FRAMEWORKINSTALLAPPSPREFIX="${prefix}/Applications"
if test "x${prefix}" = "xNONE" ; then
FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}"
-@@ -3218,7 +3218,7 @@ if test "${enable_framework+set}" = set; then :
+@@ -3160,7 +3160,7 @@ if test "${enable_framework+set}" = set; then :
case "${enableval}" in
/System*)
- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+ FRAMEWORKINSTALLAPPSPREFIX="${prefix}/Applications"
if test "${prefix}" = "NONE" ; then
# See below
FRAMEWORKUNIXTOOLSPREFIX="/usr"
-@@ -3226,13 +3226,13 @@ if test "${enable_framework+set}" = set; then :
+@@ -3168,13 +3168,13 @@ if test "${enable_framework+set}" = set; then :
;;
/Library*)
- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+ FRAMEWORKINSTALLAPPSPREFIX="${prefix}/Applications"
;;
*/Library/Frameworks)
MDIR="`dirname "${enableval}"`"
MDIR="`dirname "${MDIR}"`"
- FRAMEWORKINSTALLAPPSPREFIX="${MDIR}/Applications"
+ FRAMEWORKINSTALLAPPSPREFIX="${MDIR}${prefix}/Applications"
if test "${prefix}" = "NONE"; then
# User hasn't specified the
-@@ -3246,7 +3246,7 @@ if test "${enable_framework+set}" = set; then :
+@@ -3188,7 +3188,7 @@ if test "${enable_framework+set}" = set; then :
;;
*)
- FRAMEWORKINSTALLAPPSPREFIX="/Applications"
+ FRAMEWORKINSTALLAPPSPREFIX="${prefix}/Applications"
;;
esac
-
+
\ No newline at end of file
diff --git a/3rdparty/ext_qt/CMakeLists.txt b/3rdparty/ext_qt/CMakeLists.txt
index 83e613f9e3..f92f91088b 100644
--- a/3rdparty/ext_qt/CMakeLists.txt
+++ b/3rdparty/ext_qt/CMakeLists.txt
@@ -1,309 +1,310 @@
SET(EXTPREFIX_qt "${EXTPREFIX}")
if (WIN32)
list(APPEND _QT_conf -skip qt3d -skip qtactiveqt -skip qtcanvas3d
-skip qtconnectivity -skip qtdoc -skip qtgraphicaleffects
-skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland
-skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview
-skip qtxmlpatterns -no-sql-sqlite -nomake examples -nomake tools
-no-compile-examples -no-dbus -no-iconv -no-qml-debug
-no-libproxy -no-system-proxies -no-icu -no-mtdev
-skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth
-skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus
-skip qtspeech -skip qtvirtualkeyboard
#
-qt-zlib -qt-pcre -qt-libpng -qt-libjpeg -openssl-linked
-I ${EXTPREFIX_qt}/include
-L ${EXTPREFIX_qt}/lib
#
-opensource -confirm-license
#
-release -platform win32-g++ -prefix ${EXTPREFIX_qt}
QMAKE_LFLAGS_APP+=${SECURITY_EXE_LINKER_FLAGS}
QMAKE_LFLAGS_SHLIB+=${SECURITY_SHARED_LINKER_FLAGS}
QMAKE_LFLAGS_SONAME+=${SECURITY_SHARED_LINKER_FLAGS}
)
if (QT_ENABLE_DEBUG_INFO)
# Set the option to build Qt with debugging info enabled
list(APPEND _QT_conf -force-debug-info)
endif(QT_ENABLE_DEBUG_INFO)
if (QT_ENABLE_DYNAMIC_OPENGL)
list(APPEND _QT_conf -opengl dynamic -angle)
else (QT_ENABLE_DYNAMIC_OPENGL)
list(APPEND _QT_conf -opengl desktop -no-angle)
endif (QT_ENABLE_DYNAMIC_OPENGL)
# MIME-type optimization patches
set(ext_qt_PATCH_COMMAND
${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-Use-fast-path-for-unsupported-mime-types.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0002-Hack-always-return-we-support-DIBV5.patch
)
# Tablet support patches
if (NOT USE_QT_TABLET_WINDOWS)
set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND}
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0001-disable-wintab.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/disable-winink.patch
)
else()
set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND}
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0020-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0023-Implement-a-switch-for-tablet-API-on-Windows.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0024-Fetch-stylus-button-remapping-from-WinTab-driver.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0025-Disable-tablet-relative-mode-in-Qt.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0026-Fetch-mapped-screen-size-from-the-Wintab-driver.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0027-Switch-stylus-pointer-type-when-the-tablet-is-in-the.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0028-Fix-updating-tablet-pressure-resolution-on-every-pro.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0051-Add-workaround-for-handling-table-press-correctly-in.patch
)
endif()
# HDR patches
set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND}
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0003-Implement-openGL-surface-color-space-selection-in-An.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0004-Implement-color-space-selection-for-QSurfaceFormat.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0005-Implement-color-conversion-for-the-backing-store-tex.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0006-Return-QScreen-s-HMONITOR-handle-via-QPlatformNative.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0007-Implement-a-manual-test-for-checking-is-HDR-features.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0008-Fix-notification-of-QDockWidget-when-it-gets-undocke.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0009-Fix-Rec2020-display-format.patch
)
# AMD random generation patch
set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND}
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch
)
# Other patches
set(ext_qt_PATCH_COMMAND ${ext_qt_PATCH_COMMAND}
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0060-Windows-Add-a-default-setting-for-hasBorderInFullScr.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0061-Hack-to-hide-1px-border-with-OpenGL-fullscreen-hack.patch
COMMAND ${PATCH_COMMAND} -p1 -d qttools -i ${CMAKE_CURRENT_SOURCE_DIR}/windeployqt-force-allow-debug-info.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0080-Sync-buffers-of-the-destination-file-after-QFile-cop.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch
# COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0031-Compute-logical-DPI-on-a-per-screen-basis.patch
# COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0032-Update-Dpi-and-scale-factor-computation.patch
# COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0033-Move-QT_FONT_DPI-to-cross-platform-code.patch
# COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0034-Update-QT_SCREEN_SCALE_FACTORS.patch
# COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0035-Deprecate-QT_AUTO_SCREEN_SCALE_FACTOR.patch
# COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0036-Add-high-DPI-scale-factor-rounding-policy-C-API.patch
)
ExternalProject_Add(
ext_qt
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.zip
URL_MD5 157fa5d9c897737ce06ba4f9a20151e0
PATCH_COMMAND ${ext_qt_PATCH_COMMAND}
INSTALL_DIR ${EXTPREFIX_qt}
CONFIGURE_COMMAND <SOURCE_DIR>/configure.bat ${_QT_conf}
BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS}
INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install
UPDATE_COMMAND ""
# Use a short name to reduce the chance of exceeding path length limit
SOURCE_DIR s
BINARY_DIR b
DEPENDS ext_patch ext_openssl
)
elseif (ANDROID)
ExternalProject_Add(
ext_qt
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
URL https://download.qt.io/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz
URL_MD5 99c2eb46e533371798b4ca2d1458e065
PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/show-proper-error.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/workaround-null-eglconfig-crash.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/fix-android-menu64bit.patch
+ COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/android-add-pen-tilt-rot.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch
CONFIGURE_COMMAND <SOURCE_DIR>/configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -verbose -nomake examples -nomake tests -nomake tools -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -skip qtmultimedia -android-sdk ${ANDROID_SDK_ROOT} -android-ndk ${CMAKE_ANDROID_NDK} -android-arch ${ANDROID_ABI} -xplatform android-clang -android-ndk-platform android-21 -make libs
INSTALL_DIR ${EXTPREFIX_qt}
BUILD_COMMAND $(MAKE)
INSTALL_COMMAND $(MAKE) install
UPDATE_COMMAND ""
BUILD_IN_SOURCE 1
)
elseif (NOT APPLE)
ExternalProject_Add(
ext_qt
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.tar.xz
URL_MD5 d8c9ed842d39f1a5f31a7ab31e4e886c
PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch
CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto'
CONFIGURE_COMMAND <SOURCE_DIR>/configure -prefix ${EXTPREFIX_qt} -opensource -confirm-license -openssl-linked -verbose -nomake examples -skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtgraphicaleffects -skip qtlocation -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebsockets -skip qtwebview -skip qtandroidextras -skip qtserialport -skip qtdatavis3d -skip qtvirtualkeyboard -skip qtspeech -skip qtsensors -skip qtgamepad -skip qtscxml -skip qtremoteobjects -skip qtxmlpatterns -skip qtnetworkauth -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtpurchasing -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -skip qtmultimedia
INSTALL_DIR ${EXTPREFIX_qt}
BUILD_COMMAND $(MAKE)
INSTALL_COMMAND $(MAKE) install
UPDATE_COMMAND ""
BUILD_IN_SOURCE 1
)
else( APPLE )
# XCODE_VERSION is set by CMake when using the Xcode generator, otherwise we need
# to detect it manually here.
if (NOT XCODE_VERSION)
execute_process(
COMMAND xcodebuild -version
OUTPUT_VARIABLE xcodebuild_version
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_FILE /dev/null
)
string(REGEX MATCH "Xcode ([0-9]+([.][0-9]+)*)" version_match ${xcodebuild_version})
if (version_match)
message(STATUS "${EXTPREFIX_qt}:Identified Xcode Version: ${CMAKE_MATCH_1}")
set(XCODE_VERSION ${CMAKE_MATCH_1})
else()
# If detecting Xcode version failed, set a crazy high version so we default
# to the newest.
set(XCODE_VERSION 99)
message(WARNING "${EXTPREFIX_qt}:Failed to detect the version of an installed copy of Xcode, falling back to highest supported version. Set XCODE_VERSION to override.")
endif(version_match)
endif(NOT XCODE_VERSION)
# -------------------------------------------------------------------------------
# Verify the Xcode installation on Mac OS like Qt5.7 does/will
# If not stop now, the system isn't configured correctly for Qt.
# No reason to even proceed.
# -------------------------------------------------------------------------------
set(XCSELECT_OUTPUT)
find_program(XCSELECT_PROGRAM "xcode-select")
if(XCSELECT_PROGRAM)
message(STATUS "${EXTPREFIX_qt}:Found XCSELECT_PROGRAM as ${XCSELECT_PROGRAM}")
set(XCSELECT_COMMAND ${XCSELECT_PROGRAM}
"--print-path")
execute_process(
COMMAND ${XCSELECT_COMMAND}
RESULT_VARIABLE XCSELECT_COMMAND_RESULT
OUTPUT_VARIABLE XCSELECT_COMMAND_OUTPUT
ERROR_FILE /dev/null
)
if(NOT XCSELECT_COMMAND_RESULT)
# returned 0, we're ok.
string(REGEX REPLACE
"[ \t]*[\r\n]+[ \t]*" ";"
XCSELECT_COMMAND_OUTPUT ${XCSELECT_COMMAND_OUTPUT})
else()
string(REPLACE ";" " " XCSELECT_COMMAND_STR "${XCSELECT_COMMAND}")
# message(STATUS "${XCSELECT_COMMAND_STR}")
message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} test failed with status ${XCSELECT_COMMAND_RESULT}")
endif()
else()
message(FATAL_ERROR "${EXTPREFIX_qt}:${XCSELECT_PROGRAM} not found. No Xcode is selected. Use xcode-select -switch to choose an Xcode version")
endif()
# Belts and suspenders
# Beyond all the Xcode and Qt version checking, the proof of the pudding
# lies in the success/failure of this command: xcrun --find xcrun.
# On failure a patch is necessary, otherwise we're ok
# So hard check xcrun now...
set(XCRUN_OUTPUT)
find_program(XCRUN_PROGRAM "xcrun")
if(XCRUN_PROGRAM)
message(STATUS "${EXTPREFIX_qt}:Found XCRUN_PROGRAM as ${XCRUN_PROGRAM}")
set(XCRUN_COMMAND ${XCRUN_PROGRAM}
"--find xcrun")
execute_process(
COMMAND ${XCRUN_COMMAND}
RESULT_VARIABLE XCRUN_COMMAND_RESULT
OUTPUT_VARIABLE XCRUN_COMMAND_OUTPUT
ERROR_FILE /dev/null
)
if(NOT XCRUN_COMMAND_RESULT)
# returned 0, we're ok.
string(REGEX REPLACE
"[ \t]*[\r\n]+[ \t]*" ";"
XCRUN_COMMAND_OUTPUT ${XCRUN_COMMAND_OUTPUT})
else()
string(REPLACE ";" " " XCRUN_COMMAND_STR "${XCRUN_COMMAND}")
# message(STATUS "${XCRUN_COMMAND_STR}")
message(STATUS "${EXTPREFIX_qt}:xcrun test failed with status ${XCRUN_COMMAND_RESULT}")
endif()
else()
message(STATUS "${EXTPREFIX_qt}:xcrun not found -- ${XCRUN_PROGRAM}")
endif()
#
# Now configure ext_qt accordingly
#
if ((XCRUN_COMMAND_RESULT) AND (NOT (XCODE_VERSION VERSION_LESS 8.0.0)))
# Fix Xcode xcrun related issue.
# NOTE: This should be fixed by Qt 5.7.1 see here: http://code.qt.io/cgit/qt/qtbase.git/commit/?h=dev&id=77a71c32c9d19b87f79b208929e71282e8d8b5d9
# NOTE: but no one's holding their breath.
set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND}} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0013-Poison-Qt-s-headers-with-a-mark-about-presence-of-En.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0070-Fix-QRandomGenerator-initialization-on-AMD-CPUs.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch
#COMMAND ${PATCH_COMMAND} -p1 -b -d <SOURCE_DIR>/qtbase/mkspecs/features/mac -i ${CMAKE_CURRENT_SOURCE_DIR}/mac-default.patch
)
message(STATUS "${EXTPREFIX_qt}:Additional patches injected.")
else()
# No extra patches will be applied
# NOTE: defaults for some untested scenarios like xcrun fails and xcode_version < 8.
# NOTE: that is uncharted territory and (hopefully) a very unlikely scenario...
set(ext_qt_PATCH_COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0012-Synthesize-Enter-LeaveEvent-for-accepted-QTabletEven.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0081-non-native-file-dialog-overwrite-warnings-based-on-filter.patch
COMMAND ${PATCH_COMMAND} -p1 -d qtbase -i ${CMAKE_CURRENT_SOURCE_DIR}/0082-jpeg-extensions-handling-in-non-native-file-dialogs.patch
)
endif()
# Qt is big - try and parallelize if at all possible
include(ProcessorCount)
ProcessorCount(NUM_CORES)
if(NOT NUM_CORES EQUAL 0)
if (NUM_CORES GREATER 2)
# be nice...
MATH( EXPR NUM_CORES "${NUM_CORES} - 2" )
endif()
set(PARALLEL_MAKE "make;-j${NUM_CORES}")
message(STATUS "${EXTPREFIX_qt}:Parallelized make: ${PARALLEL_MAKE}")
else()
set(PARALLEL_MAKE "make")
endif()
ExternalProject_Add(
ext_qt
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
LOG_DOWNLOAD ON
LOG_UPDATE ON
LOG_CONFIGURE ON
LOG_BUILD ON
LOG_TEST ON
LOG_INSTALL ON
BUILD_IN_SOURCE ON
URL https://download.qt.io/official_releases/qt/5.12/5.12.5/single/qt-everywhere-src-5.12.5.tar.xz
URL_MD5 d8c9ed842d39f1a5f31a7ab31e4e886c
CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto'
INSTALL_DIR ${EXTPREFIX_qt}
CONFIGURE_COMMAND <SOURCE_DIR>/configure
-skip qt3d -skip qtactiveqt -skip qtcanvas3d -skip qtconnectivity -skip qtdoc -skip qtgraphicaleffects -skip qtlocation -skip qtsensors -skip qtserialport -skip qtwayland
-skip qtwebchannel -skip qtwebsockets -skip qtwebview -skip qtwebengine -skip qtxmlpatterns -no-sql-sqlite -skip qtcharts -skip qtdatavis3d -skip qtgamepad -skip qtnetworkauth
-skip qtpurchasing -skip qtremoteobjects -skip qtscxml -skip qtserialbus -skip qtspeech -skip qtvirtualkeyboard -nomake examples -nomake tools -no-compile-examples
-no-dbus -no-iconv -no-qml-debug -no-libproxy -no-system-proxies -no-icu -no-mtdev -system-zlib -qt-pcre
-opensource -confirm-license -openssl-linked -prefix ${EXTPREFIX_qt}
BUILD_COMMAND ${PARALLEL_MAKE}
INSTALL_COMMAND make install
UPDATE_COMMAND ""
BUILD_IN_SOURCE 1
)
endif()
diff --git a/3rdparty/ext_qt/android-add-pen-tilt-rot.patch b/3rdparty/ext_qt/android-add-pen-tilt-rot.patch
new file mode 100644
index 0000000000..5457d46526
--- /dev/null
+++ b/3rdparty/ext_qt/android-add-pen-tilt-rot.patch
@@ -0,0 +1,67 @@
+From 35ae5101395b96c5505cdaa76f9f2ce9da089cfe Mon Sep 17 00:00:00 2001
+From: Max Thomas <mtinc2@gmail.com>
+Date: Wed, 08 Jan 2020 10:34:10 -0700
+Subject: [PATCH] Add support for pen tilt/rotation for Android
+
+Change-Id: I195c891b47841ac86048dc38ea95beaeced8b70a
+---
+
+diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
+index dee5628..0862383 100644
+--- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
++++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
+@@ -515,8 +515,13 @@
+ if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+ sendMouseEvent(event, id);
+ } else if (m_tabletEventSupported && pointerType != 0) {
++ float tiltRot = event.getAxisValue(MotionEvent.AXIS_TILT);
++ float orientation = event.getAxisValue(MotionEvent.AXIS_ORIENTATION);
++ float tiltX = (float) Math.toDegrees(-Math.sin(orientation) * tiltRot);
++ float tiltY = (float) Math.toDegrees(Math.cos(orientation) * tiltRot);
+ tabletEvent(id, event.getDeviceId(), event.getEventTime(), event.getAction(), pointerType,
+- event.getButtonState(), event.getX(), event.getY(), event.getPressure());
++ event.getButtonState(), event.getX(), event.getY(), event.getPressure(), tiltX, tiltY,
++ (float) Math.toDegrees(orientation));
+ } else {
+ touchBegin(id);
+ for (int i = 0; i < event.getPointerCount(); ++i) {
+@@ -1069,7 +1074,7 @@
+
+ // tablet methods
+ public static native boolean isTabletEventSupported();
+- public static native void tabletEvent(int winId, int deviceId, long time, int action, int pointerType, int buttonState, float x, float y, float pressure);
++ public static native void tabletEvent(int winId, int deviceId, long time, int action, int pointerType, int buttonState, float x, float y, float pressure, float tiltX, float tiltY, float rotation);
+ // tablet methods
+
+ // keyboard methods
+diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp
+index 049f9b0..9768e52 100644
+--- a/src/plugins/platforms/android/androidjniinput.cpp
++++ b/src/plugins/platforms/android/androidjniinput.cpp
+@@ -304,7 +304,7 @@
+ }
+
+ static void tabletEvent(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint deviceId, jlong time, jint action,
+- jint pointerType, jint buttonState, jfloat x, jfloat y, jfloat pressure)
++ jint pointerType, jint buttonState, jfloat x, jfloat y, jfloat pressure, jfloat tiltX, jfloat tiltY, jfloat rotation)
+ {
+ #if QT_CONFIG(tabletevent)
+ QPointF globalPosF(x, y);
+@@ -347,7 +347,7 @@
+
+ QWindowSystemInterface::handleTabletEvent(tlw, ulong(time),
+ localPos, globalPosF, QTabletEvent::Stylus, pointerType,
+- buttons, pressure, 0, 0, 0., 0., 0, deviceId, Qt::NoModifier);
++ buttons, pressure, tiltX, tiltY, 0., rotation, 0, deviceId, Qt::NoModifier);
+ #endif // QT_CONFIG(tabletevent)
+ }
+
+@@ -852,7 +852,7 @@
+ {"mouseWheel", "(IIIFF)V", (void *)mouseWheel},
+ {"longPress", "(III)V", (void *)longPress},
+ {"isTabletEventSupported", "()Z", (void *)isTabletEventSupported},
+- {"tabletEvent", "(IIJIIIFFF)V", (void *)tabletEvent},
++ {"tabletEvent", "(IIJIIIFFFFFF)V", (void *)tabletEvent},
+ {"keyDown", "(IIIZ)V", (void *)keyDown},
+ {"keyUp", "(IIIZ)V", (void *)keyUp},
+ {"keyboardVisibilityChanged", "(Z)V", (void *)keyboardVisibilityChanged},
diff --git a/3rdparty/ext_sip/CMakeLists.txt b/3rdparty/ext_sip/CMakeLists.txt
index 31719881b6..831e060a53 100644
--- a/3rdparty/ext_sip/CMakeLists.txt
+++ b/3rdparty/ext_sip/CMakeLists.txt
@@ -1,75 +1,75 @@
SET(PREFIX_ext_sip "${EXTPREFIX}" )
if (UNIX)
if(NOT APPLE)
SET(PYTHON_EXECUTABLE_PATH ${PREFIX_ext_sip}/bin/python3)
if(NOT EXISTS ${PYTHON_EXECUTABLE_PATH})
message("WARNING: using system python3!")
SET(PYTHON_EXECUTABLE_PATH python3)
endif()
ExternalProject_Add( ext_sip
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
URL https://www.riverbankcomputing.com/static/Downloads/sip/4.19.15/sip-4.19.15.tar.gz
URL_MD5 236578d2199da630ae1251671b9a7bfe
- CONFIGURE_COMMAND ${PYTHON_EXECUTABLE_PATH} <SOURCE_DIR>/configure.py -b ${PREFIX_ext_sip}/bin -d ${PREFIX_ext_sip}/lib/python3.5/site-packages -e ${PREFIX_ext_sip}/include --sipdir ${PREFIX_ext_sip}/sip --target-py-version 3.5 --sip-module PyQt5.sip
+ CONFIGURE_COMMAND ${PYTHON_EXECUTABLE_PATH} <SOURCE_DIR>/configure.py -b ${PREFIX_ext_sip}/bin -d ${PREFIX_ext_sip}/lib/python3.5/site-packages -e ${PREFIX_ext_sip}/include --sipdir ${PREFIX_ext_sip}/sip --target-py-version 3.8 --sip-module PyQt5.sip
BUILD_COMMAND make
INSTALL_COMMAND make install
BUILD_IN_SOURCE 1
UPDATE_COMMAND ""
)
else(APPLE)
if(NOT PYTHONINTERP_FOUND)
SET(PYTHON_EXECUTABLE ${PREFIX_ext_sip}/bin/python3)
if(NOT EXISTS "${PYTHON_EXECUTABLE}")
message("WARNING: using system python3!")
SET(PYTHON_EXECUTABLE python3)
endif()
endif()
ExternalProject_Add( ext_sip
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
URL https://www.riverbankcomputing.com/static/Downloads/sip/4.19.15/sip-4.19.15.tar.gz
URL_MD5 236578d2199da630ae1251671b9a7bfe
CMAKE_ARGS -DPYTHON_INCLUDE_DIR=${PREFIX_ext_sip}/lib/Python.framework/Headers
CONFIGURE_COMMAND ${PYTHON_EXECUTABLE} <SOURCE_DIR>/configure.py --deployment-target=10.11
-b ${PREFIX_ext_sip}/bin -d ${PREFIX_ext_sip}/lib/Python.framework/Versions/Current/site-packages/
- -e ${PREFIX_ext_sip}/include --sipdir ${PREFIX_ext_sip}/share/sip --target-py-version 3.5 --sip-module PyQt5.sip
+ -e ${PREFIX_ext_sip}/include --sipdir ${PREFIX_ext_sip}/share/sip --target-py-version 3.8 --sip-module PyQt5.sip
BUILD_COMMAND make
INSTALL_COMMAND make install
# COMMAND ${CMAKE_COMMAND} -E create_symlink ./PyQt5/sip.so ${PREFIX_ext_sip}/lib/Python.framework/Versions/Current/site-packages/sip.so
# COMMAND ${CMAKE_COMMAND} -E create_symlink ./PyQt5/sip.pyi ${PREFIX_ext_sip}/lib/Python.framework/Versions/Current/site-packages/sip.pyi
BUILD_IN_SOURCE 1
UPDATE_COMMAND ""
)
endif()
elseif (MINGW)
list(APPEND _SIP_conf
--platform win32-g++
-b ${PREFIX_ext_sip}/bin
-d ${PREFIX_ext_sip}/lib/krita-python-libs
-e ${PREFIX_ext_sip}/include
--sipdir ${PREFIX_ext_sip}/share/sip
- --target-py-version 3.6
+ --target-py-version 3.8
--sip-module PyQt5.sip
)
ExternalProject_Add( ext_sip
DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
URL https://www.riverbankcomputing.com/static/Downloads/sip/4.19.15/sip-4.19.15.zip
URL_MD5 4a1a4760cfabef15d68f45a6920974c2
CONFIGURE_COMMAND ${PYTHON_EXECUTABLE} <SOURCE_DIR>/configure.py ${_SIP_conf}
BUILD_COMMAND mingw32-make -j${SUBMAKE_JOBS} LDFLAGS=${SECURITY_SHARED_LINKER_FLAGS}
INSTALL_COMMAND mingw32-make -j${SUBMAKE_JOBS} install
BUILD_IN_SOURCE 1
UPDATE_COMMAND ""
)
endif()
diff --git a/CMakeLists.txt b/CMakeLists.txt
index cd95efc2cd..2c8888d86c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,926 +1,925 @@
project(krita)
message(STATUS "Using CMake version: ${CMAKE_VERSION}")
cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
set(MIN_QT_VERSION 5.9.0)
set(MIN_FRAMEWORKS_VERSION 5.44.0)
set( CMAKE_CXX_STANDARD 11 )
set( CMAKE_CXX_STANDARD_REQUIRED ON )
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 (POLICY CMP0071)
cmake_policy(SET CMP0071 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.12 -Wno-macro-redefined -Wno-deprecated-register)
endif()
if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-suggest-override> -Wextra -Wno-class-memaccess)
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.3.0-prealpha")
# Major version: 3 for 3.x, 4 for 4.x, etc.
set(KRITA_STABLE_VERSION_MAJOR 4)
# Minor version: 0 for 4.0, 1 for 4.1, etc.
set(KRITA_STABLE_VERSION_MINOR 3)
# Bugfix release version, or 0 for before the first stable release
set(KRITA_VERSION_RELEASE 0)
# the 4th digit, really only used for the Windows installer:
# - [Pre-]Alpha: Starts from 0, increment 1 per release
# - Beta: Starts from 50, increment 1 per release
# - Stable: Set to 100, bump to 101 if emergency update is needed
set(KRITA_VERSION_REVISION 0)
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 2018) # 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_hash(GIT_SHA1)
get_git_branch(GIT_BRANCH)
if(GIT_SHA1)
string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1)
set(KRITA_GIT_SHA1_STRING ${GIT_SHA1})
if(GIT_BRANCH)
set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH})
else()
set(KRITA_GIT_BRANCH_STRING "(detached HEAD)")
endif()
endif()
# 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("Hide 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(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON)
configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h)
add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.")
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).")
option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON)
configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h)
add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode")
option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF)
option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF)
add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=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})
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)
if(ENABLE_PYTHON_2)
message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.")
else(ENABLE_PYTHON_2)
- find_package(PythonInterp 3.6 EXACT)
- find_package(PythonLibs 3.6 EXACT)
+ find_package(PythonInterp 3.8 EXACT)
+ find_package(PythonLibs 3.8 EXACT)
endif(ENABLE_PYTHON_2)
if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND)
if(ENABLE_PYTHON_2)
find_package(PythonLibrary 2.7)
else(ENABLE_PYTHON_2)
- find_package(PythonLibrary 3.6)
+ find_package(PythonLibrary 3.8)
endif(ENABLE_PYTHON_2)
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)
if(ENABLE_PYTHON_2)
find_package(PythonInterp 2.7)
find_package(PythonLibrary 2.7)
else(ENABLE_PYTHON_2)
find_package(PythonInterp 3.0)
find_package(PythonLibrary 3.0)
endif(ENABLE_PYTHON_2)
endif(MINGW)
########################
#########################
## Look for KDE and Qt ##
#########################
########################
find_package(ECM 5.22 REQUIRED NOMODULE)
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR})
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${ECM_FIND_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
Config
WidgetsAddons
Completion
CoreAddons
GuiAddons
I18n
ItemModels
ItemViews
WindowSystem
Archive
)
find_package(Qt5 ${MIN_QT_VERSION}
REQUIRED COMPONENTS
Core
Gui
Widgets
Xml
Network
PrintSupport
Svg
Test
Concurrent
)
if (ANDROID)
find_package(Qt5 ${MIN_QT_VERSION}
REQUIRED COMPONENTS
AndroidExtras
)
endif()
if (WIN32)
set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS})
set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES})
CHECK_CXX_SOURCE_COMPILES("
#include <QCoreApplication>
int main(int argc, char *argv[]) {
QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI);
}
"
QT_HAS_WINTAB_SWITCH
)
unset(CMAKE_REQUIRED_INCLUDES)
unset(CMAKE_REQUIRED_LIBRARIES)
option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON)
add_feature_info("Use Qt's Windows Tablet Support" USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt.")
configure_file(config_use_qt_tablet_windows.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_use_qt_tablet_windows.h)
endif ()
set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS})
set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES})
CHECK_CXX_SOURCE_COMPILES("
#include <QSurfaceFormat>
int main(int argc, char *argv[]) {
QSurfaceFormat fmt;
fmt.setColorSpace(QSurfaceFormat::scRGBColorSpace);
fmt.setColorSpace(QSurfaceFormat::bt2020PQColorSpace);
}
"
HAVE_HDR
)
configure_file(config-hdr.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hdr.h)
CHECK_CXX_SOURCE_COMPILES("
#include <QGuiApplication>
int main(int argc, char *argv[]) {
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round);
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
}
"
HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY
)
configure_file(config-high-dpi-scale-factor-rounding-policy.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-high-dpi-scale-factor-rounding-policy.h)
if (WIN32)
CHECK_CXX_SOURCE_COMPILES("
#include <QtPlatformHeaders/QWindowsWindowFunctions>
int main(int argc, char *argv[]) {
QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true);
}
"
HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT
)
configure_file(config-set-has-border-in-full-screen-default.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-set-has-border-in-full-screen-default.h)
endif (WIN32)
unset(CMAKE_REQUIRED_INCLUDES)
unset(CMAKE_REQUIRED_LIBRARIES)
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/"
+ URL "https://www.qt.io/"
TYPE OPTIONAL
PURPOSE "Optionally used to provide sound support for animations")
macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA)
configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h )
if (NOT APPLE)
find_package(Qt5Quick ${MIN_QT_VERSION})
set_package_properties(Qt5Quick PROPERTIES
DESCRIPTION "QtQuick"
- URL "http://www.qt.io/"
+ URL "https://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/"
+ URL "https://www.qt.io/"
TYPE OPTIONAL
PURPOSE "Optionally used for the touch gui for Krita")
endif()
if (NOT WIN32 AND NOT APPLE AND NOT ANDROID)
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/"
+ URL "https://www.qt.io/"
TYPE OPTIONAL
PURPOSE "Optionally used to provide a dbus api on Linux")
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"
+ URL "https://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)
else()
set(HAVE_DBUS FALSE)
set(HAVE_X11 FALSE)
endif()
add_definitions(
-DQT_USE_QSTRINGBUILDER
-DQT_STRICT_ITERATORS
-DQT_NO_SIGNALS_SLOTS_KEYWORDS
-DQT_NO_URL_CAST_FROM_STRING
-DQT_USE_FAST_CONCATENATION
-DQT_USE_FAST_OPERATOR_PLUS
)
#if (${Qt5_VERSION} VERSION_GREATER "5.14.0" )
# add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50F00)
#elseif (${Qt5_VERSION} VERSION_GREATER "5.13.0" )
# add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50E00)
#elseif (${Qt5_VERSION} VERSION_GREATER "5.12.0" )
# add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50D00)
#elseif (${Qt5_VERSION} VERSION_GREATER "5.11.0" )
# add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50C00)
#if(${Qt5_VERSION} VERSION_GREATER "5.10.0" )
# add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50B00)
#if(${Qt5_VERSION} VERSION_GREATER "5.9.0" )
# add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50A00)
#else()
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900)
#endif()
add_definitions(-DQT_DEPRECATED_WARNINGS)
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 "${CMAKE_CXX_FLAGS} -fext-numeric-literals")
endif()
option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug")
if (KRITA_DEVS)
set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE)
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()
# KDECompilerSettings adds the `--export-all-symbols` linker flag.
# We don't really need it.
if(MINGW)
string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}")
string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}")
endif(MINGW)
if(MINGW)
# Hack CMake's variables to tell AR to create thin archives to reduce unnecessary writes.
# Source of definition: https://github.com/Kitware/CMake/blob/v3.14.1/Modules/Platform/Windows-GNU.cmake#L128
# Thin archives: https://sourceware.org/binutils/docs/binutils/ar.html#index-thin-archives
macro(mingw_use_thin_archive lang)
foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE)
string(REGEX REPLACE "(<CMAKE_AR> [^ T]+) " "\\1T " CMAKE_${lang}_${rule} "${CMAKE_${lang}_${rule}}")
endforeach()
endmacro()
mingw_use_thin_archive(CXX)
endif(MINGW)
# enable exceptions globally
kde_enable_exceptions()
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
if (ANDROID)
# use default ABI
if (NOT ANDROID_ABI)
set (ANDROID_ABI armeabi-v7a)
endif()
set (ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT})
set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR})
# set (DATA_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/assets)
else()
set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins)
endif()
###########################
############################
## Required dependencies ##
############################
###########################
# FIXME: Still hardcoded
if (ANDROID)
set (Boost_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/include/boost-1_69)
set (Boost_LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/lib)
set (KF5_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/kf5/kde/install/lib)
endif()
find_package(PNG REQUIRED)
list (APPEND ANDROID_EXTRA_LIBS ${PNG_LIBRARY})
if (APPLE)
- # this is not added correctly on OSX -- see http://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242
+ # this is not added correctly on OSX -- see https://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)
include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
##
## Test for GNU Scientific Library
##
find_package(GSL)
set_package_properties(GSL PROPERTIES
- URL "http://www.gnu.org/software/gsl"
+ URL "https://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 )
if (GSL_FOUND)
list (APPEND ANDROID_EXTRA_LIBS ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES})
endif()
###########################
############################
## Optional dependencies ##
############################
###########################
find_package(ZLIB)
set_package_properties(ZLIB PROPERTIES
DESCRIPTION "Compression library"
- URL "http://www.zlib.net/"
+ URL "https://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"
+ URL "https://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"
+ URL "http://www.libtiff.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita TIFF filter")
if (TIFF_FOUND)
list (APPEND ANDROID_EXTRA_LIBS ${TIFF_LIBRARY})
endif()
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"
+ URL "https://www.libjpeg-turbo.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita JPEG filter")
if (JPEG_FOUND)
list (APPEND ANDROID_EXTRA_LIBS ${JPEG_LIBRARY})
endif()
find_package(GIF)
set_package_properties(GIF PROPERTIES
DESCRIPTION "Library for loading and saving gif files."
URL "http://giflib.sourceforge.net/"
TYPE OPTIONAL
PURPOSE "Required by the Krita GIF filter")
if (GIF_FOUND)
list (APPEND ANDROID_EXTRA_LIBS ${GIF_LIBRARY})
endif()
find_package(HEIF "1.3.0")
set_package_properties(HEIF PROPERTIES
DESCRIPTION "Library for loading and saving heif files."
URL "https://github.com/strukturag/libheif"
TYPE OPTIONAL
PURPOSE "Required by the Krita HEIF filter")
find_package(OpenJPEG "2.3.0")
set_package_properties(OpenJPEG PROPERTIES
DESCRIPTION "Library for loading and saving jp2000 files."
- URL "http://www.openjpeg.org/"
+ URL "https://www.openjpeg.org/"
TYPE OPTIONAL
PURPOSE "Required by the Krita JP2000 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"
+ URL "https://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)
if (FFTW3_FOUND)
list (APPEND ANDROID_EXTRA_LIBS ${FFTW3_LIBRARY})
endif()
find_package(OCIO)
set_package_properties(OCIO PROPERTIES
DESCRIPTION "The OpenColorIO Library"
- URL "http://www.opencolorio.org"
+ URL "https://www.opencolorio.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita LUT docker")
macro_bool_to_01(OCIO_FOUND HAVE_OCIO)
set_package_properties(PythonLibrary PROPERTIES
DESCRIPTION "Python Library"
- URL "http://www.python.org"
+ URL "https://www.python.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita PyQt plugin")
macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS)
find_package(SIP "4.19.13")
set_package_properties(SIP PROPERTIES
DESCRIPTION "Support for generating SIP Python bindings"
URL "https://www.riverbankcomputing.com/software/sip/download"
TYPE OPTIONAL
PURPOSE "Required by the Krita PyQt plugin")
macro_bool_to_01(SIP_FOUND HAVE_SIP)
find_package(PyQt5 "5.6.0")
set_package_properties(PyQt5 PROPERTIES
DESCRIPTION "Python bindings for Qt5."
URL "https://www.riverbankcomputing.com/software/pyqt/download5"
TYPE OPTIONAL
PURPOSE "Required by the Krita PyQt plugin")
macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5)
##
## 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(LibExiv2 0.16 REQUIRED)
if (ANDROID)
list (APPEND ANDROID_EXTRA_LIBS ${LibExiv2_LIBRARIES})
# because libexiv2 depends on libexpat and it is installed in the same folder
get_filename_component (_base_dir ${LibExiv2_LIBRARIES} DIRECTORY)
list (APPEND ANDROID_EXTRA_LIBS ${_base_dir}/libexpat.so)
endif()
##
## 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()
list (APPEND ANDROID_EXTRA_LIBS ${LCMS2_LIBRARIES})
##
## 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 ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm")
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()
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 "-ffp-contract=fast")
if(NOT WIN32)
set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC")
endif()
elseif (NOT MSVC)
set(ADDITIONAL_VC_FLAGS "-fabi-version=0 -ffp-contract=fast")
if(NOT WIN32)
set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC")
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})
##
## Test endianness
##
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"
+ URL "https://poppler.freedesktop.org/"
TYPE OPTIONAL
PURPOSE "Required by the Krita PDF filter.")
##
## Test for quazip
##
find_package(QuaZip 0.6)
set_package_properties(QuaZip PROPERTIES
DESCRIPTION "A library for reading and writing zip files"
URL "https://stachenov.github.io/quazip/"
TYPE REQUIRED
PURPOSE "Needed for reading and writing KRA and ORA files"
)
# FIXME: better way to do this?
list (APPEND ANDROID_EXTRA_LIBS ${QUAZIP_LIBRARIES}
${EXPAT_LIBRARY}
${KF5_LIBRARIES}/libKF5Completion.so
${KF5_LIBRARIES}/libKF5WindowSystem.so
${KF5_LIBRARIES}/libKF5WidgetsAddons.so
${KF5_LIBRARIES}/libKF5ItemViews.so
${KF5_LIBRARIES}/libKF5ItemModels.so
${KF5_LIBRARIES}/libKF5GuiAddons.so
${KF5_LIBRARIES}/libKF5I18n.so
${KF5_LIBRARIES}/libKF5CoreAddons.so
${KF5_LIBRARIES}/libKF5ConfigGui.so
${KF5_LIBRARIES}/libKF5ConfigCore.so
${KF5_LIBRARIES}/libKF5Archive.so)
##
## Test for Atomics
##
include(CheckAtomic)
############################
#############################
## 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)
if (BUILD_TESTING)
add_subdirectory(benchmarks)
endif()
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)
if(WIN32)
include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake)
endif()
message("\nBroken tests:")
foreach(tst ${KRITA_BROKEN_TESTS})
message(" * ${tst}")
endforeach()
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po )
find_package(KF5I18n CONFIG REQUIRED)
ki18n_install(po)
endif()
if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk")
set (_CMAKE_ANDROID_DIR "${ECM_DIR}/../toolchain")
list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount)
include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt.cmake)
math(EXPR last "${targetsCount}-1")
foreach(idx RANGE 0 ${last})
list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget)
list(GET ANDROID_APK_DIR ${idx} APK_DIR)
if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR)
message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}")
elseif(NOT APK_DIR)
get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE)
set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/")
endif()
ecm_androiddeployqt("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}")
set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}")
endforeach()
elseif(ANDROID)
message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET=<targetname> and -DANDROID_APK_DIR=<paths>")
endif()
diff --git a/README.md b/README.md
index 4c87ca2901..bf67829595 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,51 @@
![Picture](https://krita.org/wp-content/uploads/2019/04/krita-logo-2019.png)
| Jenkins CI Name | Master | Stable |
| --------------- | ------ | ------ |
| OpenSuse Qt 5.12 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.12/) |[![Build Status](https://build.kde.org/buildStatus/icon?job=Extragear%2Fkrita%2Fstable-kf5-qt5+SUSEQt5.12)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20SUSEQt5.12/)|
| FreeBSD Qt 5.13 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.13/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.13/) |[![Build Status](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.13/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.13/)|
Krita is a free and open source digital painting application. It is for artists who want to create professional work from start to end. Krita is used by comic book artists, illustrators, concept artists, matte and texture painters and in the digital VFX industry.
If you are reading this on Github, be aware that this is just a mirror. Our real
code repository is provided by KDE: https://invent.kde.org/kde/krita
![Picture](https://krita.org/wp-content/uploads/2016/04/krita-30-screenshot.jpg)
### User Manual
https://docs.krita.org/en/user_manual.html
### Development Notes and Build Instructions
If you're building on Windows or OSX you'll need to build some third-party dependencies first. You should look at the README in the 3rdparty folder for directions.
-If you're building on Linux, please follow David Revoy's Cat Guide: http://www.davidrevoy.com/article193/guide-building-krita-on-linux-for-cats
+If you're building on Linux, please follow [the online documentation](https://docs.krita.org/en/untranslatable_pages/building_krita.html).
Other developer guides, notes and wiki:
https://docs.krita.org/en/untranslatable_pages.html
Apidox:
https://api.kde.org/extragear-api/graphics-apidocs/krita/html/index.html
### Bugs and Wishes
https://bugs.kde.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1315444&product=krita&query_format=advanced
### Discussion Forum
http://forum.kde.org/viewforum.php?f=136
### IRC channel
Most of the developers hang out here. If you are interested in helping with the project this is a great place to start. Many of the developers based in Europe so they may be offline depending on when you join.
irc.freenode.net, #krita
### Project Website
http://www.krita.org
### License
Krita as a whole is licensed under the GNU Public License, Version 3. Individual files may have a different, but compatible license.
diff --git a/build-tools/windows/build.cmd b/build-tools/windows/build.cmd
index fb543193e3..bbdf886197 100644
--- a/build-tools/windows/build.cmd
+++ b/build-tools/windows/build.cmd
@@ -1,818 +1,817 @@
@echo off
setlocal enabledelayedexpansion
goto begin
:: Subroutines
:find_on_path out_variable file_name
set %1=%~f$PATH:2
goto :EOF
:get_dir_path out_variable file_path
set %1=%~dp2
goto :EOF
:get_full_path out_variable file_path
setlocal
set FULL_PATH=%~f2
if not exist "%FULL_PATH%" (
set FULL_PATH=
) else (
if exist "%FULL_PATH%\" (
set FULL_PATH=
)
)
endlocal & set "%1=%FULL_PATH%"
goto :EOF
:get_full_path_dir out_variable file_path
setlocal
set FULL_PATH=%~dp2
if not exist "%FULL_PATH%" (
set FULL_PATH=
)
endlocal & set "%1=%FULL_PATH%"
goto :EOF
:prompt_for_string out_variable prompt
set /p %1=%~2^>
goto :EOF
:prompt_for_positive_integer out_variable prompt
setlocal
call :prompt_for_string USER_INPUT "%~2"
if "%USER_INPUT%" == "" set USER_INPUT=0
set /a RESULT=%USER_INPUT%
if not %RESULT% GTR 0 (
set RESULT=
)
endlocal & set "%1=%RESULT%"
goto :EOF
:prompt_for_file out_variable prompt
setlocal
:prompt_for_file__retry
call :prompt_for_string USER_INPUT "%~2"
if "%USER_INPUT%" == "" (
endlocal
set %1=
goto :EOF
)
call :get_full_path RESULT "%USER_INPUT%"
if "%RESULT%" == "" (
echo Input does not point to valid file!
set USER_INPUT=
goto prompt_for_file__retry
)
endlocal & set "%1=%RESULT%"
goto :EOF
:prompt_for_dir out_variable prompt
setlocal
:prompt_for_dir__retry
call :prompt_for_string USER_INPUT "%~2"
if "%USER_INPUT%" == "" (
endlocal
set %1=
goto :EOF
)
call :get_full_path_dir RESULT "%USER_INPUT%\"
if "%RESULT%" == "" (
echo Input does not point to valid dir!
set USER_INPUT=
goto prompt_for_dir__retry
)
endlocal & set "%1=%RESULT%"
goto :EOF
:usage
echo Usage:
echo %~n0 [--no-interactive] [ OPTIONS ... ]
echo.
echo Basic options:
echo --no-interactive Run without interactive prompts
echo When not specified, the script will prompt
echo for some of the parameters.
echo --jobs ^<count^> Set parallel jobs count when building
echo Defaults to no. of logical cores
echo --skip-deps Skips (re)building of deps
echo --skip-krita Skips (re)building of Krita
echo --cmd Launch a cmd prompt instead of building.
echo The environment is set up like the build
echo environment with some helper command macros.
echo.
echo Path options:
echo --src-dir ^<dir_path^> Specify Krita source dir
echo If unspecified, this will be determined from
echo the script location.
echo --download-dir ^<dir_path^> Specify deps download dir
echo Can be omitted if --skip-deps is used
echo --deps-build-dir ^<dir_path^> Specify deps build dir
echo Can be omitted if --skip-deps is used
echo --deps-install-dir ^<dir_path^> Specify deps install dir
echo --krita-build-dir ^<dir_path^> Specify Krita build dir
echo Can be omitted if --skip-krita is used
echo --krita-install-dir ^<dir_path^> Specify Krita install dir
echo Can be omitted if --skip-krita is used
echo.
goto :EOF
:usage_and_exit
call :usage
exit /b
:usage_and_fail
call :usage
exit /b 100
:: ----------------------------
:begin
echo Krita build script for Windows
echo.
:: command-line args parsing
set ARG_NO_INTERACTIVE=
set ARG_JOBS=
set ARG_SKIP_DEPS=
set ARG_SKIP_KRITA=
set ARG_SRC_DIR=
set ARG_DOWNLOAD_DIR=
set ARG_DEPS_BUILD_DIR=
set ARG_DEPS_INSTALL_DIR=
set ARG_KRITA_BUILD_DIR=
set ARG_KRITA_INSTALL_DIR=
set ARG_CMD=
:args_parsing_loop
set CURRENT_MATCHED=
if not "%1" == "" (
if "%1" == "--no-interactive" (
set ARG_NO_INTERACTIVE=1
set CURRENT_MATCHED=1
)
if "%1" == "--jobs" (
if not "%ARG_JOBS%" == "" (
echo ERROR: Arg --jobs specified more than once 1>&2
echo.
goto usage_and_fail
)
set /a "ARG_JOBS=%2"
if not !ARG_JOBS! GTR 0 (
echo ERROR: Arg --jobs is not a positive integer 1>&2
echo.
goto usage_and_fail
)
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--skip-deps" (
set ARG_SKIP_DEPS=1
set CURRENT_MATCHED=1
)
if "%1" == "--skip-krita" (
set ARG_SKIP_KRITA=1
set CURRENT_MATCHED=1
)
if "%1" == "--src-dir" (
if not "%ARG_SRC_DIR%" == "" (
echo ERROR: Arg --src-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if not exist "%~f2\" (
echo ERROR: Arg --src-dir does not point to a directory 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_SRC_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--download-dir" (
if not "%ARG_DOWNLOAD_DIR%" == "" (
echo ERROR: Arg --download-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if "%~f2" == "" (
echo ERROR: Arg --download-dir does not point to a valid path 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_DOWNLOAD_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--deps-build-dir" (
if not "%ARG_DEPS_BUILD_DIR%" == "" (
echo ERROR: Arg --deps-build-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if "%~f2" == "" (
echo ERROR: Arg --deps-build-dir does not point to a valid path 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_DEPS_BUILD_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--deps-install-dir" (
if not "%ARG_DEPS_INSTALL_DIR%" == "" (
echo ERROR: Arg --deps-install-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if "%~f2" == "" (
echo ERROR: Arg --deps-install-dir does not point to a valid path 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_DEPS_INSTALL_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--krita-build-dir" (
if not "%ARG_KRITA_BUILD_DIR%" == "" (
echo ERROR: Arg --krita-build-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if "%~f2" == "" (
echo ERROR: Arg --krita-build-dir does not point to a valid path 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_KRITA_BUILD_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--krita-install-dir" (
if not "%ARG_KRITA_INSTALL_DIR%" == "" (
echo ERROR: Arg --krita-install-dir specified more than once 1>&2
echo.
goto usage_and_fail
)
if "%~f2" == "" (
echo ERROR: Arg --krita-install-dir does not point to a valid path 1>&2
echo.
goto usage_and_fail
)
call :get_dir_path ARG_KRITA_INSTALL_DIR "%~f2\"
shift /2
set CURRENT_MATCHED=1
)
if "%1" == "--cmd" (
set ARG_CMD=1
set CURRENT_MATCHED=1
)
if "%1" == "--help" (
goto usage_and_exit
)
if not "!CURRENT_MATCHED!" == "1" (
echo ERROR: Unknown option %1 1>&2
echo.
goto usage_and_fail
)
shift /1
goto args_parsing_loop
)
if "%ARG_NO_INTERACTIVE%" == "1" (
echo Non-interactive mode
) else (
echo Interactive mode
:: Trick to pause on exit
call :real_begin
pause
exit /b !ERRORLEVEL!
)
:real_begin
echo.
if "%ARG_SKIP_DEPS%" == "1" (
if "%ARG_SKIP_KRITA%" == "1" (
echo ERROR: You cannot skip both deps and Krita 1>&2
echo.
exit /b 102
)
echo Building of deps will be skipped.
) else (
if "%ARG_SKIP_KRITA%" == "1" (
echo Building of Krita will be skipped.
) else (
echo Both deps and Krita will be built.
)
)
:: Check environment config
if "%CMAKE_EXE%" == "" (
call :find_on_path CMAKE_EXE cmake.exe
if "!CMAKE_EXE!" == "" (
if not "%ARG_NO_INTERACTIVE%" == "1" (
call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe"
)
if "!CMAKE_EXE!" == "" (
echo ERROR: CMake not found! 1>&2
exit /b 102
)
) else (
echo Found CMake on PATH: !CMAKE_EXE!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this correct? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_file CMAKE_EXE "Provide path to cmake.exe"
if "!CMAKE_EXE!" == "" (
echo ERROR: CMake not found! 1>&2
exit /b 102
)
)
)
)
)
echo CMake: %CMAKE_EXE%
if "%SEVENZIP_EXE%" == "" (
call :find_on_path SEVENZIP_EXE 7z.exe
if "!SEVENZIP_EXE!" == "" (
set "SEVENZIP_EXE=%ProgramFiles%\7-Zip\7z.exe"
if "!SEVENZIP_EXE!" == "" (
set "SEVENZIP_EXE=%ProgramFiles(x86)%\7-Zip\7z.exe"
)
if "!SEVENZIP_EXE!" == "" (
echo 7-Zip not found
)
)
)
if "%SEVENZIP_EXE%" == "" (
echo 7-Zip: %SEVENZIP_EXE%
)
if "%MINGW_BIN_DIR%" == "" (
call :find_on_path MINGW_BIN_DIR_MAKE_EXE mingw32-make.exe
if "!MINGW_BIN_DIR_MAKE_EXE!" == "" (
if not "%ARG_NO_INTERACTIVE%" == "1" (
call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64"
)
if "!MINGW_BIN_DIR_MAKE_EXE!" == "" (
echo ERROR: mingw-w64 not found! 1>&2
exit /b 102
)
call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!"
) else (
call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!"
echo Found mingw on PATH: !MINGW_BIN_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this correct? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_file MINGW_BIN_DIR_MAKE_EXE "Provide path to mingw32-make.exe of mingw-w64"
if "!MINGW_BIN_DIR_MAKE_EXE!" == "" (
echo ERROR: mingw-w64 not found! 1>&2
exit /b 102
)
call :get_dir_path MINGW_BIN_DIR "!MINGW_BIN_DIR_MAKE_EXE!"
)
)
)
)
echo mingw-w64: %MINGW_BIN_DIR%
if "%PYTHON_BIN_DIR%" == "" (
call :find_on_path PYTHON_BIN_DIR_PYTHON_EXE python.exe
if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" (
if not "%ARG_NO_INTERACTIVE%" == "1" (
call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2"
)
if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" (
echo ERROR: Python not found! 1>&2
exit /b 102
)
call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!"
) else (
call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!"
echo Found Python on PATH: !PYTHON_BIN_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this correct? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_file PYTHON_BIN_DIR_PYTHON_EXE "Provide path to python.exe of Python 3.6.2"
if "!PYTHON_BIN_DIR_PYTHON_EXE!" == "" (
echo ERROR: Python not found! 1>&2
exit /b 102
)
call :get_dir_path PYTHON_BIN_DIR "!PYTHON_BIN_DIR_PYTHON_EXE!"
)
)
)
)
echo Python: %PYTHON_BIN_DIR%
if "%ARG_SKIP_DEPS%" == "1" goto skip_windows_sdk_dir_check
if "%WindowsSdkDir%" == "" if not "%ProgramFiles(x86)%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10"
if "%WindowsSdkDir%" == "" set "WindowsSdkDir=%ProgramFiles(x86)%\Windows Kits\10"
if exist "%WindowsSdkDir%\" (
pushd "%WindowsSdkDir%"
if exist "bin\x64\fxc.exe" (
set HAVE_FXC_EXE=1
if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%"
) else (
for /f "delims=" %%a in ('dir /a:d /b "bin\10.*"') do (
if exist "bin\%%a\x64\fxc.exe" (
set HAVE_FXC_EXE=1
if "%WindowsSdkVerBinPath%" == "" set "WindowsSdkVerBinPath=%WindowsSdkDir%\bin\%%a\"
)
)
)
popd
)
set QT_ENABLE_DYNAMIC_OPENGL=ON
if not "%HAVE_FXC_EXE%" == "1" (
set WindowsSdkDir=
echo Windows SDK 10 with fxc.exe not found
echo Qt will *not* be built with ANGLE ^(dynamic OpenGL^) support.
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
exit /b 102
)
)
set QT_ENABLE_DYNAMIC_OPENGL=OFF
) else echo Windows SDK 10 with fxc.exe found on %WindowsSdkDir%
:skip_windows_sdk_dir_check
if not "%ARG_JOBS%" == "" (
set "PARALLEL_JOBS=%ARG_JOBS%"
)
if "%PARALLEL_JOBS%" == "" (
echo Number of logical CPU cores detected: %NUMBER_OF_PROCESSORS%
echo Enabling %NUMBER_OF_PROCESSORS% parallel jobs
set PARALLEL_JOBS=%NUMBER_OF_PROCESSORS%
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this correct? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_positive_integer PARALLEL_JOBS "Provide no. of parallel jobs"
if "!PARALLEL_JOBS!" == "" (
echo ERROR: Invalid job count! 1>&2
exit /b 102
)
)
)
)
echo Parallel jobs count: %PARALLEL_JOBS%
if not "%ARG_SRC_DIR%" == "" (
set "KRITA_SRC_DIR=%ARG_SRC_DIR%"
)
if "%KRITA_SRC_DIR%" == "" (
:: Check whether this looks like to be in the source tree
set "_temp=%~dp0"
if "!_temp:~-21!" == "\build-tools\windows\" (
if exist "!_temp:~0,-21!\CMakeLists.txt" (
if exist "!_temp:~0,-21!\3rdparty\CMakeLists.txt" (
set "KRITA_SRC_DIR=!_temp:~0,-21!\"
echo Script is running inside Krita src dir
)
)
)
)
if "%KRITA_SRC_DIR%" == "" (
if not "%ARG_NO_INTERACTIVE%" == "1" (
call :prompt_for_dir KRITA_SRC_DIR "Provide path of Krita src dir"
)
if "!KRITA_SRC_DIR!" == "" (
echo ERROR: Krita src dir not found! 1>&2
exit /b 102
)
)
echo Krita src: %KRITA_SRC_DIR%
if "%ARG_SKIP_DEPS%" == "1" goto skip_deps_args_check
if not "%ARG_DOWNLOAD_DIR%" == "" (
set "DEPS_DOWNLOAD_DIR=%ARG_DOWNLOAD_DIR%"
)
if "%DEPS_DOWNLOAD_DIR%" == "" (
set DEPS_DOWNLOAD_DIR=%CD%\d\
echo Using default deps download dir: !DEPS_DOWNLOAD_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_dir DEPS_DOWNLOAD_DIR "Provide path of depps download dir"
)
)
if "!DEPS_DOWNLOAD_DIR!" == "" (
echo ERROR: Deps download dir not set! 1>&2
exit /b 102
)
)
echo Deps download dir: %DEPS_DOWNLOAD_DIR%
if not "%ARG_DEPS_BUILD_DIR%" == "" (
set "DEPS_BUILD_DIR=%ARG_DEPS_BUILD_DIR%"
)
if "%DEPS_BUILD_DIR%" == "" (
set DEPS_BUILD_DIR=%CD%\b_deps\
echo Using default deps build dir: !DEPS_BUILD_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_dir DEPS_BUILD_DIR "Provide path of deps build dir"
)
)
if "!DEPS_BUILD_DIR!" == "" (
echo ERROR: Deps build dir not set! 1>&2
exit /b 102
)
)
echo Deps build dir: %DEPS_BUILD_DIR%
:skip_deps_args_check
if not "%ARG_DEPS_INSTALL_DIR%" == "" (
set "DEPS_INSTALL_DIR=%ARG_DEPS_INSTALL_DIR%"
)
if "%DEPS_INSTALL_DIR%" == "" (
set DEPS_INSTALL_DIR=%CD%\i_deps\
echo Using default deps install dir: !DEPS_INSTALL_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_dir DEPS_INSTALL_DIR "Provide path of deps install dir"
)
)
if "!DEPS_INSTALL_DIR!" == "" (
echo ERROR: Deps install dir not set! 1>&2
exit /b 102
)
)
echo Deps install dir: %DEPS_INSTALL_DIR%
if "%ARG_SKIP_KRITA%" == "1" goto skip_krita_args_check
if not "%ARG_KRITA_BUILD_DIR%" == "" (
set "KRITA_BUILD_DIR=%ARG_KRITA_BUILD_DIR%"
)
if "%KRITA_BUILD_DIR%" == "" (
set KRITA_BUILD_DIR=%CD%\b\
echo Using default Krita build dir: !KRITA_BUILD_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_dir KRITA_BUILD_DIR "Provide path of Krita build dir"
)
)
if "!KRITA_BUILD_DIR!" == "" (
echo ERROR: Krita build dir not set! 1>&2
exit /b 102
)
)
echo Krita build dir: %KRITA_BUILD_DIR%
if not "%ARG_KRITA_INSTALL_DIR%" == "" (
set "KRITA_INSTALL_DIR=%ARG_KRITA_INSTALL_DIR%"
)
if "%KRITA_INSTALL_DIR%" == "" (
set KRITA_INSTALL_DIR=%CD%\i\
echo Using default Krita install dir: !KRITA_INSTALL_DIR!
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is this ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
call :prompt_for_dir KRITA_INSTALL_DIR "Provide path of Krita install dir"
)
)
if "!KRITA_INSTALL_DIR!" == "" (
echo ERROR: Krita install dir not set! 1>&2
exit /b 102
)
)
echo Krita install dir: %KRITA_INSTALL_DIR%
:skip_krita_args_check
echo.
if not "%ARG_NO_INTERACTIVE%" == "1" (
choice /c ny /n /m "Is the above ok? [y/n] "
if errorlevel 3 exit 255
if not errorlevel 2 (
exit /b 1
)
echo.
)
:: Initialize clean PATH
set PATH=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\
set PATH=%MINGW_BIN_DIR%;%PYTHON_BIN_DIR%;%PATH%
echo Creating dirs...
if NOT "%ARG_SKIP_DEPS%" == "1" (
mkdir %DEPS_DOWNLOAD_DIR%
if errorlevel 1 (
if not exist "%DEPS_DOWNLOAD_DIR%\" (
echo ERROR: Cannot create deps download dir! 1>&2
exit /b 103
)
)
mkdir %DEPS_BUILD_DIR%
if errorlevel 1 (
if not exist "%DEPS_BUILD_DIR%\" (
echo ERROR: Cannot create deps build dir! 1>&2
exit /b 103
)
)
mkdir %DEPS_INSTALL_DIR%
if errorlevel 1 (
if not exist "%DEPS_INSTALL_DIR%\" (
echo ERROR: Cannot create deps install dir! 1>&2
exit /b 103
)
)
)
if NOT "%ARG_SKIP_KRITA%" == "1" (
mkdir %KRITA_BUILD_DIR%
if errorlevel 1 (
if not exist "%KRITA_BUILD_DIR%\" (
echo ERROR: Cannot create Krita build dir! 1>&2
exit /b 103
)
)
mkdir %KRITA_INSTALL_DIR%
if errorlevel 1 (
if not exist "%KRITA_INSTALL_DIR%\" (
echo ERROR: Cannot create Krita install dir! 1>&2
exit /b 103
)
)
)
echo.
set CMAKE_BUILD_TYPE=RelWithDebInfo
set QT_ENABLE_DEBUG_INFO=OFF
:: Paths for CMake
set "BUILDDIR_DOWNLOAD_CMAKE=%DEPS_DOWNLOAD_DIR:\=/%"
set "BUILDDIR_DOWNLOAD_CMAKE=%BUILDDIR_DOWNLOAD_CMAKE: =\ %"
set "BUILDDIR_DEPS_INSTALL_CMAKE=%DEPS_INSTALL_DIR:\=/%"
set "BUILDDIR_DEPS_INSTALL_CMAKE=%BUILDDIR_DEPS_INSTALL_CMAKE: =\ %"
set "BUILDDIR_KRITA_INSTALL_CMAKE=%KRITA_INSTALL_DIR:\=/%"
set "BUILDDIR_KRITA_INSTALL_CMAKE=%BUILDDIR_KRITA_INSTALL_CMAKE: =\ %"
set PATH=%DEPS_INSTALL_DIR%\bin;%PATH%
if not "%GETTEXT_SEARCH_PATH%" == "" (
set PATH=!PATH!;!GETTEXT_SEARCH_PATH!
)
:: Prepare the CMake command lines
set CMDLINE_CMAKE_DEPS="%CMAKE_EXE%" "%KRITA_SRC_DIR%\3rdparty" ^
-DSUBMAKE_JOBS=%PARALLEL_JOBS% ^
-DQT_ENABLE_DEBUG_INFO=%QT_ENABLE_DEBUG_INFO% ^
-DQT_ENABLE_DYNAMIC_OPENGL=%QT_ENABLE_DYNAMIC_OPENGL% ^
-DEXTERNALS_DOWNLOAD_DIR=%BUILDDIR_DOWNLOAD_CMAKE% ^
-DINSTALL_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^
-G "MinGW Makefiles" ^
-DUSE_QT_TABLET_WINDOWS=ON ^
-DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE%
set CMDLINE_CMAKE_KRITA="%CMAKE_EXE%" "%KRITA_SRC_DIR%\." ^
-DBoost_DEBUG=OFF ^
-DBOOST_INCLUDEDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/include ^
-DBOOST_ROOT=%BUILDDIR_DEPS_INSTALL_CMAKE% ^
-DBOOST_LIBRARYDIR=%BUILDDIR_DEPS_INSTALL_CMAKE%/lib ^
-DCMAKE_PREFIX_PATH=%BUILDDIR_DEPS_INSTALL_CMAKE% ^
-DCMAKE_INSTALL_PREFIX=%BUILDDIR_KRITA_INSTALL_CMAKE% ^
-DBUILD_TESTING=OFF ^
-DHAVE_MEMORY_LEAK_TRACKER=OFF ^
-DFOUNDATION_BUILD=ON ^
-DUSE_QT_TABLET_WINDOWS=ON ^
- -DHIDE_SAFE_ASSERTS=OFF ^
+ -DHIDE_SAFE_ASSERTS=ON ^
-Wno-dev ^
- -G "MinGW Makefiles" ^
- -DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE%
+ -G "MinGW Makefiles"
:: Launch CMD prompt if requested
if "%ARG_CMD%" == "1" (
doskey cmake-deps=cmd /c "pushd %DEPS_BUILD_DIR% && %CMDLINE_CMAKE_DEPS%"
doskey cmake-krita=cmd /c "pushd %KRITA_BUILD_DIR% && %CMDLINE_CMAKE_KRITA%"
doskey make-deps=cmd /c "pushd %DEPS_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target $*"
doskey make-krita=cmd /c "pushd %KRITA_BUILD_DIR% && "%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS%"
echo.
title Krita build - %KRITA_SRC_DIR% ^(deps: %DEPS_BUILD_DIR%, krita: %KRITA_BUILD_DIR%^)
echo You're now in the build environment.
echo The following macros are available:
echo cmake-deps
echo -- Run CMake for the deps.
echo make-deps ^<deps target^>
echo -- Run build for the specified deps target. The target name should
echo include the `ext_` prefix, e.g. `ext_qt`.
echo cmake-krita
echo -- Run CMake for Krita.
echo make-krita
echo -- Run build for Krita's `install` target.
echo.
echo For more info, type `doskey /macros` to view the macro commands.
cmd /k
exit
)
if "%ARG_SKIP_DEPS%" == "1" goto skip_build_deps
pushd %DEPS_BUILD_DIR%
if errorlevel 1 (
echo ERROR: Cannot enter deps build dir! 1>&2
exit /b 104
)
echo Running CMake for deps...
@echo on
%CMDLINE_CMAKE_DEPS%
@if errorlevel 1 (
@echo ERROR: CMake configure failed! 1>&2
@exit /b 104
)
@echo off
echo.
set EXT_TARGETS=patch png2ico zlib lzma gettext openssl qt boost exiv2 fftw3 eigen3
set EXT_TARGETS=%EXT_TARGETS% ilmbase jpeg lcms2 ocio openexr png tiff gsl vc libraw
set EXT_TARGETS=%EXT_TARGETS% giflib freetype poppler kwindowsystem drmingw gmic
set EXT_TARGETS=%EXT_TARGETS% python sip pyqt
set EXT_TARGETS=%EXT_TARGETS% quazip
for %%a in (%EXT_TARGETS%) do (
echo Building ext_%%a...
"%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target ext_%%a
if errorlevel 1 (
echo ERROR: Building of ext_%%a failed! 1>&2
exit /b 105
)
)
echo.
echo ******** Built deps ********
popd
:skip_build_deps
if "%ARG_SKIP_KRITA%" == "1" goto skip_build_krita
pushd %KRITA_BUILD_DIR%
if errorlevel 1 (
echo ERROR: Cannot enter Krita build dir! 1>&2
exit /b 104
)
echo Running CMake for Krita...
@echo on
%CMDLINE_CMAKE_KRITA%
@if errorlevel 1 (
@echo ERROR: CMake configure failed! 1>&2
@exit /b 104
)
@echo off
echo.
echo Building Krita...
"%CMAKE_EXE%" --build . --config %CMAKE_BUILD_TYPE% --target install -- -j%PARALLEL_JOBS%
if errorlevel 1 (
echo ERROR: Building of Krita failed! 1>&2
exit /b 105
)
echo.
echo ******** Built Krita ********
popd
:skip_build_krita
echo Krita build completed!
diff --git a/cmake/modules/FindLibExiv2.cmake b/cmake/modules/FindLibExiv2.cmake
deleted file mode 100644
index 935cee2c55..0000000000
--- a/cmake/modules/FindLibExiv2.cmake
+++ /dev/null
@@ -1,115 +0,0 @@
-#.rst:
-# FindLibExiv2
-# ------------
-#
-# Try to find the Exiv2 library.
-#
-# This will define the following variables:
-#
-# ``LibExiv2_FOUND``
-# System has LibExiv2.
-#
-# ``LibExiv2_VERSION``
-# The version of LibExiv2.
-#
-# ``LibExiv2_INCLUDE_DIRS``
-# This should be passed to target_include_directories() if
-# the target is not used for linking.
-#
-# ``LibExiv2_LIBRARIES``
-# The LibExiv2 library.
-# This can be passed to target_link_libraries() instead of
-# the ``LibExiv2::LibExiv2`` target
-#
-# If ``LibExiv2_FOUND`` is TRUE, the following imported target
-# will be available:
-#
-# ``LibExiv2::LibExiv2``
-# The Exiv2 library
-#
-# Since 5.53.0.
-#
-#=============================================================================
-# Copyright (c) 2018, Christophe Giboudeaux, <christophe@krop.fr>
-# Copyright (c) 2010, Alexander Neundorf, <neundorf@kde.org>
-# Copyright (c) 2008, Gilles Caulier, <caulier.gilles@gmail.com>
-#
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# 1. Redistributions of source code must retain the copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3. The name of the author may not be used to endorse or promote products
-# derived from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
-# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#=============================================================================
-
-find_package(PkgConfig QUIET)
-pkg_check_modules(PC_EXIV2 QUIET exiv2)
-
-find_path(LibExiv2_INCLUDE_DIRS NAMES exiv2/exif.hpp
- HINTS ${PC_EXIV2_INCLUDEDIR}
-)
-
-find_library(LibExiv2_LIBRARIES NAMES exiv2 libexiv2
- HINTS ${PC_EXIV2_LIBRARY_DIRS}
-)
-
-set(LibExiv2_VERSION ${PC_EXIV2_VERSION})
-
-if(NOT LibExiv2_VERSION AND DEFINED LibExiv2_INCLUDE_DIRS)
- # With exiv >= 0.27, the version #defines are in exv_conf.h instead of version.hpp
- foreach(_exiv2_version_file "version.hpp" "exv_conf.h")
- if(EXISTS "${LibExiv2_INCLUDE_DIRS}/exiv2/${_exiv2_version_file}")
- file(READ "${LibExiv2_INCLUDE_DIRS}/exiv2/${_exiv2_version_file}" _exiv_version_file_content)
- string(REGEX MATCH "#define EXIV2_MAJOR_VERSION[ ]+\\([0-9]+\\)" EXIV2_MAJOR_VERSION_MATCH ${_exiv_version_file_content})
- string(REGEX MATCH "#define EXIV2_MINOR_VERSION[ ]+\\([0-9]+\\)" EXIV2_MINOR_VERSION_MATCH ${_exiv_version_file_content})
- string(REGEX MATCH "#define EXIV2_PATCH_VERSION[ ]+\\([0-9]+\\)" EXIV2_PATCH_VERSION_MATCH ${_exiv_version_file_content})
- if(EXIV2_MAJOR_VERSION_MATCH)
- string(REGEX REPLACE ".*_MAJOR_VERSION[ ]+\\((.*)\\)" "\\1" EXIV2_MAJOR_VERSION ${EXIV2_MAJOR_VERSION_MATCH})
- string(REGEX REPLACE ".*_MINOR_VERSION[ ]+\\((.*)\\)" "\\1" EXIV2_MINOR_VERSION ${EXIV2_MINOR_VERSION_MATCH})
- string(REGEX REPLACE ".*_PATCH_VERSION[ ]+\\((.*)\\)" "\\1" EXIV2_PATCH_VERSION ${EXIV2_PATCH_VERSION_MATCH})
- endif()
- endif()
- endforeach()
-
- set(LibExiv2_VERSION "${EXIV2_MAJOR_VERSION}.${EXIV2_MINOR_VERSION}.${EXIV2_PATCH_VERSION}")
-endif()
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(LibExiv2
- FOUND_VAR LibExiv2_FOUND
- REQUIRED_VARS LibExiv2_LIBRARIES LibExiv2_INCLUDE_DIRS
- VERSION_VAR LibExiv2_VERSION
-)
-
-mark_as_advanced(LibExiv2_INCLUDE_DIRS LibExiv2_LIBRARIES)
-
-if(LibExiv2_FOUND AND NOT TARGET LibExiv2::LibExiv2)
- add_library(LibExiv2::LibExiv2 UNKNOWN IMPORTED)
- set_target_properties(LibExiv2::LibExiv2 PROPERTIES
- IMPORTED_LOCATION "${LibExiv2_LIBRARIES}"
- INTERFACE_INCLUDE_DIRECTORIES "${LibExiv2_INCLUDE_DIRS}"
- )
-endif()
-
-include(FeatureSummary)
-set_package_properties(LibExiv2 PROPERTIES
- URL "http://www.exiv2.org"
- DESCRIPTION "Image metadata support"
-)
diff --git a/cmake/modules/FindOpenEXR.cmake b/cmake/modules/FindOpenEXR.cmake
deleted file mode 100644
index 93b05dd999..0000000000
--- a/cmake/modules/FindOpenEXR.cmake
+++ /dev/null
@@ -1,103 +0,0 @@
-# Try to find the OpenEXR libraries
-# This check defines:
-#
-# OPENEXR_FOUND - system has OpenEXR
-# OPENEXR_INCLUDE_DIR - OpenEXR include directory
-# OPENEXR_LIBRARIES - Libraries needed to use OpenEXR
-# OPENEXR_DEFINITIONS - definitions required to use OpenEXR
-
-# Copyright (c) 2006, Alexander Neundorf, <neundorf@kde.org>
-#
-# Redistribution and use is allowed according to the terms of the BSD license.
-# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
-
-
-if (OPENEXR_INCLUDE_DIR AND OPENEXR_LIBRARIES)
- # in cache already
- set(OPENEXR_FOUND TRUE)
-
-else (OPENEXR_INCLUDE_DIR AND OPENEXR_LIBRARIES)
-
- # use pkg-config to get the directories and then use these values
- # in the FIND_PATH() and FIND_LIBRARY() calls
- find_package(PkgConfig)
- pkg_check_modules(PC_OPENEXR QUIET OpenEXR)
-
- FIND_PATH(OPENEXR_INCLUDE_DIR ImfRgbaFile.h
- HINTS
- ${PC_OPENEXR_INCLUDEDIR}
- ${PC_OPENEXR_INCLUDE_DIRS}
- PATH_SUFFIXES OpenEXR
- )
-
- FIND_LIBRARY(OPENEXR_HALF_LIBRARY NAMES Half
- HINTS
- ${PC_OPENEXR_LIBDIR}
- ${PC_OPENEXR_LIBRARY_DIRS}
- )
-
-
- FIND_LIBRARY(OPENEXR_IEX_LIBRARY NAMES Iex
- PATHS
- ${PC_OPENEXR_LIBDIR}
- ${PC_OPENEXR_LIBRARY_DIRS}
- )
-
- FIND_LIBRARY(OPENEXR_IMATH_LIBRARY NAMES Imath
- HINTS
- ${PC_OPENEXR_LIBDIR}
- ${PC_OPENEXR_LIBRARY_DIRS}
- )
-
- FIND_LIBRARY(OPENEXR_ILMIMF_LIBRARY NAMES IlmImf
- HINTS
- ${PC_OPENEXR_LIBDIR}
- ${PC_OPENEXR_LIBRARY_DIRS}
- )
-
- FIND_LIBRARY(OPENEXR_ILMTHREAD_LIBRARY NAMES IlmThread
- HINTS
- ${PC_OPENEXR_LIBDIR}
- ${PC_OPENEXR_LIBRARY_DIRS}
- )
-
- if (OPENEXR_INCLUDE_DIR AND OPENEXR_IMATH_LIBRARY AND OPENEXR_ILMIMF_LIBRARY AND OPENEXR_IEX_LIBRARY AND OPENEXR_HALF_LIBRARY)
- set(OPENEXR_FOUND TRUE)
- if (OPENEXR_ILMTHREAD_LIBRARY)
- set(OPENEXR_LIBRARIES ${OPENEXR_IMATH_LIBRARY} ${OPENEXR_ILMIMF_LIBRARY} ${OPENEXR_IEX_LIBRARY} ${OPENEXR_HALF_LIBRARY} ${OPENEXR_ILMTHREAD_LIBRARY} )
- else (OPENEXR_ILMTHREAD_LIBRARY)
- set(OPENEXR_LIBRARIES ${OPENEXR_IMATH_LIBRARY} ${OPENEXR_ILMIMF_LIBRARY} ${OPENEXR_IEX_LIBRARY} ${OPENEXR_HALF_LIBRARY} )
- endif (OPENEXR_ILMTHREAD_LIBRARY)
-
- if (WIN32)
- set(_OPENEXR_DEFINITIONS -DOPENEXR_DLL)
- else (WIN32)
- set(_OPENEXR_DEFINITIONS)
- endif (WIN32)
-
- set(OPENEXR_DEFINITIONS ${_OPENEXR_DEFINITIONS})
-
- endif (OPENEXR_INCLUDE_DIR AND OPENEXR_IMATH_LIBRARY AND OPENEXR_ILMIMF_LIBRARY AND OPENEXR_IEX_LIBRARY AND OPENEXR_HALF_LIBRARY)
-
-
- if (OPENEXR_FOUND)
- if (NOT OpenEXR_FIND_QUIETLY)
- message(STATUS "Found OPENEXR: ${OPENEXR_LIBRARIES}")
- endif (NOT OpenEXR_FIND_QUIETLY)
- else (OPENEXR_FOUND)
- if (OpenEXR_FIND_REQUIRED)
- message(FATAL_ERROR "Could NOT find OPENEXR")
- endif (OpenEXR_FIND_REQUIRED)
- endif (OPENEXR_FOUND)
-
- mark_as_advanced(
- OPENEXR_INCLUDE_DIR
- OPENEXR_LIBRARIES
- OPENEXR_ILMIMF_LIBRARY
- OPENEXR_ILMTHREAD_LIBRARY
- OPENEXR_IMATH_LIBRARY
- OPENEXR_IEX_LIBRARY
- OPENEXR_HALF_LIBRARY
- OPENEXR_DEFINITIONS )
-
-endif (OPENEXR_INCLUDE_DIR AND OPENEXR_LIBRARIES)
diff --git a/krita/data/profiles/elles-icc-profiles/make-elles-profiles.c b/krita/data/profiles/elles-icc-profiles/make-elles-profiles.c
index 9e71e8d65e..f7212ee31c 100644
--- a/krita/data/profiles/elles-icc-profiles/make-elles-profiles.c
+++ b/krita/data/profiles/elles-icc-profiles/make-elles-profiles.c
@@ -1,1653 +1,1653 @@
/* License:
*
* Code for making well-behaved ICC profiles
* Copyright © 2013, 2014, 2015 Elle Stone
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Contact information:
* ellestone@ninedegreesbelow.com
- * http://ninedegreesbelow.com
+ * https://ninedegreesbelow.com
*
* */
/* About the ICC profile header "Platform" tag:
*
* When creating a profile, LCMS checks to see if the platform is
* Windows ('MSFT'). If your platform isn't Windows, LCMS defaults
* to using the Apple ('APPL') platform tag for the profile header.
*
* There is an unofficial Platform
* cmsPlatformSignature cmsSigUnices 0x2A6E6978 '*nix'. There is,
* however, no LCMS2 API for changing the platform when making a profile.
*
* So on my own computer, to replace 'APPL' with '*nix' in the header,
* I modified the LCMS source file 'cmsio0.c' and recompiled LCMS:
* #ifdef CMS_IS_WINDOWS_
* Header.platform= (cmsPlatformSignature) _cmsAdjustEndianess32(cmsSigMicrosoft);
* #else
* Header.platform= (cmsPlatformSignature) _cmsAdjustEndianess32(cmsSigUnices);
* #endif
*
* */
/* Sample command line to compile this code:
*
* gcc -g -O0 -Wall -o make-elles-profiles make-elles-profiles.c -llcms2
*
* You'll see a lot of harmless warnings about unused variables.
* That's because I included variables for profiles that the code
* doesn't actually make, in case you might want to make
* these profiles for your own use.
*
* */
#include <lcms2.h>
int main ()
{
/* prints D50 XYZ values from lcms2.h,
* mostly to let you know the code did something!
* */
printf("D50X, D50Y, D50Z = %1.8f %1.8f %1.8f\n", cmsD50X, cmsD50Y, cmsD50Z);
/* ************************** TONE CURVES *************************** */
/* sRGB, Rec709, and labl Tone Reproduction Curves ("TRCs") */
/* About these TRCs:
* This code makes V2 and V4 ICC profiles.
* For the V4 profiles, which are made using parametric curves,
* these TRCs can work in unbounded mode.
* For the V2 profiles, the resulting TRC is a 4096-point curve and
* cannot work in unbounded mode.
- * See http://ninedegreesbelow.com/photography/lcms2-unbounded-mode.html
+ * See https://ninedegreesbelow.com/photography/lcms2-unbounded-mode.html
* for an explanation of unbounded mode ICC profile conversions.
*
* Also, during ICC profile conversions,
* LCMS quantizes images with ICC profiles that have point TRCs.
* So use the V4 profile variants for editing images with software
* that uses LCMS2 as the Color Management System.
* */
/* sRGB TRC */
cmsFloat64Number srgb_parameters[5] =
{ 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
cmsToneCurve *srgb_parametic_curve =
cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
cmsToneCurve *srgb_parametric[3] =
{srgb_parametic_curve,srgb_parametic_curve,srgb_parametic_curve};
/* LAB "L" (perceptually uniform) TRC */
cmsFloat64Number labl_parameters[5] =
{ 3.0, 0.862076, 0.137924, 0.110703, 0.080002 };
cmsToneCurve *labl_parametic_curve =
cmsBuildParametricToneCurve(NULL, 4, labl_parameters);
cmsToneCurve *labl_parametric[3] =
{labl_parametic_curve,labl_parametic_curve,labl_parametic_curve};
/* Rec 709 TRC */
cmsFloat64Number rec709_parameters[5] =
{ 1.0 / 0.45, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.018 };
cmsToneCurve *rec709_parametic_curve =
cmsBuildParametricToneCurve(NULL, 4, rec709_parameters);
cmsToneCurve *rec709_parametric[3] =
{rec709_parametic_curve,rec709_parametic_curve,rec709_parametic_curve};
/* The following true gamma TRCs work in unbounded mode
* for both V2 and V4 profiles, and quantization during ICC profile
* conversions is not an issue with either the V2 or V4 variants: */
/* gamma=1.00, linear gamma, "linear light", etc tone response curve */
cmsToneCurve* gamma100[3];
gamma100[0] = gamma100[1] = gamma100[2] = cmsBuildGamma (NULL, 1.00);
cmsToneCurve* gamma200[3];
gamma200[0] = gamma200[1] = gamma200[2] = cmsBuildGamma (NULL, 2.00);
/* Because of hexadecimal rounding during the profile making process,
* the following two gamma values for the profile's TRC are not precisely
* preserved when a V2 profile is made. So I used the V2 value for
* both V2 and V4 versions of the profiles that use these TRCs.
* Otherwise V2 and V4 versions of nominally gamma=1.80 and gamma=2.20
* profiles would have slightly different gamma curves.
* */
/* gamma=1.80078125 tone response curve */
/* http://www.color.org/chardata/rgb/ROMMRGB.pdf indicates that
* the official tone response curve for ROMM isn't a simple gamma curve
* but rather has a linear portion in shadows, just like sRGB.
* Most ProPhotoRGB profiles use a gamma curve equal to 1.80078125.
* This odd value is because of hexadecimal rounding.
* */
cmsToneCurve* gamma180[3];
gamma180[0] = gamma180[1] = gamma180[2] = cmsBuildGamma (NULL, 1.80078125);
/* gamma=2.19921875 tone response curve */
-/* per http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf;
+/* per https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf;
* ClayRGB uses this value. Based on an old proprietary profile,
* it also appears to be the correct gamma for WideGamutRGB.
* It also is what you get when you
* try to create a V2 profile with a gamma=2.20 gamma curve.
* So perhaps the AdobeRGB1998 specifications
* were simply bowing to the inevitable hexadecimal rounding.
* Almost all (all?) V2 ICC profiles with nominally gamma=2.20
* really have a gamma of 2.19921875, not 2.20.
* */
cmsToneCurve* gamma220[3];
gamma220[0] = gamma220[1] = gamma220[2] = cmsBuildGamma (NULL, 2.19921875);
/* ************************** WHITE POINTS ************************** */
/* D50 WHITE POINTS */
cmsCIExyY d50_romm_spec= {0.3457, 0.3585, 1.0};
-/* http://photo-lovers.org/pdf/color/romm.pdf */
+/* https://photo-lovers.org/pdf/color/romm.pdf */
cmsCIExyY d50_illuminant_specs = {0.345702915, 0.358538597, 1.0};
/* calculated from D50 illuminant XYZ values in ICC specs */
/* D65 WHITE POINTS */
cmsCIExyY d65_srgb_adobe_specs = {0.3127, 0.3290, 1.0};
/* White point from the sRGB.icm and AdobeRGB1998 profile specs:
- * http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
+ * https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
* 4.2.1 Reference Display White Point
* The chromaticity coordinates of white displayed on
* the reference color monitor shall be x=0.3127, y=0.3290.
* . . . [which] correspond to CIE Standard Illuminant D65.
*
* Wikipedia gives this same white point for SMPTE-C.
* This white point is also given in the sRGB color space specs.
* It's probably correct for most or all of the standard D65 profiles.
*
* The D65 white point values used in the LCMS virtual sRGB profile
* is slightly different than the D65 white point values given in the
* sRGB color space specs, so the LCMS virtual sRGB profile
* doesn't match sRGB profiles made using the values given in the
* sRGB color space specs.
*
* */
/* Various C and E WHITE POINTS */
cmsCIExyY c_astm = {0.310060511, 0.316149551, 1.0};
/* see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html */
cmsCIExyY e_astm = {0.333333333, 0.333333333, 1.0};
/* see http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html */
cmsCIExyY c_cie= {0.310, 0.316};
/* https://en.wikipedia.org/wiki/NTSC#Colorimetry */
cmsCIExyY e_cie= {0.333, 0.333};
cmsCIExyY c_6774_robertson= {0.308548930, 0.324928102, 1.0};
-/* see http://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants
+/* see https://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants
* also see http://www.brucelindbloom.com/index.html?Eqn_T_to_xy.html for the equations */
cmsCIExyY e_5454_robertson= {0.333608970, 0.348572909, 1.0};
-/* see http://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants
+/* see https://en.wikipedia.org/wiki/Standard_illuminant#White_points_of_standard_illuminants
* also see http://www.brucelindbloom.com/index.html?Eqn_T_to_xy.html for the equations */
/* ACES white point, taken from
* Specification S-2014-004
* ACEScg – A Working Space for CGI Render and Compositing
*/
cmsCIExyY d60_aces= {0.32168, 0.33767, 1.0};
/* *****************Set up profile variables and values *************** */
cmsHPROFILE profile;
cmsToneCurve* tone_curve[3];
cmsCIExyY whitepoint;
cmsCIExyYTRIPLE primaries;
const char* filename;
cmsMLU *copyright = cmsMLUalloc(NULL, 1);
/* I put a Creative Commons Attribution-ShareAlike 3.0 Unported License
* on the profiles that I distribute (see
* https://creativecommons.org/licenses/by-sa/3.0/legalcode)
* The CC V3 Attribution-ShareAlike unported licence is accepted by both
* Debian and Fedora as a free licence.
*
* Note that the CC V3 BY-SA licence that I put on the ICC profiles
* is not the same licence that's applied to my profile-making code,
* which is LGPL2.1+.
*
* Of course you can modify my profile-making code to put some other
* *profile* copyright on the actual profiles that the code makes,
* for example public domain (Creative Commons CC0)
* or one of the GPL copyrights.
*
* The ICC suggested wording for profile copyrights is here
* (see the last section, entitled "Licensing"):
* http://www.color.org/registry/profileregistration.xalter
*
* The ICC copyright sounds like it's probably a free licence,
* but as of June 2015 it hasn't been accepted by Fedora or Debian as a
* free license.
*
* Here are the Fedora and Debian lists of free licences:
*
* https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing#Content_Licenses
* https://wiki.debian.org/DFSGLicenses
*
* */
cmsMLUsetASCII(copyright, "en", "US", "Copyright 2015, Elle Stone (website: http://ninedegreesbelow.com/; email: ellestone@ninedegreesbelow.com). This ICC profile is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License (https://creativecommons.org/licenses/by-sa/3.0/legalcode).");
cmsMLU *manufacturer = cmsMLUalloc(NULL, 1);
cmsMLU *description;
/* ********************** MAKE THE PROFILES ************************* */
/* ACES PROFILES */
/* ***** Make profile: ACEScg, D60, gamma=1.00 */
/* ACEScg chromaticities taken from
* Specification S-2014-004
* ACEScg – A Working Space for CGI Render and Compositing
*/
cmsCIExyYTRIPLE aces_cg_primaries =
{
{0.713, 0.293, 1.0},
{0.165, 0.830, 1.0},
{0.128, 0.044, 1.0}
};
whitepoint = d60_aces;
primaries = aces_cg_primaries;
/* linear gamma */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACEScg-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACEScg-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* gamma=2.2 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V4-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACEScg-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACEScg-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* sRGB TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACEScg-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACEScg-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* labl TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACEScg-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACEScg-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACEScg-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* ***** Make profile: ACES, D60, gamma=1.00 */
/* ACES chromaticities taken from
* Specification
* */
cmsCIExyYTRIPLE aces_primaries =
{
{0.73470, 0.26530, 1.0},
{0.00000, 1.00000, 1.0},
{0.00010, -0.07700, 1.0}
};
cmsCIExyYTRIPLE aces_primaries_prequantized =
{
{0.734704192222, 0.265298276252, 1.0},
{-0.000004945077, 0.999992850272, 1.0},
{0.000099889199, -0.077007518685, 1.0}
};
whitepoint = d60_aces;
primaries = aces_primaries_prequantized;
/* linear gamma */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACES-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACES-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACES-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACES-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* gamma=2.2 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACES-elle-V4-g122.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACES-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACES-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACES-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* sRGB TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACES-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACES-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACES-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACES-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* labl TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACES-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACES-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ACES-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ACES-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* D50 PROFILES */
/* ***** Make profile: AllColorsRGB, D50, gamma=1.00 */
/* AllColors.icc has a slightly larger color gamut than the ACES color space.
* It has a D50 white point and a linear gamma TRC.
* It holds all visible colors.
* Just like the ACES color space,
* AllColors also holds a high percentage of imaginary colors.
- * See http://ninedegreesbelow.com/photography/xyz-rgb.html#xyY
+ * See https://ninedegreesbelow.com/photography/xyz-rgb.html#xyY
* for more information about imaginary colors.
* AllColors primaries for red and blue from
- * http://www.ledtuning.nl/en/cie-convertor
+ * https://www.ledtuning.nl/en/cie-convertor
* blue 375nm red 780nm, plus Y intercepts:
* Color Wavelength (): 375 nm.
* Spectral Locus coordinates: X:0.17451 Y:0.005182
* Color Wavelength (): 780 nm.
* Spectral Locus coordinates: X:0.734690265 Y:0.265309735
* X1:0.17451 Y1:0.005182
* X2:0.734690265 Y2:0.265309735
* X3:0.00Y3:? Solve for Y3:
* (0.265309735-0.005182)/(0.734690265-0.17451)=0.46436433279205221554=m
* y=mx+b let x=0; y=b
* Y1=0.005182=(0.46436433279205221554*0.17451)+b
* b=0.005182-(0.46436433279205221554*0.17451)=-.07585421971554103213
* */
cmsCIExyYTRIPLE allcolors_primaries =
{
{0.734690265, 0.265309735, 1.0},
{0.000000000, 1.000000000, 1.0},
{0.000000000, -.0758542197, 1.0}
};
whitepoint = d50_illuminant_specs;
primaries = allcolors_primaries;
/* linear gamma */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "AllColorsRGB-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "AllColorsRGB-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* gamma=2.2 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V4-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "AllColorsRGB-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "AllColorsRGB-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* sRGB TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "AllColorsRGB-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "AllColorsRGB-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* labl TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "AllColorsRGB-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "AllColorsRGB-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "AllColorsRGB-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* ***** Make profile: Identity, D50, gamma=1.00. */
/* These primaries also hold all possible visible colors,
* but less efficiently than the AllColors profile.*/
cmsCIExyYTRIPLE identity_primaries = {/* */
{1.0, 0.0, 1.0},
{0.0, 1.0, 1.0},
{0.0, 0.0, 1.0}
};
whitepoint = d50_illuminant_specs;
primaries = identity_primaries;
/* linear gamma */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "IdentityRGB-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "IdentityRGB-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* gamma=2.2 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V4-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "IdentityRGB-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "IdentityRGB-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* sRGB TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "IdentityRGB-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "IdentityRGB-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* labl TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "IdentityRGB-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "IdentityRGB-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "IdentityRGB-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* ***** Make profile: Romm/Prophoto, D50, gamma=1.80 */
/* Reference Input/Output Medium Metric RGB Color Encodings (RIMM/ROMM RGB)
* Kevin E. Spaulding, Geoffrey J. Woolfe and Edward J. Giorgianni
* Eastman Kodak Company, Rochester, New York, U.S.A.
- * Above document is available at http://photo-lovers.org/pdf/color/romm.pdf
+ * Above document is available at https://photo-lovers.org/pdf/color/romm.pdf
* Kodak designed the Romm (ProPhoto) 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. For high bit depth image editing only.
*/
cmsCIExyYTRIPLE romm_primaries = {
{0.7347, 0.2653, 1.0},
{0.1596, 0.8404, 1.0},
{0.0366, 0.0001, 1.0}
};
primaries = romm_primaries;
whitepoint = d50_romm_spec;
/* gamma 1.80 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma180[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V4-g18.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "LargeRGB-elle-V4-g18.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V2-g18.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "LargeRGB-elle-V2-g18.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* linear gamma */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "LargeRGB-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "LargeRGB-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* gamma=2.2 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V4-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "LargeRGB-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "LargeRGB-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* sRGB TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "LargeRGB-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "LargeRGB-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* labl TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "LargeRGB-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "LargeRGB-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "LargeRGB-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* ***** Make profile: WidegamutRGB, D50, gamma=2.19921875 */
/* Pascale's primary values produce a profile that matches
* old V2 Widegamut profiles from Adobe and Canon.
* Danny Pascale: A review of RGB color spaces
* http://www.babelcolor.com/download/A%20review%20of%20RGB%20color%20spaces.pdf
* WideGamutRGB was designed by Adobe to be a wide gamut color space that uses
* spectral colors as its primaries. For high bit depth image editing only. */
cmsCIExyYTRIPLE widegamut_pascale_primaries = {
{0.7347, 0.2653, 1.0},
{0.1152, 0.8264, 1.0},
{0.1566, 0.0177, 1.0}
};
primaries = widegamut_pascale_primaries;
whitepoint = d50_romm_spec;
/* gamma 2.20 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V4-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "WideRGB-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "WideRGB-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* linear gamma */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "WideRGB-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "WideRGB-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* sRGB TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "WideRGB-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "WideRGB-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* labl TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "WideRGB-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "WideRGB-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "WideRGB-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* D65 PROFILES */
/* ***** Make profile: ClayRGB (AdobeRGB), D65, gamma=2.19921875 */
/* The Adobe RGB 1998 color gamut covers a higher percentage of
* real-world greens than sRGB, but still doesn't include all printable
* greens, yellows, and cyans.
* When made using the gamma=2.19921875 tone response curve,
* this profile can be used for 8-bit image editing
* if used with appropriate caution to avoid posterization.
* When made with the gamma=2.19921875 tone response curve
* this profile can be applied to DCF R98 camera-generated jpegs.
* */
cmsCIExyYTRIPLE adobe_primaries = {
{0.6400, 0.3300, 1.0},
{0.2100, 0.7100, 1.0},
{0.1500, 0.0600, 1.0}
};
cmsCIExyYTRIPLE adobe_primaries_prequantized = {
{0.639996511, 0.329996864, 1.0},
{0.210005295, 0.710004866, 1.0},
{0.149997606, 0.060003644, 1.0}
};
primaries = adobe_primaries_prequantized;
whitepoint = d65_srgb_adobe_specs;
/* gamma 2.20 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V4-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ClayRGB-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ClayRGB-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* linear gamma */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ClayRGB-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ClayRGB-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* sRGB TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ClayRGB-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ClayRGB-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* labl TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ClayRGB-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "ClayRGB-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "ClayRGB-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* ***** Make profile: Rec.2020, D65, Rec709 TRC */
/*
* */
cmsCIExyYTRIPLE rec2020_primaries = {
{0.7079, 0.2920, 1.0},
{0.1702, 0.7965, 1.0},
{0.1314, 0.0459, 1.0}
};
cmsCIExyYTRIPLE rec2020_primaries_prequantized = {
{0.708012540607, 0.291993664388, 1.0},
{0.169991652439, 0.797007778423, 1.0},
{0.130997824007, 0.045996550894, 1.0}
};
primaries = rec2020_primaries_prequantized;
whitepoint = d65_srgb_adobe_specs;
/* rec.709 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = rec709_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V4-rec709.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Rec2020-elle-V4-rec709.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V2-rec709.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Rec2020-elle-V2-rec709.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* linear gamma */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Rec2020-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Rec2020-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* gamma=2.2 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V4-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Rec2020-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Rec2020-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* sRGB TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Rec2020-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Rec2020-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* labl TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Rec2020-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Rec2020-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Rec2020-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* ***** Make profile: sRGB, D65, sRGB TRC */
-/* http://en.wikipedia.org/wiki/Srgb */
+/* https://en.wikipedia.org/wiki/Srgb */
/* Hewlett-Packard and Microsoft designed sRGB to match
* the color gamut of consumer-grade CRTs from the 1990s
* and to be the standard color space for the world wide web.
* When made using the standard sRGB TRC, this sRGB profile
* can be applied to DCF R03 camera-generated jpegs and
* is an excellent color space for editing 8-bit images.
* When made using the linear gamma TRC, the resulting profile
* should only be used for high bit depth image editing.
* */
cmsCIExyYTRIPLE srgb_primaries = {
{0.6400, 0.3300, 1.0},
{0.3000, 0.6000, 1.0},
{0.1500, 0.0600, 1.0}
};
cmsCIExyYTRIPLE srgb_primaries_pre_quantized = {
{0.639998686, 0.330010138, 1.0},
{0.300003784, 0.600003357, 1.0},
{0.150002046, 0.059997204, 1.0}
};
primaries = srgb_primaries_pre_quantized;
whitepoint = d65_srgb_adobe_specs;
/* sRGB TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "sRGB-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "sRGB-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* linear gamma */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "sRGB-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "sRGB-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* gamma=2.2 */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V4-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "sRGB-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "sRGB-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* labl TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "sRGB-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "sRGB-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* Rec.709 TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = rec709_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V4-rec709.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "sRGB-elle-V4-rec709.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "sRGB-elle-V2-rec709.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "sRGB-elle-V2-rec709.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* ***** Make profile: CIE-RGB profile, E white point*/
/* The ASTM E white point is probably the right white point
* to use when making the CIE-RGB color space profile.
* It's not clear to me what the correct CIE-RGB primaries really are.
* Lindbloom gives one set. The LCMS version 1 tutorial gives a different set.
* I asked a friend to ask an expert, who said the real primaries should
* be calculated from the spectral wavelengths.
* Two sets of primaries are given below:
* */
/* This page explains what the CIE color space is:
* https://en.wikipedia.org/wiki/CIE_1931
* These pages give the wavelengths:
* http://hackipedia.org/Color%20space/pdf/CIE%20Color%20Space.pdf
* http://infocom.ing.uniroma1.it/~gaetano/texware/Full-How%20the%20CIE%201931%20Color-Matching%20Functions%20Were%20Derived%20from%20Wright-Guild%20Data.pdf
* This page has resources for calculating xy values given a spectral color wavelength:
* http://www.cvrl.org/cmfs.htm
* This page does the calculations for you:
- * http://www.ledtuning.nl/cie.php
+ * https://www.ledtuning.nl/en/cie-convertor
* Plugging the wavelengths into the ledtuning website
* gives the following CIE RGB xy primaries:
700.0 nm has Spectral Locus coordinates: x:0.734690023 y:0.265309977
546.1 nm has Spectral Locus coordinates: x:0.2736747378 y:0.7174284409
435.8 nm has Spectral Locus coordinates: x:0.1665361196 y:0.0088826412
* */
cmsCIExyYTRIPLE cie_primaries_ledtuning = {
{0.7346900230, 0.2653099770, 1.0},
{0.2736747378, 0.7174284409, 1.0},
{0.1665361196, 0.0088826412, 1.0}
};
/* Assuming you want to use the ASTM values for the E white point,
* here are the prequantized ledtuning primaries */
cmsCIExyYTRIPLE cie_primaries_ledtuning_prequantized = {
{0.734689082, 0.265296653, 1.0},
{0.273673341, 0.717437354, 1.0},
{0.166531028, 0.008882428, 1.0}
};
primaries = cie_primaries_ledtuning_prequantized;
whitepoint = e_astm;
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V4-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "CIERGB-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "CIERGB-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* A linear gamma version of this profile makes more sense
* than a gamma=2.2 version */
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma100[0];
whitepoint = e_astm;
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "CIERGB-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "CIERGB-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* sRGB TRC*/
tone_curve[0] = tone_curve[1] = tone_curve[2] = srgb_parametric[0];
whitepoint = e_astm;
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "CIERGB-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "CIERGB-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* labl TRC */
tone_curve[0] = tone_curve[1] = tone_curve[2] = labl_parametric[0];
profile = cmsCreateRGBProfile ( &whitepoint, &primaries, tone_curve );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "CIERGB-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "CIERGB-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "CIERGB-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/*
* Here's the second set of primaries
* http://www.cis.rit.edu/research/mcsl2/research/broadbent/CIE1931_RGB.pdf
* https://groups.google.com/forum/#!topic/sci.engr.color/fBI3k1llm-g
* Lindbloom gives these values on his Working Space Information page: */
cmsCIExyYTRIPLE cie_primaries_lindbloom = {
{0.7350, 0.2650, 1.0},
{0.2740, 0.7170, 1.0},
{0.1670, 0.0090, 1.0}
};
/* Assuming you want to use the ASTM values for the E white point,
* here are the prequantized Lindbloom primaries */
cmsCIExyYTRIPLE cie_primaries_lindbloom_prequantized = {
{0.714504840, 0.297234644, 1.0},
{0.520085568, 0.452364535, 1.0},
{0.090957433, 0.051485032, 1.0}
};
/* ***** Make profile: Gray ICC profiles - D50 white point ********* */
whitepoint = d50_illuminant_specs;
const cmsToneCurve* grayTRC;
/* Gray profile with gamma=1.00, linear gamma, "linear light", etc */
grayTRC = cmsBuildGamma (NULL, 1.00);
profile = cmsCreateGrayProfile ( &whitepoint, grayTRC );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V4-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-g10.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V2-g10.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* Gray profile with gamma=1.80,
* actually 1.80078125,
* in order to create the same gamma curve in V2 and V4 profiles */
grayTRC = cmsBuildGamma (NULL, 1.80078125);
profile = cmsCreateGrayProfile ( &whitepoint, grayTRC );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-g18.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V4-g18.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-g18.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V2-g18.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* Gray profile with gamma=2.20
* actually gamma=2.19921875,
* in order to create the same gamma curve in V2 and V4 profiles */
grayTRC = cmsBuildGamma (NULL, 2.19921875);
profile = cmsCreateGrayProfile ( &whitepoint, grayTRC );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V4-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-g22.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V2-g22.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* Gray profile with srgb-trc */
grayTRC = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
profile = cmsCreateGrayProfile ( &whitepoint, grayTRC );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V4-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-srgbtrc.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V2-srgbtrc.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* Gray profile with labl TRC */
grayTRC = cmsBuildParametricToneCurve(NULL, 4, labl_parameters);
profile = cmsCreateGrayProfile ( &whitepoint, grayTRC );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V4-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-labl.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V2-labl.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* Gray profile with Rec709 TRC */
grayTRC = cmsBuildParametricToneCurve(NULL, 4, rec709_parameters);
profile = cmsCreateGrayProfile ( &whitepoint, grayTRC );
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
/* V4 */
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V4-rec709.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V4-rec709.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* V2 */
cmsSetProfileVersion(profile, 2.1);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Gray-D50-elle-V2-rec709.icc");
cmsWriteTag(profile, cmsSigProfileDescriptionTag, description);
filename = "Gray-D50-elle-V2-rec709.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* ***** Make profile: LCMS built-in LAB and XYZ profiles *********** */
/* Based on transicc output, the V4 profiles
* can be used in unbounded mode, but the V2 versions cannot. */
profile = cmsCreateLab2Profile(&d50_illuminant_specs);
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Lab-D50-Identity-elle-V2.icc");
filename = "Lab-D50-Identity-elle-V2.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
profile = cmsCreateLab4Profile(&d50_illuminant_specs);
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "Lab-D50-Identity-elle-V4.icc");
filename = "Lab-D50-Identity-elle-V4.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
profile = cmsCreateXYZProfile();
cmsWriteTag(profile, cmsSigCopyrightTag, copyright);
description = cmsMLUalloc(NULL, 1);
cmsMLUsetASCII(description, "en", "US", "XYZ-D50-Identity-elle-V4.icc");
filename = "XYZ-D50-Identity-elle-V4.icc";
cmsSaveProfileToFile(profile, filename);
cmsMLUfree(description);
/* For the following profiles, information is provided, but not the actual
* profile making code.
* */
/* old monitor-based editing profiles */
/* ColorMatchRGB, D50, gamma=1.80 */
-/* http://www.dpreview.com/forums/post/3902882
- * http://lists.apple.com/archives/colorsync-users/2001/Apr/msg00073.html
+/* https://www.dpreview.com/forums/post/3902882
+ * https://lists.apple.com/archives/colorsync-users/2001/Apr/msg00073.html
* ColorMatch was designed to fit Radius PressView CRT monitors,
* similar to sRGB fitting consumer-grade CRT monitors,
* "fit" meaning "could be calibrated to match".
* Adobe does still distribute a ColorMatchRGB profile.
* Making this profile using the D50_romm_doc white point that is used
* in other old V2 profiles (ProPhoto, WideGamut)
* doesn't make a well behaved profile,
* but the resulting profile is very close to the Adobe-supplied version.
* Using the prequantized primaries makes a profile that's just as close
* to the Adobe-supplied version and in addition is well behaved.
* Unless you have untagged images created on a PressView CRT,
* there is no reason to make or use this profile.
* */
cmsCIExyYTRIPLE colormatch_primaries = {
{0.6300, 0.3400, 1.0},
{0.2950, 0.6050, 1.0},
{0.1500, 0.0750, 1.0}
};
cmsCIExyYTRIPLE colormatch_primaries_prequantized = {
{0.629992636, 0.339999723, 1.0},
{0.295006332, 0.604997745, 1.0},
{0.149992036, 0.075005244, 1.0}
};
primaries = colormatch_primaries_prequantized;
whitepoint = d50_romm_spec;
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma180[0];
/* AppleRGB, D65, gamma=1.80 */
/* AppleRGB was created to fit the old Apple CRT displays
* just as sRGB fit consumer-grade CRT monitors
* and ColorMatch fit PressView CRT monitors.
* */
cmsCIExyYTRIPLE apple_primaries = {
{0.6250, 0.3400, 1.0},
{0.2800, 0.5950, 1.0},
{0.1550, 0.0700, 1.0}
};
cmsCIExyYTRIPLE apple_primaries_prequantized = {
{0.625012368, 0.340000081, 1.0},
{0.279996113, 0.595006943, 1.0},
{0.155001212, 0.070001183, 1.0}
};
primaries = apple_primaries_prequantized;
whitepoint = d65_srgb_adobe_specs;
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma180[0];
/* video profiles */
/* PAL-SECAM, D65 gamma=2.20 */
-/* http://en.wikipedia.org/wiki/PAL */
+/* https://en.wikipedia.org/wiki/PAL */
cmsCIExyYTRIPLE pal_primaries = {
{0.6400, 0.3300, 1.0},
{0.2900, 0.6000, 1.0},
{0.1500, 0.0600, 1.0}
};
/* PAL is one of many video and television-related color spaces.
* If you need the original profile with all its tags,
* I recommend that you use the Argyllcms version of this profile
* (EBU3213_PAL.icm, located in the "ref" folder),
* rather than making your own.
* But if you do want to make your own PAL profile using LCMS2,
* these prequantized primaries and white point make a profile with
* the same primaries and white point as the Argyllcms profile.
* The Argyllcms profile has a point curve TRC.
* */
cmsCIExyYTRIPLE pal_primaries_prequantized = {
{0.640007798, 0.330006592, 1.0},
{0.290000327, 0.600000840, 1.0},
{0.149998025, 0.059996098, 1.0}
};
primaries = pal_primaries_prequantized;
whitepoint = d65_srgb_adobe_specs;
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
/* SMPTE-C, D65, gamma=2.20 */
-/* http://en.wikipedia.org/wiki/NTSC#SMPTE_C
+/* https://en.wikipedia.org/wiki/NTSC#SMPTE_C
* SMPTE-C is one of many video and television-related color spaces
* and is an update of the original NTSC. */
cmsCIExyYTRIPLE smpte_c_primaries = {
{0.6300, 0.3400, 1.0},
{0.3100, 0.5950, 1.0},
{0.1550, 0.0700, 1.0}
};
cmsCIExyYTRIPLE smpte_c_primaries_prequantized = {
{0.629996495, 0.339990597, 1.0},
{0.309997880, 0.594995808, 1.0},
{0.149999952, 0.069999431, 1.0}
};
primaries = smpte_c_primaries_prequantized;
whitepoint = d65_srgb_adobe_specs;
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
/* NTSC, C, gamma=2.20 */
-/* http://en.wikipedia.org/wiki/NTSC#Colorimetry*/
+/* https://en.wikipedia.org/wiki/NTSC#Colorimetry*/
/* According to Wikipedia, these "original 1953 color NTSC specifications"
* were used by early television receivers. */
cmsCIExyYTRIPLE ntcs_primaries = {
{0.6700, 0.3300, 1.0},
{0.2100, 0.7100, 1.0},
{0.1400, 0.0800, 1.0}
};
cmsCIExyYTRIPLE ntcs_primaries_prequantized = {
{0.670010373, 0.330001186, 1.0},
{0.209999261, 0.710001124, 1.0},
{0.139996061, 0.080002934, 1.0}
};
primaries = ntcs_primaries_prequantized;
whitepoint = c_astm;
tone_curve[0] = tone_curve[1] = tone_curve[2] = gamma220[0];
/* *********************** wrap up and close out ****************** */
/* free copyright */
cmsMLUfree(copyright);
/* make gcc happy by returning an integer from main() */
return 0;
}
diff --git a/krita/data/templates/animation/.directory b/krita/data/templates/animation/.directory
index 6cbbe623b2..d8cd91b610 100644
--- a/krita/data/templates/animation/.directory
+++ b/krita/data/templates/animation/.directory
@@ -1,29 +1,30 @@
[Desktop Entry]
Name=Animation Templates
Name[ar]=قوالب الحركات
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[et]=Animatsioonimallid
Name[eu]=Animazio-txantiloiak
Name[fi]=Animaatiopohjat
Name[fr]=Modèles pour animation
Name[gl]=Modelos de animación
Name[is]=Sniðmát fyrir hreyfimyndir
Name[it]=Modelli di animazioni
Name[ko]=애니메이션 템플릿
Name[nl]=Animatiesjablonen
Name[nn]=Animasjonsmalar
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]=动画模板
Name[zh_TW]=動畫範本
X-KDE-DefaultTab=true
diff --git a/krita/krita.action b/krita/krita.action
index d575792eb0..6b23a5b09c 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -1,3652 +1,3665 @@
<?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 Krita log for bug reports.</text>
+ <whatsThis></whatsThis>
+ <toolTip>Show Krita log for bug reports.</toolTip>
+ <iconText>Show Krita log for bug reports.</iconText>
+ <shortcut></shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
+
+ <Action name="sysinfo">
+ <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>
<Action name="ruler_pixel_multiple2">
<icon></icon>
<text>Use multiple of 2 for pixel scale</text>
<whatsThis>Use multiple of 2 for pixel scale</whatsThis>
<toolTip>Use multiple of 2 for pixel scale</toolTip>
<iconText>Use multiple of 2 for pixel scale</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="invert_selection">
<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="create_snapshot">
<icon></icon>
<text>Create Snapshot</text>
<whatsThis></whatsThis>
<toolTip>Create Snapshot</toolTip>
<iconText></iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="switchto_snapshot">
<icon></icon>
<text>Switch to Selected Snapshot</text>
<whatsThis></whatsThis>
<toolTip>Switch to selected snapshot</toolTip>
<iconText></iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_snapshot">
<icon></icon>
<text>Remove Selected Snapshot</text>
<whatsThis></whatsThis>
<toolTip>Remove Selected Snapshot</toolTip>
<iconText></iconText>
<activationFlags>1</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>0</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>0</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 X</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="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="selection_tool_mode_add">
<icon></icon>
<text>Selection Mode: Add</text>
<whatsThis></whatsThis>
<toolTip>Selection Mode: Add</toolTip>
<iconText>Selection Mode: Add</iconText>
<shortcut>A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selection_tool_mode_subtract">
<icon></icon>
<text>Selection Mode: Subtract</text>
<whatsThis></whatsThis>
<toolTip>Selection Mode: Subtract</toolTip>
<iconText>Selection Mode: Subtract</iconText>
<shortcut>S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selection_tool_mode_intersect">
<icon></icon>
<text>Selection Mode: Intersect</text>
<whatsThis></whatsThis>
<toolTip>Selection Mode: Intersect</toolTip>
<iconText>Selection Mode: Intersect</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selection_tool_mode_replace">
<icon></icon>
<text>Selection Mode: Replace</text>
<whatsThis></whatsThis>
<toolTip>Selection Mode: Replace</toolTip>
<iconText>Selection Mode: Replace</iconText>
<shortcut>R</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="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_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="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="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>Colorize Mask Tool</text>
<whatsThis></whatsThis>
<toolTip>Colorize Mask Tool</toolTip>
<iconText>Colorize Mask 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>Select Shapes Tool</text>
<whatsThis></whatsThis>
<toolTip>Select Shapes Tool</toolTip>
<iconText>Select Shapes 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="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="KisToolSelectPath">
<icon></icon>
<text>Bezier Curve Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Bezier Curve Selection Tool</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>Similar Color Selection Tool</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 or double-click ends the curve.</toolTip>
<iconText>Bezier Curve Tool. Shift-mouseclick or double-click 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="KisAssistantTool">
<icon></icon>
<text>Assistant Tool</text>
<whatsThis></whatsThis>
<toolTip>Assistant Tool</toolTip>
<iconText>Assistant 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>
<Action name="ToolReferenceImages">
<icon></icon>
<text>Reference Images Tool</text>
<whatsThis></whatsThis>
<toolTip>Reference Images Tool</toolTip>
<iconText>Reference Images Tool</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>addblankframe</icon>
<text>Create 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>addduplicateframe</icon>
<text>Create Duplicate 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="show_in_timeline">
<icon></icon>
<text>Show in Timeline</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_keyframe_left">
<icon></icon>
<text>Insert Keyframe Left</text>
<whatsThis></whatsThis>
<toolTip>Insert keyframes to the left of selection, moving the tail of animation to the right.</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_keyframe_right">
<icon></icon>
<text>Insert Keyframe Right</text>
<whatsThis></whatsThis>
<toolTip>Insert keyframes to the right of selection, moving the tail of animation to the right.</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_multiple_keyframes">
<icon></icon>
<text>Insert Multiple Keyframes</text>
<whatsThis></whatsThis>
<toolTip>Insert several keyframes based on user parameters.</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_frames_and_pull">
<icon></icon>
<text>Remove Frame and Pull</text>
<whatsThis></whatsThis>
<toolTip>Remove keyframes moving the tail of animation to the left</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_frames">
<icon>deletekeyframe</icon>
<text>Remove Keyframe</text>
<whatsThis></whatsThis>
<toolTip>Remove keyframes without moving anything around</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_column_left">
<icon></icon>
<text>Insert Column Left</text>
<whatsThis></whatsThis>
<toolTip>Insert column to the left of selection, moving the tail of animation to the right</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_column_right">
<icon></icon>
<text>Insert Column Right</text>
<whatsThis></whatsThis>
<toolTip>Insert column to the right of selection, moving the tail of animation to the right</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_multiple_columns">
<icon></icon>
<text>Insert Multiple Columns</text>
<whatsThis></whatsThis>
<toolTip>Insert several columns based on user parameters.</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_columns_and_pull">
<icon></icon>
<text>Remove Column and Pull</text>
<whatsThis></whatsThis>
<toolTip>Remove columns moving the tail of animation to the left</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_columns">
<icon></icon>
<text>Remove Column</text>
<whatsThis></whatsThis>
<toolTip>Remove columns without moving anything around</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_hold_frame">
<icon></icon>
<text>Insert Hold Frame</text>
<whatsThis></whatsThis>
<toolTip>Insert a hold frame after every keyframe</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_multiple_hold_frames">
<icon></icon>
<text>Insert Multiple Hold Frames</text>
<whatsThis></whatsThis>
<toolTip>Insert N hold frames after every keyframe</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_hold_frame">
<icon></icon>
<text>Remove Hold Frame</text>
<whatsThis></whatsThis>
<toolTip>Remove a hold frame after every keyframe</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_multiple_hold_frames">
<icon></icon>
<text>Remove Multiple Hold Frames</text>
<whatsThis></whatsThis>
<toolTip>Remove N hold frames after every keyframe</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_hold_column">
<icon></icon>
<text>Insert Hold Column</text>
<whatsThis></whatsThis>
<toolTip>Insert a hold column into the frame at the current position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="insert_multiple_hold_columns">
<icon></icon>
<text>Insert Multiple Hold Columns</text>
<whatsThis></whatsThis>
<toolTip>Insert N hold columns into the frame at the current position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_hold_column">
<icon></icon>
<text>Remove Hold Column</text>
<whatsThis></whatsThis>
<toolTip>Remove a hold column from the frame at the current position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_multiple_hold_columns">
<icon></icon>
<text>Remove Multiple Hold Columns</text>
<whatsThis></whatsThis>
<toolTip>Remove N hold columns from the frame at the current position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirror_frames">
<icon></icon>
<text>Mirror Frames</text>
<whatsThis></whatsThis>
<toolTip>Mirror frames' position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirror_columns">
<icon></icon>
<text>Mirror Columns</text>
<whatsThis></whatsThis>
<toolTip>Mirror columns' position</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_frames_to_clipboard">
<icon></icon>
<text>Copy to Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Copy frames to clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_frames_to_clipboard">
<icon></icon>
<text>Cut to Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Cut frames to clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paste_frames_from_clipboard">
<icon></icon>
<text>Paste from Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Paste frames from clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_columns_to_clipboard">
<icon></icon>
<text>Copy Columns to Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Copy columns to clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_columns_to_clipboard">
<icon></icon>
<text>Cut Columns to Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Cut columns to clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paste_columns_from_clipboard">
<icon></icon>
<text>Paste Columns from Clipboard</text>
<whatsThis></whatsThis>
<toolTip>Paste columns from clipboard</toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_start_time">
<icon></icon>
<text>Set Start Time</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_end_time">
<icon></icon>
<text>Set End Time</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="update_playback_range">
<icon></icon>
<text>Update Playback Range</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</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>1000</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_vector_node_to_svg">
<icon>document-save</icon>
<text>Save Vector Layer as SVG...</text>
<whatsThis></whatsThis>
<toolTip>Save Vector Layer as SVG</toolTip>
<iconText>Save Vector Layer as SVG</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_to_animated">
<icon></icon>
<text>Convert to &amp;animated layer</text>
<whatsThis></whatsThis>
<toolTip>Convert layer into animation frames</toolTip>
<iconText>Convert layer into animation frames</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_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="mirrorAllNodesX">
<icon>symmetry-horizontal</icon>
<text>Mirror All Layers Hori&amp;zontally</text>
<whatsThis></whatsThis>
<toolTip>Mirror All Layers Horizontally</toolTip>
<iconText>Mirror All Layers Horizontally</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorAllNodesY">
<icon>symmetry-vertical</icon>
<text>Mirror All Layers &amp;Vertically</text>
<whatsThis></whatsThis>
<toolTip>Mirror All Layers Vertically</toolTip>
<iconText>Mirror All Layers Vertically</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateAllLayers">
<icon></icon>
<text>&amp;Rotate All Layers...</text>
<whatsThis></whatsThis>
<toolTip>Rotate All Layers</toolTip>
<iconText>Rotate All Layers</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateAllLayersCW90">
<icon>object-rotate-right</icon>
<text>Rotate All &amp;Layers 90° to the Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate All Layers 90° to the Right</toolTip>
<iconText>Rotate All Layers 90° to the Right</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateAllLayersCCW90">
<icon>object-rotate-left</icon>
<text>Rotate All Layers &amp;90° to the Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate All Layers 90° to the Left</toolTip>
<iconText>Rotate All Layers 90° to the Left</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateAllLayers180">
<icon></icon>
<text>Rotate All Layers &amp;180°</text>
<whatsThis></whatsThis>
<toolTip>Rotate All Layers 180°</toolTip>
<iconText>Rotate All Layers 180°</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="scaleAllLayers">
<icon></icon>
<text>Scale All &amp;Layers to new Size...</text>
<whatsThis></whatsThis>
<toolTip>Scale All Layers to new Size</toolTip>
<iconText>Scale All Layers to new Size</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shearAllLayers">
<icon></icon>
<text>&amp;Shear All Layers...</text>
<whatsThis></whatsThis>
<toolTip>Shear All Layers</toolTip>
<iconText>Shear All Layers</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>
<Action name="set-copy-from">
<icon></icon>
<text>Set Copy F&amp;rom...</text>
<whatsThis></whatsThis>
<toolTip>Set the source for the selected clone layer(s).</toolTip>
<iconText>Set Copy From</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
</ActionCollection>
diff --git a/krita/krita4.xmlgui b/krita/krita4.xmlgui
index b79131225a..531eee5e33 100644
--- a/krita/krita4.xmlgui
+++ b/krita/krita4.xmlgui
@@ -1,408 +1,409 @@
-<?xml version="1.0"?>
+<?xml version="1.1"?>
<kpartgui xmlns="http://www.kde.org/standards/kxmlgui/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="Krita"
-version="430"
+version="435"
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_sessions"/>
<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="paste_as_reference"/>
<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="view_detached_canvas"/>
<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_to_pixel"/>
<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"/>
<Action name="view_toggle_reference_images"/>
<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="add_new_clone_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_vector_node_to_svg"/>
<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_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="LayerTransformAll">
<text>Transform &amp;All Layers</text>
<Action name="mirrorAllNodesX"/>
<Action name="mirrorAllNodesY"/>
<Action name="scaleAllLayers"/>
<Menu name="Rotate">
<text>&amp;Rotate</text>
<Action name="rotateAllLayers"/>
<Separator/>
<Action name="rotateAllLayersCW90"/>
<Action name="rotateAllLayersCCW90"/>
<Action name="rotateAllLayers180"/>
</Menu>
<Action name="shearAllLayers"/>
</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_selection"/>
<Separator/>
<Action name="edit_selection"/>
<Action name="convert_to_vector_selection"/>
<Action name="convert_to_raster_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"/>
<Menu name="selectopaquemenu">
<text>Select &amp;Opaque</text>
<Action name="selectopaque"/>
<Separator/>
<Action name="selectopaque_add"/>
<Action name="selectopaque_subtract"/>
<Action name="selectopaque_intersect"/>
</Menu>
<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="emboss_filters"/>
<Action name="enhance_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>
<Menu name="scripts"><text>Scripts</text></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="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"/>
+ <Action name="sysinfo"/>
<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="select_layout"/>
<Action name="workspaces"/>
</ToolBar>
</kpartgui>
diff --git a/krita/main.cc b/krita/main.cc
index d2d84e3d89..1523923528 100644
--- a/krita/main.cc
+++ b/krita/main.cc
@@ -1,598 +1,598 @@
/*
* 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 <QStandardPaths>
#include <QDir>
#include <QDate>
#include <QLocale>
#include <QSettings>
#include <QByteArray>
#include <QMessageBox>
#include <QThread>
#include <QOperatingSystemVersion>
#include <time.h>
#include <KisApplication.h>
#include <KoConfig.h>
#include <KoResourcePaths.h>
#include <kis_config.h>
#include "data/splash/splash_screen.xpm"
#include "data/splash/splash_holidays.xpm"
#include "data/splash/splash_screen_x2.xpm"
#include "data/splash/splash_holidays_x2.xpm"
#include "KisDocument.h"
#include "kis_splash_screen.h"
#include "KisPart.h"
#include "KisApplicationArguments.h"
#include <opengl/kis_opengl.h>
#include "input/KisQtWidgetsTweaker.h"
#include <KisUsageLogger.h>
#include <kis_image_config.h>
#ifdef Q_OS_ANDROID
#include <QtAndroid>
#endif
#if defined Q_OS_WIN
#include "config_use_qt_tablet_windows.h"
#include <windows.h>
#ifndef USE_QT_TABLET_WINDOWS
#include <kis_tablet_support_win.h>
#include <kis_tablet_support_win8.h>
#else
#include <dialogs/KisDlgCustomTabletResolution.h>
#endif
#include "config-high-dpi-scale-factor-rounding-policy.h"
#include "config-set-has-border-in-full-screen-default.h"
#ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT
#include <QtPlatformHeaders/QWindowsWindowFunctions>
#endif
#include <QLibrary>
#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
#ifdef Q_OS_WIN
namespace
{
typedef enum ORIENTATION_PREFERENCE {
ORIENTATION_PREFERENCE_NONE = 0x0,
ORIENTATION_PREFERENCE_LANDSCAPE = 0x1,
ORIENTATION_PREFERENCE_PORTRAIT = 0x2,
ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4,
ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8
} ORIENTATION_PREFERENCE;
typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)(
ORIENTATION_PREFERENCE orientation
);
void resetRotation()
{
QLibrary user32Lib("user32");
if (!user32Lib.load()) {
qWarning() << "Failed to load user32.dll! This really should not happen.";
return;
}
pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences
= reinterpret_cast<pSetDisplayAutoRotationPreferences_t>(user32Lib.resolve("SetDisplayAutoRotationPreferences"));
if (!pSetDisplayAutoRotationPreferences) {
dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences";
return;
}
bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE);
dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result;
}
} // namespace
#endif
#ifdef Q_OS_ANDROID
extern "C" JNIEXPORT void JNICALL
Java_org_krita_android_JNIWrappers_saveState(JNIEnv* /*env*/,
jobject /*obj*/,
jint /*n*/)
{
if (!KisPart::exists()) return;
KisPart *kisPart = KisPart::instance();
QList<QPointer<KisDocument>> list = kisPart->documents();
for (QPointer<KisDocument> &doc: list)
{
doc->autoSaveOnPause();
}
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("canvasState", "OPENGL_SUCCESS");
}
extern "C" JNIEXPORT void JNICALL
Java_org_krita_android_JNIWrappers_exitFullScreen(JNIEnv* /*env*/,
jobject /*obj*/,
jint /*n*/)
{
if (!KisPart::exists()) return;
KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
mainWindow->viewFullscreen(false);
}
__attribute__ ((visibility ("default")))
#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
// Workaround a bug in QNetworkManager
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
// A per-user unique string, without /, because QLocalServer cannot use names with a / in it
QString key = "Krita4" + 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);
QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true);
#ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY
// This rounding policy depends on a series of patches to Qt related to
// https://bugreports.qt.io/browse/QTBUG-53022. These patches are applied
// in ext_qt for WIndows (patches 0031-0036).
//
// The rounding policy can be set externally by setting the environment
// variable `QT_SCALE_FACTOR_ROUNDING_POLICY` to one of the following:
// Round: Round up for .5 and above.
// Ceil: Always round up.
// Floor: Always round down.
// RoundPreferFloor: Round up for .75 and above.
// PassThrough: Don't round.
//
// The default is set to RoundPreferFloor for better behaviour than before,
// but can be overridden by the above environment variable.
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
#endif
#ifdef Q_OS_ANDROID
const QString write_permission = "android.permission.WRITE_EXTERNAL_STORAGE";
const QStringList permissions = { write_permission };
const QtAndroid::PermissionResultMap resultHash =
QtAndroid::requestPermissionsSync(QStringList(permissions));
if (resultHash[write_permission] == QtAndroid::PermissionResult::Denied) {
// TODO: show a dialog and graciously exit
dbgKrita << "Permission denied by the user";
}
else {
dbgKrita << "Permission granted";
}
#endif
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
bool singleApplication = true;
bool enableOpenGLDebug = false;
bool openGLDebugSynchronous = false;
bool logUsage = true;
{
singleApplication = kritarc.value("EnableSingleApplication", true).toBool();
if (kritarc.value("EnableHiDPI", true).toBool()) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
}
if (!qgetenv("KRITA_HIDPI").isEmpty()) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
}
#ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY
if (kritarc.value("EnableHiDPIFractionalScaling", true).toBool()) {
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
}
#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;
}
KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc);
KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto;
logUsage = kritarc.value("LogUsage", true).toBool();
#ifdef Q_OS_WIN
const QString preferredRendererString = kritarc.value("OpenGLRenderer", "angle").toString();
#else
const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString();
#endif
preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString);
const KisOpenGL::RendererConfig config =
KisOpenGL::selectSurfaceConfig(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug);
KisOpenGL::setDefaultSurfaceConfig(config);
KisOpenGL::setDebugSynchronous(openGLDebugSynchronous);
#ifdef Q_OS_WIN
// HACK: https://bugs.kde.org/show_bug.cgi?id=390651
resetRotation();
#endif
}
if (logUsage) {
KisUsageLogger::initialize();
}
QString root;
QString language;
{
// Create a temporary application to get the root
QCoreApplication app(argc, argv);
Q_UNUSED(app);
root = KoResourcePaths::getApplicationRoot();
QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat);
languageoverride.beginGroup(QStringLiteral("Language"));
language = languageoverride.value(qAppName(), "").toString();
}
#ifdef Q_OS_LINUX
{
QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS");
if (originalXdgDataDirs.isEmpty()) {
// We don't want to completely override the default
originalXdgDataDirs = "/usr/local/share/:/usr/share/";
}
qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs);
}
#else
qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share"));
#endif
dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS");
// Now that the paths are set, set the language. First check the override from the language
// selection dialog.
dbgKrita << "Override language:" << language;
bool rightToLeft = false;
if (!language.isEmpty()) {
KLocalizedString::setLanguages(language.split(":"));
// And override Qt's locale, too
qputenv("LANG", language.split(":").first().toLocal8Bit());
QLocale locale(language.split(":").first());
QLocale::setDefault(locale);
const QStringList rtlLanguages = QStringList()
<< "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi";
if (rtlLanguages.contains(language.split(':').first())) {
rightToLeft = true;
}
}
else {
dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG");
// And if there isn't one, check the one set by the system.
QLocale locale = QLocale::system();
if (locale.name() != QStringLiteral("en")) {
QStringList uiLanguages = locale.uiLanguages();
for (QString &uiLanguage : uiLanguages) {
// This list of language codes that can have a specifier should
// be extended whenever we have translations that need it; right
// now, only en, pt, zh are in this situation.
if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) {
uiLanguage.replace(QChar('-'), QChar('_'));
}
else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) {
uiLanguage = "zh_TW";
}
else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) {
uiLanguage = "zh_CN";
}
}
for (int i = 0; i < uiLanguages.size(); i++) {
QString uiLanguage = uiLanguages[i];
// Strip the country code
int idx = uiLanguage.indexOf(QChar('-'));
if (idx != -1) {
uiLanguage = uiLanguage.left(idx);
uiLanguages.replace(i, uiLanguage);
}
}
dbgKrita << "Converted ui languages:" << uiLanguages;
qputenv("LANG", uiLanguages.first().toLocal8Bit());
#ifdef Q_OS_MAC
// See https://bugs.kde.org/show_bug.cgi?id=396370
KLocalizedString::setLanguages(QStringList() << uiLanguages.first());
#else
KLocalizedString::setLanguages(QStringList() << uiLanguages);
#endif
}
}
#if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH
const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc);
QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab);
if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") &&
qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) {
QRect customTabletRect;
KisDlgCustomTabletResolution::Mode tabletMode =
KisDlgCustomTabletResolution::getTabletMode(&customTabletRect);
KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect);
}
#endif
// first create the application so we can create a pixmap
KisApplication app(key, argc, argv);
KisUsageLogger::writeHeader();
KisOpenGL::initialize();
#ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT
if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) {
QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true);
}
#endif
if (!language.isEmpty()) {
if (rightToLeft) {
app.setLayoutDirection(Qt::RightToLeft);
}
else {
app.setLayoutDirection(Qt::LeftToRight);
}
}
KLocalizedString::setApplicationDomain("krita");
dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations();
dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita");
#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));
dbgKrita << "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.exportAs() || args.exportSequence();
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 (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
app.setAttribute(Qt::AA_DisableWindowContextHelpButton);
#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), QPixmap(splash_holidays_x2_xpm));
}
else {
splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm));
}
app.setSplashScreen(splash);
}
#if defined Q_OS_WIN
KisConfig cfg(false);
bool supportedWindowsVersion = true;
QOperatingSystemVersion osVersion = QOperatingSystemVersion::current();
if (osVersion.type() == QOperatingSystemVersion::Windows) {
if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) {
supportedWindowsVersion = true;
}
else {
supportedWindowsVersion = false;
if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) {
QMessageBox::information(0,
i18nc("@title:window", "Krita: Warning"),
i18n("You are running an unsupported version of Windows: %1.\n"
"This is not recommended. Do not report any bugs.\n"
"Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name()));
cfg.writeEntry("WarnedAboutUnsupportedWindows", true);
}
}
}
#ifndef USE_QT_TABLET_WINDOWS
{
if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) {
cfg.setUseWin8PointerInput(false);
}
if (!cfg.useWin8PointerInput()) {
bool hasWinTab = KisTabletSupportWin::init();
if (!hasWinTab && supportedWindowsVersion) {
if (KisTabletSupportWin8::isPenDeviceAvailable()) {
// Use WinInk automatically
cfg.setUseWin8PointerInput(true);
} else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) {
if (KisTabletSupportWin8::isAvailable()) {
QMessageBox::information(nullptr,
i18n("Krita Tablet Support"),
i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."),
QMessageBox::Ok, QMessageBox::Ok);
} else {
QMessageBox::information(nullptr,
i18n("Krita Tablet Support"),
i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."),
QMessageBox::Ok, QMessageBox::Ok);
}
cfg.writeEntry("WarnedAboutMissingWinTab", true);
}
}
}
if (cfg.useWin8PointerInput()) {
KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8();
if (penFilter->init()) {
// penFilter.registerPointerDeviceNotifications();
app.installNativeEventFilter(penFilter);
dbgKrita << "Using Win8 Pointer Input for tablet support";
} else {
dbgKrita << "No Win8 Pointer Input available";
delete penFilter;
}
}
}
#elif defined QT_HAS_WINTAB_SWITCH
// Check if WinTab/WinInk has actually activated
const bool useWinTabAPI = app.testAttribute(Qt::AA_MSWindowsUseWinTabAPI);
if (useWinTabAPI != !cfg.useWin8PointerInput()) {
cfg.setUseWin8PointerInput(useWinTabAPI);
}
#endif
#endif
app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false);
// 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)));
// Hardware information
- KisUsageLogger::write("\nHardware Information\n");
- KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString()));
- KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM()));
- KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount()));
- KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir()));
+ KisUsageLogger::writeSysInfo("\nHardware Information\n");
+ KisUsageLogger::writeSysInfo(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString()));
+ KisUsageLogger::writeSysInfo(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM()));
+ KisUsageLogger::writeSysInfo(QString(" Number of Cores: %1").arg(QThread::idealThreadCount()));
+ KisUsageLogger::writeSysInfo(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir()));
KisConfig(true).logImportantSettings();
if (!app.start(args)) {
KisUsageLogger::log("Could not start Krita Application");
return 1;
}
int state = app.exec();
{
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("canvasState", "OPENGL_SUCCESS");
}
if (logUsage) {
KisUsageLogger::close();
}
return state;
}
diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml
index c549db0adc..3535cf4b23 100644
--- a/krita/org.kde.krita.appdata.xml
+++ b/krita/org.kde.krita.appdata.xml
@@ -1,381 +1,389 @@
<?xml version="1.0" encoding="utf-8"?>
<component type="desktop">
<id>org.kde.krita</id>
<launchable type="desktop-id">org.kde.krita.desktop</launchable>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-only</project_license>
<developer_name>Krita Foundation</developer_name>
<developer_name xml:lang="ca">Fundació Krita</developer_name>
<developer_name xml:lang="ca-valencia">Fundació Krita</developer_name>
<developer_name xml:lang="cs">Krita Foundation</developer_name>
<developer_name xml:lang="de">Krita Foundation</developer_name>
<developer_name xml:lang="en-GB">Krita Foundation</developer_name>
<developer_name xml:lang="es">Fundación Krita</developer_name>
+ <developer_name xml:lang="et">Krita sihtasutus</developer_name>
<developer_name xml:lang="eu">Krita Fundazioa</developer_name>
<developer_name xml:lang="fi">Krita Foundation</developer_name>
<developer_name xml:lang="fr">La Fondation Krita</developer_name>
<developer_name xml:lang="gl">Fundación Krita</developer_name>
<developer_name xml:lang="id">Asas Krita</developer_name>
<developer_name xml:lang="it">Fondazione Krita</developer_name>
<developer_name xml:lang="ko">Krita Foundation</developer_name>
<developer_name xml:lang="nl">Krita Foundation</developer_name>
<developer_name xml:lang="nn">Krita Foundation</developer_name>
<developer_name xml:lang="pl">Fundacja Krity</developer_name>
<developer_name xml:lang="pt">Fundação do Krita</developer_name>
<developer_name xml:lang="pt-BR">Krita Foundation</developer_name>
<developer_name xml:lang="sk">Nadácia Krita</developer_name>
<developer_name xml:lang="sv">Krita-stiftelsen</developer_name>
<developer_name xml:lang="tr">Krita Vakfı</developer_name>
<developer_name xml:lang="uk">Фундація Krita</developer_name>
<developer_name xml:lang="x-test">xxKrita Foundationxx</developer_name>
<developer_name xml:lang="zh-CN">Krita 基金会</developer_name>
<developer_name xml:lang="zh-TW">Krita 基金會</developer_name>
<update_contact>foundation@krita.org</update_contact>
<name>Krita</name>
<name xml:lang="ar">كريتا</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="et">Krita</name>
<name xml:lang="eu">Krita</name>
<name xml:lang="fi">Krita</name>
<name xml:lang="fr">Krita</name>
<name xml:lang="gl">Krita</name>
<name xml:lang="id">Krita</name>
<name xml:lang="it">Krita</name>
<name xml:lang="ko">Krita</name>
<name xml:lang="nl">Krita</name>
<name xml:lang="nn">Krita</name>
<name xml:lang="pl">Krita</name>
<name xml:lang="pt">Krita</name>
<name xml:lang="pt-BR">Krita</name>
<name xml:lang="ru">Krita</name>
<name xml:lang="sk">Krita</name>
<name xml:lang="sv">Krita</name>
<name xml:lang="tr">Krita</name>
<name xml:lang="uk">Krita</name>
<name xml:lang="x-test">xxKritaxx</name>
<name xml:lang="zh-CN">Krita</name>
<name xml:lang="zh-TW">Krita</name>
<summary>Digital Painting, Creative Freedom</summary>
<summary xml:lang="ar">رسم رقميّ، حريّة إبداعيّة</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="eu">Margolan digitala, sormen askatasuna</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="id">Pelukisan Digital, Kebebasan Berkreatif</summary>
<summary xml:lang="it">Pittura digitale, libertà creativa</summary>
<summary xml:lang="ko">디지털 페인팅, 자유로운 창의성</summary>
<summary xml:lang="nl">Digital Painting, Creative Freedom</summary>
<summary xml:lang="nn">Digital teikning – kreativ fridom</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="tr">Sayısal Boyama, Yaratıcı Özgürlük</summary>
<summary xml:lang="uk">Цифрове малювання, творча свобода</summary>
<summary xml:lang="x-test">xxDigital Painting, Creative Freedomxx</summary>
<summary xml:lang="zh-CN">自由挥洒数字绘画的无限创意</summary>
<summary xml:lang="zh-TW">數位繪畫,創作自由</summary>
<description>
<p>Krita is the full-featured digital art studio.</p>
<p xml:lang="bs">Krita je potpuni digitalni umjetnički studio.</p>
<p xml:lang="ca">El Krita és l'estudi d'art digital ple de funcionalitats.</p>
<p xml:lang="ca-valencia">El 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="eu">Krita arte lantegi digital osoa da.</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="id">Krita adalah studio seni digital yang penuh dengan fitur.</p>
<p xml:lang="it">Krita è uno studio d'arte digitale completo.</p>
<p xml:lang="ja">Krita は、フル機能を備えたデジタルなアートスタジオです。</p>
<p xml:lang="ko">Krita는 디지털 예술 스튜디오입니다.</p>
<p xml:lang="nl">Krita is de digitale kunststudio vol mogelijkheden.</p>
<p xml:lang="nn">Krita er ei funksjonsrik digital teiknestove.</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="tr">Krita, tam özellikli dijital sanat stüdyosudur.</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-TW">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="eu">Zirriborratzeko eta margotzeko ezin hobea da, eta margolan digitalen fitxategiak hutsetik sortzeko muturretik-muturrera konponbide bat aurkezten du, maisuentzako mailakoa.</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="id">Ini adalah sempurna untuk mensketsa dan melukis, dan menghadirkan sebuah solusi untuk menciptakan file-file pelukisan digital dari goresan si pelukis ulung.</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="ko">스케치, 페인팅을 위한 완벽한 도구이며, 생각에서부터 디지털 페인팅 파일을 만들어 낼 수 있는 종합적인 도구를 제공합니다.</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="nn">Passar perfekt for både teikning og måling, og dekkjer alle ledd i prosessen med å laga digitale måleri frå grunnen av.</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="tr">Eskiz ve boyama için mükemmeldir ve ustaların sıfırdan dijital boyama dosyaları oluşturmak için uçtan-uca bir çözüm sunar.</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 xml:lang="zh-TW">它是素描和繪畫的完美選擇,並提供了一個從零開始建立數位繪畫檔的端到端解決方案。</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="eu">Krita aukera bikaina da kontzeptuzko artea, komikiak, errendatzeko ehundurak eta «matte» margolanak sortzeko. Kritak kolore-espazio ugari onartzen ditu hala nola GBU eta CMYK, 8 eta 16 biteko osoko kanaletan, baita 16 eta 32 biteko koma-higikorreko kanaletan.</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="id">Krita adalah pilihan yang cocok untuk menciptakan konsep seni, komik, tekstur untuk rendering dan lukisan matte. Krita mendukung banyak ruang warna seperti RGB dan CMYK pada channel integer 8 dan 16 bit, serta channel floating point 16 dan 32 bit.</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="ko">Krita는 컨셉 아트, 만화, 렌더링용 텍스처, 풍경화 등을 그릴 때 사용할 수 있는 완벽한 도구입니다. RGB, CMYK와 같은 여러 색 공간 및 8비트/16비트 정수 채널, 16비트/32비트 부동 소수점 채널을 지원합니다.</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="nn">Krita er det ideelle valet dersom du vil laga konseptskisser, teikneseriar, teksturar for 3D-rendering eller «matte paintings». Programmet støttar fleire fargerom, både RGB- og CMYK-baserte, med 8- og 16-bits heiltals- eller flyttalskanalar.</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="tr">Krita, konsept sanat, çizgi roman, kaplama ve mat resimler için dokular oluşturmak için mükemmel bir seçimdir. Krita, 8 ve 16 bit tamsayı kanallarında RGB ve CMYK gibi birçok renk alanını ve 16 ve 32 bit kayan nokta kanallarını desteklemektedir.</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 xml:lang="zh-TW">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 avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.</p>
<p xml:lang="ca-valencia">Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques ú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="eu">Marrazten ondo pasa ezazu, isipu motor aurreratuekin, iragazki txundigarriekin eta eginbide praktiko ugariekin, zeintzuek Krita ikaragarri emankorra egiten duten.</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="id">Bersenang-senanglah melukis dengan mesin kuas canggih, filter luar biasa dan banyak fitur berguna yang membuat Krita sangat produktif.</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="ko">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="nn">Leik deg med avanserte penselmotorar og fantastiske biletfilter – og mange andre nyttige funksjonar som gjer deg produktiv med Krita.</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="tr">Gelişmiş fırça motorları, şaşırtıcı filtreler ve Krita'yı son derece üretken yapan bir çok kullanışlı özellikli boya ile iyi eğlenceler.</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>
<p xml:lang="zh-TW">使用先進的筆刷引擎、驚人的濾鏡和許多方便的功能來開心地繪畫,讓 Krita 擁有巨大的生產力。</p>
</description>
<url type="homepage">https://www.krita.org/</url>
<url type="faq">https://docs.krita.org/KritaFAQ.html</url>
<url type="donation">https://krita.org/support-us/donations/</url>
<url type="help">https://docs.krita.org/</url>
<url type="bugtracker">https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html</url>
<screenshots>
<screenshot type="default">
<caption>Krita is a full-featured digital painting studio.</caption>
<caption xml:lang="ca">Krita és un estudi de pintura digital ple de funcionalitats.</caption>
<caption xml:lang="ca-valencia">Krita és un estudi de pintura digital ple de funcionalitats.</caption>
<caption xml:lang="de">Krita ist ein digitales Zeichenstudio mit umfangreichen Funktionen.</caption>
<caption xml:lang="en-GB">Krita is a full-featured digital painting studio.</caption>
<caption xml:lang="es">Krita es un completo estudio de dibujo digital.</caption>
+ <caption xml:lang="et">Krita on rohkete võimalustega digitaalkunstistuudio.</caption>
<caption xml:lang="gl">Krita é un estudio completo de debuxo dixital.</caption>
<caption xml:lang="id">Krita adalah studio pelukisan digital dengan fitur yang lengkap.</caption>
<caption xml:lang="it">Krita è uno studio d'arte digitale completo.</caption>
<caption xml:lang="ko">Krita는 다기능 디지털 예술 스튜디오입니다.</caption>
<caption xml:lang="nl">Krita is een digitale schilderstudio vol mogelijkheden.</caption>
<caption xml:lang="nn">Krita er ei funksjonsrik digital teiknestove.</caption>
<caption xml:lang="pl">Krita jest pełnowymiarowym, cyfrowym studiem artystycznym.</caption>
<caption xml:lang="pt">O Krita é um estúdio de arte digital completo.</caption>
<caption xml:lang="pt-BR">O Krita é um estúdio de pintura digital completo.</caption>
<caption xml:lang="sk">Krita je plnohodnotné digitálne maliarske štúdio.</caption>
<caption xml:lang="sv">Krita är en fullfjädrad digital konststudio.</caption>
<caption xml:lang="uk">Krita — повноцінний комплекс для цифрового малювання.</caption>
<caption xml:lang="x-test">xxKrita is a full-featured digital painting studio.xx</caption>
<caption xml:lang="zh-CN">Krita 是一款功能齐全的数字绘画工作室软件。</caption>
<caption xml:lang="zh-TW">Krita 是全功能的數位繪圖工作室。</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png</image>
</screenshot>
<screenshot type="default">
<caption>The startup window now also gives you the latest news about Krita.</caption>
<caption xml:lang="ca">La finestra d'inici ara proporciona les darreres notícies quant al Krita.</caption>
<caption xml:lang="ca-valencia">La finestra d'inici ara proporciona les darreres noticies quant al Krita.</caption>
<caption xml:lang="en-GB">The startup window now also gives you the latest news about Krita.</caption>
<caption xml:lang="es">La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita.</caption>
+ <caption xml:lang="et">Käivitusaken jagab nüüd ka Krita värskemaid uudiseid.</caption>
<caption xml:lang="gl">Agora a xanela de inicio tamén fornece as últimas novas sobre Krita.</caption>
<caption xml:lang="id">Window pemulaian kini juga memberikan kamu kabar terkini tentang Krita.</caption>
<caption xml:lang="it">La finestra di avvio ora fornisce anche le ultime novità su Krita.</caption>
<caption xml:lang="ko">시작 창에서 Krita의 최신 소식을 볼 수 있습니다.</caption>
<caption xml:lang="nl">Het opstartvenster geeft u nu ook you het laatste nieuws over Krita.</caption>
<caption xml:lang="nn">Oppstartsvindauget viser no siste nytt om Krita.</caption>
<caption xml:lang="pl">Okno początkowe teraz wyświetla wieści o Kricie.</caption>
<caption xml:lang="pt">A janela inicial agora também lhe dá as últimas notícias sobre o Krita.</caption>
<caption xml:lang="pt-BR">A janela de inicialização agora também mostra as últimas notícias sobre o Krita.</caption>
<caption xml:lang="sk">V úvodnom okne sa tiež nachádzajú najnovšie správy o Krita.</caption>
<caption xml:lang="sv">Startfönstret ger nu också senaste nytt om Krita.</caption>
<caption xml:lang="uk">У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita.</caption>
<caption xml:lang="x-test">xxThe startup window now also gives you the latest news about Krita.xx</caption>
<caption xml:lang="zh-CN">启动画面现在可以为你呈现与 Krita 有关的最新资讯。</caption>
<caption xml:lang="zh-TW">開始視窗也提供給您關於 Krita 的最新消息。</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png</image>
</screenshot>
<screenshot type="default">
<caption>There are over ten immensely powerful brush engines.</caption>
<caption xml:lang="ca">Hi ha uns deu motors de pinzell immensament potents.</caption>
<caption xml:lang="ca-valencia">Hi ha uns deu motors de pinzell immensament potents.</caption>
<caption xml:lang="en-GB">There are over ten immensely powerful brush engines.</caption>
<caption xml:lang="es">Existen unos diez inmensamente potentes motores de pinceles.</caption>
+ <caption xml:lang="et">Üle kümne ääretult võimeka pintslimootori.</caption>
<caption xml:lang="gl">Hai máis de dez motores de pinceis inmensamente potentes.</caption>
<caption xml:lang="id">Ada lebih dari sepuluh mesin kuas yang sangat manjur.</caption>
<caption xml:lang="it">Ci sono oltre dieci motori di pennelli incredibilmente potenti.</caption>
<caption xml:lang="ko">10가지 종류의 강력한 브러시 엔진을 사용할 수 있습니다.</caption>
<caption xml:lang="nl">Er zijn meer dan tien immens krachtige penseelengines.</caption>
<caption xml:lang="nn">Det finst meir enn ti enormt kraftige penselmotorar.</caption>
<caption xml:lang="pl">Istnieje ponad dziesięć zaawansowanych silników pędzli.</caption>
<caption xml:lang="pt">Existem mais de dez motores de pincéis extremamente poderosos.</caption>
<caption xml:lang="pt-BR">Mais de dez engines de pincéis incrivelmente poderosos disponíveis.</caption>
<caption xml:lang="sk">Existuje viac ako desať nesmierne výkonných štetcových enginov.</caption>
<caption xml:lang="sv">Det finns mer än tio enormt kraftfulla penselgränssnitt.</caption>
<caption xml:lang="uk">У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів.</caption>
<caption xml:lang="x-test">xxThere are over ten immensely powerful brush engines.xx</caption>
<caption xml:lang="zh-CN">它具备超过十种相当强大的笔刷引擎。</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png</image>
</screenshot>
<screenshot type="default">
<caption>Create and use gamut masks to give your images a coherent feel.</caption>
<caption xml:lang="ca">Creeu i useu màscares de gamma per donar a les imatges una aparença coherent.</caption>
<caption xml:lang="ca-valencia">Creeu i useu màscares de gamma per donar a les imatges una aparença coherent.</caption>
<caption xml:lang="en-GB">Create and use gamut masks to give your images a coherent feel.</caption>
<caption xml:lang="es">Cree y use gamas para proporcionar a sus imágenes un aspecto coherente.</caption>
+ <caption xml:lang="et">Värviulatuse maskide loomine ja kasutamine piltidele kooskõlalise välimuse andmiseks.</caption>
<caption xml:lang="gl">Crea e usa máscaras de gama para dar ás túas imaxes un aspecto coherente.</caption>
<caption xml:lang="id">Ciptakan dan gunakan masker gamut untuk memberikan gambarmu sebuah suasana koheren.</caption>
<caption xml:lang="it">Crea e utilizza maschere gamut per dare alle tue immagini un aspetto coerente.</caption>
<caption xml:lang="ko">색역 마스크를 만들고 사용할 수 있습니다.</caption>
<caption xml:lang="nl">Maak en gebruik gamut-maskers om uw afbeeldingen een coherent gevoel te geven.</caption>
<caption xml:lang="nn">Bruk fargeområde-masker for å gje bileta eit heilsleg uttrykk.</caption>
<caption xml:lang="pl">Stwórz i używaj masek gamut, aby nadać swoim obrazom spójny wygląd.</caption>
<caption xml:lang="pt">Crie e use máscaras de gamute para dar às suas imagens uma aparência coerente.</caption>
<caption xml:lang="pt-BR">Crie e use máscaras de gama para dar um senso de coerência às suas imagens.</caption>
<caption xml:lang="sk">Vytvorte a používajte gamutové masky, aby vašim obrázkom poskytli ucelený pocit.</caption>
<caption xml:lang="sv">Att skapa och använda färgomfångsmasker ger bilder en sammanhängande känsla.</caption>
<caption xml:lang="uk">Створюйте маски палітри і користуйтеся ними для надання вашим малюнкам однорідного вигляду.</caption>
<caption xml:lang="x-test">xxCreate and use gamut masks to give your images a coherent feel.xx</caption>
<caption xml:lang="zh-CN">创建并使用色域蒙版可以为你的图像带来更加一致的观感。</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png</image>
</screenshot>
<screenshot type="default">
<caption>Into animation? Krita provides everything you need for traditional, hand-drawn animation.</caption>
<caption xml:lang="ca">Esteu amb animacions? El Krita proporciona tot el que cal per a l'animació a mà tradicional.</caption>
<caption xml:lang="ca-valencia">Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional.</caption>
<caption xml:lang="en-GB">Into animation? Krita provides everything you need for traditional, hand-drawn animation.</caption>
<caption xml:lang="es">¿Trabaja con animación? Krita proporciona todo lo necesario para la animación manual tradicional.</caption>
+ <caption xml:lang="et">Sind huvitab animatsioon? Krita pakub kõike, mida läheb tarvis traditsioonilise käsitsi loodud animatsiooni jaoks.</caption>
<caption xml:lang="gl">Gusta das animacións? Krita fornece todo o necesario para animacións tradicionais debuxadas a man.</caption>
<caption xml:lang="id">Soal animasi? krita menyediakan apa pun yang kamu perlukan untuk animasi gambar-tangan, tradisional.</caption>
<caption xml:lang="it">Per le animazioni? Krita fornisce tutto ciò che ti server per l'animazione tradizionale, disegnata a mano.</caption>
<caption xml:lang="ko">애니메이션을 만들 계획이 있으신가요? Krita를 통해서 수작업 애니메이션을 작업할 수 있습니다.</caption>
<caption xml:lang="nl">Naar animatie? Krita biedt alles wat u nodig hebt voor traditionele, met de hand getekende animatie.</caption>
<caption xml:lang="nn">Interessert i animasjon? Krita har alt du treng for tradisjonelle, handteikna animasjonar.</caption>
<caption xml:lang="pl">Zajmujesz się animacjami? Krita zapewnia wszystko czego potrzebujesz do tworzenia tradycyjnych, ręcznie rysowanych animacji.</caption>
<caption xml:lang="pt">Gosta de animação? O Krita oferece tudo o que precisa para o desenho animado tradicional e desenhado à mão.</caption>
<caption xml:lang="pt-BR">Curte animação? O Krita fornece tudo necessário para você poder trabalhar com animação tradicional ou feita à mão.</caption>
<caption xml:lang="sk">Ste do animácie? Krita poskytuje všetko, čo potrebujete pre tradičné ručne kreslené animácie.</caption>
<caption xml:lang="sv">Gillar du animering? Krita tillhandahåller allt som behövs för traditionella, handritade animeringar.</caption>
<caption xml:lang="uk">Працюєте із анімацією? У Krita ви знайдете усе, що потрібно для створення традиційної, намальованої вручну анімації.</caption>
<caption xml:lang="x-test">xxInto animation? Krita provides everything you need for traditional, hand-drawn animation.xx</caption>
<caption xml:lang="zh-CN">喜欢制作动画吗?Krita 提供了制作传统手绘动画的全套工具。</caption>
<caption xml:lang="zh-TW">想做動畫?Krita 提供您在傳統、手繪動畫所需的任何東西。</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png</image>
</screenshot>
<screenshot type="default">
<caption>If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.</caption>
<caption xml:lang="ca">Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat.</caption>
<caption xml:lang="ca-valencia">Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat.</caption>
<caption xml:lang="en-GB">If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.</caption>
<caption xml:lang="es">Si está empezando con el dibujo digital o si quiere saber más sobre la posibilidades de Krita, dispone de un extenso y actualizado manual.</caption>
+ <caption xml:lang="et">Kui oled digitaalkunstis alles uustulnuk või tunned huvi Krita võimaluste vastu, on meil välja pakkuda mahukas ajakohane käsiraamat.</caption>
<caption xml:lang="gl">Se está a empezar co debuxo dixital, ou quere saber máis sobre as posibilidades de Krita, existe un manual exhaustivo e actualizado.</caption>
<caption xml:lang="id">Jika kamu baru dalam pelukisan digital, atau ingin mengetahui selebihnya tentang Krita, di situ ada manual yang update dan luas.</caption>
<caption xml:lang="it">Se sei nuovo del disegno digitale, o vuoi saperne di più sulle possibilità di Krita, è disponibile un manuale completo e aggiornato.</caption>
<caption xml:lang="ko">디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오.</caption>
<caption xml:lang="nl">Als u nieuw bent in digitaal schilderen of u wilt meer weten over de mogelijkheden van Krita, dan is er een uitgebreide, bijgewerkte handleiding.</caption>
<caption xml:lang="nn">Viss du er nybegynnar innan digital teikning, eller ønskjer å veta meir om kva som er mogleg med Krita, finst det ei omfattande og oppdatert brukarhandbok.</caption>
<caption xml:lang="pl">Jeśli cyfrowe malowanie to dla ciebie nowość, lub jeśli chcesz dowiedzieć się więcej o możliwościach Krity, to dostępny jest wyczerpująca i aktualna instrukcja obsługi.</caption>
<caption xml:lang="pt">Se é novo na pintura digital, ou deseja saber mais sobre as possibilidades do Krita, existe um manual extenso e actualizado.</caption>
<caption xml:lang="pt-BR">Se você for iniciante em pintura digital ou gostaria de saber mais sobre as possibilidades que o Krita oferece, há um extenso e atualizado manual para isso.</caption>
<caption xml:lang="sk">Ak ste v oblasti digitálnej maľby nováčikom alebo sa chcete dozvedieť viac o možnostiach programu Krita, existuje o tom rozsiahla a aktuálna príručka.</caption>
<caption xml:lang="sv">Om digital målning är nytt för dig, eller om du vill veta mer om Kritas möjligheter, finns en omfattande, aktuell handbok.</caption>
<caption xml:lang="uk">Якщо ви не маєте достатнього досвіду у цифровому малюванні або хочете дізнатися більше про можливості Krita, скористайтеся нашим докладним і актуальним підручником.</caption>
<caption xml:lang="x-test">xxIf you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.xx</caption>
<caption xml:lang="zh-CN">不管你是数字绘画的新手,还是想发现 Krita 更多的用法,你都可以在我们详尽并持续更新的使用手册中找到答案。</caption>
<image>https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png</image>
</screenshot>
</screenshots>
<categories>
<category>Graphics</category>
<category>2DGraphics</category>
<category>RasterGraphics</category>
</categories>
<project_group>KDE</project_group>
<provides>
<binary>krita</binary>
<id>org.kde.krita.desktop</id>
</provides>
<content_rating type="oars-1.1"/>
<releases>
<release date="2019-05-29" version="4.3.0.0"/>
</releases>
<custom>
<value key="KDE::windows_store">https://www.microsoft.com/store/apps/9n6x57zgrw96</value>
</custom>
</component>
diff --git a/libs/basicflakes/tools/KoCreatePathTool.cpp b/libs/basicflakes/tools/KoCreatePathTool.cpp
index 4c8fa65b07..5e751ef1d7 100644
--- a/libs/basicflakes/tools/KoCreatePathTool.cpp
+++ b/libs/basicflakes/tools/KoCreatePathTool.cpp
@@ -1,584 +1,586 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2008-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 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 "KoCreatePathTool.h"
#include "KoCreatePathTool_p.h"
#include <KoUnit.h>
#include "KoPointerEvent.h"
#include "KoPathShape.h"
#include "KoSelection.h"
#include "KoDocumentResourceManager.h"
#include "KoShapePaintingContext.h"
#include "KoShapeStroke.h"
#include "KoCanvasBase.h"
#include "kis_int_parse_spin_box.h"
#include <KoColor.h>
#include "kis_canvas_resource_provider.h"
#include <KisHandlePainterHelper.h>
#include "KoPathPointTypeCommand.h"
#include <klocalizedstring.h>
#include <QSpinBox>
#include <QPainter>
#include <QLabel>
#include <QGridLayout>
#include <QCheckBox>
KoCreatePathTool::KoCreatePathTool(KoCanvasBase *canvas)
: KoToolBase(*(new KoCreatePathToolPrivate(this, canvas)))
{
}
KoCreatePathTool::~KoCreatePathTool()
{
}
void KoCreatePathTool::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_D(KoCreatePathTool);
if (pathStarted()) {
painter.save();
paintPath(*(d->shape), painter, converter);
painter.restore();
KisHandlePainterHelper helper =
- KoShape::createHandlePainterHelper(&painter, d->shape, converter, d->handleRadius);
+ KoShape::createHandlePainterHelperView(&painter, d->shape, converter, d->handleRadius);
const bool firstPointActive = d->firstPoint == d->activePoint;
if (d->pointIsDragged || firstPointActive) {
const bool onlyPaintActivePoints = false;
KoPathPoint::PointTypes paintFlags = KoPathPoint::ControlPoint2;
if (d->activePoint->activeControlPoint1()) {
paintFlags |= KoPathPoint::ControlPoint1;
}
helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
d->activePoint->paint(helper, paintFlags, onlyPaintActivePoints);
}
if (!firstPointActive) {
helper.setHandleStyle(d->mouseOverFirstPoint ?
KisHandleStyle::highlightedPrimaryHandles() :
KisHandleStyle::primarySelection());
d->firstPoint->paint(helper, KoPathPoint::Node);
}
}
if (d->hoveredPoint) {
- KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, d->hoveredPoint->parent(), converter, d->handleRadius);
+ KisHandlePainterHelper helper = KoShape::createHandlePainterHelperView(&painter, d->hoveredPoint->parent(), converter, d->handleRadius);
helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
d->hoveredPoint->paint(helper, KoPathPoint::Node);
}
painter.save();
- KoShape::applyConversion(painter, converter);
+ painter.setTransform(converter.documentToView());
canvas()->snapGuide()->paint(painter, converter);
painter.restore();
}
void KoCreatePathTool::paintPath(KoPathShape& pathShape, QPainter &painter, const KoViewConverter &converter)
{
Q_D(KoCreatePathTool);
- painter.setTransform(pathShape.absoluteTransformation(&converter) * painter.transform());
+ painter.setTransform(pathShape.absoluteTransformation() *
+ converter.documentToView() *
+ painter.transform());
painter.save();
KoShapePaintingContext paintContext; //FIXME
- pathShape.paint(painter, converter, paintContext);
+ pathShape.paint(painter, paintContext);
painter.restore();
if (pathShape.stroke()) {
painter.save();
- pathShape.stroke()->paint(d->shape, painter, converter);
+ pathShape.stroke()->paint(d->shape, painter);
painter.restore();
}
}
void KoCreatePathTool::mousePressEvent(KoPointerEvent *event)
{
Q_D(KoCreatePathTool);
//Right click removes last point
if (event->button() == Qt::RightButton) {
removeLastPoint();
return;
}
const bool isOverFirstPoint = d->shape &&
handleGrabRect(d->firstPoint->point()).contains(event->point);
const bool haveCloseModifier = d->enableClosePathShortcut
&& d->shape
&& d->shape->pointCount() > 2
&& (event->modifiers() & Qt::ShiftModifier);
if ((event->button() == Qt::LeftButton) && haveCloseModifier && !isOverFirstPoint) {
endPathWithoutLastPoint();
return;
}
d->finishAfterThisPoint = false;
if (d->shape && pathStarted()) {
if (isOverFirstPoint) {
d->activePoint->setPoint(d->firstPoint->point());
canvas()->updateCanvas(d->shape->boundingRect());
canvas()->updateCanvas(canvas()->snapGuide()->boundingRect());
if (haveCloseModifier) {
d->shape->closeMerge();
// we are closing the path, so reset the existing start path point
d->existingStartPoint = 0;
// finish path
endPath();
} else {
// the path shape will get closed when the user releases
// the mouse button
d->finishAfterThisPoint = true;
}
} else {
canvas()->updateCanvas(canvas()->snapGuide()->boundingRect());
QPointF point = canvas()->snapGuide()->snap(event->point, event->modifiers());
// check whether we hit an start/end node of an existing path
d->existingEndPoint = d->endPointAtPosition(point);
if (d->existingEndPoint.isValid() && d->existingEndPoint != d->existingStartPoint) {
point = d->existingEndPoint.path->shapeToDocument(d->existingEndPoint.point->point());
d->activePoint->setPoint(point);
// finish path
endPath();
} else {
d->activePoint->setPoint(point);
canvas()->updateCanvas(d->shape->boundingRect());
canvas()->updateCanvas(canvas()->snapGuide()->boundingRect());
}
}
} else {
KoPathShape *pathShape = new KoPathShape();
d->shape = pathShape;
pathShape->setShapeId(KoPathShapeId);
KoShapeStrokeSP stroke(new KoShapeStroke());
const qreal size = canvas()->resourceManager()->resource(KisCanvasResourceProvider::Size).toReal();
stroke->setLineWidth(canvas()->unit().fromUserValue(size));
stroke->setColor(canvas()->resourceManager()->foregroundColor().toQColor());
pathShape->setStroke(stroke);
canvas()->updateCanvas(canvas()->snapGuide()->boundingRect());
QPointF point = canvas()->snapGuide()->snap(event->point, event->modifiers());
// check whether we hit an start/end node of an existing path
d->existingStartPoint = d->endPointAtPosition(point);
if (d->existingStartPoint.isValid()) {
point = d->existingStartPoint.path->shapeToDocument(d->existingStartPoint.point->point());
}
d->activePoint = pathShape->moveTo(point);
d->firstPoint = d->activePoint;
canvas()->updateCanvas(handlePaintRect(point));
canvas()->updateCanvas(canvas()->snapGuide()->boundingRect());
canvas()->snapGuide()->setAdditionalEditedShape(pathShape);
d->angleSnapStrategy = new AngleSnapStrategy(d->angleSnappingDelta, d->angleSnapStatus);
canvas()->snapGuide()->addCustomSnapStrategy(d->angleSnapStrategy);
}
d->dragStartPoint = event->point;
if (d->angleSnapStrategy)
d->angleSnapStrategy->setStartPoint(d->activePoint->point());
}
bool KoCreatePathTool::pathStarted()
{
Q_D(KoCreatePathTool);
return ((bool) d->shape);
}
bool KoCreatePathTool::tryMergeInPathShape(KoPathShape *pathShape)
{
return addPathShapeImpl(pathShape, true);
}
void KoCreatePathTool::setEnableClosePathShortcut(bool value)
{
Q_D(KoCreatePathTool);
d->enableClosePathShortcut = value;
}
void KoCreatePathTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
//remove handle
canvas()->updateCanvas(handlePaintRect(event->point));
endPathWithoutLastPoint();
}
void KoCreatePathTool::mouseMoveEvent(KoPointerEvent *event)
{
Q_D(KoCreatePathTool);
KoPathPoint *endPoint = d->endPointAtPosition(event->point);
if (d->hoveredPoint != endPoint) {
if (d->hoveredPoint) {
QPointF nodePos = d->hoveredPoint->parent()->shapeToDocument(d->hoveredPoint->point());
canvas()->updateCanvas(handlePaintRect(nodePos));
}
d->hoveredPoint = endPoint;
if (d->hoveredPoint) {
QPointF nodePos = d->hoveredPoint->parent()->shapeToDocument(d->hoveredPoint->point());
canvas()->updateCanvas(handlePaintRect(nodePos));
}
}
if (!pathStarted()) {
canvas()->updateCanvas(canvas()->snapGuide()->boundingRect());
canvas()->snapGuide()->snap(event->point, event->modifiers());
canvas()->updateCanvas(canvas()->snapGuide()->boundingRect());
d->mouseOverFirstPoint = false;
return;
}
d->mouseOverFirstPoint = handleGrabRect(d->firstPoint->point()).contains(event->point);
canvas()->updateCanvas(d->shape->boundingRect());
canvas()->updateCanvas(canvas()->snapGuide()->boundingRect());
QPointF snappedPosition = canvas()->snapGuide()->snap(event->point, event->modifiers());
d->repaintActivePoint();
if (event->buttons() & Qt::LeftButton) {
if (d->pointIsDragged ||
!handleGrabRect(d->dragStartPoint).contains(event->point)) {
d->pointIsDragged = true;
QPointF offset = snappedPosition - d->activePoint->point();
d->activePoint->setControlPoint2(d->activePoint->point() + offset);
// pressing <alt> stops controls points moving symmetrically
if ((event->modifiers() & Qt::AltModifier) == 0) {
d->activePoint->setControlPoint1(d->activePoint->point() - offset);
}
d->repaintActivePoint();
}
} else {
d->activePoint->setPoint(snappedPosition);
if (!d->prevPointWasDragged && d->autoSmoothCurves) {
KoPathPointIndex index = d->shape->pathPointIndex(d->activePoint);
if (index.second > 0) {
KoPathPointIndex prevIndex(index.first, index.second - 1);
KoPathPoint *prevPoint = d->shape->pointByIndex(prevIndex);
if (prevPoint) {
KoPathPoint *prevPrevPoint = 0;
if (index.second > 1) {
KoPathPointIndex prevPrevIndex(index.first, index.second - 2);
prevPrevPoint = d->shape->pointByIndex(prevPrevIndex);
}
if (prevPrevPoint) {
const QPointF control1 = prevPoint->point() + 0.3 * (prevPrevPoint->point() - prevPoint->point());
prevPoint->setControlPoint1(control1);
}
const QPointF control2 = prevPoint->point() + 0.3 * (d->activePoint->point() - prevPoint->point());
prevPoint->setControlPoint2(control2);
const QPointF activeControl = d->activePoint->point() + 0.3 * (prevPoint->point() - d->activePoint->point());
d->activePoint->setControlPoint1(activeControl);
KoPathPointTypeCommand::makeCubicPointSmooth(prevPoint);
}
}
}
}
canvas()->updateCanvas(d->shape->boundingRect());
canvas()->updateCanvas(canvas()->snapGuide()->boundingRect());
}
void KoCreatePathTool::mouseReleaseEvent(KoPointerEvent *event)
{
Q_D(KoCreatePathTool);
if (! d->shape || (event->buttons() & Qt::RightButton)) return;
d->repaintActivePoint();
d->prevPointWasDragged = d->pointIsDragged;
d->pointIsDragged = false;
KoPathPoint *lastActivePoint = d->activePoint;
if (!d->finishAfterThisPoint) {
d->activePoint = d->shape->lineTo(event->point);
canvas()->snapGuide()->setIgnoredPathPoints((QList<KoPathPoint*>() << d->activePoint));
}
// apply symmetric point property if applicable
if (lastActivePoint->activeControlPoint1() && lastActivePoint->activeControlPoint2()) {
QPointF diff1 = lastActivePoint->point() - lastActivePoint->controlPoint1();
QPointF diff2 = lastActivePoint->controlPoint2() - lastActivePoint->point();
if (qFuzzyCompare(diff1.x(), diff2.x()) && qFuzzyCompare(diff1.y(), diff2.y()))
lastActivePoint->setProperty(KoPathPoint::IsSymmetric);
}
if (d->finishAfterThisPoint) {
d->firstPoint->setControlPoint1(d->activePoint->controlPoint1());
delete d->shape->removePoint(d->shape->pathPointIndex(d->activePoint));
d->activePoint = d->firstPoint;
if (!d->prevPointWasDragged && d->autoSmoothCurves) {
KoPathPointTypeCommand::makeCubicPointSmooth(d->activePoint);
}
d->shape->closeMerge();
// we are closing the path, so reset the existing start path point
d->existingStartPoint = 0;
// finish path
endPath();
}
if (d->angleSnapStrategy && lastActivePoint->activeControlPoint2()) {
d->angleSnapStrategy->deactivate();
}
}
void KoCreatePathTool::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
emit done();
} else {
event->ignore();
}
}
void KoCreatePathTool::endPath()
{
Q_D(KoCreatePathTool);
d->addPathShape();
}
void KoCreatePathTool::endPathWithoutLastPoint()
{
Q_D(KoCreatePathTool);
if (d->shape) {
QRectF dirtyRect = d->shape->boundingRect();
delete d->shape->removePoint(d->shape->pathPointIndex(d->activePoint));
canvas()->updateCanvas(dirtyRect);
d->addPathShape();
}
}
void KoCreatePathTool::cancelPath()
{
Q_D(KoCreatePathTool);
if (d->shape) {
canvas()->updateCanvas(handlePaintRect(d->firstPoint->point()));
canvas()->updateCanvas(d->shape->boundingRect());
d->firstPoint = 0;
d->activePoint = 0;
}
d->cleanUp();
}
void KoCreatePathTool::removeLastPoint()
{
Q_D(KoCreatePathTool);
if ((d->shape)) {
KoPathPointIndex lastPointIndex = d->shape->pathPointIndex(d->activePoint);
if (lastPointIndex.second > 1) {
lastPointIndex.second--;
delete d->shape->removePoint(lastPointIndex);
d->hoveredPoint = 0;
d->repaintActivePoint();
canvas()->updateCanvas(d->shape->boundingRect());
}
}
}
void KoCreatePathTool::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
KoToolBase::activate(activation, shapes);
Q_D(KoCreatePathTool);
useCursor(Qt::ArrowCursor);
// retrieve the actual global handle radius
d->handleRadius = handleRadius();
d->loadAutoSmoothValueFromConfig();
// reset snap guide
canvas()->updateCanvas(canvas()->snapGuide()->boundingRect());
canvas()->snapGuide()->reset();
}
void KoCreatePathTool::deactivate()
{
cancelPath();
KoToolBase::deactivate();
}
void KoCreatePathTool::documentResourceChanged(int key, const QVariant & res)
{
Q_D(KoCreatePathTool);
switch (key) {
case KoDocumentResourceManager::HandleRadius: {
d->handleRadius = res.toUInt();
}
break;
default:
return;
}
}
bool KoCreatePathTool::addPathShapeImpl(KoPathShape *pathShape, bool tryMergeOnly)
{
Q_D(KoCreatePathTool);
KoPathShape *startShape = 0;
KoPathShape *endShape = 0;
pathShape->normalize();
// check if existing start/end points are still valid
d->existingStartPoint.validate(canvas());
d->existingEndPoint.validate(canvas());
if (d->connectPaths(pathShape, d->existingStartPoint, d->existingEndPoint)) {
if (d->existingStartPoint.isValid()) {
startShape = d->existingStartPoint.path;
}
if (d->existingEndPoint.isValid() && d->existingEndPoint != d->existingStartPoint) {
endShape = d->existingEndPoint.path;
}
}
if (tryMergeOnly && !startShape && !endShape) {
return false;
}
KUndo2Command *cmd = canvas()->shapeController()->addShape(pathShape, 0);
KIS_SAFE_ASSERT_RECOVER(cmd) {
canvas()->updateCanvas(pathShape->boundingRect());
delete pathShape;
return true;
}
KoSelection *selection = canvas()->shapeManager()->selection();
selection->deselectAll();
selection->select(pathShape);
if (startShape) {
pathShape->setBackground(startShape->background());
pathShape->setStroke(startShape->stroke());
} else if (endShape) {
pathShape->setBackground(endShape->background());
pathShape->setStroke(endShape->stroke());
}
if (startShape) {
canvas()->shapeController()->removeShape(startShape, cmd);
}
if (endShape && startShape != endShape) {
canvas()->shapeController()->removeShape(endShape, cmd);
}
canvas()->addCommand(cmd);
return true;
}
void KoCreatePathTool::addPathShape(KoPathShape *pathShape)
{
addPathShapeImpl(pathShape, false);
}
QList<QPointer<QWidget> > KoCreatePathTool::createOptionWidgets()
{
Q_D(KoCreatePathTool);
QList<QPointer<QWidget> > list;
QCheckBox *smoothCurves = new QCheckBox(i18n("Autosmooth curve"));
smoothCurves->setObjectName("smooth-curves-widget");
smoothCurves->setChecked(d->autoSmoothCurves);
connect(smoothCurves, SIGNAL(toggled(bool)), this, SLOT(autoSmoothCurvesChanged(bool)));
connect(this, SIGNAL(sigUpdateAutoSmoothCurvesGUI(bool)), smoothCurves, SLOT(setChecked(bool)));
list.append(smoothCurves);
QWidget *angleWidget = new QWidget();
angleWidget->setObjectName("Angle Constraints");
QGridLayout *layout = new QGridLayout(angleWidget);
layout->addWidget(new QLabel(i18n("Angle snapping delta:"), angleWidget), 0, 0);
QSpinBox *angleEdit = new KisIntParseSpinBox(angleWidget);
angleEdit->setValue(d->angleSnappingDelta);
angleEdit->setRange(1, 360);
angleEdit->setSingleStep(1);
angleEdit->setSuffix(QChar(Qt::Key_degree));
layout->addWidget(angleEdit, 0, 1);
layout->addWidget(new QLabel(i18n("Activate angle snap:"), angleWidget), 1, 0);
QCheckBox *angleSnap = new QCheckBox(angleWidget);
angleSnap->setChecked(false);
angleSnap->setCheckable(true);
layout->addWidget(angleSnap, 1, 1);
QWidget *specialSpacer = new QWidget();
specialSpacer->setObjectName("SpecialSpacer");
layout->addWidget(specialSpacer, 2, 1);
angleWidget->setWindowTitle(i18n("Angle Constraints"));
list.append(angleWidget);
connect(angleEdit, SIGNAL(valueChanged(int)), this, SLOT(angleDeltaChanged(int)));
connect(angleSnap, SIGNAL(stateChanged(int)), this, SLOT(angleSnapChanged(int)));
return list;
}
//have to include this because of Q_PRIVATE_SLOT
#include <moc_KoCreatePathTool.cpp>
diff --git a/libs/basicflakes/tools/KoPencilTool.cpp b/libs/basicflakes/tools/KoPencilTool.cpp
index 7ea130f025..5d9f38e344 100644
--- a/libs/basicflakes/tools/KoPencilTool.cpp
+++ b/libs/basicflakes/tools/KoPencilTool.cpp
@@ -1,588 +1,590 @@
/* This file is part of the KDE project
* Copyright (C) 2007,2009,2011 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 "KoPencilTool.h"
#include "KoCurveFit.h"
#include <KoPathShape.h>
#include <KoParameterShape.h>
#include <KoShapeStroke.h>
#include <KoPointerEvent.h>
#include <KoCanvasBase.h>
#include <KoShapeController.h>
#include <KoShapeManager.h>
#include <KoSelection.h>
#include <KoCanvasResourceProvider.h>
#include <KoColor.h>
#include <KoPathPoint.h>
#include <KoPathPointData.h>
#include <KoPathPointMergeCommand.h>
#include <KoShapePaintingContext.h>
#include <widgets/KoStrokeConfigWidget.h>
#include <KisHandlePainterHelper.h>
#include <klocalizedstring.h>
#include <QDoubleSpinBox>
#include <QComboBox>
#include <QStackedWidget>
#include <QGroupBox>
#include <QCheckBox>
#include <QVBoxLayout>
#include <QPainter>
#include <QLabel>
#include <math.h>
#include "KoCreatePathTool_p.h"
#include "kis_double_parse_spin_box.h"
KoPencilTool::KoPencilTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_mode(ModeCurve)
, m_optimizeRaw(false)
, m_optimizeCurve(false)
, m_combineAngle(15.0)
, m_fittingError(5.0)
, m_close(false)
, m_shape(0)
, m_existingStartPoint(0)
, m_existingEndPoint(0)
, m_hoveredPoint(0)
, m_strokeWidget(0)
{
}
KoPencilTool::~KoPencilTool()
{
}
void KoPencilTool::paint(QPainter &painter, const KoViewConverter &converter)
{
if (m_shape) {
painter.save();
- painter.setTransform(m_shape->absoluteTransformation(&converter) * painter.transform());
+ painter.setTransform(m_shape->absoluteTransformation() *
+ converter.documentToView() *
+ painter.transform());
painter.save();
KoShapePaintingContext paintContext; //FIXME
- m_shape->paint(painter, converter, paintContext);
+ m_shape->paint(painter, paintContext);
painter.restore();
if (m_shape->stroke()) {
painter.save();
- m_shape->stroke()->paint(m_shape, painter, converter);
+ m_shape->stroke()->paint(m_shape, painter);
painter.restore();
}
painter.restore();
}
if (m_hoveredPoint) {
KisHandlePainterHelper helper =
- KoShape::createHandlePainterHelper(&painter, m_hoveredPoint->parent(), converter, handleRadius());
+ KoShape::createHandlePainterHelperView(&painter, m_hoveredPoint->parent(), converter, handleRadius());
helper.setHandleStyle(KisHandleStyle::primarySelection());
m_hoveredPoint->paint(helper, KoPathPoint::Node);
}
}
void KoPencilTool::repaintDecorations()
{
}
void KoPencilTool::mousePressEvent(KoPointerEvent *event)
{
KoShapeStrokeSP stroke = createStroke();
if (!m_shape && stroke && stroke->isVisible()) {
m_shape = new KoPathShape();
m_shape->setShapeId(KoPathShapeId);
m_shape->setStroke(createStroke());
m_points.clear();
QPointF point = event->point;
m_existingStartPoint = endPointAtPosition(point);
if (m_existingStartPoint)
point = m_existingStartPoint->parent()->shapeToDocument(m_existingStartPoint->point());
addPoint(point);
}
}
void KoPencilTool::mouseMoveEvent(KoPointerEvent *event)
{
if (event->buttons() & Qt::LeftButton)
addPoint(event->point);
KoPathPoint * endPoint = endPointAtPosition(event->point);
if (m_hoveredPoint != endPoint) {
if (m_hoveredPoint) {
QPointF nodePos = m_hoveredPoint->parent()->shapeToDocument(m_hoveredPoint->point());
canvas()->updateCanvas(handlePaintRect(nodePos));
}
m_hoveredPoint = endPoint;
if (m_hoveredPoint) {
QPointF nodePos = m_hoveredPoint->parent()->shapeToDocument(m_hoveredPoint->point());
canvas()->updateCanvas(handlePaintRect(nodePos));
}
}
}
void KoPencilTool::mouseReleaseEvent(KoPointerEvent *event)
{
if (! m_shape)
return;
QPointF point = event->point;
m_existingEndPoint = endPointAtPosition(point);
if (m_existingEndPoint)
point = m_existingEndPoint->parent()->shapeToDocument(m_existingEndPoint->point());
addPoint(point);
finish(event->modifiers() & Qt::ShiftModifier);
m_existingStartPoint = 0;
m_existingEndPoint = 0;
m_hoveredPoint = 0;
// the original path may be different from the one added
if (canvas() && m_shape) {
canvas()->updateCanvas(m_shape->boundingRect());
}
delete m_shape;
m_shape = 0;
m_points.clear();
}
void KoPencilTool::keyPressEvent(QKeyEvent *event)
{
if (m_shape) {
event->accept();
} else {
event->ignore();
}
}
void KoPencilTool::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
KoToolBase::activate(activation, shapes);
m_points.clear();
m_close = false;
slotUpdatePencilCursor();
if (m_strokeWidget) {
m_strokeWidget->activate();
}
}
void KoPencilTool::deactivate()
{
m_points.clear();
delete m_shape;
m_shape = 0;
m_existingStartPoint = 0;
m_existingEndPoint = 0;
m_hoveredPoint = 0;
if (m_strokeWidget) {
m_strokeWidget->deactivate();
}
KoToolBase::deactivate();
}
void KoPencilTool::slotUpdatePencilCursor()
{
KoShapeStrokeSP stroke = createStroke();
useCursor((stroke && stroke->isVisible()) ? Qt::ArrowCursor : Qt::ForbiddenCursor);
}
void KoPencilTool::addPoint(const QPointF & point)
{
if (! m_shape)
return;
// do a moveTo for the first point added
if (m_points.empty())
m_shape->moveTo(point);
// do not allow coincident points
else if (point != m_points.last())
m_shape->lineTo(point);
else
return;
m_points.append(point);
canvas()->updateCanvas(m_shape->boundingRect());
}
qreal KoPencilTool::lineAngle(const QPointF &p1, const QPointF &p2)
{
qreal angle = atan2(p2.y() - p1.y(), p2.x() - p1.x());
if (angle < 0.0)
angle += 2 * M_PI;
return angle * 180.0 / M_PI;
}
void KoPencilTool::finish(bool closePath)
{
if (m_points.count() < 2)
return;
KoPathShape * path = 0;
QList<QPointF> complete;
QList<QPointF> *points = &m_points;
if (m_mode == ModeStraight || m_optimizeRaw || m_optimizeCurve) {
float combineAngle;
if (m_mode == ModeStraight)
combineAngle = m_combineAngle;
else
combineAngle = 0.50f;
//Add the first two points
complete.append(m_points[0]);
complete.append(m_points[1]);
//Now we need to get the angle of the first line
float lastAngle = lineAngle(complete[0], complete[1]);
uint pointCount = m_points.count();
for (uint i = 2; i < pointCount; ++i) {
float angle = lineAngle(complete.last(), m_points[i]);
if (qAbs(angle - lastAngle) < combineAngle)
complete.removeLast();
complete.append(m_points[i]);
lastAngle = angle;
}
m_points.clear();
points = &complete;
}
switch (m_mode) {
case ModeCurve: {
path = bezierFit(*points, m_fittingError);
}
break;
case ModeStraight:
case ModeRaw: {
path = new KoPathShape();
uint pointCount = points->count();
path->moveTo(points->at(0));
for (uint i = 1; i < pointCount; ++i)
path->lineTo(points->at(i));
}
break;
}
if (! path)
return;
path->setShapeId(KoPathShapeId);
path->setStroke(createStroke());
addPathShape(path, closePath);
}
QList<QPointer<QWidget> > KoPencilTool::createOptionWidgets()
{
QList<QPointer<QWidget> > widgets;
QWidget *optionWidget = new QWidget();
QVBoxLayout * layout = new QVBoxLayout(optionWidget);
QHBoxLayout *modeLayout = new QHBoxLayout;
modeLayout->setSpacing(3);
QLabel *modeLabel = new QLabel(i18n("Precision:"), optionWidget);
QComboBox * modeBox = new QComboBox(optionWidget);
modeBox->addItem(i18nc("The raw line data", "Raw"));
modeBox->addItem(i18n("Curve"));
modeBox->addItem(i18n("Straight"));
modeLayout->addWidget(modeLabel);
modeLayout->addWidget(modeBox, 1);
layout->addLayout(modeLayout);
QStackedWidget * stackedWidget = new QStackedWidget(optionWidget);
QWidget * rawBox = new QWidget(stackedWidget);
QVBoxLayout * rawLayout = new QVBoxLayout(rawBox);
QCheckBox * optimizeRaw = new QCheckBox(i18n("Optimize"), rawBox);
rawLayout->addWidget(optimizeRaw);
rawLayout->setContentsMargins(0, 0, 0, 0);
QWidget * curveBox = new QWidget(stackedWidget);
QHBoxLayout * curveLayout = new QHBoxLayout(curveBox);
QCheckBox * optimizeCurve = new QCheckBox(i18n("Optimize"), curveBox);
QDoubleSpinBox * fittingError = new KisDoubleParseSpinBox(curveBox);
fittingError->setValue(0.50);
fittingError->setMaximum(400.0);
fittingError->setMinimum(0.0);
fittingError->setSingleStep(m_fittingError);
fittingError->setToolTip(i18n("Exactness:"));
curveLayout->addWidget(optimizeCurve);
curveLayout->addWidget(fittingError);
curveLayout->setContentsMargins(0, 0, 0, 0);
QWidget *straightBox = new QWidget(stackedWidget);
QVBoxLayout *straightLayout = new QVBoxLayout(straightBox);
QDoubleSpinBox *combineAngle = new KisDoubleParseSpinBox(straightBox);
combineAngle->setValue(0.50);
combineAngle->setMaximum(360.0);
combineAngle->setMinimum(0.0);
combineAngle->setSingleStep(m_combineAngle);
combineAngle->setSuffix(" deg");
// QT5TODO
//combineAngle->setLabel(i18n("Combine angle:"), Qt::AlignLeft | Qt::AlignVCenter);
straightLayout->addWidget(combineAngle);
straightLayout->setContentsMargins(0, 0, 0, 0);
stackedWidget->addWidget(rawBox);
stackedWidget->addWidget(curveBox);
stackedWidget->addWidget(straightBox);
layout->addWidget(stackedWidget);
layout->addStretch(1);
connect(modeBox, SIGNAL(activated(int)), stackedWidget, SLOT(setCurrentIndex(int)));
connect(modeBox, SIGNAL(activated(int)), this, SLOT(selectMode(int)));
connect(optimizeRaw, SIGNAL(stateChanged(int)), this, SLOT(setOptimize(int)));
connect(optimizeCurve, SIGNAL(stateChanged(int)), this, SLOT(setOptimize(int)));
connect(fittingError, SIGNAL(valueChanged(double)), this, SLOT(setDelta(double)));
connect(combineAngle, SIGNAL(valueChanged(double)), this, SLOT(setDelta(double)));
modeBox->setCurrentIndex(m_mode);
stackedWidget->setCurrentIndex(m_mode);
optionWidget->setObjectName(i18n("Pencil"));
optionWidget->setWindowTitle(i18n("Pencil"));
widgets.append(optionWidget);
m_strokeWidget = new KoStrokeConfigWidget(canvas(), 0);
m_strokeWidget->setNoSelectionTrackingMode(true);
m_strokeWidget->setWindowTitle(i18n("Line"));
connect(m_strokeWidget, SIGNAL(sigStrokeChanged()), SLOT(slotUpdatePencilCursor()));
if (isActivated()) {
m_strokeWidget->activate();
}
widgets.append(m_strokeWidget);
return widgets;
}
void KoPencilTool::addPathShape(KoPathShape* path, bool closePath)
{
KoShape * startShape = 0;
KoShape * endShape = 0;
if (closePath) {
path->close();
path->normalize();
} else {
path->normalize();
if (connectPaths(path, m_existingStartPoint, m_existingEndPoint)) {
if (m_existingStartPoint)
startShape = m_existingStartPoint->parent();
if (m_existingEndPoint && m_existingEndPoint != m_existingStartPoint)
endShape = m_existingEndPoint->parent();
}
}
KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0);
if (cmd) {
KoSelection *selection = canvas()->shapeManager()->selection();
selection->deselectAll();
selection->select(path);
if (startShape)
canvas()->shapeController()->removeShape(startShape, cmd);
if (endShape && startShape != endShape)
canvas()->shapeController()->removeShape(endShape, cmd);
canvas()->addCommand(cmd);
} else {
canvas()->updateCanvas(path->boundingRect());
delete path;
}
}
void KoPencilTool::selectMode(int mode)
{
m_mode = static_cast<PencilMode>(mode);
}
void KoPencilTool::setOptimize(int state)
{
if (m_mode == ModeRaw)
m_optimizeRaw = state == Qt::Checked ? true : false;
else
m_optimizeCurve = state == Qt::Checked ? true : false;
}
void KoPencilTool::setDelta(double delta)
{
if (m_mode == ModeCurve)
m_fittingError = delta;
else if (m_mode == ModeStraight)
m_combineAngle = delta;
}
KoShapeStrokeSP KoPencilTool::createStroke()
{
KoShapeStrokeSP stroke;
if (m_strokeWidget) {
stroke = m_strokeWidget->createShapeStroke();
}
return stroke;
}
KoPathShape * KoPencilTool::path()
{
return m_shape;
}
KoPathPoint* KoPencilTool::endPointAtPosition(const QPointF &position)
{
QRectF roi = handleGrabRect(position);
QList<KoShape *> shapes = canvas()->shapeManager()->shapesAt(roi);
KoPathPoint * nearestPoint = 0;
qreal minDistance = HUGE_VAL;
qreal maxDistance = canvas()->viewConverter()->viewToDocumentX(grabSensitivity());
Q_FOREACH(KoShape * shape, shapes) {
KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
if (!path)
continue;
KoParameterShape *paramShape = dynamic_cast<KoParameterShape*>(shape);
if (paramShape && paramShape->isParametricShape())
continue;
KoPathPoint * p = 0;
uint subpathCount = path->subpathCount();
for (uint i = 0; i < subpathCount; ++i) {
if (path->isClosedSubpath(i))
continue;
p = path->pointByIndex(KoPathPointIndex(i, 0));
// check start of subpath
qreal d = squareDistance(position, path->shapeToDocument(p->point()));
if (d < minDistance && d < maxDistance) {
nearestPoint = p;
minDistance = d;
}
// check end of subpath
p = path->pointByIndex(KoPathPointIndex(i, path->subpathPointCount(i) - 1));
d = squareDistance(position, path->shapeToDocument(p->point()));
if (d < minDistance && d < maxDistance) {
nearestPoint = p;
minDistance = d;
}
}
}
return nearestPoint;
}
bool KoPencilTool::connectPaths(KoPathShape *pathShape, KoPathPoint *pointAtStart, KoPathPoint *pointAtEnd)
{
// at least one point must be valid
if (!pointAtStart && !pointAtEnd)
return false;
// do not allow connecting to the same point twice
if (pointAtStart == pointAtEnd)
pointAtEnd = 0;
// we have hit an existing path point on start/finish
// what we now do is:
// 1. combine the new created path with the ones we hit on start/finish
// 2. merge the endpoints of the corresponding subpaths
uint newPointCount = pathShape->subpathPointCount(0);
KoPathPointIndex newStartPointIndex(0, 0);
KoPathPointIndex newEndPointIndex(0, newPointCount - 1);
KoPathPoint * newStartPoint = pathShape->pointByIndex(newStartPointIndex);
KoPathPoint * newEndPoint = pathShape->pointByIndex(newEndPointIndex);
KoPathShape * startShape = pointAtStart ? pointAtStart->parent() : 0;
KoPathShape * endShape = pointAtEnd ? pointAtEnd->parent() : 0;
// combine with the path we hit on start
KoPathPointIndex startIndex(-1, -1);
if (pointAtStart) {
startIndex = startShape->pathPointIndex(pointAtStart);
pathShape->combine(startShape);
pathShape->moveSubpath(0, pathShape->subpathCount() - 1);
}
// combine with the path we hit on finish
KoPathPointIndex endIndex(-1, -1);
if (pointAtEnd) {
endIndex = endShape->pathPointIndex(pointAtEnd);
if (endShape != startShape) {
endIndex.first += pathShape->subpathCount();
pathShape->combine(endShape);
}
}
// do we connect twice to a single subpath ?
bool connectToSingleSubpath = (startShape == endShape && startIndex.first == endIndex.first);
if (startIndex.second == 0 && !connectToSingleSubpath) {
pathShape->reverseSubpath(startIndex.first);
startIndex.second = pathShape->subpathPointCount(startIndex.first) - 1;
}
if (endIndex.second > 0 && !connectToSingleSubpath) {
pathShape->reverseSubpath(endIndex.first);
endIndex.second = 0;
}
// after combining we have a path where with the subpaths in the following
// order:
// 1. the subpaths of the pathshape we started the new path at
// 2. the subpath we just created
// 3. the subpaths of the pathshape we finished the new path at
// get the path points we want to merge, as these are not going to
// change while merging
KoPathPoint * existingStartPoint = pathShape->pointByIndex(startIndex);
KoPathPoint * existingEndPoint = pathShape->pointByIndex(endIndex);
// merge first two points
if (existingStartPoint) {
KoPathPointData pd1(pathShape, pathShape->pathPointIndex(existingStartPoint));
KoPathPointData pd2(pathShape, pathShape->pathPointIndex(newStartPoint));
KoPathPointMergeCommand cmd1(pd1, pd2);
cmd1.redo();
}
// merge last two points
if (existingEndPoint) {
KoPathPointData pd3(pathShape, pathShape->pathPointIndex(newEndPoint));
KoPathPointData pd4(pathShape, pathShape->pathPointIndex(existingEndPoint));
KoPathPointMergeCommand cmd2(pd3, pd4);
cmd2.redo();
}
return true;
}
qreal KoPencilTool::getFittingError()
{
return this->m_fittingError;
}
void KoPencilTool::setFittingError(qreal fittingError)
{
this->m_fittingError = fittingError;
}
diff --git a/libs/command/kis_undo_store.h b/libs/command/kis_undo_store.h
index dc57a06516..2a56a77686 100644
--- a/libs/command/kis_undo_store.h
+++ b/libs/command/kis_undo_store.h
@@ -1,81 +1,81 @@
/*
* Copyright (c) 2003 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_UNDO_STORE_H_
#define KIS_UNDO_STORE_H_
#include <QString>
#include <QVector>
#include <kritacommand_export.h>
class KUndo2Command;
class KUndo2MagicString;
/**
- * See also: http://community.kde.org/Krita/Undo_adapter_vs_Undo_store
+ * See also: https://community.kde.org/Krita/Undo_adapter_vs_Undo_store
*
* Split the functionality of KisUndoAdapter into two classes:
* KisUndoStore and KisUndoAdapter. The former one works as an
* interface to an external storage of the undo information:
* undo stack, KisDocument, /dev/null. The latter one defines the
* behavior of the system when someone wants to add a command. There
* are three variants:
* 1) KisSurrogateUndoAdapter -- saves commands directly to the
* internal stack. Used for wrapping around legacy code into
* a single command.
* 2) KisLegacyUndoAdapter -- blocks the strokes and updates queue,
* and then adds the command to a store
* 3) KisPostExecutionUndoAdapter -- used by the strokes. It doesn't
* call redo() when you add a command. It is assumed, that you have
* already executed the command yourself and now just notify
* the system about it. Warning: it doesn't inherit KisUndoAdapter
* because it doesn't fit the contract of this class. And, more
* important, KisTransaction should work differently with this class.
*
* The ownership on the KisUndoStore (that substituted KisUndoAdapter
* in the document's code) now belongs to the image. It means that
* KisDocument::createUndoStore() is just a factory method, the document
* doesn't store the undo store itself.
*/
class KRITACOMMAND_EXPORT KisUndoStore
{
public:
KisUndoStore();
virtual ~KisUndoStore();
public:
/**
* WARNING: All these methods are not considered as thread-safe
*/
virtual const KUndo2Command* presentCommand() = 0;
virtual void undoLastCommand() = 0;
virtual void addCommand(KUndo2Command *cmd) = 0;
virtual void beginMacro(const KUndo2MagicString& macroName) = 0;
virtual void endMacro() = 0;
virtual void purgeRedoState() = 0;
private:
Q_DISABLE_COPY(KisUndoStore)
};
#endif // KIS_UNDO_STORE_H_
diff --git a/libs/command/kundo2magicstring.h b/libs/command/kundo2magicstring.h
index a8a6ebe0b0..0d5d486e8b 100644
--- a/libs/command/kundo2magicstring.h
+++ b/libs/command/kundo2magicstring.h
@@ -1,318 +1,318 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
* Copyright (c) 2014 Alexander Potashev <aspotashev@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 KUNDO2MAGICSTRING_H
#define KUNDO2MAGICSTRING_H
#include <QString>
#include <QDebug>
#include <klocalizedstring.h>
#include "kritacommand_export.h"
/**
* \class KUndo2MagicString is a special wrapper for a string that is
* going to passed to a KUndo2Command and be later shown in the undo
* history and undo action in menu. The strings like that must have
* (qtundo-format) context to let translators know that they are
* allowed to use magic split in them.
*
* Magic split is used in some languages to split the message in the
* undo history docker (which is either verb or <a
- * href="http://en.wikipedia.org/wiki/Nominative_case">noun in
+ * href="https://en.wikipedia.org/wiki/Nominative_case">noun in
* nominative</a>) and the message in undo/redo actions (which is
- * usually a <a href="http://en.wikipedia.org/wiki/Accusative_case">noun
+ * usually a <a href="https://en.wikipedia.org/wiki/Accusative_case">noun
* in accusative</a>). When the translator needs it he, splits two
* translations with '\n' symbol and the magic string will recognize
* it.
*
* \note KUndo2MagicString will never support concatenation operators,
* because in many languages you cannot combine words without
* knowing the proper case.
*/
class KRITACOMMAND_EXPORT KUndo2MagicString
{
public:
/**
* Construct an empty string. Note that you cannot create a
* non-empy string without special functions, all the calls to which
* are processed by xgettext.
*/
KUndo2MagicString();
/**
* Fetch the main translated string. That is the one that goes to
* undo history and resembles the action name in verb/nominative
*/
QString toString() const;
/**
* Fetch the secondary string which will go to the undo/redo
* action. This is usually a noun in accusative. If the
* translator didn't provide a secondary string, toString() and
* toSecondaryString() return the same values.
*/
QString toSecondaryString() const;
/**
* \return true if the contained string is empty
*/
bool isEmpty() const;
bool operator==(const KUndo2MagicString &rhs) const;
bool operator!=(const KUndo2MagicString &rhs) const;
private:
/**
* Construction of a magic string is allowed only with the means
* of special macros which resemble their kde-wide counterparts
*/
explicit KUndo2MagicString(const QString &text);
friend KUndo2MagicString kundo2_noi18n(const QString &text);
template <typename A1>
friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1);
template <typename A1, typename A2>
friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2);
template <typename A1, typename A2, typename A3>
friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3);
template <typename A1, typename A2, typename A3, typename A4>
friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4);
friend KUndo2MagicString kundo2_i18n(const char *text);
template <typename A1>
friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1);
template <typename A1, typename A2>
friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2);
template <typename A1, typename A2, typename A3>
friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3);
template <typename A1, typename A2, typename A3, typename A4>
friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4);
friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text);
template <typename A1>
friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1);
template <typename A1, typename A2>
friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2);
template <typename A1, typename A2, typename A3>
friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2, const A3 &a3);
template <typename A1>
friend KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1);
template <typename A1, typename A2>
friend KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2);
template <typename A1, typename A2, typename A3>
friend KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3);
template <typename A1>
friend KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1);
template <typename A1, typename A2>
friend KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2);
template <typename A1, typename A2, typename A3>
friend KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3);
private:
QString m_text;
};
inline QDebug operator<<(QDebug dbg, const KUndo2MagicString &v)
{
if (v.toString() != v.toSecondaryString()) {
dbg.nospace() << v.toString() << "(" << v.toSecondaryString() << ")";
} else {
dbg.nospace() << v.toString();
}
return dbg.space();
}
/**
* This is a special wrapper to a string which tells explicitly
* that we don't need a translation for a given string. It is used
* either in testing or internal commands, which don't go to the
* stack directly.
*/
inline KUndo2MagicString kundo2_noi18n(const QString &text)
{
return KUndo2MagicString(text);
}
template <typename A1>
inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1)
{
return KUndo2MagicString(QString(text).arg(a1));
}
template <typename A1, typename A2>
inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2)
{
return KUndo2MagicString(QString(text).arg(a1).arg(a2));
}
template <typename A1, typename A2, typename A3>
inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3)
{
return KUndo2MagicString(QString(text).arg(a1).arg(a2).arg(a3));
}
template <typename A1, typename A2, typename A3, typename A4>
inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4)
{
return KUndo2MagicString(QString(text).arg(a1).arg(a2).arg(a3).arg(a4));
}
/**
* Same as ki18n, but is supposed to work with strings going to
* undo stack
*/
inline KUndo2MagicString kundo2_i18n(const char *text)
{
return KUndo2MagicString(i18nc("(qtundo-format)", text));
}
template <typename A1>
inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1)
{
return KUndo2MagicString(i18nc("(qtundo-format)", text, a1));
}
template <typename A1, typename A2>
inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2)
{
return KUndo2MagicString(i18nc("(qtundo-format)", text, a1, a2));
}
template <typename A1, typename A2, typename A3>
inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3)
{
return KUndo2MagicString(i18nc("(qtundo-format)", text, a1, a2, a3));
}
template <typename A1, typename A2, typename A3, typename A4>
inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4)
{
return KUndo2MagicString(i18nc("(qtundo-format)", text, a1, a2, a3, a4));
}
inline QString prependContext(const char *ctxt)
{
return QString("(qtundo-format) %1").arg(ctxt);
}
/**
* Same as ki18nc, but is supposed to work with strings going to
* undo stack
*/
inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text)
{
return KUndo2MagicString(i18nc(prependContext(ctxt).toLatin1().data(), text));
}
template <typename A1>
inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1)
{
return KUndo2MagicString(i18nc(prependContext(ctxt).toLatin1().data(), text, a1));
}
template <typename A1, typename A2>
inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2)
{
return KUndo2MagicString(i18nc(prependContext(ctxt).toLatin1().data(), text, a1, a2));
}
template <typename A1, typename A2, typename A3>
inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2, const A3 &a3)
{
return KUndo2MagicString(i18nc(prependContext(ctxt).toLatin1().data(), text, a1, a2, a3));
}
template <typename A1, typename A2, typename A3, typename A4>
inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4)
{
return KUndo2MagicString(i18nc(prependContext(ctxt).toLatin1().data(), text, a1, a2, a3, a4));
}
/**
* Same as ki18np, but is supposed to work with strings going to
* undo stack
*/
template <typename A1>
inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1)
{
return KUndo2MagicString(i18ncp("(qtundo-format)", sing, plur, a1));
}
template <typename A1, typename A2>
inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2)
{
return i18ncp("(qtundo-format)", sing, plur, a1, a2);
}
template <typename A1, typename A2, typename A3>
inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3)
{
return i18ncp("(qtundo-format)", sing, plur, a1, a2, a3);
}
template <typename A1, typename A2, typename A3, typename A4>
inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4)
{
return i18ncp("(qtundo-format)", sing, plur, a1, a2, a3, a4);
}
/**
* Same as ki18ncp, but is supposed to work with strings going to
* undo stack
*/
template <typename A1>
inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1)
{
return KUndo2MagicString(i18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1));
}
template <typename A1, typename A2>
inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2)
{
return i18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1, a2);
}
template <typename A1, typename A2, typename A3>
inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3)
{
return i18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1, a2, a3);
}
template <typename A1, typename A2, typename A3, typename A4>
inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4)
{
return i18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1, a2, a3, a4);
}
#endif /* KUNDO2MAGICSTRING_H */
diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt
index 1926026a53..d1bec01e65 100644
--- a/libs/flake/CMakeLists.txt
+++ b/libs/flake/CMakeLists.txt
@@ -1,249 +1,248 @@
project(kritaflake)
include_directories(
${CMAKE_SOURCE_DIR}/libs/flake/commands
${CMAKE_SOURCE_DIR}/libs/flake/tools
${CMAKE_SOURCE_DIR}/libs/flake/svg
${CMAKE_SOURCE_DIR}/libs/flake/text
${CMAKE_BINARY_DIR}/libs/flake
)
add_subdirectory(styles)
add_subdirectory(tests)
add_subdirectory(resources/tests)
set(kritaflake_SRCS
KoGradientHelper.cpp
KoFlake.cpp
KoCanvasBase.cpp
KoResourceManager_p.cpp
KoDerivedResourceConverter.cpp
KoResourceUpdateMediator.cpp
KoCanvasResourceProvider.cpp
KoDocumentResourceManager.cpp
KoCanvasObserverBase.cpp
KoCanvasSupervisor.cpp
KoDockFactoryBase.cpp
KoDockRegistry.cpp
KoDataCenterBase.cpp
KoInsets.cpp
KoPathShape.cpp
KoPathPoint.cpp
KoPathSegment.cpp
KoSelection.cpp
KoSelectedShapesProxy.cpp
KoSelectedShapesProxySimple.cpp
KoShape.cpp
KoShapeAnchor.cpp
KoShapeControllerBase.cpp
KoShapeApplicationData.cpp
KoShapeContainer.cpp
KoShapeContainerModel.cpp
KoShapeGroup.cpp
KoShapeManager.cpp
KoShapePaintingContext.cpp
KoFrameShape.cpp
KoMarker.cpp
KoMarkerCollection.cpp
KoToolBase.cpp
KoCanvasController.cpp
KoCanvasControllerWidget.cpp
KoCanvasControllerWidgetViewport_p.cpp
KoShapeRegistry.cpp
KoDeferredShapeFactoryBase.cpp
KoToolFactoryBase.cpp
KoPathShapeFactory.cpp
KoShapeFactoryBase.cpp
KoShapeUserData.cpp
KoParameterShape.cpp
KoPointerEvent.cpp
KoShapeController.cpp
KoToolSelection.cpp
KoShapeLayer.cpp
KoPostscriptPaintDevice.cpp
KoInputDevice.cpp
KoToolManager_p.cpp
KoToolManager.cpp
KoToolRegistry.cpp
KoToolProxy.cpp
KoShapeSavingContext.cpp
KoShapeLoadingContext.cpp
KoLoadingShapeUpdater.cpp
KoPathShapeLoader.cpp
KoShapeStrokeModel.cpp
KoShapeStroke.cpp
KoShapeBackground.cpp
KoColorBackground.cpp
KoGradientBackground.cpp
KoOdfGradientBackground.cpp
KoHatchBackground.cpp
KoPatternBackground.cpp
KoVectorPatternBackground.cpp
KoShapeFillWrapper.cpp
KoShapeFillResourceConnector.cpp
KoShapeConfigWidgetBase.cpp
KoDrag.cpp
KoSvgPaste.cpp
KoDragOdfSaveHelper.cpp
KoShapeOdfSaveHelper.cpp
KoConnectionPoint.cpp
KoConnectionShape.cpp
KoConnectionShapeLoadingUpdater.cpp
KoConnectionShapeFactory.cpp
KoConnectionShapeConfigWidget.cpp
KoSnapGuide.cpp
KoSnapProxy.cpp
KoSnapStrategy.cpp
KoSnapData.cpp
KoShapeShadow.cpp
KoSharedLoadingData.cpp
KoSharedSavingData.cpp
KoViewConverter.cpp
KoInputDeviceHandler.cpp
KoInputDeviceHandlerEvent.cpp
KoInputDeviceHandlerRegistry.cpp
KoImageData.cpp
KoImageData_p.cpp
KoImageCollection.cpp
KoOdfWorkaround.cpp
KoFilterEffect.cpp
KoFilterEffectStack.cpp
KoFilterEffectFactoryBase.cpp
KoFilterEffectRegistry.cpp
KoFilterEffectConfigWidgetBase.cpp
KoFilterEffectRenderContext.cpp
KoFilterEffectLoadingContext.cpp
KoTextShapeDataBase.cpp
KoTosContainer.cpp
KoTosContainerModel.cpp
KoClipPath.cpp
KoClipMask.cpp
KoClipMaskPainter.cpp
KoCurveFit.cpp
- KisGamutMaskViewConverter.cpp
commands/KoShapeGroupCommand.cpp
commands/KoShapeAlignCommand.cpp
commands/KoShapeBackgroundCommand.cpp
commands/KoShapeCreateCommand.cpp
commands/KoShapeDeleteCommand.cpp
commands/KoShapeDistributeCommand.cpp
commands/KoShapeLockCommand.cpp
commands/KoShapeMoveCommand.cpp
commands/KoShapeResizeCommand.cpp
commands/KoShapeShearCommand.cpp
commands/KoShapeSizeCommand.cpp
commands/KoShapeStrokeCommand.cpp
commands/KoShapeUngroupCommand.cpp
commands/KoShapeReorderCommand.cpp
commands/KoShapeKeepAspectRatioCommand.cpp
commands/KoPathBaseCommand.cpp
commands/KoPathPointMoveCommand.cpp
commands/KoPathControlPointMoveCommand.cpp
commands/KoPathPointTypeCommand.cpp
commands/KoPathPointRemoveCommand.cpp
commands/KoPathPointInsertCommand.cpp
commands/KoPathSegmentBreakCommand.cpp
commands/KoPathBreakAtPointCommand.cpp
commands/KoPathSegmentTypeCommand.cpp
commands/KoPathCombineCommand.cpp
commands/KoSubpathRemoveCommand.cpp
commands/KoSubpathJoinCommand.cpp
commands/KoParameterHandleMoveCommand.cpp
commands/KoParameterToPathCommand.cpp
commands/KoShapeTransformCommand.cpp
commands/KoPathFillRuleCommand.cpp
commands/KoConnectionShapeTypeCommand.cpp
commands/KoShapeShadowCommand.cpp
commands/KoPathReverseCommand.cpp
commands/KoShapeRenameCommand.cpp
commands/KoShapeRunAroundCommand.cpp
commands/KoPathPointMergeCommand.cpp
commands/KoShapeTransparencyCommand.cpp
commands/KoShapeClipCommand.cpp
commands/KoShapeUnclipCommand.cpp
commands/KoPathShapeMarkerCommand.cpp
commands/KoShapeConnectionChangeCommand.cpp
commands/KoMultiPathPointMergeCommand.cpp
commands/KoMultiPathPointJoinCommand.cpp
commands/KoKeepShapesSelectedCommand.cpp
commands/KoPathMergeUtils.cpp
html/HtmlSavingContext.cpp
html/HtmlWriter.cpp
tools/KoPathToolFactory.cpp
tools/KoPathTool.cpp
tools/KoPathToolSelection.cpp
tools/KoPathToolHandle.cpp
tools/PathToolOptionWidget.cpp
tools/KoPathPointRubberSelectStrategy.cpp
tools/KoPathPointMoveStrategy.cpp
tools/KoPathConnectionPointStrategy.cpp
tools/KoPathControlPointMoveStrategy.cpp
tools/KoParameterChangeStrategy.cpp
tools/KoZoomTool.cpp
tools/KoZoomToolFactory.cpp
tools/KoZoomToolWidget.cpp
tools/KoZoomStrategy.cpp
tools/KoInteractionTool.cpp
tools/KoInteractionStrategy.cpp
tools/KoInteractionStrategyFactory.cpp
tools/KoShapeRubberSelectStrategy.cpp
tools/KoPathSegmentChangeStrategy.cpp
svg/KoShapePainter.cpp
svg/SvgUtil.cpp
svg/SvgGraphicContext.cpp
svg/SvgSavingContext.cpp
svg/SvgWriter.cpp
svg/SvgStyleWriter.cpp
svg/SvgShape.cpp
svg/SvgParser.cpp
svg/SvgStyleParser.cpp
svg/SvgGradientHelper.cpp
svg/SvgFilterHelper.cpp
svg/SvgCssHelper.cpp
svg/SvgClipPathHelper.cpp
svg/SvgLoadingContext.cpp
svg/SvgShapeFactory.cpp
svg/parsers/SvgTransformParser.cpp
text/KoSvgText.cpp
text/KoSvgTextProperties.cpp
text/KoSvgTextChunkShape.cpp
text/KoSvgTextShape.cpp
text/KoSvgTextShapeMarkupConverter.cpp
resources/KoSvgSymbolCollectionResource.cpp
resources/KoGamutMask.cpp
FlakeDebug.cpp
tests/MockShapes.cpp
)
ki18n_wrap_ui(kritaflake_SRCS
tools/PathToolOptionWidgetBase.ui
KoConnectionShapeConfigWidget.ui
tools/KoZoomToolWidget.ui
)
add_library(kritaflake SHARED ${kritaflake_SRCS})
generate_export_header(kritaflake BASE_NAME kritaflake)
target_include_directories(kritaflake
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/commands>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/tools>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/svg>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/text>
)
target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg)
set_target_properties(kritaflake PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/flake/KisGamutMaskViewConverter.cpp b/libs/flake/KisGamutMaskViewConverter.cpp
deleted file mode 100644
index 0d7a7cf9b8..0000000000
--- a/libs/flake/KisGamutMaskViewConverter.cpp
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (c) 2018 Anna Medonosova <anna.medonosova@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 "KisGamutMaskViewConverter.h"
-
-#include <QPointF>
-#include <QRectF>
-#include <QSizeF>
-
-#include <kis_assert.h>
-#include <FlakeDebug.h>
-
-//#define DEBUG_GAMUT_MASK_CONVERTER
-
-KisGamutMaskViewConverter::KisGamutMaskViewConverter()
- : m_viewSize(1.0)
- , m_maskSize(QSizeF(1,1))
- , m_maskResolution(1)
-{
- computeAndSetZoom();
-}
-
-KisGamutMaskViewConverter::~KisGamutMaskViewConverter()
-{
-}
-
-QSize KisGamutMaskViewConverter::viewSize() const
-{
- return QSize(m_viewSize, m_viewSize);
-}
-
-QPointF KisGamutMaskViewConverter::documentToView(const QPointF &documentPoint) const
-{
- return QPointF(documentToViewX(documentPoint.x()), documentToViewY(documentPoint.y()));
-}
-
-
-QPointF KisGamutMaskViewConverter::viewToDocument(const QPointF &viewPoint) const
-{
- return QPointF(viewToDocumentX(viewPoint.x()), viewToDocumentY(viewPoint.y()));
-}
-
-QRectF KisGamutMaskViewConverter::documentToView(const QRectF &documentRect) const
-{
- return QRectF(documentToView(documentRect.topLeft()), documentToView(documentRect.size()));
-}
-
-QRectF KisGamutMaskViewConverter::viewToDocument(const QRectF &viewRect) const
-{
- return QRectF(viewToDocument(viewRect.topLeft()), viewToDocument(viewRect.size()));
-}
-
-QSizeF KisGamutMaskViewConverter::documentToView(const QSizeF &documentSize) const
-{
- return QSizeF(documentToViewX(documentSize.width()), documentToViewY(documentSize.height()));
-}
-
-QSizeF KisGamutMaskViewConverter::viewToDocument(const QSizeF &viewSize) const
-{
- return QSizeF(viewToDocumentX(viewSize.width()), viewToDocumentY(viewSize.height()));
-}
-
-qreal KisGamutMaskViewConverter::documentToViewX(qreal documentX) const
-{
- qreal translated = documentX * m_zoomLevel;
-
-#ifdef DEBUG_GAMUT_MASK_CONVERTER
- debugFlake << "KisGamutMaskViewConverter::DocumentToViewX: "
- << "documentX: " << documentX
- << " -> translated: " << translated;
-#endif
-
- return translated;
-}
-
-qreal KisGamutMaskViewConverter::documentToViewY(qreal documentY) const
-{
- qreal translated = documentY * m_zoomLevel;
-
-#ifdef DEBUG_GAMUT_MASK_CONVERTER
- debugFlake << "KisGamutMaskViewConverter::DocumentToViewY: "
- << "documentY: " << documentY
- << " -> translated: " << translated;
-#endif
-
- return translated;
-}
-
-qreal KisGamutMaskViewConverter::viewToDocumentX(qreal viewX) const
-{
- qreal translated = viewX / m_zoomLevel;
-
-#ifdef DEBUG_GAMUT_MASK_CONVERTER
- debugFlake << "KisGamutMaskViewConverter::viewToDocumentX: "
- << "viewX: " << viewX
- << " -> translated: " << translated;
-#endif
-
- return translated;
-}
-
-qreal KisGamutMaskViewConverter::viewToDocumentY(qreal viewY) const
-{
- qreal translated = viewY / m_zoomLevel;
-
-#ifdef DEBUG_GAMUT_MASK_CONVERTER
- debugFlake << "KisGamutMaskViewConverter::viewToDocumentY: "
- << "viewY: " << viewY
- << " -> translated: " << translated;
-#endif
-
- return translated;
-}
-
-
-void KisGamutMaskViewConverter::setZoom(qreal zoom)
-{
- if (qFuzzyCompare(zoom, qreal(0.0)) || qFuzzyCompare(zoom, qreal(1.0))) {
- zoom = 1;
- }
-
-#ifdef DEBUG_GAMUT_MASK_CONVERTER
- debugFlake << "KisGamutMaskViewConverter::setZoom: setting to " << zoom;
-#endif
-
- m_zoomLevel = zoom;
-}
-
-void KisGamutMaskViewConverter::zoom(qreal *zoomX, qreal *zoomY) const
-{
- *zoomX = m_zoomLevel;
- *zoomY = m_zoomLevel;
-}
-
-void KisGamutMaskViewConverter::setViewSize(QSize viewSize)
-{
- m_viewSize = viewSize.width();
-
- computeAndSetZoom();
-}
-
-void KisGamutMaskViewConverter::setMaskSize(QSizeF maskSize)
-{
- KIS_ASSERT(!qIsNull(maskSize.width()));
- KIS_ASSERT(!qIsNull(maskSize.height()));
-
- m_maskSize = maskSize;
- m_maskResolution = maskSize.width();
-
- computeAndSetZoom();
-}
-
-void KisGamutMaskViewConverter::computeAndSetZoom()
-{
- qreal zoom = m_viewSize / m_maskResolution;
-
-#ifdef DEBUG_GAMUT_MASK_CONVERTER
- debugFlake << "KisGamutMaskViewConverter::computeAndSetZoom: "
- << "m_viewSize: " << m_viewSize
- << " m_maskSize: " << m_maskResolution;
-#endif
-
- setZoom(zoom);
-}
diff --git a/libs/flake/KisGamutMaskViewConverter.h b/libs/flake/KisGamutMaskViewConverter.h
deleted file mode 100644
index 6155528abf..0000000000
--- a/libs/flake/KisGamutMaskViewConverter.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (c) 2018 Anna Medonosova <anna.medonosova@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 KISGAMUTMASKVIEWCONVERTER_H
-#define KISGAMUTMASKVIEWCONVERTER_H
-
-#include "kritaflake_export.h"
-
-#include <QtGlobal>
-#include <KoViewConverter.h>
-#include <QSizeF>
-
-class QPointF;
-class QRectF;
-
-/**
- * @brief view convertor for gamut mask calculations and painting
- */
-class KRITAFLAKE_EXPORT KisGamutMaskViewConverter : public KoViewConverter
-{
-public:
- KisGamutMaskViewConverter();
- ~KisGamutMaskViewConverter();
-
- QSize viewSize() const;
- void setViewSize(QSize viewSize);
-
- void setMaskSize(QSizeF maskSize);
-
- QPointF documentToView(const QPointF &documentPoint) const override;
- QPointF viewToDocument(const QPointF &viewPoint) const override;
-
- QRectF documentToView(const QRectF &documentRect) const override;
- QRectF viewToDocument(const QRectF &viewRect) const override;
-
- QSizeF documentToView(const QSizeF& documentSize) const override;
- QSizeF viewToDocument(const QSizeF& viewSize) const override;
-
- qreal documentToViewX(qreal documentX) const override;
- qreal documentToViewY(qreal documentY) const override;
- qreal viewToDocumentX(qreal viewX) const override;
- qreal viewToDocumentY(qreal viewY) const override;
-
- void setZoom(qreal zoom) override;
- void zoom(qreal *zoomX, qreal *zoomY) const override;
-
-private:
- void computeAndSetZoom();
-
- qreal m_zoomLevel; // 1.0 is 100%
- int m_viewSize;
- QSizeF m_maskSize;
- qreal m_maskResolution;
-};
-
-#endif // KISGAMUTMASKVIEWCONVERTER_H
diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
index ef532a43bb..a78d006881 100644
--- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
+++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
@@ -1,332 +1,333 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007, 2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007-2010 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 "KoCanvasControllerWidgetViewport_p.h"
#include <limits.h>
#include <stdlib.h>
#include <QPainter>
#include <QDragEnterEvent>
#include <QMimeData>
#include <KoProperties.h>
#include <FlakeDebug.h>
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoShapeFactoryBase.h" // for the SHAPE mimetypes
#include "KoShapeRegistry.h"
#include "KoShapeController.h"
#include "KoShapeManager.h"
#include "KoSelection.h"
#include "KoCanvasBase.h"
#include "KoShapeLayer.h"
#include "KoShapePaintingContext.h"
#include "KoToolProxy.h"
#include "KoCanvasControllerWidget.h"
#include "KoViewConverter.h"
#include "KoSvgPaste.h"
// ********** Viewport **********
Viewport::Viewport(KoCanvasControllerWidget *parent)
: QWidget(parent)
, m_draggedShape(0)
, m_canvas(0)
, m_documentOffset(QPoint(0, 0))
, m_margin(0)
{
setAutoFillBackground(true);
setAcceptDrops(true);
setMouseTracking(true);
m_parent = parent;
}
void Viewport::setCanvas(QWidget *canvas)
{
if (m_canvas) {
m_canvas->hide();
delete m_canvas;
}
m_canvas = canvas;
if (!canvas) return;
m_canvas->setParent(this);
m_canvas->show();
if (!m_canvas->minimumSize().isNull()) {
m_documentSize = m_canvas->minimumSize();
}
resetLayout();
}
void Viewport::setDocumentSize(const QSizeF &size)
{
m_documentSize = size;
resetLayout();
}
void Viewport::documentOffsetMoved(const QPoint &pt)
{
m_documentOffset = pt;
resetLayout();
}
void Viewport::handleDragEnterEvent(QDragEnterEvent *event)
{
// if not a canvas set then ignore this, makes it possible to assume
// we have a canvas in all the support methods.
if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) {
event->ignore();
return;
}
delete m_draggedShape;
m_draggedShape = 0;
// only allow dropping when active layer is editable
KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
KoShapeLayer *activeLayer = selection->activeLayer();
if (activeLayer && (!activeLayer->isShapeEditable() || activeLayer->isGeometryProtected())) {
event->ignore();
return;
}
const QMimeData *data = event->mimeData();
if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) ||
data->hasFormat(SHAPEID_MIMETYPE) ||
data->hasFormat("image/svg+xml"))
{
if (data->hasFormat("image/svg+xml")) {
KoCanvasBase *canvas = m_parent->canvas();
QSizeF fragmentSize;
QList<KoShape*> shapes = KoSvgPaste::fetchShapesFromData(data->data("image/svg+xml"),
canvas->shapeController()->documentRectInPixels(),
canvas->shapeController()->pixelsPerInch(),
&fragmentSize);
if (!shapes.isEmpty()) {
m_draggedShape = shapes[0];
}
}
else {
QByteArray itemData;
bool isTemplate = true;
if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) {
itemData = data->data(SHAPETEMPLATE_MIMETYPE);
}
else if (data->hasFormat(SHAPEID_MIMETYPE)) {
isTemplate = false;
itemData = data->data(SHAPEID_MIMETYPE);
}
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
QString id;
dataStream >> id;
QString properties;
if (isTemplate)
dataStream >> properties;
// and finally, there is a point.
QPointF offset;
dataStream >> offset;
// The rest of this method is mostly a copy paste from the KoCreateShapeStrategy
// So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave)
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id);
if (! factory) {
warnFlake << "Application requested a shape that is not registered '" <<
id << "', Ignoring";
event->ignore();
return;
}
if (isTemplate) {
KoProperties props;
props.load(properties);
m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager());
}
else {
m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager());
}
if (m_draggedShape->shapeId().isEmpty()) {
m_draggedShape->setShapeId(factory->id());
}
}
event->setDropAction(Qt::CopyAction);
event->accept();
Q_ASSERT(m_draggedShape);
if (!m_draggedShape) return;
// calculate maximum existing shape zIndex
int pasteZIndex = 0;
{
QList<KoShape*> allShapes = m_parent->canvas()->shapeManager()->topLevelShapes();
if (!allShapes.isEmpty()) {
std::sort(allShapes.begin(), allShapes.end(), KoShape::compareShapeZIndex);
pasteZIndex = qMin(int(KoShape::maxZIndex), allShapes.last()->zIndex() + 1);
}
}
m_draggedShape->setZIndex(pasteZIndex);
m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
m_parent->canvas()->shapeManager()->addShape(m_draggedShape);
} else {
event->ignore();
}
}
void Viewport::handleDropEvent(QDropEvent *event)
{
if (!m_draggedShape) {
m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos()));
return;
}
repaint(m_draggedShape);
m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc.
m_draggedShape->setPosition(QPointF(0, 0)); // always save position.
QPointF newPos = correctPosition(event->pos());
m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document.
m_draggedShape->setAbsolutePosition(newPos);
KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape, 0);
if (cmd) {
m_parent->canvas()->addCommand(cmd);
KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
// repaint selection before selecting newly create shape
Q_FOREACH (KoShape * shape, selection->selectedShapes()) {
shape->update();
}
selection->deselectAll();
selection->select(m_draggedShape);
} else {
delete m_draggedShape;
}
m_draggedShape = 0;
}
QPointF Viewport::correctPosition(const QPoint &point) const
{
QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y());
correctedPos += m_documentOffset;
return m_parent->canvas()->viewToDocument(correctedPos);
}
void Viewport::handleDragMoveEvent(QDragMoveEvent *event)
{
if (!m_draggedShape) {
m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos()));
return;
}
m_draggedShape->update();
repaint(m_draggedShape);
m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
m_draggedShape->update();
repaint(m_draggedShape);
}
void Viewport::repaint(KoShape *shape)
{
QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect();
QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x());
rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y());
rect.adjust(-2, -2, 2, 2); // adjust for antialias
update(rect);
}
void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event)
{
if (m_draggedShape) {
repaint(m_draggedShape);
m_parent->canvas()->shapeManager()->remove(m_draggedShape);
delete m_draggedShape;
m_draggedShape = 0;
} else {
m_parent->canvas()->toolProxy()->dragLeaveEvent(event);
}
}
void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event)
{
Q_UNUSED(event);
if (m_draggedShape) {
const KoViewConverter *vc = m_parent->canvas()->viewConverter();
painter.save();
QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
painter.translate(canvasWidget->x() - m_documentOffset.x(),
canvasWidget->y() - m_documentOffset.y());
QPointF offset = vc->documentToView(m_draggedShape->position());
painter.setOpacity(0.6);
painter.translate(offset.x(), offset.y());
painter.setRenderHint(QPainter::Antialiasing);
KoShapePaintingContext paintContext; //FIXME
- m_draggedShape->paint(painter, *vc, paintContext);
+ painter.setTransform(vc->documentToView());
+ m_draggedShape->paint(painter, paintContext);
painter.restore();
}
}
void Viewport::resetLayout()
{
// Determine the area we have to show
QRect viewRect(m_documentOffset, size());
const int viewH = viewRect.height();
const int viewW = viewRect.width();
if (m_canvas) {
QRect geom = QRect(0, 0, viewW, viewH);
if (m_canvas->geometry() != geom) {
m_canvas->setGeometry(geom);
m_canvas->update();
}
}
emit sizeChanged();
#if 0
debugFlake <<"View port geom:" << geometry();
if (m_canvas)
debugFlake <<"Canvas widget geom:" << m_canvas->geometry();
#endif
}
diff --git a/libs/flake/KoClipMask.cpp b/libs/flake/KoClipMask.cpp
index 189984e003..7a97cfe4c4 100644
--- a/libs/flake/KoClipMask.cpp
+++ b/libs/flake/KoClipMask.cpp
@@ -1,177 +1,175 @@
/*
* 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 "KoClipMask.h"
#include <QRectF>
#include <QTransform>
#include <QPainter>
#include <QSharedData>
#include <KoShape.h>
#include "kis_algebra_2d.h"
-#include <KoViewConverter.h>
#include <KoShapePainter.h>
struct Q_DECL_HIDDEN KoClipMask::Private : public QSharedData
{
Private()
: QSharedData()
{}
Private(const Private &rhs)
: QSharedData()
, coordinates(rhs.coordinates)
, contentCoordinates(rhs.contentCoordinates)
, maskRect(rhs.maskRect)
, extraShapeTransform(rhs.extraShapeTransform)
{
Q_FOREACH (KoShape *shape, rhs.shapes) {
KoShape *clonedShape = shape->cloneShape();
KIS_ASSERT_RECOVER(clonedShape) { continue; }
shapes << clonedShape;
}
}
~Private() {
qDeleteAll(shapes);
shapes.clear();
}
KoFlake::CoordinateSystem coordinates = KoFlake::ObjectBoundingBox;
KoFlake::CoordinateSystem contentCoordinates = KoFlake::UserSpaceOnUse;
QRectF maskRect = QRectF(-0.1, -0.1, 1.2, 1.2);
QList<KoShape*> shapes;
QTransform extraShapeTransform; // TODO: not used anymore, use direct shape transform instead
};
KoClipMask::KoClipMask()
: m_d(new Private)
{
}
KoClipMask::~KoClipMask()
{
}
KoClipMask *KoClipMask::clone() const
{
return new KoClipMask(*this);
}
KoFlake::CoordinateSystem KoClipMask::coordinates() const
{
return m_d->coordinates;
}
void KoClipMask::setCoordinates(KoFlake::CoordinateSystem value)
{
m_d->coordinates = value;
}
KoFlake::CoordinateSystem KoClipMask::contentCoordinates() const
{
return m_d->contentCoordinates;
}
void KoClipMask::setContentCoordinates(KoFlake::CoordinateSystem value)
{
m_d->contentCoordinates = value;
}
QRectF KoClipMask::maskRect() const
{
return m_d->maskRect;
}
void KoClipMask::setMaskRect(const QRectF &value)
{
m_d->maskRect = value;
}
QList<KoShape *> KoClipMask::shapes() const
{
return m_d->shapes;
}
void KoClipMask::setShapes(const QList<KoShape *> &value)
{
m_d->shapes = value;
}
bool KoClipMask::isEmpty() const
{
return m_d->shapes.isEmpty();
}
void KoClipMask::setExtraShapeOffset(const QPointF &value)
{
/**
* TODO: when we implement source shapes sharing, please wrap the shapes
* into a group and apply this transform to the group instead
*/
if (m_d->contentCoordinates == KoFlake::UserSpaceOnUse) {
const QTransform t = QTransform::fromTranslate(value.x(), value.y());
Q_FOREACH (KoShape *shape, m_d->shapes) {
shape->applyAbsoluteTransformation(t);
}
}
if (m_d->coordinates == KoFlake::UserSpaceOnUse) {
m_d->maskRect.translate(value);
}
}
void KoClipMask::drawMask(QPainter *painter, KoShape *shape)
{
painter->save();
QPainterPath clipPathInShapeSpace;
if (m_d->coordinates == KoFlake::ObjectBoundingBox) {
QTransform relativeToShape = KisAlgebra2D::mapToRect(shape->outlineRect());
clipPathInShapeSpace.addPolygon(relativeToShape.map(m_d->maskRect));
} else {
clipPathInShapeSpace.addRect(m_d->maskRect);
clipPathInShapeSpace = m_d->extraShapeTransform.map(clipPathInShapeSpace);
}
painter->setClipPath(clipPathInShapeSpace, Qt::IntersectClip);
if (m_d->contentCoordinates == KoFlake::ObjectBoundingBox) {
QTransform relativeToShape = KisAlgebra2D::mapToRect(shape->outlineRect());
painter->setTransform(relativeToShape, true);
} else {
painter->setTransform(m_d->extraShapeTransform, true);
}
- KoViewConverter converter;
KoShapePainter p;
p.setShapes(m_d->shapes);
- p.paint(*painter, converter);
+ p.paint(*painter);
painter->restore();
}
diff --git a/libs/flake/KoClipPath.cpp b/libs/flake/KoClipPath.cpp
index 4a2ffe4817..e83fef76b4 100644
--- a/libs/flake/KoClipPath.cpp
+++ b/libs/flake/KoClipPath.cpp
@@ -1,238 +1,215 @@
/* This file is part of the KDE project
* Copyright (C) 2011 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 "KoClipPath.h"
#include "KoPathShape.h"
-#include "KoViewConverter.h"
#include "KoShapeGroup.h"
#include <QTransform>
#include <QPainterPath>
#include <QPainter>
#include <QVarLengthArray>
#include <QSharedData>
#include <kis_algebra_2d.h>
QTransform scaleToPercent(const QSizeF &size)
{
const qreal w = qMax(static_cast<qreal>(1e-5), size.width());
const qreal h = qMax(static_cast<qreal>(1e-5), size.height());
return QTransform().scale(1.0/w, 1.0/h);
}
QTransform scaleFromPercent(const QSizeF &size)
{
const qreal w = qMax(static_cast<qreal>(1e-5), size.width());
const qreal h = qMax(static_cast<qreal>(1e-5), size.height());
return QTransform().scale(w/1.0, h/1.0);
}
class Q_DECL_HIDDEN KoClipPath::Private : public QSharedData
{
public:
Private()
: QSharedData()
{}
Private(const Private &rhs)
: QSharedData()
, clipPath(rhs.clipPath)
, clipRule(rhs.clipRule)
, coordinates(rhs.coordinates)
, initialTransformToShape(rhs.initialTransformToShape)
, initialShapeSize(rhs.initialShapeSize)
{
Q_FOREACH (KoShape *shape, rhs.shapes) {
KoShape *clonedShape = shape->cloneShape();
KIS_ASSERT_RECOVER(clonedShape) { continue; }
shapes.append(clonedShape);
}
}
~Private()
{
qDeleteAll(shapes);
shapes.clear();
}
void collectShapePath(QPainterPath *result, const KoShape *shape) {
if (const KoPathShape *pathShape = dynamic_cast<const KoPathShape*>(shape)) {
// different shapes add up to the final path using Windind Fill rule (acc. to SVG 1.1)
- QTransform t = pathShape->absoluteTransformation(0);
+ QTransform t = pathShape->absoluteTransformation();
result->addPath(t.map(pathShape->outline()));
} else if (const KoShapeGroup *groupShape = dynamic_cast<const KoShapeGroup*>(shape)) {
QList<KoShape*> shapes = groupShape->shapes();
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
Q_FOREACH (const KoShape *child, shapes) {
collectShapePath(result, child);
}
}
}
void compileClipPath()
{
QList<KoShape*> clipShapes = this->shapes;
if (clipShapes.isEmpty())
return;
clipPath = QPainterPath();
clipPath.setFillRule(Qt::WindingFill);
std::sort(clipShapes.begin(), clipShapes.end(), KoShape::compareShapeZIndex);
Q_FOREACH (KoShape *path, clipShapes) {
if (!path) continue;
collectShapePath(&clipPath, path);
}
}
QList<KoShape*> shapes;
QPainterPath clipPath; ///< the compiled clip path in shape coordinates of the clipped shape
Qt::FillRule clipRule = Qt::WindingFill;
KoFlake::CoordinateSystem coordinates = KoFlake::ObjectBoundingBox;
QTransform initialTransformToShape; ///< initial transformation to shape coordinates of the clipped shape
QSizeF initialShapeSize; ///< initial size of clipped shape
};
KoClipPath::KoClipPath(QList<KoShape*> clipShapes, KoFlake::CoordinateSystem coordinates)
: d(new Private())
{
d->shapes = clipShapes;
d->coordinates = coordinates;
d->compileClipPath();
}
KoClipPath::~KoClipPath()
{
}
KoClipPath *KoClipPath::clone() const
{
return new KoClipPath(*this);
}
void KoClipPath::setClipRule(Qt::FillRule clipRule)
{
d->clipRule = clipRule;
}
Qt::FillRule KoClipPath::clipRule() const
{
return d->clipRule;
}
KoFlake::CoordinateSystem KoClipPath::coordinates() const
{
return d->coordinates;
}
-void KoClipPath::applyClipping(KoShape *clippedShape, QPainter &painter, const KoViewConverter &converter)
+void KoClipPath::applyClipping(KoShape *shape, QPainter &painter)
{
- QPainterPath clipPath;
- KoShape *shape = clippedShape;
- while (shape) {
- if (shape->clipPath()) {
- QPainterPath path = shape->clipPath()->path();
+ if (shape->clipPath()) {
+ QPainterPath path = shape->clipPath()->path();
- QTransform t;
-
- if (shape->clipPath()->coordinates() == KoFlake::ObjectBoundingBox) {
- const QRectF shapeLocalBoundingRect = shape->outline().boundingRect();
- t = KisAlgebra2D::mapToRect(shapeLocalBoundingRect) * shape->absoluteTransformation(0);
-
- } else {
- t = shape->absoluteTransformation(0);
- }
-
- path = t.map(path);
-
- if (clipPath.isEmpty()) {
- clipPath = path;
- } else {
- clipPath &= path;
- }
+ if (shape->clipPath()->coordinates() == KoFlake::ObjectBoundingBox) {
+ const QRectF shapeLocalBoundingRect = shape->outline().boundingRect();
+ path = KisAlgebra2D::mapToRect(shapeLocalBoundingRect).map(path);
}
- shape = shape->parent();
- }
- if (!clipPath.isEmpty()) {
- QTransform viewMatrix;
- qreal zoomX, zoomY;
- converter.zoom(&zoomX, &zoomY);
- viewMatrix.scale(zoomX, zoomY);
- painter.setClipPath(viewMatrix.map(clipPath), Qt::IntersectClip);
+ if (!path.isEmpty()) {
+ painter.setClipPath(path, Qt::IntersectClip);
+ }
}
}
QPainterPath KoClipPath::path() const
{
return d->clipPath;
}
QPainterPath KoClipPath::pathForSize(const QSizeF &size) const
{
return scaleFromPercent(size).map(d->clipPath);
}
QList<KoPathShape*> KoClipPath::clipPathShapes() const
{
// TODO: deprecate this method!
QList<KoPathShape*> shapes;
Q_FOREACH (KoShape *shape, d->shapes) {
KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
if (pathShape) {
shapes << pathShape;
}
}
return shapes;
}
QList<KoShape *> KoClipPath::clipShapes() const
{
return d->shapes;
}
QTransform KoClipPath::clipDataTransformation(KoShape *clippedShape) const
{
if (!clippedShape)
return d->initialTransformToShape;
// the current transformation of the clipped shape
- QTransform currentShapeTransform = clippedShape->absoluteTransformation(0);
+ QTransform currentShapeTransform = clippedShape->absoluteTransformation();
// calculate the transformation which represents any resizing of the clipped shape
const QSizeF currentShapeSize = clippedShape->outline().boundingRect().size();
const qreal sx = currentShapeSize.width() / d->initialShapeSize.width();
const qreal sy = currentShapeSize.height() / d->initialShapeSize.height();
QTransform scaleTransform = QTransform().scale(sx, sy);
// 1. transform to initial clipped shape coordinates
// 2. apply resizing transformation
// 3. convert to current clipped shape document coordinates
return d->initialTransformToShape * scaleTransform * currentShapeTransform;
}
diff --git a/libs/flake/KoClipPath.h b/libs/flake/KoClipPath.h
index 8db2c15b43..4168a37c4e 100644
--- a/libs/flake/KoClipPath.h
+++ b/libs/flake/KoClipPath.h
@@ -1,91 +1,90 @@
/* This file is part of the KDE project
* Copyright (C) 2011 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.
*/
#ifndef KOCLIPPATH_H
#define KOCLIPPATH_H
#include "kritaflake_export.h"
#include <QScopedPointer>
#include <QList>
#include <QSharedDataPointer>
#include <qnamespace.h>
#include <KoFlakeCoordinateSystem.h>
class KoShape;
class KoPathShape;
-class KoViewConverter;
class QPainter;
class QTransform;
class QPainterPath;
class QSizeF;
/// Clip path used to clip shapes
class KRITAFLAKE_EXPORT KoClipPath
{
public:
/**
* Create a new shape clipping using the given clip data
* @param clipShapes define the clipping shapes, owned by KoClipPath!
* @param coordinates shows if ObjectBoundingBox or UserSpaceOnUse coordinate
* system is used.
*/
KoClipPath(QList<KoShape*> clipShapes, KoFlake::CoordinateSystem coordinates);
~KoClipPath();
KoClipPath *clone() const;
KoFlake::CoordinateSystem coordinates() const;
/// Sets the clip rule to be used for the clip path
void setClipRule(Qt::FillRule clipRule);
/// Returns the current clip rule
Qt::FillRule clipRule() const;
/// Returns the current clip path with coordinates in percent of the clipped shape size
QPainterPath path() const;
/// Returns the current clip path scaled to match the specified shape size
QPainterPath pathForSize(const QSizeF &size) const;
/// Returns the clip path shapes
QList<KoPathShape*> clipPathShapes() const;
QList<KoShape*> clipShapes() const;
/**
* Returns the transformation from the clip data path shapes to the
* current document coordinates of the specified clipped shape.
* If the specified clipped shape is null, the transformation
* from clip data path shapes to shape coordinates of the clipped shape
* at the time of creating this clip path is being returned.
*/
QTransform clipDataTransformation(KoShape *clippedShape) const;
/// Applies the clipping to the given painter
- static void applyClipping(KoShape *clippedShape, QPainter &painter, const KoViewConverter &converter);
+ static void applyClipping(KoShape *clippedShape, QPainter &painter);
private:
class Private;
QSharedDataPointer<Private> d;
};
#endif // KOCLIPPATH_H
diff --git a/libs/flake/KoColorBackground.cpp b/libs/flake/KoColorBackground.cpp
index dc9c57f46f..2eacb4790a 100644
--- a/libs/flake/KoColorBackground.cpp
+++ b/libs/flake/KoColorBackground.cpp
@@ -1,117 +1,117 @@
/* This file is part of the KDE project
* Copyright (C) 2008 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 "KoColorBackground.h"
#include "KoShapeSavingContext.h"
#include <KoOdfGraphicStyles.h>
#include <KoOdfLoadingContext.h>
#include <KoXmlNS.h>
#include <KoStyleStack.h>
#include <QColor>
#include <QPainter>
class KoColorBackground::Private : public QSharedData
{
public:
Private()
: QSharedData()
, color(Qt::black)
, style(Qt::SolidPattern)
{}
QColor color;
Qt::BrushStyle style;
};
KoColorBackground::KoColorBackground()
: KoShapeBackground()
, d(new Private)
{
}
KoColorBackground::KoColorBackground(const QColor &color, Qt::BrushStyle style)
: KoShapeBackground()
, d(new Private)
{
if (style < Qt::SolidPattern || style >= Qt::LinearGradientPattern) {
style = Qt::SolidPattern;
}
d->style = style;
d->color = color;
}
KoColorBackground::~KoColorBackground()
{
}
bool KoColorBackground::compareTo(const KoShapeBackground *other) const
{
const KoColorBackground *bg = dynamic_cast<const KoColorBackground*>(other);
return bg && bg->color() == d->color;
}
QColor KoColorBackground::color() const
{
return d->color;
}
void KoColorBackground::setColor(const QColor &color)
{
d->color = color;
}
Qt::BrushStyle KoColorBackground::style() const
{
return d->style;
}
QBrush KoColorBackground::brush() const
{
return QBrush(d->color, d->style);
}
-void KoColorBackground::paint(QPainter &painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const
+void KoColorBackground::paint(QPainter &painter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const
{
painter.setBrush(brush());
painter.drawPath(fillPath);
}
void KoColorBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context)
{
KoOdfGraphicStyles::saveOdfFillStyle(style, context.mainStyles(), QBrush(d->color, d->style));
}
bool KoColorBackground::loadStyle(KoOdfLoadingContext & context, const QSizeF &)
{
KoStyleStack &styleStack = context.styleStack();
if (! styleStack.hasProperty(KoXmlNS::draw, "fill"))
return false;
QString fillStyle = styleStack.property(KoXmlNS::draw, "fill");
if (fillStyle == "solid" || fillStyle == "hatch") {
QBrush brush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, fillStyle, context.stylesReader());
d->color = brush.color();
d->style = brush.style();
return true;
}
return false;
}
diff --git a/libs/flake/KoColorBackground.h b/libs/flake/KoColorBackground.h
index 4398b16b0f..474db8efa0 100644
--- a/libs/flake/KoColorBackground.h
+++ b/libs/flake/KoColorBackground.h
@@ -1,67 +1,67 @@
/* This file is part of the KDE project
* Copyright (C) 2008 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.
*/
#ifndef KOCOLORBACKGROUND_H
#define KOCOLORBACKGROUND_H
#include "KoShapeBackground.h"
#include "kritaflake_export.h"
#include <Qt>
#include <QSharedDataPointer>
class KoColorBackgroundPrivate;
class QColor;
class QBrush;
/// A simple solid color shape background
class KRITAFLAKE_EXPORT KoColorBackground : public KoShapeBackground
{
public:
KoColorBackground();
/// Creates background from given color and style
explicit KoColorBackground(const QColor &color, Qt::BrushStyle style = Qt::SolidPattern);
~KoColorBackground() override;
bool compareTo(const KoShapeBackground *other) const override;
/// Returns the background color
QColor color() const;
/// Sets the background color
void setColor(const QColor &color);
/// Returns the background style
Qt::BrushStyle style() const;
QBrush brush() const;
// reimplemented from KoShapeBackground
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override;
+ void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override;
// reimplemented from KoShapeBackground
void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override;
// reimplemented from KoShapeBackground
bool loadStyle(KoOdfLoadingContext & context, const QSizeF &shapeSize) override;
private:
class Private;
QSharedDataPointer<Private> d;
};
#endif // KOCOLORBACKGROUND_H
diff --git a/libs/flake/KoConnectionShape.cpp b/libs/flake/KoConnectionShape.cpp
index ae6d46d22a..28f3cf8f1e 100644
--- a/libs/flake/KoConnectionShape.cpp
+++ b/libs/flake/KoConnectionShape.cpp
@@ -1,779 +1,778 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Boudewijn Rempt <boud@kde.org>
* Copyright (C) 2007,2009 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007,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 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 "KoConnectionShape.h"
#include "KoConnectionShape_p.h"
-#include "KoViewConverter.h"
#include "KoShapeLoadingContext.h"
#include "KoShapeSavingContext.h"
#include "KoConnectionShapeLoadingUpdater.h"
#include "KoPathShapeLoader.h"
#include "KoPathPoint.h"
#include "KoShapeBackground.h"
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoXmlNS.h>
#include <KoUnit.h>
#include <QPainter>
#include <FlakeDebug.h>
KoConnectionShape::Private::Private()
: QSharedData()
, shape1(0)
, shape2(0)
, connectionPointId1(-1)
, connectionPointId2(-1)
, connectionType(KoConnectionShape::Standard)
, forceUpdate(false)
, hasCustomPath(false)
{
}
KoConnectionShape::Private::Private(const KoConnectionShape::Private &rhs)
: QSharedData()
, path(rhs.path)
, shape1(0) // FIXME: it should point to the new shapes!!!
, shape2(0) // FIXME: it should point to the new shapes!!!
, connectionPointId1(rhs.connectionPointId1)
, connectionPointId2(rhs.connectionPointId2)
, connectionType(rhs.connectionType)
, forceUpdate(rhs.forceUpdate)
, hasCustomPath(rhs.hasCustomPath)
{
}
QPointF KoConnectionShape::escapeDirection(int handleId) const
{
QPointF direction;
if (d->handleConnected(handleId)) {
KoShape *attachedShape = handleId == KoConnectionShape::StartHandle ? d->shape1 : d->shape2;
int connectionPointId = handleId == KoConnectionShape::StartHandle ? d->connectionPointId1 : d->connectionPointId2;
KoConnectionPoint::EscapeDirection ed = attachedShape->connectionPoint(connectionPointId).escapeDirection;
if (ed == KoConnectionPoint::AllDirections) {
QPointF handlePoint = shapeToDocument(handles()[handleId]);
QPointF centerPoint = attachedShape->absolutePosition(KoFlake::Center);
/*
* Determine the best escape direction from the position of the handle point
* and the position and orientation of the attached shape.
* The idea is to define 4 sectors, one for each edge of the attached shape.
* Each sector starts at the center point of the attached shape and has it
* left and right edge going through the two points which define the edge.
* Then we check which sector contains our handle point, for which we can
* simply calculate the corresponding direction which is orthogonal to the
* corresponding bounding box edge.
* From that we derive the escape direction from looking at the main coordinate
* of the orthogonal direction.
*/
// define our edge points in the right order
const KoFlake::AnchorPosition corners[4] = {
KoFlake::BottomRight,
KoFlake::BottomLeft,
KoFlake::TopLeft,
KoFlake::TopRight
};
QPointF vHandle = handlePoint-centerPoint;
for (int i = 0; i < 4; ++i) {
// first point of bounding box edge
QPointF p1 = attachedShape->absolutePosition(corners[i]);
// second point of bounding box edge
QPointF p2 = attachedShape->absolutePosition(corners[(i+1)%4]);
// check on which side of the first sector edge our second sector edge is
const qreal c0 = d->crossProd(p1-centerPoint, p2-centerPoint);
// check on which side of the first sector edge our handle point is
const qreal c1 = d->crossProd(p1-centerPoint, vHandle);
// second edge and handle point must be on the same side of first edge
if ((c0 < 0 && c1 > 0) || (c0 > 0 && c1 < 0))
continue;
// check on which side of the handle point our second sector edge is
const qreal c2 = d->crossProd(vHandle, p2-centerPoint);
// second edge must be on the same side of the handle point as on first edge
if ((c0 < 0 && c2 > 0) || (c0 > 0 && c2 < 0))
continue;
// now we found the correct edge
QPointF vDir = 0.5 *(p1+p2) - centerPoint;
// look at coordinate with the greatest absolute value
// and construct our escape direction accordingly
const qreal xabs = qAbs<qreal>(vDir.x());
const qreal yabs = qAbs<qreal>(vDir.y());
if (xabs > yabs) {
direction.rx() = vDir.x() > 0 ? 1.0 : -1.0;
direction.ry() = 0.0;
} else {
direction.rx() = 0.0;
direction.ry() = vDir.y() > 0 ? 1.0 : -1.0;
}
break;
}
} else if (ed == KoConnectionPoint::HorizontalDirections) {
QPointF handlePoint = shapeToDocument(handles()[handleId]);
QPointF centerPoint = attachedShape->absolutePosition(KoFlake::Center);
// use horizontal direction pointing away from center point
if (handlePoint.x() < centerPoint.x())
direction = QPointF(-1.0, 0.0);
else
direction = QPointF(1.0, 0.0);
} else if (ed == KoConnectionPoint::VerticalDirections) {
QPointF handlePoint = shapeToDocument(handles()[handleId]);
QPointF centerPoint = attachedShape->absolutePosition(KoFlake::Center);
// use vertical direction pointing away from center point
if (handlePoint.y() < centerPoint.y())
direction = QPointF(0.0, -1.0);
else
direction = QPointF(0.0, 1.0);
} else if (ed == KoConnectionPoint::LeftDirection) {
direction = QPointF(-1.0, 0.0);
} else if (ed == KoConnectionPoint::RightDirection) {
direction = QPointF(1.0, 0.0);
} else if (ed == KoConnectionPoint::UpDirection) {
direction = QPointF(0.0, -1.0);
} else if (ed == KoConnectionPoint::DownDirection) {
direction = QPointF(0.0, 1.0);
}
// transform escape direction by using our own transformation matrix
- QTransform invMatrix = absoluteTransformation(0).inverted();
+ QTransform invMatrix = absoluteTransformation().inverted();
direction = invMatrix.map(direction) - invMatrix.map(QPointF());
direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y());
}
return direction;
}
bool KoConnectionShape::Private::intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect)
{
qreal sp1 = scalarProd(d1, p2 - p1);
if (sp1 < 0.0)
return false;
qreal sp2 = scalarProd(d2, p1 - p2);
if (sp2 < 0.0)
return false;
// use cross product to check if rays intersects at all
qreal cp = crossProd(d1, d2);
if (cp == 0.0) {
// rays are parallel or coincident
if (p1.x() == p2.x() && d1.x() == 0.0 && d1.y() != d2.y()) {
// vertical, coincident
isect = 0.5 * (p1 + p2);
} else if (p1.y() == p2.y() && d1.y() == 0.0 && d1.x() != d2.x()) {
// horizontal, coincident
isect = 0.5 * (p1 + p2);
} else {
return false;
}
} else {
// they are intersecting normally
isect = p1 + sp1 * d1;
}
return true;
}
QPointF KoConnectionShape::Private::perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2)
{
QPointF perpendicular(d1.y(), -d1.x());
qreal sp = scalarProd(perpendicular, p2 - p1);
if (sp < 0.0)
perpendicular *= -1.0;
return perpendicular;
}
void KoConnectionShape::normalPath(const qreal MinimumEscapeLength)
{
// Clear the path to build it again.
d->path.clear();
d->path.append(handles()[KoConnectionShape::StartHandle]);
QList<QPointF> edges1;
QList<QPointF> edges2;
QPointF direction1 = escapeDirection(KoConnectionShape::StartHandle);
QPointF direction2 = escapeDirection(KoConnectionShape::EndHandle);
QPointF edgePoint1 = handles()[KoConnectionShape::StartHandle] + MinimumEscapeLength * direction1;
QPointF edgePoint2 = handles()[KoConnectionShape::EndHandle] + MinimumEscapeLength * direction2;
edges1.append(edgePoint1);
edges2.prepend(edgePoint2);
if (d->handleConnected(KoConnectionShape::StartHandle) && d->handleConnected(KoConnectionShape::EndHandle)) {
QPointF intersection;
// TODO: check if this loop actually ever exits? (DK)
while (true) {
// first check if directions from current edge points intersect
if (d->intersects(edgePoint1, direction1, edgePoint2, direction2, intersection)) {
// directions intersect, we have another edge point and be done
edges1.append(intersection);
break;
}
// check if we are going toward the other handle
qreal sp = d->scalarProd(direction1, edgePoint2 - edgePoint1);
if (sp >= 0.0) {
// if we are having the same direction, go all the way toward
// the other handle, else only go half the way
if (direction1 == direction2)
edgePoint1 += sp * direction1;
else
edgePoint1 += 0.5 * sp * direction1;
edges1.append(edgePoint1);
// switch direction
direction1 = d->perpendicularDirection(edgePoint1, direction1, edgePoint2);
} else {
// we are not going into the same direction, so switch direction
direction1 = d->perpendicularDirection(edgePoint1, direction1, edgePoint2);
}
}
}
d->path.append(edges1);
d->path.append(edges2);
d->path.append(handles()[KoConnectionShape::EndHandle]);
}
qreal KoConnectionShape::Private::scalarProd(const QPointF &v1, const QPointF &v2) const
{
return v1.x() * v2.x() + v1.y() * v2.y();
}
qreal KoConnectionShape::Private::crossProd(const QPointF &v1, const QPointF &v2) const
{
return v1.x() * v2.y() - v1.y() * v2.x();
}
bool KoConnectionShape::Private::handleConnected(int handleId) const
{
if (handleId == KoConnectionShape::StartHandle && shape1 && connectionPointId1 >= 0)
return true;
if (handleId == KoConnectionShape::EndHandle && shape2 && connectionPointId2 >= 0)
return true;
return false;
}
void KoConnectionShape::updateConnections()
{
bool updateHandles = false;
if (d->handleConnected(StartHandle)) {
if (d->shape1->hasConnectionPoint(d->connectionPointId1)) {
// map connection point into our shape coordinates
- QPointF p = documentToShape(d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position));
+ QPointF p = documentToShape(d->shape1->absoluteTransformation().map(d->shape1->connectionPoint(d->connectionPointId1).position));
if (handles()[StartHandle] != p) {
handles()[StartHandle] = p;
updateHandles = true;
}
}
}
if (d->handleConnected(EndHandle)) {
if (d->shape2->hasConnectionPoint(d->connectionPointId2)) {
// map connection point into our shape coordinates
- QPointF p = documentToShape(d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position));
+ QPointF p = documentToShape(d->shape2->absoluteTransformation().map(d->shape2->connectionPoint(d->connectionPointId2).position));
if (handles()[EndHandle] != p) {
handles()[EndHandle] = p;
updateHandles = true;
}
}
}
if (updateHandles || d->forceUpdate) {
update(); // ugly, for repainting the connection we just changed
updatePath(QSizeF());
update(); // ugly, for repainting the connection we just changed
d->forceUpdate = false;
}
}
KoConnectionShape::KoConnectionShape()
: KoParameterShape()
, d(new Private)
{
handles().push_back(QPointF(0, 0));
handles().push_back(QPointF(140, 140));
moveTo(handles()[StartHandle]);
lineTo(handles()[EndHandle]);
updatePath(QSizeF(140, 140));
clearConnectionPoints();
}
KoConnectionShape::KoConnectionShape(const KoConnectionShape &rhs)
: KoParameterShape(rhs)
, d(rhs.d)
{
}
KoConnectionShape::~KoConnectionShape()
{
if (d->shape1)
d->shape1->removeDependee(this);
if (d->shape2)
d->shape2->removeDependee(this);
}
KoShape *KoConnectionShape::cloneShape() const
{
return new KoConnectionShape(*this);
}
void KoConnectionShape::saveOdf(KoShapeSavingContext & context) const
{
context.xmlWriter().startElement("draw:connector");
saveOdfAttributes(context, OdfMandatories | OdfAdditionalAttributes);
switch (d->connectionType) {
case Lines:
context.xmlWriter().addAttribute("draw:type", "lines");
break;
case Straight:
context.xmlWriter().addAttribute("draw:type", "line");
break;
case Curve:
context.xmlWriter().addAttribute("draw:type", "curve");
break;
default:
context.xmlWriter().addAttribute("draw:type", "standard");
break;
}
if (d->shape1) {
context.xmlWriter().addAttribute("draw:start-shape", context.xmlid(d->shape1, "shape", KoElementReference::Counter).toString());
context.xmlWriter().addAttribute("draw:start-glue-point", d->connectionPointId1);
} else {
QPointF p(shapeToDocument(handles()[StartHandle]) * context.shapeOffset(this));
context.xmlWriter().addAttribute("svg:x1", p.x());
context.xmlWriter().addAttribute("svg:y1", p.y());
}
if (d->shape2) {
context.xmlWriter().addAttribute("draw:end-shape", context.xmlid(d->shape2, "shape", KoElementReference::Counter).toString());
context.xmlWriter().addAttribute("draw:end-glue-point", d->connectionPointId2);
} else {
QPointF p(shapeToDocument(handles()[EndHandle]) * context.shapeOffset(this));
context.xmlWriter().addAttribute("svg:x2", p.x());
context.xmlWriter().addAttribute("svg:y2", p.y());
}
// write the path data
context.xmlWriter().addAttribute("svg:d", toString());
saveOdfAttributes(context, OdfViewbox);
saveOdfCommonChildElements(context);
saveText(context);
context.xmlWriter().endElement();
}
bool KoConnectionShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context)
{
loadOdfAttributes(element, context, OdfMandatories | OdfCommonChildElements | OdfAdditionalAttributes);
QString type = element.attributeNS(KoXmlNS::draw, "type", "standard");
if (type == "lines")
d->connectionType = Lines;
else if (type == "line")
d->connectionType = Straight;
else if (type == "curve")
d->connectionType = Curve;
else
d->connectionType = Standard;
// reset connection point indices
d->connectionPointId1 = -1;
d->connectionPointId2 = -1;
// reset connected shapes
d->shape1 = 0;
d->shape2 = 0;
if (element.hasAttributeNS(KoXmlNS::draw, "start-shape")) {
d->connectionPointId1 = element.attributeNS(KoXmlNS::draw, "start-glue-point", QString()).toInt();
QString shapeId1 = element.attributeNS(KoXmlNS::draw, "start-shape", QString());
debugFlake << "references start-shape" << shapeId1 << "at glue-point" << d->connectionPointId1;
d->shape1 = context.shapeById(shapeId1);
if (d->shape1) {
debugFlake << "start-shape was already loaded";
d->shape1->addDependee(this);
if (d->shape1->hasConnectionPoint(d->connectionPointId1)) {
debugFlake << "connecting to start-shape";
- handles()[StartHandle] = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position);
+ handles()[StartHandle] = d->shape1->absoluteTransformation().map(d->shape1->connectionPoint(d->connectionPointId1).position);
debugFlake << "start handle position =" << handles()[StartHandle];
}
} else {
debugFlake << "start-shape not loaded yet, deferring connection";
context.updateShape(shapeId1, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::First));
}
} else {
handles()[StartHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString())));
handles()[StartHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", QString())));
}
if (element.hasAttributeNS(KoXmlNS::draw, "end-shape")) {
d->connectionPointId2 = element.attributeNS(KoXmlNS::draw, "end-glue-point", "").toInt();
QString shapeId2 = element.attributeNS(KoXmlNS::draw, "end-shape", "");
debugFlake << "references end-shape " << shapeId2 << "at glue-point" << d->connectionPointId2;
d->shape2 = context.shapeById(shapeId2);
if (d->shape2) {
debugFlake << "end-shape was already loaded";
d->shape2->addDependee(this);
if (d->shape2->hasConnectionPoint(d->connectionPointId2)) {
debugFlake << "connecting to end-shape";
- handles()[EndHandle] = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position);
+ handles()[EndHandle] = d->shape2->absoluteTransformation().map(d->shape2->connectionPoint(d->connectionPointId2).position);
debugFlake << "end handle position =" << handles()[EndHandle];
}
} else {
debugFlake << "end-shape not loaded yet, deferring connection";
context.updateShape(shapeId2, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::Second));
}
} else {
handles()[EndHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString())));
handles()[EndHandle].setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", QString())));
}
QString skew = element.attributeNS(KoXmlNS::draw, "line-skew", QString());
QStringList skewValues = skew.simplified().split(' ', QString::SkipEmptyParts);
// TODO apply skew values once we support them
// load the path data if there is any
d->hasCustomPath = element.hasAttributeNS(KoXmlNS::svg, "d");
if (d->hasCustomPath) {
KoPathShapeLoader loader(this);
loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true);
if (subpaths().size() > 0) {
QRectF viewBox = loadOdfViewbox(element);
if (viewBox.isEmpty()) {
// there should be a viewBox to transform the path data
// if there is none, use the bounding rectangle of the parsed path
viewBox = outline().boundingRect();
}
// convert path to viewbox coordinates to have a bounding rect of (0,0 1x1)
// which can later be fitted back into the target rect once we have all
// the required information
QTransform viewMatrix;
viewMatrix.scale(viewBox.width() ? static_cast<qreal>(1.0) / viewBox.width() : 1.0,
viewBox.height() ? static_cast<qreal>(1.0) / viewBox.height() : 1.0);
viewMatrix.translate(-viewBox.left(), -viewBox.top());
map(viewMatrix);
// trigger finishing the connections in case we have all data
// otherwise it gets called again once the shapes we are
// connected to are loaded
}
else {
d->hasCustomPath = false;
}
finishLoadingConnection();
} else {
d->forceUpdate = true;
updateConnections();
}
loadText(element, context);
return true;
}
void KoConnectionShape::finishLoadingConnection()
{
if (d->hasCustomPath) {
const bool loadingFinished1 = d->connectionPointId1 >= 0 ? d->shape1 != 0 : true;
const bool loadingFinished2 = d->connectionPointId2 >= 0 ? d->shape2 != 0 : true;
if (loadingFinished1 && loadingFinished2) {
QPointF p1, p2;
if (d->handleConnected(StartHandle)) {
if (d->shape1->hasConnectionPoint(d->connectionPointId1)) {
- p1 = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position);
+ p1 = d->shape1->absoluteTransformation().map(d->shape1->connectionPoint(d->connectionPointId1).position);
}
} else {
p1 = handles()[StartHandle];
}
if (d->handleConnected(EndHandle)) {
if (d->shape2->hasConnectionPoint(d->connectionPointId2)) {
- p2 = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position);
+ p2 = d->shape2->absoluteTransformation().map(d->shape2->connectionPoint(d->connectionPointId2).position);
}
} else {
p2 = handles()[EndHandle];
}
QPointF relativeBegin = subpaths().first()->first()->point();
QPointF relativeEnd = subpaths().last()->last()->point();
QPointF diffRelative(relativeBegin - relativeEnd);
QPointF diffAbsolute(p1 - p2);
qreal factorX = diffRelative.x() ? diffAbsolute.x() / diffRelative.x(): 1.0;
qreal factorY = diffRelative.y() ? diffAbsolute.y() / diffRelative.y(): 1.0;
p1.setX(p1.x() - relativeBegin.x() * factorX);
p1.setY(p1.y() - relativeBegin.y() * factorY);
p2.setX(p2.x() + (1 - relativeEnd.x()) * factorX);
p2.setY(p2.y() + (1 - relativeEnd.y()) * factorY);
QRectF targetRect = QRectF(p1, p2).normalized();
// transform the normalized coordinates back to our target rectangle
QTransform viewMatrix;
viewMatrix.translate(targetRect.x(), targetRect.y());
viewMatrix.scale(targetRect.width(), targetRect.height());
map(viewMatrix);
// pretend we are during a forced update, so normalize()
// will not trigger an updateConnections() call
d->forceUpdate = true;
normalize();
d->forceUpdate = false;
}
} else {
updateConnections();
}
}
void KoConnectionShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
if (handleId >= handles().size())
return;
handles()[handleId] = point;
}
void KoConnectionShape::updatePath(const QSizeF &size)
{
Q_UNUSED(size);
const qreal MinimumEscapeLength = (qreal)20.;
clear();
switch (d->connectionType) {
case Standard: {
normalPath(MinimumEscapeLength);
if (d->path.count() != 0){
moveTo(d->path[0]);
for (int index = 1; index < d->path.count(); ++index)
lineTo(d->path[index]);
}
break;
}
case Lines: {
QPointF direction1 = escapeDirection(0);
QPointF direction2 = escapeDirection(handles().count() - 1);
moveTo(handles()[StartHandle]);
if (! direction1.isNull())
lineTo(handles()[StartHandle] + MinimumEscapeLength * direction1);
if (! direction2.isNull())
lineTo(handles()[EndHandle] + MinimumEscapeLength * direction2);
lineTo(handles()[EndHandle]);
break;
}
case Straight:
moveTo(handles()[StartHandle]);
lineTo(handles()[EndHandle]);
break;
case Curve:
// TODO
QPointF direction1 = escapeDirection(0);
QPointF direction2 = escapeDirection(handles().count() - 1);
moveTo(handles()[StartHandle]);
if (! direction1.isNull() && ! direction2.isNull()) {
QPointF curvePoint1 = handles()[StartHandle] + 5.0 * MinimumEscapeLength * direction1;
QPointF curvePoint2 = handles()[EndHandle] + 5.0 * MinimumEscapeLength * direction2;
curveTo(curvePoint1, curvePoint2, handles()[EndHandle]);
} else {
lineTo(handles()[EndHandle]);
}
break;
}
normalize();
}
bool KoConnectionShape::connectFirst(KoShape * shape1, int connectionPointId)
{
// refuse to connect to a shape that depends on us (e.g. a artistic text shape)
if (hasDependee(shape1))
return false;
if (shape1) {
// check if the connection point does exist
if (!shape1->hasConnectionPoint(connectionPointId))
return false;
// do not connect to the same connection point twice
if (d->shape2 == shape1 && d->connectionPointId2 == connectionPointId)
return false;
}
if (d->shape1)
d->shape1->removeDependee(this);
d->shape1 = shape1;
if (d->shape1)
d->shape1->addDependee(this);
d->connectionPointId1 = connectionPointId;
return true;
}
bool KoConnectionShape::connectSecond(KoShape * shape2, int connectionPointId)
{
// refuse to connect to a shape that depends on us (e.g. a artistic text shape)
if (hasDependee(shape2))
return false;
if (shape2) {
// check if the connection point does exist
if (!shape2->hasConnectionPoint(connectionPointId))
return false;
// do not connect to the same connection point twice
if (d->shape1 == shape2 && d->connectionPointId1 == connectionPointId)
return false;
}
if (d->shape2)
d->shape2->removeDependee(this);
d->shape2 = shape2;
if (d->shape2)
d->shape2->addDependee(this);
d->connectionPointId2 = connectionPointId;
return true;
}
KoShape *KoConnectionShape::firstShape() const
{
return d->shape1;
}
int KoConnectionShape::firstConnectionId() const
{
return d->connectionPointId1;
}
KoShape *KoConnectionShape::secondShape() const
{
return d->shape2;
}
int KoConnectionShape::secondConnectionId() const
{
return d->connectionPointId2;
}
KoConnectionShape::Type KoConnectionShape::type() const
{
return d->connectionType;
}
void KoConnectionShape::setType(Type connectionType)
{
d->connectionType = connectionType;
updatePath(size());
}
void KoConnectionShape::shapeChanged(ChangeType type, KoShape *shape)
{
KoTosContainer::shapeChanged(type, shape);
// check if we are during a forced update
const bool updateIsActive = d->forceUpdate;
switch (type) {
case PositionChanged:
case RotationChanged:
case ShearChanged:
case ScaleChanged:
case GenericMatrixChange:
case ParameterChanged:
if (isParametricShape() && shape == 0)
d->forceUpdate = true;
break;
case Deleted:
if (shape != d->shape1 && shape != d->shape2)
return;
if (shape == d->shape1)
connectFirst(0, -1);
if (shape == d->shape2)
connectSecond(0, -1);
break;
case ConnectionPointChanged:
if (shape == d->shape1 && !shape->hasConnectionPoint(d->connectionPointId1)) {
connectFirst(0, -1);
} else if ( shape == d->shape2 && !shape->hasConnectionPoint(d->connectionPointId2)){
connectSecond(0, -1);
} else {
d->forceUpdate = true;
}
break;
case BackgroundChanged:
{
// connection shape should not have a background
QSharedPointer<KoShapeBackground> fill = background();
if (fill) {
setBackground(QSharedPointer<KoShapeBackground>(0));
}
return;
}
default:
return;
}
// the connection was moved while it is connected to some other shapes
const bool connectionChanged = !shape && (d->shape1 || d->shape2);
// one of the connected shape has moved
const bool connectedShapeChanged = shape && (shape == d->shape1 || shape == d->shape2);
if (!updateIsActive && (connectionChanged || connectedShapeChanged) && isParametricShape())
updateConnections();
// reset the forced update flag
d->forceUpdate = false;
}
QString KoConnectionShape::pathShapeId() const
{
return KOCONNECTIONSHAPEID;
}
diff --git a/libs/flake/KoCurveFit.h b/libs/flake/KoCurveFit.h
index 35ab8b29a3..0f1ae931d8 100644
--- a/libs/flake/KoCurveFit.h
+++ b/libs/flake/KoCurveFit.h
@@ -1,49 +1,49 @@
/* This file is part of the KDE project
Copyright (C) 2001-2003 Rob Buis <buis@kde.org>
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.
*/
#ifndef KOCURVEFIT_H
#define KOCURVEFIT_H
#include <QList>
#include <QPointF>
#include "kritaflake_export.h"
class KoPathShape;
/*
* Fits bezier curve to given list of points.
*
* An Algorithm for Automatically Fitting Digitized Curves
* by Philip J. Schneider
* from "Graphics Gems", Academic Press, 1990
*
- * http://www.acm.org/pubs/tog/GraphicsGems/gems/FitCurves.c
- * http://www.acm.org/pubs/tog/GraphicsGems/gems/README
+ * http://web.archive.org/web/20061118130015/http://www.acm.org/pubs/tog/GraphicsGems/gems/FitCurves.c
+ * http://web.archive.org/web/20040519052901/http://www.acm.org/pubs/tog/GraphicsGems/gems/README
*
* @param points the list of points to fit curve to
* @param error the max. fitting error
* @return a path shape representing the fitted curve
*/
KRITAFLAKE_EXPORT KoPathShape * bezierFit(const QList<QPointF> &points, float error);
#endif
diff --git a/libs/flake/KoDrag.cpp b/libs/flake/KoDrag.cpp
index e6453be246..e77a3d10da 100644
--- a/libs/flake/KoDrag.cpp
+++ b/libs/flake/KoDrag.cpp
@@ -1,128 +1,128 @@
/* This file is part of the KDE project
* Copyright (C) 2007-2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2009 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 "KoDrag.h"
#include "KoDragOdfSaveHelper.h"
#include <QApplication>
#include <QBuffer>
#include <QByteArray>
#include <QClipboard>
#include <QMimeData>
#include <QString>
#include <QTransform>
#include <FlakeDebug.h>
#include <KoStore.h>
#include <KoGenStyles.h>
#include <KoOdfWriteStore.h>
#include <KoXmlWriter.h>
#include <KoDocumentBase.h>
#include <KoEmbeddedDocumentSaver.h>
#include "KoShapeSavingContext.h"
#include <KoShapeContainer.h>
#include <KoShape.h>
#include <QRect>
#include <SvgWriter.h>
class KoDragPrivate {
public:
KoDragPrivate() : mimeData(0) { }
~KoDragPrivate() { delete mimeData; }
QMimeData *mimeData;
};
KoDrag::KoDrag()
: d(new KoDragPrivate())
{
}
KoDrag::~KoDrag()
{
delete d;
}
bool KoDrag::setSvg(const QList<KoShape *> originalShapes)
{
QRectF boundingRect;
QList<KoShape*> shapes;
Q_FOREACH (KoShape *shape, originalShapes) {
boundingRect |= shape->boundingRect();
KoShape *clonedShape = shape->cloneShape();
/**
* The shape is cloned without its parent's transformation, so we should
* adjust it manually.
*/
KoShape *oldParentShape = shape->parent();
if (oldParentShape) {
- clonedShape->applyTransformation(oldParentShape->absoluteTransformation(0));
+ clonedShape->applyTransformation(oldParentShape->absoluteTransformation());
}
shapes.append(clonedShape);
}
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QBuffer buffer;
QLatin1String mimeType("image/svg+xml");
buffer.open(QIODevice::WriteOnly);
const QSizeF pageSize(boundingRect.right(), boundingRect.bottom());
SvgWriter writer(shapes);
writer.save(buffer, pageSize);
buffer.close();
qDeleteAll(shapes);
setData(mimeType, buffer.data());
return true;
}
void KoDrag::setData(const QString &mimeType, const QByteArray &data)
{
if (d->mimeData == 0) {
d->mimeData = new QMimeData();
}
d->mimeData->setData(mimeType, data);
}
void KoDrag::addToClipboard()
{
if (d->mimeData) {
QApplication::clipboard()->setMimeData(d->mimeData);
d->mimeData = 0;
}
}
QMimeData * KoDrag::mimeData()
{
QMimeData *mimeData = d->mimeData;
d->mimeData = 0;
return mimeData;
}
diff --git a/libs/flake/KoFlake.cpp b/libs/flake/KoFlake.cpp
index ad46c89c67..fbda248c67 100644
--- a/libs/flake/KoFlake.cpp
+++ b/libs/flake/KoFlake.cpp
@@ -1,346 +1,346 @@
/* This file is part of the KDE project
* Copyright (C) 2009 Jos van den Oever <jos@vandenoever.info>
* Copyright (C) 2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2010 Thorsten Zachmann <zachmann@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 "KoFlake.h"
#include "KoShape.h"
#include <QGradient>
#include <math.h>
#include "kis_global.h"
QGradient *KoFlake::cloneGradient(const QGradient *gradient)
{
if (! gradient)
return 0;
QGradient *clone = 0;
switch (gradient->type()) {
case QGradient::LinearGradient:
{
const QLinearGradient *lg = static_cast<const QLinearGradient*>(gradient);
clone = new QLinearGradient(lg->start(), lg->finalStop());
break;
}
case QGradient::RadialGradient:
{
const QRadialGradient *rg = static_cast<const QRadialGradient*>(gradient);
clone = new QRadialGradient(rg->center(), rg->radius(), rg->focalPoint());
break;
}
case QGradient::ConicalGradient:
{
const QConicalGradient *cg = static_cast<const QConicalGradient*>(gradient);
clone = new QConicalGradient(cg->center(), cg->angle());
break;
}
default:
return 0;
}
clone->setCoordinateMode(gradient->coordinateMode());
clone->setSpread(gradient->spread());
clone->setStops(gradient->stops());
return clone;
}
QGradient *KoFlake::mergeGradient(const QGradient *coordsSource, const QGradient *fillSource)
{
QPointF start;
QPointF end;
QPointF focalPoint;
switch (coordsSource->type()) {
case QGradient::LinearGradient: {
const QLinearGradient *lg = static_cast<const QLinearGradient*>(coordsSource);
start = lg->start();
focalPoint = start;
end = lg->finalStop();
break;
}
case QGradient::RadialGradient: {
const QRadialGradient *rg = static_cast<const QRadialGradient*>(coordsSource);
start = rg->center();
end = start + QPointF(rg->radius(), 0);
focalPoint = rg->focalPoint();
break;
}
case QGradient::ConicalGradient: {
const QConicalGradient *cg = static_cast<const QConicalGradient*>(coordsSource);
start = cg->center();
focalPoint = start;
QLineF l (start, start + QPointF(1.0, 0));
l.setAngle(cg->angle());
end = l.p2();
break;
}
default:
return 0;
}
QGradient *clone = 0;
switch (fillSource->type()) {
case QGradient::LinearGradient:
clone = new QLinearGradient(start, end);
break;
case QGradient::RadialGradient:
clone = new QRadialGradient(start, kisDistance(start, end), focalPoint);
break;
case QGradient::ConicalGradient: {
QLineF l(start, end);
clone = new QConicalGradient(l.p1(), l.angle());
break;
}
default:
return 0;
}
clone->setCoordinateMode(fillSource->coordinateMode());
clone->setSpread(fillSource->spread());
clone->setStops(fillSource->stops());
return clone;
}
QPointF KoFlake::toRelative(const QPointF &absolute, const QSizeF &size)
{
return QPointF(size.width() == 0 ? 0: absolute.x() / size.width(),
size.height() == 0 ? 0: absolute.y() / size.height());
}
QPointF KoFlake::toAbsolute(const QPointF &relative, const QSizeF &size)
{
return QPointF(relative.x() * size.width(), relative.y() * size.height());
}
#include <QTransform>
#include "kis_debug.h"
#include "kis_algebra_2d.h"
namespace {
qreal getScaleByPointsPair(qreal x1, qreal x2, qreal expX1, qreal expX2)
{
static const qreal eps = 1e-10;
const qreal diff = x2 - x1;
const qreal expDiff = expX2 - expX1;
return qAbs(diff) > eps ? expDiff / diff : 1.0;
}
void findMinMaxPoints(const QPolygonF &poly, int *minPoint, int *maxPoint, std::function<qreal(const QPointF&)> dimension)
{
KIS_ASSERT_RECOVER_RETURN(minPoint);
KIS_ASSERT_RECOVER_RETURN(maxPoint);
qreal minValue = dimension(poly[*minPoint]);
qreal maxValue = dimension(poly[*maxPoint]);
for (int i = 0; i < poly.size(); i++) {
const qreal value = dimension(poly[i]);
if (value < minValue) {
*minPoint = i;
minValue = value;
}
if (value > maxValue) {
*maxPoint = i;
maxValue = value;
}
}
}
}
Qt::Orientation KoFlake::significantScaleOrientation(qreal scaleX, qreal scaleY)
{
const qreal scaleXDeviation = qAbs(1.0 - scaleX);
const qreal scaleYDeviation = qAbs(1.0 - scaleY);
return scaleXDeviation > scaleYDeviation ? Qt::Horizontal : Qt::Vertical;
}
void KoFlake::resizeShape(KoShape *shape, qreal scaleX, qreal scaleY,
const QPointF &absoluteStillPoint,
bool useGlobalMode,
bool usePostScaling, const QTransform &postScalingCoveringTransform)
{
- QPointF localStillPoint = shape->absoluteTransformation(0).inverted().map(absoluteStillPoint);
+ QPointF localStillPoint = shape->absoluteTransformation().inverted().map(absoluteStillPoint);
QPointF relativeStillPoint = KisAlgebra2D::absoluteToRelative(localStillPoint, shape->outlineRect());
QPointF parentalStillPointBefore = shape->transformation().map(localStillPoint);
if (usePostScaling) {
const QTransform scale = QTransform::fromScale(scaleX, scaleY);
if (!useGlobalMode) {
shape->setTransformation(shape->transformation() *
postScalingCoveringTransform.inverted() *
scale * postScalingCoveringTransform);
} else {
const QTransform uniformGlobalTransform =
- shape->absoluteTransformation(0) *
+ shape->absoluteTransformation() *
scale *
- shape->absoluteTransformation(0).inverted() *
+ shape->absoluteTransformation().inverted() *
shape->transformation();
shape->setTransformation(uniformGlobalTransform);
}
} else {
using namespace KisAlgebra2D;
if (useGlobalMode) {
const QTransform scale = QTransform::fromScale(scaleX, scaleY);
const QTransform uniformGlobalTransform =
- shape->absoluteTransformation(0) *
+ shape->absoluteTransformation() *
scale *
- shape->absoluteTransformation(0).inverted();
+ shape->absoluteTransformation().inverted();
const QRectF rect = shape->outlineRect();
/**
* The basic idea of such global scaling:
*
* 1) We choose two the most distant points of the original outline rect
* 2) Calculate their expected position if transformed using `uniformGlobalTransform`
* 3) NOTE1: we do not transform the entire shape using `uniformGlobalTransform`,
* because it will cause massive shearing. We transform only two points
* and adjust other points using dumb scaling.
* 4) NOTE2: given that `scale` transform is much more simpler than
* `uniformGlobalTransform`, we cannot guarantee equivalent changes on
* both globalScaleX and globalScaleY at the same time. We can guarantee
* only one of them. Therefore we select the most "important" axis and
* guarantee scael along it. The scale along the other direction is not
* controlled.
* 5) After we have the two most distant points, we can just calculate the scale
* by dividing difference between their expected and original positions. This
* formula can be derived from equation:
*
* localPoint_i * ScaleMatrix = localPoint_i * UniformGlobalTransform = expectedPoint_i
*/
// choose the most significant scale direction
Qt::Orientation significantOrientation = significantScaleOrientation(scaleX, scaleY);
std::function<qreal(const QPointF&)> dimension;
if (significantOrientation == Qt::Horizontal) {
dimension = [] (const QPointF &pt) {
return pt.x();
};
} else {
dimension = [] (const QPointF &pt) {
return pt.y();
};
}
// find min and max points (in absolute coordinates),
// by default use top-left and bottom-right
QPolygonF localPoints(rect);
- QPolygonF globalPoints = shape->absoluteTransformation(0).map(localPoints);
+ QPolygonF globalPoints = shape->absoluteTransformation().map(localPoints);
int minPointIndex = 0;
int maxPointIndex = 2;
findMinMaxPoints(globalPoints, &minPointIndex, &maxPointIndex, dimension);
// calculate the scale using the extremum points
const QPointF minPoint = localPoints[minPointIndex];
const QPointF maxPoint = localPoints[maxPointIndex];
const QPointF minPointExpected = uniformGlobalTransform.map(minPoint);
const QPointF maxPointExpected = uniformGlobalTransform.map(maxPoint);
scaleX = getScaleByPointsPair(minPoint.x(), maxPoint.x(),
minPointExpected.x(), maxPointExpected.x());
scaleY = getScaleByPointsPair(minPoint.y(), maxPoint.y(),
minPointExpected.y(), maxPointExpected.y());
}
const QSizeF oldSize(shape->size());
const QSizeF newSize(oldSize.width() * qAbs(scaleX), oldSize.height() * qAbs(scaleY));
const QTransform mirrorTransform = QTransform::fromScale(signPZ(scaleX), signPZ(scaleY));
shape->setSize(newSize);
if (!mirrorTransform.isIdentity()) {
shape->setTransformation(mirrorTransform * shape->transformation());
}
}
QPointF newLocalStillPoint = KisAlgebra2D::relativeToAbsolute(relativeStillPoint, shape->outlineRect());
QPointF parentalStillPointAfter = shape->transformation().map(newLocalStillPoint);
QPointF diff = parentalStillPointBefore - parentalStillPointAfter;
shape->setTransformation(shape->transformation() * QTransform::fromTranslate(diff.x(), diff.y()));
}
QPointF KoFlake::anchorToPoint(AnchorPosition anchor, const QRectF rect, bool *valid)
{
static QVector<QPointF> anchorTable;
if (anchorTable.isEmpty()) {
anchorTable << QPointF(0.0,0.0);
anchorTable << QPointF(0.5,0.0);
anchorTable << QPointF(1.0,0.0);
anchorTable << QPointF(0.0,0.5);
anchorTable << QPointF(0.5,0.5);
anchorTable << QPointF(1.0,0.5);
anchorTable << QPointF(0.0,1.0);
anchorTable << QPointF(0.5,1.0);
anchorTable << QPointF(1.0,1.0);
}
if (valid)
*valid = false;
switch(anchor)
{
case AnchorPosition::TopLeft:
case AnchorPosition::Top:
case AnchorPosition::TopRight:
case AnchorPosition::Left:
case AnchorPosition::Center:
case AnchorPosition::Right:
case AnchorPosition::BottomLeft:
case AnchorPosition::Bottom:
case AnchorPosition::BottomRight:
if (valid)
*valid = true;
return KisAlgebra2D::relativeToAbsolute(anchorTable[int(anchor)], rect);
default:
KIS_SAFE_ASSERT_RECOVER_NOOP(anchor >= AnchorPosition::TopLeft && anchor < AnchorPosition::NumAnchorPositions);
return rect.topLeft();
}
}
diff --git a/libs/flake/KoGradientBackground.cpp b/libs/flake/KoGradientBackground.cpp
index 39d5dadff4..a4f804d111 100644
--- a/libs/flake/KoGradientBackground.cpp
+++ b/libs/flake/KoGradientBackground.cpp
@@ -1,183 +1,183 @@
/* This file is part of the KDE project
* Copyright (C) 2008 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 "KoGradientBackground.h"
#include "KoFlake.h"
#include <KoStyleStack.h>
#include <KoXmlNS.h>
#include <KoOdfLoadingContext.h>
#include <KoOdfGraphicStyles.h>
#include <KoShapeSavingContext.h>
#include <FlakeDebug.h>
#include <QSharedPointer>
#include <QBrush>
#include <QPainter>
#include <QSharedData>
class KoGradientBackground::Private : public QSharedData
{
public:
Private()
: QSharedData()
, gradient(0)
{}
QGradient *gradient;
QTransform matrix;
};
KoGradientBackground::KoGradientBackground(QGradient * gradient, const QTransform &matrix)
: KoShapeBackground()
, d(new Private)
{
d->gradient = gradient;
d->matrix = matrix;
Q_ASSERT(d->gradient);
}
KoGradientBackground::KoGradientBackground(const QGradient & gradient, const QTransform &matrix)
: KoShapeBackground()
, d(new Private)
{
d->gradient = KoFlake::cloneGradient(&gradient);
d->matrix = matrix;
Q_ASSERT(d->gradient);
}
KoGradientBackground::~KoGradientBackground()
{
delete d->gradient;
}
bool KoGradientBackground::compareTo(const KoShapeBackground *other) const
{
const KoGradientBackground *otherGradient = dynamic_cast<const KoGradientBackground*>(other);
return otherGradient &&
d->matrix == otherGradient->d->matrix &&
*d->gradient == *otherGradient->d->gradient;
}
void KoGradientBackground::setTransform(const QTransform &matrix)
{
d->matrix = matrix;
}
QTransform KoGradientBackground::transform() const
{
return d->matrix;
}
void KoGradientBackground::setGradient(const QGradient &gradient)
{
delete d->gradient;
d->gradient = KoFlake::cloneGradient(&gradient);
Q_ASSERT(d->gradient);
}
const QGradient * KoGradientBackground::gradient() const
{
return d->gradient;
}
-void KoGradientBackground::paint(QPainter &painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const
+void KoGradientBackground::paint(QPainter &painter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const
{
if (!d->gradient) return;
if (d->gradient->coordinateMode() == QGradient::ObjectBoundingMode) {
/**
* NOTE: important hack!
*
* Qt has different notation of QBrush::setTransform() in comparison
* to what SVG defines. SVG defines gradientToUser matrix to be postmultiplied
* by QBrush::transform(), but Qt does exactly reverse!
*
* That most probably has beed caused by the fact that Qt uses transposed
* matrices and someone just mistyped the stuff long ago :(
*
* So here we basically emulate this feature by converting the gradient into
* QGradient::LogicalMode and doing transformations manually.
*/
const QRectF boundingRect = fillPath.boundingRect();
QTransform gradientToUser(boundingRect.width(), 0, 0, boundingRect.height(),
boundingRect.x(), boundingRect.y());
// TODO: how about slicing the object?
QGradient g = *d->gradient;
g.setCoordinateMode(QGradient::LogicalMode);
QBrush b(g);
b.setTransform(d->matrix * gradientToUser);
painter.setBrush(b);
} else {
QBrush b(*d->gradient);
b.setTransform(d->matrix);
painter.setBrush(b);
}
painter.drawPath(fillPath);
}
void KoGradientBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context)
{
if (!d->gradient) return;
QBrush brush(*d->gradient);
brush.setTransform(d->matrix);
KoOdfGraphicStyles::saveOdfFillStyle(style, context.mainStyles(), brush);
}
bool KoGradientBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize)
{
KoStyleStack &styleStack = context.styleStack();
if (! styleStack.hasProperty(KoXmlNS::draw, "fill"))
return false;
QString fillStyle = styleStack.property(KoXmlNS::draw, "fill");
if (fillStyle == "gradient") {
QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyle(styleStack, context.stylesReader(), shapeSize);
const QGradient * gradient = brush.gradient();
if (gradient) {
d->gradient = KoFlake::cloneGradient(gradient);
d->matrix = brush.transform();
//Gopalakrishna Bhat: If the brush has transparency then we ignore the draw:opacity property and use the brush transparency.
// Brush will have transparency if the svg:linearGradient stop point has stop-opacity property otherwise it is opaque
if (brush.isOpaque() && styleStack.hasProperty(KoXmlNS::draw, "opacity")) {
QString opacityPercent = styleStack.property(KoXmlNS::draw, "opacity");
if (! opacityPercent.isEmpty() && opacityPercent.right(1) == "%") {
float opacity = qMin(opacityPercent.left(opacityPercent.length() - 1).toDouble(), 100.0) / 100;
QGradientStops stops;
Q_FOREACH (QGradientStop stop, d->gradient->stops()) {
stop.second.setAlphaF(opacity);
stops << stop;
}
d->gradient->setStops(stops);
}
}
return true;
}
}
return false;
}
diff --git a/libs/flake/KoGradientBackground.h b/libs/flake/KoGradientBackground.h
index 800603108a..71955abafe 100644
--- a/libs/flake/KoGradientBackground.h
+++ b/libs/flake/KoGradientBackground.h
@@ -1,78 +1,78 @@
/* This file is part of the KDE project
* Copyright (C) 2008 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.
*/
#ifndef KOGRADIENTBACKGROUND_H
#define KOGRADIENTBACKGROUND_H
#include "KoShapeBackground.h"
#include "kritaflake_export.h"
#include <QTransform>
#include <QSharedDataPointer>
class QGradient;
/// A gradient shape background
class KRITAFLAKE_EXPORT KoGradientBackground : public KoShapeBackground
{
public:
/**
* Creates new gradient background from given gradient.
* The background takes ownership of the given gradient.
*/
explicit KoGradientBackground(QGradient *gradient, const QTransform &matrix = QTransform());
/**
* Create new gradient background from the given gradient.
* A clone of the given gradient is used.
*/
explicit KoGradientBackground(const QGradient &gradient, const QTransform &matrix = QTransform());
/// Destroys the background
~KoGradientBackground() override;
bool compareTo(const KoShapeBackground *other) const override;
/// Sets the transform matrix
void setTransform(const QTransform &matrix);
/// Returns the transform matrix
QTransform transform() const;
/**
* Sets a new gradient.
* A clone of the given gradient is used.
*/
void setGradient(const QGradient &gradient);
/// Returns the gradient
const QGradient *gradient() const;
/// reimplemented from KoShapeBackground
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override;
+ void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override;
/// reimplemented from KoShapeBackground
void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override;
/// reimplemented from KoShapeBackground
bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override;
private:
class Private;
QSharedDataPointer<Private> d;
};
#endif // KOGRADIENTBACKGROUND_H
diff --git a/libs/flake/KoHatchBackground.cpp b/libs/flake/KoHatchBackground.cpp
index 844df15973..b4d20be657 100644
--- a/libs/flake/KoHatchBackground.cpp
+++ b/libs/flake/KoHatchBackground.cpp
@@ -1,237 +1,237 @@
/* This file is part of the KDE project
*
* Copyright (C) 2012 Thorsten Zachmann <zachmann@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 "KoHatchBackground.h"
#include <KoOdfLoadingContext.h>
#include <KoStyleStack.h>
#include <KoShapeSavingContext.h>
#include <KoGenStyles.h>
#include <KoGenStyle.h>
#include <KoXmlNS.h>
#include <KoUnit.h>
#include <KoOdfStylesReader.h>
#include <KoXmlReader.h>
#include <FlakeDebug.h>
#include <QColor>
#include <QString>
#include <QPainter>
class KoHatchBackground::Private : public QSharedData
{
public:
Private()
: QSharedData()
, angle(0.0)
, distance(1.0)
, style(KoHatchBackground::Single)
{}
QColor lineColor;
int angle;
qreal distance;
KoHatchBackground::HatchStyle style;
QString name;
};
KoHatchBackground::KoHatchBackground()
: KoColorBackground()
, d(new Private)
{
}
KoHatchBackground::~KoHatchBackground()
{
}
-void KoHatchBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const
+void KoHatchBackground::paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const
{
if (color().isValid()) {
// paint background color if set by using the color background
- KoColorBackground::paint(painter, converter, context, fillPath);
+ KoColorBackground::paint(painter, context, fillPath);
}
const QRectF targetRect = fillPath.boundingRect();
painter.save();
painter.setClipPath(fillPath);
QPen pen(d->lineColor);
// we set the pen width to 0.5 pt for the hatch. This is not defined in the spec.
pen.setWidthF(0.5);
painter.setPen(pen);
QVector<QLineF> lines;
// The different styles are handled by painting the lines multiple times with a different
// angel offset as basically it just means we paint the lines also at a different angle.
// This are the angle offsets we need to apply to the different lines of a style.
// -90 is for single, 0 for the 2nd line in double and -45 for the 3th line in triple.
const int angleOffset[] = {-90, 0, -45 };
// The number of loops is defined by the style.
int loops = (d->style == Single) ? 1 : (d->style == Double) ? 2 : 3;
for (int i = 0; i < loops; ++i) {
int angle = d->angle - angleOffset[i];
qreal cosAngle = ::cos(angle/180.0*M_PI);
// if cos is nearly 0 the lines are horizontal. Use a special case for that
if (qAbs(cosAngle) > 0.00001) {
qreal xDiff = tan(angle/180.0*M_PI) * targetRect.height();
// calculate the distance we need to increase x when creating the lines so that the
// distance between the lines is also correct for rotated lines.
qreal xOffset = qAbs(d->distance / cosAngle);
// if the lines go to the right we need to start more to the left. Get the correct start.
qreal xStart = 0;
while (-xDiff < xStart) {
xStart -= xOffset;
}
// if the lines go to the left we need to stop more at the right. Get the correct end offset
qreal xEndOffset = 0;
if (xDiff < 0) {
while (xDiff < -xEndOffset) {
xEndOffset += xOffset;
}
}
// create line objects.
lines.reserve(lines.size() + int((targetRect.width() + xEndOffset - xStart) / xOffset) + 1);
for (qreal x = xStart; x < targetRect.width() + xEndOffset; x += xOffset) {
lines.append(QLineF(x, 0, x + xDiff, targetRect.height()));
}
}
else {
// horizontal lines
lines.reserve(lines.size() + int(targetRect.height()/d->distance) + 1);
for (qreal y = 0; y < targetRect.height(); y += d->distance) {
lines.append(QLineF(0, y, targetRect.width(), y));
}
}
}
painter.drawLines(lines);
painter.restore();
}
void KoHatchBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context)
{
KoGenStyle::Type type = style.type();
KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle ||
type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle )
? KoGenStyle::DefaultType : KoGenStyle::GraphicType;
style.addProperty("draw:fill", "hatch", propertyType);
style.addProperty("draw:fill-hatch-name", saveHatchStyle(context), propertyType);
bool fillHatchSolid = color().isValid();
style.addProperty("draw:fill-hatch-solid", fillHatchSolid, propertyType);
if (fillHatchSolid) {
style.addProperty("draw:fill-color", color().name(), propertyType);
}
}
QString KoHatchBackground::saveHatchStyle(KoShapeSavingContext &context) const
{
KoGenStyle hatchStyle(KoGenStyle::HatchStyle /*no family name*/);
hatchStyle.addAttribute("draw:display-name", d->name);
hatchStyle.addAttribute("draw:color", d->lineColor.name());
hatchStyle.addAttribute("draw:distance", d->distance);
hatchStyle.addAttribute("draw:rotation", QString("%1").arg(d->angle * 10));
switch (d->style) {
case Single:
hatchStyle.addAttribute("draw:style", "single");
break;
case Double:
hatchStyle.addAttribute("draw:style", "double");
break;
case Triple:
hatchStyle.addAttribute("draw:style", "triple");
break;
}
return context.mainStyles().insert(hatchStyle, "hatch");
}
bool KoHatchBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize)
{
// <draw:hatch draw:name="hatchStyle3" draw:color="#000000" draw:display-name="#000000 Vertical" draw:distance="0.102cm" draw:rotation="900" draw:style="single"/>
Q_UNUSED(shapeSize);
KoStyleStack &styleStack = context.styleStack();
QString fillStyle = styleStack.property(KoXmlNS::draw, "fill");
if (fillStyle == "hatch") {
QString style = styleStack.property(KoXmlNS::draw, "fill-hatch-name");
debugFlake << " hatch style is :" << style;
KoXmlElement* draw = context.stylesReader().drawStyles("hatch")[style];
if (draw) {
debugFlake << "Hatch style found for:" << style;
QString angle = draw->attributeNS(KoXmlNS::draw, "rotation", QString("0"));
if (angle.at(angle.size()-1).isLetter()) {
d->angle = KoUnit::parseAngle(angle);
}
else {
// OO saves the angle value without unit and multiplied by a factor of 10
d->angle = int(angle.toInt() / 10);
}
debugFlake << "angle :" << d->angle;
d->name = draw->attributeNS(KoXmlNS::draw, "display-name");
// use 2mm as default, just in case it is not given in a document so we show something sensible.
d->distance = KoUnit::parseValue(draw->attributeNS(KoXmlNS::draw, "distance", "2mm"));
bool fillHatchSolid = styleStack.property(KoXmlNS::draw, "fill-hatch-solid") == QLatin1String("true");
if (fillHatchSolid) {
QString fillColor = styleStack.property(KoXmlNS::draw, "fill-color");
if (!fillColor.isEmpty()) {
QColor c = color();
c.setNamedColor(fillColor);
setColor(c);
}
else {
setColor(QColor());
}
}
else {
setColor(QColor());
}
d->lineColor.setNamedColor(draw->attributeNS(KoXmlNS::draw, "color", QString("#000000")));
QString style = draw->attributeNS(KoXmlNS::draw, "style", QString());
if (style == "double") {
d->style = Double;
}
else if (style == "triple") {
d->style = Triple;
}
else {
d->style = Single;
}
}
return true;
}
return false;
}
diff --git a/libs/flake/KoHatchBackground.h b/libs/flake/KoHatchBackground.h
index e4d09690e5..3f50301c17 100644
--- a/libs/flake/KoHatchBackground.h
+++ b/libs/flake/KoHatchBackground.h
@@ -1,58 +1,58 @@
/* This file is part of the KDE project
*
* Copyright (C) 2012 Thorsten Zachmann <zachmann@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 KOHATCHBACKGROUND_H
#define KOHATCHBACKGROUND_H
#include "KoColorBackground.h"
/**
* A hatch shape background
*/
class KoHatchBackground : public KoColorBackground
{
public:
enum HatchStyle {
Single,
Double,
Triple
};
KoHatchBackground();
~KoHatchBackground() override;
// reimplemented
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override;
+ void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override;
// reimplemented
void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override;
// reimplemented
bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override;
private:
QString saveHatchStyle(KoShapeSavingContext &context) const;
private:
class Private;
QSharedDataPointer<Private> d;
};
#endif /* KOHATCHBACKGROUND_H */
diff --git a/libs/flake/KoMarker.cpp b/libs/flake/KoMarker.cpp
index 278950856a..a27b20a915 100644
--- a/libs/flake/KoMarker.cpp
+++ b/libs/flake/KoMarker.cpp
@@ -1,419 +1,416 @@
/* This file is part of the KDE project
Copyright (C) 2011 Thorsten Zachmann <zachmann@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 "KoMarker.h"
#include <KoXmlReader.h>
#include <KoXmlNS.h>
#include <KoGenStyle.h>
#include <KoGenStyles.h>
#include "KoPathShape.h"
#include "KoPathShapeLoader.h"
#include "KoShapeLoadingContext.h"
#include "KoShapeSavingContext.h"
#include "KoOdfWorkaround.h"
#include "KoShapePainter.h"
-#include "KoViewConverter.h"
#include <KoShapeStroke.h>
#include <KoGradientBackground.h>
#include <KoColorBackground.h>
#include <QString>
#include <QUrl>
#include <QPainterPath>
#include <QPainter>
#include "kis_global.h"
#include "kis_algebra_2d.h"
class Q_DECL_HIDDEN KoMarker::Private
{
public:
Private()
: coordinateSystem(StrokeWidth),
referenceSize(3,3),
hasAutoOrientation(false),
explicitOrientation(0)
{}
~Private() {
// shape manager that is stored in the painter should be destroyed
// before the shapes themselves
shapePainter.reset();
qDeleteAll(shapes);
}
bool operator==(const KoMarker::Private &other) const
{
// WARNING: comparison of shapes is extremely fuzzy! Don't
// trust it in life-critical cases!
return name == other.name &&
coordinateSystem == other.coordinateSystem &&
referencePoint == other.referencePoint &&
referenceSize == other.referenceSize &&
hasAutoOrientation == other.hasAutoOrientation &&
explicitOrientation == other.explicitOrientation &&
compareShapesTo(other.shapes);
}
Private(const Private &rhs)
: name(rhs.name),
coordinateSystem(rhs.coordinateSystem),
referencePoint(rhs.referencePoint),
referenceSize(rhs.referenceSize),
hasAutoOrientation(rhs.hasAutoOrientation),
explicitOrientation(rhs.explicitOrientation)
{
Q_FOREACH (KoShape *shape, rhs.shapes) {
shapes << shape->cloneShape();
}
}
QString name;
MarkerCoordinateSystem coordinateSystem;
QPointF referencePoint;
QSizeF referenceSize;
bool hasAutoOrientation;
qreal explicitOrientation;
QList<KoShape*> shapes;
QScopedPointer<KoShapePainter> shapePainter;
bool compareShapesTo(const QList<KoShape*> other) const {
if (shapes.size() != other.size()) return false;
for (int i = 0; i < shapes.size(); i++) {
if (shapes[i]->outline() != other[i]->outline() ||
- shapes[i]->absoluteTransformation(0) != other[i]->absoluteTransformation(0)) {
+ shapes[i]->absoluteTransformation() != other[i]->absoluteTransformation()) {
return false;
}
}
return true;
}
QTransform markerTransform(qreal strokeWidth, qreal nodeAngle, const QPointF &pos = QPointF()) {
const QTransform translate = QTransform::fromTranslate(-referencePoint.x(), -referencePoint.y());
QTransform t = translate;
if (coordinateSystem == StrokeWidth) {
t *= QTransform::fromScale(strokeWidth, strokeWidth);
}
const qreal angle = hasAutoOrientation ? nodeAngle : explicitOrientation;
if (angle != 0.0) {
QTransform r;
r.rotateRadians(angle);
t *= r;
}
t *= QTransform::fromTranslate(pos.x(), pos.y());
return t;
}
};
KoMarker::KoMarker()
: d(new Private())
{
}
KoMarker::~KoMarker()
{
delete d;
}
QString KoMarker::name() const
{
return d->name;
}
KoMarker::KoMarker(const KoMarker &rhs)
: QSharedData(rhs),
d(new Private(*rhs.d))
{
}
bool KoMarker::operator==(const KoMarker &other) const
{
return *d == *other.d;
}
void KoMarker::setCoordinateSystem(KoMarker::MarkerCoordinateSystem value)
{
d->coordinateSystem = value;
}
KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystem() const
{
return d->coordinateSystem;
}
KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystemFromString(const QString &value)
{
MarkerCoordinateSystem result = StrokeWidth;
if (value == "userSpaceOnUse") {
result = UserSpaceOnUse;
}
return result;
}
QString KoMarker::coordinateSystemToString(KoMarker::MarkerCoordinateSystem value)
{
return
value == StrokeWidth ?
"strokeWidth" :
"userSpaceOnUse";
}
void KoMarker::setReferencePoint(const QPointF &value)
{
d->referencePoint = value;
}
QPointF KoMarker::referencePoint() const
{
return d->referencePoint;
}
void KoMarker::setReferenceSize(const QSizeF &size)
{
d->referenceSize = size;
}
QSizeF KoMarker::referenceSize() const
{
return d->referenceSize;
}
bool KoMarker::hasAutoOtientation() const
{
return d->hasAutoOrientation;
}
void KoMarker::setAutoOrientation(bool value)
{
d->hasAutoOrientation = value;
}
qreal KoMarker::explicitOrientation() const
{
return d->explicitOrientation;
}
void KoMarker::setExplicitOrientation(qreal value)
{
d->explicitOrientation = value;
}
void KoMarker::setShapes(const QList<KoShape *> &shapes)
{
d->shapes = shapes;
if (d->shapePainter) {
d->shapePainter->setShapes(shapes);
}
}
QList<KoShape *> KoMarker::shapes() const
{
return d->shapes;
}
void KoMarker::paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle)
{
QTransform oldTransform = painter->transform();
- KoViewConverter converter;
-
if (!d->shapePainter) {
d->shapePainter.reset(new KoShapePainter());
d->shapePainter->setShapes(d->shapes);
}
painter->setTransform(d->markerTransform(strokeWidth, nodeAngle, pos), true);
- d->shapePainter->paint(*painter, converter);
+ d->shapePainter->paint(*painter);
painter->setTransform(oldTransform);
}
qreal KoMarker::maxInset(qreal strokeWidth) const
{
QRectF shapesBounds = boundingRect(strokeWidth, 0.0); // normalized to 0,0
qreal result = 0.0;
result = qMax(KisAlgebra2D::norm(shapesBounds.topLeft()), result);
result = qMax(KisAlgebra2D::norm(shapesBounds.topRight()), result);
result = qMax(KisAlgebra2D::norm(shapesBounds.bottomLeft()), result);
result = qMax(KisAlgebra2D::norm(shapesBounds.bottomRight()), result);
if (d->coordinateSystem == StrokeWidth) {
result *= strokeWidth;
}
return result;
}
QRectF KoMarker::boundingRect(qreal strokeWidth, qreal nodeAngle) const
{
QRectF shapesBounds = KoShape::boundingRect(d->shapes);
const QTransform t = d->markerTransform(strokeWidth, nodeAngle);
if (!t.isIdentity()) {
shapesBounds = t.mapRect(shapesBounds);
}
return shapesBounds;
}
QPainterPath KoMarker::outline(qreal strokeWidth, qreal nodeAngle) const
{
QPainterPath outline;
Q_FOREACH (KoShape *shape, d->shapes) {
- outline |= shape->absoluteTransformation(0).map(shape->outline());
+ outline |= shape->absoluteTransformation().map(shape->outline());
}
const QTransform t = d->markerTransform(strokeWidth, nodeAngle);
if (!t.isIdentity()) {
outline = t.map(outline);
}
return outline;
}
void KoMarker::drawPreview(QPainter *painter, const QRectF &previewRect, const QPen &pen, KoFlake::MarkerPosition position)
{
const QRectF outlineRect = outline(pen.widthF(), 0).boundingRect(); // normalized to 0,0
QPointF marker;
QPointF start;
QPointF end;
if (position == KoFlake::StartMarker) {
marker = QPointF(-outlineRect.left() + previewRect.left(), previewRect.center().y());
start = marker;
end = QPointF(previewRect.right(), start.y());
} else if (position == KoFlake::MidMarker) {
start = QPointF(previewRect.left(), previewRect.center().y());
marker = QPointF(-outlineRect.center().x() + previewRect.center().x(), start.y());
end = QPointF(previewRect.right(), start.y());
} else if (position == KoFlake::EndMarker) {
start = QPointF(previewRect.left(), previewRect.center().y());
marker = QPointF(-outlineRect.right() + previewRect.right(), start.y());
end = marker;
}
painter->save();
painter->setPen(pen);
painter->setClipRect(previewRect);
painter->drawLine(start, end);
paintAtPosition(painter, marker, pen.widthF(), 0);
painter->restore();
}
-void KoMarker::applyShapeStroke(KoShape *parentShape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle)
+void KoMarker::applyShapeStroke(const KoShape *parentShape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle)
{
const QGradient *originalGradient = stroke->lineBrush().gradient();
if (!originalGradient) {
QList<KoShape*> linearizedShapes = KoShape::linearizeSubtree(d->shapes);
Q_FOREACH(KoShape *shape, linearizedShapes) {
// update the stroke
KoShapeStrokeSP shapeStroke = shape->stroke() ?
qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) :
KoShapeStrokeSP();
if (shapeStroke) {
shapeStroke = toQShared(new KoShapeStroke(*shapeStroke));
shapeStroke->setLineBrush(QBrush());
shapeStroke->setColor(stroke->color());
shape->setStroke(shapeStroke);
}
// update the background
if (shape->background()) {
QSharedPointer<KoColorBackground> bg(new KoColorBackground(stroke->color()));
shape->setBackground(bg);
}
}
} else {
QScopedPointer<QGradient> g(KoFlake::cloneGradient(originalGradient));
KIS_ASSERT_RECOVER_RETURN(g);
const QTransform markerTransformInverted =
d->markerTransform(strokeWidth, nodeAngle, pos).inverted();
QTransform gradientToUser;
// Unwrap the gradient to work in global mode
if (g->coordinateMode() == QGradient::ObjectBoundingMode) {
QRectF boundingRect =
parentShape ?
parentShape->outline().boundingRect() :
this->boundingRect(strokeWidth, nodeAngle);
boundingRect = KisAlgebra2D::ensureRectNotSmaller(boundingRect, QSizeF(1.0, 1.0));
gradientToUser = QTransform(boundingRect.width(), 0, 0, boundingRect.height(),
boundingRect.x(), boundingRect.y());
g->setCoordinateMode(QGradient::LogicalMode);
}
QList<KoShape*> linearizedShapes = KoShape::linearizeSubtree(d->shapes);
Q_FOREACH(KoShape *shape, linearizedShapes) {
// shape-unwinding transform
- QTransform t = gradientToUser * markerTransformInverted * shape->absoluteTransformation(0).inverted();
+ QTransform t = gradientToUser * markerTransformInverted * shape->absoluteTransformation().inverted();
// update the stroke
KoShapeStrokeSP shapeStroke = shape->stroke() ?
qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) :
KoShapeStrokeSP();
if (shapeStroke) {
shapeStroke = toQShared(new KoShapeStroke(*shapeStroke));
QBrush brush(*g);
brush.setTransform(t);
shapeStroke->setLineBrush(brush);
shapeStroke->setColor(Qt::transparent);
shape->setStroke(shapeStroke);
}
// update the background
if (shape->background()) {
QSharedPointer<KoGradientBackground> bg(new KoGradientBackground(KoFlake::cloneGradient(g.data()), t));
shape->setBackground(bg);
}
}
}
}
diff --git a/libs/flake/KoMarker.h b/libs/flake/KoMarker.h
index 2413ced08b..2e1d3a6dea 100644
--- a/libs/flake/KoMarker.h
+++ b/libs/flake/KoMarker.h
@@ -1,123 +1,123 @@
/* This file is part of the KDE project
Copyright (C) 2011 Thorsten Zachmann <zachmann@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 KOMARKER_H
#define KOMARKER_H
#include <QMetaType>
#include <QSharedData>
#include "kritaflake_export.h"
#include <KoFlake.h>
#include <KoXmlReaderForward.h>
class KoShapeLoadingContext;
class KoShapeSavingContext;
class QString;
class QPainterPath;
class KoShape;
class QPainter;
class KoShapeStroke;
class KRITAFLAKE_EXPORT KoMarker : public QSharedData
{
public:
KoMarker();
~KoMarker();
/**
* Display name of the marker
*
* @return Display name of the marker
*/
QString name() const;
KoMarker(const KoMarker &rhs);
bool operator==(const KoMarker &other) const;
enum MarkerCoordinateSystem {
StrokeWidth,
UserSpaceOnUse
};
void setCoordinateSystem(MarkerCoordinateSystem value);
MarkerCoordinateSystem coordinateSystem() const;
static MarkerCoordinateSystem coordinateSystemFromString(const QString &value);
static QString coordinateSystemToString(MarkerCoordinateSystem value);
void setReferencePoint(const QPointF &value);
QPointF referencePoint() const;
void setReferenceSize(const QSizeF &size);
QSizeF referenceSize() const;
bool hasAutoOtientation() const;
void setAutoOrientation(bool value);
// measured in radians!
qreal explicitOrientation() const;
// measured in radians!
void setExplicitOrientation(qreal value);
void setShapes(const QList<KoShape*> &shapes);
QList<KoShape*> shapes() const;
/**
* @brief paintAtOrigin paints the marker at the position \p pos.
* Scales and rotates the masrker if needed.
*/
void paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle);
/**
* Return maximum distance that the marker can take outside the shape itself
*/
qreal maxInset(qreal strokeWidth) const;
/**
* Bounding rect of the marker in local coordinates. It is assumed that the marker
* is painted with the reference point placed at position (0,0)
*/
QRectF boundingRect(qreal strokeWidth, qreal nodeAngle) const;
/**
* Outline of the marker in local coordinates. It is assumed that the marker
* is painted with the reference point placed at position (0,0)
*/
QPainterPath outline(qreal strokeWidth, qreal nodeAngle) const;
/**
* Draws a preview of the marker in \p previewRect of \p painter
*/
void drawPreview(QPainter *painter, const QRectF &previewRect,
const QPen &pen, KoFlake::MarkerPosition position);
- void applyShapeStroke(KoShape *shape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle);
+ void applyShapeStroke(const KoShape *shape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle);
private:
class Private;
Private * const d;
};
Q_DECLARE_METATYPE(KoMarker*)
#endif /* KOMARKER_H */
diff --git a/libs/flake/KoOdfGradientBackground.cpp b/libs/flake/KoOdfGradientBackground.cpp
index 68cb015e5a..7fb8971984 100644
--- a/libs/flake/KoOdfGradientBackground.cpp
+++ b/libs/flake/KoOdfGradientBackground.cpp
@@ -1,379 +1,379 @@
/* This file is part of the KDE project
*
* Copyright (C) 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.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 "KoOdfGradientBackground.h"
#include "KoShapeSavingContext.h"
#include <KoUnit.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoGenStyles.h>
#include <KoOdfLoadingContext.h>
#include <KoStyleStack.h>
#include <KoOdfStylesReader.h>
#include <QPainter>
#include <QColor>
#include <QImage>
#include <qmath.h>
#include <QSharedData>
#include "FlakeDebug.h"
class KoOdfGradientBackground::Private : public QSharedData
{
public:
Private()
: QSharedData()
, style()
, cx(0)
, cy(0)
, startColor()
, endColor()
, angle(0)
, border(0)
, opacity(1.0)
{}
~Private() = default;
//data
QString style;
int cx;
int cy;
QColor startColor;
QColor endColor;
qreal angle;
qreal border;
qreal opacity;
};
KoOdfGradientBackground::KoOdfGradientBackground()
: KoShapeBackground()
, d(new Private)
{
}
KoOdfGradientBackground::~KoOdfGradientBackground()
{
}
bool KoOdfGradientBackground::compareTo(const KoShapeBackground *other) const
{
Q_UNUSED(other);
return false;
}
bool KoOdfGradientBackground::loadOdf(const KoXmlElement& e)
{
d->style = e.attributeNS(KoXmlNS::draw, "style", QString());
//TODO: support ellipsoid here too
if ((d->style != "rectangular") && (d->style != "square")) {
return false;
}
d->cx = KoUnit::parseValue(e.attributeNS(KoXmlNS::draw, "cx", QString()).remove('%'));
d->cy = KoUnit::parseValue(e.attributeNS(KoXmlNS::draw, "cy", QString()).remove('%'));
d->border = qBound(0.0,0.01 * e.attributeNS(KoXmlNS::draw, "border", "0").remove('%').toDouble(),1.0);
d->startColor = QColor(e.attributeNS(KoXmlNS::draw, "start-color", QString()));
d->startColor.setAlphaF((0.01 * e.attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble()));
d->endColor = QColor(e.attributeNS(KoXmlNS::draw, "end-color", QString()));
d->endColor.setAlphaF(0.01 * e.attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble());
d->angle = e.attributeNS(KoXmlNS::draw, "angle", "0").toDouble() / 10;
return true;
}
void KoOdfGradientBackground::saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const
{
KoGenStyle::Type type = styleFill.type();
KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle ||
type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle )
? KoGenStyle::DefaultType : KoGenStyle::GraphicType;
KoGenStyle gradientStyle(KoGenStyle::GradientStyle);
gradientStyle.addAttribute("draw:style", d->style); // draw:style="square"
gradientStyle.addAttribute("draw:cx", QString("%1%").arg(d->cx));
gradientStyle.addAttribute("draw:cy", QString("%1%").arg(d->cy));
gradientStyle.addAttribute("draw:start-color", d->startColor.name());
gradientStyle.addAttribute("draw:end-color", d->endColor.name());
gradientStyle.addAttribute("draw:start-intensity", QString("%1%").arg(qRound(d->startColor.alphaF() * 100)) );
gradientStyle.addAttribute("draw:end-intensity", QString("%1%").arg(qRound(d->endColor.alphaF() * 100)) );
gradientStyle.addAttribute("draw:angle", QString("%1").arg(d->angle * 10));
gradientStyle.addAttribute("draw:border", QString("%1%").arg(qRound(d->border * 100.0)));
QString gradientStyleName = mainStyles.insert(gradientStyle, "gradient");
styleFill.addProperty("draw:fill", "gradient", propertyType);
styleFill.addProperty("draw:fill-gradient-name", gradientStyleName, propertyType);
if (d->opacity <= 1.0) {
styleFill.addProperty("draw:opacity", QString("%1%").arg(d->opacity * 100.0), propertyType);
}
}
-void KoOdfGradientBackground::paint(QPainter& painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath& fillPath) const
+void KoOdfGradientBackground::paint(QPainter& painter, KoShapePaintingContext &/*context*/, const QPainterPath& fillPath) const
{
QImage buffer;
QRectF targetRect = fillPath.boundingRect();
QRectF pixels = painter.transform().mapRect(QRectF(0,0,targetRect.width(), targetRect.height()));
QSize currentSize( qCeil(pixels.size().width()), qCeil(pixels.size().height()) );
if (buffer.isNull() || buffer.size() != currentSize){
buffer = QImage(currentSize, QImage::Format_ARGB32_Premultiplied);
if (d->style == "square") {
renderSquareGradient(buffer);
} else {
renderRectangleGradient(buffer);
}
}
painter.setClipPath(fillPath);
painter.setOpacity(d->opacity);
painter.drawImage(targetRect, buffer, QRectF(QPointF(0,0), buffer.size()));
}
void KoOdfGradientBackground::fillStyle(KoGenStyle& style, KoShapeSavingContext& context)
{
saveOdf(style, context.mainStyles());
}
bool KoOdfGradientBackground::loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize)
{
Q_UNUSED(shapeSize);
KoStyleStack &styleStack = context.styleStack();
if (!styleStack.hasProperty(KoXmlNS::draw, "fill")) {
return false;
}
QString fillStyle = styleStack.property(KoXmlNS::draw, "fill");
if (fillStyle == "gradient") {
if (styleStack.hasProperty(KoXmlNS::draw, "opacity")) {
QString opacity = styleStack.property(KoXmlNS::draw, "opacity");
if (! opacity.isEmpty() && opacity.right(1) == "%") {
d->opacity = qMin(opacity.left(opacity.length() - 1).toDouble(), 100.0) / 100;
}
}
QString styleName = styleStack.property(KoXmlNS::draw, "fill-gradient-name");
KoXmlElement * e = context.stylesReader().drawStyles("gradient")[styleName];
return loadOdf(*e);
}
return false;
}
void KoOdfGradientBackground::renderSquareGradient(QImage& buffer) const
{
buffer.fill(d->startColor.rgba());
QPainter painter(&buffer);
painter.setPen(Qt::NoPen);
painter.setRenderHint(QPainter::Antialiasing, false);
int width = buffer.width();
int height = buffer.height();
qreal gradientCenterX = qRound(width * d->cx * 0.01);
qreal gradientCenterY = qRound(height * d->cy * 0.01);
qreal centerX = width * 0.5;
qreal centerY = height * 0.5;
qreal areaCenterX = qRound(centerX);
qreal areaCenterY = qRound(centerY);
QTransform m;
m.translate(gradientCenterX, gradientCenterY);
m.rotate(-d->angle);
m.scale(1.0 - d->border, 1.0 - d->border);
m.translate(-gradientCenterX, -gradientCenterY);
m.translate(gradientCenterX - areaCenterX,gradientCenterY - areaCenterY);
painter.setTransform(m);
QLinearGradient linearGradient;
linearGradient.setColorAt(1, d->startColor);
linearGradient.setColorAt(0, d->endColor);
// from center going North
linearGradient.setStart(centerX, centerY);
linearGradient.setFinalStop(centerX, 0);
painter.setBrush(linearGradient);
painter.drawRect(0, 0, width, centerY);
// from center going South
linearGradient.setFinalStop(centerX, height);
painter.setBrush(linearGradient);
painter.drawRect(0, centerY, width, centerY);
// clip the East and West portion
QPainterPath clip;
clip.moveTo(width, 0);
clip.lineTo(width, height);
clip.lineTo(0, 0);
clip.lineTo(0, height);
clip.closeSubpath();
painter.setClipPath(clip);
// from center going East
linearGradient.setFinalStop(width, centerY);
painter.setBrush(linearGradient);
painter.drawRect(centerX, 0, width, height);
// from center going West
linearGradient.setFinalStop( 0, centerY);
painter.setBrush(linearGradient);
painter.drawRect(0, 0, centerX, height);
}
void KoOdfGradientBackground::renderRectangleGradient(QImage& buffer) const
{
buffer.fill(d->startColor.rgba());
QPainter painter(&buffer);
painter.setPen(Qt::NoPen);
painter.setRenderHint(QPainter::Antialiasing, false);
int width = buffer.width();
int height = buffer.height();
qreal gradientCenterX = qRound(width * d->cx * 0.01);
qreal gradientCenterY = qRound(height * d->cy * 0.01);
qreal centerX = width * 0.5;
qreal centerY = height * 0.5;
qreal areaCenterY = qRound(centerY);
qreal areaCenterX = qRound(centerX);
QTransform m;
m.translate(gradientCenterX, gradientCenterY);
// m.rotate(-d->angle); // OOo rotates the gradient differently
m.scale(1.0 - d->border, 1.0 - d->border);
m.translate(-gradientCenterX, -gradientCenterY);
m.translate(gradientCenterX - areaCenterX,gradientCenterY - areaCenterY);
painter.setTransform(m);
QLinearGradient linearGradient;
linearGradient.setColorAt(1, d->startColor);
linearGradient.setColorAt(0, d->endColor);
// render background
QPainterPath clipPath;
if (width < height) {
QRectF west(0,0,centerX, height);
QRectF east(centerX, 0, centerX, height);
linearGradient.setStart(centerX, centerY);
linearGradient.setFinalStop(0, centerY);
painter.setBrush(linearGradient);
painter.drawRect(west);
linearGradient.setFinalStop(width, centerY);
painter.setBrush(linearGradient);
painter.drawRect(east);
QRectF north(0,0,width, centerX);
QRectF south(0,height - centerX, width, centerX);
clipPath.moveTo(0,0);
clipPath.lineTo(width, 0);
clipPath.lineTo(centerX, centerX);
clipPath.closeSubpath();
clipPath.moveTo(width, height);
clipPath.lineTo(0, height);
clipPath.lineTo(centerX, south.y());
clipPath.closeSubpath();
linearGradient.setStart(centerX, centerX);
linearGradient.setFinalStop(centerX, 0);
painter.setClipPath(clipPath);
painter.setBrush(linearGradient);
painter.drawRect(north);
linearGradient.setStart(centerX, south.y());
linearGradient.setFinalStop(centerX, height);
painter.setBrush(linearGradient);
painter.drawRect(south);
} else {
QRectF north(0,0,width, centerY);
QRectF south(0, centerY, width, centerY);
linearGradient.setStart(centerX, centerY);
linearGradient.setFinalStop(centerX, 0);
painter.setBrush(linearGradient);
painter.drawRect(north);
linearGradient.setFinalStop(centerX, height);
painter.setBrush(linearGradient);
painter.drawRect(south);
QRectF west(0,0,centerY, height);
QRectF east(width - centerY, 0, centerY, height);
clipPath.moveTo(0,0);
clipPath.lineTo(centerY, centerY);
clipPath.lineTo(0,height);
clipPath.closeSubpath();
clipPath.moveTo(width, height);
clipPath.lineTo(east.x(), centerY);
clipPath.lineTo(width,0);
clipPath.closeSubpath();
linearGradient.setStart(centerY, centerY);
linearGradient.setFinalStop(0, centerY);
painter.setClipPath(clipPath);
painter.setBrush(linearGradient);
painter.drawRect(west);
linearGradient.setStart(east.x(), centerY);
linearGradient.setFinalStop(width, centerY);
painter.setBrush(linearGradient);
painter.drawRect(east);
}
}
void KoOdfGradientBackground::debug() const
{
debugFlake << "cx,cy: "<< d->cx << d->cy;
debugFlake << "style" << d->style;
debugFlake << "colors" << d->startColor << d->endColor;
debugFlake << "angle:" << d->angle;
debugFlake << "border" << d->border;
}
diff --git a/libs/flake/KoOdfGradientBackground.h b/libs/flake/KoOdfGradientBackground.h
index f5974313aa..19a94f6832 100644
--- a/libs/flake/KoOdfGradientBackground.h
+++ b/libs/flake/KoOdfGradientBackground.h
@@ -1,66 +1,66 @@
/* This file is part of the KDE project
*
* Copyright (C) 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.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 KOODFGRADIENTBACKGROUND_H
#define KOODFGRADIENTBACKGROUND_H
#include "KoShapeBackground.h"
#include "kritaflake_export.h"
class QImage;
#include <KoXmlReaderForward.h>
#include <QSharedDataPointer>
class KoGenStyles;
class KoGenStyle;
/// Gradients from odf that are not native to Qt
class KoOdfGradientBackground : public KoShapeBackground {
public:
// constructor
KoOdfGradientBackground();
// destructor
~KoOdfGradientBackground() override;
bool compareTo(const KoShapeBackground *other) const override;
/// reimplemented from KoShapeBackground
void fillStyle(KoGenStyle& style, KoShapeSavingContext& context) override;
/// reimplemented from KoShapeBackground
bool loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize) override;
/// reimplemented from KoShapeBackground
- void paint(QPainter& painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath& fillPath) const override;
+ void paint(QPainter& painter, KoShapePaintingContext &context, const QPainterPath& fillPath) const override;
private:
bool loadOdf(const KoXmlElement &element);
void saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const;
void renderSquareGradient(QImage &buffer) const;
void renderRectangleGradient(QImage &buffer) const;
private:
void debug() const;
private:
class Private;
QSharedDataPointer<Private> d;
};
#endif
diff --git a/libs/flake/KoOdfWorkaround.h b/libs/flake/KoOdfWorkaround.h
index 038f4cf8c9..9bf9e91d95 100644
--- a/libs/flake/KoOdfWorkaround.h
+++ b/libs/flake/KoOdfWorkaround.h
@@ -1,162 +1,162 @@
/* This file is part of the KDE project
Copyright (C) 2009 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2011 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.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 KOODFWORKAROUND_H
#define KOODFWORKAROUND_H
#include "kritaflake_export.h"
#include "KoTextShapeDataBase.h"
#include <qnamespace.h>
#include <QSharedPointer>
#include <KoXmlReaderForward.h>
class KoShape;
class KoShapeLoadingContext;
class QPen;
class QColor;
class QString;
class KoColorBackground;
/**
* This class should contain all workarounds to correct problems with different ODF
* implementations. If you need to access application specific things please create a
* new namespace in the application you need it in
* All calls to methods of this class should be wrapped into ifndefs like e.g.
*
* @code
* #ifndef NWORKAROUND_ODF_BUGS
* KoOdfWorkaround::fixPenWidth(pen, context);
* #endif
* @endcode
*/
namespace KoOdfWorkaround
{
/**
* OpenOffice handles a line with the width of 0 as a cosmetic line but in svg it makes the line invisible.
* To show it in calligra use a very small line width. However this is not a cosmetic line.
*/
KRITAFLAKE_EXPORT void fixPenWidth(QPen &pen, KoShapeLoadingContext &context);
/**
* OpenOffice < 3.0 does not store the draw:enhanced-path for draw:type="ellipse"
* Add the path needed for the ellipse
*/
KRITAFLAKE_EXPORT void fixEnhancedPath(QString &path, const KoXmlElement &element, KoShapeLoadingContext &context);
/**
* OpenOffice interchanges the position coordinates for polar handles.
* According to the specification the first coordinate is the angle, the
* second coordinates is the radius. OpenOffice does it the other way around.
*/
KRITAFLAKE_EXPORT void fixEnhancedPathPolarHandlePosition(QString &position, const KoXmlElement &element, KoShapeLoadingContext &context);
KRITAFLAKE_EXPORT bool fixMissingStroke(QPen &pen, const KoXmlElement &element, KoShapeLoadingContext &context, const KoShape *shape = 0);
KRITAFLAKE_EXPORT QColor fixMissingFillColor(const KoXmlElement &element, KoShapeLoadingContext &context);
KRITAFLAKE_EXPORT bool fixMissingStyle_DisplayLabel(const KoXmlElement &element, KoShapeLoadingContext &context);
KRITAFLAKE_EXPORT QSharedPointer<KoColorBackground> fixBackgroundColor(const KoShape *shape, KoShapeLoadingContext &context);
/**
* Old versions of ooimpress does not set the placeholder for shapes that should have it set
- * See open office issue http://www.openoffice.org/issues/show_bug.cgi?id=96406
+ * See open office issue https://bz.apache.org/ooo/show_bug.cgi?id=96406
* And kde bug https://bugs.kde.org/show_bug.cgi?id=185354
*/
KRITAFLAKE_EXPORT void setFixPresentationPlaceholder(bool fix, KoShapeLoadingContext &context);
KRITAFLAKE_EXPORT bool fixPresentationPlaceholder();
KRITAFLAKE_EXPORT void fixPresentationPlaceholder(KoShape *shape);
/**
* OpenOffice and LibreOffice save gluepoint positions wrong when no align is specified.
* According to the specification for the above situation, the position should be saved
* as percent values relative to the shapes center point. OpenOffice seems to write
* these percent values converted to length units, where the millimeter value corresponds
* to the correct percent value (i.e. -5cm = -50mm = -50%).
*/
KRITAFLAKE_EXPORT void fixGluePointPosition(QString &positionString, KoShapeLoadingContext &context);
/**
* OpenOffice and LibreOffice does not conform to the specification about default value
* of the svg:fill-rule. If this attribute is missing, according the spec, the initial
* value is nonzero, but OOo uses evenodd. Because we are conform to the spec, we need
* to set what OOo display.
* See http://www.w3.org/TR/SVG/painting.html#FillRuleProperty
*/
KRITAFLAKE_EXPORT void fixMissingFillRule(Qt::FillRule &fillRule, KoShapeLoadingContext &context);
/**
* OpenOffice resizes text shapes with autogrow in both directions. If the text box is saved to
* small the text will not fit and it needs to be adjusted during the first layout.
* This methods returns true if we need to adjust the layout. The adjusting is handled at a different place.
*/
KRITAFLAKE_EXPORT bool fixAutoGrow(KoTextShapeDataBase::ResizeMethod method, KoShapeLoadingContext &context);
/**
* OpenOffice and LibreOffice do not set the svg:width, svg:height, svg:x and svg:y correctly when saving
* parts of draw:ellipses or draw:circle
* This method returns true when the width, height, x and y is given for the full circle
*/
KRITAFLAKE_EXPORT bool fixEllipse(const QString &kind, KoShapeLoadingContext &context);
/**
* Calligra did use the bad strings "Formula.hidden" and "protected Formula.hidden" as values
* for style:cell-protect, instead of "formula-hidden" and "protected formula-hidden".
* This method fixes the bad strings to the correct ones.
*/
KRITAFLAKE_EXPORT void fixBadFormulaHiddenForStyleCellProtect(QString &value);
/**
* Calligra used to store text:time-value with a "0-00-00T" prefix
* This method removes that prefix.
*/
KRITAFLAKE_EXPORT void fixBadDateForTextTime(QString &value);
/**
* OpenOffice.org used to write the "rect(...)" value for fo:clip without
* separating the 4 offset values by commas.
* This method changes the string with the offset values to have commas as separators.
*/
KRITAFLAKE_EXPORT void fixClipRectOffsetValuesString(QString &offsetValuesString);
/**
* LibreOffice used to write text:style-name attribute for table:table-template element,
* which is not a valid attribute for the element.
*/
KRITAFLAKE_EXPORT QString fixTableTemplateName(const KoXmlElement &e);
/**
* LibreOffice used to write text:style-name attribute for
* table:first-row, table:last-row, table:first-column,
* table:last-column, table:odd-rows, table:odd-columns,
* table:body elements, which is not a valid attribute for the element.
*/
KRITAFLAKE_EXPORT QString fixTableTemplateCellStyleName(const KoXmlElement &e);
/**
* LibreOffice used to have a bug with handling of z command in svg path.
* This resulted in broken marker path used (and copied also to Calligra).
* This methods substitutes known old marker paths with the latest (fixed)
* path variant.
*/
KRITAFLAKE_EXPORT void fixMarkerPath(QString &path);
}
#endif /* KOODFWORKAROUND_H */
diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp
index 641de90986..7732fd38bc 100644
--- a/libs/flake/KoPathShape.cpp
+++ b/libs/flake/KoPathShape.cpp
@@ -1,1661 +1,1667 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008, 2010-2011 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2011 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2007-2009 Thomas Zander <zander@kde.org>
Copyright (C) 2011 Jean-Nicolas Artaud <jeannicolasartaud@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 "KoPathShape.h"
#include "KoPathShape_p.h"
#include "KoPathSegment.h"
#include "KoOdfWorkaround.h"
#include "KoPathPoint.h"
#include "KoShapeStrokeModel.h"
-#include "KoViewConverter.h"
#include "KoPathShapeLoader.h"
#include "KoShapeSavingContext.h"
#include "KoShapeLoadingContext.h"
#include "KoShapeShadow.h"
#include "KoShapeBackground.h"
#include "KoShapeContainer.h"
#include "KoFilterEffectStack.h"
#include "KoMarker.h"
#include "KoShapeStroke.h"
#include "KoInsets.h"
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoXmlNS.h>
#include <KoUnit.h>
#include <KoGenStyle.h>
#include <KoStyleStack.h>
#include <KoOdfLoadingContext.h>
#include "KisQPainterStateSaver.h"
#include <FlakeDebug.h>
#include <QPainter>
#include "kis_global.h"
#include <qnumeric.h> // for qIsNaN
static bool qIsNaNPoint(const QPointF &p) {
return qIsNaN(p.x()) || qIsNaN(p.y());
}
KoPathShape::Private::Private()
- : QSharedData()
- , fillRule(Qt::OddEvenFill)
+ : fillRule(Qt::OddEvenFill)
, autoFillMarkers(false)
{
}
KoPathShape::Private::Private(const Private &rhs)
- : QSharedData()
- , fillRule(rhs.fillRule)
+ : fillRule(rhs.fillRule)
, markersNew(rhs.markersNew)
, autoFillMarkers(rhs.autoFillMarkers)
{
}
QRectF KoPathShape::Private::handleRect(const QPointF &p, qreal radius) const
{
return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius);
}
void KoPathShape::Private::applyViewboxTransformation(const KoXmlElement &element)
{
// apply viewbox transformation
const QRect viewBox = KoPathShape::loadOdfViewbox(element);
if (! viewBox.isEmpty()) {
// load the desired size
QSizeF size;
size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString())));
size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString())));
// load the desired position
QPointF pos;
pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString())));
pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString())));
// create matrix to transform original path data into desired size and position
QTransform viewMatrix;
viewMatrix.translate(-viewBox.left(), -viewBox.top());
viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height());
viewMatrix.translate(pos.x(), pos.y());
// transform the path data
map(viewMatrix);
}
}
KoPathShape::KoPathShape()
: KoTosContainer()
, d(new Private)
{
}
KoPathShape::KoPathShape(const KoPathShape &rhs)
: KoTosContainer(rhs)
- , d(rhs.d)
+ , d(new Private(*rhs.d))
{
+ // local data cannot be shared via QSharedData because
+ // every path point holds a pointer to the parent shape
KoSubpathList subpaths;
Q_FOREACH (KoSubpath *subPath, rhs.d->subpaths) {
KoSubpath *clonedSubPath = new KoSubpath();
Q_FOREACH (KoPathPoint *point, *subPath) {
*clonedSubPath << new KoPathPoint(*point, this);
}
subpaths << clonedSubPath;
}
d->subpaths = subpaths;
}
KoPathShape::~KoPathShape()
{
clear();
}
KoShape *KoPathShape::cloneShape() const
{
return new KoPathShape(*this);
}
void KoPathShape::saveContourOdf(KoShapeSavingContext &context, const QSizeF &scaleFactor) const
{
if (d->subpaths.length() <= 1) {
QTransform matrix;
matrix.scale(scaleFactor.width(), scaleFactor.height());
QString points;
KoSubpath *subPath = d->subpaths.first();
KoSubpath::const_iterator pointIt(subPath->constBegin());
KoPathPoint *currPoint= 0;
// iterate over all points
for (; pointIt != subPath->constEnd(); ++pointIt) {
currPoint = *pointIt;
if (currPoint->activeControlPoint1() || currPoint->activeControlPoint2()) {
break;
}
const QPointF p = matrix.map(currPoint->point());
points += QString("%1,%2 ").arg(qRound(1000*p.x())).arg(qRound(1000*p.y()));
}
if (currPoint && !(currPoint->activeControlPoint1() || currPoint->activeControlPoint2())) {
context.xmlWriter().startElement("draw:contour-polygon");
context.xmlWriter().addAttribute("svg:width", size().width());
context.xmlWriter().addAttribute("svg:height", size().height());
const QSizeF s(size());
QString viewBox = QString("0 0 %1 %2").arg(qRound(1000*s.width())).arg(qRound(1000*s.height()));
context.xmlWriter().addAttribute("svg:viewBox", viewBox);
context.xmlWriter().addAttribute("draw:points", points);
context.xmlWriter().addAttribute("draw:recreate-on-edit", "true");
context.xmlWriter().endElement();
return;
}
}
// if we get here we couldn't save as polygon - let-s try contour-path
context.xmlWriter().startElement("draw:contour-path");
saveOdfAttributes(context, OdfViewbox);
context.xmlWriter().addAttribute("svg:d", toString());
context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes());
context.xmlWriter().addAttribute("draw:recreate-on-edit", "true");
context.xmlWriter().endElement();
}
void KoPathShape::saveOdf(KoShapeSavingContext & context) const
{
context.xmlWriter().startElement("draw:path");
saveOdfAttributes(context, OdfAllAttributes | OdfViewbox);
context.xmlWriter().addAttribute("svg:d", toString());
context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes());
saveOdfCommonChildElements(context);
saveText(context);
context.xmlWriter().endElement();
}
bool KoPathShape::loadContourOdf(const KoXmlElement &element, KoShapeLoadingContext &, const QSizeF &scaleFactor)
{
// first clear the path data from the default path
clear();
if (element.localName() == "contour-polygon") {
QString points = element.attributeNS(KoXmlNS::draw, "points").simplified();
points.replace(',', ' ');
points.remove('\r');
points.remove('\n');
bool firstPoint = true;
const QStringList coordinateList = points.split(' ');
for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) {
QPointF point;
point.setX((*it).toDouble());
++it;
point.setY((*it).toDouble());
if (firstPoint) {
moveTo(point);
firstPoint = false;
} else
lineTo(point);
}
close();
} else if (element.localName() == "contour-path") {
KoPathShapeLoader loader(this);
loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true);
d->loadNodeTypes(element);
}
// apply viewbox transformation
const QRect viewBox = KoPathShape::loadOdfViewbox(element);
if (! viewBox.isEmpty()) {
QSizeF size;
size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString())));
size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString())));
// create matrix to transform original path data into desired size and position
QTransform viewMatrix;
viewMatrix.translate(-viewBox.left(), -viewBox.top());
viewMatrix.scale(scaleFactor.width(), scaleFactor.height());
viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height());
// transform the path data
d->map(viewMatrix);
}
setTransformation(QTransform());
return true;
}
bool KoPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context)
{
loadOdfAttributes(element, context, OdfMandatories | OdfAdditionalAttributes | OdfCommonChildElements);
// first clear the path data from the default path
clear();
if (element.localName() == "line") {
QPointF start;
start.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", "")));
start.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", "")));
QPointF end;
end.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", "")));
end.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", "")));
moveTo(start);
lineTo(end);
} else if (element.localName() == "polyline" || element.localName() == "polygon") {
QString points = element.attributeNS(KoXmlNS::draw, "points").simplified();
points.replace(',', ' ');
points.remove('\r');
points.remove('\n');
bool firstPoint = true;
const QStringList coordinateList = points.split(' ');
for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) {
QPointF point;
point.setX((*it).toDouble());
++it;
point.setY((*it).toDouble());
if (firstPoint) {
moveTo(point);
firstPoint = false;
} else
lineTo(point);
}
if (element.localName() == "polygon")
close();
} else { // path loading
KoPathShapeLoader loader(this);
loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true);
d->loadNodeTypes(element);
}
d->applyViewboxTransformation(element);
QPointF pos = normalize();
setTransformation(QTransform());
if (element.hasAttributeNS(KoXmlNS::svg, "x") || element.hasAttributeNS(KoXmlNS::svg, "y")) {
pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString())));
pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString())));
}
setPosition(pos);
loadOdfAttributes(element, context, OdfTransformation);
// now that the correct transformation is set up
// apply that matrix to the path geometry so that
// we don't transform the stroke
d->map(transformation());
setTransformation(QTransform());
normalize();
loadText(element, context);
return true;
}
QString KoPathShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const
{
style.addProperty("svg:fill-rule", d->fillRule == Qt::OddEvenFill ? "evenodd" : "nonzero");
QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(stroke());
qreal lineWidth = 0;
if (lineBorder) {
lineWidth = lineBorder->lineWidth();
}
Q_UNUSED(lineWidth)
return KoTosContainer::saveStyle(style, context);
}
void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext &context)
{
KoTosContainer::loadStyle(element, context);
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.setTypeProperties("graphic");
if (styleStack.hasProperty(KoXmlNS::svg, "fill-rule")) {
QString rule = styleStack.property(KoXmlNS::svg, "fill-rule");
d->fillRule = (rule == "nonzero") ? Qt::WindingFill : Qt::OddEvenFill;
} else {
d->fillRule = Qt::WindingFill;
#ifndef NWORKAROUND_ODF_BUGS
KoOdfWorkaround::fixMissingFillRule(d->fillRule, context);
#endif
}
QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(stroke());
qreal lineWidth = 0;
if (lineBorder) {
lineWidth = lineBorder->lineWidth();
}
Q_UNUSED(lineWidth);
}
QRect KoPathShape::loadOdfViewbox(const KoXmlElement & element)
{
QRect viewbox;
QString data = element.attributeNS(KoXmlNS::svg, QLatin1String("viewBox"));
if (! data.isEmpty()) {
data.replace(QLatin1Char(','), QLatin1Char(' '));
const QStringList coordinates = data.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts);
if (coordinates.count() == 4) {
viewbox.setRect(coordinates.at(0).toInt(), coordinates.at(1).toInt(),
coordinates.at(2).toInt(), coordinates.at(3).toInt());
}
}
return viewbox;
}
void KoPathShape::clear()
{
Q_FOREACH (KoSubpath *subpath, d->subpaths) {
Q_FOREACH (KoPathPoint *point, *subpath)
delete point;
delete subpath;
}
d->subpaths.clear();
notifyPointsChanged();
}
-void KoPathShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
+void KoPathShape::paint(QPainter &painter, KoShapePaintingContext &paintContext) const
{
KisQPainterStateSaver saver(&painter);
- applyConversion(painter, converter);
QPainterPath path(outline());
path.setFillRule(d->fillRule);
if (background()) {
- background()->paint(painter, converter, paintContext, path);
+ background()->paint(painter, paintContext, path);
}
//d->paintDebug(painter);
}
#ifndef NDEBUG
void KoPathShape::Private::paintDebug(QPainter &painter)
{
KoSubpathList::const_iterator pathIt(subpaths.constBegin());
int i = 0;
QPen pen(Qt::black, 0);
painter.save();
painter.setPen(pen);
for (; pathIt != subpaths.constEnd(); ++pathIt) {
KoSubpath::const_iterator it((*pathIt)->constBegin());
for (; it != (*pathIt)->constEnd(); ++it) {
++i;
KoPathPoint *point = (*it);
QRectF r(point->point(), QSizeF(5, 5));
r.translate(-2.5, -2.5);
QPen pen(Qt::black, 0);
painter.setPen(pen);
if (point->activeControlPoint1() && point->activeControlPoint2()) {
QBrush b(Qt::red);
painter.setBrush(b);
} else if (point->activeControlPoint1()) {
QBrush b(Qt::yellow);
painter.setBrush(b);
} else if (point->activeControlPoint2()) {
QBrush b(Qt::darkYellow);
painter.setBrush(b);
}
painter.drawEllipse(r);
}
}
painter.restore();
debugFlake << "nop =" << i;
}
void KoPathShape::Private::debugPath() const
{
KoSubpathList::const_iterator pathIt(subpaths.constBegin());
for (; pathIt != subpaths.constEnd(); ++pathIt) {
KoSubpath::const_iterator it((*pathIt)->constBegin());
for (; it != (*pathIt)->constEnd(); ++it) {
debugFlake << "p:" << (*pathIt) << "," << *it << "," << (*it)->point() << "," << (*it)->properties();
}
}
}
#endif
void KoPathShape::paintPoints(KisHandlePainterHelper &handlesHelper)
{
KoSubpathList::const_iterator pathIt(d->subpaths.constBegin());
for (; pathIt != d->subpaths.constEnd(); ++pathIt) {
KoSubpath::const_iterator it((*pathIt)->constBegin());
for (; it != (*pathIt)->constEnd(); ++it)
(*it)->paint(handlesHelper, KoPathPoint::Node);
}
}
QRectF KoPathShape::outlineRect() const
{
return outline().boundingRect();
}
QPainterPath KoPathShape::outline() const
{
QPainterPath path;
for (auto subpathIt = d->subpaths.constBegin(); subpathIt != d->subpaths.constEnd(); ++subpathIt) {
const KoSubpath * subpath = *subpathIt;
const KoPathPoint * lastPoint = subpath->constFirst();
bool activeCP = false;
for (auto pointIt = subpath->constBegin(); pointIt != subpath->constEnd(); ++pointIt) {
const KoPathPoint * currPoint = *pointIt;
KoPathPoint::PointProperties currProperties = currPoint->properties();
if (currPoint == subpath->constFirst()) {
if (currProperties & KoPathPoint::StartSubpath) {
Q_ASSERT(!qIsNaNPoint(currPoint->point()));
path.moveTo(currPoint->point());
}
} else if (activeCP && currPoint->activeControlPoint1()) {
Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2()));
Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1()));
Q_ASSERT(!qIsNaNPoint(currPoint->point()));
path.cubicTo(
lastPoint->controlPoint2(),
currPoint->controlPoint1(),
currPoint->point());
} else if (activeCP || currPoint->activeControlPoint1()) {
Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2()));
Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1()));
path.quadTo(
activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(),
currPoint->point());
} else {
Q_ASSERT(!qIsNaNPoint(currPoint->point()));
path.lineTo(currPoint->point());
}
if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) {
// add curve when there is a curve on the way to the first point
KoPathPoint * firstPoint = subpath->first();
Q_ASSERT(!qIsNaNPoint(firstPoint->point()));
if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) {
path.cubicTo(
currPoint->controlPoint2(),
firstPoint->controlPoint1(),
firstPoint->point());
}
else if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) {
Q_ASSERT(!qIsNaNPoint(currPoint->point()));
Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1()));
path.quadTo(
currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(),
firstPoint->point());
}
path.closeSubpath();
}
if (currPoint->activeControlPoint2()) {
activeCP = true;
} else {
activeCP = false;
}
lastPoint = currPoint;
}
}
return path;
}
QRectF KoPathShape::boundingRect() const
{
- const QTransform transform = absoluteTransformation(0);
+ const QTransform transform = absoluteTransformation();
/**
* First we approximate the insets of the stroke by rendering a fat bezier curve
* with width set to the maximum inset of miters and markers. The are swept by this
* curve will be a good approximation of the real curve bounding rect.
*/
qreal outlineSweepWidth = 0;
const QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(stroke());
if (lineBorder) {
outlineSweepWidth = lineBorder->lineWidth();
}
if (stroke()) {
KoInsets inset;
stroke()->strokeInsets(this, inset);
const qreal maxInset = std::max({inset.left, inset.top, inset.right, inset.bottom});
// insets extend outside the shape, but width extends both inside and outside,
// so we should multiply insets by 2.0
outlineSweepWidth = std::max({outlineSweepWidth,
2.0 * maxInset,
2.0 * stroke()->strokeMaxMarkersInset(this)});
}
+
+
+ /// NOTE: stroking the entire shape might be too expensive, so try to
+ /// estimate the bounds using insets only...
+
+#if 0
QPen pen(Qt::black, outlineSweepWidth);
// select round joins and caps to ensure it sweeps exactly
// 'outlineSweepWidth' pixels in every possible
pen.setJoinStyle(Qt::RoundJoin);
pen.setCapStyle(Qt::RoundCap);
-
QRectF bb = transform.map(pathStroke(pen)).boundingRect();
+#endif
+
+ QRectF bb = transform.mapRect(kisGrowRect(outline().boundingRect(), outlineSweepWidth));
if (shadow()) {
KoInsets insets;
shadow()->insets(insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
if (filterEffectStack()) {
QRectF clipRect = filterEffectStack()->clipRectForBoundingRect(QRectF(QPointF(), size()));
bb |= transform.mapRect(clipRect);
}
return bb;
}
QSizeF KoPathShape::size() const
{
// don't call boundingRect here as it uses absoluteTransformation
// which itself uses size() -> leads to infinite recursion
return outlineRect().size();
}
void KoPathShape::setSize(const QSizeF &newSize)
{
QTransform matrix(resizeMatrix(newSize));
KoShape::setSize(newSize);
d->map(matrix);
}
QTransform KoPathShape::resizeMatrix(const QSizeF & newSize) const
{
QSizeF oldSize = size();
if (oldSize.width() == 0.0) {
oldSize.setWidth(0.000001);
}
if (oldSize.height() == 0.0) {
oldSize.setHeight(0.000001);
}
QSizeF sizeNew(newSize);
if (sizeNew.width() == 0.0) {
sizeNew.setWidth(0.000001);
}
if (sizeNew.height() == 0.0) {
sizeNew.setHeight(0.000001);
}
return QTransform(sizeNew.width() / oldSize.width(), 0, 0, sizeNew.height() / oldSize.height(), 0, 0);
}
KoPathPoint * KoPathShape::moveTo(const QPointF &p)
{
KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath);
KoSubpath * path = new KoSubpath;
path->push_back(point);
d->subpaths.push_back(path);
notifyPointsChanged();
return point;
}
KoPathPoint * KoPathShape::lineTo(const QPointF &p)
{
if (d->subpaths.empty()) {
moveTo(QPointF(0, 0));
}
KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath);
KoPathPoint * lastPoint = d->subpaths.last()->last();
updateLastPriv(&lastPoint);
d->subpaths.last()->push_back(point);
notifyPointsChanged();
return point;
}
KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p)
{
if (d->subpaths.empty()) {
moveTo(QPointF(0, 0));
}
KoPathPoint * lastPoint = d->subpaths.last()->last();
updateLastPriv(&lastPoint);
lastPoint->setControlPoint2(c1);
KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath);
point->setControlPoint1(c2);
d->subpaths.last()->push_back(point);
notifyPointsChanged();
return point;
}
KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p)
{
if (d->subpaths.empty())
moveTo(QPointF(0, 0));
KoPathPoint * lastPoint = d->subpaths.last()->last();
updateLastPriv(&lastPoint);
lastPoint->setControlPoint2(c);
KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath);
d->subpaths.last()->push_back(point);
notifyPointsChanged();
return point;
}
KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle)
{
if (d->subpaths.empty()) {
moveTo(QPointF(0, 0));
}
KoPathPoint * lastPoint = d->subpaths.last()->last();
if (lastPoint->properties() & KoPathPoint::CloseSubpath) {
lastPoint = d->subpaths.last()->first();
}
QPointF startpoint(lastPoint->point());
KoPathPoint * newEndPoint = lastPoint;
QPointF curvePoints[12];
int pointCnt = arcToCurve(rx, ry, startAngle, sweepAngle, startpoint, curvePoints);
for (int i = 0; i < pointCnt; i += 3) {
newEndPoint = curveTo(curvePoints[i], curvePoints[i+1], curvePoints[i+2]);
}
return newEndPoint;
}
int KoPathShape::arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF & offset, QPointF * curvePoints) const
{
int pointCnt = 0;
// check Parameters
if (sweepAngle == 0.0)
return pointCnt;
sweepAngle = qBound(-360.0, sweepAngle, 360.0);
if (rx == 0 || ry == 0) {
//TODO
}
// split angles bigger than 90° so that it gives a good approximation to the circle
qreal parts = ceil(qAbs(sweepAngle / 90.0));
qreal sa_rad = startAngle * M_PI / 180.0;
qreal partangle = sweepAngle / parts;
qreal endangle = startAngle + partangle;
qreal se_rad = endangle * M_PI / 180.0;
qreal sinsa = sin(sa_rad);
qreal cossa = cos(sa_rad);
qreal kappa = 4.0 / 3.0 * tan((se_rad - sa_rad) / 4);
// startpoint is at the last point is the path but when it is closed
// it is at the first point
QPointF startpoint(offset);
//center berechnen
QPointF center(startpoint - QPointF(cossa * rx, -sinsa * ry));
//debugFlake <<"kappa" << kappa <<"parts" << parts;
for (int part = 0; part < parts; ++part) {
// start tangent
curvePoints[pointCnt++] = QPointF(startpoint - QPointF(sinsa * rx * kappa, cossa * ry * kappa));
qreal sinse = sin(se_rad);
qreal cosse = cos(se_rad);
// end point
QPointF endpoint(center + QPointF(cosse * rx, -sinse * ry));
// end tangent
curvePoints[pointCnt++] = QPointF(endpoint - QPointF(-sinse * rx * kappa, -cosse * ry * kappa));
curvePoints[pointCnt++] = endpoint;
// set the endpoint as next start point
startpoint = endpoint;
sinsa = sinse;
cossa = cosse;
endangle += partangle;
se_rad = endangle * M_PI / 180.0;
}
return pointCnt;
}
void KoPathShape::close()
{
if (d->subpaths.empty()) {
return;
}
closeSubpathPriv(d->subpaths.last());
}
void KoPathShape::closeMerge()
{
if (d->subpaths.empty()) {
return;
}
closeMergeSubpathPriv(d->subpaths.last());
}
QPointF KoPathShape::normalize()
{
QPointF tl(outline().boundingRect().topLeft());
QTransform matrix;
matrix.translate(-tl.x(), -tl.y());
d->map(matrix);
// keep the top left point of the object
applyTransformation(matrix.inverted());
shapeChangedPriv(ContentChanged);
return tl;
}
void KoPathShape::Private::map(const QTransform &matrix)
{
KoSubpathList::const_iterator pathIt(subpaths.constBegin());
for (; pathIt != subpaths.constEnd(); ++pathIt) {
KoSubpath::const_iterator it((*pathIt)->constBegin());
for (; it != (*pathIt)->constEnd(); ++it) {
// It's possible there are null points in the map...
if (*it) {
(*it)->map(matrix);
}
}
}
}
void KoPathShape::updateLastPriv(KoPathPoint **lastPoint)
{
// check if we are about to add a new point to a closed subpath
if ((*lastPoint)->properties() & KoPathPoint::StopSubpath
&& (*lastPoint)->properties() & KoPathPoint::CloseSubpath) {
// get the first point of the subpath
KoPathPoint *subpathStart = d->subpaths.last()->first();
// clone the first point of the subpath...
KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart, this);
// ... and make it a normal point
newLastPoint->setProperties(KoPathPoint::Normal);
// now start a new subpath with the cloned start point
KoSubpath *path = new KoSubpath;
path->push_back(newLastPoint);
d->subpaths.push_back(path);
*lastPoint = newLastPoint;
} else {
// the subpath was not closed so the formerly last point
// of the subpath is no end point anymore
(*lastPoint)->unsetProperty(KoPathPoint::StopSubpath);
}
(*lastPoint)->unsetProperty(KoPathPoint::CloseSubpath);
}
QList<KoPathPoint*> KoPathShape::pointsAt(const QRectF &r) const
{
QList<KoPathPoint*> result;
KoSubpathList::const_iterator pathIt(d->subpaths.constBegin());
for (; pathIt != d->subpaths.constEnd(); ++pathIt) {
KoSubpath::const_iterator it((*pathIt)->constBegin());
for (; it != (*pathIt)->constEnd(); ++it) {
if (r.contains((*it)->point()))
result.append(*it);
else if ((*it)->activeControlPoint1() && r.contains((*it)->controlPoint1()))
result.append(*it);
else if ((*it)->activeControlPoint2() && r.contains((*it)->controlPoint2()))
result.append(*it);
}
}
return result;
}
QList<KoPathSegment> KoPathShape::segmentsAt(const QRectF &r) const
{
QList<KoPathSegment> segments;
int subpathCount = d->subpaths.count();
for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) {
KoSubpath * subpath = d->subpaths[subpathIndex];
int pointCount = subpath->count();
bool subpathClosed = isClosedSubpath(subpathIndex);
for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
if (pointIndex == (pointCount - 1) && ! subpathClosed)
break;
KoPathSegment s(subpath->at(pointIndex), subpath->at((pointIndex + 1) % pointCount));
QRectF controlRect = s.controlPointRect();
if (! r.intersects(controlRect) && ! controlRect.contains(r))
continue;
QRectF bound = s.boundingRect();
if (! r.intersects(bound) && ! bound.contains(r))
continue;
segments.append(s);
}
}
return segments;
}
KoPathPointIndex KoPathShape::pathPointIndex(const KoPathPoint *point) const
{
for (int subpathIndex = 0; subpathIndex < d->subpaths.size(); ++subpathIndex) {
KoSubpath * subpath = d->subpaths.at(subpathIndex);
for (int pointPos = 0; pointPos < subpath->size(); ++pointPos) {
if (subpath->at(pointPos) == point) {
return KoPathPointIndex(subpathIndex, pointPos);
}
}
}
return KoPathPointIndex(-1, -1);
}
KoPathPoint * KoPathShape::pointByIndex(const KoPathPointIndex &pointIndex) const
{
KoSubpath *subpath = d->subPath(pointIndex.first);
if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size())
return 0;
return subpath->at(pointIndex.second);
}
KoPathSegment KoPathShape::segmentByIndex(const KoPathPointIndex &pointIndex) const
{
KoPathSegment segment(0, 0);
KoSubpath *subpath = d->subPath(pointIndex.first);
if (subpath != 0 && pointIndex.second >= 0 && pointIndex.second < subpath->size()) {
KoPathPoint * point = subpath->at(pointIndex.second);
int index = pointIndex.second;
// check if we have a (closing) segment starting from the last point
if ((index == subpath->size() - 1) && point->properties() & KoPathPoint::CloseSubpath)
index = 0;
else
++index;
if (index < subpath->size()) {
segment = KoPathSegment(point, subpath->at(index));
}
}
return segment;
}
int KoPathShape::pointCount() const
{
int i = 0;
KoSubpathList::const_iterator pathIt(d->subpaths.constBegin());
for (; pathIt != d->subpaths.constEnd(); ++pathIt) {
i += (*pathIt)->size();
}
return i;
}
int KoPathShape::subpathCount() const
{
return d->subpaths.count();
}
int KoPathShape::subpathPointCount(int subpathIndex) const
{
KoSubpath *subpath = d->subPath(subpathIndex);
if (subpath == 0)
return -1;
return subpath->size();
}
bool KoPathShape::isClosedSubpath(int subpathIndex) const
{
KoSubpath *subpath = d->subPath(subpathIndex);
if (subpath == 0)
return false;
const bool firstClosed = subpath->first()->properties() & KoPathPoint::CloseSubpath;
const bool lastClosed = subpath->last()->properties() & KoPathPoint::CloseSubpath;
return firstClosed && lastClosed;
}
bool KoPathShape::insertPoint(KoPathPoint* point, const KoPathPointIndex &pointIndex)
{
KoSubpath *subpath = d->subPath(pointIndex.first);
if (subpath == 0 || pointIndex.second < 0 || pointIndex.second > subpath->size())
return false;
KoPathPoint::PointProperties properties = point->properties();
properties &= ~KoPathPoint::StartSubpath;
properties &= ~KoPathPoint::StopSubpath;
properties &= ~KoPathPoint::CloseSubpath;
// check if new point starts subpath
if (pointIndex.second == 0) {
properties |= KoPathPoint::StartSubpath;
// subpath was closed
if (subpath->last()->properties() & KoPathPoint::CloseSubpath) {
// keep the path closed
properties |= KoPathPoint::CloseSubpath;
}
// old first point does not start the subpath anymore
subpath->first()->unsetProperty(KoPathPoint::StartSubpath);
}
// check if new point stops subpath
else if (pointIndex.second == subpath->size()) {
properties |= KoPathPoint::StopSubpath;
// subpath was closed
if (subpath->last()->properties() & KoPathPoint::CloseSubpath) {
// keep the path closed
properties = properties | KoPathPoint::CloseSubpath;
}
// old last point does not end subpath anymore
subpath->last()->unsetProperty(KoPathPoint::StopSubpath);
}
point->setProperties(properties);
point->setParent(this);
subpath->insert(pointIndex.second , point);
notifyPointsChanged();
return true;
}
KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex)
{
KoSubpath *subpath = d->subPath(pointIndex.first);
if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size())
return 0;
KoPathPoint * point = subpath->takeAt(pointIndex.second);
point->setParent(0);
//don't do anything (not even crash), if there was only one point
if (pointCount()==0) {
return point;
}
// check if we removed the first point
else if (pointIndex.second == 0) {
// first point removed, set new StartSubpath
subpath->first()->setProperty(KoPathPoint::StartSubpath);
// check if path was closed
if (subpath->last()->properties() & KoPathPoint::CloseSubpath) {
// keep path closed
subpath->first()->setProperty(KoPathPoint::CloseSubpath);
}
}
// check if we removed the last point
else if (pointIndex.second == subpath->size()) { // use size as point is already removed
// last point removed, set new StopSubpath
subpath->last()->setProperty(KoPathPoint::StopSubpath);
// check if path was closed
if (point->properties() & KoPathPoint::CloseSubpath) {
// keep path closed
subpath->last()->setProperty(KoPathPoint::CloseSubpath);
}
}
notifyPointsChanged();
return point;
}
bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex)
{
KoSubpath *subpath = d->subPath(pointIndex.first);
if (!subpath || pointIndex.second < 0 || pointIndex.second > subpath->size() - 2
|| isClosedSubpath(pointIndex.first))
return false;
KoSubpath * newSubpath = new KoSubpath;
int size = subpath->size();
for (int i = pointIndex.second + 1; i < size; ++i) {
newSubpath->append(subpath->takeAt(pointIndex.second + 1));
}
// now make the first point of the new subpath a starting node
newSubpath->first()->setProperty(KoPathPoint::StartSubpath);
// the last point of the old subpath is now an ending node
subpath->last()->setProperty(KoPathPoint::StopSubpath);
// insert the new subpath after the broken one
d->subpaths.insert(pointIndex.first + 1, newSubpath);
notifyPointsChanged();
return true;
}
bool KoPathShape::join(int subpathIndex)
{
KoSubpath *subpath = d->subPath(subpathIndex);
KoSubpath *nextSubpath = d->subPath(subpathIndex + 1);
if (!subpath || !nextSubpath || isClosedSubpath(subpathIndex)
|| isClosedSubpath(subpathIndex+1))
return false;
// the last point of the subpath does not end the subpath anymore
subpath->last()->unsetProperty(KoPathPoint::StopSubpath);
// the first point of the next subpath does not start a subpath anymore
nextSubpath->first()->unsetProperty(KoPathPoint::StartSubpath);
// append the second subpath to the first
Q_FOREACH (KoPathPoint * p, *nextSubpath)
subpath->append(p);
// remove the nextSubpath from path
d->subpaths.removeAt(subpathIndex + 1);
// delete it as it is no longer possible to use it
delete nextSubpath;
notifyPointsChanged();
return true;
}
bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex)
{
KoSubpath *subpath = d->subPath(oldSubpathIndex);
if (subpath == 0 || newSubpathIndex >= d->subpaths.size())
return false;
if (oldSubpathIndex == newSubpathIndex)
return true;
d->subpaths.removeAt(oldSubpathIndex);
d->subpaths.insert(newSubpathIndex, subpath);
notifyPointsChanged();
return true;
}
KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex)
{
KoSubpath *subpath = d->subPath(pointIndex.first);
if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size()
|| !isClosedSubpath(pointIndex.first))
return KoPathPointIndex(-1, -1);
KoPathPoint * oldStartPoint = subpath->first();
// the old starting node no longer starts the subpath
oldStartPoint->unsetProperty(KoPathPoint::StartSubpath);
// the old end node no longer closes the subpath
subpath->last()->unsetProperty(KoPathPoint::StopSubpath);
// reorder the subpath
for (int i = 0; i < pointIndex.second; ++i) {
subpath->append(subpath->takeFirst());
}
// make the first point a start node
subpath->first()->setProperty(KoPathPoint::StartSubpath);
// make the last point an end node
subpath->last()->setProperty(KoPathPoint::StopSubpath);
notifyPointsChanged();
return pathPointIndex(oldStartPoint);
}
KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex)
{
KoSubpath *subpath = d->subPath(pointIndex.first);
if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size()
|| isClosedSubpath(pointIndex.first))
return KoPathPointIndex(-1, -1);
KoPathPoint * oldStartPoint = subpath->first();
// the old starting node no longer starts the subpath
oldStartPoint->unsetProperty(KoPathPoint::StartSubpath);
// the old end node no longer ends the subpath
subpath->last()->unsetProperty(KoPathPoint::StopSubpath);
// reorder the subpath
for (int i = 0; i < pointIndex.second; ++i) {
subpath->append(subpath->takeFirst());
}
subpath->first()->setProperty(KoPathPoint::StartSubpath);
subpath->last()->setProperty(KoPathPoint::StopSubpath);
closeSubpathPriv(subpath);
notifyPointsChanged();
return pathPointIndex(oldStartPoint);
}
bool KoPathShape::reverseSubpath(int subpathIndex)
{
KoSubpath *subpath = d->subPath(subpathIndex);
if (subpath == 0)
return false;
int size = subpath->size();
for (int i = 0; i < size; ++i) {
KoPathPoint *p = subpath->takeAt(i);
p->reverse();
subpath->prepend(p);
}
// adjust the position dependent properties
KoPathPoint *first = subpath->first();
KoPathPoint *last = subpath->last();
KoPathPoint::PointProperties firstProps = first->properties();
KoPathPoint::PointProperties lastProps = last->properties();
firstProps |= KoPathPoint::StartSubpath;
firstProps &= ~KoPathPoint::StopSubpath;
lastProps |= KoPathPoint::StopSubpath;
lastProps &= ~KoPathPoint::StartSubpath;
if (firstProps & KoPathPoint::CloseSubpath) {
firstProps |= KoPathPoint::CloseSubpath;
lastProps |= KoPathPoint::CloseSubpath;
}
first->setProperties(firstProps);
last->setProperties(lastProps);
notifyPointsChanged();
return true;
}
KoSubpath * KoPathShape::removeSubpath(int subpathIndex)
{
KoSubpath *subpath = d->subPath(subpathIndex);
if (subpath != 0) {
Q_FOREACH (KoPathPoint* point, *subpath) {
point->setParent(this);
}
d->subpaths.removeAt(subpathIndex);
}
notifyPointsChanged();
return subpath;
}
bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex)
{
if (subpathIndex < 0 || subpathIndex > d->subpaths.size())
return false;
Q_FOREACH (KoPathPoint* point, *subpath) {
point->setParent(this);
}
d->subpaths.insert(subpathIndex, subpath);
notifyPointsChanged();
return true;
}
int KoPathShape::combine(KoPathShape *path)
{
int insertSegmentPosition = -1;
if (!path) return insertSegmentPosition;
- QTransform pathMatrix = path->absoluteTransformation(0);
- QTransform myMatrix = absoluteTransformation(0).inverted();
+ QTransform pathMatrix = path->absoluteTransformation();
+ QTransform myMatrix = absoluteTransformation().inverted();
Q_FOREACH (KoSubpath* subpath, path->d->subpaths) {
KoSubpath *newSubpath = new KoSubpath();
Q_FOREACH (KoPathPoint* point, *subpath) {
KoPathPoint *newPoint = new KoPathPoint(*point, this);
newPoint->map(pathMatrix);
newPoint->map(myMatrix);
newSubpath->append(newPoint);
}
d->subpaths.append(newSubpath);
if (insertSegmentPosition < 0) {
insertSegmentPosition = d->subpaths.size() - 1;
}
}
normalize();
notifyPointsChanged();
return insertSegmentPosition;
}
bool KoPathShape::separate(QList<KoPathShape*> & separatedPaths)
{
if (! d->subpaths.size())
return false;
- QTransform myMatrix = absoluteTransformation(0);
+ QTransform myMatrix = absoluteTransformation();
Q_FOREACH (KoSubpath* subpath, d->subpaths) {
KoPathShape *shape = new KoPathShape();
shape->setStroke(stroke());
shape->setBackground(background());
shape->setShapeId(shapeId());
shape->setZIndex(zIndex());
KoSubpath *newSubpath = new KoSubpath();
Q_FOREACH (KoPathPoint* point, *subpath) {
KoPathPoint *newPoint = new KoPathPoint(*point, shape);
newPoint->map(myMatrix);
newSubpath->append(newPoint);
}
shape->d->subpaths.append(newSubpath);
shape->normalize();
// NOTE: shape cannot have any listeners yet, so no notification about
// points modification is needed
separatedPaths.append(shape);
}
return true;
}
void KoPathShape::closeSubpathPriv(KoSubpath *subpath)
{
if (! subpath)
return;
subpath->last()->setProperty(KoPathPoint::CloseSubpath);
subpath->first()->setProperty(KoPathPoint::CloseSubpath);
notifyPointsChanged();
}
void KoPathShape::closeMergeSubpathPriv(KoSubpath *subpath)
{
if (! subpath || subpath->size() < 2)
return;
KoPathPoint * lastPoint = subpath->last();
KoPathPoint * firstPoint = subpath->first();
// check if first and last points are coincident
if (lastPoint->point() == firstPoint->point()) {
// we are removing the current last point and
// reuse its first control point if active
firstPoint->setProperty(KoPathPoint::StartSubpath);
firstPoint->setProperty(KoPathPoint::CloseSubpath);
if (lastPoint->activeControlPoint1())
firstPoint->setControlPoint1(lastPoint->controlPoint1());
// remove last point
delete subpath->takeLast();
// the new last point closes the subpath now
lastPoint = subpath->last();
lastPoint->setProperty(KoPathPoint::StopSubpath);
lastPoint->setProperty(KoPathPoint::CloseSubpath);
notifyPointsChanged();
} else {
closeSubpathPriv(subpath);
}
}
const KoSubpathList &KoPathShape::subpaths() const
{
return d->subpaths;
}
KoSubpathList &KoPathShape::subpaths()
{
return d->subpaths;
}
void KoPathShape::map(const QTransform &matrix)
{
return d->map(matrix);
}
KoSubpath *KoPathShape::Private::subPath(int subpathIndex) const
{
if (subpathIndex < 0 || subpathIndex >= subpaths.size())
return 0;
return subpaths.at(subpathIndex);
}
QString KoPathShape::pathShapeId() const
{
return KoPathShapeId;
}
QString KoPathShape::toString(const QTransform &matrix) const
{
QString pathString;
// iterate over all subpaths
KoSubpathList::const_iterator pathIt(d->subpaths.constBegin());
for (; pathIt != d->subpaths.constEnd(); ++pathIt) {
KoSubpath::const_iterator pointIt((*pathIt)->constBegin());
// keep a pointer to the first point of the subpath
KoPathPoint *firstPoint(*pointIt);
// keep a pointer to the previous point of the subpath
KoPathPoint *lastPoint = firstPoint;
// keep track if the previous point has an active control point 2
bool activeControlPoint2 = false;
// iterate over all points of the current subpath
for (; pointIt != (*pathIt)->constEnd(); ++pointIt) {
KoPathPoint *currPoint(*pointIt);
if (!currPoint) {
qWarning() << "Found a zero point in the shape's path!";
continue;
}
// first point of subpath ?
if (currPoint == firstPoint) {
// are we starting a subpath ?
if (currPoint->properties() & KoPathPoint::StartSubpath) {
const QPointF p = matrix.map(currPoint->point());
pathString += QString("M%1 %2").arg(p.x()).arg(p.y());
}
}
// end point of curve segment ?
else if (activeControlPoint2 || currPoint->activeControlPoint1()) {
// check if we have a cubic or quadratic curve
const bool isCubic = activeControlPoint2 && currPoint->activeControlPoint1();
KoPathSegment cubicSeg = isCubic ? KoPathSegment(lastPoint, currPoint)
: KoPathSegment(lastPoint, currPoint).toCubic();
if (cubicSeg.first() && cubicSeg.second()) {
const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2());
const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1());
const QPointF p = matrix.map(cubicSeg.second()->point());
pathString += QString("C%1 %2 %3 %4 %5 %6")
.arg(cp1.x()).arg(cp1.y())
.arg(cp2.x()).arg(cp2.y())
.arg(p.x()).arg(p.y());
}
}
// end point of line segment!
else {
const QPointF p = matrix.map(currPoint->point());
pathString += QString("L%1 %2").arg(p.x()).arg(p.y());
}
// last point closes subpath ?
if (currPoint->properties() & KoPathPoint::StopSubpath
&& currPoint->properties() & KoPathPoint::CloseSubpath) {
// add curve when there is a curve on the way to the first point
if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) {
// check if we have a cubic or quadratic curve
const bool isCubic = currPoint->activeControlPoint2() && firstPoint->activeControlPoint1();
KoPathSegment cubicSeg = isCubic ? KoPathSegment(currPoint, firstPoint)
: KoPathSegment(currPoint, firstPoint).toCubic();
if (cubicSeg.first() && cubicSeg.second()) {
const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2());
const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1());
const QPointF p = matrix.map(cubicSeg.second()->point());
pathString += QString("C%1 %2 %3 %4 %5 %6")
.arg(cp1.x()).arg(cp1.y())
.arg(cp2.x()).arg(cp2.y())
.arg(p.x()).arg(p.y());
}
}
pathString += QString("Z");
}
activeControlPoint2 = currPoint->activeControlPoint2();
lastPoint = currPoint;
}
}
return pathString;
}
char nodeType(const KoPathPoint * point)
{
if (point->properties() & KoPathPoint::IsSmooth) {
return 's';
}
else if (point->properties() & KoPathPoint::IsSymmetric) {
return 'z';
}
else {
return 'c';
}
}
QString KoPathShape::Private::nodeTypes() const
{
QString types;
KoSubpathList::const_iterator pathIt(subpaths.constBegin());
for (; pathIt != subpaths.constEnd(); ++pathIt) {
KoSubpath::const_iterator it((*pathIt)->constBegin());
for (; it != (*pathIt)->constEnd(); ++it) {
if (it == (*pathIt)->constBegin()) {
types.append('c');
}
else {
types.append(nodeType(*it));
}
if ((*it)->properties() & KoPathPoint::StopSubpath
&& (*it)->properties() & KoPathPoint::CloseSubpath) {
KoPathPoint * firstPoint = (*pathIt)->first();
types.append(nodeType(firstPoint));
}
}
}
return types;
}
void updateNodeType(KoPathPoint * point, const QChar & nodeType)
{
if (nodeType == 's') {
point->setProperty(KoPathPoint::IsSmooth);
}
else if (nodeType == 'z') {
point->setProperty(KoPathPoint::IsSymmetric);
}
}
void KoPathShape::Private::loadNodeTypes(const KoXmlElement &element)
{
if (element.hasAttributeNS(KoXmlNS::calligra, "nodeTypes")) {
QString nodeTypes = element.attributeNS(KoXmlNS::calligra, "nodeTypes");
QString::const_iterator nIt(nodeTypes.constBegin());
KoSubpathList::const_iterator pathIt(subpaths.constBegin());
for (; pathIt != subpaths.constEnd(); ++pathIt) {
KoSubpath::const_iterator it((*pathIt)->constBegin());
for (; it != (*pathIt)->constEnd(); ++it, nIt++) {
// be sure not to crash if there are not enough nodes in nodeTypes
if (nIt == nodeTypes.constEnd()) {
warnFlake << "not enough nodes in calligra:nodeTypes";
return;
}
// the first node is always of type 'c'
if (it != (*pathIt)->constBegin()) {
updateNodeType(*it, *nIt);
}
if ((*it)->properties() & KoPathPoint::StopSubpath
&& (*it)->properties() & KoPathPoint::CloseSubpath) {
++nIt;
updateNodeType((*pathIt)->first(), *nIt);
}
}
}
}
}
Qt::FillRule KoPathShape::fillRule() const
{
return d->fillRule;
}
void KoPathShape::setFillRule(Qt::FillRule fillRule)
{
d->fillRule = fillRule;
}
KoPathShape * KoPathShape::createShapeFromPainterPath(const QPainterPath &path)
{
KoPathShape * shape = new KoPathShape();
int elementCount = path.elementCount();
for (int i = 0; i < elementCount; i++) {
QPainterPath::Element element = path.elementAt(i);
switch (element.type) {
case QPainterPath::MoveToElement:
shape->moveTo(QPointF(element.x, element.y));
break;
case QPainterPath::LineToElement:
shape->lineTo(QPointF(element.x, element.y));
break;
case QPainterPath::CurveToElement:
shape->curveTo(QPointF(element.x, element.y),
QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y),
QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y));
break;
default:
continue;
}
}
shape->setShapeId(KoPathShapeId);
//shape->normalize();
return shape;
}
bool KoPathShape::hitTest(const QPointF &position) const
{
if (parent() && parent()->isClipped(this) && ! parent()->hitTest(position))
return false;
- QPointF point = absoluteTransformation(0).inverted().map(position);
+ QPointF point = absoluteTransformation().inverted().map(position);
const QPainterPath outlinePath = outline();
if (stroke()) {
KoInsets insets;
stroke()->strokeInsets(this, insets);
QRectF roi(QPointF(-insets.left, -insets.top), QPointF(insets.right, insets.bottom));
roi.moveCenter(point);
if (outlinePath.intersects(roi) || outlinePath.contains(roi))
return true;
} else {
if (outlinePath.contains(point))
return true;
}
// if there is no shadow we can as well just leave
if (! shadow())
return false;
// the shadow has an offset to the shape, so we simply
// check if the position minus the shadow offset hits the shape
- point = absoluteTransformation(0).inverted().map(position - shadow()->offset());
+ point = absoluteTransformation().inverted().map(position - shadow()->offset());
return outlinePath.contains(point);
}
void KoPathShape::setMarker(KoMarker *marker, KoFlake::MarkerPosition pos)
{
if (!marker && d->markersNew.contains(pos)) {
d->markersNew.remove(pos);
} else {
d->markersNew[pos] = marker;
}
}
KoMarker *KoPathShape::marker(KoFlake::MarkerPosition pos) const
{
return d->markersNew[pos].data();
}
bool KoPathShape::hasMarkers() const
{
return !d->markersNew.isEmpty();
}
bool KoPathShape::autoFillMarkers() const
{
return d->autoFillMarkers;
}
void KoPathShape::setAutoFillMarkers(bool value)
{
d->autoFillMarkers = value;
}
void KoPathShape::recommendPointSelectionChange(const QList<KoPathPointIndex> &newSelection)
{
Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) {
PointSelectionChangeListener *pointListener = dynamic_cast<PointSelectionChangeListener*>(listener);
if (pointListener) {
pointListener->recommendPointSelectionChange(this, newSelection);
}
}
}
void KoPathShape::notifyPointsChanged()
{
Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners()) {
PointSelectionChangeListener *pointListener = dynamic_cast<PointSelectionChangeListener*>(listener);
if (pointListener) {
pointListener->notifyPathPointsChanged(this);
}
}
}
QPainterPath KoPathShape::pathStroke(const QPen &pen) const
{
if (d->subpaths.isEmpty()) {
return QPainterPath();
}
QPainterPath pathOutline;
QPainterPathStroker stroker;
stroker.setWidth(0);
stroker.setJoinStyle(Qt::MiterJoin);
stroker.setWidth(pen.widthF());
stroker.setJoinStyle(pen.joinStyle());
stroker.setMiterLimit(pen.miterLimit());
stroker.setCapStyle(pen.capStyle());
stroker.setDashOffset(pen.dashOffset());
stroker.setDashPattern(pen.dashPattern());
QPainterPath path = stroker.createStroke(outline());
pathOutline.addPath(path);
pathOutline.setFillRule(Qt::WindingFill);
return pathOutline;
}
void KoPathShape::PointSelectionChangeListener::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
{
Q_UNUSED(type);
Q_UNUSED(shape);
}
diff --git a/libs/flake/KoPathShape.h b/libs/flake/KoPathShape.h
index a8c1b41d2a..44b7fb994b 100644
--- a/libs/flake/KoPathShape.h
+++ b/libs/flake/KoPathShape.h
@@ -1,534 +1,534 @@
/* This file is part of the KDE project
Copyright (C) 2006, 2011 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007,2009 Thomas Zander <zander@kde.org>
Copyright (C) 2006-2008 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2011 Jean-Nicolas Artaud <jeannicolasartaud@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 KOPATHSHAPE_H
#define KOPATHSHAPE_H
#include "kritaflake_export.h"
#include <QMetaType>
#include <QTransform>
#include "KoTosContainer.h"
#define KoPathShapeId "KoPathShape"
class KoPathSegment;
class KoPathPoint;
class KoPathShapePrivate;
class KoMarker;
class KisHandlePainterHelper;
typedef QPair<int, int> KoPathPointIndex;
/// a KoSubpath contains a path from a moveTo until a close or a new moveTo
typedef QList<KoPathPoint *> KoSubpath;
typedef QList<KoSubpath *> KoSubpathList;
/// The position of a path point within a path shape
/**
* @brief This is the base for all graphical objects.
*
* All graphical objects are based on this object e.g. lines, rectangulars, pies
* and so on.
*
* The KoPathShape uses KoPathPoint's to describe the path of the shape.
*
* Here a short example:
* 3 points connected by a curveTo's described by the following svg:
* M 100,200 C 100,100 250,100 250,200 C 250,200 400,300 400,200.
*
* This will be stored in 3 KoPathPoint's as
* The first point contains in
* point 100,200
* controlPoint2 100,100
* The second point contains in
* point 250,200
* controlPoint1 250,100
* controlPoint2 250,300
* The third point contains in
* point 400,300
* controlPoint1 400,200
*
* Not the segments are stored but the points. Out of the points the segments are
* generated. See the outline method. The reason for storing it like that is that
* it is the points that are modified by the user and not the segments.
*/
class KRITAFLAKE_EXPORT KoPathShape : public KoTosContainer
{
public:
/**
* @brief constructor
*/
KoPathShape();
/**
* @brief
*/
~KoPathShape() override;
KoShape *cloneShape() const override;
/// reimplemented
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override;
+ void paint(QPainter &painter, KoShapePaintingContext &paintContext) const override;
virtual void paintPoints(KisHandlePainterHelper &handlesHelper);
/// reimplemented
QRectF outlineRect() const override;
/// reimplemented
QPainterPath outline() const override;
/// reimplemented
QRectF boundingRect() const override;
/// reimplemented
QSizeF size() const override;
QPainterPath pathStroke(const QPen &pen) const;
/**
* Resize the shape
*
* This makes sure that the pathshape will not be resized to 0 if the new size
* is null as that makes it impossible to undo the change.
*
* All functions that overwrite this function should also use the resizeMatrix
* function to get and use the same data in resizing.
*
* @see resizeMatrix()
*/
void setSize(const QSizeF &size) override;
/// reimplemented
bool hitTest(const QPointF &position) const override;
// reimplemented
void saveOdf(KoShapeSavingContext &context) const override;
// reimplemented
bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override;
// basically the same as loadOdf but adapted to the contour cases
// tag needs to be either contour-polygon or contour-path from another shape
bool loadContourOdf(const KoXmlElement & element, KoShapeLoadingContext &context, const QSizeF &scaleFactor);
/** basically the equivalent saveOdf but adapted to the contour cases
* @param context the saving context
* @param originalSize the original size of the unscaled image.
*/
void saveContourOdf(KoShapeSavingContext &context, const QSizeF &originalSize) const;
/// Removes all subpaths and their points from the path
void clear();
/**
* @brief Starts a new Subpath
*
* Moves the pen to p and starts a new subpath.
*
* @return the newly created point
*/
KoPathPoint *moveTo(const QPointF &p);
/**
* @brief Adds a new line segment
*
* Adds a straight line between the last point and the given point p.
*
* @return the newly created point
*/
KoPathPoint *lineTo(const QPointF &p);
/**
* @brief Adds a new cubic Bezier curve segment.
*
* Adds a cubic Bezier curve between the last point and the given point p,
* using the control points specified by c1 and c2.
*
* @param c1 control point1
* @param c2 control point2
* @param p the endpoint of this curve segment
*
* @return The newly created point
*/
KoPathPoint *curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p);
/**
* @brief Adds a new quadratic Bezier curve segment.
*
* Adds a quadratic Bezier curve between the last point and the given point p,
* using the control point specified by c.
*
* @param c control point
* @param p the endpoint of this curve segment
*
* @return The newly created point
*/
KoPathPoint *curveTo(const QPointF &c, const QPointF &p);
/**
* @brief Add an arc.
*
* Adds an arc starting at the current point. The arc will be converted to bezier curves.
*
* @param rx x radius of the ellipse
* @param ry y radius of the ellipse
* @param startAngle the angle where the arc will be started
* @param sweepAngle the length of the angle
* TODO add param to have angle of the ellipse
*
* @return The newly created point
*/
KoPathPoint *arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle);
/**
* @brief Closes the current subpath
*/
void close();
/**
* @brief Closes the current subpath
*
* It tries to merge the last and first point of the subpath
* to one point and then closes the subpath. If merging is not
* possible as the two point are to far from each other a close
* will be done.
* TODO define a maximum distance between the two points until this is working
*/
void closeMerge();
/**
* @brief Normalizes the path data.
*
* The path points are transformed so that the top-left corner
* of the bounding rect is at (0,0).
* This should be called after adding points to the path or changing
* positions of path points.
* @return the offset by which the points are moved in shape coordinates.
*/
virtual QPointF normalize();
/**
* @brief Returns the path points within the given rectangle.
* @param rect the rectangle the requested points are in
* @return list of points within the rectangle
*/
QList<KoPathPoint*> pointsAt(const QRectF &rect) const;
/**
* @brief Returns the list of path segments within the given rectangle.
* @param rect the rectangle the requested segments are in
* @return list of segments within the rectangle
*/
QList<KoPathSegment> segmentsAt(const QRectF &rect) const;
/**
* @brief Returns the path point index of a given path point
*
* @param point the point for which you want to get the index
* @return path point index of the point if it exists
* otherwise KoPathPointIndex( -1, -1 )
*/
KoPathPointIndex pathPointIndex(const KoPathPoint *point) const;
/**
* @brief Returns the path point specified by a path point index
*
* @param pointIndex index of the point to get
*
* @return KoPathPoint on success, 0 otherwise e.g. out of bounds
*/
KoPathPoint *pointByIndex(const KoPathPointIndex &pointIndex) const;
/**
* @brief Returns the segment specified by a path point index
*
* A segment is defined by the point index of the first point in the segment.
* A segment contains the defined point and its following point. If the subpath is
* closed and the and the pointIndex point to the last point in the subpath, the
* following point is the first point in the subpath.
*
* @param pointIndex index of the first point of the segment
*
* @return Segment containing both points of the segment or KoPathSegment( 0, 0 ) on error e.g. out of bounds
*/
KoPathSegment segmentByIndex(const KoPathPointIndex &pointIndex) const;
/**
* @brief Returns the number of points in the path
*
* @return The number of points in the path
*/
int pointCount() const;
/**
* @brief Returns the number of subpaths in the path
*
* @return The number of subpaths in the path
*/
int subpathCount() const;
/**
* @brief Returns the number of points in a subpath
*
* @return The number of points in the subpath or -1 if subpath out of bounds
*/
int subpathPointCount(int subpathIndex) const;
/**
* @brief Checks if a subpath is closed
*
* @param subpathIndex index of the subpath to check
*
* @return true when the subpath is closed, false otherwise
*/
bool isClosedSubpath(int subpathIndex) const;
/**
* @brief Inserts a new point into the given subpath at the specified position
*
* This method keeps the subpath closed if it is closed, and open when it was
* open. So it can change the properties of the point inserted.
* You might need to update the point before/after to get the desired result
* e.g. when you insert the point into a curve.
*
* @param point to insert
* @param pointIndex index at which the point should be inserted
*
* @return true on success,
* false when pointIndex is out of bounds
*/
bool insertPoint(KoPathPoint *point, const KoPathPointIndex &pointIndex);
/**
* @brief Removes a point from the path.
*
* Note that the ownership of the point will pass to the caller.
*
* @param pointIndex index of the point which should be removed
*
* @return The removed point on success,
* otherwise 0
*/
KoPathPoint *removePoint(const KoPathPointIndex &pointIndex);
/**
* @brief Breaks the path after the point index
*
* The new subpath will be behind the one that was broken. The segment between
* the given point and the one behind will be removed. If you want to split at
* one point insert first a copy of the point behind it.
* This does not work when the subpath is closed. Use openSubpath for this.
* It does not break at the last position of a subpath or if there is only one
* point in the subpath.
*
* @param pointIndex index of the point after which the path should be broken
*
* @return true if the subpath was broken, otherwise false
*/
bool breakAfter(const KoPathPointIndex &pointIndex);
/**
* @brief Joins the given subpath with the following one
*
* Joins the given subpath with the following one by inserting a segment between
* the two subpaths.
* This does nothing if the specified subpath is the last subpath
* or one of both subpaths is closed.
*
* @param subpathIndex index of the subpath being joined with the following subpath
*
* @return true if the subpath was joined, otherwise false
*/
bool join(int subpathIndex);
/**
* @brief Moves the position of a subpath within a path
*
* @param oldSubpathIndex old index of the subpath
* @param newSubpathIndex new index of the subpath
*
* @return true if the subpath was moved, otherwise false e.g. if an index is out of bounds
*/
bool moveSubpath(int oldSubpathIndex, int newSubpathIndex);
/**
* @brief Opens a closed subpath
*
* The subpath is opened by removing the segment before the given point, making
* the given point the new start point of the subpath.
*
* @param pointIndex the index of the point at which to open the closed subpath
* @return the new position of the old first point in the subpath
* otherwise KoPathPointIndex( -1, -1 )
*/
KoPathPointIndex openSubpath(const KoPathPointIndex &pointIndex);
/**
* @brief Close a open subpath
*
* The subpath is closed be inserting a segment between the start and end point, making
* the given point the new start point of the subpath.
*
* @return the new position of the old first point in the subpath
* otherwise KoPathPointIndex( -1, -1 )
*/
KoPathPointIndex closeSubpath(const KoPathPointIndex &pointIndex);
/**
* @brief Reverse subpath
*
* The last point becomes the first point and the first one becomes the last one.
*
* @param subpathIndex the index of the subpath to reverse
*/
bool reverseSubpath(int subpathIndex);
/**
* @brief Removes subpath from the path
* @param subpathIndex the index of the subpath to remove
* @return the removed subpath on success, 0 otherwise.
*/
KoSubpath *removeSubpath(int subpathIndex);
/**
* @brief Adds a subpath at the given index to the path
* @param subpath the subpath to add
* @param subpathIndex the index at which the new subpath should be inserted
* @return true on success, false otherwise e.g. subpathIndex out of bounds
*/
bool addSubpath(KoSubpath *subpath, int subpathIndex);
/**
* @brief Combines two path shapes by appending the data of the specified path.
* @param path the path to combine with
* @return index of the first segment inserted or -1 on failure
*/
int combine(KoPathShape *path);
/**
* @brief Creates separate path shapes, one for each existing subpath.
* @param separatedPaths the list which contains the separated path shapes
* @return true if separating the path was successful, false otherwise
*/
bool separate(QList<KoPathShape*> &separatedPaths);
/**
* Returns the specific path shape id.
*
* Path shape derived shapes have a different shape id which link them
* to their respective shape factories. In most cases they do not have
* a special tool for editing them.
* This function returns the specific shape id for finding the shape
* factory from KoShapeRegistry. The default KoPathShapeId is returned
* from KoShape::shapeId() so that the generic path editing tool gets
* activated when the shape is selected.
*
* @return the specific shape id
*/
virtual QString pathShapeId() const;
/// Returns a odf/svg string representation of the path data with the given matrix applied.
QString toString(const QTransform &matrix = QTransform()) const;
/// Returns the fill rule for the path object
Qt::FillRule fillRule() const;
/// Sets the fill rule to be used for painting the background
void setFillRule(Qt::FillRule fillRule);
/// Creates path shape from given QPainterPath
static KoPathShape *createShapeFromPainterPath(const QPainterPath &path);
/// Returns the viewbox from the given xml element.
static QRect loadOdfViewbox(const KoXmlElement &element);
void setMarker(KoMarker *marker, KoFlake::MarkerPosition pos);
KoMarker* marker(KoFlake::MarkerPosition pos) const;
bool hasMarkers() const;
bool autoFillMarkers() const;
void setAutoFillMarkers(bool value);
public:
struct KRITAFLAKE_EXPORT PointSelectionChangeListener : public ShapeChangeListener {
void notifyShapeChanged(ChangeType type, KoShape *shape) override;
virtual void recommendPointSelectionChange(KoPathShape *shape, const QList<KoPathPointIndex> &newSelection) = 0;
virtual void notifyPathPointsChanged(KoPathShape *shape) = 0;
};
void recommendPointSelectionChange(const QList<KoPathPointIndex> &newSelection);
protected:
void notifyPointsChanged();
protected:
/// constructor: to be used in cloneShape(), not in descendants!
/// \internal
/// XXX private?
KoPathShape(const KoPathShape &rhs);
protected:
/// reimplemented
QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const override;
/// reimplemented
void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) override;
/**
* @brief Add an arc.
*
* Adds an arc starting at the current point. The arc will be converted to bezier curves.
* @param rx x radius of the ellipse
* @param ry y radius of the ellipse
* @param startAngle the angle where the arc will be started
* @param sweepAngle the length of the angle
* TODO add param to have angle of the ellipse
* @param offset to the first point in the arc
* @param curvePoints a array which take the curve points, pass a 'QPointF curvePoins[12]';
*
* @return number of points created by the curve
*/
int arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF &offset, QPointF *curvePoints) const;
/**
* Get the resize matrix
*
* This makes sure that also if the newSize isNull that there will be a
* very small size of 0.000001 pixels
*/
QTransform resizeMatrix( const QSizeF &newSize ) const;
private:
/// close-merges specified subpath
void closeMergeSubpathPriv(KoSubpath *subpath);
/// closes specified subpath
void closeSubpathPriv(KoSubpath *subpath);
void updateLastPriv(KoPathPoint **lastPoint);
protected:
const KoSubpathList &subpaths() const;
/// XXX: refactor this using setter?
KoSubpathList &subpaths();
void map(const QTransform &matrix);
private:
class Private;
- QSharedDataPointer<Private> d;
+ QScopedPointer<Private> d;
};
Q_DECLARE_METATYPE(KoPathShape*)
#endif /* KOPATHSHAPE_H */
diff --git a/libs/flake/KoPathShape_p.h b/libs/flake/KoPathShape_p.h
index f860054ea6..0105a935e0 100644
--- a/libs/flake/KoPathShape_p.h
+++ b/libs/flake/KoPathShape_p.h
@@ -1,94 +1,92 @@
/* This file is part of the KDE project
* Copyright (C) 2009 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 KOPATHSHAPEPRIVATE_H
#define KOPATHSHAPEPRIVATE_H
#include "KoPathShape.h"
#include "KoMarker.h"
#include <QSharedData>
-class KoPathShape::Private : public QSharedData
+class KoPathShape::Private
{
public:
explicit Private();
explicit Private(const Private &rhs);
QRectF handleRect(const QPointF &p, qreal radius) const;
/// Applies the viewbox transformation defined in the given element
void applyViewboxTransformation(const KoXmlElement &element);
void map(const QTransform &matrix);
/**
* @brief Saves the node types
*
* This is inspired by inkscape and uses the same mechanism as they do.
* The only difference is that they use sodipodi:nodeTypes as element and
* we use calligra:nodeTyes as attribute.
* This attribute contains of a string which has the node type of each point
* in it. The following node types exist:
*
* c corner
* s smooth
* z symmetric
*
* The first point of a path is always of the type c.
* If the path is closed the type of the first point is saved in the last element
* E.g. you have a closed path with 2 points in it. The first one (start/end of path)
* is symmetric and the second one is smooth that will result in the nodeType="czs"
* So if there is a closed sub path the nodeTypes contain one more entry then there
* are points. That is due to the first and the last point of a closed sub path get
* merged into one when they are on the same position.
*
* @return The node types as string
*/
QString nodeTypes() const;
/**
* @brief Loads node types
*/
void loadNodeTypes(const KoXmlElement &element);
/**
* @brief Returns subpath at given index
* @param subpathIndex the index of the subpath to return
* @return subPath on success, or 0 when subpathIndex is out of bounds
*/
KoSubpath *subPath(int subpathIndex) const;
#ifndef NDEBUG
/// \internal
void paintDebug(QPainter &painter);
/**
* @brief print debug information about a the points of the path
*/
void debugPath() const;
#endif
Qt::FillRule fillRule;
KoSubpathList subpaths;
QMap<KoFlake::MarkerPosition, QExplicitlySharedDataPointer<KoMarker>> markersNew;
bool autoFillMarkers;
-
- QList<KoPathShape::PointSelectionChangeListener*> pointChangeListeners;
};
#endif
diff --git a/libs/flake/KoPatternBackground.cpp b/libs/flake/KoPatternBackground.cpp
index 41e8a69dd4..7581aff47f 100644
--- a/libs/flake/KoPatternBackground.cpp
+++ b/libs/flake/KoPatternBackground.cpp
@@ -1,482 +1,477 @@
/* This file is part of the KDE project
* Copyright (C) 2008 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 "KoPatternBackground.h"
#include "KoShapeSavingContext.h"
#include "KoImageData.h"
#include "KoImageCollection.h"
#include <KoStyleStack.h>
#include <KoGenStyle.h>
#include <KoGenStyles.h>
#include <KoXmlNS.h>
#include <KoOdfLoadingContext.h>
#include <KoOdfGraphicStyles.h>
#include <KoOdfStylesReader.h>
#include <KoUnit.h>
-#include <KoViewConverter.h>
#include <KoXmlReader.h>
#include <FlakeDebug.h>
#include <QBrush>
#include <QPainter>
#include <QPointer>
#include <QSharedData>
class KoPatternBackground::Private : public QSharedData
{
public:
Private()
: QSharedData()
, repeat(KoPatternBackground::Tiled)
, refPoint(KoPatternBackground::TopLeft)
, imageCollection(0)
, imageData(0)
{
}
~Private()
{
delete imageData;
}
QSizeF targetSize() const {
QSizeF size = imageData->imageSize();
if (targetImageSizePercent.width() > 0.0)
size.setWidth(0.01 * targetImageSizePercent.width() * size.width());
else if (targetImageSize.width() > 0.0)
size.setWidth(targetImageSize.width());
if (targetImageSizePercent.height() > 0.0)
size.setHeight(0.01 * targetImageSizePercent.height() * size.height());
else if (targetImageSize.height() > 0.0)
size.setHeight(targetImageSize.height());
return size;
}
QPointF offsetFromRect(const QRectF &fillRect, const QSizeF &imageSize) const {
QPointF offset;
switch (refPoint) {
case KoPatternBackground::TopLeft:
offset = fillRect.topLeft();
break;
case KoPatternBackground::Top:
offset.setX(fillRect.center().x() - 0.5 * imageSize.width());
offset.setY(fillRect.top());
break;
case KoPatternBackground::TopRight:
offset.setX(fillRect.right() - imageSize.width());
offset.setY(fillRect.top());
break;
case KoPatternBackground::Left:
offset.setX(fillRect.left());
offset.setY(fillRect.center().y() - 0.5 * imageSize.height());
break;
case KoPatternBackground::Center:
offset.setX(fillRect.center().x() - 0.5 * imageSize.width());
offset.setY(fillRect.center().y() - 0.5 * imageSize.height());
break;
case KoPatternBackground::Right:
offset.setX(fillRect.right() - imageSize.width());
offset.setY(fillRect.center().y() - 0.5 * imageSize.height());
break;
case KoPatternBackground::BottomLeft:
offset.setX(fillRect.left());
offset.setY(fillRect.bottom() - imageSize.height());
break;
case KoPatternBackground::Bottom:
offset.setX(fillRect.center().x() - 0.5 * imageSize.width());
offset.setY(fillRect.bottom() - imageSize.height());
break;
case KoPatternBackground::BottomRight:
offset.setX(fillRect.right() - imageSize.width());
offset.setY(fillRect.bottom() - imageSize.height());
break;
default:
break;
}
if (refPointOffsetPercent.x() > 0.0)
offset += QPointF(0.01 * refPointOffsetPercent.x() * imageSize.width(), 0);
if (refPointOffsetPercent.y() > 0.0)
offset += QPointF(0, 0.01 * refPointOffsetPercent.y() * imageSize.height());
return offset;
}
QTransform matrix;
KoPatternBackground::PatternRepeat repeat;
KoPatternBackground::ReferencePoint refPoint;
QSizeF targetImageSize;
QSizeF targetImageSizePercent;
QPointF refPointOffsetPercent;
QPointF tileRepeatOffsetPercent;
QPointer<KoImageCollection> imageCollection;
KoImageData * imageData;
};
// ----------------------------------------------------------------
KoPatternBackground::KoPatternBackground(KoImageCollection *imageCollection)
: KoShapeBackground()
, d(new Private)
{
d->imageCollection = imageCollection;
Q_ASSERT(d->imageCollection);
}
KoPatternBackground::~KoPatternBackground()
{
}
bool KoPatternBackground::compareTo(const KoShapeBackground *other) const
{
Q_UNUSED(other);
return false;
}
void KoPatternBackground::setTransform(const QTransform &matrix)
{
d->matrix = matrix;
}
QTransform KoPatternBackground::transform() const
{
return d->matrix;
}
void KoPatternBackground::setPattern(const QImage &pattern)
{
delete d->imageData;
if (d->imageCollection) {
d->imageData = d->imageCollection->createImageData(pattern);
}
}
void KoPatternBackground::setPattern(KoImageData *imageData)
{
delete d->imageData;
d->imageData = imageData;
}
QImage KoPatternBackground::pattern() const
{
if (d->imageData)
return d->imageData->image();
return QImage();
}
void KoPatternBackground::setRepeat(PatternRepeat repeat)
{
d->repeat = repeat;
}
KoPatternBackground::PatternRepeat KoPatternBackground::repeat() const
{
return d->repeat;
}
KoPatternBackground::ReferencePoint KoPatternBackground::referencePoint() const
{
return d->refPoint;
}
void KoPatternBackground::setReferencePoint(ReferencePoint referencePoint)
{
d->refPoint = referencePoint;
}
QPointF KoPatternBackground::referencePointOffset() const
{
return d->refPointOffsetPercent;
}
void KoPatternBackground::setReferencePointOffset(const QPointF &offset)
{
qreal ox = qMax(qreal(0.0), qMin(qreal(100.0), offset.x()));
qreal oy = qMax(qreal(0.0), qMin(qreal(100.0), offset.y()));
d->refPointOffsetPercent = QPointF(ox, oy);
}
QPointF KoPatternBackground::tileRepeatOffset() const
{
return d->tileRepeatOffsetPercent;
}
void KoPatternBackground::setTileRepeatOffset(const QPointF &offset)
{
d->tileRepeatOffsetPercent = offset;
}
QSizeF KoPatternBackground::patternDisplaySize() const
{
return d->targetSize();
}
void KoPatternBackground::setPatternDisplaySize(const QSizeF &size)
{
d->targetImageSizePercent = QSizeF();
d->targetImageSize = size;
}
QSizeF KoPatternBackground::patternOriginalSize() const
{
return d->imageData->imageSize();
}
-void KoPatternBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const
+void KoPatternBackground::paint(QPainter &painter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const
{
if (! d->imageData)
return;
painter.save();
if (d->repeat == Tiled) {
// calculate scaling of pixmap
QSizeF targetSize = d->targetSize();
QSizeF imageSize = d->imageData->imageSize();
qreal scaleX = targetSize.width() / imageSize.width();
qreal scaleY = targetSize.height() / imageSize.height();
QRectF targetRect = fillPath.boundingRect();
// undo scaling on target rectangle
targetRect.setWidth(targetRect.width() / scaleX);
targetRect.setHeight(targetRect.height() / scaleY);
// determine pattern offset
QPointF offset = d->offsetFromRect(targetRect, imageSize);
// create matrix for pixmap scaling
QTransform matrix;
matrix.scale(scaleX, scaleY);
painter.setClipPath(fillPath);
painter.setWorldTransform(matrix, true);
painter.drawTiledPixmap(targetRect, d->imageData->pixmap(imageSize.toSize()), -offset);
} else if (d->repeat == Original) {
QRectF sourceRect(QPointF(0, 0), d->imageData->imageSize());
QRectF targetRect(QPoint(0, 0), d->targetSize());
targetRect.moveCenter(fillPath.boundingRect().center());
painter.setClipPath(fillPath);
painter.drawPixmap(targetRect, d->imageData->pixmap(sourceRect.size().toSize()), sourceRect);
} else if (d->repeat == Stretched) {
painter.setClipPath(fillPath);
// undo conversion of the scaling so that we can use a nicely scaled image of the correct size
- qreal zoomX, zoomY;
- converter.zoom(&zoomX, &zoomY);
- zoomX = zoomX ? 1 / zoomX : zoomX;
- zoomY = zoomY ? 1 / zoomY : zoomY;
- painter.scale(zoomX, zoomY);
- QRectF targetRect = converter.documentToView(fillPath.boundingRect());
+ qWarning() << "WARNING: stretched KoPatternBackground painting code is abandoned. The result might be not correct";
+ const QRectF targetRect = fillPath.boundingRect();
painter.drawPixmap(targetRect.topLeft(), d->imageData->pixmap(targetRect.size().toSize()));
}
painter.restore();
}
void KoPatternBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context)
{
if (! d->imageData)
return;
switch (d->repeat) {
case Original:
style.addProperty("style:repeat", "no-repeat");
break;
case Tiled:
style.addProperty("style:repeat", "repeat");
break;
case Stretched:
style.addProperty("style:repeat", "stretch");
break;
}
if (d->repeat == Tiled) {
QString refPointId = "top-left";
switch (d->refPoint) {
case TopLeft: refPointId = "top-left"; break;
case Top: refPointId = "top"; break;
case TopRight: refPointId = "top-right"; break;
case Left: refPointId = "left"; break;
case Center: refPointId = "center"; break;
case Right: refPointId = "right"; break;
case BottomLeft: refPointId = "bottom-left"; break;
case Bottom: refPointId = "bottom"; break;
case BottomRight: refPointId = "bottom-right"; break;
}
style.addProperty("draw:fill-image-ref-point", refPointId);
if (d->refPointOffsetPercent.x() > 0.0)
style.addProperty("draw:fill-image-ref-point-x", QString("%1%").arg(d->refPointOffsetPercent.x()));
if (d->refPointOffsetPercent.y() > 0.0)
style.addProperty("draw:fill-image-ref-point-y", QString("%1%").arg(d->refPointOffsetPercent.y()));
}
if (d->repeat != Stretched) {
QSizeF targetSize = d->targetSize();
QSizeF imageSize = d->imageData->imageSize();
if (targetSize.height() != imageSize.height())
style.addPropertyPt("draw:fill-image-height", targetSize.height());
if (targetSize.width() != imageSize.width())
style.addPropertyPt("draw:fill-image-width", targetSize.width());
}
KoGenStyle patternStyle(KoGenStyle::FillImageStyle /*no family name*/);
patternStyle.addAttribute("xlink:show", "embed");
patternStyle.addAttribute("xlink:actuate", "onLoad");
patternStyle.addAttribute("xlink:type", "simple");
patternStyle.addAttribute("xlink:href", context.imageHref(d->imageData));
QString patternStyleName = context.mainStyles().insert(patternStyle, "picture");
style.addProperty("draw:fill", "bitmap");
style.addProperty("draw:fill-image-name", patternStyleName);
if (d->imageCollection) {
context.addDataCenter(d->imageCollection);
}
}
bool KoPatternBackground::loadStyle(KoOdfLoadingContext &context, const QSizeF &)
{
KoStyleStack &styleStack = context.styleStack();
if (! styleStack.hasProperty(KoXmlNS::draw, "fill"))
return false;
QString fillStyle = styleStack.property(KoXmlNS::draw, "fill");
if (fillStyle != "bitmap")
return false;
QString styleName = styleStack.property(KoXmlNS::draw, "fill-image-name");
KoXmlElement* e = context.stylesReader().drawStyles("fill-image")[styleName];
if (! e)
return false;
const QString href = e->attributeNS(KoXmlNS::xlink, "href", QString());
if (href.isEmpty())
return false;
delete d->imageData;
d->imageData = 0;
if (d->imageCollection) {
d->imageData = d->imageCollection->createImageData(href, context.store());
}
if (! d->imageData) {
return false;
}
// read the pattern repeat style
QString style = styleStack.property(KoXmlNS::style, "repeat");
if (style == "stretch")
d->repeat = Stretched;
else if (style == "no-repeat")
d->repeat = Original;
else
d->repeat = Tiled;
if (style != "stretch") {
// optional attributes which can override original image size
if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-height")) {
QString height = styleStack.property(KoXmlNS::draw, "fill-image-height");
if (height.endsWith('%'))
d->targetImageSizePercent.setHeight(height.remove('%').toDouble());
else
d->targetImageSize.setHeight(KoUnit::parseValue(height));
}
if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-width")) {
QString width = styleStack.property(KoXmlNS::draw, "fill-image-width");
if (width.endsWith('%'))
d->targetImageSizePercent.setWidth(width.remove('%').toDouble());
else
d->targetImageSize.setWidth(KoUnit::parseValue(width));
}
}
if (style == "repeat") {
if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point")) {
// align pattern to the given size
QString align = styleStack.property(KoXmlNS::draw, "fill-image-ref-point");
if (align == "top-left")
d->refPoint = TopLeft;
else if (align == "top")
d->refPoint = Top;
else if (align == "top-right")
d->refPoint = TopRight;
else if (align == "left")
d->refPoint = Left;
else if (align == "center")
d->refPoint = Center;
else if (align == "right")
d->refPoint = Right;
else if (align == "bottom-left")
d->refPoint = BottomLeft;
else if (align == "bottom")
d->refPoint = Bottom;
else if (align == "bottom-right")
d->refPoint = BottomRight;
}
if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-x")) {
QString pointX = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-x");
d->refPointOffsetPercent.setX(pointX.remove('%').toDouble());
}
if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-y")) {
QString pointY = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-y");
d->refPointOffsetPercent.setY(pointY.remove('%').toDouble());
}
if (styleStack.hasProperty(KoXmlNS::draw, "tile-repeat-offset")) {
QString repeatOffset = styleStack.property(KoXmlNS::draw, "tile-repeat-offset");
QStringList tokens = repeatOffset.split('%');
if (tokens.count() == 2) {
QString direction = tokens[1].simplified();
if (direction == "horizontal")
d->tileRepeatOffsetPercent.setX(tokens[0].toDouble());
else if (direction == "vertical")
d->tileRepeatOffsetPercent.setY(tokens[0].toDouble());
}
}
}
return true;
}
QRectF KoPatternBackground::patternRectFromFillSize(const QSizeF &size)
{
QRectF rect;
switch (d->repeat) {
case Tiled:
rect.setTopLeft(d->offsetFromRect(QRectF(QPointF(), size), d->targetSize()));
rect.setSize(d->targetSize());
break;
case Original:
rect.setLeft(0.5 * (size.width() - d->targetSize().width()));
rect.setTop(0.5 * (size.height() - d->targetSize().height()));
rect.setSize(d->targetSize());
break;
case Stretched:
rect.setTopLeft(QPointF(0.0, 0.0));
rect.setSize(size);
break;
}
return rect;
}
diff --git a/libs/flake/KoPatternBackground.h b/libs/flake/KoPatternBackground.h
index cec17a6a66..5500f51d59 100644
--- a/libs/flake/KoPatternBackground.h
+++ b/libs/flake/KoPatternBackground.h
@@ -1,130 +1,130 @@
/* This file is part of the KDE project
* Copyright (C) 2008 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.
*/
#ifndef KOPATTERNBACKGROUND_H
#define KOPATTERNBACKGROUND_H
#include "KoShapeBackground.h"
#include "kritaflake_export.h"
#include <QSharedDataPointer>
class KoImageCollection;
class KoOdfLoadingContext;
class KoPatternBackgroundPrivate;
class KoImageData;
class QTransform;
class QImage;
class QPointF;
class QRectF;
/// A pattern shape background
class KRITAFLAKE_EXPORT KoPatternBackground : public KoShapeBackground
{
public:
/// Pattern rendering style
enum PatternRepeat {
Original,
Tiled,
Stretched
};
/// Pattern reference point
enum ReferencePoint {
TopLeft,
Top,
TopRight,
Left,
Center,
Right,
BottomLeft,
Bottom,
BottomRight
};
/// Constructs a new pattern background utilizing the given image collection
explicit KoPatternBackground(KoImageCollection *collection);
~KoPatternBackground() override;
bool compareTo(const KoShapeBackground *other) const override;
/// Sets the transform matrix
void setTransform(const QTransform &matrix);
/// Returns the transform matrix
QTransform transform() const;
/// Sets a new pattern
void setPattern(const QImage &pattern);
/// Sets a new pattern. imageData memory is deleted inside this class
void setPattern(KoImageData *imageData);
/// Returns the pattern
QImage pattern() const;
/// Sets the pattern repeatgfl
void setRepeat(PatternRepeat repeat);
/// Returns the pattern repeat
PatternRepeat repeat() const;
/// Returns the pattern reference point identifier
ReferencePoint referencePoint() const;
/// Sets the pattern reference point
void setReferencePoint(ReferencePoint referencePoint);
/// Returns reference point offset in percent of the pattern display size
QPointF referencePointOffset() const;
/// Sets the reference point offset in percent of the pattern display size
void setReferencePointOffset(const QPointF &offset);
/// Returns tile repeat offset in percent of the pattern display size
QPointF tileRepeatOffset() const;
/// Sets the tile repeat offset in percent of the pattern display size
void setTileRepeatOffset(const QPointF &offset);
/// Returns the pattern display size
QSizeF patternDisplaySize() const;
/// Sets pattern display size
void setPatternDisplaySize(const QSizeF &size);
/// Returns the original image size
QSizeF patternOriginalSize() const;
/// reimplemented from KoShapeBackground
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override;
+ void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const override;
/// reimplemented from KoShapeBackground
void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override;
/// reimplemented from KoShapeBackground
bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override;
/// Returns the bounding rect of the pattern image based on the given fill size
QRectF patternRectFromFillSize(const QSizeF &size);
private:
class Private;
QSharedDataPointer<Private> d;
};
#endif // KOPATTERNBACKGROUND_H
diff --git a/libs/flake/KoRTree.h b/libs/flake/KoRTree.h
index 42e97d7785..81ceebacfa 100644
--- a/libs/flake/KoRTree.h
+++ b/libs/flake/KoRTree.h
@@ -1,1165 +1,1166 @@
/* This file is part of the KDE project
Copyright (c) 2006 Thorsten Zachmann <zachmann@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.
Based on code from Wolfgang Baer - WBaer@gmx.de
*/
#ifndef KORTREE_H
#define KORTREE_H
#include <QPair>
#include <QMap>
#include <QList>
#include <QVector>
#include <QPointF>
#include <QRectF>
#include <QVarLengthArray>
#include <QDebug>
#include "kis_assert.h"
// #define CALLIGRA_RTREE_DEBUG
#ifdef CALLIGRA_RTREE_DEBUG
#include <QPainter>
#endif
/**
* @brief The KoRTree class is a template class that provides a R-tree.
*
* This class implements a R-tree as described in
* "R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING" by Antonin Guttman
*
* It only supports 2 dimensional bounding boxes which are represented by a QRectF.
* For node splitting the Quadratic-Cost Algorithm is used as described by Guttman.
*/
template <typename T>
class KoRTree
{
public:
/**
* @brief Constructor
*
* @param capacity the capacity a node can take
* @param minimum the minimum filling of a node max 0.5 * capacity
*/
KoRTree(int capacity, int minimum);
/**
* @brief Destructor
*/
virtual ~KoRTree();
/**
* @brief Insert data item into the tree
*
* This will insert a data item into the tree. If necessary the tree will
* adjust itself.
*
* @param data
* @param bb
*/
virtual void insert(const QRectF& bb, const T& data);
/**
* @brief Show if a shape is a part of the tree
* @param data
*/
bool contains(const T &data);
/**
* @brief Remove a data item from the tree
*
* This removed a data item from the tree. If necessary the tree will
* adjust itself.
*
* @param data
*/
void remove(const T& data);
/**
* @brief Find all data items which intersects rect
* The items are sorted by insertion time in ascending order.
*
* @param rect where the objects have to be in
*
* @return objects intersecting the rect
*/
virtual QList<T> intersects(const QRectF& rect) const;
/**
* @brief Find all data item which contain the point
* The items are sorted by insertion time in ascending order.
*
* @param point which should be contained in the objects
*
* @return objects which contain the point
*/
QList<T> contains(const QPointF &point) const;
/**
* @brief Find all data item which contain the point
* The items are sorted by insertion time in ascending order.
*
* @param point which should be contained in the objects
*
* @return objects which contain the point
*/
QList<T> contained(const QRectF &point) const;
/**
* @brief Find all data rectangles
* The order is NOT guaranteed to be the same as that used by values().
*
* @return a list containing all the data rectangles used in the tree
*/
QList<QRectF> keys() const;
/**
* @brief Find all data items
* The order is NOT guaranteed to be the same as that used by keys().
*
* @return a list containing all the data used in the tree
*/
QList<T> values() const;
virtual void clear() {
delete m_root;
m_root = createLeafNode(m_capacity + 1, 0, 0);
m_leafMap.clear();
}
#ifdef CALLIGRA_RTREE_DEBUG
/**
* @brief Paint the tree
*
* @param p painter which should be used for painting
*/
void paint(QPainter & p) const;
/**
* @brief Print the tree using qdebug
*/
void debug() const;
#endif
protected:
class NonLeafNode;
class LeafNode;
class Node
{
public:
#ifdef CALLIGRA_RTREE_DEBUG
static int nodeIdCnt;
#endif
Node(int capacity, int level, Node * parent);
virtual ~Node() {}
virtual void remove(int index);
// move node between nodes of the same type from node
virtual void move(Node * node, int index) = 0;
virtual LeafNode * chooseLeaf(const QRectF& bb) = 0;
virtual NonLeafNode * chooseNode(const QRectF& bb, int level) = 0;
virtual void intersects(const QRectF& rect, QMap<int, T> & result) const = 0;
virtual void contains(const QPointF & point, QMap<int, T> & result) const = 0;
virtual void contained(const QRectF & point, QMap<int, T> & result) const = 0;
virtual void keys(QList<QRectF> & result) const = 0;
virtual void values(QMap<int, T> & result) const = 0;
virtual Node * parent() const {
return m_parent;
}
virtual void setParent(Node * parent) {
m_parent = parent;
}
virtual int childCount() const {
return m_counter;
}
virtual const QRectF& boundingBox() const {
return m_boundingBox;
}
virtual void updateBoundingBox();
virtual const QRectF& childBoundingBox(int index) const {
return m_childBoundingBox[index];
}
virtual void setChildBoundingBox(int index, const QRectF& rect) {
m_childBoundingBox[index] = rect;
}
virtual void clear();
virtual bool isRoot() const {
return m_parent == 0;
}
virtual bool isLeaf() const {
return false;
}
virtual int place() const {
return m_place;
}
virtual void setPlace(int place) {
m_place = place;
}
virtual int level() const {
return m_level;
}
virtual void setLevel(int level) {
m_level = level;
}
#ifdef CALLIGRA_RTREE_DEBUG
virtual int nodeId() const {
return m_nodeId;
}
virtual void paint(QPainter & p, int level) const = 0;
virtual void debug(QString line) const = 0;
protected:
#define levelColorSize 5
static QColor levelColor[levelColorSize];
virtual void paintRect(QPainter & p, int level) const;
#endif
protected:
Node * m_parent;
QRectF m_boundingBox;
QVector<QRectF> m_childBoundingBox;
int m_counter;
// the position in the parent
int m_place;
#ifdef CALLIGRA_RTREE_DEBUG
int m_nodeId;
#endif
int m_level;
};
class NonLeafNode : virtual public Node
{
public:
NonLeafNode(int capacity, int level, Node * parent);
~NonLeafNode() override;
virtual void insert(const QRectF& bb, Node * data);
void remove(int index) override;
void move(Node * node, int index) override;
LeafNode * chooseLeaf(const QRectF& bb) override;
NonLeafNode * chooseNode(const QRectF& bb, int level) override;
void intersects(const QRectF& rect, QMap<int, T> & result) const override;
void contains(const QPointF & point, QMap<int, T> & result) const override;
void contained(const QRectF & point, QMap<int, T> & result) const override;
void keys(QList<QRectF> & result) const override;
void values(QMap<int, T> & result) const override;
virtual Node * getNode(int index) const;
#ifdef CALLIGRA_RTREE_DEBUG
virtual void paint(QPainter & p, int level) const;
virtual void debug(QString line) const;
#endif
protected:
virtual Node * getLeastEnlargement(const QRectF& bb) const;
QVector<Node *> m_childs;
};
class LeafNode : virtual public Node
{
public:
static int dataIdCounter;
LeafNode(int capacity, int level, Node * parent);
~LeafNode() override;
virtual void insert(const QRectF& bb, const T& data, int id);
void remove(int index) override;
virtual void remove(const T& data);
void move(Node * node, int index) override;
LeafNode * chooseLeaf(const QRectF& bb) override;
NonLeafNode * chooseNode(const QRectF& bb, int level) override;
void intersects(const QRectF& rect, QMap<int, T> & result) const override;
void contains(const QPointF & point, QMap<int, T> & result) const override;
void contained(const QRectF & point, QMap<int, T> & result) const override;
void keys(QList<QRectF> & result) const override;
void values(QMap<int, T> & result) const override;
virtual const T& getData(int index) const;
virtual int getDataId(int index) const;
bool isLeaf() const override {
return true;
}
#ifdef CALLIGRA_RTREE_DEBUG
virtual void debug(QString line) const;
virtual void paint(QPainter & p, int level) const;
#endif
protected:
QVector<T> m_data;
QVector<int> m_dataIds;
};
// factory methods
virtual LeafNode* createLeafNode(int capacity, int level, Node * parent) {
return new LeafNode(capacity, level, parent);
}
virtual NonLeafNode* createNonLeafNode(int capacity, int level, Node * parent) {
return new NonLeafNode(capacity, level, parent);
}
// methods for insert
QPair<Node *, Node *> splitNode(Node * node);
QPair<int, int> pickSeeds(Node * node);
QPair<int, int> pickNext(Node * node, QVector<bool> & marker, Node * group1, Node * group2);
virtual void adjustTree(Node * node1, Node * node2);
void insertHelper(const QRectF& bb, const T& data, int id);
// methods for delete
void insert(Node * node);
virtual void condenseTree(Node * node, QVector<Node *> & reinsert);
int m_capacity;
int m_minimum;
Node * m_root;
QMap<T, LeafNode *> m_leafMap;
};
template <typename T>
KoRTree<T>::KoRTree(int capacity, int minimum)
: m_capacity(capacity)
, m_minimum(minimum)
, m_root(createLeafNode(m_capacity + 1, 0, 0))
{
if (minimum > capacity / 2)
qFatal("KoRTree::KoRTree minimum can be maximal capacity/2");
//debugFlake << "root node " << m_root->nodeId();
}
template <typename T>
KoRTree<T>::~KoRTree()
{
delete m_root;
}
template <typename T>
void KoRTree<T>::insert(const QRectF& bb, const T& data)
{
// check if the shape is not already registered
KIS_SAFE_ASSERT_RECOVER_NOOP(!m_leafMap[data]);
insertHelper(bb, data, LeafNode::dataIdCounter++);
}
template <typename T>
void KoRTree<T>::insertHelper(const QRectF& bb, const T& data, int id)
{
QRectF nbb(bb.normalized());
// This has to be done as it is not possible to use QRectF::united() with a isNull()
if (nbb.isNull()) {
+ qWarning() << "KoRTree::insert boundingBox isNull setting size to" << nbb.size();
+
nbb.setWidth(0.0001);
nbb.setHeight(0.0001);
- qWarning() << "KoRTree::insert boundingBox isNull setting size to" << nbb.size();
}
else {
// This has to be done as QRectF::intersects() return false if the rect does not have any area overlapping.
// If there is no width or height there is no area and therefore no overlapping.
if ( nbb.width() == 0 ) {
nbb.setWidth(0.0001);
}
if ( nbb.height() == 0 ) {
nbb.setHeight(0.0001);
}
}
LeafNode * leaf = m_root->chooseLeaf(nbb);
//debugFlake << " leaf" << leaf->nodeId() << nbb;
if (leaf->childCount() < m_capacity) {
leaf->insert(nbb, data, id);
m_leafMap[data] = leaf;
adjustTree(leaf, 0);
} else {
leaf->insert(nbb, data, id);
m_leafMap[data] = leaf;
QPair<Node *, Node *> newNodes = splitNode(leaf);
LeafNode * l = dynamic_cast<LeafNode *>(newNodes.first);
if (l)
for (int i = 0; i < l->childCount(); ++i)
m_leafMap[l->getData(i)] = l;
l = dynamic_cast<LeafNode *>(newNodes.second);
if (l)
for (int i = 0; i < l->childCount(); ++i)
m_leafMap[l->getData(i)] = l;
adjustTree(newNodes.first, newNodes.second);
}
}
template <typename T>
void KoRTree<T>::insert(Node * node)
{
if (node->level() == m_root->level()) {
adjustTree(m_root, node);
} else {
QRectF bb(node->boundingBox());
NonLeafNode * newParent = m_root->chooseNode(bb, node->level() + 1);
newParent->insert(bb, node);
QPair<Node *, Node *> newNodes(node, 0);
if (newParent->childCount() > m_capacity) {
newNodes = splitNode(newParent);
}
adjustTree(newNodes.first, newNodes.second);
}
}
template <typename T>
bool KoRTree<T>::contains(const T &data)
{
return m_leafMap[data];
}
template <typename T>
void KoRTree<T>::remove(const T&data)
{
//debugFlake << "KoRTree remove";
LeafNode * leaf = m_leafMap[data];
// Trying to remove inexistent leaf. Most probably, this leaf hasn't been added
// to the shape manager correctly
KIS_SAFE_ASSERT_RECOVER_RETURN(leaf);
m_leafMap.remove(data);
leaf->remove(data);
/**
* WARNING: after calling condenseTree() the temporary enters an inconsistent state!
* m_leafMap still points to the nodes now stored in 'reinsert' list, although
* they are not a part of the hierarchy. This state does not cause any use
* visible changes, but should be considered while implementing sanity checks.
*/
QVector<Node *> reinsert;
condenseTree(leaf, reinsert);
for (int i = 0; i < reinsert.size(); ++i) {
if (reinsert[i]->isLeaf()) {
LeafNode * leaf = dynamic_cast<LeafNode *>(reinsert[i]);
for (int j = 0; j < leaf->childCount(); ++j) {
insertHelper(leaf->childBoundingBox(j), leaf->getData(j), leaf->getDataId(j));
}
// clear is needed as the data items are not removed when insert into a new node
leaf->clear();
delete leaf;
} else {
NonLeafNode * node = dynamic_cast<NonLeafNode *>(reinsert[i]);
for (int j = 0; j < node->childCount(); ++j) {
insert(node->getNode(j));
}
// clear is needed as the data items are not removed when insert into a new node
node->clear();
delete node;
}
}
}
template <typename T>
QList<T> KoRTree<T>::intersects(const QRectF& rect) const
{
QMap<int, T> found;
m_root->intersects(rect, found);
return found.values();
}
template <typename T>
QList<T> KoRTree<T>::contains(const QPointF &point) const
{
QMap<int, T> found;
m_root->contains(point, found);
return found.values();
}
template <typename T>
QList<T> KoRTree<T>::contained(const QRectF& rect) const
{
QMap<int, T> found;
m_root->contained(rect, found);
return found.values();
}
template <typename T>
QList<QRectF> KoRTree<T>::keys() const
{
QList<QRectF> found;
m_root->keys(found);
return found;
}
template <typename T>
QList<T> KoRTree<T>::values() const
{
QMap<int, T> found;
m_root->values(found);
return found.values();
}
#ifdef CALLIGRA_RTREE_DEBUG
template <typename T>
void KoRTree<T>::paint(QPainter & p) const
{
if (m_root) {
m_root->paint(p, 0);
}
}
template <typename T>
void KoRTree<T>::debug() const
{
QString prefix("");
m_root->debug(prefix);
}
#endif
template <typename T>
QPair< typename KoRTree<T>::Node*, typename KoRTree<T>::Node* > KoRTree<T>::splitNode(typename KoRTree<T>::Node* node)
{
//debugFlake << "KoRTree::splitNode" << node;
Node * n1;
Node * n2;
if (node->isLeaf()) {
n1 = createLeafNode(m_capacity + 1, node->level(), node->parent());
n2 = createLeafNode(m_capacity + 1, node->level(), node->parent());
} else {
n1 = createNonLeafNode(m_capacity + 1, node->level(), node->parent());
n2 = createNonLeafNode(m_capacity + 1, node->level(), node->parent());
}
//debugFlake << " n1" << n1 << n1->nodeId();
//debugFlake << " n2" << n2 << n2->nodeId();
QVector<bool> marker(m_capacity + 1);
QPair<int, int> seeds(pickSeeds(node));
n1->move(node, seeds.first);
n2->move(node, seeds.second);
marker[seeds.first] = true;
marker[seeds.second] = true;
// There is one more in a node to split than the capacity and as we
// already put the seeds in the new nodes subtract them.
int remaining = m_capacity + 1 - 2;
while (remaining > 0) {
if (m_minimum - n1->childCount() == remaining) {
for (int i = 0; i < m_capacity + 1; ++i) {
if (!marker[i]) {
n1->move(node, i);
--remaining;
}
}
} else if (m_minimum - n2->childCount() == remaining) {
for (int i = 0; i < m_capacity + 1; ++i) {
if (!marker[i]) {
n2->move(node, i);
--remaining;
}
}
} else {
QPair<int, int> next(pickNext(node, marker, n1, n2));
if (next.first == 0) {
n1->move(node, next.second);
} else {
n2->move(node, next.second);
}
--remaining;
}
}
Q_ASSERT(n1->childCount() + n2->childCount() == node->childCount());
// move the data back to the old node
// this has to be done as the current node is already in the tree.
node->clear();
for (int i = 0; i < n1->childCount(); ++i) {
node->move(n1, i);
}
//debugFlake << " delete n1" << n1 << n1->nodeId();
// clear is needed as the data items are not removed
n1->clear();
delete n1;
return qMakePair(node, n2);
}
template <typename T>
QPair<int, int> KoRTree<T>::pickSeeds(Node *node)
{
int s1 = 0;
int s2 = 1;
qreal max = 0;
for (int i = 0; i < m_capacity + 1; ++i) {
for (int j = i+1; j < m_capacity + 1; ++j) {
if (i != j) {
QRectF bb1(node->childBoundingBox(i));
QRectF bb2(node->childBoundingBox(j));
QRectF comp(node->childBoundingBox(i).united(node->childBoundingBox(j)));
qreal area = comp.width() * comp.height() - bb1.width() * bb1.height() - bb2.width() * bb2.height();
//debugFlake << " ps" << i << j << area;
if (area > max) {
max = area;
s1 = i;
s2 = j;
}
}
}
}
return qMakePair(s1, s2);
}
template <typename T>
QPair<int, int> KoRTree<T>::pickNext(Node * node, QVector<bool> & marker, Node * group1, Node * group2)
{
//debugFlake << "KoRTree::pickNext" << marker;
qreal max = -1.0;
int select = 0;
int group = 0;
for (int i = 0; i < m_capacity + 1; ++i) {
if (marker[i] == false) {
QRectF bb1 = group1->boundingBox().united(node->childBoundingBox(i));
QRectF bb2 = group2->boundingBox().united(node->childBoundingBox(i));
qreal d1 = bb1.width() * bb1.height() - group1->boundingBox().width() * group1->boundingBox().height();
qreal d2 = bb2.width() * bb2.height() - group2->boundingBox().width() * group2->boundingBox().height();
qreal diff = qAbs(d1 - d2);
//debugFlake << " diff" << diff << i << d1 << d2;
if (diff > max) {
max = diff;
select = i;
//debugFlake << " i =" << i;
if (qAbs(d1) > qAbs(d2)) {
group = 1;
} else {
group = 0;
}
//debugFlake << " group =" << group;
}
}
}
marker[select] = true;
return qMakePair(group, select);
}
template <typename T>
void KoRTree<T>::adjustTree(Node *node1, Node *node2)
{
//debugFlake << "KoRTree::adjustTree";
if (node1->isRoot()) {
//debugFlake << " root";
if (node2) {
NonLeafNode * newRoot = createNonLeafNode(m_capacity + 1, node1->level() + 1, 0);
newRoot->insert(node1->boundingBox(), node1);
newRoot->insert(node2->boundingBox(), node2);
m_root = newRoot;
//debugFlake << "new root" << m_root->nodeId();
}
} else {
NonLeafNode * parent = dynamic_cast<NonLeafNode *>(node1->parent());
if (!parent) {
qFatal("KoRTree::adjustTree: no parent node found!");
return;
}
//QRectF pbbold( parent->boundingBox() );
parent->setChildBoundingBox(node1->place(), node1->boundingBox());
parent->updateBoundingBox();
//debugFlake << " bb1 =" << node1->boundingBox() << node1->place() << pbbold << "->" << parent->boundingBox() << parent->nodeId();
if (!node2) {
//debugFlake << " update";
adjustTree(parent, 0);
} else {
if (parent->childCount() < m_capacity) {
//debugFlake << " no split needed";
parent->insert(node2->boundingBox(), node2);
adjustTree(parent, 0);
} else {
//debugFlake << " split again";
parent->insert(node2->boundingBox(), node2);
QPair<Node *, Node *> newNodes = splitNode(parent);
adjustTree(newNodes.first, newNodes.second);
}
}
}
}
template <typename T>
void KoRTree<T>::condenseTree(Node *node, QVector<Node*> & reinsert)
{
//debugFlake << "KoRTree::condenseTree begin reinsert.size()" << reinsert.size();
if (!node->isRoot()) {
Node * parent = node->parent();
//debugFlake << " !node->isRoot us" << node->childCount();
if (node->childCount() < m_minimum) {
//debugFlake << " remove node";
parent->remove(node->place());
reinsert.push_back(node);
/**
* WARNING: here we leave the tree in an inconsistent state! 'reinsert'
* nodes may still be kept in m_leafMap structure, but we will
* *not* remove them for the efficiency reasons. They are guaranteed
* to be readded in remove().
*/
} else {
//debugFlake << " update BB parent is root" << parent->isRoot();
parent->setChildBoundingBox(node->place(), node->boundingBox());
parent->updateBoundingBox();
}
condenseTree(parent, reinsert);
} else {
//debugFlake << " node->isRoot us" << node->childCount();
if (node->childCount() == 1 && !node->isLeaf()) {
//debugFlake << " usedSpace = 1";
NonLeafNode * n = dynamic_cast<NonLeafNode *>(node);
if (n) {
Node * kid = n->getNode(0);
// clear is needed as the data items are not removed
m_root->clear();
delete m_root;
m_root = kid;
m_root->setParent(0);
//debugFlake << " new root" << m_root;
} else {
qFatal("KoRTree::condenseTree cast to NonLeafNode failed");
}
}
}
//debugFlake << "KoRTree::condenseTree end reinsert.size()" << reinsert.size();
}
#ifdef CALLIGRA_RTREE_DEBUG
template <typename T>
QColor KoRTree<T>::Node::levelColor[] = {
QColor(Qt::green),
QColor(Qt::red),
QColor(Qt::cyan),
QColor(Qt::magenta),
QColor(Qt::yellow),
};
template <class T>
int KoRTree<T>::Node::nodeIdCnt = 0;
#endif
template <typename T>
KoRTree<T>::Node::Node(int capacity, int level, Node * parent)
: m_parent(parent)
, m_childBoundingBox(capacity)
, m_counter(0)
#ifdef CALLIGRA_RTREE_DEBUG
, m_nodeId(nodeIdCnt++)
#endif
, m_level(level)
{
}
template <typename T>
void KoRTree<T>::Node::remove(int index)
{
for (int i = index + 1; i < m_counter; ++i) {
m_childBoundingBox[i-1] = m_childBoundingBox[i];
}
--m_counter;
updateBoundingBox();
}
template <typename T>
void KoRTree<T>::Node::updateBoundingBox()
{
m_boundingBox = QRectF();
for (int i = 0; i < m_counter; ++i) {
m_boundingBox = m_boundingBox.united(m_childBoundingBox[i]);
}
}
template <typename T>
void KoRTree<T>::Node::clear()
{
m_counter = 0;
m_boundingBox = QRectF();
}
#ifdef CALLIGRA_RTREE_DEBUG
template <typename T>
void KoRTree<T>::Node::paintRect(QPainter & p, int level) const
{
QColor c(Qt::black);
if (level < levelColorSize) {
c = levelColor[level];
}
QPen pen(c, 0);
p.setPen(pen);
QRectF bbdraw(this->m_boundingBox);
bbdraw.adjust(level * 2, level * 2, -level * 2, -level * 2);
p.drawRect(bbdraw);
}
#endif
template <typename T>
KoRTree<T>::NonLeafNode::NonLeafNode(int capacity, int level, Node * parent)
: Node(capacity, level, parent)
, m_childs(capacity)
{
//debugFlake << "NonLeafNode::NonLeafNode()" << this;
}
template <typename T>
KoRTree<T>::NonLeafNode::~NonLeafNode()
{
//debugFlake << "NonLeafNode::~NonLeafNode()" << this;
for (int i = 0; i < this->m_counter; ++i) {
delete m_childs[i];
}
}
template <typename T>
void KoRTree<T>::NonLeafNode::insert(const QRectF& bb, Node * data)
{
m_childs[this->m_counter] = data;
data->setPlace(this->m_counter);
data->setParent(this);
this->m_childBoundingBox[this->m_counter] = bb;
this->m_boundingBox = this->m_boundingBox.united(bb);
//debugFlake << "NonLeafNode::insert" << this->nodeId() << data->nodeId();
++this->m_counter;
}
template <typename T>
void KoRTree<T>::NonLeafNode::remove(int index)
{
for (int i = index + 1; i < this->m_counter; ++i) {
m_childs[i-1] = m_childs[i];
m_childs[i-1]->setPlace(i - 1);
}
Node::remove(index);
}
template <typename T>
void KoRTree<T>::NonLeafNode::move(Node * node, int index)
{
//debugFlake << "NonLeafNode::move" << this << node << index << node->nodeId() << "->" << this->nodeId();
NonLeafNode * n = dynamic_cast<NonLeafNode *>(node);
if (n) {
QRectF bb = n->childBoundingBox(index);
insert(bb, n->getNode(index));
}
}
template <typename T>
typename KoRTree<T>::LeafNode * KoRTree<T>::NonLeafNode::chooseLeaf(const QRectF& bb)
{
return getLeastEnlargement(bb)->chooseLeaf(bb);
}
template <typename T>
typename KoRTree<T>::NonLeafNode * KoRTree<T>::NonLeafNode::chooseNode(const QRectF& bb, int level)
{
if (this->m_level > level) {
return getLeastEnlargement(bb)->chooseNode(bb, level);
} else {
return this;
}
}
template <typename T>
void KoRTree<T>::NonLeafNode::intersects(const QRectF& rect, QMap<int, T> & result) const
{
for (int i = 0; i < this->m_counter; ++i) {
if (this->m_childBoundingBox[i].intersects(rect)) {
m_childs[i]->intersects(rect, result);
}
}
}
template <typename T>
void KoRTree<T>::NonLeafNode::contains(const QPointF & point, QMap<int, T> & result) const
{
for (int i = 0; i < this->m_counter; ++i) {
if (this->m_childBoundingBox[i].contains(point)) {
m_childs[i]->contains(point, result);
}
}
}
template <typename T>
void KoRTree<T>::NonLeafNode::contained(const QRectF& rect, QMap<int, T> & result) const
{
for (int i = 0; i < this->m_counter; ++i) {
if (this->m_childBoundingBox[i].intersects(rect)) {
m_childs[i]->contained(rect, result);
}
}
}
template <typename T>
void KoRTree<T>::NonLeafNode::keys(QList<QRectF> & result) const
{
for (int i = 0; i < this->m_counter; ++i) {
m_childs[i]->keys(result);
}
}
template <typename T>
void KoRTree<T>::NonLeafNode::values(QMap<int, T> & result) const
{
for (int i = 0; i < this->m_counter; ++i) {
m_childs[i]->values(result);
}
}
template <typename T>
typename KoRTree<T>::Node * KoRTree<T>::NonLeafNode::getNode(int index) const
{
return m_childs[index];
}
template <typename T>
typename KoRTree<T>::Node * KoRTree<T>::NonLeafNode::getLeastEnlargement(const QRectF& bb) const
{
//debugFlake << "NonLeafNode::getLeastEnlargement";
QVarLengthArray<qreal> area(this->m_counter);
for (int i = 0; i < this->m_counter; ++i) {
QSizeF big(this->m_childBoundingBox[i].united(bb).size());
area[i] = big.width() * big.height() - this->m_childBoundingBox[i].width() * this->m_childBoundingBox[i].height();
}
int minIndex = 0;
qreal minArea = area[minIndex];
//debugFlake << " min" << minIndex << minArea;
for (int i = 1; i < this->m_counter; ++i) {
if (area[i] < minArea) {
minIndex = i;
minArea = area[i];
//debugFlake << " min" << minIndex << minArea;
}
}
return m_childs[minIndex];
}
#ifdef CALLIGRA_RTREE_DEBUG
template <typename T>
void KoRTree<T>::NonLeafNode::debug(QString line) const
{
for (int i = 0; i < this->m_counter; ++i) {
qDebug("%s %d %d", qPrintable(line), this->nodeId(), i);
m_childs[i]->debug(line + " ");
}
}
template <typename T>
void KoRTree<T>::NonLeafNode::paint(QPainter & p, int level) const
{
this->paintRect(p, level);
for (int i = 0; i < this->m_counter; ++i) {
m_childs[i]->paint(p, level + 1);
}
}
#endif
template <class T>
int KoRTree<T>::LeafNode::dataIdCounter = 0;
template <typename T>
KoRTree<T>::LeafNode::LeafNode(int capacity, int level, Node * parent)
: Node(capacity, level, parent)
, m_data(capacity)
, m_dataIds(capacity)
{
//debugFlake << "LeafNode::LeafNode" << this;
}
template <typename T>
KoRTree<T>::LeafNode::~LeafNode()
{
//debugFlake << "LeafNode::~LeafNode" << this;
}
template <typename T>
void KoRTree<T>::LeafNode::insert(const QRectF& bb, const T& data, int id)
{
m_data[this->m_counter] = data;
m_dataIds[this->m_counter] = id;
this->m_childBoundingBox[this->m_counter] = bb;
this->m_boundingBox = this->m_boundingBox.united(bb);
++this->m_counter;
}
template <typename T>
void KoRTree<T>::LeafNode::remove(int index)
{
for (int i = index + 1; i < this->m_counter; ++i) {
m_data[i-1] = m_data[i];
m_dataIds[i-1] = m_dataIds[i];
}
Node::remove(index);
}
template <typename T>
void KoRTree<T>::LeafNode::remove(const T& data)
{
int old_counter = this->m_counter;
for (int i = 0; i < this->m_counter; ++i) {
if (m_data[i] == data) {
//debugFlake << "LeafNode::remove id" << i;
remove(i);
break;
}
}
if (old_counter == this->m_counter) {
qWarning() << "LeafNode::remove( const T&data) data not found";
}
}
template <typename T>
void KoRTree<T>::LeafNode::move(Node * node, int index)
{
LeafNode * n = dynamic_cast<LeafNode*>(node);
if (n) {
//debugFlake << "LeafNode::move" << this << node << index
// << node->nodeId() << "->" << this->nodeId() << n->childBoundingBox( index );
QRectF bb = n->childBoundingBox(index);
insert(bb, n->getData(index), n->getDataId(index));
}
}
template <typename T>
typename KoRTree<T>::LeafNode * KoRTree<T>::LeafNode::chooseLeaf(const QRectF& bb)
{
Q_UNUSED(bb);
return this;
}
template <typename T>
typename KoRTree<T>::NonLeafNode * KoRTree<T>::LeafNode::chooseNode(const QRectF& bb, int level)
{
Q_UNUSED(bb);
Q_UNUSED(level);
qFatal("LeafNode::chooseNode called. This should not happen!");
return 0;
}
template <typename T>
void KoRTree<T>::LeafNode::intersects(const QRectF& rect, QMap<int, T> & result) const
{
for (int i = 0; i < this->m_counter; ++i) {
if (this->m_childBoundingBox[i].intersects(rect)) {
result.insert(m_dataIds[i], m_data[i]);
}
}
}
template <typename T>
void KoRTree<T>::LeafNode::contains(const QPointF & point, QMap<int, T> & result) const
{
for (int i = 0; i < this->m_counter; ++i) {
if (this->m_childBoundingBox[i].contains(point)) {
result.insert(m_dataIds[i], m_data[i]);
}
}
}
template <typename T>
void KoRTree<T>::LeafNode::contained(const QRectF& rect, QMap<int, T> & result) const
{
for (int i = 0; i < this->m_counter; ++i) {
if (rect.contains(this->m_childBoundingBox[i])) {
result.insert(m_dataIds[i], m_data[i]);
}
}
}
template <typename T>
void KoRTree<T>::LeafNode::keys(QList<QRectF> & result) const
{
for (int i = 0; i < this->m_counter; ++i) {
result.push_back(this->m_childBoundingBox[i]);
}
}
template <typename T>
void KoRTree<T>::LeafNode::values(QMap<int, T> & result) const
{
for (int i = 0; i < this->m_counter; ++i) {
result.insert(m_dataIds[i], m_data[i]);
}
}
template <typename T>
const T& KoRTree<T>::LeafNode::getData(int index) const
{
return m_data[ index ];
}
template <typename T>
int KoRTree<T>::LeafNode::getDataId(int index) const
{
return m_dataIds[ index ];
}
#ifdef CALLIGRA_RTREE_DEBUG
template <typename T>
void KoRTree<T>::LeafNode::debug(QString line) const
{
for (int i = 0; i < this->m_counter; ++i) {
qDebug("%s %d %d %p", qPrintable(line), this->nodeId(), i, &(m_data[i]));
debugFlake << this->m_childBoundingBox[i].toRect();
}
}
template <typename T>
void KoRTree<T>::LeafNode::paint(QPainter & p, int level) const
{
if (this->m_counter) {
this->paintRect(p, level);
}
}
#endif
#endif /* KORTREE_H */
diff --git a/libs/flake/KoSelection.cpp b/libs/flake/KoSelection.cpp
index 81c2910809..022ff27176 100644
--- a/libs/flake/KoSelection.cpp
+++ b/libs/flake/KoSelection.cpp
@@ -1,260 +1,266 @@
/* This file is part of the KDE project
Copyright (C) 2006 Boudewijn Rempt <boud@valdyas.org>
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2006-2007,2009 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 "KoSelection.h"
#include "KoSelection_p.h"
#include "KoShapeContainer.h"
#include "KoShapeGroup.h"
#include "KoPointerEvent.h"
#include "KoShapePaintingContext.h"
#include "kis_algebra_2d.h"
#include "krita_container_utils.h"
#include <QPainter>
#include "kis_debug.h"
KoSelection::KoSelection(QObject *parent)
: QObject(parent)
, KoShape()
, d(new Private)
{
connect(&d->selectionChangedCompressor, SIGNAL(timeout()), SIGNAL(selectionChanged()));
}
KoSelection::KoSelection(const KoSelection &rhs)
: QObject()
, KoShape(rhs)
, d(rhs.d)
{
}
KoSelection::~KoSelection()
{
}
-void KoSelection::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext)
+void KoSelection::paint(QPainter &painter, KoShapePaintingContext &paintcontext) const
{
Q_UNUSED(painter);
- Q_UNUSED(converter);
Q_UNUSED(paintcontext);
}
void KoSelection::setSize(const QSizeF &size)
{
Q_UNUSED(size);
qWarning() << "WARNING: KoSelection::setSize() should never be used!";
}
QSizeF KoSelection::size() const
{
return outlineRect().size();
}
QRectF KoSelection::outlineRect() const
{
- QPolygonF globalPolygon;
+ const QTransform invertedTransform = transformation().inverted();
+ QRectF boundingRect;
+
Q_FOREACH (KoShape *shape, selectedVisibleShapes()) {
- globalPolygon = globalPolygon.united(
- shape->absoluteTransformation(0).map(QPolygonF(shape->outlineRect())));
+ if (!shape->outlineRect().isValid()) continue;
+
+ // it is cheaper to invert-transform each outline, than
+ // to group 300+ rotated rectangles into a polygon
+ boundingRect |=
+ invertedTransform.map(
+ shape->absoluteTransformation().map(
+ QPolygonF(shape->outlineRect()))).boundingRect();
}
- const QPolygonF localPolygon = transformation().inverted().map(globalPolygon);
- return localPolygon.boundingRect();
+ return boundingRect;
}
QRectF KoSelection::boundingRect() const
{
return KoShape::boundingRect(selectedVisibleShapes());
}
void KoSelection::select(KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(shape != this);
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
if (!shape->isSelectable() || !shape->isVisible()) {
return;
}
// check recursively
if (isSelected(shape)) {
return;
}
// find the topmost parent to select
while (KoShapeGroup *parentGroup = dynamic_cast<KoShapeGroup*>(shape->parent())) {
shape = parentGroup;
}
d->selectedShapes << shape;
shape->addShapeChangeListener(this);
if (d->selectedShapes.size() == 1) {
- setTransformation(shape->absoluteTransformation(0));
+ setTransformation(shape->absoluteTransformation());
} else {
setTransformation(QTransform());
}
d->selectionChangedCompressor.start();
}
void KoSelection::deselect(KoShape *shape)
{
if (!d->selectedShapes.contains(shape))
return;
d->selectedShapes.removeAll(shape);
shape->removeShapeChangeListener(this);
if (d->selectedShapes.size() == 1) {
- setTransformation(d->selectedShapes.first()->absoluteTransformation(0));
+ setTransformation(d->selectedShapes.first()->absoluteTransformation());
}
d->selectionChangedCompressor.start();
}
void KoSelection::deselectAll()
{
if (d->selectedShapes.isEmpty())
return;
Q_FOREACH (KoShape *shape, d->selectedShapes) {
shape->removeShapeChangeListener(this);
}
// reset the transformation matrix of the selection
setTransformation(QTransform());
d->selectedShapes.clear();
d->selectionChangedCompressor.start();
}
int KoSelection::count() const
{
return d->selectedShapes.size();
}
bool KoSelection::hitTest(const QPointF &position) const
{
Q_FOREACH (KoShape *shape, d->selectedShapes) {
if (shape->isVisible()) continue;
if (shape->hitTest(position)) return true;
}
return false;
}
const QList<KoShape*> KoSelection::selectedShapes() const
{
return d->selectedShapes;
}
const QList<KoShape *> KoSelection::selectedVisibleShapes() const
{
QList<KoShape*> shapes = selectedShapes();
KritaUtils::filterContainer (shapes, [](KoShape *shape) {
return shape->isVisible();
});
return shapes;
}
const QList<KoShape *> KoSelection::selectedEditableShapes() const
{
QList<KoShape*> shapes = selectedShapes();
KritaUtils::filterContainer (shapes, [](KoShape *shape) {
return shape->isShapeEditable();
});
return shapes;
}
const QList<KoShape *> KoSelection::selectedEditableShapesAndDelegates() const
{
QList<KoShape*> shapes;
Q_FOREACH (KoShape *shape, selectedShapes()) {
QSet<KoShape *> delegates = shape->toolDelegates();
if (delegates.isEmpty()) {
shapes.append(shape);
} else {
Q_FOREACH (KoShape *delegatedShape, delegates) {
shapes.append(delegatedShape);
}
}
}
return shapes;
}
bool KoSelection::isSelected(const KoShape *shape) const
{
if (shape == this)
return true;
const KoShape *tmpShape = shape;
while (tmpShape && std::find(d->selectedShapes.begin(), d->selectedShapes.end(), tmpShape) == d->selectedShapes.end()) {
tmpShape = tmpShape->parent();
}
return tmpShape;
}
KoShape *KoSelection::firstSelectedShape() const
{
return !d->selectedShapes.isEmpty() ? d->selectedShapes.first() : 0;
}
void KoSelection::setActiveLayer(KoShapeLayer *layer)
{
d->activeLayer = layer;
emit currentLayerChanged(layer);
}
KoShapeLayer* KoSelection::activeLayer() const
{
return d->activeLayer;
}
void KoSelection::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
{
Q_UNUSED(shape);
if (type == KoShape::Deleted) {
deselect(shape);
// HACK ALERT: the caller will also remove the listener, which was
// removed in deselect(), so re-add it here
shape->addShapeChangeListener(this);
}
}
void KoSelection::saveOdf(KoShapeSavingContext &) const
{
}
bool KoSelection::loadOdf(const KoXmlElement &, KoShapeLoadingContext &)
{
return true;
}
diff --git a/libs/flake/KoSelection.h b/libs/flake/KoSelection.h
index b11578304d..a372e331dd 100644
--- a/libs/flake/KoSelection.h
+++ b/libs/flake/KoSelection.h
@@ -1,170 +1,169 @@
/* This file is part of the KDE project
Copyright (C) 2006 Boudewijn Rempt <boud@valdyas.org>
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007,2009 Thomas Zander <zander@kde.org>
Copyright (C) 2006,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.
*/
#ifndef KOSELECTION_H
#define KOSELECTION_H
#include <QObject>
#include "KoShape.h"
#include "KoFlake.h"
#include "kritaflake_export.h"
-class KoViewConverter;
class KoShapeLayer;
class KoSelectionPrivate;
/**
* A selection is a shape that contains a number of references
* to shapes. That means that a selection can be manipulated in
* the same way as a single shape.
*
* Note that a single shape can be selected in one view, and not in
* another, and that in a single view, more than one selection can be
* present. So selections should not be seen as singletons, or as
* something completely transient.
*
* A selection, however, should not be selectable. We need to think
* a little about the interaction here.
*/
class KRITAFLAKE_EXPORT KoSelection : public QObject, public KoShape, public KoShape::ShapeChangeListener
{
Q_OBJECT
public:
KoSelection(QObject *parent = 0);
~KoSelection() override;
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
+ void paint(QPainter &painter, KoShapePaintingContext &paintcontext) const override;
void setSize(const QSizeF &size) override;
QSizeF size() const override;
QRectF outlineRect() const override;
QRectF boundingRect() const override;
/**
* Adds a shape to the selection.
*
* If the shape is a KoShapeGroup all of its child shapes are automatically added
* to the selection.
* If the shape has no parent or is not a KoShapeGroup, only the given shape is
* added to the selection.
* If the given shape is a child of a KoShapeGroup and recursive selection is enabled
* the all parents and their child shapes up to the toplevel KoShapeGroup are added to
* the selection.
*
* @param shape the shape to add to the selection
*/
void select(KoShape *shape);
/**
* Removes a selected shape.
*
* If the shape is a KoShapeGroup all of its child shapes are automatically removed
* from the selection.
* If the shape has no parent or is not a KoShapeGroup, only the given shape is
* removed from the selection.
* If the given shape is a child of a KoShapeGroup and recursive selection is enabled
* the all parents and their child shape up to the toplevel KoShapeGroup are removed
* from the selection.
*
* @param shape the shape to remove from the selection
*/
void deselect(KoShape *shape);
/// clear the selections list
void deselectAll();
/**
* Return the list of selected shapes
* @return the list of selected shapes
*/
const QList<KoShape*> selectedShapes() const;
/**
* Same as selectedShapes() but only for shapes in visible state. Used by
* the algorithms that draw shapes on the image
*/
const QList<KoShape*> selectedVisibleShapes() const;
/**
* Same as selectedShapes() but only for editable shapes. Used by
* the algorithms that modify the image
*/
const QList<KoShape*> selectedEditableShapes() const;
/**
* Same as selectedEditableShapes() but also includes shapes delegates.
* Used for
*/
const QList<KoShape*> selectedEditableShapesAndDelegates() const;
/**
* Return the first selected shape, or 0 if there is nothing selected.
*/
KoShape *firstSelectedShape() const;
/// return true if the shape is selected
bool isSelected(const KoShape *shape) const;
/// return the selection count, i.e. the number of all selected shapes
int count() const;
bool hitTest(const QPointF &position) const override;
/**
* Sets the currently active layer.
* @param layer the new active layer
*/
void setActiveLayer(KoShapeLayer *layer);
/**
* Returns a currently active layer.
*
* @return the currently active layer, or zero if there is none
*/
KoShapeLayer *activeLayer() const;
void notifyShapeChanged(ChangeType type, KoShape *shape) override;
Q_SIGNALS:
/// emitted when the selection is changed
void selectionChanged();
/// emitted when the current layer is changed
void currentLayerChanged(const KoShapeLayer *layer);
private:
void saveOdf(KoShapeSavingContext &) const override;
bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override;
protected:
KoSelection(const KoSelection &rhs);
private:
class Private;
QSharedDataPointer<Private> d;
};
#endif
diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp
index 9a88c382c3..8e8feb652a 100644
--- a/libs/flake/KoShape.cpp
+++ b/libs/flake/KoShape.cpp
@@ -1,2426 +1,2411 @@
/* This file is part of the KDE project
Copyright (C) 2006 C. Boemann Rasmussen <cbo@boemann.dk>
Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
Copyright (C) 2006-2010 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007-2009,2011 Jan Hambrecht <jaham@gmx.net>
CopyRight (C) 2010 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 <limits>
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoShapeContainer.h"
#include "KoShapeLayer.h"
#include "KoShapeContainerModel.h"
#include "KoSelection.h"
#include "KoPointerEvent.h"
#include "KoInsets.h"
#include "KoShapeStrokeModel.h"
#include "KoShapeBackground.h"
#include "KoColorBackground.h"
#include "KoHatchBackground.h"
#include "KoGradientBackground.h"
#include "KoPatternBackground.h"
#include "KoShapeManager.h"
#include "KoShapeUserData.h"
#include "KoShapeApplicationData.h"
#include "KoShapeSavingContext.h"
#include "KoShapeLoadingContext.h"
#include "KoViewConverter.h"
#include "KoShapeStroke.h"
#include "KoShapeShadow.h"
#include "KoClipPath.h"
#include "KoPathShape.h"
#include "KoOdfWorkaround.h"
#include "KoFilterEffectStack.h"
#include <KoSnapData.h>
#include <KoElementReference.h>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoXmlNS.h>
#include <KoGenStyle.h>
#include <KoGenStyles.h>
#include <KoUnit.h>
#include <KoOdfStylesReader.h>
#include <KoOdfGraphicStyles.h>
#include <KoOdfLoadingContext.h>
#include <KoStyleStack.h>
#include <KoBorder.h>
#include <QPainter>
#include <QVariant>
#include <QPainterPath>
#include <QList>
#include <QMap>
#include <QByteArray>
#include <FlakeDebug.h>
#include "kis_assert.h"
#include "KoOdfGradientBackground.h"
#include <KisHandlePainterHelper.h>
// KoShape::Private
-KoShape::Private::Private()
+KoShape::SharedData::SharedData()
: QSharedData()
, size(50, 50)
- , parent(0)
, shadow(0)
, border(0)
, filterEffectStack(0)
, transparency(0.0)
, zIndex(0)
, runThrough(0)
, visible(true)
, printable(true)
, geometryProtected(false)
, keepAspect(false)
, selectable(true)
- , detectCollision(false)
, protectContent(false)
, textRunAroundSide(KoShape::BiggestRunAroundSide)
, textRunAroundDistanceLeft(0.0)
, textRunAroundDistanceTop(0.0)
, textRunAroundDistanceRight(0.0)
, textRunAroundDistanceBottom(0.0)
, textRunAroundThreshold(0.0)
, textRunAroundContour(KoShape::ContourFull)
{
connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint);
connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint);
connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint);
connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint);
connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter);
}
-KoShape::Private::Private(const Private &rhs)
+KoShape::SharedData::SharedData(const SharedData &rhs)
: QSharedData()
, size(rhs.size)
, shapeId(rhs.shapeId)
, name(rhs.name)
, localMatrix(rhs.localMatrix)
, connectors(rhs.connectors)
- , parent(0) // to be initialized later
- , shapeManagers() // to be initialized later
- , toolDelegates() // FIXME: how to initialize them?
, userData(rhs.userData ? rhs.userData->clone() : 0)
, stroke(rhs.stroke)
, fill(rhs.fill)
, inheritBackground(rhs.inheritBackground)
, inheritStroke(rhs.inheritStroke)
- , dependees() // FIXME: how to initialize them?
, shadow(0) // WARNING: not implemented in Krita
, border(0) // WARNING: not implemented in Krita
, clipPath(rhs.clipPath ? rhs.clipPath->clone() : 0)
, clipMask(rhs.clipMask ? rhs.clipMask->clone() : 0)
, additionalAttributes(rhs.additionalAttributes)
, additionalStyleAttributes(rhs.additionalStyleAttributes)
, filterEffectStack(0) // WARNING: not implemented in Krita
, transparency(rhs.transparency)
, hyperLink(rhs.hyperLink)
, zIndex(rhs.zIndex)
, runThrough(rhs.runThrough)
, visible(rhs.visible)
, printable(rhs.visible)
, geometryProtected(rhs.geometryProtected)
, keepAspect(rhs.keepAspect)
, selectable(rhs.selectable)
- , detectCollision(rhs.detectCollision)
, protectContent(rhs.protectContent)
, textRunAroundSide(rhs.textRunAroundSide)
, textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft)
, textRunAroundDistanceTop(rhs.textRunAroundDistanceTop)
, textRunAroundDistanceRight(rhs.textRunAroundDistanceRight)
, textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom)
, textRunAroundThreshold(rhs.textRunAroundThreshold)
, textRunAroundContour(rhs.textRunAroundContour)
{
}
-KoShape::Private::~Private()
+KoShape::SharedData::~SharedData()
{
if (shadow && !shadow->deref())
delete shadow;
if (filterEffectStack && !filterEffectStack->deref())
delete filterEffectStack;
}
void KoShape::shapeChangedPriv(KoShape::ChangeType type)
{
if (d->parent)
d->parent->model()->childChanged(this, type);
this->shapeChanged(type);
Q_FOREACH (KoShape * shape, d->dependees) {
shape->shapeChanged(type, this);
}
Q_FOREACH (KoShape::ShapeChangeListener *listener, d->listeners) {
listener->notifyShapeChangedImpl(type, this);
}
}
void KoShape::addShapeManager(KoShapeManager *manager)
{
d->shapeManagers.insert(manager);
}
void KoShape::removeShapeManager(KoShapeManager *manager)
{
d->shapeManagers.remove(manager);
}
-void KoShape::Private::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const
+void KoShape::SharedData::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const
{
switch(point.alignment) {
case KoConnectionPoint::AlignNone:
point.position = KoFlake::toRelative(point.position, shapeSize);
point.position.rx() = qBound<qreal>(0.0, point.position.x(), 1.0);
point.position.ry() = qBound<qreal>(0.0, point.position.y(), 1.0);
break;
case KoConnectionPoint::AlignRight:
point.position.rx() -= shapeSize.width();
break;
case KoConnectionPoint::AlignLeft:
point.position.ry() = 0.5*shapeSize.height();
break;
case KoConnectionPoint::AlignBottom:
point.position.ry() -= shapeSize.height();
break;
case KoConnectionPoint::AlignTop:
point.position.rx() = 0.5*shapeSize.width();
break;
case KoConnectionPoint::AlignTopLeft:
// nothing to do here
break;
case KoConnectionPoint::AlignTopRight:
point.position.rx() -= shapeSize.width();
break;
case KoConnectionPoint::AlignBottomLeft:
point.position.ry() -= shapeSize.height();
break;
case KoConnectionPoint::AlignBottomRight:
point.position.rx() -= shapeSize.width();
point.position.ry() -= shapeSize.height();
break;
case KoConnectionPoint::AlignCenter:
point.position.rx() -= 0.5 * shapeSize.width();
point.position.ry() -= 0.5 * shapeSize.height();
break;
}
}
-void KoShape::Private::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const
+void KoShape::SharedData::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const
{
switch(point.alignment) {
case KoConnectionPoint::AlignNone:
point.position = KoFlake::toAbsolute(point.position, shapeSize);
break;
case KoConnectionPoint::AlignRight:
point.position.rx() += shapeSize.width();
break;
case KoConnectionPoint::AlignLeft:
point.position.ry() = 0.5*shapeSize.height();
break;
case KoConnectionPoint::AlignBottom:
point.position.ry() += shapeSize.height();
break;
case KoConnectionPoint::AlignTop:
point.position.rx() = 0.5*shapeSize.width();
break;
case KoConnectionPoint::AlignTopLeft:
// nothing to do here
break;
case KoConnectionPoint::AlignTopRight:
point.position.rx() += shapeSize.width();
break;
case KoConnectionPoint::AlignBottomLeft:
point.position.ry() += shapeSize.height();
break;
case KoConnectionPoint::AlignBottomRight:
point.position.rx() += shapeSize.width();
point.position.ry() += shapeSize.height();
break;
case KoConnectionPoint::AlignCenter:
point.position.rx() += 0.5 * shapeSize.width();
point.position.ry() += 0.5 * shapeSize.height();
break;
}
}
// static
-QString KoShape::Private::getStyleProperty(const char *property, KoShapeLoadingContext &context)
+QString KoShape::SharedData::getStyleProperty(const char *property, KoShapeLoadingContext &context)
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
QString value;
if (styleStack.hasProperty(KoXmlNS::draw, property)) {
value = styleStack.property(KoXmlNS::draw, property);
}
return value;
}
// ======== KoShape
const qint16 KoShape::maxZIndex = std::numeric_limits<qint16>::max();
const qint16 KoShape::minZIndex = std::numeric_limits<qint16>::min();
KoShape::KoShape()
- : d(new Private)
+ : d(new Private()),
+ s(new SharedData)
{
notifyChanged();
}
KoShape::KoShape(const KoShape &rhs)
- : d(rhs.d)
+ : d(new Private()),
+ s(rhs.s)
{
}
KoShape::~KoShape()
{
shapeChangedPriv(Deleted);
d->listeners.clear();
/**
* The shape must have already been detached from all the parents and
* shape managers. Otherwise we migh accidentally request some RTTI
* information, which is not available anymore (we are in d-tor).
*
* TL;DR: fix the code that caused this destruction without unparenting
* instead of trying to remove these assert!
*/
KIS_SAFE_ASSERT_RECOVER (!d->parent) {
d->parent->removeShape(this);
}
KIS_SAFE_ASSERT_RECOVER (d->shapeManagers.isEmpty()) {
Q_FOREACH (KoShapeManager *manager, d->shapeManagers) {
manager->shapeInterface()->notifyShapeDestructed(this);
}
d->shapeManagers.clear();
}
}
KoShape *KoShape::cloneShape() const
{
KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "not implemented!");
qWarning() << shapeId() << "cannot be cloned";
return 0;
}
-void KoShape::paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext)
+void KoShape::paintStroke(QPainter &painter, KoShapePaintingContext &paintcontext) const
{
Q_UNUSED(paintcontext);
if (stroke()) {
- stroke()->paint(this, painter, converter);
+ stroke()->paint(this, painter);
}
}
void KoShape::scale(qreal sx, qreal sy)
{
QPointF pos = position();
QTransform scaleMatrix;
scaleMatrix.translate(pos.x(), pos.y());
scaleMatrix.scale(sx, sy);
scaleMatrix.translate(-pos.x(), -pos.y());
- d->localMatrix = d->localMatrix * scaleMatrix;
+ s->localMatrix = s->localMatrix * scaleMatrix;
notifyChanged();
shapeChangedPriv(ScaleChanged);
}
void KoShape::rotate(qreal angle)
{
- QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height()));
+ QPointF center = s->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height()));
QTransform rotateMatrix;
rotateMatrix.translate(center.x(), center.y());
rotateMatrix.rotate(angle);
rotateMatrix.translate(-center.x(), -center.y());
- d->localMatrix = d->localMatrix * rotateMatrix;
+ s->localMatrix = s->localMatrix * rotateMatrix;
notifyChanged();
shapeChangedPriv(RotationChanged);
}
void KoShape::shear(qreal sx, qreal sy)
{
QPointF pos = position();
QTransform shearMatrix;
shearMatrix.translate(pos.x(), pos.y());
shearMatrix.shear(sx, sy);
shearMatrix.translate(-pos.x(), -pos.y());
- d->localMatrix = d->localMatrix * shearMatrix;
+ s->localMatrix = s->localMatrix * shearMatrix;
notifyChanged();
shapeChangedPriv(ShearChanged);
}
void KoShape::setSize(const QSizeF &newSize)
{
QSizeF oldSize(size());
// always set size, as d->size and size() may vary
setSizeImpl(newSize);
if (oldSize == newSize)
return;
notifyChanged();
shapeChangedPriv(SizeChanged);
}
void KoShape::setSizeImpl(const QSizeF &size) const
{
- d->size = size;
+ s->size = size;
}
void KoShape::setPosition(const QPointF &newPosition)
{
QPointF currentPos = position();
if (newPosition == currentPos)
return;
QTransform translateMatrix;
translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y());
- d->localMatrix = d->localMatrix * translateMatrix;
+ s->localMatrix = s->localMatrix * translateMatrix;
notifyChanged();
shapeChangedPriv(PositionChanged);
}
bool KoShape::hitTest(const QPointF &position) const
{
if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position))
return false;
- QPointF point = absoluteTransformation(0).inverted().map(position);
+ QPointF point = absoluteTransformation().inverted().map(position);
QRectF bb = outlineRect();
- if (d->stroke) {
+ if (s->stroke) {
KoInsets insets;
- d->stroke->strokeInsets(this, insets);
+ s->stroke->strokeInsets(this, insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
if (bb.contains(point))
return true;
// if there is no shadow we can as well just leave
- if (! d->shadow)
+ if (! s->shadow)
return false;
// the shadow has an offset to the shape, so we simply
// check if the position minus the shadow offset hits the shape
- point = absoluteTransformation(0).inverted().map(position - d->shadow->offset());
+ point = absoluteTransformation().inverted().map(position - s->shadow->offset());
return bb.contains(point);
}
QRectF KoShape::boundingRect() const
{
- QTransform transform = absoluteTransformation(0);
+ QTransform transform = absoluteTransformation();
QRectF bb = outlineRect();
- if (d->stroke) {
+ if (s->stroke) {
KoInsets insets;
- d->stroke->strokeInsets(this, insets);
+ s->stroke->strokeInsets(this, insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
bb = transform.mapRect(bb);
- if (d->shadow) {
+ if (s->shadow) {
KoInsets insets;
- d->shadow->insets(insets);
+ s->shadow->insets(insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
- if (d->filterEffectStack) {
- QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect());
+ if (s->filterEffectStack) {
+ QRectF clipRect = s->filterEffectStack->clipRectForBoundingRect(outlineRect());
bb |= transform.mapRect(clipRect);
}
return bb;
}
QRectF KoShape::boundingRect(const QList<KoShape *> &shapes)
{
QRectF boundingRect;
Q_FOREACH (KoShape *shape, shapes) {
boundingRect |= shape->boundingRect();
}
return boundingRect;
}
-QRectF KoShape::absoluteOutlineRect(KoViewConverter *converter) const
+QRectF KoShape::absoluteOutlineRect() const
{
- return absoluteTransformation(converter).map(outline()).boundingRect();
+ return absoluteTransformation().map(outline()).boundingRect();
}
-QRectF KoShape::absoluteOutlineRect(const QList<KoShape *> &shapes, KoViewConverter *converter)
+QRectF KoShape::absoluteOutlineRect(const QList<KoShape *> &shapes)
{
QRectF absoluteOutlineRect;
Q_FOREACH (KoShape *shape, shapes) {
- absoluteOutlineRect |= shape->absoluteOutlineRect(converter);
+ absoluteOutlineRect |= shape->absoluteOutlineRect();
}
return absoluteOutlineRect;
}
-QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const
+QTransform KoShape::absoluteTransformation() const
{
QTransform matrix;
// apply parents matrix to inherit any transformations done there.
KoShapeContainer * container = d->parent;
if (container) {
if (container->inheritsTransform(this)) {
- // We do need to pass the converter here, otherwise the parent's
- // translation is not inherited.
- matrix = container->absoluteTransformation(converter);
+ matrix = container->absoluteTransformation();
} else {
QSizeF containerSize = container->size();
QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height());
- if (converter)
- containerPos = converter->documentToView(containerPos);
matrix.translate(containerPos.x(), containerPos.y());
}
}
- if (converter) {
- QPointF pos = d->localMatrix.map(QPointF());
- QPointF trans = converter->documentToView(pos) - pos;
- matrix.translate(trans.x(), trans.y());
- }
-
- return d->localMatrix * matrix;
+ return s->localMatrix * matrix;
}
void KoShape::applyAbsoluteTransformation(const QTransform &matrix)
{
- QTransform globalMatrix = absoluteTransformation(0);
+ QTransform globalMatrix = absoluteTransformation();
// the transformation is relative to the global coordinate system
// but we want to change the local matrix, so convert the matrix
// to be relative to the local coordinate system
QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted();
applyTransformation(transformMatrix);
}
void KoShape::applyTransformation(const QTransform &matrix)
{
- d->localMatrix = matrix * d->localMatrix;
+ s->localMatrix = matrix * s->localMatrix;
notifyChanged();
shapeChangedPriv(GenericMatrixChange);
}
void KoShape::setTransformation(const QTransform &matrix)
{
- d->localMatrix = matrix;
+ s->localMatrix = matrix;
notifyChanged();
shapeChangedPriv(GenericMatrixChange);
}
QTransform KoShape::transformation() const
{
- return d->localMatrix;
+ return s->localMatrix;
}
KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy()
{
return ChildZDefault;
}
bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2)
{
/**
* WARNING: Our definition of zIndex is not yet compatible with SVG2's
* definition. In SVG stacking context of groups with the same
* zIndex are **merged**, while in Krita the contents of groups
* is never merged. One group will always below than the other.
* Therefore, when zIndex of two groups inside the same parent
* coincide, the resulting painting order in Krita is
* **UNDEFINED**.
*
* To avoid this trouble we use KoShapeReorderCommand::mergeInShape()
* inside KoShapeCreateCommand.
*/
/**
* The algorithm below doesn't correctly handle the case when the two pointers actually
* point to the same shape. So just check it in advance to guarantee strict weak ordering
* relation requirement
*/
if (s1 == s2) return false;
// First sort according to runThrough which is sort of a master level
KoShape *parentShapeS1 = s1->parent();
KoShape *parentShapeS2 = s2->parent();
int runThrough1 = s1->runThrough();
int runThrough2 = s2->runThrough();
while (parentShapeS1) {
if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) {
runThrough1 = parentShapeS1->runThrough();
} else {
runThrough1 = runThrough1 + parentShapeS1->runThrough();
}
parentShapeS1 = parentShapeS1->parent();
}
while (parentShapeS2) {
if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) {
runThrough2 = parentShapeS2->runThrough();
} else {
runThrough2 = runThrough2 + parentShapeS2->runThrough();
}
parentShapeS2 = parentShapeS2->parent();
}
if (runThrough1 > runThrough2) {
return false;
}
if (runThrough1 < runThrough2) {
return true;
}
// If on the same runThrough level then the zIndex is all that matters.
//
// We basically walk up through the parents until we find a common base parent
// To do that we need two loops where the inner loop walks up through the parents
// of s2 every time we step up one parent level on s1
//
// We don't update the index value until after we have seen that it's not a common base
// That way we ensure that two children of a common base are sorted according to their respective
// z value
bool foundCommonParent = false;
int index1 = s1->zIndex();
int index2 = s2->zIndex();
parentShapeS1 = s1;
parentShapeS2 = s2;
while (parentShapeS1 && !foundCommonParent) {
parentShapeS2 = s2;
index2 = parentShapeS2->zIndex();
while (parentShapeS2) {
if (parentShapeS2 == parentShapeS1) {
foundCommonParent = true;
break;
}
if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) {
index2 = parentShapeS2->zIndex();
}
parentShapeS2 = parentShapeS2->parent();
}
if (!foundCommonParent) {
if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) {
index1 = parentShapeS1->zIndex();
}
parentShapeS1 = parentShapeS1->parent();
}
}
// If the one shape is a parent/child of the other then sort so.
if (s1 == parentShapeS2) {
return true;
}
if (s2 == parentShapeS1) {
return false;
}
// If we went that far then the z-Index is used for sorting.
return index1 < index2;
}
void KoShape::setParent(KoShapeContainer *parent)
{
if (d->parent == parent) {
return;
}
KoShapeContainer *oldParent = d->parent;
d->parent = 0; // avoids recursive removing
if (oldParent) {
oldParent->shapeInterface()->removeShape(this);
}
KIS_SAFE_ASSERT_RECOVER_NOOP(parent != this);
if (parent && parent != this) {
d->parent = parent;
parent->shapeInterface()->addShape(this);
}
notifyChanged();
shapeChangedPriv(ParentChanged);
}
bool KoShape::inheritsTransformFromAny(const QList<KoShape *> ancestorsInQuestion) const
{
bool result = false;
KoShape *shape = const_cast<KoShape*>(this);
while (shape) {
KoShapeContainer *parent = shape->parent();
if (parent && !parent->inheritsTransform(shape)) {
break;
}
if (ancestorsInQuestion.contains(shape)) {
result = true;
break;
}
shape = parent;
}
return result;
}
bool KoShape::hasCommonParent(const KoShape *shape) const
{
const KoShape *thisShape = this;
while (thisShape) {
const KoShape *otherShape = shape;
while (otherShape) {
if (thisShape == otherShape) {
return true;
}
otherShape = otherShape->parent();
}
thisShape = thisShape->parent();
}
return false;
}
qint16 KoShape::zIndex() const
{
- return d->zIndex;
+ return s->zIndex;
}
void KoShape::update() const
{
if (!d->shapeManagers.empty()) {
QRectF rect(boundingRect());
Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
manager->update(rect, this, true);
}
}
}
void KoShape::updateAbsolute(const QRectF &rect) const
{
if (rect.isEmpty() && !rect.isNull()) {
return;
}
if (!d->shapeManagers.empty() && isVisible()) {
Q_FOREACH (KoShapeManager *manager, d->shapeManagers) {
manager->update(rect);
}
}
}
QPainterPath KoShape::outline() const
{
QPainterPath path;
path.addRect(outlineRect());
return path;
}
QRectF KoShape::outlineRect() const
{
const QSizeF s = size();
return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)),
qMax(s.height(), qreal(0.0001))));
}
QPainterPath KoShape::shadowOutline() const
{
if (background()) {
return outline();
}
return QPainterPath();
}
QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const
{
const QRectF rc = outlineRect();
QPointF point = rc.topLeft();
bool valid = false;
QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid);
if (valid) {
point = anchoredPoint;
}
- return absoluteTransformation(0).map(point);
+ return absoluteTransformation().map(point);
}
void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor)
{
QPointF currentAbsPosition = absolutePosition(anchor);
QPointF translate = newPosition - currentAbsPosition;
QTransform translateMatrix;
translateMatrix.translate(translate.x(), translate.y());
applyAbsoluteTransformation(translateMatrix);
notifyChanged();
shapeChangedPriv(PositionChanged);
}
void KoShape::copySettings(const KoShape *shape)
{
- d->size = shape->size();
- d->connectors.clear();
+ s->size = shape->size();
+ s->connectors.clear();
Q_FOREACH (const KoConnectionPoint &point, shape->connectionPoints())
addConnectionPoint(point);
- d->zIndex = shape->zIndex();
- d->visible = shape->isVisible(false);
+ s->zIndex = shape->zIndex();
+ s->visible = shape->isVisible(false);
// Ensure printable is true by default
- if (!d->visible)
- d->printable = true;
+ if (!s->visible)
+ s->printable = true;
else
- d->printable = shape->isPrintable();
+ s->printable = shape->isPrintable();
- d->geometryProtected = shape->isGeometryProtected();
- d->protectContent = shape->isContentProtected();
- d->selectable = shape->isSelectable();
- d->keepAspect = shape->keepAspectRatio();
- d->localMatrix = shape->d->localMatrix;
+ s->geometryProtected = shape->isGeometryProtected();
+ s->protectContent = shape->isContentProtected();
+ s->selectable = shape->isSelectable();
+ s->keepAspect = shape->keepAspectRatio();
+ s->localMatrix = shape->s->localMatrix;
}
void KoShape::notifyChanged()
{
Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
manager->notifyShapeChanged(this);
}
}
void KoShape::setUserData(KoShapeUserData *userData)
{
- d->userData.reset(userData);
+ s->userData.reset(userData);
}
KoShapeUserData *KoShape::userData() const
{
- return d->userData.data();
+ return s->userData.data();
}
bool KoShape::hasTransparency() const
{
QSharedPointer<KoShapeBackground> bg = background();
- return !bg || bg->hasTransparency() || d->transparency > 0.0;
+ return !bg || bg->hasTransparency() || s->transparency > 0.0;
}
void KoShape::setTransparency(qreal transparency)
{
- d->transparency = qBound<qreal>(0.0, transparency, 1.0);
+ s->transparency = qBound<qreal>(0.0, transparency, 1.0);
shapeChangedPriv(TransparencyChanged);
notifyChanged();
}
qreal KoShape::transparency(bool recursive) const
{
if (!recursive || !parent()) {
- return d->transparency;
+ return s->transparency;
} else {
const qreal parentOpacity = 1.0-parent()->transparency(recursive);
- const qreal childOpacity = 1.0-d->transparency;
+ const qreal childOpacity = 1.0-s->transparency;
return 1.0-(parentOpacity*childOpacity);
}
}
KoInsets KoShape::strokeInsets() const
{
KoInsets answer;
- if (d->stroke)
- d->stroke->strokeInsets(this, answer);
+ if (s->stroke)
+ s->stroke->strokeInsets(this, answer);
return answer;
}
qreal KoShape::rotation() const
{
// try to extract the rotation angle out of the local matrix
// if it is a pure rotation matrix
// check if the matrix has shearing mixed in
- if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10)
+ if (fabs(fabs(s->localMatrix.m12()) - fabs(s->localMatrix.m21())) > 1e-10)
return std::numeric_limits<qreal>::quiet_NaN();
// check if the matrix has scaling mixed in
- if (fabs(d->localMatrix.m11() - d->localMatrix.m22()) > 1e-10)
+ if (fabs(s->localMatrix.m11() - s->localMatrix.m22()) > 1e-10)
return std::numeric_limits<qreal>::quiet_NaN();
// calculate the angle from the matrix elements
- qreal angle = atan2(-d->localMatrix.m21(), d->localMatrix.m11()) * 180.0 / M_PI;
+ qreal angle = atan2(-s->localMatrix.m21(), s->localMatrix.m11()) * 180.0 / M_PI;
if (angle < 0.0)
angle += 360.0;
return angle;
}
QSizeF KoShape::size() const
{
- return d->size;
+ return s->size;
}
QPointF KoShape::position() const
{
QPointF center = outlineRect().center();
- return d->localMatrix.map(center) - center;
+ return s->localMatrix.map(center) - center;
}
int KoShape::addConnectionPoint(const KoConnectionPoint &point)
{
// get next glue point id
int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint;
- if (d->connectors.size())
- nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1);
+ if (s->connectors.size())
+ nextConnectionPointId = qMax(nextConnectionPointId, (--s->connectors.end()).key()+1);
KoConnectionPoint p = point;
- d->convertFromShapeCoordinates(p, size());
- d->connectors[nextConnectionPointId] = p;
+ s->convertFromShapeCoordinates(p, size());
+ s->connectors[nextConnectionPointId] = p;
return nextConnectionPointId;
}
bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point)
{
if (connectionPointId < 0)
return false;
const bool insertPoint = !hasConnectionPoint(connectionPointId);
switch(connectionPointId) {
case KoConnectionPoint::TopConnectionPoint:
case KoConnectionPoint::RightConnectionPoint:
case KoConnectionPoint::BottomConnectionPoint:
case KoConnectionPoint::LeftConnectionPoint:
{
KoConnectionPoint::PointId id = static_cast<KoConnectionPoint::PointId>(connectionPointId);
- d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id);
+ s->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id);
break;
}
default:
{
KoConnectionPoint p = point;
- d->convertFromShapeCoordinates(p, size());
- d->connectors[connectionPointId] = p;
+ s->convertFromShapeCoordinates(p, size());
+ s->connectors[connectionPointId] = p;
break;
}
}
if(!insertPoint)
shapeChangedPriv(ConnectionPointChanged);
return true;
}
bool KoShape::hasConnectionPoint(int connectionPointId) const
{
- return d->connectors.contains(connectionPointId);
+ return s->connectors.contains(connectionPointId);
}
KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const
{
- KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint());
+ KoConnectionPoint p = s->connectors.value(connectionPointId, KoConnectionPoint());
// convert glue point to shape coordinates
- d->convertToShapeCoordinates(p, size());
+ s->convertToShapeCoordinates(p, size());
return p;
}
KoConnectionPoints KoShape::connectionPoints() const
{
- QSizeF s = size();
- KoConnectionPoints points = d->connectors;
+ QSizeF size = this->size();
+ KoConnectionPoints points = s->connectors;
KoConnectionPoints::iterator point = points.begin();
KoConnectionPoints::iterator lastPoint = points.end();
// convert glue points to shape coordinates
for(; point != lastPoint; ++point) {
- d->convertToShapeCoordinates(point.value(), s);
+ s->convertToShapeCoordinates(point.value(), size);
}
return points;
}
void KoShape::removeConnectionPoint(int connectionPointId)
{
- d->connectors.remove(connectionPointId);
+ s->connectors.remove(connectionPointId);
shapeChangedPriv(ConnectionPointChanged);
}
void KoShape::clearConnectionPoints()
{
- d->connectors.clear();
+ s->connectors.clear();
}
KoShape::TextRunAroundSide KoShape::textRunAroundSide() const
{
- return d->textRunAroundSide;
+ return s->textRunAroundSide;
}
void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought)
{
if (side == RunThrough) {
if (runThrought == Background) {
setRunThrough(-1);
} else {
setRunThrough(1);
}
} else {
setRunThrough(0);
}
- if ( d->textRunAroundSide == side) {
+ if ( s->textRunAroundSide == side) {
return;
}
- d->textRunAroundSide = side;
+ s->textRunAroundSide = side;
notifyChanged();
shapeChangedPriv(TextRunAroundChanged);
}
qreal KoShape::textRunAroundDistanceTop() const
{
- return d->textRunAroundDistanceTop;
+ return s->textRunAroundDistanceTop;
}
void KoShape::setTextRunAroundDistanceTop(qreal distance)
{
- d->textRunAroundDistanceTop = distance;
+ s->textRunAroundDistanceTop = distance;
}
qreal KoShape::textRunAroundDistanceLeft() const
{
- return d->textRunAroundDistanceLeft;
+ return s->textRunAroundDistanceLeft;
}
void KoShape::setTextRunAroundDistanceLeft(qreal distance)
{
- d->textRunAroundDistanceLeft = distance;
+ s->textRunAroundDistanceLeft = distance;
}
qreal KoShape::textRunAroundDistanceRight() const
{
- return d->textRunAroundDistanceRight;
+ return s->textRunAroundDistanceRight;
}
void KoShape::setTextRunAroundDistanceRight(qreal distance)
{
- d->textRunAroundDistanceRight = distance;
+ s->textRunAroundDistanceRight = distance;
}
qreal KoShape::textRunAroundDistanceBottom() const
{
- return d->textRunAroundDistanceBottom;
+ return s->textRunAroundDistanceBottom;
}
void KoShape::setTextRunAroundDistanceBottom(qreal distance)
{
- d->textRunAroundDistanceBottom = distance;
+ s->textRunAroundDistanceBottom = distance;
}
qreal KoShape::textRunAroundThreshold() const
{
- return d->textRunAroundThreshold;
+ return s->textRunAroundThreshold;
}
void KoShape::setTextRunAroundThreshold(qreal threshold)
{
- d->textRunAroundThreshold = threshold;
+ s->textRunAroundThreshold = threshold;
}
KoShape::TextRunAroundContour KoShape::textRunAroundContour() const
{
- return d->textRunAroundContour;
+ return s->textRunAroundContour;
}
void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour)
{
- d->textRunAroundContour = contour;
+ s->textRunAroundContour = contour;
}
void KoShape::setBackground(QSharedPointer<KoShapeBackground> fill)
{
- d->inheritBackground = false;
- d->fill = fill;
+ s->inheritBackground = false;
+ s->fill = fill;
shapeChangedPriv(BackgroundChanged);
notifyChanged();
}
QSharedPointer<KoShapeBackground> KoShape::background() const
{
QSharedPointer<KoShapeBackground> bg;
- if (!d->inheritBackground) {
- bg = d->fill;
+ if (!s->inheritBackground) {
+ bg = s->fill;
} else if (parent()) {
bg = parent()->background();
}
return bg;
}
void KoShape::setInheritBackground(bool value)
{
- d->inheritBackground = value;
- if (d->inheritBackground) {
- d->fill.clear();
+ s->inheritBackground = value;
+ if (s->inheritBackground) {
+ s->fill.clear();
}
}
bool KoShape::inheritBackground() const
{
- return d->inheritBackground;
+ return s->inheritBackground;
}
void KoShape::setZIndex(qint16 zIndex)
{
- if (d->zIndex == zIndex)
+ if (s->zIndex == zIndex)
return;
- d->zIndex = zIndex;
+ s->zIndex = zIndex;
notifyChanged();
}
-int KoShape::runThrough()
+int KoShape::runThrough() const
{
- return d->runThrough;
+ return s->runThrough;
}
void KoShape::setRunThrough(short int runThrough)
{
- d->runThrough = runThrough;
+ s->runThrough = runThrough;
}
void KoShape::setVisible(bool on)
{
int _on = (on ? 1 : 0);
- if (d->visible == _on) return;
- d->visible = _on;
+ if (s->visible == _on) return;
+ s->visible = _on;
}
bool KoShape::isVisible(bool recursive) const
{
if (!recursive)
- return d->visible;
+ return s->visible;
- if (!d->visible)
+ if (!s->visible)
return false;
KoShapeContainer * parentShape = parent();
if (parentShape) {
return parentShape->isVisible(true);
}
return true;
}
void KoShape::setPrintable(bool on)
{
- d->printable = on;
+ s->printable = on;
}
bool KoShape::isPrintable() const
{
- if (d->visible)
- return d->printable;
+ if (s->visible)
+ return s->printable;
else
return false;
}
void KoShape::setSelectable(bool selectable)
{
- d->selectable = selectable;
+ s->selectable = selectable;
}
bool KoShape::isSelectable() const
{
- return d->selectable;
+ return s->selectable;
}
void KoShape::setGeometryProtected(bool on)
{
- d->geometryProtected = on;
+ s->geometryProtected = on;
}
bool KoShape::isGeometryProtected() const
{
- return d->geometryProtected;
+ return s->geometryProtected;
}
void KoShape::setContentProtected(bool protect)
{
- d->protectContent = protect;
+ s->protectContent = protect;
}
bool KoShape::isContentProtected() const
{
- return d->protectContent;
+ return s->protectContent;
}
KoShapeContainer *KoShape::parent() const
{
return d->parent;
}
void KoShape::setKeepAspectRatio(bool keepAspect)
{
- d->keepAspect = keepAspect;
+ s->keepAspect = keepAspect;
shapeChangedPriv(KeepAspectRatioChange);
notifyChanged();
}
bool KoShape::keepAspectRatio() const
{
- return d->keepAspect;
+ return s->keepAspect;
}
QString KoShape::shapeId() const
{
- return d->shapeId;
+ return s->shapeId;
}
void KoShape::setShapeId(const QString &id)
{
- d->shapeId = id;
-}
-
-void KoShape::setCollisionDetection(bool detect)
-{
- d->detectCollision = detect;
-}
-
-bool KoShape::collisionDetection()
-{
- return d->detectCollision;
+ s->shapeId = id;
}
KoShapeStrokeModelSP KoShape::stroke() const
{
KoShapeStrokeModelSP stroke;
- if (!d->inheritStroke) {
- stroke = d->stroke;
+ if (!s->inheritStroke) {
+ stroke = s->stroke;
} else if (parent()) {
stroke = parent()->stroke();
}
return stroke;
}
void KoShape::setStroke(KoShapeStrokeModelSP stroke)
{
- d->inheritStroke = false;
- d->stroke = stroke;
+ s->inheritStroke = false;
+ s->stroke = stroke;
shapeChangedPriv(StrokeChanged);
notifyChanged();
}
void KoShape::setInheritStroke(bool value)
{
- d->inheritStroke = value;
- if (d->inheritStroke) {
- d->stroke.clear();
+ s->inheritStroke = value;
+ if (s->inheritStroke) {
+ s->stroke.clear();
}
}
bool KoShape::inheritStroke() const
{
- return d->inheritStroke;
+ return s->inheritStroke;
}
void KoShape::setShadow(KoShapeShadow *shadow)
{
- if (d->shadow)
- d->shadow->deref();
- d->shadow = shadow;
- if (d->shadow) {
- d->shadow->ref();
+ if (s->shadow)
+ s->shadow->deref();
+ s->shadow = shadow;
+ if (s->shadow) {
+ s->shadow->ref();
// TODO update changed area
}
shapeChangedPriv(ShadowChanged);
notifyChanged();
}
KoShapeShadow *KoShape::shadow() const
{
- return d->shadow;
+ return s->shadow;
}
void KoShape::setBorder(KoBorder *border)
{
- if (d->border) {
+ if (s->border) {
// The shape owns the border.
- delete d->border;
+ delete s->border;
}
- d->border = border;
+ s->border = border;
shapeChangedPriv(BorderChanged);
notifyChanged();
}
KoBorder *KoShape::border() const
{
- return d->border;
+ return s->border;
}
void KoShape::setClipPath(KoClipPath *clipPath)
{
- d->clipPath.reset(clipPath);
+ s->clipPath.reset(clipPath);
shapeChangedPriv(ClipPathChanged);
notifyChanged();
}
KoClipPath * KoShape::clipPath() const
{
- return d->clipPath.data();
+ return s->clipPath.data();
}
void KoShape::setClipMask(KoClipMask *clipMask)
{
- d->clipMask.reset(clipMask);
+ s->clipMask.reset(clipMask);
+ shapeChangedPriv(ClipMaskChanged);
+ notifyChanged();
}
KoClipMask* KoShape::clipMask() const
{
- return d->clipMask.data();
+ return s->clipMask.data();
}
QTransform KoShape::transform() const
{
- return d->localMatrix;
+ return s->localMatrix;
}
QString KoShape::name() const
{
- return d->name;
+ return s->name;
}
void KoShape::setName(const QString &name)
{
- d->name = name;
+ s->name = name;
}
-void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const
+void KoShape::waitUntilReady(bool asynchronous) const
{
- Q_UNUSED(converter);
Q_UNUSED(asynchronous);
}
bool KoShape::isShapeEditable(bool recursive) const
{
- if (!d->visible || d->geometryProtected)
+ if (!s->visible || s->geometryProtected)
return false;
if (recursive && d->parent) {
return d->parent->isShapeEditable(true);
}
return true;
}
-// painting
-void KoShape::paintBorder(QPainter &painter, const KoViewConverter &converter)
-{
- Q_UNUSED(converter);
- KoBorder *bd = border();
- if (!bd) {
- return;
- }
-
- QRectF borderRect = QRectF(QPointF(0, 0), size());
- // Paint the border.
- bd->paint(painter, borderRect, KoBorder::PaintInsideLine);
-}
-
-
// loading & saving methods
QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const
{
// and fill the style
KoShapeStrokeModelSP sm = stroke();
if (sm) {
sm->fillStyle(style, context);
}
else {
style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
}
- KoShapeShadow *s = shadow();
- if (s)
- s->fillStyle(style, context);
+ KoShapeShadow *shadow = this->shadow();
+ if (shadow)
+ shadow->fillStyle(style, context);
QSharedPointer<KoShapeBackground> bg = background();
if (bg) {
bg->fillStyle(style, context);
}
else {
style.addProperty("draw:fill", "none", KoGenStyle::GraphicType);
}
KoBorder *b = border();
if (b) {
b->saveOdf(style);
}
if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) {
style.setAutoStyleInStylesDotXml(true);
}
QString value;
if (isGeometryProtected()) {
value = "position size";
}
if (isContentProtected()) {
if (! value.isEmpty())
value += ' ';
value += "content";
}
if (!value.isEmpty()) {
style.addProperty("style:protect", value, KoGenStyle::GraphicType);
}
- QMap<QByteArray, QString>::const_iterator it(d->additionalStyleAttributes.constBegin());
- for (; it != d->additionalStyleAttributes.constEnd(); ++it) {
+ QMap<QByteArray, QString>::const_iterator it(s->additionalStyleAttributes.constBegin());
+ for (; it != s->additionalStyleAttributes.constEnd(); ++it) {
style.addProperty(it.key(), it.value());
}
if (parent() && parent()->isClipped(this)) {
/*
* In Calligra clipping is done using a parent shape which can be rotated, sheared etc
* and even non-square. So the ODF interoperability version we write here is really
* just a very simple version of that...
*/
qreal top = -position().y();
qreal left = -position().x();
qreal right = parent()->size().width() - size().width() - left;
qreal bottom = parent()->size().height() - size().height() - top;
style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)")
.arg(top, 10, 'f').arg(right, 10, 'f')
.arg(bottom, 10, 'f').arg(left, 10, 'f'), KoGenStyle::GraphicType);
}
QString wrap;
switch (textRunAroundSide()) {
case BiggestRunAroundSide:
wrap = "biggest";
break;
case LeftRunAroundSide:
wrap = "left";
break;
case RightRunAroundSide:
wrap = "right";
break;
case EnoughRunAroundSide:
wrap = "dynamic";
break;
case BothRunAroundSide:
wrap = "parallel";
break;
case NoRunAround:
wrap = "none";
break;
case RunThrough:
wrap = "run-through";
break;
}
style.addProperty("style:wrap", wrap, KoGenStyle::GraphicType);
switch (textRunAroundContour()) {
case ContourBox:
style.addProperty("style:wrap-contour", "false", KoGenStyle::GraphicType);
break;
case ContourFull:
style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType);
style.addProperty("style:wrap-contour-mode", "full", KoGenStyle::GraphicType);
break;
case ContourOutside:
style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType);
style.addProperty("style:wrap-contour-mode", "outside", KoGenStyle::GraphicType);
break;
}
style.addPropertyPt("style:wrap-dynamic-threshold", textRunAroundThreshold(), KoGenStyle::GraphicType);
if ((textRunAroundDistanceLeft() == textRunAroundDistanceRight())
&& (textRunAroundDistanceTop() == textRunAroundDistanceBottom())
&& (textRunAroundDistanceLeft() == textRunAroundDistanceTop())) {
style.addPropertyPt("fo:margin", textRunAroundDistanceLeft(), KoGenStyle::GraphicType);
} else {
style.addPropertyPt("fo:margin-left", textRunAroundDistanceLeft(), KoGenStyle::GraphicType);
style.addPropertyPt("fo:margin-top", textRunAroundDistanceTop(), KoGenStyle::GraphicType);
style.addPropertyPt("fo:margin-right", textRunAroundDistanceRight(), KoGenStyle::GraphicType);
style.addPropertyPt("fo:margin-bottom", textRunAroundDistanceBottom(), KoGenStyle::GraphicType);
}
return context.mainStyles().insert(style, context.isSet(KoShapeSavingContext::PresentationShape) ? "pr" : "gr");
}
void KoShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context)
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.setTypeProperties("graphic");
- d->fill.clear();
- d->stroke.clear();
+ s->fill.clear();
+ s->stroke.clear();
- if (d->shadow && !d->shadow->deref()) {
- delete d->shadow;
- d->shadow = 0;
+ if (s->shadow && !s->shadow->deref()) {
+ delete s->shadow;
+ s->shadow = 0;
}
setBackground(loadOdfFill(context));
setStroke(loadOdfStroke(element, context));
- setShadow(d->loadOdfShadow(context));
- setBorder(d->loadOdfBorder(context));
+ setShadow(s->loadOdfShadow(context));
+ setBorder(s->loadOdfBorder(context));
QString protect(styleStack.property(KoXmlNS::style, "protect"));
setGeometryProtected(protect.contains("position") || protect.contains("size"));
setContentProtected(protect.contains("content"));
QString margin = styleStack.property(KoXmlNS::fo, "margin");
if (!margin.isEmpty()) {
setTextRunAroundDistanceLeft(KoUnit::parseValue(margin));
setTextRunAroundDistanceTop(KoUnit::parseValue(margin));
setTextRunAroundDistanceRight(KoUnit::parseValue(margin));
setTextRunAroundDistanceBottom(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-left");
if (!margin.isEmpty()) {
setTextRunAroundDistanceLeft(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-top");
if (!margin.isEmpty()) {
setTextRunAroundDistanceTop(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-right");
if (!margin.isEmpty()) {
setTextRunAroundDistanceRight(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-bottom");
if (!margin.isEmpty()) {
setTextRunAroundDistanceBottom(KoUnit::parseValue(margin));
}
QString wrap;
if (styleStack.hasProperty(KoXmlNS::style, "wrap")) {
wrap = styleStack.property(KoXmlNS::style, "wrap");
} else {
// no value given in the file, but guess biggest
wrap = "biggest";
}
if (wrap == "none") {
setTextRunAroundSide(KoShape::NoRunAround);
} else if (wrap == "run-through") {
QString runTrought = styleStack.property(KoXmlNS::style, "run-through", "background");
if (runTrought == "background") {
setTextRunAroundSide(KoShape::RunThrough, KoShape::Background);
} else {
setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground);
}
} else {
if (wrap == "biggest")
setTextRunAroundSide(KoShape::BiggestRunAroundSide);
else if (wrap == "left")
setTextRunAroundSide(KoShape::LeftRunAroundSide);
else if (wrap == "right")
setTextRunAroundSide(KoShape::RightRunAroundSide);
else if (wrap == "dynamic")
setTextRunAroundSide(KoShape::EnoughRunAroundSide);
else if (wrap == "parallel")
setTextRunAroundSide(KoShape::BothRunAroundSide);
}
if (styleStack.hasProperty(KoXmlNS::style, "wrap-dynamic-threshold")) {
QString wrapThreshold = styleStack.property(KoXmlNS::style, "wrap-dynamic-threshold");
if (!wrapThreshold.isEmpty()) {
setTextRunAroundThreshold(KoUnit::parseValue(wrapThreshold));
}
}
if (styleStack.property(KoXmlNS::style, "wrap-contour", "false") == "true") {
if (styleStack.property(KoXmlNS::style, "wrap-contour-mode", "full") == "full") {
setTextRunAroundContour(KoShape::ContourFull);
} else {
setTextRunAroundContour(KoShape::ContourOutside);
}
} else {
setTextRunAroundContour(KoShape::ContourBox);
}
}
bool KoShape::loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes)
{
if (attributes & OdfPosition) {
QPointF pos(position());
if (element.hasAttributeNS(KoXmlNS::svg, "x"))
pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString())));
if (element.hasAttributeNS(KoXmlNS::svg, "y"))
pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString())));
setPosition(pos);
}
if (attributes & OdfSize) {
QSizeF s(size());
if (element.hasAttributeNS(KoXmlNS::svg, "width"))
s.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString())));
if (element.hasAttributeNS(KoXmlNS::svg, "height"))
s.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString())));
setSize(s);
}
if (attributes & OdfLayer) {
if (element.hasAttributeNS(KoXmlNS::draw, "layer")) {
KoShapeLayer *layer = context.layer(element.attributeNS(KoXmlNS::draw, "layer"));
if (layer) {
setParent(layer);
}
}
}
if (attributes & OdfId) {
KoElementReference ref;
ref.loadOdf(element);
if (ref.isValid()) {
context.addShapeId(this, ref.toString());
}
}
if (attributes & OdfZIndex) {
if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) {
setZIndex(element.attributeNS(KoXmlNS::draw, "z-index").toInt());
} else {
setZIndex(context.zIndex());
}
}
if (attributes & OdfName) {
if (element.hasAttributeNS(KoXmlNS::draw, "name")) {
setName(element.attributeNS(KoXmlNS::draw, "name"));
}
}
if (attributes & OdfStyle) {
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.save();
if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) {
context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic");
}
if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) {
context.odfLoadingContext().fillStyleStack(element, KoXmlNS::presentation, "style-name", "presentation");
}
loadStyle(element, context);
styleStack.restore();
}
if (attributes & OdfTransformation) {
QString transform = element.attributeNS(KoXmlNS::draw, "transform", QString());
if (! transform.isEmpty())
applyAbsoluteTransformation(parseOdfTransform(transform));
}
if (attributes & OdfAdditionalAttributes) {
QSet<KoShapeLoadingContext::AdditionalAttributeData> additionalAttributeData = KoShapeLoadingContext::additionalAttributeData();
Q_FOREACH (const KoShapeLoadingContext::AdditionalAttributeData &attributeData, additionalAttributeData) {
if (element.hasAttributeNS(attributeData.ns, attributeData.tag)) {
QString value = element.attributeNS(attributeData.ns, attributeData.tag);
//debugFlake << "load additional attribute" << attributeData.tag << value;
setAdditionalAttribute(attributeData.name, value);
}
}
}
if (attributes & OdfCommonChildElements) {
// load glue points (connection points)
loadOdfGluePoints(element, context);
}
return true;
}
QSharedPointer<KoShapeBackground> KoShape::loadOdfFill(KoShapeLoadingContext &context) const
{
- QString fill = KoShape::Private::getStyleProperty("fill", context);
+ QString fill = KoShape::SharedData::getStyleProperty("fill", context);
QSharedPointer<KoShapeBackground> bg;
if (fill == "solid") {
bg = QSharedPointer<KoShapeBackground>(new KoColorBackground());
}
else if (fill == "hatch") {
bg = QSharedPointer<KoShapeBackground>(new KoHatchBackground());
}
else if (fill == "gradient") {
- QString styleName = KoShape::Private::getStyleProperty("fill-gradient-name", context);
+ QString styleName = KoShape::SharedData::getStyleProperty("fill-gradient-name", context);
KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient")[styleName];
QString style;
if (e) {
style = e->attributeNS(KoXmlNS::draw, "style", QString());
}
if ((style == "rectangular") || (style == "square")) {
bg = QSharedPointer<KoShapeBackground>(new KoOdfGradientBackground());
} else {
QGradient *gradient = new QLinearGradient();
gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
bg = QSharedPointer<KoShapeBackground>(new KoGradientBackground(gradient));
}
} else if (fill == "bitmap") {
bg = QSharedPointer<KoShapeBackground>(new KoPatternBackground(context.imageCollection()));
#ifndef NWORKAROUND_ODF_BUGS
} else if (fill.isEmpty()) {
bg = QSharedPointer<KoShapeBackground>(KoOdfWorkaround::fixBackgroundColor(this, context));
return bg;
#endif
} else {
return QSharedPointer<KoShapeBackground>(0);
}
if (!bg->loadStyle(context.odfLoadingContext(), size())) {
return QSharedPointer<KoShapeBackground>(0);
}
return bg;
}
KoShapeStrokeModelSP KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader();
- QString stroke = KoShape::Private::getStyleProperty("stroke", context);
+ QString stroke = KoShape::SharedData::getStyleProperty("stroke", context);
if (stroke == "solid" || stroke == "dash") {
QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader);
QSharedPointer<KoShapeStroke> stroke(new KoShapeStroke());
if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) {
QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient");
QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, size());
stroke->setLineBrush(brush);
} else {
stroke->setColor(pen.color());
}
#ifndef NWORKAROUND_ODF_BUGS
KoOdfWorkaround::fixPenWidth(pen, context);
#endif
stroke->setLineWidth(pen.widthF());
stroke->setJoinStyle(pen.joinStyle());
stroke->setLineStyle(pen.style(), pen.dashPattern());
stroke->setCapStyle(pen.capStyle());
return stroke;
#ifndef NWORKAROUND_ODF_BUGS
} else if (stroke.isEmpty()) {
QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader);
if (KoOdfWorkaround::fixMissingStroke(pen, element, context, this)) {
QSharedPointer<KoShapeStroke> stroke(new KoShapeStroke());
#ifndef NWORKAROUND_ODF_BUGS
KoOdfWorkaround::fixPenWidth(pen, context);
#endif
stroke->setLineWidth(pen.widthF());
stroke->setJoinStyle(pen.joinStyle());
stroke->setLineStyle(pen.style(), pen.dashPattern());
stroke->setCapStyle(pen.capStyle());
stroke->setColor(pen.color());
return stroke;
}
#endif
}
return KoShapeStrokeModelSP();
}
-KoShapeShadow *KoShape::Private::loadOdfShadow(KoShapeLoadingContext &context) const
+KoShapeShadow *KoShape::SharedData::loadOdfShadow(KoShapeLoadingContext &context) const
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
- QString shadowStyle = KoShape::Private::getStyleProperty("shadow", context);
+ QString shadowStyle = KoShape::SharedData::getStyleProperty("shadow", context);
if (shadowStyle == "visible" || shadowStyle == "hidden") {
KoShapeShadow *shadow = new KoShapeShadow();
QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color"));
qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x"));
qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y"));
shadow->setOffset(QPointF(offsetX, offsetY));
qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius"));
shadow->setBlur(blur);
QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity");
if (! opacity.isEmpty() && opacity.right(1) == "%")
shadowColor.setAlphaF(opacity.left(opacity.length() - 1).toFloat() / 100.0);
shadow->setColor(shadowColor);
shadow->setVisible(shadowStyle == "visible");
return shadow;
}
return 0;
}
-KoBorder *KoShape::Private::loadOdfBorder(KoShapeLoadingContext &context) const
+KoBorder *KoShape::SharedData::loadOdfBorder(KoShapeLoadingContext &context) const
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
KoBorder *border = new KoBorder();
if (border->loadOdf(styleStack)) {
return border;
}
delete border;
return 0;
}
void KoShape::loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context)
{
KoXmlElement child;
bool hasCenterGluePoint = false;
forEachElement(child, element) {
if (child.namespaceURI() != KoXmlNS::draw)
continue;
if (child.localName() != "glue-point")
continue;
// NOTE: this uses draw:id, but apparently while ODF 1.2 has deprecated
// all use of draw:id for xml:id, it didn't specify that here, so it
// doesn't support xml:id (and so, maybe, shouldn't use KoElementReference.
const QString id = child.attributeNS(KoXmlNS::draw, "id", QString());
const int index = id.toInt();
// connection point in center should be default but odf doesn't support,
// in new shape, first custom point is in center, it's okay to replace that point
// with point from xml now, we'll add it back later
if(id.isEmpty() || index < KoConnectionPoint::FirstCustomConnectionPoint ||
- (index != KoConnectionPoint::FirstCustomConnectionPoint && d->connectors.contains(index))) {
+ (index != KoConnectionPoint::FirstCustomConnectionPoint && s->connectors.contains(index))) {
warnFlake << "glue-point with no or invalid id";
continue;
}
QString xStr = child.attributeNS(KoXmlNS::svg, "x", QString()).simplified();
QString yStr = child.attributeNS(KoXmlNS::svg, "y", QString()).simplified();
if(xStr.isEmpty() || yStr.isEmpty()) {
warnFlake << "glue-point with invald position";
continue;
}
KoConnectionPoint connector;
const QString align = child.attributeNS(KoXmlNS::draw, "align", QString());
if (align.isEmpty()) {
#ifndef NWORKAROUND_ODF_BUGS
KoOdfWorkaround::fixGluePointPosition(xStr, context);
KoOdfWorkaround::fixGluePointPosition(yStr, context);
#endif
if(!xStr.endsWith('%') || !yStr.endsWith('%')) {
warnFlake << "glue-point with invald position";
continue;
}
// x and y are relative to drawing object center
connector.position.setX(xStr.remove('%').toDouble()/100.0);
connector.position.setY(yStr.remove('%').toDouble()/100.0);
// convert position to be relative to top-left corner
connector.position += QPointF(0.5, 0.5);
connector.position.rx() = qBound<qreal>(0.0, connector.position.x(), 1.0);
connector.position.ry() = qBound<qreal>(0.0, connector.position.y(), 1.0);
} else {
// absolute distances to the edge specified by align
connector.position.setX(KoUnit::parseValue(xStr));
connector.position.setY(KoUnit::parseValue(yStr));
if (align == "top-left") {
connector.alignment = KoConnectionPoint::AlignTopLeft;
} else if (align == "top") {
connector.alignment = KoConnectionPoint::AlignTop;
} else if (align == "top-right") {
connector.alignment = KoConnectionPoint::AlignTopRight;
} else if (align == "left") {
connector.alignment = KoConnectionPoint::AlignLeft;
} else if (align == "center") {
connector.alignment = KoConnectionPoint::AlignCenter;
} else if (align == "right") {
connector.alignment = KoConnectionPoint::AlignRight;
} else if (align == "bottom-left") {
connector.alignment = KoConnectionPoint::AlignBottomLeft;
} else if (align == "bottom") {
connector.alignment = KoConnectionPoint::AlignBottom;
} else if (align == "bottom-right") {
connector.alignment = KoConnectionPoint::AlignBottomRight;
}
debugFlake << "using alignment" << align;
}
const QString escape = child.attributeNS(KoXmlNS::draw, "escape-direction", QString());
if (!escape.isEmpty()) {
if (escape == "horizontal") {
connector.escapeDirection = KoConnectionPoint::HorizontalDirections;
} else if (escape == "vertical") {
connector.escapeDirection = KoConnectionPoint::VerticalDirections;
} else if (escape == "left") {
connector.escapeDirection = KoConnectionPoint::LeftDirection;
} else if (escape == "right") {
connector.escapeDirection = KoConnectionPoint::RightDirection;
} else if (escape == "up") {
connector.escapeDirection = KoConnectionPoint::UpDirection;
} else if (escape == "down") {
connector.escapeDirection = KoConnectionPoint::DownDirection;
}
debugFlake << "using escape direction" << escape;
}
- d->connectors[index] = connector;
+ s->connectors[index] = connector;
debugFlake << "loaded glue-point" << index << "at position" << connector.position;
- if (d->connectors[index].position == QPointF(0.5, 0.5)) {
+ if (s->connectors[index].position == QPointF(0.5, 0.5)) {
hasCenterGluePoint = true;
debugFlake << "center glue-point found at id " << index;
}
}
if (!hasCenterGluePoint) {
- d->connectors[d->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5),
+ s->connectors[s->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5),
KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter);
}
- debugFlake << "shape has now" << d->connectors.count() << "glue-points";
+ debugFlake << "shape has now" << s->connectors.count() << "glue-points";
}
void KoShape::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor)
{
KoXmlElement child;
forEachElement(child, element) {
if (child.namespaceURI() != KoXmlNS::draw)
continue;
if (child.localName() != "contour-polygon")
continue;
debugFlake << "shape loads contour-polygon";
KoPathShape *ps = new KoPathShape();
ps->loadContourOdf(child, context, scaleFactor);
ps->setTransformation(transformation());
KoClipPath *clipPath = new KoClipPath({ps}, KoFlake::UserSpaceOnUse);
- d->clipPath.reset(clipPath);
+ s->clipPath.reset(clipPath);
}
}
QTransform KoShape::parseOdfTransform(const QString &transform)
{
QTransform matrix;
// Split string for handling 1 transform statement at a time
QStringList subtransforms = transform.split(')', QString::SkipEmptyParts);
QStringList::ConstIterator it = subtransforms.constBegin();
QStringList::ConstIterator end = subtransforms.constEnd();
for (; it != end; ++it) {
QStringList subtransform = (*it).split('(', QString::SkipEmptyParts);
subtransform[0] = subtransform[0].trimmed().toLower();
subtransform[1] = subtransform[1].simplified();
QRegExp reg("[,( ]");
QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts);
if (subtransform[0].startsWith(';') || subtransform[0].startsWith(','))
subtransform[0] = subtransform[0].right(subtransform[0].length() - 1);
QString cmd = subtransform[0].toLower();
if (cmd == "rotate") {
QTransform rotMatrix;
if (params.count() == 3) {
qreal x = KoUnit::parseValue(params[1]);
qreal y = KoUnit::parseValue(params[2]);
rotMatrix.translate(x, y);
// oo2 rotates by radians
rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI);
rotMatrix.translate(-x, -y);
} else {
// oo2 rotates by radians
rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI);
}
matrix = matrix * rotMatrix;
} else if (cmd == "translate") {
QTransform moveMatrix;
if (params.count() == 2) {
qreal x = KoUnit::parseValue(params[0]);
qreal y = KoUnit::parseValue(params[1]);
moveMatrix.translate(x, y);
} else // Spec : if only one param given, assume 2nd param to be 0
moveMatrix.translate(KoUnit::parseValue(params[0]) , 0);
matrix = matrix * moveMatrix;
} else if (cmd == "scale") {
QTransform scaleMatrix;
if (params.count() == 2)
scaleMatrix.scale(params[0].toDouble(), params[1].toDouble());
else // Spec : if only one param given, assume uniform scaling
scaleMatrix.scale(params[0].toDouble(), params[0].toDouble());
matrix = matrix * scaleMatrix;
} else if (cmd == "skewx") {
QPointF p = absolutePosition(KoFlake::TopLeft);
QTransform shearMatrix;
shearMatrix.translate(p.x(), p.y());
shearMatrix.shear(tan(-params[0].toDouble()), 0.0F);
shearMatrix.translate(-p.x(), -p.y());
matrix = matrix * shearMatrix;
} else if (cmd == "skewy") {
QPointF p = absolutePosition(KoFlake::TopLeft);
QTransform shearMatrix;
shearMatrix.translate(p.x(), p.y());
shearMatrix.shear(0.0F, tan(-params[0].toDouble()));
shearMatrix.translate(-p.x(), -p.y());
matrix = matrix * shearMatrix;
} else if (cmd == "matrix") {
QTransform m;
if (params.count() >= 6) {
m.setMatrix(params[0].toDouble(), params[1].toDouble(), 0,
params[2].toDouble(), params[3].toDouble(), 0,
KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1);
}
matrix = matrix * m;
}
}
return matrix;
}
void KoShape::saveOdfAttributes(KoShapeSavingContext &context, int attributes) const
{
if (attributes & OdfStyle) {
KoGenStyle style;
// all items that should be written to 'draw:frame' and any other 'draw:' object that inherits this shape
if (context.isSet(KoShapeSavingContext::PresentationShape)) {
style = KoGenStyle(KoGenStyle::PresentationAutoStyle, "presentation");
context.xmlWriter().addAttribute("presentation:style-name", saveStyle(style, context));
} else {
style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic");
context.xmlWriter().addAttribute("draw:style-name", saveStyle(style, context));
}
}
if (attributes & OdfId) {
if (context.isSet(KoShapeSavingContext::DrawId)) {
KoElementReference ref = context.xmlid(this, "shape", KoElementReference::Counter);
ref.saveOdf(&context.xmlWriter(), KoElementReference::DrawId);
}
}
if (attributes & OdfName) {
if (! name().isEmpty())
context.xmlWriter().addAttribute("draw:name", name());
}
if (attributes & OdfLayer) {
KoShape *parent = d->parent;
while (parent) {
if (dynamic_cast<KoShapeLayer*>(parent)) {
context.xmlWriter().addAttribute("draw:layer", parent->name());
break;
}
parent = parent->parent();
}
}
if (attributes & OdfZIndex && context.isSet(KoShapeSavingContext::ZIndex)) {
context.xmlWriter().addAttribute("draw:z-index", zIndex());
}
if (attributes & OdfSize) {
QSizeF s(size());
if (parent() && parent()->isClipped(this)) { // being clipped shrinks our visible size
// clipping in ODF is done using a combination of visual size and content cliprect.
// A picture of 10cm x 10cm displayed in a box of 2cm x 4cm will be scaled (out
// of proportion in this case). If we then add a fo:clip like;
// fo:clip="rect(2cm, 3cm, 4cm, 5cm)" (top, right, bottom, left)
// our original 10x10 is clipped to 2cm x 4cm and *then* fitted in that box.
// TODO do this properly by subtracting rects
s = parent()->size();
}
context.xmlWriter().addAttribute("svg:width", s.width());
context.xmlWriter().addAttribute("svg:height", s.height());
}
// The position is implicitly stored in the transformation matrix
// if the transformation is saved as well
if ((attributes & OdfPosition) && !(attributes & OdfTransformation)) {
const QPointF p(position() * context.shapeOffset(this));
context.xmlWriter().addAttribute("svg:x", p.x());
context.xmlWriter().addAttribute("svg:y", p.y());
}
if (attributes & OdfTransformation) {
- QTransform matrix = absoluteTransformation(0) * context.shapeOffset(this);
+ QTransform matrix = absoluteTransformation() * context.shapeOffset(this);
if (! matrix.isIdentity()) {
if (qAbs(matrix.m11() - 1) < 1E-5 // 1
&& qAbs(matrix.m12()) < 1E-5 // 0
&& qAbs(matrix.m21()) < 1E-5 // 0
&& qAbs(matrix.m22() - 1) < 1E-5) { // 1
context.xmlWriter().addAttribute("svg:x", matrix.dx());
context.xmlWriter().addAttribute("svg:y", matrix.dy());
} else {
QString m = QString("matrix(%1 %2 %3 %4 %5pt %6pt)")
.arg(matrix.m11(), 0, 'f', 11)
.arg(matrix.m12(), 0, 'f', 11)
.arg(matrix.m21(), 0, 'f', 11)
.arg(matrix.m22(), 0, 'f', 11)
.arg(matrix.dx(), 0, 'f', 11)
.arg(matrix.dy(), 0, 'f', 11);
context.xmlWriter().addAttribute("draw:transform", m);
}
}
}
if (attributes & OdfViewbox) {
const QSizeF s(size());
QString viewBox = QString("0 0 %1 %2").arg(qRound(s.width())).arg(qRound(s.height()));
context.xmlWriter().addAttribute("svg:viewBox", viewBox);
}
if (attributes & OdfAdditionalAttributes) {
- QMap<QString, QString>::const_iterator it(d->additionalAttributes.constBegin());
- for (; it != d->additionalAttributes.constEnd(); ++it) {
+ QMap<QString, QString>::const_iterator it(s->additionalAttributes.constBegin());
+ for (; it != s->additionalAttributes.constEnd(); ++it) {
context.xmlWriter().addAttribute(it.key().toUtf8(), it.value());
}
}
}
void KoShape::saveOdfCommonChildElements(KoShapeSavingContext &context) const
{
// save glue points see ODF 9.2.19 Glue Points
- if(d->connectors.count()) {
- KoConnectionPoints::const_iterator cp = d->connectors.constBegin();
- KoConnectionPoints::const_iterator lastCp = d->connectors.constEnd();
+ if(s->connectors.count()) {
+ KoConnectionPoints::const_iterator cp = s->connectors.constBegin();
+ KoConnectionPoints::const_iterator lastCp = s->connectors.constEnd();
for(; cp != lastCp; ++cp) {
// do not save default glue points
if(cp.key() < 4)
continue;
context.xmlWriter().startElement("draw:glue-point");
context.xmlWriter().addAttribute("draw:id", QString("%1").arg(cp.key()));
if (cp.value().alignment == KoConnectionPoint::AlignNone) {
// convert to percent from center
const qreal x = cp.value().position.x() * 100.0 - 50.0;
const qreal y = cp.value().position.y() * 100.0 - 50.0;
context.xmlWriter().addAttribute("svg:x", QString("%1%").arg(x));
context.xmlWriter().addAttribute("svg:y", QString("%1%").arg(y));
} else {
context.xmlWriter().addAttribute("svg:x", cp.value().position.x());
context.xmlWriter().addAttribute("svg:y", cp.value().position.y());
}
QString escapeDirection;
switch(cp.value().escapeDirection) {
case KoConnectionPoint::HorizontalDirections:
escapeDirection = "horizontal";
break;
case KoConnectionPoint::VerticalDirections:
escapeDirection = "vertical";
break;
case KoConnectionPoint::LeftDirection:
escapeDirection = "left";
break;
case KoConnectionPoint::RightDirection:
escapeDirection = "right";
break;
case KoConnectionPoint::UpDirection:
escapeDirection = "up";
break;
case KoConnectionPoint::DownDirection:
escapeDirection = "down";
break;
default:
break;
}
if(!escapeDirection.isEmpty()) {
context.xmlWriter().addAttribute("draw:escape-direction", escapeDirection);
}
QString alignment;
switch(cp.value().alignment) {
case KoConnectionPoint::AlignTopLeft:
alignment = "top-left";
break;
case KoConnectionPoint::AlignTop:
alignment = "top";
break;
case KoConnectionPoint::AlignTopRight:
alignment = "top-right";
break;
case KoConnectionPoint::AlignLeft:
alignment = "left";
break;
case KoConnectionPoint::AlignCenter:
alignment = "center";
break;
case KoConnectionPoint::AlignRight:
alignment = "right";
break;
case KoConnectionPoint::AlignBottomLeft:
alignment = "bottom-left";
break;
case KoConnectionPoint::AlignBottom:
alignment = "bottom";
break;
case KoConnectionPoint::AlignBottomRight:
alignment = "bottom-right";
break;
default:
break;
}
if(!alignment.isEmpty()) {
context.xmlWriter().addAttribute("draw:align", alignment);
}
context.xmlWriter().endElement();
}
}
}
void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const
{
debugFlake << "shape saves contour-polygon";
- if (d->clipPath && !d->clipPath->clipPathShapes().isEmpty()) {
+ if (s->clipPath && !s->clipPath->clipPathShapes().isEmpty()) {
// This will loose data as odf can only save one set of contour whereas
// svg loading and at least karbon editing can produce more than one
// TODO, FIXME see if we can save more than one clipshape to odf
- d->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize);
+ s->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize);
}
}
// end loading & saving methods
-// static
-void KoShape::applyConversion(QPainter &painter, const KoViewConverter &converter)
+KisHandlePainterHelper KoShape::createHandlePainterHelperView(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius)
{
- qreal zoomX, zoomY;
- converter.zoom(&zoomX, &zoomY);
- painter.scale(zoomX, zoomY);
+ const QTransform originalPainterTransform = painter->transform();
+
+ painter->setTransform(shape->absoluteTransformation() *
+ converter.documentToView() *
+ painter->transform());
+
+ // move c-tor
+ return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius);
}
-KisHandlePainterHelper KoShape::createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius)
+KisHandlePainterHelper KoShape::createHandlePainterHelperDocument(QPainter *painter, KoShape *shape, qreal handleRadius)
{
const QTransform originalPainterTransform = painter->transform();
- painter->setTransform(shape->absoluteTransformation(&converter) * painter->transform());
- KoShape::applyConversion(*painter, converter);
+ painter->setTransform(shape->absoluteTransformation() *
+ painter->transform());
// move c-tor
return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius);
}
+
QPointF KoShape::shapeToDocument(const QPointF &point) const
{
- return absoluteTransformation(0).map(point);
+ return absoluteTransformation().map(point);
}
QRectF KoShape::shapeToDocument(const QRectF &rect) const
{
- return absoluteTransformation(0).mapRect(rect);
+ return absoluteTransformation().mapRect(rect);
}
QPointF KoShape::documentToShape(const QPointF &point) const
{
- return absoluteTransformation(0).inverted().map(point);
+ return absoluteTransformation().inverted().map(point);
}
QRectF KoShape::documentToShape(const QRectF &rect) const
{
- return absoluteTransformation(0).inverted().mapRect(rect);
+ return absoluteTransformation().inverted().mapRect(rect);
}
bool KoShape::addDependee(KoShape *shape)
{
if (! shape)
return false;
// refuse to establish a circular dependency
if (shape->hasDependee(this))
return false;
if (! d->dependees.contains(shape))
d->dependees.append(shape);
return true;
}
void KoShape::removeDependee(KoShape *shape)
{
int index = d->dependees.indexOf(shape);
if (index >= 0)
d->dependees.removeAt(index);
}
bool KoShape::hasDependee(KoShape *shape) const
{
return d->dependees.contains(shape);
}
QList<KoShape*> KoShape::dependees() const
{
return d->dependees;
}
void KoShape::shapeChanged(ChangeType type, KoShape *shape)
{
Q_UNUSED(type);
Q_UNUSED(shape);
}
KoSnapData KoShape::snapData() const
{
return KoSnapData();
}
void KoShape::setAdditionalAttribute(const QString &name, const QString &value)
{
- d->additionalAttributes.insert(name, value);
+ s->additionalAttributes.insert(name, value);
}
void KoShape::removeAdditionalAttribute(const QString &name)
{
- d->additionalAttributes.remove(name);
+ s->additionalAttributes.remove(name);
}
bool KoShape::hasAdditionalAttribute(const QString &name) const
{
- return d->additionalAttributes.contains(name);
+ return s->additionalAttributes.contains(name);
}
QString KoShape::additionalAttribute(const QString &name) const
{
- return d->additionalAttributes.value(name);
+ return s->additionalAttributes.value(name);
}
void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value)
{
- d->additionalStyleAttributes.insert(name, value);
+ s->additionalStyleAttributes.insert(name, value);
}
void KoShape::removeAdditionalStyleAttribute(const char *name)
{
- d->additionalStyleAttributes.remove(name);
+ s->additionalStyleAttributes.remove(name);
}
KoFilterEffectStack *KoShape::filterEffectStack() const
{
- return d->filterEffectStack;
+ return s->filterEffectStack;
}
void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack)
{
- if (d->filterEffectStack)
- d->filterEffectStack->deref();
- d->filterEffectStack = filterEffectStack;
- if (d->filterEffectStack) {
- d->filterEffectStack->ref();
+ if (s->filterEffectStack)
+ s->filterEffectStack->deref();
+ s->filterEffectStack = filterEffectStack;
+ if (s->filterEffectStack) {
+ s->filterEffectStack->ref();
}
notifyChanged();
}
QSet<KoShape*> KoShape::toolDelegates() const
{
return d->toolDelegates;
}
void KoShape::setToolDelegates(const QSet<KoShape*> &delegates)
{
d->toolDelegates = delegates;
}
QString KoShape::hyperLink () const
{
- return d->hyperLink;
+ return s->hyperLink;
}
void KoShape::setHyperLink(const QString &hyperLink)
{
- d->hyperLink = hyperLink;
+ s->hyperLink = hyperLink;
}
KoShape::ShapeChangeListener::~ShapeChangeListener()
{
Q_FOREACH(KoShape *shape, m_registeredShapes) {
shape->removeShapeChangeListener(this);
}
}
void KoShape::ShapeChangeListener::registerShape(KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!m_registeredShapes.contains(shape));
m_registeredShapes.append(shape);
}
void KoShape::ShapeChangeListener::unregisterShape(KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape));
m_registeredShapes.removeAll(shape);
}
void KoShape::ShapeChangeListener::notifyShapeChangedImpl(KoShape::ChangeType type, KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape));
notifyShapeChanged(type, shape);
if (type == KoShape::Deleted) {
unregisterShape(shape);
}
}
void KoShape::addShapeChangeListener(KoShape::ShapeChangeListener *listener)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener));
listener->registerShape(this);
d->listeners.append(listener);
}
void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener));
d->listeners.removeAll(listener);
listener->unregisterShape(this);
}
QList<KoShape::ShapeChangeListener *> KoShape::listeners() const
{
return d->listeners;
}
QList<KoShape *> KoShape::linearizeSubtree(const QList<KoShape *> &shapes)
{
QList<KoShape *> result;
Q_FOREACH (KoShape *shape, shapes) {
result << shape;
KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
if (container) {
result << linearizeSubtree(container->shapes());
}
}
return result;
}
+
+QList<KoShape *> KoShape::linearizeSubtreeSorted(const QList<KoShape *> &shapes)
+{
+ QList<KoShape*> sortedShapes = shapes;
+ std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
+
+ QList<KoShape *> result;
+
+ Q_FOREACH (KoShape *shape, sortedShapes) {
+ result << shape;
+
+ KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
+ if (container) {
+ result << linearizeSubtreeSorted(container->shapes());
+ }
+ }
+
+ return result;
+}
diff --git a/libs/flake/KoShape.h b/libs/flake/KoShape.h
index 50fca444dd..338cadf35c 100644
--- a/libs/flake/KoShape.h
+++ b/libs/flake/KoShape.h
@@ -1,1313 +1,1273 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006, 2008 C. Boemann <cbo@boemann.dk>
Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
Copyright (C) 2007-2009,2011 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.
*/
#ifndef KOSHAPE_H
#define KOSHAPE_H
#include "KoFlake.h"
#include "KoFlakeTypes.h"
#include "KoConnectionPoint.h"
#include <QSharedPointer>
#include <QSet>
#include <QMetaType>
#include <QSharedDataPointer>
#include <KoXmlReaderForward.h>
#include "kritaflake_export.h"
class QPainter;
class QRectF;
class QPainterPath;
class QTransform;
class KoShapeContainer;
class KoShapeStrokeModel;
class KoShapeUserData;
class KoViewConverter;
class KoShapeApplicationData;
class KoShapeSavingContext;
class KoShapeLoadingContext;
class KoGenStyle;
class KoShapeShadow;
class KoFilterEffectStack;
class KoSnapData;
class KoClipPath;
class KoClipMask;
class KoShapePaintingContext;
class KoShapeAnchor;
class KoBorder;
struct KoInsets;
class KoShapeBackground;
class KisHandlePainterHelper;
class KoShapeManager;
/**
* Base class for all flake shapes. Shapes extend this class
* to allow themselves to be manipulated. This class just represents
* a graphical shape in the document and can be manipulated by some default
* tools in this library.
*
* Due to the limited responsibility of this class, the extending object
* can have any data backend and is responsible for painting itself.
*
* We strongly suggest that any extending class will use a Model View
* Controller (MVC) design where the View part is all in this class, as well
* as the one that inherits from this one. This allows the data that rests
* in the model to be reused in different parts of the document. For example
* by having two flake objects that show that same data. Or each showing a section of it.
*
* The KoShape data is completely in postscript-points (pt) (see KoUnit
* for conversion methods to and from points).
* This image will explain the real-world use of the shape and its options.
* <img src="../flake_shape_coords.png" align=center><br>
* The Rotation center can be returned with absolutePosition()
*
* <p>Flake objects can be created in three ways:
* <ul>
* <li>a simple new KoDerivedFlake(),
* <li>through an associated tool,
* <li>through a factory
* </ul>
*
* <h1>Shape interaction notifications</h1>
* We had several notification methods that allow your shape to be notified of changes in other
* shapes positions or rotation etc.
* <ol><li>The most general is KoShape::shapeChanged().<br>
* a virtual method that you can use to check various changed to your shape made by tools or otherwise.</li>
* <li>for shape hierarchies the parent may receive a notification when a child was modified.
* This is done though KoShapeContainerModel::childChanged()</li>
* <li>any shape that is at a similar position as another shape there is collision detection.
* You can register your shape to be sensitive to any changes like moving or whatever to
* <b>other</b> shapes that intersect yours.
* Such changes will then be notified to your shape using the method from (1) You should call
* KoShape::setCollisionDetection(bool) to enable this.
* </ol>
*/
class KRITAFLAKE_EXPORT KoShape
{
public:
/// Used by shapeChanged() to select which change was made
enum ChangeType {
PositionChanged, ///< used after a setPosition()
RotationChanged, ///< used after a setRotation()
ScaleChanged, ///< used after a scale()
ShearChanged, ///< used after a shear()
SizeChanged, ///< used after a setSize()
GenericMatrixChange, ///< used after the matrix was changed without knowing which property explicitly changed
KeepAspectRatioChange, ///< used after setKeepAspectRatio()
ParentChanged, ///< used after a setParent()
- CollisionDetected, ///< used when another shape moved in our boundingrect
Deleted, ///< the shape was deleted
StrokeChanged, ///< the shapes stroke has changed
BackgroundChanged, ///< the shapes background has changed
ShadowChanged, ///< the shapes shadow has changed
BorderChanged, ///< the shapes border has changed
ParameterChanged, ///< the shapes parameter has changed (KoParameterShape only)
ContentChanged, ///< the content of the shape changed e.g. a new image inside a pixmap/text change inside a textshape
TextRunAroundChanged, ///< used after a setTextRunAroundSide()
ChildChanged, ///< a child of a container was changed/removed. This is propagated to all parents
ConnectionPointChanged, ///< a connection point has changed
ClipPathChanged, ///< the shapes clip path has changed
+ ClipMaskChanged, ///< the shapes clip path has changed
TransparencyChanged ///< the shapetransparency value has changed
};
/// The behavior text should do when intersecting this shape.
enum TextRunAroundSide {
BiggestRunAroundSide, ///< Run other text around the side that has the most space
LeftRunAroundSide, ///< Run other text around the left side of the frame
RightRunAroundSide, ///< Run other text around the right side of the frame
EnoughRunAroundSide, ///< Run other text dynamically around both sides of the shape, provided there is sufficient space left
BothRunAroundSide, ///< Run other text around both sides of the shape
NoRunAround, ///< The text will be completely avoiding the frame by keeping the horizontal space that this frame occupies blank.
RunThrough ///< The text will completely ignore the frame and layout as if it was not there
};
/// The behavior text should do when intersecting this shape.
enum TextRunAroundContour {
ContourBox, /// Run other text around a bounding rect of the outline
ContourFull, ///< Run other text around also on the inside
ContourOutside ///< Run other text around only on the outside
};
/**
* TODO
*/
enum RunThroughLevel {
Background,
Foreground
};
/**
* @brief Constructor
*/
KoShape();
/**
* @brief Destructor
*/
virtual ~KoShape();
/**
* @brief creates a deep copy of the shape or shape's subtree
* @return a cloned shape
*/
virtual KoShape* cloneShape() const;
/**
* @brief Paint the shape fill
- * The class extending this one is responsible for painting itself. Since we do not
- * assume the shape is square the paint must also clear its background if it will draw
- * something transparent on top.
- * This can be done with a method like:
- * <code>
- painter.fillRect(converter.normalToView(QRectF(QPointF(0.0,0.0), size())), background());</code>
- * Or equivalent for non-square objects.
- * Do note that a shape's top-left is always at coordinate 0,0. Even if the shape itself is rotated
- * or translated.
+ * The class extending this one is responsible for painting itself. \p painter is expected
+ * to be preconfigured to work in "document" pixels.
+ *
* @param painter used for painting the shape
- * @param converter to convert between internal and view coordinates.
- * @see applyConversion()
* @param paintcontext the painting context.
*/
- virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) = 0;
+ virtual void paint(QPainter &painter, KoShapePaintingContext &paintcontext) const = 0;
/**
* @brief paintStroke paints the shape's stroked outline
* @param painter used for painting the shape
- * @param converter to convert between internal and view coordinates.
* @see applyConversion()
* @param paintcontext the painting context.
*/
- virtual void paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext);
-
- /**
- * @brief Paint the shape's border
- * This is a helper function that could be called from the paint() method of all shapes.
- * @param painter used for painting the shape
- * @param converter to convert between internal and view coordinates.
- * @see applyConversion()
- */
- virtual void paintBorder(QPainter &painter, const KoViewConverter &converter);
+ virtual void paintStroke(QPainter &painter, KoShapePaintingContext &paintcontext) const;
/**
* Load a shape from odf
*
* @param context the KoShapeLoadingContext used for loading
* @param element element which represents the shape in odf
*
* @return false if loading failed
*/
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) = 0;
/**
* @brief store the shape data as ODF XML.
* This is the method that will be called when saving a shape as a described in
* OpenDocument 9.2 Drawing Shapes.
* @see saveOdfAttributes
*/
virtual void saveOdf(KoShapeSavingContext &context) const = 0;
/**
* This method can be used while saving the shape as ODF to add the data
* stored on this shape to the current element.
*
* @param context the context for the current save.
* @param attributes a number of OdfAttribute items to state which attributes to save.
* @see saveOdf
*/
void saveOdfAttributes(KoShapeSavingContext &context, int attributes) const;
/**
* This method can be used while saving the shape as Odf to add common child elements
*
* The office:event-listeners and draw:glue-point are saved.
* @param context the context for the current save.
*/
void saveOdfCommonChildElements(KoShapeSavingContext &context) const;
/**
* This method can be used to save contour data from the clipPath()
*
* The draw:contour-polygon or draw:contour-path elements are saved.
* @param context the context for the current save.
* @param originalSize the original size of the unscaled image.
*/
void saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const;
/**
* @brief Scale the shape using the zero-point which is the top-left corner.
* @see position()
*
* @param sx scale in x direction
* @param sy scale in y direction
*/
void scale(qreal sx, qreal sy);
/**
* @brief Rotate the shape (relative)
*
* The shape will be rotated from the current rotation using the center of the shape using the size()
*
* @param angle change the angle of rotation increasing it with 'angle' degrees
*/
void rotate(qreal angle);
/**
* Return the current rotation in degrees.
* It returns NaN if the shape has a shearing or scaling transformation applied.
*/
qreal rotation() const;
/**
* @brief Shear the shape
* The shape will be sheared using the zero-point which is the top-left corner.
* @see position()
*
* @param sx shear in x direction
* @param sy shear in y direction
*/
void shear(qreal sx, qreal sy);
/**
* @brief Resize the shape
*
* @param size the new size of the shape. This is different from scaling as
* scaling is a so called secondary operation which is comparable to zooming in
* instead of changing the size of the basic shape.
* Easiest example of this difference is that using this method will not distort the
* size of pattern-fills and strokes.
*/
virtual void setSize(const QSizeF &size);
/**
* @brief Get the size of the shape in pt.
*
* The size is in shape coordinates.
*
* @return the size of the shape as set by setSize()
*/
virtual QSizeF size() const;
/**
* @brief Set the position of the shape in pt
*
* @param position the new position of the shape
*/
virtual void setPosition(const QPointF &position);
/**
* @brief Get the position of the shape in pt
*
* @return the position of the shape
*/
QPointF position() const;
/**
* @brief Check if the shape is hit on position
* @param position the position where the user clicked.
* @return true when it hits.
*/
virtual bool hitTest(const QPointF &position) const;
/**
* @brief Get the bounding box of the shape
*
* This includes the line width and the shadow of the shape
*
* @return the bounding box of the shape
*/
virtual QRectF boundingRect() const;
/**
* Get the united bounding box of a group of shapes. This is a utility
* function used in many places in Krita.
*/
static QRectF boundingRect(const QList<KoShape*> &shapes);
/**
* @return the bounding rect of the outline of the shape measured
* in absolute coordinate system. Please note that in contrast to
* boundingRect() this rect doesn't include the stroke and other
* insets.
*/
- QRectF absoluteOutlineRect(KoViewConverter *converter = 0) const;
+ QRectF absoluteOutlineRect() const;
/**
* Same as a member function, but applies to a list of shapes and returns a
* united rect.
*/
- static QRectF absoluteOutlineRect(const QList<KoShape*> &shapes, KoViewConverter *converter = 0);
+ static QRectF absoluteOutlineRect(const QList<KoShape*> &shapes);
/**
* @brief Add a connector point to the shape
*
* A connector is a place on the shape that allows a graphical connection to be made
* using a line, for example.
*
* @param point the connection point to add
* @return the id of the new connection point
*/
int addConnectionPoint(const KoConnectionPoint &point);
/**
* Sets data of connection point with specified id.
*
* The position of the connector is restricted to the bounding rectangle of the shape.
* When setting a default connection point, the new position is ignored, as these
* are fixed at their default position.
* The function will insert a new connection point if the specified id was not used
* before.
*
* @param connectionPointId the id of the connection point to set
* @param point the connection point data
* @return false if specified connection point id is invalid, else true
*/
bool setConnectionPoint(int connectionPointId, const KoConnectionPoint &point);
/// Checks if a connection point with the specified id exists
bool hasConnectionPoint(int connectionPointId) const;
/// Returns connection point with specified connection point id
KoConnectionPoint connectionPoint(int connectionPointId) const;
/**
* Return a list of the connection points that have been added to this shape.
* All the points are relative to the shape position, see absolutePosition().
* @return a list of the connectors that have been added to this shape.
*/
KoConnectionPoints connectionPoints() const;
/// Removes connection point with specified id
void removeConnectionPoint(int connectionPointId);
/// Removes all connection points
void clearConnectionPoints();
/**
* Return the side text should flow around this shape. This implements the ODF style:wrap
* attribute that specifies how text is displayed around a frame or graphic object.
*/
TextRunAroundSide textRunAroundSide() const;
/**
* Set the side text should flow around this shape.
* @param side the requested side
* @param runThrough run through the foreground or background or...
*/
void setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrough = Background);
/**
* The space between this shape's left edge and text that runs around this shape.
* @return the space around this shape to keep free from text
*/
qreal textRunAroundDistanceLeft() const;
/**
* Set the space between this shape's left edge and the text that run around this shape.
* @param distance the space around this shape to keep free from text
*/
void setTextRunAroundDistanceLeft(qreal distance);
/**
* The space between this shape's top edge and text that runs around this shape.
* @return the space around this shape to keep free from text
*/
qreal textRunAroundDistanceTop() const;
/**
* Set the space between this shape's top edge and the text that run around this shape.
* @param distance the space around this shape to keep free from text
*/
void setTextRunAroundDistanceTop(qreal distance);
/**
* The space between this shape's right edge and text that runs around this shape.
* @return the space around this shape to keep free from text
*/
qreal textRunAroundDistanceRight() const;
/**
* Set the space between this shape's right edge and the text that run around this shape.
* @param distance the space around this shape to keep free from text
*/
void setTextRunAroundDistanceRight(qreal distance);
/**
* The space between this shape's bottom edge and text that runs around this shape.
* @return the space around this shape to keep free from text
*/
qreal textRunAroundDistanceBottom() const;
/**
* Set the space between this shape's bottom edge and the text that run around this shape.
* @param distance the space around this shape to keep free from text
*/
void setTextRunAroundDistanceBottom(qreal distance);
/**
* Return the threshold above which text should flow around this shape.
* The text will not flow around the shape on a side unless the space available on that side
* is above this threshold. Only used when the text run around side is EnoughRunAroundSide.
* @return threshold the threshold
*/
qreal textRunAroundThreshold() const;
/**
* Set the threshold above which text should flow around this shape.
* The text will not flow around the shape on a side unless the space available on that side
* is above this threshold. Only used when the text run around side is EnoughRunAroundSide.
* @param threshold the new threshold
*/
void setTextRunAroundThreshold(qreal threshold);
/**
* Return the how tight text run around is done around this shape.
* @return the contour
*/
TextRunAroundContour textRunAroundContour() const;
/**
* Set how tight text run around is done around this shape.
* @param contour the new contour
*/
void setTextRunAroundContour(TextRunAroundContour contour);
/**
* Set the KoShapeAnchor
*/
void setAnchor(KoShapeAnchor *anchor);
/**
* Return the KoShapeAnchor, or 0
*/
KoShapeAnchor *anchor() const;
/**
* Set the minimum height of the shape.
* Currently it's not respected but only for informational purpose
* @param height the minimum height of the frame.
*/
void setMinimumHeight(qreal height);
/**
* Return the minimum height of the shape.
* @return the minimum height of the shape. Default is 0.0.
*/
qreal minimumHeight() const;
/**
* Set the background of the shape.
* A shape background can be a plain color, a gradient, a pattern, be fully transparent
* or have a complex fill.
* Setting such a background will allow the shape to be filled and will be able to tell
* if it is transparent or not.
*
* If the shape inherited the background from its parent, its stops inheriting it, that
* is inheritBackground property resets to false.
*
* @param background the new shape background.
*/
void setBackground(QSharedPointer<KoShapeBackground> background);
/**
* return the brush used to paint te background of this shape with.
* A QBrush can have a plain color, be fully transparent or have a complex fill.
* setting such a brush will allow the shape to fill itself using that brush and
* will be able to tell if its transparent or not.
* @return the background-brush
*/
QSharedPointer<KoShapeBackground> background() const;
/**
* @brief setInheritBackground marks a shape as inhiriting the background
* from the parent shape. NOTE: The currently selected background is destroyed.
* @param value true if the shape should inherit the filling background
*/
void setInheritBackground(bool value);
/**
* @brief inheritBackground shows if the shape inherits background from its parent
* @return true if the shape inherits the fill
*/
bool inheritBackground() const;
/**
* Returns true if there is some transparency, false if the shape is fully opaque.
* The default implementation will just return if the background has some transparency,
* you should override it and always return true if your shape is not square.
* @return if the shape is (partly) transparent.
*/
virtual bool hasTransparency() const;
/**
* Sets shape level transparency.
* @param transparency the new shape level transparency
*/
void setTransparency(qreal transparency);
/**
* Returns the shape level transparency.
* @param recursive when true takes the parents transparency into account
*/
qreal transparency(bool recursive=false) const;
/**
* Retrieve the z-coordinate of this shape.
* The zIndex property is used to determine which shape lies on top of other objects.
* An shape with a higher z-order is on top, and can obscure another shape.
* @return the z-index of this shape.
* @see setZIndex()
*/
qint16 zIndex() const;
/**
* Set the z-coordinate of this shape.
* The zIndex property is used to determine which shape lies on top of other objects.
* An shape with a higher z-order is on top, and can obscure, another shape.
* <p>Just like two objects having the same x or y coordinate will make them 'touch',
* so will two objects with the same z-index touch on the z plane. In layering the
* shape this, however, can cause a little confusion as one always has to be on top.
* The layering if two overlapping objects have the same index is implementation dependent
* and probably depends on the order in which they are added to the shape manager.
* @param zIndex the new z-index;
*/
void setZIndex(qint16 zIndex);
/**
* Maximum value of z-index
*/
static const qint16 maxZIndex;
/**
* Minimum value of z-index
*/
static const qint16 minZIndex;
/**
* Retrieve the run through property of this shape.
* The run through property is used to determine if the shape is behind, inside or before text.
* @return the run through of this shape.
*/
- int runThrough();
+ int runThrough() const;
/**
* Set the run through property of this shape.
* The run through property is used to determine if the shape is behind, inside or before text.
* @param runThrough the new run through;
*/
virtual void setRunThrough(short int runThrough);
/**
* Changes the Shape to be visible or invisible.
* Being visible means being painted, as well as being used for
* things like guidelines or searches.
* @param on when true; set the shape to be visible.
* @see setGeometryProtected(), setContentProtected(), setSelectable()
*/
void setVisible(bool on);
/**
* Returns current visibility state of this shape.
* Being visible means being painted, as well as being used for
* things like guidelines or searches.
* @param recursive when true, checks visibility recursively
* @return current visibility state of this shape.
* @see isGeometryProtected(), isContentProtected(), isSelectable()
*/
bool isVisible(bool recursive = true) const;
/**
* Changes the shape to be printable or not. The default is true.
*
* If a Shape's print flag is true, the shape will be printed. If
* false, the shape will not be printed. If a shape is not visible (@see isVisible),
* it isPrinted will return false, too.
*/
void setPrintable(bool on);
/**
* Returns the current printable state of this shape.
*
* A shape can be visible but not printable, not printable and not visible
* or visible and printable, but not invisible and still printable.
*
* @return current printable state of this shape.
*/
bool isPrintable() const;
/**
* Makes it possible for the user to select this shape.
* This parameter defaults to true.
* @param selectable when true; set the shape to be selectable by the user.
* @see setGeometryProtected(), setContentProtected(), setVisible()
*/
void setSelectable(bool selectable);
/**
* Returns if this shape can be selected by the user.
* @return true only when the object is selectable.
* @see isGeometryProtected(), isContentProtected(), isVisible()
*/
bool isSelectable() const;
/**
* Tells the shape to have its position/rotation and size protected from user-changes.
* The geometry being protected means the user can not change shape or position of the
* shape. This includes any matrix operation such as rotation.
* @param on when true; set the shape to have its geometry protected.
* @see setContentProtected(), setSelectable(), setVisible()
*/
void setGeometryProtected(bool on);
/**
* Returns current geometry protection state of this shape.
* The geometry being protected means the user can not change shape or position of the
* shape. This includes any matrix operation such as rotation.
* @return current geometry protection state of this shape.
* @see isContentProtected(), isSelectable(), isVisible()
*/
bool isGeometryProtected() const;
/**
* Marks the shape to have its content protected against editing.
* Content protection is a hint for tools to disallow the user editing the content.
* @param protect when true set the shapes content to be protected from user modification.
* @see setGeometryProtected(), setSelectable(), setVisible()
*/
void setContentProtected(bool protect);
/**
* Returns current content protection state of this shape.
* Content protection is a hint for tools to disallow the user editing the content.
* @return current content protection state of this shape.
* @see isGeometryProtected(), isSelectable(), isVisible()
*/
bool isContentProtected() const;
/**
* Returns the parent, or 0 if there is no parent.
* @return the parent, or 0 if there is no parent.
*/
KoShapeContainer *parent() const;
/**
* Set the parent of this shape.
* @param parent the new parent of this shape. Can be 0 if the shape has no parent anymore.
*/
void setParent(KoShapeContainer *parent);
/**
* @brief inheritsTransformFromAny checks if the shape inherits transformation from
* any of the shapes listed in \p ancestorsInQuestion. The inheritance is checked
* in recursive way.
* @return true if there is a (transitive) transformation-wise parent found in \p ancestorsInQuestion
*/
bool inheritsTransformFromAny(const QList<KoShape*> ancestorsInQuestion) const;
/**
* @return true if this shape has a common parent with \p shape
*/
bool hasCommonParent(const KoShape *shape) const;
/**
* Request a repaint to be queued.
* The repaint will be of the entire Shape, including its selection handles should this
* shape be selected.
* <p>This method will return immediately and only request a repaint. Successive calls
* will be merged into an appropriate repaint action.
*/
virtual void update() const;
/**
* Request a repaint to be queued.
* The repaint will be restricted to the parameters rectangle, which is expected to be
* in absolute coordinates of the canvas and it is expected to be
* normalized.
* <p>This method will return immediately and only request a repaint. Successive calls
* will be merged into an appropriate repaint action.
* @param rect the rectangle (in pt) to queue for repaint.
*/
virtual void updateAbsolute(const QRectF &rect) const;
/// Used by compareShapeZIndex() to order shapes
enum ChildZOrderPolicy {
ChildZDefault,
ChildZParentChild = ChildZDefault, ///< normal parent/child ordering
ChildZPassThrough ///< children are considered equal to this shape
};
/**
* Returns if during compareShapeZIndex() how this shape portrays the values
* of its children. The default behaviour is to let this shape's z values take
* the place of its childrens values, so you get a parent/child relationship.
* The children are naturally still ordered relatively to their z values
*
* But for special cases (like Calligra's TextShape) it can be overloaded to return
* ChildZPassThrough which means the children keep their own z values
* @returns the z order policy of this shape
*/
virtual ChildZOrderPolicy childZOrderPolicy();
/**
* This is a method used to sort a list using the STL sorting methods.
* @param s1 the first shape
* @param s2 the second shape
*/
static bool compareShapeZIndex(KoShape *s1, KoShape *s2);
/**
* returns the outline of the shape in the form of a path.
* The outline returned will always be relative to the position() of the shape, so
* moving the shape will not alter the result. The outline is used to draw the stroke
* on, for example.
* @returns the outline of the shape in the form of a path.
*/
virtual QPainterPath outline() const;
/**
* returns the outline of the shape in the form of a rect.
* The outlineRect returned will always be relative to the position() of the shape, so
* moving the shape will not alter the result. The outline is used to calculate
* the boundingRect.
* @returns the outline of the shape in the form of a rect.
*/
virtual QRectF outlineRect() const;
/**
* returns the outline of the shape in the form of a path for the use of painting a shadow.
*
* Normally this would be the same as outline() if there is a fill (background) set on the
* shape and empty if not. However, a shape could reimplement this to return an outline
* even if no fill is defined. A typical example of this would be the picture shape
* which has a picture but almost never a background.
*
* @returns the outline of the shape in the form of a path.
*/
virtual QPainterPath shadowOutline() const;
/**
* Returns the currently set stroke, or 0 if there is no stroke.
* @return the currently set stroke, or 0 if there is no stroke.
*/
KoShapeStrokeModelSP stroke() const;
/**
* Set a new stroke, removing the old one. The stroke inheritance becomes disabled.
* @param stroke the new stroke, or 0 if there should be no stroke.
*/
void setStroke(KoShapeStrokeModelSP stroke);
/**
* @brief setInheritStroke marks a shape as inhiriting the stroke
* from the parent shape. NOTE: The currently selected stroke is destroyed.
* @param value true if the shape should inherit the stroke style
*/
void setInheritStroke(bool value);
/**
* @brief inheritStroke shows if the shape inherits the stroke from its parent
* @return true if the shape inherits the stroke style
*/
bool inheritStroke() const;
/**
* Return the insets of the stroke.
* Convenience method for KoShapeStrokeModel::strokeInsets()
*/
KoInsets strokeInsets() const;
/// Sets the new shadow, removing the old one
void setShadow(KoShapeShadow *shadow);
/// Returns the currently set shadow or 0 if there is no shadow set
KoShapeShadow *shadow() const;
/// Sets the new border, removing the old one.
void setBorder(KoBorder *border);
/// Returns the currently set border or 0 if there is no border set
KoBorder *border() const;
/// Sets a new clip path, removing the old one
void setClipPath(KoClipPath *clipPath);
/// Returns the currently set clip path or 0 if there is no clip path set
KoClipPath * clipPath() const;
/// Sets a new clip mask, removing the old one. The mask is owned by the shape.
void setClipMask(KoClipMask *clipMask);
/// Returns the currently set clip mask or 0 if there is no clip mask set
KoClipMask* clipMask() const;
/**
* Setting the shape to keep its aspect-ratio has the effect that user-scaling will
* keep the width/height ratio intact so as not to distort shapes that rely on that
* ratio.
* @param keepAspect the new value
*/
void setKeepAspectRatio(bool keepAspect);
/**
* Setting the shape to keep its aspect-ratio has the effect that user-scaling will
* keep the width/height ratio intact so as not to distort shapes that rely on that
* ratio.
* @return whether to keep aspect ratio of this shape
*/
bool keepAspectRatio() const;
/**
* Return the position of this shape regardless of rotation/skew/scaling and regardless of
* this shape having a parent (being in a group) or not.<br>
* @param anchor The place on the (unaltered) shape that you want the position of.
* @return the point that is the absolute, centered position of this shape.
*/
QPointF absolutePosition(KoFlake::AnchorPosition anchor = KoFlake::Center) const;
/**
* Move this shape to an absolute position where the end location will be the same
* regardless of the shape's rotation/skew/scaling and regardless of this shape having
* a parent (being in a group) or not.<br>
* The newPosition is going to be the center of the shape.
* This has the convenient effect that: <pre>
shape-&gt;setAbsolutePosition(QPointF(0,0));
shape-&gt;rotate(45);</pre>
Will result in the same visual position of the shape as the opposite:<pre>
shape-&gt;rotate(45);
shape-&gt;setAbsolutePosition(QPointF(0,0));</pre>
* @param newPosition the new absolute center of the shape.
* @param anchor The place on the (unaltered) shape that you set the position of.
*/
void setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor = KoFlake::Center);
/**
* Set a data object on the shape to be used by an application.
* This is specifically useful when a shape is created in a plugin and that data from that
* shape should be accessible outside the plugin.
* @param userData the new user data, or 0 to delete the current one.
*/
void setUserData(KoShapeUserData *userData);
/**
* Return the current userData.
*/
KoShapeUserData *userData() const;
/**
* Return the Id of this shape, identifying the type of shape by the id of the factory.
* @see KoShapeFactoryBase::shapeId()
* @return the id of the shape-type
*/
QString shapeId() const;
/**
* Set the Id of this shape. A shapeFactory is expected to set the Id at creation
* so applications can find out what kind of shape this is.
* @see KoShapeFactoryBase::shapeId()
* @param id the ID from the factory that created this shape
*/
void setShapeId(const QString &id);
/**
* Create a matrix that describes all the transformations done on this shape.
*
* The absolute transformation is the combined transformation of this shape
* and all its parents and grandparents.
- *
- * @param converter if not null, this method uses the converter to mark the right
- * offsets in the current view.
*/
- QTransform absoluteTransformation(const KoViewConverter *converter) const;
+ QTransform absoluteTransformation() const;
/**
* Applies a transformation to this shape.
*
* The transformation given is relative to the global coordinate system, i.e. the document.
* This is a convenience function to apply a global transformation to this shape.
* @see applyTransformation
*
* @param matrix the transformation matrix to apply
*/
void applyAbsoluteTransformation(const QTransform &matrix);
/**
* Sets a new transformation matrix describing the local transformations on this shape.
* @param matrix the new transformation matrix
*/
void setTransformation(const QTransform &matrix);
/// Returns the shapes local transformation matrix
QTransform transformation() const;
/**
* Applies a transformation to this shape.
*
* The transformation given is relative to the shape coordinate system.
*
* @param matrix the transformation matrix to apply
*/
void applyTransformation(const QTransform &matrix);
/**
* Copy all the settings from the parameter shape and apply them to this shape.
* Settings like the position and rotation to visible and locked. The parent
* is a notable exclusion.
* @param shape the shape to use as original
*/
void copySettings(const KoShape *shape);
- /**
- * Convenience method that allows people implementing paint() to use the shape
- * internal coordinate system directly to paint itself instead of considering the
- * views zoom.
- * @param painter the painter to alter the zoom level of.
- * @param converter the converter for the current views zoom.
- */
- static void applyConversion(QPainter &painter, const KoViewConverter &converter);
-
/**
* A convenience method that creates a handles helper with applying transformations at
* the same time. Please note that you shouldn't save/restore additionally. All the work
* on restoring original painter's transformations is done by the helper.
*/
- static KisHandlePainterHelper createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius = 0.0);
+ static KisHandlePainterHelper createHandlePainterHelperView(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius = 0.0);
+ static KisHandlePainterHelper createHandlePainterHelperDocument(QPainter *painter, KoShape *shape, qreal handleRadius);
/**
* @brief Transforms point from shape coordinates to document coordinates
* @param point in shape coordinates
* @return point in document coordinates
*/
QPointF shapeToDocument(const QPointF &point) const;
/**
* @brief Transforms rect from shape coordinates to document coordinates
* @param rect in shape coordinates
* @return rect in document coordinates
*/
QRectF shapeToDocument(const QRectF &rect) const;
/**
* @brief Transforms point from document coordinates to shape coordinates
* @param point in document coordinates
* @return point in shape coordinates
*/
QPointF documentToShape(const QPointF &point) const;
/**
* @brief Transform rect from document coordinates to shape coordinates
* @param rect in document coordinates
* @return rect in shape coordinates
*/
QRectF documentToShape(const QRectF &rect) const;
/**
* Returns the name of the shape.
* @return the shapes name
*/
QString name() const;
/**
* Sets the name of the shape.
* @param name the new shape name
*/
void setName(const QString &name);
/**
* Update the position of the shape in the tree of the KoShapeManager.
*/
void notifyChanged();
/**
* A shape can be in a state that it is doing processing data like loading or text layout.
* In this case it can be shown on screen probably partially but it should really not be printed
* until it is fully done processing.
* Warning! This method can be blocking for a long time
- * @param converter The converter
* @param asynchronous If set to true the processing will can take place in a different thread and the
* function will not block until the shape is finished.
* In case of printing Flake will call this method from a non-main thread and only
* start printing it when the in case of printing method returned.
* If set to false the processing needs to be done synchronously and will
* block until the result is finished.
*/
- virtual void waitUntilReady(const KoViewConverter &converter, bool asynchronous = true) const;
+ virtual void waitUntilReady(bool asynchronous = true) const;
/// checks recursively if the shape or one of its parents is not visible or locked
virtual bool isShapeEditable(bool recursive = true) const;
/**
* Adds a shape which depends on this shape.
* Making a shape dependent on this one means it will get shapeChanged() called
* on each update of this shape.
*
* If this shape already depends on the given shape, establishing the
* dependency is refused to prevent circular dependencies.
*
* @param shape the shape which depends on this shape
* @return true if dependency could be established, otherwise false
* @see removeDependee(), hasDependee()
*/
bool addDependee(KoShape *shape);
/**
* Removes as shape depending on this shape.
* @see addDependee(), hasDependee()
*/
void removeDependee(KoShape *shape);
/// Returns if the given shape is dependent on this shape
bool hasDependee(KoShape *shape) const;
/// Returns list of shapes depending on this shape
QList<KoShape*> dependees() const;
/// Returns additional snap data the shape wants to have snapping to
virtual KoSnapData snapData() const;
/**
* Set additional attribute
*
* This can be used to attach additional attributes to a shape for attributes
* that are application specific like presentation:placeholder
*
* @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder
* @param value The value of the attribute
*/
void setAdditionalAttribute(const QString &name, const QString &value);
/**
* Remove additional attribute
*
* @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder
*/
void removeAdditionalAttribute(const QString &name);
/**
* Check if additional attribute is set
*
* @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder
*
* @return true if there is a attribute with prefix:tag set, false otherwise
*/
bool hasAdditionalAttribute(const QString &name) const;
/**
* Get additional attribute
*
* @param name The name of the attribute in the following form prefix:tag e.g. presentation:placeholder
*
* @return The value of the attribute if it exists or a null string if not found.
*/
QString additionalAttribute(const QString &name) const;
void setAdditionalStyleAttribute(const char *name, const QString &value);
void removeAdditionalStyleAttribute(const char *name);
/**
* Returns the filter effect stack of the shape
*
* @return the list of filter effects applied on the shape when rendering.
*/
KoFilterEffectStack *filterEffectStack() const;
/// Sets the new filter effect stack, removing the old one
void setFilterEffectStack(KoFilterEffectStack *filterEffectStack);
- /**
- * Set the property collision detection.
- * Setting this to true will result in calls to shapeChanged() with the CollisionDetected
- * parameter whenever either this or another shape is moved/rotated etc and intersects this shape.
- * @param detect if true detect collisions.
- */
- void setCollisionDetection(bool detect);
-
- /**
- * get the property collision detection.
- * @returns true if collision detection is on.
- */
- bool collisionDetection();
-
/**
* Return the tool delegates for this shape.
* In Flake a shape being selected will cause the tool manager to make available all tools that
* can edit the selected shapes. In some cases selecting one shape should allow the tool to
* edit a related shape be available too. The tool delegates allows this to happen by taking
* all the shapes in the set into account on tool selection.
* Notice that if the set is non-empty 'this' shape is no longer looked at. You can choose
* to add itself to the set too.
*/
QSet<KoShape*> toolDelegates() const;
/**
* Set the tool delegates.
* @param delegates the new delegates.
* @see toolDelegates()
*/
void setToolDelegates(const QSet<KoShape*> &delegates);
/**
* Return the hyperlink for this shape.
*/
QString hyperLink () const;
/**
* Set hyperlink for this shape.
* @param hyperLink name.
*/
void setHyperLink(const QString &hyperLink);
public:
struct KRITAFLAKE_EXPORT ShapeChangeListener {
virtual ~ShapeChangeListener();
virtual void notifyShapeChanged(ChangeType type, KoShape *shape) = 0;
private:
friend class KoShape;
void registerShape(KoShape *shape);
void unregisterShape(KoShape *shape);
void notifyShapeChangedImpl(ChangeType type, KoShape *shape);
QList<KoShape*> m_registeredShapes;
};
void addShapeChangeListener(ShapeChangeListener *listener);
void removeShapeChangeListener(ShapeChangeListener *listener);
protected:
QList<ShapeChangeListener *> listeners() const;
void setSizeImpl(const QSizeF &size) const;
public:
static QList<KoShape*> linearizeSubtree(const QList<KoShape*> &shapes);
-
+ static QList<KoShape *> linearizeSubtreeSorted(const QList<KoShape *> &shapes);
protected:
KoShape(const KoShape &rhs);
/* ** loading saving helper methods */
/// attributes from ODF 1.1 chapter 9.2.15 Common Drawing Shape Attributes
enum OdfAttribute {
OdfTransformation = 1, ///< Store transformation information
OdfSize = 2, ///< Store size information
OdfPosition = 8, ///< Store position
OdfAdditionalAttributes = 4, ///< Store additional attributes of the shape
OdfCommonChildElements = 16, ///< Event actions and connection points
OdfLayer = 64, ///< Store layer name
OdfStyle = 128, ///< Store the style
OdfId = 256, ///< Store the unique ID
OdfName = 512, ///< Store the name of the shape
OdfZIndex = 1024, ///< Store the z-index
OdfViewbox = 2048, ///< Store the viewbox
/// A mask for all mandatory attributes
OdfMandatories = OdfLayer | OdfStyle | OdfId | OdfName | OdfZIndex,
/// A mask for geometry attributes
OdfGeometry = OdfPosition | OdfSize,
/// A mask for all the attributes
OdfAllAttributes = OdfTransformation | OdfGeometry | OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements
};
/**
* This method is used during loading of the shape to load common attributes
*
* @param context the KoShapeLoadingContext used for loading
* @param element element which represents the shape in odf
* @param attributes a number of OdfAttribute items to state which attributes to load.
*/
bool loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes);
/**
* Parses the transformation attribute from the given string
* @param transform the transform attribute string
* @return the resulting transformation matrix
*/
QTransform parseOdfTransform(const QString &transform);
/**
* @brief Saves the style used for the shape
*
* This method fills the given style object with the stroke and
* background properties and then adds the style to the context.
*
* @param style the style object to fill
* @param context used for saving
* @return the name of the style
* @see saveOdf
*/
virtual QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const;
/**
* Loads the stroke and fill style from the given element.
*
* @param element the xml element to load the style from
* @param context the loading context used for loading
*/
virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context);
/// Loads the stroke style
KoShapeStrokeModelSP loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const;
/// Loads the fill style
QSharedPointer<KoShapeBackground> loadOdfFill(KoShapeLoadingContext &context) const;
/// Loads the connection points
void loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context);
/// Loads the clip contour
void loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor);
/* ** end loading saving */
/**
* A hook that allows inheriting classes to do something after a KoShape property changed
* This is called whenever the shape, position rotation or scale properties were altered.
* @param type an indicator which type was changed.
* @param shape the shape.
*/
virtual void shapeChanged(ChangeType type, KoShape *shape = 0);
/// return the current matrix that contains the rotation/scale/position of this shape
QTransform transform() const;
private:
- class Private;
- QSharedDataPointer<Private> d;
+ struct Private;
+ QScopedPointer<Private> d;
+
+ class SharedData;
+ QSharedDataPointer<SharedData> s;
+
protected:
/**
* Notify the shape that a change was done. To be used by inheriting shapes.
* @param type the change type
*/
void shapeChangedPriv(KoShape::ChangeType type);
private:
void addShapeManager(KoShapeManager *manager);
void removeShapeManager(KoShapeManager *manager);
friend class KoShapeManager;
};
Q_DECLARE_METATYPE(KoShape*)
#endif
diff --git a/libs/flake/KoShapeAnchor.cpp b/libs/flake/KoShapeAnchor.cpp
index 30288c0641..a393e26235 100644
--- a/libs/flake/KoShapeAnchor.cpp
+++ b/libs/flake/KoShapeAnchor.cpp
@@ -1,528 +1,528 @@
/* This file is part of the KDE project
* Copyright (C) 2007, 2009-2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 Ko Gmbh <cbo@kogmbh.com>
* Copyright (C) 2011 Matus Hanzes <matus.hanzes@ixonos.com>
* Copyright (C) 2013 C. Boemann <cbo@boemann.dk>
*
* 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 "KoShapeAnchor.h"
#include "KoStyleStack.h"
#include "KoOdfLoadingContext.h"
#include <KoShapeContainer.h>
#include <KoXmlWriter.h>
#include <KoXmlReader.h>
#include <KoXmlNS.h>
#include <KoShapeSavingContext.h>
#include <KoShapeLoadingContext.h>
#include <QRectF>
#include <QTransform>
#include <FlakeDebug.h>
#include <KoGenChanges.h>
class Q_DECL_HIDDEN KoShapeAnchor::Private
{
public:
Private(KoShape *s)
: shape(s)
, verticalPos(KoShapeAnchor::VTop)
, verticalRel(KoShapeAnchor::VLine)
, horizontalPos(KoShapeAnchor::HLeft)
, horizontalRel(KoShapeAnchor::HChar)
, flowWithText(true)
, anchorType(KoShapeAnchor::AnchorToCharacter)
, placementStrategy(0)
, pageNumber(-1)
, textLocation(0)
{
}
QDebug printDebug(QDebug dbg) const
{
#ifndef NDEBUG
dbg.space() << "KoShapeAnchor" << this;
dbg.space() << "offset:" << offset;
dbg.space() << "shape:" << shape->name();
#endif
return dbg.space();
}
KoShape * const shape;
QPointF offset;
KoShapeAnchor::VerticalPos verticalPos;
KoShapeAnchor::VerticalRel verticalRel;
KoShapeAnchor::HorizontalPos horizontalPos;
KoShapeAnchor::HorizontalRel horizontalRel;
QString wrapInfluenceOnPosition;
bool flowWithText;
KoShapeAnchor::AnchorType anchorType;
KoShapeAnchor::PlacementStrategy *placementStrategy;
int pageNumber;
KoShapeAnchor::TextLocation *textLocation;
};
KoShapeAnchor::KoShapeAnchor(KoShape *shape)
: d(new Private(shape))
{
}
KoShapeAnchor::~KoShapeAnchor()
{
if (d->placementStrategy != 0) {
delete d->placementStrategy;
}
delete d;
}
KoShape *KoShapeAnchor::shape() const
{
return d->shape;
}
KoShapeAnchor::AnchorType KoShapeAnchor::anchorType() const
{
return d->anchorType;
}
void KoShapeAnchor::setHorizontalPos(HorizontalPos hp)
{
d->horizontalPos = hp;
}
KoShapeAnchor::HorizontalPos KoShapeAnchor::horizontalPos() const
{
return d->horizontalPos;
}
void KoShapeAnchor::setHorizontalRel(HorizontalRel hr)
{
d->horizontalRel = hr;
}
KoShapeAnchor::HorizontalRel KoShapeAnchor::horizontalRel() const
{
return d->horizontalRel;
}
void KoShapeAnchor::setVerticalPos(VerticalPos vp)
{
d->verticalPos = vp;
}
KoShapeAnchor::VerticalPos KoShapeAnchor::verticalPos() const
{
return d->verticalPos;
}
void KoShapeAnchor::setVerticalRel(VerticalRel vr)
{
d->verticalRel = vr;
}
KoShapeAnchor::VerticalRel KoShapeAnchor::verticalRel() const
{
return d->verticalRel;
}
QString KoShapeAnchor::wrapInfluenceOnPosition() const
{
return d->wrapInfluenceOnPosition;
}
bool KoShapeAnchor::flowWithText() const
{
return d->flowWithText;
}
int KoShapeAnchor::pageNumber() const
{
return d->pageNumber;
}
const QPointF &KoShapeAnchor::offset() const
{
return d->offset;
}
void KoShapeAnchor::setOffset(const QPointF &offset)
{
d->offset = offset;
}
void KoShapeAnchor::saveOdf(KoShapeSavingContext &context) const
{
// anchor-type
switch (d->anchorType) {
case AnchorToCharacter:
shape()->setAdditionalAttribute("text:anchor-type", "char");
break;
case AnchorAsCharacter:
shape()->setAdditionalAttribute("text:anchor-type", "as-char");
break;
case AnchorParagraph:
shape()->setAdditionalAttribute("text:anchor-type", "paragraph");
break;
case AnchorPage:
shape()->setAdditionalAttribute("text:anchor-type", "page");
break;
default:
break;
}
// vertical-pos
switch (d->verticalPos) {
case VBelow:
shape()->setAdditionalStyleAttribute("style:vertical-pos", "below");
break;
case VBottom:
shape()->setAdditionalStyleAttribute("style:vertical-pos", "bottom");
break;
case VFromTop:
shape()->setAdditionalStyleAttribute("style:vertical-pos", "from-top");
break;
case VMiddle:
shape()->setAdditionalStyleAttribute("style:vertical-pos", "middle");
break;
case VTop:
shape()->setAdditionalStyleAttribute("style:vertical-pos", "top");
break;
default:
break;
}
// vertical-rel
switch (d->verticalRel) {
case VBaseline:
shape()->setAdditionalStyleAttribute("style:vertical-rel", "baseline");
break;
case VChar:
shape()->setAdditionalStyleAttribute("style:vertical-rel", "char");
break;
case VFrame:
shape()->setAdditionalStyleAttribute("style:vertical-rel", "frame");
break;
case VFrameContent:
shape()->setAdditionalStyleAttribute("style:vertical-rel", "frame-content");
break;
case VLine:
shape()->setAdditionalStyleAttribute("style:vertical-rel", "line");
break;
case VPage:
shape()->setAdditionalStyleAttribute("style:vertical-rel", "page");
break;
case VPageContent:
shape()->setAdditionalStyleAttribute("style:vertical-rel", "page-content");
break;
case VParagraph:
shape()->setAdditionalStyleAttribute("style:vertical-rel", "paragraph");
break;
case VParagraphContent:
shape()->setAdditionalStyleAttribute("style:vertical-rel", "paragraph-content");
break;
case VText:
shape()->setAdditionalStyleAttribute("style:vertical-rel", "text");
break;
default:
break;
}
// horizontal-pos
switch (d->horizontalPos) {
case HCenter:
shape()->setAdditionalStyleAttribute("style:horizontal-pos", "center");
break;
case HFromInside:
shape()->setAdditionalStyleAttribute("style:horizontal-pos", "from-inside");
break;
case HFromLeft:
shape()->setAdditionalStyleAttribute("style:horizontal-pos", "from-left");
break;
case HInside:
shape()->setAdditionalStyleAttribute("style:horizontal-posl", "inside");
break;
case HLeft:
shape()->setAdditionalStyleAttribute("style:horizontal-pos", "left");
break;
case HOutside:
shape()->setAdditionalStyleAttribute("style:horizontal-pos", "outside");
break;
case HRight:
shape()->setAdditionalStyleAttribute("style:horizontal-pos", "right");
break;
default:
break;
}
// horizontal-rel
switch (d->horizontalRel) {
case HChar:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "char");
break;
case HPage:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page");
break;
case HPageContent:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-content");
break;
case HPageStartMargin:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-start-margin");
break;
case HPageEndMargin:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "page-end-margin");
break;
case HFrame:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame");
break;
case HFrameContent:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-content");
break;
case HFrameEndMargin:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-end-margin");
break;
case HFrameStartMargin:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "frame-start-margin");
break;
case HParagraph:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph");
break;
case HParagraphContent:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-content");
break;
case HParagraphEndMargin:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-end-margin");
break;
case HParagraphStartMargin:
shape()->setAdditionalStyleAttribute("style:horizontal-rel", "paragraph-start-margin");
break;
default:
break;
}
if (!d->wrapInfluenceOnPosition.isEmpty()) {
shape()->setAdditionalStyleAttribute("draw:wrap-influence-on-position", d->wrapInfluenceOnPosition);
}
if (d->flowWithText) {
shape()->setAdditionalStyleAttribute("style:flow-with-text", "true");
} else {
shape()->setAdditionalStyleAttribute("style:flow-with-text", "false");
}
if (shape()->parent()) {// an anchor may not yet have been layout-ed
- QTransform parentMatrix = shape()->parent()->absoluteTransformation(0).inverted();
- QTransform shapeMatrix = shape()->absoluteTransformation(0);
+ QTransform parentMatrix = shape()->parent()->absoluteTransformation().inverted();
+ QTransform shapeMatrix = shape()->absoluteTransformation();
qreal dx = d->offset.x() - shapeMatrix.dx()*parentMatrix.m11()
- shapeMatrix.dy()*parentMatrix.m21();
qreal dy = d->offset.y() - shapeMatrix.dx()*parentMatrix.m12()
- shapeMatrix.dy()*parentMatrix.m22();
context.addShapeOffset(shape(), QTransform(parentMatrix.m11(),parentMatrix.m12(),
parentMatrix.m21(),parentMatrix.m22(),
dx,dy));
}
shape()->saveOdf(context);
context.removeShapeOffset(shape());
}
bool KoShapeAnchor::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
d->offset = shape()->position();
QString anchorType = shape()->additionalAttribute("text:anchor-type");
if (anchorType == "char") {
d->anchorType = AnchorToCharacter;
} else if (anchorType == "as-char") {
d->anchorType = AnchorAsCharacter;
d->horizontalRel = HChar;
d->horizontalPos = HLeft;
} else if (anchorType == "paragraph") {
d->anchorType = AnchorParagraph;
} else {
d->anchorType = AnchorPage;
// it has different defaults at least LO thinks so - ODF doesn't define defaults for this
d->horizontalPos = HFromLeft;
d->verticalPos = VFromTop;
d->horizontalRel = HPage;
d->verticalRel = VPage;
}
if (anchorType == "page" && shape()->hasAdditionalAttribute("text:anchor-page-number")) {
d->pageNumber = shape()->additionalAttribute("text:anchor-page-number").toInt();
if (d->pageNumber <= 0) {
// invalid if the page-number is invalid (OO.org does the same)
- // see http://bugs.kde.org/show_bug.cgi?id=281869
+ // see https://bugs.kde.org/show_bug.cgi?id=281869
d->pageNumber = -1;
}
} else {
d->pageNumber = -1;
}
// always make it invisible or it will create empty rects on the first page
// during initial layout. This is because only when we layout it's final page is
// the shape moved away from page 1
// in KWRootAreaProvider of textlayout it's set back to visible
shape()->setVisible(false);
// load settings from graphic style
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.save();
if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) {
context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic");
styleStack.setTypeProperties("graphic");
}
QString verticalPos = styleStack.property(KoXmlNS::style, "vertical-pos");
QString verticalRel = styleStack.property(KoXmlNS::style, "vertical-rel");
QString horizontalPos = styleStack.property(KoXmlNS::style, "horizontal-pos");
QString horizontalRel = styleStack.property(KoXmlNS::style, "horizontal-rel");
d->wrapInfluenceOnPosition = styleStack.property(KoXmlNS::draw, "wrap-influence-on-position");
QString flowWithText = styleStack.property(KoXmlNS::style, "flow-with-text");
d->flowWithText = flowWithText.isEmpty() ? false : flowWithText == "true";
styleStack.restore();
// vertical-pos
if (verticalPos == "below") {//svg:y attribute is ignored
d->verticalPos = VBelow;
d->offset.setY(0);
} else if (verticalPos == "bottom") {//svg:y attribute is ignored
d->verticalPos = VBottom;
d->offset.setY(-shape()->size().height());
} else if (verticalPos == "from-top") {
d->verticalPos = VFromTop;
} else if (verticalPos == "middle") {//svg:y attribute is ignored
d->verticalPos = VMiddle;
d->offset.setY(-(shape()->size().height()/2));
} else if (verticalPos == "top") {//svg:y attribute is ignored
d->verticalPos = VTop;
d->offset.setY(0);
}
// vertical-rel
if (verticalRel == "baseline")
d->verticalRel = VBaseline;
else if (verticalRel == "char")
d->verticalRel = VChar;
else if (verticalRel == "frame")
d->verticalRel = VFrame;
else if (verticalRel == "frame-content")
d->verticalRel = VFrameContent;
else if (verticalRel == "line")
d->verticalRel = VLine;
else if (verticalRel == "page")
d->verticalRel = VPage;
else if (verticalRel == "page-content")
d->verticalRel = VPageContent;
else if (verticalRel == "paragraph")
d->verticalRel = VParagraph;
else if (verticalRel == "paragraph-content")
d->verticalRel = VParagraphContent;
else if (verticalRel == "text")
d->verticalRel = VText;
// horizontal-pos
if (horizontalPos == "center") {//svg:x attribute is ignored
d->horizontalPos = HCenter;
d->offset.setX(-(shape()->size().width()/2));
} else if (horizontalPos == "from-inside") {
d->horizontalPos = HFromInside;
} else if (horizontalPos == "from-left") {
d->horizontalPos = HFromLeft;
} else if (horizontalPos == "inside") {//svg:x attribute is ignored
d->horizontalPos = HInside;
d->offset.setX(0);
} else if (horizontalPos == "left") {//svg:x attribute is ignored
d->horizontalPos = HLeft;
d->offset.setX(0);
}else if (horizontalPos == "outside") {//svg:x attribute is ignored
d->horizontalPos = HOutside;
d->offset.setX(-shape()->size().width());
}else if (horizontalPos == "right") {//svg:x attribute is ignored
d->horizontalPos = HRight;
d->offset.setX(-shape()->size().width());
}
// horizontal-rel
if (horizontalRel == "char")
d->horizontalRel = HChar;
else if (horizontalRel == "page")
d->horizontalRel = HPage;
else if (horizontalRel == "page-content")
d->horizontalRel = HPageContent;
else if (horizontalRel == "page-start-margin")
d->horizontalRel = HPageStartMargin;
else if (horizontalRel == "page-end-margin")
d->horizontalRel = HPageEndMargin;
else if (horizontalRel == "frame")
d->horizontalRel = HFrame;
else if (horizontalRel == "frame-content")
d->horizontalRel = HFrameContent;
else if (horizontalRel == "frame-end-margin")
d->horizontalRel = HFrameEndMargin;
else if (horizontalRel == "frame-start-margin")
d->horizontalRel = HFrameStartMargin;
else if (horizontalRel == "paragraph")
d->horizontalRel = HParagraph;
else if (horizontalRel == "paragraph-content")
d->horizontalRel = HParagraphContent;
else if (horizontalRel == "paragraph-end-margin")
d->horizontalRel = HParagraphEndMargin;
else if (horizontalRel == "paragraph-start-margin")
d->horizontalRel = HParagraphStartMargin;
// if svg:x or svg:y should be ignored set new position
shape()->setPosition(d->offset);
return true;
}
void KoShapeAnchor::setAnchorType(KoShapeAnchor::AnchorType type)
{
d->anchorType = type;
if (type == AnchorAsCharacter) {
d->horizontalRel = HChar;
d->horizontalPos = HLeft;
}
}
KoShapeAnchor::TextLocation *KoShapeAnchor::textLocation() const
{
return d->textLocation;
}
void KoShapeAnchor::setTextLocation(TextLocation *textLocation)
{
d->textLocation = textLocation;
}
KoShapeAnchor::PlacementStrategy *KoShapeAnchor::placementStrategy() const
{
return d->placementStrategy;
}
void KoShapeAnchor::setPlacementStrategy(PlacementStrategy *placementStrategy)
{
if (placementStrategy != d->placementStrategy) {
delete d->placementStrategy;
d->placementStrategy = placementStrategy;
}
}
diff --git a/libs/flake/KoShapeBackground.h b/libs/flake/KoShapeBackground.h
index ed5aa2afc2..51f2d762c1 100644
--- a/libs/flake/KoShapeBackground.h
+++ b/libs/flake/KoShapeBackground.h
@@ -1,69 +1,68 @@
/* This file is part of the KDE project
* Copyright (C) 2008 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.
*/
#ifndef KOSHAPEBACKGROUND_H
#define KOSHAPEBACKGROUND_H
#include "kritaflake_export.h"
#include <QtGlobal>
class QSizeF;
class QPainter;
class QPainterPath;
class KoGenStyle;
class KoShapeSavingContext;
class KoOdfLoadingContext;
class KoShapePaintingContext;
-class KoViewConverter;
/**
* This is the base class for shape backgrounds.
* Derived classes are used to paint the background of
* a shape within a given painter path.
*/
class KRITAFLAKE_EXPORT KoShapeBackground
{
public:
KoShapeBackground();
virtual ~KoShapeBackground();
/// Paints the background using the given fill path
- virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const = 0;
+ virtual void paint(QPainter &painter, KoShapePaintingContext &context, const QPainterPath &fillPath) const = 0;
/// Returns if the background has some transparency.
virtual bool hasTransparency() const;
virtual bool compareTo(const KoShapeBackground *other) const = 0;
/**
* Fills the style object
* @param style object
* @param context used for saving
*/
virtual void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) = 0;
/// load background from odf styles
virtual bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) = 0;
virtual explicit operator bool() const { return true; }
};
#endif // KOSHAPEBACKGROUND_H
diff --git a/libs/flake/KoShapeContainer.cpp b/libs/flake/KoShapeContainer.cpp
index d00a82171d..a3ca04bda4 100644
--- a/libs/flake/KoShapeContainer.cpp
+++ b/libs/flake/KoShapeContainer.cpp
@@ -1,231 +1,230 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
* 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 "KoShapeContainer.h"
#include "KoShapeContainer_p.h"
#include "KoShapeContainerModel.h"
#include "KoShapeStrokeModel.h"
#include "SimpleShapeContainerModel.h"
#include "KoShapeSavingContext.h"
-#include "KoViewConverter.h"
#include <QPointF>
#include <QPainter>
#include <QPainterPath>
#include "kis_painting_tweaks.h"
#include "kis_assert.h"
KoShapeContainer::Private::Private(KoShapeContainer *q)
: shapeInterface(q)
, model(0)
{
}
KoShapeContainer::Private::~Private()
{
delete model;
}
KoShapeContainer::Private::Private(const KoShapeContainer::Private &rhs, KoShapeContainer *q)
: shapeInterface(q)
, model(0)
{
Q_UNUSED(rhs);
}
KoShapeContainer::KoShapeContainer(KoShapeContainerModel *model)
: KoShape()
, d(new Private(this))
{
d->model = model;
}
KoShapeContainer::KoShapeContainer(const KoShapeContainer &rhs)
: KoShape(rhs)
, d(new Private(*(rhs.d.data()), this))
{
}
KoShapeContainer::~KoShapeContainer()
{
if (d->model) {
d->model->deleteOwnedShapes();
}
}
void KoShapeContainer::addShape(KoShape *shape)
{
shape->setParent(this);
}
void KoShapeContainer::removeShape(KoShape *shape)
{
shape->setParent(0);
}
int KoShapeContainer::shapeCount() const
{
if (d->model == 0)
return 0;
return d->model->count();
}
void KoShapeContainer::setClipped(const KoShape *child, bool clipping)
{
if (d->model == 0)
return;
d->model->setClipped(child, clipping);
}
void KoShapeContainer::setInheritsTransform(const KoShape *shape, bool inherit)
{
if (d->model == 0)
return;
d->model->setInheritsTransform(shape, inherit);
}
bool KoShapeContainer::inheritsTransform(const KoShape *shape) const
{
if (d->model == 0)
return false;
return d->model->inheritsTransform(shape);
}
-void KoShapeContainer::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext)
+void KoShapeContainer::paint(QPainter &painter, KoShapePaintingContext &paintcontext) const
{
// Shape container paints only its internal component part. All the children are rendered
// by the shape manager itself
painter.save();
- paintComponent(painter, converter, paintcontext);
+ paintComponent(painter, paintcontext);
painter.restore();
}
void KoShapeContainer::shapeChanged(ChangeType type, KoShape* shape)
{
Q_UNUSED(shape);
if (d->model == 0)
return;
if (!(type == RotationChanged || type == ScaleChanged || type == ShearChanged
|| type == SizeChanged || type == PositionChanged || type == GenericMatrixChange))
return;
d->model->containerChanged(this, type);
Q_FOREACH (KoShape *shape, d->model->shapes())
shape->notifyChanged();
}
bool KoShapeContainer::isClipped(const KoShape *child) const
{
if (d->model == 0) // throw exception??
return false;
return d->model->isClipped(child);
}
void KoShapeContainer::update() const
{
KoShape::update();
if (d->model)
Q_FOREACH (KoShape *shape, d->model->shapes())
shape->update();
}
QList<KoShape*> KoShapeContainer::shapes() const
{
if (d->model == 0)
return QList<KoShape*>();
return d->model->shapes();
}
KoShapeContainerModel *KoShapeContainer::model() const
{
return d->model;
}
void KoShapeContainer::setModel(KoShapeContainerModel *model)
{
d->model = model;
}
void KoShapeContainer::setModelInit(KoShapeContainerModel *model)
{
setModel(model);
// HACK ALERT: the shapes are copied inside the model,
// but we still need to connect the to the
// hierarchy here!
if (d->model) {
Q_FOREACH (KoShape *shape, d->model->shapes()) {
if (shape) { // Note: shape can be 0 because not all shapes
// implement cloneShape, e.g. the text shape.
shape->setParent(this);
}
}
}
}
KoShapeContainer::ShapeInterface *KoShapeContainer::shapeInterface()
{
return &d->shapeInterface;
}
KoShapeContainer::ShapeInterface::ShapeInterface(KoShapeContainer *_q)
: q(_q)
{
}
void KoShapeContainer::ShapeInterface::addShape(KoShape *shape)
{
KoShapeContainer::Private * const d = q->d.data();
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
if (shape->parent() == q && q->shapes().contains(shape)) {
return;
}
// TODO add a method to create a default model depending on the shape container
if (!d->model) {
d->model = new SimpleShapeContainerModel();
}
if (shape->parent() && shape->parent() != q) {
shape->parent()->shapeInterface()->removeShape(shape);
}
d->model->add(shape);
d->model->shapeHasBeenAddedToHierarchy(shape, q);
}
void KoShapeContainer::ShapeInterface::removeShape(KoShape *shape)
{
KoShapeContainer::Private * const d = q->d.data();
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
KIS_SAFE_ASSERT_RECOVER_RETURN(d->model);
KIS_SAFE_ASSERT_RECOVER_RETURN(d->model->shapes().contains(shape));
d->model->shapeToBeRemovedFromHierarchy(shape, q);
d->model->remove(shape);
KoShapeContainer *grandparent = q->parent();
if (grandparent) {
grandparent->model()->childChanged(q, KoShape::ChildChanged);
}
}
diff --git a/libs/flake/KoShapeContainer.h b/libs/flake/KoShapeContainer.h
index 3e33f41e1f..d99b471390 100644
--- a/libs/flake/KoShapeContainer.h
+++ b/libs/flake/KoShapeContainer.h
@@ -1,264 +1,262 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2010 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 KOSHAPECONTAINER_H
#define KOSHAPECONTAINER_H
#include "KoShape.h"
#include <QList>
#include "kritaflake_export.h"
class QPainter;
class KoShapeContainerModel;
class KoShapeContainerPrivate;
-class KoViewConverter;
/**
* This is the base class that all Flake group-shapes are based on.
* Extending from this class allows you to have child-shapes.
* Like the KoShape class, this shape is a visible class with
* a position and a size. It can paint itself as well if you implement
* the paintComponent() method.
*
* <p>The most important feature of this class is that you can make
* other KoShape classes to be children of this container.
*
* <p>The effect of grouping those shapes is that their position
* is relative to the position of the container. Move the container and
* all children move with it.
*
* <p>Each child can optionally be said to be 'clipped' by the container.
* This feature will give the effect that if the child has a size and
* position outside the container, parts outside the container will not be shown.
* This is especially useful
* for showing cutouts of content, like images, without changing the actual content.
*
* <p>For so called clipped children any modification made to the container is
* propagated to the child. This includes rotation as well as scaling
* and shearing.
*
* <p>Maintaining the list of children can be done using the supplied methods
* addChild() and removeChild(). However, they only forward their requests to the
* data model KoShapeContainerModel and if you provide a custom implementation
* of that model any means can be used to maintain a list of children, as long as
* you will take care to register them with the appropriate shape manager.
*
* <p>An example usage where a custom model might be useful is when you have a
* container for text areas which are split into columns. If you resize the container
* and the width of the individual columns gets too small, the model can choose to
* remove a child or add one when the width allows another column.
*/
class KRITAFLAKE_EXPORT KoShapeContainer : public KoShape
{
public:
/**
* Constructor with custom model to be used for maintaining the list of children.
* For all the normal cases you don't need a custom model. Only when you want to respond
* to moves of the container to do something special, or disable one of the features the
* container normally has (like clipping). Use the default constructor in those cases.
* @param model the custom model to be used for maintaining the list of children.
*/
explicit KoShapeContainer(KoShapeContainerModel *model = 0);
/**
* Destructor for the shape container.
* All children will be orphaned by calling a KoShape::setParent(0)
*/
~KoShapeContainer() override;
/**
* Add a child to this container.
*
* This container will NOT take over ownership of the shape. The caller or those creating
* the shape is responsible to delete it if not needed any longer.
*
* @param shape the child to be managed in the container.
*/
void addShape(KoShape *shape);
/**
* Remove a child to be completely separated from the container.
*
* The shape will only be removed from this container but not be deleted.
*
* @param shape the child to be removed.
*/
void removeShape(KoShape *shape);
/**
* Return the current number of children registered.
* @return the current number of children registered.
*/
int shapeCount() const;
/**
* Set the argument child to have its 'clipping' property set.
*
* A shape that is clipped by the container will have its visible portion
* limited to the area where it intersects with the container.
* If a shape is positioned or sized such that it would be painted outside
* of the KoShape::outline() of its parent container, setting this property
* to true will clip the shape painting to the container outline.
*
* @param child the child for which the property will be changed.
* @param clipping the property
*/
void setClipped(const KoShape *child, bool clipping);
/**
* Returns if the argument child has its 'clipping' property set.
*
* A shape that is clipped by the container will have its visible portion
* limited to the area where it intersects with the container.
* If a shape is positioned or sized such that it would be painted outside
* of the KoShape::outline() of its parent container, setting this property
* to true will clip the shape painting to the container outline.
*
* @return if the argument child has its 'clipping' property set.
* @param child the child for which the property will be returned.
*/
bool isClipped(const KoShape *child) const;
/**
* Set the shape to inherit the container transform.
*
* A shape that inherits the transform of the parent container will have its
* share / rotation / skew etc be calculated as being the product of both its
* own local transformation and also that of its parent container.
* If you set this to true and rotate the container, the shape will get that
* rotation as well automatically.
*
* @param shape the shape for which the property will be changed.
* @param inherit the new value
*/
void setInheritsTransform(const KoShape *shape, bool inherit);
/**
* Returns if the shape inherits the container transform.
*
* A shape that inherits the transform of the parent container will have its
* share / rotation / skew etc be calculated as being the product of both its
* own local transformation and also that of its parent container.
* If you set this to true and rotate the container, the shape will get that
* rotation as well automatically.
*
* @return if the argument shape has its 'inherits transform' property set.
* @param shape the shape for which the property will be returned.
*/
bool inheritsTransform(const KoShape *shape) const;
/// reimplemented
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
+ void paint(QPainter &painter, KoShapePaintingContext &paintcontext) const override;
/**
* @brief Paint the component
* Implement this method to allow the shape to paint itself, just like the KoShape::paint()
* method does.
*
* @param painter used for painting the shape
- * @param converter to convert between internal and view coordinates.
* @param paintcontext the painting context
* @see applyConversion()
*/
- virtual void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) = 0;
+ virtual void paintComponent(QPainter &painter, KoShapePaintingContext &paintcontext) const = 0;
using KoShape::update;
/// reimplemented
void update() const override;
/**
* Return the list of all child shapes.
* @return the list of all child shapes
*/
QList<KoShape*> shapes() const;
/**
* return the model for this container
*/
KoShapeContainerModel *model() const;
protected:
/**
* set the model for this container
*/
void setModel(KoShapeContainerModel *model);
/**
* set the model, and take control of all its children
*/
void setModelInit(KoShapeContainerModel *model);
public:
/**
* A special interface for KoShape to use during setParent call. Don't use
* these method directly for managing shapes hierarchy! Use shape->setParent()
* instead.
*/
struct ShapeInterface {
ShapeInterface(KoShapeContainer *_q);
/**
* Add a child to this container.
*
* This container will NOT take over ownership of the shape. The caller or those creating
* the shape is responsible to delete it if not needed any longer.
*
* @param shape the child to be managed in the container.
*/
void addShape(KoShape *shape);
/**
* Remove a child to be completely separated from the container.
*
* The shape will only be removed from this container but not be deleted.
*
* @param shape the child to be removed.
*/
void removeShape(KoShape *shape);
protected:
KoShapeContainer *q;
};
ShapeInterface* shapeInterface();
protected:
KoShapeContainer(const KoShapeContainer &rhs);
/**
* This hook is for inheriting classes that need to do something on adding/removing
* of children.
* This method will be called just after the child has been added/removed.
* The default implementation is empty.
*/
virtual void shapeCountChanged() { }
void shapeChanged(ChangeType type, KoShape *shape = 0) override;
private:
class Private;
QScopedPointer<Private> d;
};
#endif
diff --git a/libs/flake/KoShapeContainerModel.cpp b/libs/flake/KoShapeContainerModel.cpp
index d400c84d15..7eb1a08831 100644
--- a/libs/flake/KoShapeContainerModel.cpp
+++ b/libs/flake/KoShapeContainerModel.cpp
@@ -1,85 +1,83 @@
/* This file is part of the KDE project
* Copyright (C) 2009 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 "KoShapeContainerModel.h"
#include "KoShapeContainer.h"
#include "kis_assert.h"
KoShapeContainerModel::KoShapeContainerModel()
{
}
KoShapeContainerModel::~KoShapeContainerModel()
{
}
void KoShapeContainerModel::deleteOwnedShapes()
{
QList<KoShape*> ownedShapes = this->shapes();
Q_FOREACH (KoShape *shape, ownedShapes) {
shape->setParent(0);
delete shape;
}
KIS_SAFE_ASSERT_RECOVER_NOOP(!this->count());
}
void KoShapeContainerModel::proposeMove(KoShape *child, QPointF &move)
{
Q_UNUSED(child);
Q_UNUSED(move);
}
void KoShapeContainerModel::childChanged(KoShape *child, KoShape::ChangeType type)
{
Q_UNUSED(type);
- if (type != KoShape::CollisionDetected) {
- KoShapeContainer * parent = child->parent();
- Q_ASSERT(parent);
- // propagate the change up the hierarchy
- KoShapeContainer * grandparent = parent->parent();
- if (grandparent) {
- grandparent->model()->childChanged(parent, KoShape::ChildChanged);
- }
+ KoShapeContainer * parent = child->parent();
+ Q_ASSERT(parent);
+ // propagate the change up the hierarchy
+ KoShapeContainer * grandparent = parent->parent();
+ if (grandparent) {
+ grandparent->model()->childChanged(parent, KoShape::ChildChanged);
}
}
void KoShapeContainerModel::shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree)
{
KoShapeContainer *parent = addedToSubtree->parent();
if (parent) {
parent->model()->shapeHasBeenAddedToHierarchy(shape, parent);
}
}
void KoShapeContainerModel::shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree)
{
KoShapeContainer *parent = removedFromSubtree->parent();
if (parent) {
parent->model()->shapeToBeRemovedFromHierarchy(shape, parent);
}
}
KoShapeContainerModel::KoShapeContainerModel(const KoShapeContainerModel &rhs)
{
Q_UNUSED(rhs);
}
diff --git a/libs/flake/KoShapeGroup.cpp b/libs/flake/KoShapeGroup.cpp
index 2600f49203..28ee45da2a 100644
--- a/libs/flake/KoShapeGroup.cpp
+++ b/libs/flake/KoShapeGroup.cpp
@@ -1,276 +1,276 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* 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 "KoShapeGroup.h"
#include "KoShapeContainerModel.h"
#include "KoShapeContainer_p.h"
#include "KoShapeLayer.h"
#include "SimpleShapeContainerModel.h"
#include "KoShapeSavingContext.h"
#include "KoShapeLoadingContext.h"
#include "KoXmlWriter.h"
#include "KoXmlReader.h"
#include "KoShapeRegistry.h"
#include "KoShapeStrokeModel.h"
#include "KoShapeShadow.h"
#include "KoInsets.h"
#include <FlakeDebug.h>
#include <QPainter>
class ShapeGroupContainerModel : public SimpleShapeContainerModel
{
public:
ShapeGroupContainerModel(KoShapeGroup *group) : m_group(group) {}
~ShapeGroupContainerModel() override {}
ShapeGroupContainerModel(const ShapeGroupContainerModel &rhs, KoShapeGroup *group)
: SimpleShapeContainerModel(rhs),
m_group(group)
{
}
void add(KoShape *child) override
{
SimpleShapeContainerModel::add(child);
m_group->invalidateSizeCache();
}
void remove(KoShape *child) override
{
SimpleShapeContainerModel::remove(child);
m_group->invalidateSizeCache();
}
void childChanged(KoShape *shape, KoShape::ChangeType type) override
{
SimpleShapeContainerModel::childChanged(shape, type);
//debugFlake << type;
switch (type) {
case KoShape::PositionChanged:
case KoShape::RotationChanged:
case KoShape::ScaleChanged:
case KoShape::ShearChanged:
case KoShape::SizeChanged:
case KoShape::GenericMatrixChange:
case KoShape::ParameterChanged:
case KoShape::ClipPathChanged :
+ case KoShape::ClipMaskChanged :
m_group->invalidateSizeCache();
break;
default:
break;
}
}
private: // members
KoShapeGroup * m_group;
};
class KoShapeGroup::Private
{
public:
Private() {}
Private(const Private &) {}
virtual ~Private() = default;
mutable QRectF savedOutlineRect;
mutable bool sizeCached = false;
};
KoShapeGroup::KoShapeGroup()
: KoShapeContainer()
, d(new Private)
{
setModelInit(new ShapeGroupContainerModel(this));
}
KoShapeGroup::KoShapeGroup(const KoShapeGroup &rhs)
: KoShapeContainer(rhs)
, d(new Private(*rhs.d))
{
ShapeGroupContainerModel *otherModel = dynamic_cast<ShapeGroupContainerModel*>(rhs.model());
KIS_ASSERT_RECOVER_RETURN(otherModel);
setModelInit(new ShapeGroupContainerModel(*otherModel, this));
}
KoShapeGroup::~KoShapeGroup()
{
/**
* HACK alert: model will use KoShapeGroup::invalidateSizeCache(), which uses
* KoShapeGroup's d-pointer. We have to manually remove child shapes from the
* model in the destructor of KoShapeGroup as the instance d is no longer accessible
* since ~KoShapeGroup() is executed
*/
model()->deleteOwnedShapes();
}
KoShape *KoShapeGroup::cloneShape() const
{
return new KoShapeGroup(*this);
}
-void KoShapeGroup::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &)
+void KoShapeGroup::paintComponent(QPainter &painter, KoShapePaintingContext &) const
{
Q_UNUSED(painter);
- Q_UNUSED(converter);
}
bool KoShapeGroup::hitTest(const QPointF &position) const
{
Q_UNUSED(position);
return false;
}
void KoShapeGroup::tryUpdateCachedSize() const
{
if (!d->sizeCached) {
QRectF bound;
Q_FOREACH (KoShape *shape, shapes()) {
bound |= shape->transformation().mapRect(shape->outlineRect());
}
d->savedOutlineRect = bound;
KoShape::setSizeImpl(bound.size());
d->sizeCached = true;
}
}
QSizeF KoShapeGroup::size() const
{
tryUpdateCachedSize();
return KoShape::size();
}
void KoShapeGroup::setSize(const QSizeF &size)
{
QSizeF oldSize = this->size();
if (!shapeCount() || oldSize.isNull()) return;
const QTransform scale =
QTransform::fromScale(size.width() / oldSize.width(), size.height() / oldSize.height());
setTransformation(scale * transformation());
KoShapeContainer::setSize(size);
}
QRectF KoShapeGroup::outlineRect() const
{
tryUpdateCachedSize();
return d->savedOutlineRect;
}
QRectF KoShapeGroup::boundingRect() const
{
QRectF groupBound = KoShape::boundingRect(shapes());
if (shadow()) {
KoInsets insets;
shadow()->insets(insets);
groupBound.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
return groupBound;
}
void KoShapeGroup::saveOdf(KoShapeSavingContext & context) const
{
context.xmlWriter().startElement("draw:g");
saveOdfAttributes(context, (OdfMandatories ^ (OdfLayer | OdfZIndex)) | OdfAdditionalAttributes);
context.xmlWriter().addAttribute("svg:y", position().y());
QList<KoShape*> shapes = this->shapes();
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
Q_FOREACH (KoShape* shape, shapes) {
shape->saveOdf(context);
}
saveOdfCommonChildElements(context);
context.xmlWriter().endElement();
}
bool KoShapeGroup::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context)
{
loadOdfAttributes(element, context, OdfMandatories | OdfStyle | OdfAdditionalAttributes | OdfCommonChildElements);
KoXmlElement child;
QMap<KoShapeLayer*, int> usedLayers;
forEachElement(child, element) {
KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context);
if (shape) {
KoShapeLayer *layer = dynamic_cast<KoShapeLayer*>(shape->parent());
if (layer) {
usedLayers[layer]++;
}
addShape(shape);
}
}
KoShapeLayer *parent = 0;
int maxUseCount = 0;
// find most used layer and use this as parent for the group
for (QMap<KoShapeLayer*, int>::const_iterator it(usedLayers.constBegin()); it != usedLayers.constEnd(); ++it) {
if (it.value() > maxUseCount) {
maxUseCount = it.value();
parent = it.key();
}
}
setParent(parent);
QRectF bound;
bool boundInitialized = false;
Q_FOREACH (KoShape * shape, shapes()) {
if (! boundInitialized) {
bound = shape->boundingRect();
boundInitialized = true;
} else
bound = bound.united(shape->boundingRect());
}
setSize(bound.size());
d->sizeCached = true;
setPosition(bound.topLeft());
Q_FOREACH (KoShape * shape, shapes())
shape->setAbsolutePosition(shape->absolutePosition() - bound.topLeft());
return true;
}
void KoShapeGroup::shapeChanged(ChangeType type, KoShape *shape)
{
Q_UNUSED(shape);
KoShapeContainer::shapeChanged(type, shape);
switch (type) {
case KoShape::StrokeChanged:
break;
default:
break;
}
invalidateSizeCache();
}
void KoShapeGroup::invalidateSizeCache()
{
d->sizeCached = false;
}
diff --git a/libs/flake/KoShapeGroup.h b/libs/flake/KoShapeGroup.h
index 0ddf7e9a06..3da60e2e43 100644
--- a/libs/flake/KoShapeGroup.h
+++ b/libs/flake/KoShapeGroup.h
@@ -1,96 +1,96 @@
/* This file is part of the KDE project
* Copyright (C) 2006 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 KOSHAPEGROUP_H
#define KOSHAPEGROUP_H
#include "KoShapeContainer.h"
#include <QList>
#include "kritaflake_export.h"
class KoShapeSavingContext;
class KoShapeLoadingContext;
class KoShapeGroupPrivate;
/**
* Provide grouping for shapes.
* The group shape allows you to add children which will then be grouped in selections
* and actions.
* <p>If you have a set of shapes that together make up a bigger shape it is often
* useful to group them together so the user will perceive the different shapes as
* actually being one. This means that if the user clicks on one shape, all shapes
* in the group will be selected at once, making the tools that works on
* selections alter all of them at the same time.
*
* <p>Note that while this object is also a shape, it is not actually visible and the user
* can't interact with it.
*
* <p>WARNING: this class is NOT threadsafe, it caches the size in an unsafe way
*/
class KRITAFLAKE_EXPORT KoShapeGroup : public KoShapeContainer
{
public:
/// Constructor
KoShapeGroup();
/// destructor
~KoShapeGroup() override;
KoShape* cloneShape() const override;
/// This implementation is empty since a group is itself not visible.
- void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
+ void paintComponent(QPainter &painter, KoShapePaintingContext &paintcontext) const override;
/// always returns false since the group itself can't be selected or hit
bool hitTest(const QPointF &position) const override;
QSizeF size() const override;
void setSize(const QSizeF &size) override;
QRectF outlineRect() const override;
/// a group's boundingRect
QRectF boundingRect() const override;
/// reimplemented from KoShape
void saveOdf(KoShapeSavingContext &context) const override;
// reimplemented
bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override;
private:
friend class ShapeGroupContainerModel;
/**
* @brief Invalidate the size cache of the group
*
* The group shape caches the size of itself as it can be quite expensive to recalculate
* the size if there are a lot of subshapes. This function is called when the cache needs
* to be invalidated.
*/
void invalidateSizeCache();
private:
KoShapeGroup(const KoShapeGroup &rhs);
private:
void tryUpdateCachedSize() const;
void shapeChanged(ChangeType type, KoShape *shape = 0) override;
class Private;
QScopedPointer<Private> d;
};
#endif
diff --git a/libs/flake/KoShapeLayer.cpp b/libs/flake/KoShapeLayer.cpp
index 181a347adc..1a11b5f6f4 100644
--- a/libs/flake/KoShapeLayer.cpp
+++ b/libs/flake/KoShapeLayer.cpp
@@ -1,81 +1,81 @@
/* This file is part of the KDE project
Copyright (C) 2006-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 "KoShapeLayer.h"
#include <QRectF>
#include "SimpleShapeContainerModel.h"
#include "KoShapeSavingContext.h"
#include "KoShapeLoadingContext.h"
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoXmlNS.h>
KoShapeLayer::KoShapeLayer()
: KoShapeContainer(new SimpleShapeContainerModel())
{
setSelectable(false);
}
KoShapeLayer::KoShapeLayer(KoShapeContainerModel *model)
: KoShapeContainer(model)
{
setSelectable(false);
}
bool KoShapeLayer::hitTest(const QPointF &position) const
{
Q_UNUSED(position);
return false;
}
QRectF KoShapeLayer::boundingRect() const
{
return KoShape::boundingRect(shapes());
}
void KoShapeLayer::saveOdf(KoShapeSavingContext & context) const
{
QList<KoShape*> shapes = this->shapes();
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
Q_FOREACH (KoShape* shape, shapes) {
shape->saveOdf(context);
}
}
bool KoShapeLayer::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context)
{
// set layer name
setName(element.attributeNS(KoXmlNS::draw, "name"));
// layer locking
setGeometryProtected(element.attributeNS(KoXmlNS::draw, "protected", "false") == "true");
// layer visibility
setVisible(element.attributeNS(KoXmlNS::draw, "display", "false") != "none");
// add layer by name into shape context
context.addLayer(this, name());
return true;
}
-void KoShapeLayer::paintComponent(QPainter &, const KoViewConverter &, KoShapePaintingContext &)
+void KoShapeLayer::paintComponent(QPainter &, KoShapePaintingContext &) const
{
}
diff --git a/libs/flake/KoShapeLayer.h b/libs/flake/KoShapeLayer.h
index 8ade3662e0..a9208a6143 100644
--- a/libs/flake/KoShapeLayer.h
+++ b/libs/flake/KoShapeLayer.h
@@ -1,54 +1,54 @@
/* This file is part of the KDE project
Copyright (C) 2006-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.
*/
#ifndef __KOSHAPELAYER_H__
#define __KOSHAPELAYER_H__
#include "KoShapeContainer.h"
#include "kritaflake_export.h"
/**
* Provides arranging shapes into layers.
* This makes it possible to have a higher key of a number of objects
* in a document.
* A layer is always invisible and unselectable.
*/
class KRITAFLAKE_EXPORT KoShapeLayer : public KoShapeContainer
{
public:
/// The default constructor
KoShapeLayer();
/**
* Constructor with custom model
* @param model the custom modem
*/
explicit KoShapeLayer(KoShapeContainerModel *model);
/**
* Empty implementation, as the layer itself is not visible
*/
- void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
+ void paintComponent(QPainter &painter, KoShapePaintingContext &paintcontext) const override;
bool hitTest(const QPointF &position) const override;
QRectF boundingRect() const override;
void saveOdf(KoShapeSavingContext & context) const override;
bool loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) override;
};
#endif // __KOSHAPELAYER_H__
diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp
index b7abf6c89e..492dfbd24d 100644
--- a/libs/flake/KoShapeManager.cpp
+++ b/libs/flake/KoShapeManager.cpp
@@ -1,741 +1,765 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
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 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 "KoShapeManager.h"
#include "KoShapeManager_p.h"
#include "KoSelection.h"
#include "KoToolManager.h"
#include "KoPointerEvent.h"
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoCanvasBase.h"
#include "KoShapeContainer.h"
#include "KoShapeStrokeModel.h"
#include "KoShapeGroup.h"
#include "KoToolProxy.h"
#include "KoShapeShadow.h"
#include "KoShapeLayer.h"
#include "KoFilterEffect.h"
#include "KoFilterEffectStack.h"
#include "KoFilterEffectRenderContext.h"
#include "KoShapeBackground.h"
#include <KoRTree.h>
#include "KoClipPath.h"
#include "KoClipMaskPainter.h"
#include "KoShapePaintingContext.h"
#include "KoViewConverter.h"
#include "KisQPainterStateSaver.h"
#include "KoSvgTextChunkShape.h"
#include "KoSvgTextShape.h"
#include <QApplication>
#include <QPainter>
#include <QTimer>
#include <FlakeDebug.h>
#include "kis_painting_tweaks.h"
+#include "kis_debug.h"
+#include "KisForest.h"
+#include <unordered_set>
-bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape)
+
+namespace {
+
+/**
+ * Returns whether the shape should be added to the RTree for collision and ROI
+ * detection.
+ */
+inline bool shapeUsedInRenderingTree(KoShape *shape)
{
// FIXME: make more general!
return !dynamic_cast<KoShapeGroup*>(shape) &&
!dynamic_cast<KoShapeLayer*>(shape) &&
!(dynamic_cast<KoSvgTextChunkShape*>(shape) && !dynamic_cast<KoSvgTextShape*>(shape));
}
+/**
+ * Returns whether a shape should be added to the rendering tree because of
+ * its clip mask/path or effects.
+ */
+inline bool shapeHasGroupEffects(KoShape *shape) {
+ return shape->clipPath() ||
+ (shape->filterEffectStack() && !shape->filterEffectStack()->isEmpty()) ||
+ shape->clipMask();
+}
+
+/**
+ * Returns true if the shape is not fully transparent
+ */
+inline bool shapeIsVisible(KoShape *shape) {
+ return shape->isVisible(false) && shape->transparency() < 1.0;
+}
+
+
+/**
+ * Populate \p tree with the subtree of shapes pointed by a shape \p parentShape.
+ * All new shapes are added as children of \p parentIt. Please take it into account
+ * that \c *parentIt might be not the same as \c parentShape, because \c parentShape
+ * may be hidden from rendering.
+ */
+void populateRenderSubtree(KoShape *parentShape,
+ KisForest<KoShape*>::child_iterator parentIt,
+ KisForest<KoShape*> &tree,
+ std::function<bool(KoShape*)> shouldIncludeNode,
+ std::function<bool(KoShape*)> shouldEnterSubtree)
+{
+ KoShapeContainer *parentContainer = dynamic_cast<KoShapeContainer*>(parentShape);
+ if (!parentContainer) return;
+
+ QList<KoShape*> children = parentContainer->shapes();
+ std::sort(children.begin(), children.end(), KoShape::compareShapeZIndex);
+
+ for (auto it = children.constBegin(); it != children.constEnd(); ++it) {
+ auto newParentIt = parentIt;
+
+ if (shouldIncludeNode(*it)) {
+ newParentIt = tree.insert(childEnd(parentIt), *it);
+ }
+
+ if (shouldEnterSubtree(*it)) {
+ populateRenderSubtree(*it, newParentIt, tree, shouldIncludeNode, shouldEnterSubtree);
+ }
+ }
+
+}
+
+/**
+ * Build a rendering tree for **leaf** nodes defined by \p leafNodes
+ *
+ * Sometimes we should render only a part of the layer (e.g. when we render
+ * in patches). So we shouldn't render the whole graph. The problem is that
+ * some of the shapes may have parents with clip paths/masks and/or effects.
+ * In such a case, these parents should be also included into the rendering
+ * process.
+ *
+ * \c buildRenderTree() builds a graph for such rendering. It includes the
+ * leaf shapes themselves, and all parent shapes that have some effects affecting
+ * these shapes.
+ */
+void buildRenderTree(QList<KoShape*> leafShapes,
+ KisForest<KoShape*> &tree)
+{
+ QList<KoShape*> sortedShapes = leafShapes;
+ std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
+
+ std::unordered_set<KoShape*> includedShapes;
+
+ Q_FOREACH (KoShape *shape, sortedShapes) {
+ bool shouldSkipShape = !shapeIsVisible(shape);
+ if (shouldSkipShape) continue;
+
+ bool shapeIsPartOfIncludedSubtree = false;
+ QVector<KoShape*> hierarchy = {shape};
+
+ while ((shape = shape->parent())) {
+ if (!shapeIsVisible(shape)) {
+ shouldSkipShape = true;
+ break;
+ }
+
+ if (includedShapes.find(shape) != end(includedShapes)) {
+ shapeIsPartOfIncludedSubtree = true;
+ break;
+ }
+
+ if (shapeHasGroupEffects(shape)) {
+ hierarchy << shape;
+ }
+ }
+
+ if (shouldSkipShape) continue;
+
+ if (!shapeIsPartOfIncludedSubtree &&
+ includedShapes.find(hierarchy.last()) == end(includedShapes)) {
+
+ tree.insert(childEnd(tree), hierarchy.last());
+ }
+ std::copy(hierarchy.begin(), hierarchy.end(),
+ std::inserter(includedShapes, end(includedShapes)));
+ }
+
+ auto shouldIncludeShape =
+ [includedShapes] (KoShape *shape) {
+ // included shapes are guaranteed to be visible
+ return includedShapes.find(shape) != end(includedShapes);
+ };
+
+ for (auto it = childBegin(tree); it != childEnd(tree); ++it) {
+ populateRenderSubtree(*it, it, tree, shouldIncludeShape, &shapeIsVisible);
+ }
+}
+
+/**
+ * Render the prebuilt rendering tree on \p painter
+ */
+void renderShapes(typename KisForest<KoShape*>::child_iterator beginIt,
+ typename KisForest<KoShape*>::child_iterator endIt,
+ QPainter &painter,
+ KoShapePaintingContext &paintContext)
+{
+ for (auto it = beginIt; it != endIt; ++it) {
+ KoShape *shape = *it;
+
+ KisQPainterStateSaver saver(&painter);
+
+ if (!isEnd(parent(it))) {
+ painter.setTransform(shape->transformation() * painter.transform());
+ } else {
+ painter.setTransform(shape->absoluteTransformation() * painter.transform());
+ }
+
+ KoClipPath::applyClipping(shape, painter);
+
+ qreal transparency = shape->transparency(true);
+ if (transparency > 0.0) {
+ painter.setOpacity(1.0-transparency);
+ }
+
+ if (shape->shadow()) {
+ KisQPainterStateSaver saver(&painter);
+ shape->shadow()->paint(shape, painter);
+ }
+
+ QScopedPointer<KoClipMaskPainter> clipMaskPainter;
+ QPainter *shapePainter = &painter;
+
+ KoClipMask *clipMask = shape->clipMask();
+ if (clipMask) {
+ const QRectF bounds = painter.transform().mapRect(shape->outlineRect());
+
+ clipMaskPainter.reset(new KoClipMaskPainter(&painter, bounds/*shape->boundingRect())*/));
+ shapePainter = clipMaskPainter->shapePainter();
+ }
+
+ /**
+ * We expect the shape to save/restore the painter's state itself. Such design was not
+ * not always here, so we need a period of sanity checks to ensure all the shapes are
+ * ported correctly.
+ */
+ const QTransform sanityCheckTransformSaved = shapePainter->transform();
+
+ renderShapes(childBegin(it), childEnd(it), *shapePainter, paintContext);
+
+ shape->paint(*shapePainter, paintContext);
+ shape->paintStroke(*shapePainter, paintContext);
+
+ KIS_SAFE_ASSERT_RECOVER(shapePainter->transform() == sanityCheckTransformSaved) {
+ shapePainter->setTransform(sanityCheckTransformSaved);
+ }
+
+ if (clipMask) {
+ clipMaskPainter->maskPainter()->save();
+
+ shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape);
+ clipMaskPainter->renderOnGlobalPainter();
+
+ clipMaskPainter->maskPainter()->restore();
+ }
+ }
+}
+
+}
+
void KoShapeManager::Private::updateTree()
{
QMutexLocker l(&this->treeMutex);
- // for detecting collisions between shapes.
- DetectCollision detector;
bool selectionModified = false;
bool anyModified = false;
Q_FOREACH (KoShape *shape, aggregate4update) {
- if (shapeIndexesBeforeUpdate.contains(shape))
- detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]);
selectionModified = selectionModified || selection->isSelected(shape);
anyModified = true;
}
foreach (KoShape *shape, aggregate4update) {
if (!shapeUsedInRenderingTree(shape)) continue;
tree.remove(shape);
QRectF br(shape->boundingRect());
tree.insert(br, shape);
}
- // do it again to see which shapes we intersect with _after_ moving.
- foreach (KoShape *shape, aggregate4update) {
- detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]);
- }
aggregate4update.clear();
shapeIndexesBeforeUpdate.clear();
- detector.fireSignals();
if (selectionModified) {
emit q->selectionContentChanged();
}
if (anyModified) {
emit q->contentChanged();
}
}
void KoShapeManager::Private::forwardCompressedUdpate()
{
bool shouldUpdateDecorations = false;
QRectF scheduledUpdate;
{
QMutexLocker l(&shapesMutex);
if (!compressedUpdate.isEmpty()) {
scheduledUpdate = compressedUpdate;
compressedUpdate = QRect();
}
Q_FOREACH (const KoShape *shape, compressedUpdatedShapes) {
if (selection->isSelected(shape)) {
shouldUpdateDecorations = true;
break;
}
}
compressedUpdatedShapes.clear();
}
if (shouldUpdateDecorations && canvas->toolProxy()) {
canvas->toolProxy()->repaintDecorations();
}
canvas->updateCanvas(scheduledUpdate);
}
-void KoShapeManager::Private::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(false))
- 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();
- }
- }
-}
-
KoShapeManager::KoShapeManager(KoCanvasBase *canvas, const QList<KoShape *> &shapes)
: d(new Private(this, canvas))
{
Q_ASSERT(d->canvas); // not optional.
connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
setShapes(shapes);
/**
* Shape manager uses signal compressors with timers, therefore
* it might handle queued signals, therefore it should belong
* to the GUI thread.
*/
this->moveToThread(qApp->thread());
connect(&d->updateCompressor, SIGNAL(timeout()), this, SLOT(forwardCompressedUdpate()));
}
KoShapeManager::KoShapeManager(KoCanvasBase *canvas)
: d(new Private(this, canvas))
{
Q_ASSERT(d->canvas); // not optional.
connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
// see a comment in another constructor
this->moveToThread(qApp->thread());
connect(&d->updateCompressor, SIGNAL(timeout()), this, SLOT(forwardCompressedUdpate()));
}
void KoShapeManager::Private::unlinkFromShapesRecursively(const QList<KoShape*> &shapes)
{
Q_FOREACH (KoShape *shape, shapes) {
shape->removeShapeManager(q);
KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
if (container) {
unlinkFromShapesRecursively(container->shapes());
}
}
}
KoShapeManager::~KoShapeManager()
{
d->unlinkFromShapesRecursively(d->shapes);
d->shapes.clear();
delete d;
}
void KoShapeManager::setShapes(const QList<KoShape *> &shapes, Repaint repaint)
{
{
QMutexLocker l1(&d->shapesMutex);
QMutexLocker l2(&d->treeMutex);
//clear selection
d->selection->deselectAll();
d->unlinkFromShapesRecursively(d->shapes);
d->compressedUpdate = QRect();
d->compressedUpdatedShapes.clear();
d->aggregate4update.clear();
d->shapeIndexesBeforeUpdate.clear();
d->tree.clear();
d->shapes.clear();
}
Q_FOREACH (KoShape *shape, shapes) {
addShape(shape, repaint);
}
}
void KoShapeManager::addShape(KoShape *shape, Repaint repaint)
{
{
QMutexLocker l1(&d->shapesMutex);
if (d->shapes.contains(shape))
return;
shape->addShapeManager(this);
d->shapes.append(shape);
- if (d->shapeUsedInRenderingTree(shape)) {
+ if (shapeUsedInRenderingTree(shape)) {
QMutexLocker l2(&d->treeMutex);
QRectF br(shape->boundingRect());
d->tree.insert(br, shape);
}
}
if (repaint == PaintShapeOnAdd) {
shape->update();
}
// add the children of a KoShapeContainer
KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
if (container) {
foreach (KoShape *containerShape, container->shapes()) {
addShape(containerShape, repaint);
}
}
-
- {
- QMutexLocker l(&d->treeMutex);
-
- Private::DetectCollision detector;
- detector.detect(d->tree, shape, shape->zIndex());
- detector.fireSignals();
- }
}
void KoShapeManager::remove(KoShape *shape)
{
QRectF dirtyRect;
{
QMutexLocker l1(&d->shapesMutex);
QMutexLocker l2(&d->treeMutex);
- Private::DetectCollision detector;
- detector.detect(d->tree, shape, shape->zIndex());
- detector.fireSignals();
-
dirtyRect = shape->absoluteOutlineRect();
shape->removeShapeManager(this);
d->selection->deselect(shape);
d->aggregate4update.remove(shape);
d->compressedUpdatedShapes.remove(shape);
- if (d->shapeUsedInRenderingTree(shape)) {
+ if (shapeUsedInRenderingTree(shape)) {
d->tree.remove(shape);
}
d->shapes.removeAll(shape);
}
if (!dirtyRect.isEmpty()) {
d->canvas->updateCanvas(dirtyRect);
}
// remove the children of a KoShapeContainer
KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
if (container) {
foreach (KoShape *containerShape, container->shapes()) {
remove(containerShape);
}
}
}
KoShapeManager::ShapeInterface::ShapeInterface(KoShapeManager *_q)
: q(_q)
{
}
void KoShapeManager::ShapeInterface::notifyShapeDestructed(KoShape *shape)
{
QMutexLocker l1(&q->d->shapesMutex);
QMutexLocker l2(&q->d->treeMutex);
q->d->selection->deselect(shape);
q->d->aggregate4update.remove(shape);
q->d->compressedUpdatedShapes.remove(shape);
// we cannot access RTTI of the semi-destructed shape, so just
// unlink it lazily
if (q->d->tree.contains(shape)) {
q->d->tree.remove(shape);
}
q->d->shapes.removeAll(shape);
}
KoShapeManager::ShapeInterface *KoShapeManager::shapeInterface()
{
return &d->shapeInterface;
}
-
-void KoShapeManager::paint(QPainter &painter, const KoViewConverter &converter, bool forPrint)
+void KoShapeManager::preparePaintJobs(PaintJobsList &jobs,
+ KoShape *excludeRoot)
{
QMutexLocker l1(&d->shapesMutex);
-
d->updateTree();
- painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off.
- painter.setBrush(Qt::NoBrush);
-
- QList<KoShape*> unsortedShapes;
- if (painter.hasClipping()) {
- QMutexLocker l(&d->treeMutex);
-
- QRectF rect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter));
- unsortedShapes = d->tree.intersects(rect);
- } else {
- unsortedShapes = d->shapes;
- warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!";
- }
- // filter all hidden shapes from the list
- // also filter shapes with a parent which has filter effects applied
- QList<KoShape*> sortedShapes;
- foreach (KoShape *shape, unsortedShapes) {
- if (!shape->isVisible())
- continue;
- bool addShapeToList = true;
- // check if one of the shapes ancestors have filter effects
- KoShapeContainer *parent = shape->parent();
- while (parent) {
- // parent must be part of the shape manager to be taken into account
- if (!d->shapes.contains(parent))
- break;
- if (parent->filterEffectStack() && !parent->filterEffectStack()->isEmpty()) {
- addShapeToList = false;
- break;
- }
- parent = parent->parent();
+ QSet<KoShape*> rootShapesSet;
+ Q_FOREACH (KoShape *shape, d->shapes) {
+ while (shape->parent() && shape->parent() != excludeRoot) {
+ shape = shape->parent();
}
- if (addShapeToList) {
- sortedShapes.append(shape);
- } else if (parent) {
- sortedShapes.append(parent);
+
+ if (!rootShapesSet.contains(shape) && shape != excludeRoot) {
+ rootShapesSet.insert(shape);
}
}
- std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
+ const QList<KoShape*> rootShapes = rootShapesSet.toList();
- KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
+ QList<KoShape*> newRootShapes;
- foreach (KoShape *shape, sortedShapes) {
- renderSingleShape(shape, painter, converter, paintContext);
+ Q_FOREACH (KoShape *srcShape, rootShapes) {
+ newRootShapes << srcShape->cloneShape();
}
-#ifdef CALLIGRA_RTREE_DEBUG
- // paint tree
- qreal zx = 0;
- qreal zy = 0;
- converter.zoom(&zx, &zy);
- painter.save();
- painter.scale(zx, zy);
- d->tree.paint(painter);
- painter.restore();
-#endif
+ PaintJobsList result;
- if (! forPrint) {
- KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
- d->selection->paint(painter, converter, paintContext);
+ PaintJob::SharedSafeStorage shapesStorage = std::make_shared<PaintJob::ShapesStorage>();
+ Q_FOREACH (KoShape *shape, newRootShapes) {
+ shapesStorage->emplace_back(std::unique_ptr<KoShape>(shape));
}
-}
-void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
-{
- KisQPainterStateSaver saver(&painter);
+ const QList<KoShape*> originalShapes = KoShape::linearizeSubtreeSorted(rootShapes);
+ const QList<KoShape*> clonedShapes = KoShape::linearizeSubtreeSorted(newRootShapes);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(clonedShapes.size() == originalShapes.size());
- // apply shape clipping
- KoClipPath::applyClipping(shape, painter, converter);
+ QHash<KoShape*, KoShape*> clonedFromOriginal;
+ for (int i = 0; i < originalShapes.size(); i++) {
+ clonedFromOriginal[originalShapes[i]] = clonedShapes[i];
+ }
- // apply transformation
- painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform());
- // paint the shape
- paintShape(shape, painter, converter, paintContext);
-}
+ for (auto it = std::begin(jobs); it != std::end(jobs); ++it) {
+ QMutexLocker l(&d->treeMutex);
+ QList<KoShape*> unsortedOriginalShapes = d->tree.intersects(it->docUpdateRect);
-void KoShapeManager::paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
-{
- qreal transparency = shape->transparency(true);
- if (transparency > 0.0) {
- painter.setOpacity(1.0-transparency);
- }
+ it->allClonedShapes = shapesStorage;
- if (shape->shadow()) {
- painter.save();
- shape->shadow()->paint(shape, painter, converter);
- painter.restore();
+ Q_FOREACH (KoShape *shape, unsortedOriginalShapes) {
+ KIS_SAFE_ASSERT_RECOVER(shapeUsedInRenderingTree(shape)) { continue; }
+ it->shapes << clonedFromOriginal[shape];
+ }
}
- if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) {
+}
- QScopedPointer<KoClipMaskPainter> clipMaskPainter;
- QPainter *shapePainter = &painter;
+void KoShapeManager::paintJob(QPainter &painter, const KoShapeManager::PaintJob &job, bool forPrint)
+{
+ painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off.
+ painter.setBrush(Qt::NoBrush);
- KoClipMask *clipMask = shape->clipMask();
- if (clipMask) {
- clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect()));
- shapePainter = clipMaskPainter->shapePainter();
- }
+ KisForest<KoShape*> renderTree;
+ buildRenderTree(job.shapes, renderTree);
- /**
- * We expect the shape to save/restore the painter's state itself. Such design was not
- * not always here, so we need a period of sanity checks to ensure all the shapes are
- * ported correctly.
- */
- const QTransform sanityCheckTransformSaved = shapePainter->transform();
+ KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
+ renderShapes(childBegin(renderTree), childEnd(renderTree), painter, paintContext);
+}
- shape->paint(*shapePainter, converter, paintContext);
- shape->paintStroke(*shapePainter, converter, paintContext);
+void KoShapeManager::paint(QPainter &painter, bool forPrint)
+{
+ QMutexLocker l1(&d->shapesMutex);
- KIS_SAFE_ASSERT_RECOVER(shapePainter->transform() == sanityCheckTransformSaved) {
- shapePainter->setTransform(sanityCheckTransformSaved);
- }
+ d->updateTree();
+ painter.setPen(Qt::NoPen); // painters by default have a black stroke, lets turn that off.
+ painter.setBrush(Qt::NoBrush);
- if (clipMask) {
- shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape);
- clipMaskPainter->renderOnGlobalPainter();
- }
+ QList<KoShape*> unsortedShapes;
+ if (painter.hasClipping()) {
+ QMutexLocker l(&d->treeMutex);
+ QRectF rect = KisPaintingTweaks::safeClipBoundingRect(painter);
+ unsortedShapes = d->tree.intersects(rect);
} else {
- // TODO: clipping mask is not implemented for this case!
-
- // There are filter effects, then we need to prerender the shape on an image, to filter it
- QRectF shapeBound(QPointF(), shape->size());
- // First step, compute the rectangle used for the image
- QRectF clipRegion = shape->filterEffectStack()->clipRectForBoundingRect(shapeBound);
- // convert clip region to view coordinates
- QRectF zoomedClipRegion = converter.documentToView(clipRegion);
- // determine the offset of the clipping rect from the shapes origin
- QPointF clippingOffset = zoomedClipRegion.topLeft();
-
- // Initialize the buffer image
- QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied);
- sourceGraphic.fill(qRgba(0,0,0,0));
-
- QHash<QString, QImage> imageBuffers;
-
- QSet<QString> requiredStdInputs = shape->filterEffectStack()->requiredStandarsInputs();
-
- if (requiredStdInputs.contains("SourceGraphic") || requiredStdInputs.contains("SourceAlpha")) {
- // Init the buffer painter
- QPainter imagePainter(&sourceGraphic);
- imagePainter.translate(-1.0f*clippingOffset);
- imagePainter.setPen(Qt::NoPen);
- imagePainter.setBrush(Qt::NoBrush);
- imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing));
-
- // Paint the shape on the image
- KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
- if (group) {
- // the childrens matrix contains the groups matrix as well
- // so we have to compensate for that before painting the children
- imagePainter.setTransform(group->absoluteTransformation(&converter).inverted(), true);
- Private::paintGroup(group, imagePainter, converter, paintContext);
- } else {
- imagePainter.save();
- shape->paint(imagePainter, converter, paintContext);
- shape->paintStroke(imagePainter, converter, paintContext);
- imagePainter.restore();
- imagePainter.end();
- }
- }
- if (requiredStdInputs.contains("SourceAlpha")) {
- QImage sourceAlpha = sourceGraphic;
- sourceAlpha.fill(qRgba(0,0,0,255));
- sourceAlpha.setAlphaChannel(sourceGraphic.alphaChannel());
- imageBuffers.insert("SourceAlpha", sourceAlpha);
- }
- if (requiredStdInputs.contains("FillPaint")) {
- QImage fillPaint = sourceGraphic;
- if (shape->background()) {
- QPainter fillPainter(&fillPaint);
- QPainterPath fillPath;
- fillPath.addRect(fillPaint.rect().adjusted(-1,-1,1,1));
- shape->background()->paint(fillPainter, converter, paintContext, fillPath);
- } else {
- fillPaint.fill(qRgba(0,0,0,0));
- }
- imageBuffers.insert("FillPaint", fillPaint);
- }
+ unsortedShapes = d->shapes;
+ warnFlake << "KoShapeManager::paint Painting with a painter that has no clipping will lead to too much being painted!";
+ }
- imageBuffers.insert("SourceGraphic", sourceGraphic);
- imageBuffers.insert(QString(), sourceGraphic);
-
- KoFilterEffectRenderContext renderContext(converter);
- renderContext.setShapeBoundingBox(shapeBound);
-
- QImage result;
- QList<KoFilterEffect*> filterEffects = shape->filterEffectStack()->filterEffects();
- // Filter
- foreach (KoFilterEffect *filterEffect, filterEffects) {
- QRectF filterRegion = filterEffect->filterRectForBoundingRect(shapeBound);
- filterRegion = converter.documentToView(filterRegion);
- QRect subRegion = filterRegion.translated(-clippingOffset).toRect();
- // set current filter region
- renderContext.setFilterRegion(subRegion & sourceGraphic.rect());
-
- if (filterEffect->maximalInputCount() <= 1) {
- QList<QString> inputs = filterEffect->inputs();
- QString input = inputs.count() ? inputs.first() : QString();
- // get input image from image buffers and apply the filter effect
- QImage image = imageBuffers.value(input);
- if (!image.isNull()) {
- result = filterEffect->processImage(imageBuffers.value(input), renderContext);
- }
- } else {
- QList<QImage> inputImages;
- Q_FOREACH (const QString &input, filterEffect->inputs()) {
- QImage image = imageBuffers.value(input);
- if (!image.isNull())
- inputImages.append(imageBuffers.value(input));
- }
- // apply the filter effect
- if (filterEffect->inputs().count() == inputImages.count())
- result = filterEffect->processImages(inputImages, renderContext);
- }
- // store result of effect
- imageBuffers.insert(filterEffect->output(), result);
- }
+ KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
- KoFilterEffect *lastEffect = filterEffects.last();
+ KisForest<KoShape*> renderTree;
+ buildRenderTree(unsortedShapes, renderTree);
+ renderShapes(childBegin(renderTree), childEnd(renderTree), painter, paintContext);
+}
- // Paint the result
- painter.save();
- painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output()));
- painter.restore();
- }
+void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, KoShapePaintingContext &paintContext)
+{
+ KisForest<KoShape*> renderTree;
+
+ KoViewConverter converter;
+
+ auto root = renderTree.insert(childBegin(renderTree), shape);
+ populateRenderSubtree(shape, root, renderTree, &shapeIsVisible, &shapeIsVisible);
+ renderShapes(childBegin(renderTree), childEnd(renderTree), painter, paintContext);
}
KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes)
{
QMutexLocker l(&d->shapesMutex);
d->updateTree();
QList<KoShape*> sortedShapes;
{
QMutexLocker l(&d->treeMutex);
sortedShapes = d->tree.contains(position);
}
std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
KoShape *firstUnselectedShape = 0;
for (int count = sortedShapes.count() - 1; count >= 0; count--) {
KoShape *shape = sortedShapes.at(count);
if (omitHiddenShapes && ! shape->isVisible())
continue;
if (! shape->hitTest(position))
continue;
switch (selection) {
case KoFlake::ShapeOnTop:
if (shape->isSelectable())
return shape;
break;
case KoFlake::Selected:
if (d->selection->isSelected(shape))
return shape;
break;
case KoFlake::Unselected:
if (! d->selection->isSelected(shape))
return shape;
break;
case KoFlake::NextUnselected:
// we want an unselected shape
if (d->selection->isSelected(shape))
continue;
// memorize the first unselected shape
if (! firstUnselectedShape)
firstUnselectedShape = shape;
// check if the shape above is selected
if (count + 1 < sortedShapes.count() && d->selection->isSelected(sortedShapes.at(count + 1)))
return shape;
break;
}
}
// if we want the next unselected below a selected but there was none selected,
// return the first found unselected shape
if (selection == KoFlake::NextUnselected && firstUnselectedShape)
return firstUnselectedShape;
if (d->selection->hitTest(position))
return d->selection;
return 0; // missed everything
}
QList<KoShape *> KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode)
{
QMutexLocker l(&d->shapesMutex);
d->updateTree();
QList<KoShape*> shapes;
{
QMutexLocker l(&d->treeMutex);
shapes = containedMode ? d->tree.contained(rect) : d->tree.intersects(rect);
}
for (int count = shapes.count() - 1; count >= 0; count--) {
KoShape *shape = shapes.at(count);
if (omitHiddenShapes && !shape->isVisible()) {
shapes.removeAt(count);
} else {
- const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline());
+ const QPainterPath outline = shape->absoluteTransformation().map(shape->outline());
if (!containedMode && !outline.intersects(rect) && !outline.contains(rect)) {
shapes.removeAt(count);
} else if (containedMode) {
QPainterPath containingPath;
containingPath.addRect(rect);
if (!containingPath.contains(outline)) {
shapes.removeAt(count);
}
}
}
}
return shapes;
}
void KoShapeManager::update(const QRectF &rect, const KoShape *shape, bool selectionHandles)
{
if (d->updatesBlocked) return;
{
QMutexLocker l(&d->shapesMutex);
d->compressedUpdate |= rect;
if (selectionHandles) {
d->compressedUpdatedShapes.insert(shape);
}
}
d->updateCompressor.start();
}
void KoShapeManager::setUpdatesBlocked(bool value)
{
d->updatesBlocked = value;
}
bool KoShapeManager::updatesBlocked() const
{
return d->updatesBlocked;
}
void KoShapeManager::notifyShapeChanged(KoShape *shape)
{
{
QMutexLocker l(&d->treeMutex);
Q_ASSERT(shape);
if (d->aggregate4update.contains(shape)) {
return;
}
d->aggregate4update.insert(shape);
d->shapeIndexesBeforeUpdate.insert(shape, shape->zIndex());
}
KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
if (container) {
Q_FOREACH (KoShape *child, container->shapes())
notifyShapeChanged(child);
}
}
QList<KoShape*> KoShapeManager::shapes() const
{
QMutexLocker l(&d->shapesMutex);
return d->shapes;
}
QList<KoShape*> KoShapeManager::topLevelShapes() const
{
QMutexLocker l(&d->shapesMutex);
QList<KoShape*> shapes;
// get all toplevel shapes
Q_FOREACH (KoShape *shape, d->shapes) {
if (!shape->parent() || dynamic_cast<KoShapeLayer*>(shape->parent())) {
shapes.append(shape);
}
}
return shapes;
}
KoSelection *KoShapeManager::selection() const
{
return d->selection;
}
KoCanvasBase *KoShapeManager::canvas()
{
return d->canvas;
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoShapeManager.cpp"
diff --git a/libs/flake/KoShapeManager.h b/libs/flake/KoShapeManager.h
index ac747b7265..014f840439 100644
--- a/libs/flake/KoShapeManager.h
+++ b/libs/flake/KoShapeManager.h
@@ -1,226 +1,269 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007, 2009 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 KOSHAPEMANAGER_H
#define KOSHAPEMANAGER_H
#include <QList>
#include <QObject>
#include <QSet>
+#include <QRect>
#include "KoFlake.h"
#include "kritaflake_export.h"
+#include <memory>
+#include <vector>
+
class KoShape;
class KoSelection;
class KoViewConverter;
class KoCanvasBase;
class KoPointerEvent;
class KoShapePaintingContext;
class QPainter;
class QPointF;
class QRectF;
/**
* The shape manager hold a list of all shape which are in scope.
* There is one shape manager per canvas. This makes the shape manager
* different from QGraphicsScene, which contains the datamodel for all
* graphics items: KoShapeManager only contains the subset of shapes
* that are shown in its canvas.
*
* The selection in the different views can be different.
*/
class KRITAFLAKE_EXPORT KoShapeManager : public QObject
{
Q_OBJECT
public:
/// enum for add()
enum Repaint {
PaintShapeOnAdd, ///< Causes each shapes 'update()' to be called after being added to the shapeManager
AddWithoutRepaint ///< Avoids each shapes 'update()' to be called for faster addition when its possible.
};
/**
* Constructor.
*/
explicit KoShapeManager(KoCanvasBase *canvas);
/**
* Constructor that takes a list of shapes, convenience version.
* @param shapes the shapes to start out with, see also setShapes()
* @param canvas the canvas this shape manager is working on.
*/
KoShapeManager(KoCanvasBase *canvas, const QList<KoShape *> &shapes);
~KoShapeManager() override;
/**
* Remove all previously owned shapes and make the argument list the new shapes
* to be managed by this manager.
* @param shapes the new shapes to manage.
* @param repaint if true it will trigger a repaint of the shapes
*/
void setShapes(const QList<KoShape *> &shapes, Repaint repaint = PaintShapeOnAdd);
/// returns the list of maintained shapes
QList<KoShape*> shapes() const;
/**
* Get a list of all shapes that don't have a parent.
*/
QList<KoShape*> topLevelShapes() const;
public Q_SLOTS:
/**
* Add a KoShape to be displayed and managed by this manager.
* This will trigger a repaint of the shape.
* @param shape the shape to add
* @param repaint if true it will trigger a repaint of the shape
*/
void addShape(KoShape *shape, KoShapeManager::Repaint repaint = PaintShapeOnAdd);
/**
* Remove a KoShape from this manager
* @param shape the shape to remove
*/
void remove(KoShape *shape);
public:
/// return the selection shapes for this shapeManager
KoSelection *selection() const;
+ struct PaintJob {
+ using ShapesStorage = std::vector<std::unique_ptr<KoShape>>;
+ using SharedSafeStorage = std::shared_ptr<ShapesStorage>;
+
+ PaintJob() = default;
+ PaintJob(QRectF _docUpdateRect, QRect _viewUpdateRect)
+ : docUpdateRect(_docUpdateRect),
+ viewUpdateRect(_viewUpdateRect)
+ {
+ }
+
+ QRectF docUpdateRect;
+ QRect viewUpdateRect;
+
+ QList<KoShape*> shapes;
+ SharedSafeStorage allClonedShapes;
+ };
+
+ using PaintJobsList = QList<PaintJob>;
+
+
+ /**
+ * Prepare a shallow copy of all the shapes and the jobs to be rendered
+ * asynchronoursly later. The copies are stored in jobs, so that the user
+ * could later pass these jobs into paintJob() in a separate thread.
+ *
+ * @param jobs a list of rects that are going to be updated. docUpdateRect
+ * and viewUpdateRect should be preinitialized by the caller.
+ * @param excludeRoot the root shape which should not be copied. It is basically
+ * a hack to avoid copying of KisShapeLayer, which is not
+ * copiable.
+ * \see paintJob()
+ * \see a comment in KisShapeLayerCanvas::slotStartAsyncRepaint()
+ */
+ void preparePaintJobs(PaintJobsList &jobs, KoShape *excludeRoot);
+
+ /**
+ * Render a \p job on \p painter. No mutable internals of the shape
+ * manager are accessed, so calling this method is safe in multithreading
+ * environment.
+ *
+ * @param painter a painter to paint on. Clip rect of the painter is expected to be setup correctly.
+ * @param job a job to paint.
+ * @param forPrint not used in Krita.
+ *
+ * \see preparePaintJobs
+ */
+ void paintJob(QPainter &painter, const KoShapeManager::PaintJob &job, bool forPrint);
+
/**
* Paint all shapes and their selection handles etc.
* @param painter the painter to paint to.
* @param forPrint if true, make sure only actual content is drawn and no decorations.
* @param converter to convert between document and view coordinates.
*/
- void paint(QPainter &painter, const KoViewConverter &converter, bool forPrint);
+ void paint(QPainter &painter, bool forPrint);
/**
* Returns the shape located at a specific point in the document.
* If more than one shape is located at the specific point, the given selection type
* controls which of them is returned.
* @param position the position in the document coordinate system.
* @param selection controls which shape is returned when more than one shape is at the specific point
* @param omitHiddenShapes if true, only visible shapes are considered
*/
KoShape *shapeAt(const QPointF &position, KoFlake::ShapeSelection selection = KoFlake::ShapeOnTop, bool omitHiddenShapes = true);
/**
* Returns the shapes which intersects the specific rect in the document.
* @param rect the rectangle in the document coordinate system.
* @param omitHiddenShapes if @c true, only visible shapes are considered
* @param containedMode if @c true use contained mode
*/
QList<KoShape *> shapesAt(const QRectF &rect, bool omitHiddenShapes = true, bool containedMode = false);
/**
* Request a repaint to be queued.
* The repaint will be restricted to the parameters rectangle, which is expected to be
* in points (the document coordinates system of KoShape) and it is expected to be
* normalized and based in the global coordinates, not any local coordinates.
* <p>This method will return immediately and only request a repaint. Successive calls
* will be merged into an appropriate repaint action.
* @param rect the rectangle (in pt) to queue for repaint.
* @param shape the shape that is going to be redrawn; only needed when selectionHandles=true
* @param selectionHandles if true; find out if the shape is selected and repaint its
* selection handles at the same time.
*/
void update(const QRectF &rect, const KoShape *shape = 0, bool selectionHandles = false);
/**
* Block all updates initiated with update() call. The incoming updates will
* be dropped completely.
*/
void setUpdatesBlocked(bool value);
/**
* \see setUpdatesBlocked()
*/
bool updatesBlocked() const;
/**
* Update the tree for finding the shapes.
* This will remove the shape from the tree and will reinsert it again.
* The update to the tree will be posponed until it is needed so that successive calls
* will be merged into one.
* @param shape the shape to updated its position in the tree.
*/
void notifyShapeChanged(KoShape *shape);
- /**
- * Paint a shape
- *
- * @param shape the shape to paint
- * @param painter the painter to paint to.
- * @param converter to convert between document and view coordinates.
- * @param paintContext the painting context
- */
- static void paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
-
/**
* @brief renderSingleShape renders a shape on \p painter. This method includes all the
* needed steps for painting a single shape: setting transformations, clipping and masking.
*/
- static void renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
+ static void renderSingleShape(KoShape *shape, QPainter &painter, KoShapePaintingContext &paintContext);
/**
* A special interface for KoShape to use during shape destruction. Don't use this
* interface directly unless you are KoShape.
*/
struct ShapeInterface {
ShapeInterface(KoShapeManager *_q);
/**
* Called by a shape when it is destructed. Please note that you cannot access
* any shape's method type or information during this call because the shape might be
* semi-destroyed.
*/
void notifyShapeDestructed(KoShape *shape);
protected:
KoShapeManager *q;
};
ShapeInterface* shapeInterface();
Q_SIGNALS:
/// emitted when the selection is changed
void selectionChanged();
/// emitted when an object in the selection is changed (moved/rotated etc)
void selectionContentChanged();
/// emitted when any object changed (moved/rotated etc)
void contentChanged();
private:
KoCanvasBase *canvas();
class Private;
Private * const d;
Q_PRIVATE_SLOT(d, void updateTree())
Q_PRIVATE_SLOT(d, void forwardCompressedUdpate())
};
#endif
diff --git a/libs/flake/KoShapeManager_p.h b/libs/flake/KoShapeManager_p.h
index 5371a8d074..2fcbfe8813 100644
--- a/libs/flake/KoShapeManager_p.h
+++ b/libs/flake/KoShapeManager_p.h
@@ -1,132 +1,88 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
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 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 KoShapeManager_p_h
#define KoShapeManager_p_h
#include "KoSelection.h"
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoShapeContainer.h"
#include "KoShapeManager.h"
#include <KoRTree.h>
#include <QMutex>
#include "kis_thread_safe_signal_compressor.h"
class KoCanvasBase;
class KoShapeGroup;
class KoShapePaintingContext;
class QPainter;
class Q_DECL_HIDDEN KoShapeManager::Private
{
public:
Private(KoShapeManager *shapeManager, KoCanvasBase *c)
: selection(new KoSelection(shapeManager)),
canvas(c),
tree(4, 2),
q(shapeManager),
shapeInterface(shapeManager),
updateCompressor(100, KisSignalCompressor::FIRST_ACTIVE)
{
}
~Private() {
delete selection;
}
/**
* Update the tree when there are shapes in m_aggregate4update. This is done so not all
* updates to the tree are done when they are asked for but when they are needed.
*/
void updateTree();
void forwardCompressedUdpate();
- /**
- * Returns whether the shape should be added to the RTree for collision and ROI
- * detection.
- */
- bool shapeUsedInRenderingTree(KoShape *shape);
/**
* Recursively detach the shapes from this shape manager
*/
void unlinkFromShapesRecursively(const QList<KoShape *> &shapes);
- /**
- * Recursively paints the given group shape to the specified painter
- * This is needed for filter effects on group shapes where the filter effect
- * applies to all the children of the group shape at once
- */
- static void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
-
- class DetectCollision
- {
- public:
- DetectCollision() {}
- void detect(KoRTree<KoShape *> &tree, KoShape *s, int prevZIndex) {
- Q_FOREACH (KoShape *shape, tree.intersects(s->boundingRect())) {
- bool isChild = false;
- KoShapeContainer *parent = s->parent();
- while (parent && !isChild) {
- if (parent == shape)
- isChild = true;
- parent = parent->parent();
- }
- if (isChild)
- continue;
- if (s->zIndex() <= shape->zIndex() && prevZIndex <= shape->zIndex())
- // Moving a shape will only make it collide with shapes below it.
- continue;
- if (shape->collisionDetection() && !shapesWithCollisionDetection.contains(shape))
- shapesWithCollisionDetection.append(shape);
- }
- }
-
- void fireSignals() {
- Q_FOREACH (KoShape *shape, shapesWithCollisionDetection)
- shape->shapeChangedPriv(KoShape::CollisionDetected);
- }
-
- private:
- QList<KoShape*> shapesWithCollisionDetection;
- };
-
QList<KoShape *> shapes;
KoSelection *selection;
KoCanvasBase *canvas;
KoRTree<KoShape *> tree;
QSet<KoShape *> aggregate4update;
QHash<KoShape*, int> shapeIndexesBeforeUpdate;
KoShapeManager *q;
KoShapeManager::ShapeInterface shapeInterface;
QMutex shapesMutex;
QMutex treeMutex;
KisThreadSafeSignalCompressor updateCompressor;
QRectF compressedUpdate;
QSet<const KoShape*> compressedUpdatedShapes;
bool updatesBlocked = false;
};
#endif
diff --git a/libs/flake/KoShapeRegistry.cpp b/libs/flake/KoShapeRegistry.cpp
index d70d101726..22f3e582a4 100644
--- a/libs/flake/KoShapeRegistry.cpp
+++ b/libs/flake/KoShapeRegistry.cpp
@@ -1,575 +1,575 @@
/* This file is part of the KDE project
* Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org)
* Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2006,2008-2010 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2010 Inge Wallin <inge@lysator.liu.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.
*/
// Own
#include "KoShapeRegistry.h"
#include "KoSvgTextShape.h"
#include "KoPathShapeFactory.h"
#include "KoConnectionShapeFactory.h"
#include "KoShapeLoadingContext.h"
#include "KoShapeSavingContext.h"
#include "KoShapeGroup.h"
#include "KoShapeLayer.h"
#include "SvgShapeFactory.h"
#include <KoPluginLoader.h>
#include <KoXmlReader.h>
#include <KoXmlNS.h>
#include <KoOdfLoadingContext.h>
#include <KoStyleStack.h>
#include <QString>
#include <QHash>
#include <QMultiMap>
#include <QPainter>
#include <QGlobalStatic>
#include <FlakeDebug.h>
Q_GLOBAL_STATIC(KoShapeRegistry, s_instance)
class Q_DECL_HIDDEN KoShapeRegistry::Private
{
public:
void insertFactory(KoShapeFactoryBase *factory);
void init(KoShapeRegistry *q);
KoShape *createShapeInternal(const KoXmlElement &fullElement, KoShapeLoadingContext &context, const KoXmlElement &element) const;
// Map namespace,tagname to priority:factory
QHash<QPair<QString, QString>, QMultiMap<int, KoShapeFactoryBase*> > factoryMap;
};
KoShapeRegistry::KoShapeRegistry()
: d(new Private())
{
}
KoShapeRegistry::~KoShapeRegistry()
{
qDeleteAll(doubleEntries());
qDeleteAll(values());
delete d;
}
void KoShapeRegistry::Private::init(KoShapeRegistry *q)
{
KoPluginLoader::PluginsConfig config;
config.whiteList = "FlakePlugins";
config.blacklist = "FlakePluginsDisabled";
config.group = "calligra";
KoPluginLoader::instance()->load(QString::fromLatin1("Calligra/Flake"),
QString::fromLatin1("[X-Flake-PluginVersion] == 28"),
config);
config.whiteList = "ShapePlugins";
config.blacklist = "ShapePluginsDisabled";
KoPluginLoader::instance()->load(QString::fromLatin1("Calligra/Shape"),
QString::fromLatin1("[X-Flake-PluginVersion] == 28"),
config);
// Also add our hard-coded basic shapes
q->add(new KoSvgTextShapeFactory());
q->add(new KoPathShapeFactory(QStringList()));
q->add(new KoConnectionShapeFactory());
// As long as there is no shape dealing with embedded svg images
// we add the svg shape factory here by default
q->add(new SvgShapeFactory);
// Now all shape factories are registered with us, determine their
// associated odf tagname & priority and prepare ourselves for
// loading ODF.
QList<KoShapeFactoryBase*> factories = q->values();
for (int i = 0; i < factories.size(); ++i) {
insertFactory(factories[i]);
}
}
KoShapeRegistry* KoShapeRegistry::instance()
{
if (!s_instance.exists()) {
s_instance->d->init(s_instance);
}
return s_instance;
}
void KoShapeRegistry::addFactory(KoShapeFactoryBase * factory)
{
add(factory);
d->insertFactory(factory);
}
void KoShapeRegistry::Private::insertFactory(KoShapeFactoryBase *factory)
{
const QList<QPair<QString, QStringList> > odfElements(factory->odfElements());
if (odfElements.isEmpty()) {
debugFlake << "Shape factory" << factory->id() << " does not have OdfNamespace defined, ignoring";
}
else {
int priority = factory->loadingPriority();
for (QList<QPair<QString, QStringList> >::const_iterator it(odfElements.begin()); it != odfElements.end(); ++it) {
foreach (const QString &elementName, (*it).second) {
QPair<QString, QString> p((*it).first, elementName);
QMultiMap<int, KoShapeFactoryBase*> & priorityMap = factoryMap[p];
priorityMap.insert(priority, factory);
debugFlake << "Inserting factory" << factory->id() << " for"
<< p << " with priority "
<< priority << " into factoryMap making "
<< priorityMap.size() << " entries. ";
}
}
}
}
#include <KoXmlWriter.h>
#include <QBuffer>
#include <KoStore.h>
#include <boost/optional.hpp>
namespace {
struct ObjectEntry {
ObjectEntry()
{
}
ObjectEntry(const ObjectEntry &rhs)
: objectXmlContents(rhs.objectXmlContents),
objectName(rhs.objectName),
isDir(rhs.isDir)
{
}
~ObjectEntry()
{
}
QByteArray objectXmlContents; // the XML tree in the object
QString objectName; // object name in the frame without "./"
// This is extracted from objectXmlContents.
bool isDir = false;
};
// A FileEntry is used to store information about embedded files
// inside (i.e. referred to by) an object.
struct FileEntry {
FileEntry() {}
FileEntry(const FileEntry &rhs)
: path(rhs.path),
mimeType(rhs.mimeType),
isDir(rhs.isDir),
contents(rhs.contents)
{
}
QString path; // Normalized filename, i.e. without "./".
QString mimeType;
bool isDir;
QByteArray contents;
};
QByteArray loadFile(const QString &fileName, KoShapeLoadingContext &context)
{
// Can't load a file which is a directory, return an invalid QByteArray
if (fileName.endsWith('/'))
return QByteArray();
KoStore *store = context.odfLoadingContext().store();
QByteArray fileContent;
if (!store->open(fileName)) {
store->close();
return QByteArray();
}
int fileSize = store->size();
fileContent = store->read(fileSize);
store->close();
//debugFlake << "File content: " << fileContent;
return fileContent;
}
boost::optional<FileEntry> storeFile(const QString &fileName, KoShapeLoadingContext &context)
{
debugFlake << "Saving file: " << fileName;
boost::optional<FileEntry> result;
QByteArray fileContent = loadFile(fileName, context);
if (!fileContent.isNull()) {
// Actually store the file in the list.
FileEntry entry;
entry.path = fileName;
if (entry.path.startsWith(QLatin1String("./"))) {
entry.path.remove(0, 2);
}
entry.mimeType = context.odfLoadingContext().mimeTypeForPath(entry.path);
entry.isDir = false;
entry.contents = fileContent;
result = entry;
}
return result;
}
void storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer,
ObjectEntry *object, QHash<QString, QString> &unknownNamespaces)
{
// Start the element;
// keep the name in a QByteArray so that it stays valid until end element is called.
const QByteArray name(el.nodeName().toLatin1());
writer.startElement(name.constData());
// Child elements
// Loop through all the child elements of the draw:frame.
KoXmlNode n = el.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
if (n.isElement()) {
storeXmlRecursive(n.toElement(), writer, object, unknownNamespaces);
}
else if (n.isText()) {
writer.addTextNode(n.toText().data()/*.toUtf8()*/);
}
}
// End the element
writer.endElement();
}
QVector<ObjectEntry> storeObjects(const KoXmlElement &element)
{
QVector<ObjectEntry> result;
// Loop through all the child elements of the draw:frame and save them.
KoXmlNode n = element.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
debugFlake << "In draw:frame, node =" << n.nodeName();
// This disregards #text, but that's not in the spec anyway so
// it doesn't need to be saved.
if (!n.isElement())
continue;
KoXmlElement el = n.toElement();
ObjectEntry object;
QByteArray contentsTmp;
QBuffer buffer(&contentsTmp); // the member
KoXmlWriter writer(&buffer);
// 1. Find out the objectName
// Save the normalized filename, i.e. without a starting "./".
// An empty string is saved if no name is found.
QString name = el.attributeNS(KoXmlNS::xlink, "href", QString());
if (name.startsWith(QLatin1String("./")))
name.remove(0, 2);
object.objectName = name;
// 2. Copy the XML code.
QHash<QString, QString> unknownNamespaces;
storeXmlRecursive(el, writer, &object, unknownNamespaces);
object.objectXmlContents = contentsTmp;
// 3, 4: the isDir and manifestEntry members are not set here,
// but initialize them anyway. .
object.isDir = false; // Has to be initialized to something.
result.append(object);
}
return result;
}
}
#include <svg/SvgShapeFactory.h>
#include "kis_debug.h"
#include <QMimeDatabase>
#include <KoUnit.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeController.h>
#include <KoShapeGroupCommand.h>
KoShape * KoShapeRegistry::createShapeFromOdf(const KoXmlElement & e, KoShapeLoadingContext & context) const
{
debugFlake << "Going to check for" << e.namespaceURI() << ":" << e.tagName();
KoShape * shape = 0;
// Handle the case where the element is a draw:frame differently from other cases.
if (e.tagName() == "frame" && e.namespaceURI() == KoXmlNS::draw) {
// If the element is in a frame, the frame is already added by the
// application and we only want to create a shape from the
// embedded element. The very first shape we create is accepted.
//
// FIXME: we might want to have some code to determine which is
// the "best" of the creatable shapes.
if (e.hasChildNodes()) {
// if we don't ignore white spaces it can be that the first child is not a element so look for the first element
KoXmlNode node = e.firstChild();
KoXmlElement element;
while (!node.isNull() && element.isNull()) {
element = node.toElement();
node = node.nextSibling();
}
if (!element.isNull()) {
// Check for draw:object
if (element.tagName() == "object" && element.namespaceURI() == KoXmlNS::draw && element.hasChildNodes()) {
// Loop through the elements and find the first one
// that is handled by any shape.
KoXmlNode n = element.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
if (n.isElement()) {
debugFlake << "trying for element " << n.toElement().tagName();
shape = d->createShapeInternal(e, context, n.toElement());
break;
}
}
if (shape)
debugFlake << "Found a shape for draw:object";
else
debugFlake << "Found NO shape shape for draw:object";
}
else {
// If not draw:object, e.g draw:image or draw:plugin
shape = d->createShapeInternal(e, context, element);
}
}
if (shape) {
debugFlake << "A shape supporting the requested type was found.";
}
else {
// If none of the registered shapes could handle the frame
// contents, try to fetch SVG it from an embedded link
const KoXmlElement &frameElement = e;
const int frameZIndex = SvgShapeFactory::calculateZIndex(frameElement, context);
QList<KoShape*> resultShapes;
QVector<ObjectEntry> objects = storeObjects(frameElement);
Q_FOREACH (const ObjectEntry &object, objects) {
if (object.objectName.isEmpty()) continue;
boost::optional<FileEntry> file = storeFile(object.objectName, context);
if (file && !file->contents.isEmpty()) {
QMimeDatabase db;
QMimeType mime = db.mimeTypeForData(file->contents);
const int zIndex = SvgShapeFactory::calculateZIndex(element, context);
if (mime.inherits("image/svg+xml")) {
KoXmlDocument xmlDoc;
int line, col;
QString errormessage;
const bool parsed = xmlDoc.setContent(file->contents, &errormessage, &line, &col);
if (!parsed) continue;
const QRectF bounds = context.documentResourceManager()->documentRectInPixels();
// WARNING: Krita 3.x expects all the embedded objects to
// be loaded in default resolution of 72.0 ppi.
// Don't change it to the correct data in the image,
// it will change back compatibility (and this code will
// be deprecated some time soon
// UPDATE (DK): There is actually no difference in what resolution we
// load these shapes, because they will be scaled into
// the bounds of the parent odf-frame
const qreal pixelsPerInch = 72.0;
const qreal forcedFontSizeResolution = 72.0;
QPointF pos;
pos.setX(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "x", QString::number(bounds.x()))));
pos.setY(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "y", QString::number(bounds.y()))));
QSizeF size;
size.setWidth(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "width", QString::number(bounds.width()))));
size.setHeight(KoUnit::parseValue(frameElement.attributeNS(KoXmlNS::svg, "height", QString::number(bounds.height()))));
KoShape *shape = SvgShapeFactory::createShapeFromSvgDirect(xmlDoc.documentElement(),
QRectF(pos, size),
pixelsPerInch,
forcedFontSizeResolution,
zIndex,
context);
if (shape) {
// NOTE: here we are expected to stretch the internal to the bounds of
// the frame! Sounds weird, but it is what Krita 3.x did.
- const QRectF shapeRect = shape->absoluteOutlineRect(0);
+ const QRectF shapeRect = shape->absoluteOutlineRect();
const QPointF offset = shapeRect.topLeft();
const QSizeF fragmentSize = shapeRect.size();
if (fragmentSize.isValid()) {
/**
* Yes, you see what you see. The previous versions of Krita used
* QSvgRenderer to render the object, which allegedly truncated the
* object on sides. Even though we don't use pre-rendering now,
* we should still reproduce the old way...
*/
const QSizeF newSize = QSizeF(int(size.width()), int(size.height()));
shape->applyAbsoluteTransformation(
QTransform::fromTranslate(-offset.x(), -offset.y()) *
QTransform::fromScale(
newSize.width() / fragmentSize.width(),
newSize.height() / fragmentSize.height()) *
QTransform::fromTranslate(pos.x(), pos.y()));
resultShapes.append(shape);
}
}
} else {
// TODO: implement raster images?
}
}
}
if (resultShapes.size() == 1) {
shape = resultShapes.takeLast();
} else if (resultShapes.size() > 1) {
KoShapeGroup *groupShape = new KoShapeGroup;
KoShapeGroupCommand cmd(groupShape, resultShapes);
cmd.redo();
groupShape->setZIndex(frameZIndex);
shape = groupShape;
}
}
}
}
// Hardwire the group shape into the loading as it should not appear
// in the shape selector
else if (e.localName() == "g" && e.namespaceURI() == KoXmlNS::draw) {
KoShapeGroup * group = new KoShapeGroup();
context.odfLoadingContext().styleStack().save();
bool loaded = group->loadOdf(e, context);
context.odfLoadingContext().styleStack().restore();
if (loaded) {
shape = group;
}
else {
delete group;
}
} else {
shape = d->createShapeInternal(e, context, e);
}
if (shape) {
context.shapeLoaded(shape);
}
return shape;
}
KoShape *KoShapeRegistry::Private::createShapeInternal(const KoXmlElement &fullElement,
KoShapeLoadingContext &context,
const KoXmlElement &element) const
{
// Pair of namespace, tagname
QPair<QString, QString> p = QPair<QString, QString>(element.namespaceURI(), element.tagName());
// Remove duplicate lookup.
if (!factoryMap.contains(p))
return 0;
QMultiMap<int, KoShapeFactoryBase*> priorityMap = factoryMap.value(p);
QList<KoShapeFactoryBase*> factories = priorityMap.values();
#ifndef NDEBUG
debugFlake << "Supported factories for=" << p;
foreach (KoShapeFactoryBase *f, factories)
debugFlake << f->id() << f->name();
#endif
// Loop through all shape factories. If any of them supports this
// element, then we let the factory create a shape from it. This
// may fail because the element itself is too generic to draw any
// real conclusions from it - we actually have to try to load it.
// An example of this is the draw:image element which have
// potentially hundreds of different image formats to support,
// including vector formats.
//
// If it succeeds, then we use this shape, if it fails, then just
// try the next.
//
// Higher numbers are more specific, map is sorted by keys.
for (int i = factories.size() - 1; i >= 0; --i) {
KoShapeFactoryBase * factory = factories[i];
if (factory->supports(element, context)) {
KoShape *shape = factory->createShapeFromOdf(fullElement, context);
if (shape) {
debugFlake << "Shape found for factory " << factory->id() << factory->name();
// we return the top-level most shape as that's the one that we'll have to
// add to the KoShapeManager for painting later (and also to avoid memory leaks)
// but don't go past a KoShapeLayer as KoShape adds those from the context
// during loading and those are already added.
while (shape->parent() && dynamic_cast<KoShapeLayer*>(shape->parent()) == 0)
shape = shape->parent();
return shape;
}
// Maybe a shape with a lower priority can load our
// element, but this attempt has failed.
}
else {
debugFlake << "No support for" << p << "by" << factory->id();
}
}
return 0;
}
QList<KoShapeFactoryBase*> KoShapeRegistry::factoriesForElement(const QString &nameSpace, const QString &elementName)
{
// Pair of namespace, tagname
QPair<QString, QString> p = QPair<QString, QString>(nameSpace, elementName);
QMultiMap<int, KoShapeFactoryBase*> priorityMap = d->factoryMap.value(p);
QList<KoShapeFactoryBase*> shapeFactories;
// sort list by priority
Q_FOREACH (KoShapeFactoryBase *f, priorityMap.values()) {
shapeFactories.prepend(f);
}
return shapeFactories;
}
diff --git a/libs/flake/KoShapeShadow.cpp b/libs/flake/KoShapeShadow.cpp
index 299759097a..30ebe2291d 100644
--- a/libs/flake/KoShapeShadow.cpp
+++ b/libs/flake/KoShapeShadow.cpp
@@ -1,357 +1,356 @@
/* This file is part of the KDE project
* Copyright (C) 2008-2009 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 Ariya Hidayat <ariya.hidayat@gmail.com>
* Copyright (C) 2010-2011 Yue Liu <yue.liu@mail.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 "KoShapeShadow.h"
#include "KoShapeGroup.h"
#include "KoSelection.h"
#include "KoShapeSavingContext.h"
#include "KoShapeStrokeModel.h"
#include "KoShape.h"
#include "KoInsets.h"
#include "KoPathShape.h"
#include <KoGenStyle.h>
-#include <KoViewConverter.h>
#include <FlakeDebug.h>
#include <QPainter>
#include <QAtomicInt>
#include <QImage>
#include <QRectF>
class Q_DECL_HIDDEN KoShapeShadow::Private
{
public:
Private()
: offset(2, 2), color(Qt::black), blur(8), visible(true), refCount(0) {
}
QPointF offset;
QColor color;
qreal blur;
bool visible;
QAtomicInt refCount;
/**
* Paints the shadow of the shape group to the buffer image.
* @param group the shape group to paint around
* @param painter the painter to paint on the image
* @param converter to convert between internal and view coordinates.
*/
- void paintGroupShadow(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter);
+ void paintGroupShadow(KoShapeGroup *group, QPainter &painter);
/**
* Paints the shadow of the shape to the buffer image.
* @param shape the shape to paint around
* @param painter the painter to paint on the image
- * @param converter to convert between internal and view coordinates.
*/
- void paintShadow(KoShape *shape, QPainter &painter, const KoViewConverter &converter);
+ void paintShadow(KoShape *shape, QPainter &painter);
void blurShadow(QImage &image, int radius, const QColor& shadowColor);
};
-void KoShapeShadow::Private::paintGroupShadow(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter)
+void KoShapeShadow::Private::paintGroupShadow(KoShapeGroup *group, QPainter &painter)
{
QList<KoShape*> shapes = group->shapes();
Q_FOREACH (KoShape *child, shapes) {
// we paint recursively here, so we do not have to check recursively for visibility
if (!child->isVisible(false))
continue;
painter.save();
//apply group child's transformation
- painter.setTransform(child->absoluteTransformation(&converter), true);
- paintShadow(child, painter, converter);
+ painter.setTransform(child->absoluteTransformation(), true);
+ paintShadow(child, painter);
painter.restore();
}
}
-void KoShapeShadow::Private::paintShadow(KoShape *shape, QPainter &painter, const KoViewConverter &converter)
+void KoShapeShadow::Private::paintShadow(KoShape *shape, QPainter &painter)
{
QPainterPath path(shape->shadowOutline());
if (!path.isEmpty()) {
painter.save();
- KoShape::applyConversion(painter, converter);
painter.setBrush(QBrush(color));
// Make sure the shadow has the same fill rule as the shape.
KoPathShape * pathShape = dynamic_cast<KoPathShape*>(shape);
if (pathShape)
path.setFillRule(pathShape->fillRule());
painter.drawPath(path);
painter.restore();
}
if (shape->stroke()) {
- shape->stroke()->paint(shape, painter, converter);
+ shape->stroke()->paint(shape, painter);
}
}
/* You can also find a BSD version to this method from
- * http://gitorious.org/ofi-labs/x2/blobs/master/graphics/shadowblur/
+ * https://github.com/ariya/X2/tree/master/graphics/shadowblur
*/
void KoShapeShadow::Private::blurShadow(QImage &image, int radius, const QColor& shadowColor)
{
static const int BlurSumShift = 15;
// Check http://www.w3.org/TR/SVG/filters.html#
// As noted in the SVG filter specification, ru
// approximates a real gaussian blur nicely.
- // See comments in http://webkit.org/b/40793, it seems sensible
+ // See comments in https://bugs.webkit.org/show_bug.cgi?id=40793, it seems sensible
// to follow Skia's limit of 128 pixels for the blur radius.
if (radius > 128)
radius = 128;
int channels[4] = { 3, 0, 1, 3 };
int dmax = radius >> 1;
int dmin = dmax - 1 + (radius & 1);
if (dmin < 0)
dmin = 0;
// Two stages: horizontal and vertical
for (int k = 0; k < 2; ++k) {
unsigned char* pixels = image.bits();
int stride = (k == 0) ? 4 : image.bytesPerLine();
int delta = (k == 0) ? image.bytesPerLine() : 4;
int jfinal = (k == 0) ? image.height() : image.width();
int dim = (k == 0) ? image.width() : image.height();
for (int j = 0; j < jfinal; ++j, pixels += delta) {
// For each step, we blur the alpha in a channel and store the result
// in another channel for the subsequent step.
// We use sliding window algorithm to accumulate the alpha values.
// This is much more efficient than computing the sum of each pixels
// covered by the box kernel size for each x.
for (int step = 0; step < 3; ++step) {
int side1 = (step == 0) ? dmin : dmax;
int side2 = (step == 1) ? dmin : dmax;
int pixelCount = side1 + 1 + side2;
int invCount = ((1 << BlurSumShift) + pixelCount - 1) / pixelCount;
int ofs = 1 + side2;
int alpha1 = pixels[channels[step]];
int alpha2 = pixels[(dim - 1) * stride + channels[step]];
unsigned char* ptr = pixels + channels[step + 1];
unsigned char* prev = pixels + stride + channels[step];
unsigned char* next = pixels + ofs * stride + channels[step];
int i;
int sum = side1 * alpha1 + alpha1;
int limit = (dim < side2 + 1) ? dim : side2 + 1;
for (i = 1; i < limit; ++i, prev += stride)
sum += *prev;
if (limit <= side2)
sum += (side2 - limit + 1) * alpha2;
limit = (side1 < dim) ? side1 : dim;
for (i = 0; i < limit; ptr += stride, next += stride, ++i, ++ofs) {
*ptr = (sum * invCount) >> BlurSumShift;
sum += ((ofs < dim) ? *next : alpha2) - alpha1;
}
prev = pixels + channels[step];
for (; ofs < dim; ptr += stride, prev += stride, next += stride, ++i, ++ofs) {
*ptr = (sum * invCount) >> BlurSumShift;
sum += (*next) - (*prev);
}
for (; i < dim; ptr += stride, prev += stride, ++i) {
*ptr = (sum * invCount) >> BlurSumShift;
sum += alpha2 - (*prev);
}
}
}
}
// "Colorize" with the right shadow color.
QPainter p(&image);
p.setCompositionMode(QPainter::CompositionMode_SourceIn);
p.fillRect(image.rect(), shadowColor);
p.end();
}
// ----------------------------------------------------------------
// KoShapeShadow
KoShapeShadow::KoShapeShadow()
: d(new Private())
{
}
KoShapeShadow::~KoShapeShadow()
{
delete d;
}
KoShapeShadow::KoShapeShadow(const KoShapeShadow &rhs)
: d(new Private(*rhs.d))
{
d->refCount = 0;
}
KoShapeShadow& KoShapeShadow::operator=(const KoShapeShadow &rhs)
{
*d = *rhs.d;
d->refCount = 0;
return *this;
}
void KoShapeShadow::fillStyle(KoGenStyle &style, KoShapeSavingContext &context)
{
Q_UNUSED(context);
style.addProperty("draw:shadow", d->visible ? "visible" : "hidden", KoGenStyle::GraphicType);
style.addProperty("draw:shadow-color", d->color.name(), KoGenStyle::GraphicType);
if (d->color.alphaF() != 1.0)
style.addProperty("draw:shadow-opacity", QString("%1%").arg(d->color.alphaF() * 100.0), KoGenStyle::GraphicType);
style.addProperty("draw:shadow-offset-x", QString("%1pt").arg(d->offset.x()), KoGenStyle::GraphicType);
style.addProperty("draw:shadow-offset-y", QString("%1pt").arg(d->offset.y()), KoGenStyle::GraphicType);
if (d->blur != 0)
style.addProperty("calligra:shadow-blur-radius", QString("%1pt").arg(d->blur), KoGenStyle::GraphicType);
}
-void KoShapeShadow::paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter)
+void KoShapeShadow::paint(KoShape *shape, QPainter &painter)
{
if (! d->visible)
return;
// So the approach we are taking here is to draw into a buffer image the size of boundingRect
// We offset by the shadow offset at the time we draw into the buffer
// Then we filter the image and draw it at the position of the bounding rect on canvas
+ QTransform documentToView = painter.transform();
+
//the boundingRect of the shape or the KoSelection boundingRect of the group
QRectF shadowRect = shape->boundingRect();
- QRectF zoomedClipRegion = converter.documentToView(shadowRect);
+ QRectF zoomedClipRegion = documentToView.mapRect(shadowRect);
// Init the buffer image
QImage sourceGraphic(zoomedClipRegion.size().toSize(), QImage::Format_ARGB32_Premultiplied);
sourceGraphic.fill(qRgba(0,0,0,0));
// Init the buffer painter
QPainter imagePainter(&sourceGraphic);
imagePainter.setPen(Qt::NoPen);
imagePainter.setBrush(Qt::NoBrush);
imagePainter.setRenderHint(QPainter::Antialiasing, painter.testRenderHint(QPainter::Antialiasing));
// Since our imagebuffer and the canvas don't align we need to offset our drawings
- imagePainter.translate(-1.0f*converter.documentToView(shadowRect.topLeft()));
+ imagePainter.translate(-1.0f*documentToView.map(shadowRect.topLeft()));
// Handle the shadow offset
- imagePainter.translate(converter.documentToView(offset()));
+ imagePainter.translate(documentToView.map(offset()));
KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
if (group) {
- d->paintGroupShadow(group, imagePainter, converter);
+ d->paintGroupShadow(group, imagePainter);
} else {
//apply shape's transformation
- imagePainter.setTransform(shape->absoluteTransformation(&converter), true);
+ imagePainter.setTransform(shape->absoluteTransformation(), true);
- d->paintShadow(shape, imagePainter, converter);
+ d->paintShadow(shape, imagePainter);
}
imagePainter.end();
// Blur the shadow (well the entire buffer)
- d->blurShadow(sourceGraphic, converter.documentToViewX(d->blur), d->color);
+ d->blurShadow(sourceGraphic, qRound(documentToView.m11() * d->blur), d->color);
// Paint the result
painter.save();
// The painter is initialized for us with canvas transform 'plus' shape transform
// we are only interested in the canvas transform so 'subtract' the shape transform part
- painter.setTransform(shape->absoluteTransformation(&converter).inverted() * painter.transform());
+ painter.setTransform(shape->absoluteTransformation().inverted() * painter.transform());
painter.drawImage(zoomedClipRegion.topLeft(), sourceGraphic);
painter.restore();
}
void KoShapeShadow::setOffset(const QPointF & offset)
{
d->offset = offset;
}
QPointF KoShapeShadow::offset() const
{
return d->offset;
}
void KoShapeShadow::setColor(const QColor &color)
{
d->color = color;
}
QColor KoShapeShadow::color() const
{
return d->color;
}
void KoShapeShadow::setBlur(qreal blur)
{
// force positive blur radius
d->blur = qAbs(blur);
}
qreal KoShapeShadow::blur() const
{
return d->blur;
}
void KoShapeShadow::setVisible(bool visible)
{
d->visible = visible;
}
bool KoShapeShadow::isVisible() const
{
return d->visible;
}
void KoShapeShadow::insets(KoInsets &insets) const
{
if (!d->visible) {
insets.top = 0;
insets.bottom = 0;
insets.left = 0;
insets.right = 0;
return;
}
qreal expand = d->blur;
insets.left = (d->offset.x() < 0.0) ? qAbs(d->offset.x()) : 0.0;
insets.top = (d->offset.y() < 0.0) ? qAbs(d->offset.y()) : 0.0;
insets.right = (d->offset.x() > 0.0) ? d->offset.x() : 0.0;
insets.bottom = (d->offset.y() > 0.0) ? d->offset.y() : 0.0;
insets.left += expand;
insets.top += expand;
insets.right += expand;
insets.bottom += expand;
}
bool KoShapeShadow::ref()
{
return d->refCount.ref();
}
bool KoShapeShadow::deref()
{
return d->refCount.deref();
}
int KoShapeShadow::useCount() const
{
return d->refCount;
}
diff --git a/libs/flake/KoShapeShadow.h b/libs/flake/KoShapeShadow.h
index 83b4f04a7e..6f6270481d 100644
--- a/libs/flake/KoShapeShadow.h
+++ b/libs/flake/KoShapeShadow.h
@@ -1,115 +1,114 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2010 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 KOSHAPESHADOW_H
#define KOSHAPESHADOW_H
#include "kritaflake_export.h"
#include <QtGlobal>
class KoShape;
class KoGenStyle;
class KoShapeSavingContext;
class QPainter;
class QPointF;
class QColor;
-class KoViewConverter;
struct KoInsets;
class KRITAFLAKE_EXPORT KoShapeShadow
{
public:
KoShapeShadow();
~KoShapeShadow();
KoShapeShadow(const KoShapeShadow &rhs);
KoShapeShadow& operator=(const KoShapeShadow &rhs);
/**
* Fills the style object
* @param style object
* @param context used for saving
*/
void fillStyle(KoGenStyle &style, KoShapeSavingContext &context);
/**
* Paints the shadow of the shape.
* @param shape the shape to paint around
* @param painter the painter to paint shadows to canvas
* @param converter to convert between internal and view coordinates.
*/
- void paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter);
+ void paint(KoShape *shape, QPainter &painter);
/**
* Sets the shadow offset from the topleft corner of the shape
* @param offset the shadow offset
*/
void setOffset(const QPointF &offset);
/// Returns the shadow offset
QPointF offset() const;
/**
* Sets the shadow color, including the shadow opacity.
* @param color the shadow color and opacity
*/
void setColor(const QColor &color);
/// Returns the shadow color including opacity
QColor color() const;
/**
* Sets the shadow blur radius of the shape
* @param blur the shadow blur radius
*/
void setBlur(qreal blur);
/// Returns the shadow blur radius
qreal blur() const;
/// Sets the shadow visibility
void setVisible(bool visible);
/// Returns if shadow is visible
bool isVisible() const;
/// Fills the insets object with the space the shadow takes around a shape
void insets(KoInsets &insets) const;
/**
* Increments the use-value.
* Returns true if the new value is non-zero, false otherwise.
*/
bool ref();
/**
* Decrements the use-value.
* Returns true if the new value is non-zero, false otherwise.
*/
bool deref();
/// Return the usage count
int useCount() const;
private:
class Private;
Private * const d;
};
#endif // KOSHAPESHADOW_H
diff --git a/libs/flake/KoShapeStroke.cpp b/libs/flake/KoShapeStroke.cpp
index a5153af3a4..2f84aa4a63 100644
--- a/libs/flake/KoShapeStroke.cpp
+++ b/libs/flake/KoShapeStroke.cpp
@@ -1,421 +1,418 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2008 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2007,2009 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2012 Inge Wallin <inge@lysator.liu.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.
*/
// Own
#include "KoShapeStroke.h"
// Posix
#include <math.h>
// Qt
#include <QPainterPath>
#include <QPainter>
// Calligra
#include <KoGenStyles.h>
#include <KoOdfGraphicStyles.h>
// Flake
-#include "KoViewConverter.h"
#include "KoShape.h"
#include "KoShapeSavingContext.h"
#include "KoPathShape.h"
#include "KoMarker.h"
#include "KoInsets.h"
#include <KoPathSegment.h>
#include <KoPathPoint.h>
#include <cmath>
#include "KisQPainterStateSaver.h"
#include "kis_global.h"
class Q_DECL_HIDDEN KoShapeStroke::Private
{
public:
Private(KoShapeStroke *_q) : q(_q) {}
KoShapeStroke *q;
- void paintBorder(KoShape *shape, QPainter &painter, const QPen &pen) const;
+ void paintBorder(const KoShape *shape, QPainter &painter, const QPen &pen) const;
QColor color;
QPen pen;
QBrush brush;
};
namespace {
QPair<qreal, qreal> anglesForSegment(KoPathSegment segment) {
const qreal eps = 1e-6;
if (segment.degree() < 3) {
segment = segment.toCubic();
}
QList<QPointF> points = segment.controlPoints();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(points.size() == 4, qMakePair(0.0, 0.0));
QPointF vec1 = points[1] - points[0];
QPointF vec2 = points[3] - points[2];
if (vec1.manhattanLength() < eps) {
points[1] = segment.pointAt(eps);
vec1 = points[1] - points[0];
}
if (vec2.manhattanLength() < eps) {
points[2] = segment.pointAt(1.0 - eps);
vec2 = points[3] - points[2];
}
const qreal angle1 = std::atan2(vec1.y(), vec1.x());
const qreal angle2 = std::atan2(vec2.y(), vec2.x());
return qMakePair(angle1, angle2);
}
}
-void KoShapeStroke::Private::paintBorder(KoShape *shape, QPainter &painter, const QPen &pen) const
+void KoShapeStroke::Private::paintBorder(const KoShape *shape, QPainter &painter, const QPen &pen) const
{
if (!pen.isCosmetic() && pen.style() != Qt::NoPen) {
- KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
+ const KoPathShape *pathShape = dynamic_cast<const KoPathShape *>(shape);
if (pathShape) {
QPainterPath path = pathShape->pathStroke(pen);
painter.fillPath(path, pen.brush());
if (!pathShape->hasMarkers()) return;
const bool autoFillMarkers = pathShape->autoFillMarkers();
KoMarker *startMarker = pathShape->marker(KoFlake::StartMarker);
KoMarker *midMarker = pathShape->marker(KoFlake::MidMarker);
KoMarker *endMarker = pathShape->marker(KoFlake::EndMarker);
for (int i = 0; i < pathShape->subpathCount(); i++) {
const int numSubPoints = pathShape->subpathPointCount(i);
if (numSubPoints < 2) continue;
const bool isClosedSubpath = pathShape->isClosedSubpath(i);
qreal firstAngle = 0.0;
{
KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, 0));
firstAngle= anglesForSegment(segment).first;
}
const int numSegments = isClosedSubpath ? numSubPoints : numSubPoints - 1;
qreal lastAngle = 0.0;
{
KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, numSegments - 1));
lastAngle = anglesForSegment(segment).second;
}
qreal previousAngle = 0.0;
for (int j = 0; j < numSegments; j++) {
KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, j));
QPair<qreal, qreal> angles = anglesForSegment(segment);
const qreal angle1 = angles.first;
const qreal angle2 = angles.second;
if (j == 0 && startMarker) {
const qreal angle = isClosedSubpath ? bisectorAngle(firstAngle, lastAngle) : firstAngle;
if (autoFillMarkers) {
startMarker->applyShapeStroke(shape, q, segment.first()->point(), pen.widthF(), angle);
}
startMarker->paintAtPosition(&painter, segment.first()->point(), pen.widthF(), angle);
}
if (j > 0 && midMarker) {
const qreal angle = bisectorAngle(previousAngle, angle1);
if (autoFillMarkers) {
midMarker->applyShapeStroke(shape, q, segment.first()->point(), pen.widthF(), angle);
}
midMarker->paintAtPosition(&painter, segment.first()->point(), pen.widthF(), angle);
}
if (j == numSegments - 1 && endMarker) {
const qreal angle = isClosedSubpath ? bisectorAngle(firstAngle, lastAngle) : lastAngle;
if (autoFillMarkers) {
endMarker->applyShapeStroke(shape, q, segment.second()->point(), pen.widthF(), angle);
}
endMarker->paintAtPosition(&painter, segment.second()->point(), pen.widthF(), angle);
}
previousAngle = angle2;
}
}
return;
}
painter.strokePath(shape->outline(), pen);
}
}
KoShapeStroke::KoShapeStroke()
: d(new Private(this))
{
d->color = QColor(Qt::black);
// we are not rendering stroke with zero width anymore
// so lets use a default width of 1.0
d->pen.setWidthF(1.0);
}
KoShapeStroke::KoShapeStroke(const KoShapeStroke &other)
: KoShapeStrokeModel(), d(new Private(this))
{
d->color = other.d->color;
d->pen = other.d->pen;
d->brush = other.d->brush;
}
KoShapeStroke::KoShapeStroke(qreal lineWidth, const QColor &color)
: d(new Private(this))
{
d->pen.setWidthF(qMax(qreal(0.0), lineWidth));
d->pen.setJoinStyle(Qt::MiterJoin);
d->color = color;
}
KoShapeStroke::~KoShapeStroke()
{
delete d;
}
KoShapeStroke &KoShapeStroke::operator = (const KoShapeStroke &rhs)
{
if (this == &rhs)
return *this;
d->pen = rhs.d->pen;
d->color = rhs.d->color;
d->brush = rhs.d->brush;
return *this;
}
void KoShapeStroke::fillStyle(KoGenStyle &style, KoShapeSavingContext &context) const
{
QPen pen = d->pen;
if (d->brush.gradient())
pen.setBrush(d->brush);
else
pen.setColor(d->color);
KoOdfGraphicStyles::saveOdfStrokeStyle(style, context.mainStyles(), pen);
}
void KoShapeStroke::strokeInsets(const KoShape *shape, KoInsets &insets) const
{
Q_UNUSED(shape);
// '0.5' --- since we draw a line half inside, and half outside the object.
qreal extent = 0.5 * (d->pen.widthF() >= 0 ? d->pen.widthF() : 1.0);
// if we have square cap, we need a little more space
// -> sqrt((0.5*penWidth)^2 + (0.5*penWidth)^2)
if (capStyle() == Qt::SquareCap) {
extent *= M_SQRT2;
}
if (joinStyle() == Qt::MiterJoin) {
// miter limit in Qt is normalized by the line width (and not half-width)
extent = qMax(extent, d->pen.widthF() * miterLimit());
}
insets.top = extent;
insets.bottom = extent;
insets.left = extent;
insets.right = extent;
}
qreal KoShapeStroke::strokeMaxMarkersInset(const KoShape *shape) const
{
qreal result = 0.0;
const KoPathShape *pathShape = dynamic_cast<const KoPathShape *>(shape);
if (pathShape && pathShape->hasMarkers()) {
const qreal lineWidth = d->pen.widthF();
QVector<const KoMarker*> markers;
markers << pathShape->marker(KoFlake::StartMarker);
markers << pathShape->marker(KoFlake::MidMarker);
markers << pathShape->marker(KoFlake::EndMarker);
Q_FOREACH (const KoMarker *marker, markers) {
if (marker) {
result = qMax(result, marker->maxInset(lineWidth));
}
}
}
return result;
}
bool KoShapeStroke::hasTransparency() const
{
return d->color.alpha() > 0;
}
QPen KoShapeStroke::resultLinePen() const
{
QPen pen = d->pen;
if (d->brush.gradient()) {
pen.setBrush(d->brush);
} else {
pen.setColor(d->color);
}
return pen;
}
-void KoShapeStroke::paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter)
+void KoShapeStroke::paint(const KoShape *shape, QPainter &painter) const
{
KisQPainterStateSaver saver(&painter);
- // TODO: move apply conversion to some centralized place
- KoShape::applyConversion(painter, converter);
d->paintBorder(shape, painter, resultLinePen());
}
bool KoShapeStroke::compareFillTo(const KoShapeStrokeModel *other)
{
if (!other) return false;
const KoShapeStroke *stroke = dynamic_cast<const KoShapeStroke*>(other);
if (!stroke) return false;
return (d->brush.gradient() && d->brush == stroke->d->brush) ||
(!d->brush.gradient() && d->color == stroke->d->color);
}
bool KoShapeStroke::compareStyleTo(const KoShapeStrokeModel *other)
{
if (!other) return false;
const KoShapeStroke *stroke = dynamic_cast<const KoShapeStroke*>(other);
if (!stroke) return false;
QPen pen1 = d->pen;
QPen pen2 = stroke->d->pen;
// just a random color top avoid comparison of that property
pen1.setColor(Qt::magenta);
pen2.setColor(Qt::magenta);
return pen1 == pen2;
}
bool KoShapeStroke::isVisible() const
{
return d->pen.widthF() > 0 &&
(d->brush.gradient() || d->color.alpha() > 0);
}
void KoShapeStroke::setCapStyle(Qt::PenCapStyle style)
{
d->pen.setCapStyle(style);
}
Qt::PenCapStyle KoShapeStroke::capStyle() const
{
return d->pen.capStyle();
}
void KoShapeStroke::setJoinStyle(Qt::PenJoinStyle style)
{
d->pen.setJoinStyle(style);
}
Qt::PenJoinStyle KoShapeStroke::joinStyle() const
{
return d->pen.joinStyle();
}
void KoShapeStroke::setLineWidth(qreal lineWidth)
{
d->pen.setWidthF(qMax(qreal(0.0), lineWidth));
}
qreal KoShapeStroke::lineWidth() const
{
return d->pen.widthF();
}
void KoShapeStroke::setMiterLimit(qreal miterLimit)
{
d->pen.setMiterLimit(miterLimit);
}
qreal KoShapeStroke::miterLimit() const
{
return d->pen.miterLimit();
}
QColor KoShapeStroke::color() const
{
return d->color;
}
void KoShapeStroke::setColor(const QColor &color)
{
d->color = color;
}
void KoShapeStroke::setLineStyle(Qt::PenStyle style, const QVector<qreal> &dashes)
{
if (style < Qt::CustomDashLine) {
d->pen.setStyle(style);
} else {
d->pen.setDashPattern(dashes);
}
}
Qt::PenStyle KoShapeStroke::lineStyle() const
{
return d->pen.style();
}
QVector<qreal> KoShapeStroke::lineDashes() const
{
return d->pen.dashPattern();
}
void KoShapeStroke::setDashOffset(qreal dashOffset)
{
d->pen.setDashOffset(dashOffset);
}
qreal KoShapeStroke::dashOffset() const
{
return d->pen.dashOffset();
}
void KoShapeStroke::setLineBrush(const QBrush &brush)
{
d->brush = brush;
}
QBrush KoShapeStroke::lineBrush() const
{
return d->brush;
}
diff --git a/libs/flake/KoShapeStroke.h b/libs/flake/KoShapeStroke.h
index bbb844125a..154e8c0de2 100644
--- a/libs/flake/KoShapeStroke.h
+++ b/libs/flake/KoShapeStroke.h
@@ -1,123 +1,122 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2008 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2007,2009 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2012 Inge Wallin <inge@lysator.liu.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.
*/
#ifndef KOSHAPESTROKE_H
#define KOSHAPESTROKE_H
#include "KoFlakeTypes.h"
#include "KoShapeStrokeModel.h"
#include "kritaflake_export.h"
#include <QMetaType>
#include <QColor>
class KoShape;
class QPainter;
class QBrush;
class QPen;
-class KoViewConverter;
struct KoInsets;
/**
* A border for shapes that draws a single line around the object.
*/
class KRITAFLAKE_EXPORT KoShapeStroke : public KoShapeStrokeModel
{
public:
/// Constructor for a thin line in black
KoShapeStroke();
/// Copy constructor
KoShapeStroke(const KoShapeStroke &other);
/**
* Constructor for a Stroke
* @param lineWidth the width, in pt
* @param color the color we draw the outline in.
*/
explicit KoShapeStroke(qreal lineWidth, const QColor &color = Qt::black);
~KoShapeStroke() override;
/// Assignment operator
KoShapeStroke& operator = (const KoShapeStroke &rhs);
/// Sets the lines cap style
void setCapStyle(Qt::PenCapStyle style);
/// Returns the lines cap style
Qt::PenCapStyle capStyle() const;
/// Sets the lines join style
void setJoinStyle(Qt::PenJoinStyle style);
/// Returns the lines join style
Qt::PenJoinStyle joinStyle() const;
/// Sets the line width
void setLineWidth(qreal lineWidth);
/// Returns the line width
qreal lineWidth() const;
/// Sets the miter limit
void setMiterLimit(qreal miterLimit);
/// Returns the miter limit
qreal miterLimit() const;
/// Sets the line style
void setLineStyle(Qt::PenStyle style, const QVector<qreal> &dashes);
/// Returns the line style
Qt::PenStyle lineStyle() const;
/// Returns the line dashes
QVector<qreal> lineDashes() const;
/// Sets the dash offset
void setDashOffset(qreal dashOffset);
/// Returns the dash offset
qreal dashOffset() const;
/// Returns the color
QColor color() const;
/// Sets the color
void setColor(const QColor &color);
/// Sets the strokes brush used to fill strokes of this border
void setLineBrush(const QBrush & brush);
/// Returns the strokes brush
QBrush lineBrush() const;
// pure virtuals from KoShapeStrokeModel implemented here.
void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) const override;
void strokeInsets(const KoShape *shape, KoInsets &insets) const override;
qreal strokeMaxMarkersInset(const KoShape *shape) const override;
bool hasTransparency() const override;
- void paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter) override;
+ void paint(const KoShape *shape, QPainter &painter) const override;
QPen resultLinePen() const;
bool compareFillTo(const KoShapeStrokeModel *other) override;
bool compareStyleTo(const KoShapeStrokeModel *other) override;
bool isVisible() const override;
private:
class Private;
Private * const d;
};
Q_DECLARE_METATYPE( KoShapeStroke )
#endif // KOSHAPESTROKE_H
diff --git a/libs/flake/KoShapeStrokeModel.h b/libs/flake/KoShapeStrokeModel.h
index 37c19be820..45c5ab145d 100644
--- a/libs/flake/KoShapeStrokeModel.h
+++ b/libs/flake/KoShapeStrokeModel.h
@@ -1,93 +1,91 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* Copyright (C) 2007,2009 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2012 Inge Wallin <inge@lysator.liu.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.
*/
#ifndef KOSHAPESTROKEMODEL_H
#define KOSHAPESTROKEMODEL_H
#include "kritaflake_export.h"
#include <QtGlobal>
class KoShape;
class KoGenStyle;
class KoShapeSavingContext;
-class KoViewConverter;
struct KoInsets;
class QColor;
class QPainter;
/**
* A model for strokes of KoShapes.
* Classes that implement this model will be allowed to draw the stroke of the outline
* of a shape.
* Note that since the important members take a KoShape as argument it is possible,
* and preferred behavior, to have one instance of a stroke that is reused on several
* objects.
*/
class KRITAFLAKE_EXPORT KoShapeStrokeModel
{
public:
virtual ~KoShapeStrokeModel();
/**
* @brief Fill the style object (aka save)
*
* @param style object
* @param context used for saving
*/
virtual void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) const = 0;
/**
* Return a strokeInsets object filled with the size inside the shape that this stroke takes.
* @param shape the shape the insets will be calculated for
* @param insets the insets object that will be filled and returned.
*/
virtual void strokeInsets(const KoShape *shape, KoInsets &insets) const = 0;
/**
* Return a maximum distance that the markers of the shape can take outside the
* shape itself
*/
virtual qreal strokeMaxMarkersInset(const KoShape *shape) const = 0;
/**
* Returns true if there is some transparency, false if the stroke is fully opaque.
* @return if the stroke is transparent.
*/
virtual bool hasTransparency() const = 0;
/**
* Paint the stroke.
* This method should paint the stroke around shape.
* @param shape the shape to paint around
* @param painter the painter to paint to, the painter will have the topleft of the
* shape as its start coordinate.
- * @param converter to convert between internal and view coordinates.
*/
- virtual void paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter) = 0;
+ virtual void paint(const KoShape *shape, QPainter &painter) const = 0;
virtual bool compareFillTo(const KoShapeStrokeModel *other) = 0;
virtual bool compareStyleTo(const KoShapeStrokeModel *other) = 0;
virtual bool isVisible() const = 0;
};
#endif
diff --git a/libs/flake/KoShape_p.h b/libs/flake/KoShape_p.h
index 7f4a4fc3ba..fbd34abf42 100644
--- a/libs/flake/KoShape_p.h
+++ b/libs/flake/KoShape_p.h
@@ -1,117 +1,121 @@
/* This file is part of the KDE project
* Copyright (C) 2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 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 KOSHAPEPRIVATE_H
#define KOSHAPEPRIVATE_H
#include "KoShape.h"
#include <QPoint>
#include <QPaintDevice>
#include <QTransform>
#include <QScopedPointer>
#include <QSharedData>
#include <KoClipMask.h>
class KoBorder;
class KoShapeManager;
-class KoShape::Private : public QSharedData
+class KoShape::SharedData : public QSharedData
{
public:
- explicit Private();
- virtual ~Private();
+ explicit SharedData();
+ virtual ~SharedData();
- explicit Private(const Private &rhs);
+ explicit SharedData(const SharedData &rhs);
/**
* Fills the style stack and returns the value of the given style property (e.g fill, stroke).
*/
static QString getStyleProperty(const char *property, KoShapeLoadingContext &context);
/// Loads the shadow style
KoShapeShadow *loadOdfShadow(KoShapeLoadingContext &context) const;
// Loads the border style.
KoBorder *loadOdfBorder(KoShapeLoadingContext &context) const;
public:
// Members
mutable QSizeF size; // size in pt
QString shapeId;
QString name; ///< the shapes names
QTransform localMatrix; ///< the shapes local transformation matrix
KoConnectionPoints connectors; ///< glue point id to data mapping
- KoShapeContainer *parent;
- QSet<KoShapeManager *> shapeManagers;
- QSet<KoShape *> toolDelegates;
QScopedPointer<KoShapeUserData> userData;
QSharedPointer<KoShapeStrokeModel> stroke; ///< points to a stroke, or 0 if there is no stroke
QSharedPointer<KoShapeBackground> fill; ///< Stands for the background color / fill etc.
bool inheritBackground = false;
bool inheritStroke = false;
- QList<KoShape*> dependees; ///< list of shape dependent on this shape
- QList<KoShape::ShapeChangeListener*> listeners;
KoShapeShadow * shadow; ///< the current shape shadow
KoBorder *border; ///< the current shape border
// XXX: change this to instance instead of pointer
QScopedPointer<KoClipPath> clipPath; ///< the current clip path
QScopedPointer<KoClipMask> clipMask; ///< the current clip mask
QMap<QString, QString> additionalAttributes;
QMap<QByteArray, QString> additionalStyleAttributes;
KoFilterEffectStack *filterEffectStack; ///< stack of filter effects applied to the shape
qreal transparency; ///< the shapes transparency
QString hyperLink; //hyperlink for this shape
int zIndex : 16; // keep maxZIndex in sync!
int runThrough : 16;
int visible : 1;
int printable : 1;
int geometryProtected : 1;
int keepAspect : 1;
int selectable : 1;
- int detectCollision : 1;
int protectContent : 1;
KoShape::TextRunAroundSide textRunAroundSide;
qreal textRunAroundDistanceLeft;
qreal textRunAroundDistanceTop;
qreal textRunAroundDistanceRight;
qreal textRunAroundDistanceBottom;
qreal textRunAroundThreshold;
KoShape::TextRunAroundContour textRunAroundContour;
public:
/// Connection point converters
/// Convert connection point position from shape coordinates, taking alignment into account
void convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const;
/// Convert connection point position to shape coordinates, taking alignment into account
void convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const;
};
+class KoShape::Private
+{
+public:
+ KoShapeContainer *parent;
+ QSet<KoShapeManager *> shapeManagers;
+ QSet<KoShape *> toolDelegates;
+ QList<KoShape*> dependees; ///< list of shape dependent on this shape
+ QList<KoShape::ShapeChangeListener*> listeners;
+};
+
#endif
diff --git a/libs/flake/KoSnapProxy.cpp b/libs/flake/KoSnapProxy.cpp
index 4c8119ba32..bd7a3c3e3b 100644
--- a/libs/flake/KoSnapProxy.cpp
+++ b/libs/flake/KoSnapProxy.cpp
@@ -1,187 +1,187 @@
/* This file is part of the KDE project
* Copyright (C) 2008-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 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 "KoSnapProxy.h"
#include "KoSnapGuide.h"
#include "KoCanvasBase.h"
#include "KoShapeManager.h"
#include "KoPathShape.h"
#include "KoPathPoint.h"
#include <KoSnapData.h>
KoSnapProxy::KoSnapProxy(KoSnapGuide * snapGuide)
: m_snapGuide(snapGuide)
{
}
QList<QPointF> KoSnapProxy::pointsInRect(const QRectF &rect, bool omitEditedShape)
{
QList<QPointF> points;
QList<KoShape*> shapes = shapesInRect(rect, omitEditedShape);
Q_FOREACH (KoShape * shape, shapes) {
Q_FOREACH (const QPointF & point, pointsFromShape(shape)) {
if (rect.contains(point))
points.append(point);
}
}
return points;
}
QList<KoShape*> KoSnapProxy::shapesInRect(const QRectF &rect, bool omitEditedShape)
{
QList<KoShape*> shapes = m_snapGuide->canvas()->shapeManager()->shapesAt(rect);
Q_FOREACH (KoShape * shape, m_snapGuide->ignoredShapes()) {
const int index = shapes.indexOf(shape);
if (index >= 0) {
shapes.removeAt(index);
}
}
if (omitEditedShape) {
Q_FOREACH (KoPathPoint *point, m_snapGuide->ignoredPathPoints()) {
const int index = shapes.indexOf(point->parent());
if (index >= 0) {
shapes.removeAt(index);
}
}
}
if (!omitEditedShape && m_snapGuide->additionalEditedShape()) {
QRectF bound = m_snapGuide->additionalEditedShape()->boundingRect();
if (rect.intersects(bound) || rect.contains(bound))
shapes.append(m_snapGuide->additionalEditedShape());
}
return shapes;
}
QList<QPointF> KoSnapProxy::pointsFromShape(KoShape * shape)
{
QList<QPointF> snapPoints;
// no snapping to hidden shapes
if (! shape->isVisible())
return snapPoints;
// return the special snap points of the shape
snapPoints += shape->snapData().snapPoints();
KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
if (path) {
- QTransform m = path->absoluteTransformation(0);
+ QTransform m = path->absoluteTransformation();
QList<KoPathPoint*> ignoredPoints = m_snapGuide->ignoredPathPoints();
int subpathCount = path->subpathCount();
for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) {
int pointCount = path->subpathPointCount(subpathIndex);
for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
KoPathPoint * p = path->pointByIndex(KoPathPointIndex(subpathIndex, pointIndex));
if (! p || ignoredPoints.contains(p))
continue;
snapPoints.append(m.map(p->point()));
}
}
}
else
{
// add the bounding box corners as default snap points
QRectF bbox = shape->boundingRect();
snapPoints.append(bbox.topLeft());
snapPoints.append(bbox.topRight());
snapPoints.append(bbox.bottomRight());
snapPoints.append(bbox.bottomLeft());
}
return snapPoints;
}
QList<KoPathSegment> KoSnapProxy::segmentsInRect(const QRectF &rect, bool omitEditedShape)
{
QList<KoShape*> shapes = shapesInRect(rect, omitEditedShape);
QList<KoPathPoint*> ignoredPoints = m_snapGuide->ignoredPathPoints();
QList<KoPathSegment> segments;
Q_FOREACH (KoShape * shape, shapes) {
QList<KoPathSegment> shapeSegments;
QRectF rectOnShape = shape->documentToShape(rect);
KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
if (path) {
shapeSegments = path->segmentsAt(rectOnShape);
} else {
Q_FOREACH (const KoPathSegment & s, shape->snapData().snapSegments()) {
QRectF controlRect = s.controlPointRect();
if (! rect.intersects(controlRect) && ! controlRect.contains(rect))
continue;
QRectF bound = s.boundingRect();
if (! rect.intersects(bound) && ! bound.contains(rect))
continue;
shapeSegments.append(s);
}
}
- QTransform m = shape->absoluteTransformation(0);
+ QTransform m = shape->absoluteTransformation();
// transform segments to document coordinates
Q_FOREACH (const KoPathSegment & s, shapeSegments) {
if (ignoredPoints.contains(s.first()) || ignoredPoints.contains(s.second()))
continue;
segments.append(s.mapped(m));
}
}
return segments;
}
QList<KoShape*> KoSnapProxy::shapes(bool omitEditedShape)
{
QList<KoShape*> allShapes = m_snapGuide->canvas()->shapeManager()->shapes();
QList<KoShape*> filteredShapes;
QList<KoShape*> ignoredShapes = m_snapGuide->ignoredShapes();
// filter all hidden and ignored shapes
Q_FOREACH (KoShape * shape, allShapes) {
if (shape->isVisible() &&
!ignoredShapes.contains(shape)) {
filteredShapes.append(shape);
}
}
if (omitEditedShape) {
Q_FOREACH (KoPathPoint *point, m_snapGuide->ignoredPathPoints()) {
const int index = filteredShapes.indexOf(point->parent());
if (index >= 0) {
filteredShapes.removeAt(index);
}
}
}
if (!omitEditedShape && m_snapGuide->additionalEditedShape()) {
filteredShapes.append(m_snapGuide->additionalEditedShape());
}
return filteredShapes;
}
KoCanvasBase * KoSnapProxy::canvas()
{
return m_snapGuide->canvas();
}
diff --git a/libs/flake/KoSnapStrategy.cpp b/libs/flake/KoSnapStrategy.cpp
index f18c1dd23d..7f1e004fe3 100644
--- a/libs/flake/KoSnapStrategy.cpp
+++ b/libs/flake/KoSnapStrategy.cpp
@@ -1,641 +1,641 @@
/* This file is part of the KDE project
* Copyright (C) 2008 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 "KoSnapStrategy.h"
#include "KoSnapProxy.h"
#include "KoSnapGuide.h"
#include <KoPathShape.h>
#include <KoPathPoint.h>
#include <KoPathSegment.h>
#include <KoCanvasBase.h>
#include <KoViewConverter.h>
#include <QPainter>
#include <cmath>
#if defined(_MSC_VER) && (_MSC_VER < 1800)
#define isfinite(x) (double)(x)
#endif
KoSnapStrategy::KoSnapStrategy(KoSnapGuide::Strategy type)
: m_snapType(type)
{
}
QPointF KoSnapStrategy::snappedPosition() const
{
return m_snappedPosition;
}
void KoSnapStrategy::setSnappedPosition(const QPointF &position)
{
m_snappedPosition = position;
}
KoSnapGuide::Strategy KoSnapStrategy::type() const
{
return m_snapType;
}
qreal KoSnapStrategy::squareDistance(const QPointF &p1, const QPointF &p2)
{
const qreal dx = p1.x() - p2.x();
const qreal dy = p1.y() - p2.y();
return dx*dx + dy*dy;
}
qreal KoSnapStrategy::scalarProduct(const QPointF &p1, const QPointF &p2)
{
return p1.x() * p2.x() + p1.y() * p2.y();
}
OrthogonalSnapStrategy::OrthogonalSnapStrategy()
: KoSnapStrategy(KoSnapGuide::OrthogonalSnapping)
{
}
bool OrthogonalSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
{
Q_ASSERT(std::isfinite(maxSnapDistance));
QPointF horzSnap, vertSnap;
qreal minVertDist = HUGE_VAL;
qreal minHorzDist = HUGE_VAL;
- QList<KoShape*> shapes = proxy->shapes();
+ QList<KoShape*> shapes = proxy->shapes(true);
Q_FOREACH (KoShape * shape, shapes) {
QList<QPointF> points = proxy->pointsFromShape(shape);
foreach (const QPointF &point, points) {
qreal dx = fabs(point.x() - mousePosition.x());
if (dx < minHorzDist && dx < maxSnapDistance) {
minHorzDist = dx;
horzSnap = point;
}
qreal dy = fabs(point.y() - mousePosition.y());
if (dy < minVertDist && dy < maxSnapDistance) {
minVertDist = dy;
vertSnap = point;
}
}
}
QPointF snappedPoint = mousePosition;
if (minHorzDist < HUGE_VAL)
snappedPoint.setX(horzSnap.x());
if (minVertDist < HUGE_VAL)
snappedPoint.setY(vertSnap.y());
if (minHorzDist < HUGE_VAL)
m_hLine = QLineF(horzSnap, snappedPoint);
else
m_hLine = QLineF();
if (minVertDist < HUGE_VAL)
m_vLine = QLineF(vertSnap, snappedPoint);
else
m_vLine = QLineF();
setSnappedPosition(snappedPoint);
return (minHorzDist < HUGE_VAL || minVertDist < HUGE_VAL);
}
QPainterPath OrthogonalSnapStrategy::decoration(const KoViewConverter &/*converter*/) const
{
QPainterPath decoration;
if (! m_hLine.isNull()) {
decoration.moveTo(m_hLine.p1());
decoration.lineTo(m_hLine.p2());
}
if (! m_vLine.isNull()) {
decoration.moveTo(m_vLine.p1());
decoration.lineTo(m_vLine.p2());
}
return decoration;
}
NodeSnapStrategy::NodeSnapStrategy()
: KoSnapStrategy(KoSnapGuide::NodeSnapping)
{
}
bool NodeSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
{
Q_ASSERT(std::isfinite(maxSnapDistance));
const qreal maxDistance = maxSnapDistance * maxSnapDistance;
qreal minDistance = HUGE_VAL;
QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance);
rect.moveCenter(mousePosition);
QList<QPointF> points = proxy->pointsInRect(rect, false);
QPointF snappedPoint = mousePosition;
foreach (const QPointF &point, points) {
qreal distance = squareDistance(mousePosition, point);
if (distance < maxDistance && distance < minDistance) {
snappedPoint = point;
minDistance = distance;
}
}
setSnappedPosition(snappedPoint);
return (minDistance < HUGE_VAL);
}
QPainterPath NodeSnapStrategy::decoration(const KoViewConverter &converter) const
{
QRectF unzoomedRect = converter.viewToDocument(QRectF(0, 0, 11, 11));
unzoomedRect.moveCenter(snappedPosition());
QPainterPath decoration;
decoration.addEllipse(unzoomedRect);
return decoration;
}
ExtensionSnapStrategy::ExtensionSnapStrategy()
: KoSnapStrategy(KoSnapGuide::ExtensionSnapping)
{
}
bool ExtensionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
{
Q_ASSERT(std::isfinite(maxSnapDistance));
const qreal maxDistance = maxSnapDistance * maxSnapDistance;
qreal minDistances[2] = { HUGE_VAL, HUGE_VAL };
QPointF snappedPoints[2] = { mousePosition, mousePosition };
QPointF startPoints[2];
QList<KoShape*> shapes = proxy->shapes(true);
Q_FOREACH (KoShape * shape, shapes) {
KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
if (! path) {
continue;
}
- QTransform matrix = path->absoluteTransformation(0);
+ QTransform matrix = path->absoluteTransformation();
const int subpathCount = path->subpathCount();
for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) {
if (path->isClosedSubpath(subpathIndex))
continue;
int pointCount = path->subpathPointCount(subpathIndex);
// check the extension from the start point
KoPathPoint * first = path->pointByIndex(KoPathPointIndex(subpathIndex, 0));
QPointF firstSnapPosition = mousePosition;
if (snapToExtension(firstSnapPosition, first, matrix)) {
qreal distance = squareDistance(firstSnapPosition, mousePosition);
if (distance < maxDistance) {
if (distance < minDistances[0]) {
minDistances[1] = minDistances[0];
snappedPoints[1] = snappedPoints[0];
startPoints[1] = startPoints[0];
minDistances[0] = distance;
snappedPoints[0] = firstSnapPosition;
startPoints[0] = matrix.map(first->point());
}
else if (distance < minDistances[1]) {
minDistances[1] = distance;
snappedPoints[1] = firstSnapPosition;
startPoints[1] = matrix.map(first->point());
}
}
}
// now check the extension from the last point
KoPathPoint * last = path->pointByIndex(KoPathPointIndex(subpathIndex, pointCount - 1));
QPointF lastSnapPosition = mousePosition;
if (snapToExtension(lastSnapPosition, last, matrix)) {
qreal distance = squareDistance(lastSnapPosition, mousePosition);
if (distance < maxDistance) {
if (distance < minDistances[0]) {
minDistances[1] = minDistances[0];
snappedPoints[1] = snappedPoints[0];
startPoints[1] = startPoints[0];
minDistances[0] = distance;
snappedPoints[0] = lastSnapPosition;
startPoints[0] = matrix.map(last->point());
}
else if (distance < minDistances[1]) {
minDistances[1] = distance;
snappedPoints[1] = lastSnapPosition;
startPoints[1] = matrix.map(last->point());
}
}
}
}
}
m_lines.clear();
// if we have to extension near our mouse position, they might have an intersection
// near our mouse position which we want to use as the snapped position
if (minDistances[0] < HUGE_VAL && minDistances[1] < HUGE_VAL) {
// check if intersection of extension lines is near mouse position
KoPathSegment s1(startPoints[0], snappedPoints[0] + snappedPoints[0]-startPoints[0]);
KoPathSegment s2(startPoints[1], snappedPoints[1] + snappedPoints[1]-startPoints[1]);
QList<QPointF> isects = s1.intersections(s2);
if (isects.count() == 1 && squareDistance(isects[0], mousePosition) < maxDistance) {
// add both extension lines
m_lines.append(QLineF(startPoints[0], isects[0]));
m_lines.append(QLineF(startPoints[1], isects[0]));
setSnappedPosition(isects[0]);
}
else {
// only add nearest extension line of both
uint index = minDistances[0] < minDistances[1] ? 0 : 1;
m_lines.append(QLineF(startPoints[index], snappedPoints[index]));
setSnappedPosition(snappedPoints[index]);
}
}
else if (minDistances[0] < HUGE_VAL) {
m_lines.append(QLineF(startPoints[0], snappedPoints[0]));
setSnappedPosition(snappedPoints[0]);
}
else if (minDistances[1] < HUGE_VAL) {
m_lines.append(QLineF(startPoints[1], snappedPoints[1]));
setSnappedPosition(snappedPoints[1]);
}
else {
// none of the extension lines is near our mouse position
return false;
}
return true;
}
QPainterPath ExtensionSnapStrategy::decoration(const KoViewConverter &/*converter*/) const
{
QPainterPath decoration;
foreach (const QLineF &line, m_lines) {
decoration.moveTo(line.p1());
decoration.lineTo(line.p2());
}
return decoration;
}
bool ExtensionSnapStrategy::snapToExtension(QPointF &position, KoPathPoint * point, const QTransform &matrix)
{
Q_ASSERT(point);
QPointF direction = extensionDirection(point, matrix);
if (direction.isNull())
return false;
QPointF extensionStart = matrix.map(point->point());
QPointF extensionStop = matrix.map(point->point()) + direction;
float posOnExtension = project(extensionStart, extensionStop, position);
if (posOnExtension < 0.0)
return false;
position = extensionStart + posOnExtension * direction;
return true;
}
qreal ExtensionSnapStrategy::project(const QPointF &lineStart, const QPointF &lineEnd, const QPointF &point)
{
// This is how the returned value should be used to get the
// projectionPoint: ProjectionPoint = lineStart(1-resultingReal) + resultingReal*lineEnd;
QPointF diff = lineEnd - lineStart;
QPointF relPoint = point - lineStart;
qreal diffLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
if (diffLength == 0.0)
return 0.0;
diff /= diffLength;
// project mouse position relative to stop position on extension line
qreal scalar = relPoint.x() * diff.x() + relPoint.y() * diff.y();
return scalar /= diffLength;
}
QPointF ExtensionSnapStrategy::extensionDirection(KoPathPoint * point, const QTransform &matrix)
{
Q_ASSERT(point);
KoPathShape * path = point->parent();
KoPathPointIndex index = path->pathPointIndex(point);
// check if it is a start point
if (point->properties() & KoPathPoint::StartSubpath) {
if (point->activeControlPoint2()) {
return matrix.map(point->point()) - matrix.map(point->controlPoint2());
} else {
KoPathPoint * next = path->pointByIndex(KoPathPointIndex(index.first, index.second + 1));
if (! next){
return QPointF();
}
else if (next->activeControlPoint1()) {
return matrix.map(point->point()) - matrix.map(next->controlPoint1());
}
else {
return matrix.map(point->point()) - matrix.map(next->point());
}
}
}
else {
if (point->activeControlPoint1()) {
return matrix.map(point->point()) - matrix.map(point->controlPoint1());
}
else {
KoPathPoint * prev = path->pointByIndex(KoPathPointIndex(index.first, index.second - 1));
if (! prev){
return QPointF();
}
else if (prev->activeControlPoint2()) {
return matrix.map(point->point()) - matrix.map(prev->controlPoint2());
}
else {
return matrix.map(point->point()) - matrix.map(prev->point());
}
}
}
}
IntersectionSnapStrategy::IntersectionSnapStrategy()
: KoSnapStrategy(KoSnapGuide::IntersectionSnapping)
{
}
bool IntersectionSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance)
{
Q_ASSERT(std::isfinite(maxSnapDistance));
const qreal maxDistance = maxSnapDistance * maxSnapDistance;
qreal minDistance = HUGE_VAL;
QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance);
rect.moveCenter(mousePosition);
QPointF snappedPoint = mousePosition;
QList<KoPathSegment> segments = proxy->segmentsInRect(rect, false);
int segmentCount = segments.count();
for (int i = 0; i < segmentCount; ++i) {
const KoPathSegment &s1 = segments[i];
for (int j = i + 1; j < segmentCount; ++j) {
QList<QPointF> isects = s1.intersections(segments[j]);
Q_FOREACH (const QPointF &point, isects) {
if (! rect.contains(point))
continue;
qreal distance = squareDistance(mousePosition, point);
if (distance < maxDistance && distance < minDistance) {
snappedPoint = point;
minDistance = distance;
}
}
}
}
setSnappedPosition(snappedPoint);
return (minDistance < HUGE_VAL);
}
QPainterPath IntersectionSnapStrategy::decoration(const KoViewConverter &converter) const
{
QRectF unzoomedRect = converter.viewToDocument(QRectF(0, 0, 11, 11));
unzoomedRect.moveCenter(snappedPosition());
QPainterPath decoration;
decoration.addRect(unzoomedRect);
return decoration;
}
GridSnapStrategy::GridSnapStrategy()
: KoSnapStrategy(KoSnapGuide::GridSnapping)
{
}
bool GridSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance)
{
Q_ASSERT(std::isfinite(maxSnapDistance));
if (! proxy->canvas()->snapToGrid())
return false;
// The 1e-10 here is a workaround for some weird division problem.
// 360.00062366 / 2.83465058 gives 127 'exactly' when shown as a qreal,
// but when casting into an int, we get 126. In fact it's 127 - 5.64e-15 !
QPointF offset;
QSizeF spacing;
proxy->canvas()->gridSize(&offset, &spacing);
// we want to snap to the nearest grid point, so calculate
// the grid rows/columns before and after the points position
int col = static_cast<int>((mousePosition.x() - offset.x()) / spacing.width() + 1e-10);
int nextCol = col + 1;
int row = static_cast<int>((mousePosition.y() - offset.y()) / spacing.height() + 1e-10);
int nextRow = row + 1;
// now check which grid line has less distance to the point
qreal distToCol = qAbs(offset.x() + col * spacing.width() - mousePosition.x());
qreal distToNextCol = qAbs(offset.x() + nextCol * spacing.width() - mousePosition.x());
if (distToCol > distToNextCol) {
col = nextCol;
distToCol = distToNextCol;
}
qreal distToRow = qAbs(offset.y() + row * spacing.height() - mousePosition.y());
qreal distToNextRow = qAbs(offset.y() + nextRow * spacing.height() - mousePosition.y());
if (distToRow > distToNextRow) {
row = nextRow;
distToRow = distToNextRow;
}
QPointF snappedPoint = mousePosition;
bool pointIsSnapped = false;
const qreal sqDistance = distToCol * distToCol + distToRow * distToRow;
const qreal maxSqDistance = maxSnapDistance * maxSnapDistance;
// now check if we are inside the snap distance
if (sqDistance < maxSqDistance) {
snappedPoint = QPointF(offset.x() + col * spacing.width(), offset.y() + row * spacing.height());
pointIsSnapped = true;
} else if (distToRow < maxSnapDistance) {
snappedPoint.ry() = offset.y() + row * spacing.height();
pointIsSnapped = true;
} else if (distToCol < maxSnapDistance) {
snappedPoint.rx() = offset.x() + col * spacing.width();
pointIsSnapped = true;
}
setSnappedPosition(snappedPoint);
return pointIsSnapped;
}
QPainterPath GridSnapStrategy::decoration(const KoViewConverter &converter) const
{
QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5));
QPainterPath decoration;
decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), 0));
decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), 0));
decoration.moveTo(snappedPosition() - QPointF(0, unzoomedSize.height()));
decoration.lineTo(snappedPosition() + QPointF(0, unzoomedSize.height()));
return decoration;
}
BoundingBoxSnapStrategy::BoundingBoxSnapStrategy()
: KoSnapStrategy(KoSnapGuide::BoundingBoxSnapping)
{
}
bool BoundingBoxSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance)
{
Q_ASSERT(std::isfinite(maxSnapDistance));
const qreal maxDistance = maxSnapDistance * maxSnapDistance;
qreal minDistance = HUGE_VAL;
QRectF rect(-maxSnapDistance, -maxSnapDistance, maxSnapDistance, maxSnapDistance);
rect.moveCenter(mousePosition);
QPointF snappedPoint = mousePosition;
KoFlake::AnchorPosition pointId[5] = {
KoFlake::TopLeft,
KoFlake::TopRight,
KoFlake::BottomRight,
KoFlake::BottomLeft,
KoFlake::Center
};
QList<KoShape*> shapes = proxy->shapesInRect(rect, true);
Q_FOREACH (KoShape * shape, shapes) {
qreal shapeMinDistance = HUGE_VAL;
// first check the corner and center points
for (int i = 0; i < 5; ++i) {
m_boxPoints[i] = shape->absolutePosition(pointId[i]);
qreal d = squareDistance(mousePosition, m_boxPoints[i]);
if (d < minDistance && d < maxDistance) {
shapeMinDistance = d;
minDistance = d;
snappedPoint = m_boxPoints[i];
}
}
// prioritize points over edges
if (shapeMinDistance < maxDistance)
continue;
// now check distances to edges of bounding box
for (int i = 0; i < 4; ++i) {
QPointF pointOnLine;
qreal d = squareDistanceToLine(m_boxPoints[i], m_boxPoints[(i+1)%4], mousePosition, pointOnLine);
if (d < minDistance && d < maxDistance) {
minDistance = d;
snappedPoint = pointOnLine;
}
}
}
setSnappedPosition(snappedPoint);
return (minDistance < maxDistance);
}
qreal BoundingBoxSnapStrategy::squareDistanceToLine(const QPointF &lineA, const QPointF &lineB, const QPointF &point, QPointF &pointOnLine)
{
QPointF diff = lineB - lineA;
if(lineA == lineB)
return HUGE_VAL;
const qreal diffLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
// project mouse position relative to start position on line
const qreal scalar = KoSnapStrategy::scalarProduct(point - lineA, diff / diffLength);
if (scalar < 0.0 || scalar > diffLength)
return HUGE_VAL;
// calculate vector between relative mouse position and projected mouse position
pointOnLine = lineA + scalar / diffLength * diff;
QPointF distVec = pointOnLine - point;
return distVec.x()*distVec.x() + distVec.y()*distVec.y();
}
QPainterPath BoundingBoxSnapStrategy::decoration(const KoViewConverter &converter) const
{
QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5));
QPainterPath decoration;
decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), unzoomedSize.height()));
decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), unzoomedSize.height()));
decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), -unzoomedSize.height()));
decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), -unzoomedSize.height()));
return decoration;
}
// KoGuidesData has been moved into Krita. Please port this class!
// LineGuideSnapStrategy::LineGuideSnapStrategy()
// : KoSnapStrategy(KoSnapGuide::GuideLineSnapping)
// {
// }
// bool LineGuideSnapStrategy::snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance)
// {
// Q_ASSERT(std::isfinite(maxSnapDistance));
// KoGuidesData * guidesData = proxy->canvas()->guidesData();
// if (!guidesData || !guidesData->showGuideLines())
// return false;
// QPointF snappedPoint = mousePosition;
// m_orientation = 0;
// qreal minHorzDistance = maxSnapDistance;
// Q_FOREACH (qreal guidePos, guidesData->horizontalGuideLines()) {
// qreal distance = qAbs(guidePos - mousePosition.y());
// if (distance < minHorzDistance) {
// snappedPoint.setY(guidePos);
// minHorzDistance = distance;
// m_orientation |= Qt::Horizontal;
// }
// }
// qreal minVertSnapDistance = maxSnapDistance;
// Q_FOREACH (qreal guidePos, guidesData->verticalGuideLines()) {
// qreal distance = qAbs(guidePos - mousePosition.x());
// if (distance < minVertSnapDistance) {
// snappedPoint.setX(guidePos);
// minVertSnapDistance = distance;
// m_orientation |= Qt::Vertical;
// }
// }
// setSnappedPosition(snappedPoint);
// return (minHorzDistance < maxSnapDistance || minVertSnapDistance < maxSnapDistance);
// }
// QPainterPath LineGuideSnapStrategy::decoration(const KoViewConverter &converter) const
// {
// QSizeF unzoomedSize = converter.viewToDocument(QSizeF(5, 5));
// Q_ASSERT(unzoomedSize.isValid());
// QPainterPath decoration;
// if (m_orientation & Qt::Horizontal) {
// decoration.moveTo(snappedPosition() - QPointF(unzoomedSize.width(), 0));
// decoration.lineTo(snappedPosition() + QPointF(unzoomedSize.width(), 0));
// }
// if (m_orientation & Qt::Vertical) {
// decoration.moveTo(snappedPosition() - QPointF(0, unzoomedSize.height()));
// decoration.lineTo(snappedPosition() + QPointF(0, unzoomedSize.height()));
// }
// return decoration;
// }
diff --git a/libs/flake/KoTosContainer.cpp b/libs/flake/KoTosContainer.cpp
index 83f26535b1..484530451f 100644
--- a/libs/flake/KoTosContainer.cpp
+++ b/libs/flake/KoTosContainer.cpp
@@ -1,349 +1,349 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 KO GmbH <boud@valdyas.org>
* Copyright (C) 2010 Thorsten Zachmann <zachmann@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 "KoTosContainer.h"
#include "KoTosContainer_p.h"
#include "KoShapeRegistry.h"
#include "KoShapeFactoryBase.h"
#include "KoShapeLoadingContext.h"
#include "KoTextShapeDataBase.h"
#include "KoTosContainerModel.h"
#include "KoStyleStack.h"
#include "KoOdfLoadingContext.h"
#include "KoXmlNS.h"
#include "KoGenStyle.h"
#include <FlakeDebug.h>
#include <QTextCursor>
#include <QTextDocument>
KoTosContainer::Private::Private()
: QSharedData()
, resizeBehavior(KoTosContainer::IndependentSizes)
{
}
KoTosContainer::Private::Private(const Private &rhs)
: QSharedData()
, resizeBehavior(rhs.resizeBehavior)
, preferredTextRect(rhs.preferredTextRect)
, alignment(rhs.alignment)
{
}
KoTosContainer::Private::~Private()
{
}
KoTosContainer::KoTosContainer()
: KoShapeContainer()
, d(new Private)
{
}
KoTosContainer::KoTosContainer(const KoTosContainer &rhs)
: KoShapeContainer(rhs)
, d(rhs.d)
{
}
KoTosContainer::~KoTosContainer()
{
delete textShape();
}
-void KoTosContainer::paintComponent(QPainter &, const KoViewConverter &, KoShapePaintingContext &)
+void KoTosContainer::paintComponent(QPainter &, KoShapePaintingContext &) const
{
}
bool KoTosContainer::loadText(const KoXmlElement &element, KoShapeLoadingContext &context)
{
KoXmlElement child;
forEachElement(child, element) {
// only recreate the text shape if there's something to be loaded
if (child.localName() == "p" || child.localName() == "list") {
KoShape *textShape = createTextShape(context.documentResourceManager());
if (!textShape) {
return false;
}
//apply the style properties to the loaded text
setTextAlignment(d->alignment);
// In the case of text on shape, we cannot ask the text shape to load
// the odf, since it expects a complete document with style info and
// everything, so we have to use the KoTextShapeData object instead.
KoTextShapeDataBase *shapeData = qobject_cast<KoTextShapeDataBase*>(textShape->userData());
Q_ASSERT(shapeData);
shapeData->loadStyle(element, context);
bool loadOdf = shapeData->loadOdf(element, context);
return loadOdf;
}
}
return true;
}
void KoTosContainer::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context)
{
KoShapeContainer::loadStyle(element, context);
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.setTypeProperties("graphic");
QString verticalAlign(styleStack.property(KoXmlNS::draw, "textarea-vertical-align"));
Qt::Alignment vAlignment(Qt::AlignTop);
if (verticalAlign == "bottom") {
vAlignment = Qt::AlignBottom;
} else if (verticalAlign == "justify") {
// not yet supported
vAlignment = Qt::AlignVCenter;
} else if (verticalAlign == "middle") {
vAlignment = Qt::AlignVCenter;
}
QString horizontalAlign(styleStack.property(KoXmlNS::draw, "textarea-horizontal-align"));
Qt::Alignment hAlignment(Qt::AlignLeft);
if (horizontalAlign == "center") {
hAlignment = Qt::AlignCenter;
} else if (horizontalAlign == "justify") {
// not yet supported
hAlignment = Qt::AlignCenter;
} else if (horizontalAlign == "right") {
hAlignment = Qt::AlignRight;
}
d->alignment = vAlignment | hAlignment;
}
QString KoTosContainer::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const
{
Qt::Alignment alignment = textAlignment();
QString verticalAlignment = "top";
Qt::Alignment vAlignment(alignment & Qt::AlignVertical_Mask);
if (vAlignment == Qt::AlignBottom) {
verticalAlignment = "bottom";
} else if (vAlignment == Qt::AlignVCenter || vAlignment == Qt::AlignCenter) {
verticalAlignment = "middle";
}
style.addProperty("draw:textarea-vertical-align", verticalAlignment);
QString horizontalAlignment = "left";
Qt::Alignment hAlignment(alignment & Qt::AlignHorizontal_Mask);
if (hAlignment == Qt::AlignCenter || hAlignment == Qt::AlignHCenter) {
horizontalAlignment = "center";
} else if (hAlignment == Qt::AlignJustify) {
horizontalAlignment = "justify";
} else if (hAlignment == Qt::AlignRight) {
horizontalAlignment = "right";
}
style.addProperty("draw:textarea-horizontal-align", horizontalAlignment);
return KoShapeContainer::saveStyle(style, context);
}
void KoTosContainer::saveText(KoShapeSavingContext &context) const
{
KoShape *textShape = this->textShape();
if (!textShape) {
return;
}
// In the case of text on shape, we cannot ask the text shape to save
// the odf, since it would save all the frame information as well, which
// is wrong.
// Only save the text shape if it has content.
KoTextShapeDataBase *shapeData = qobject_cast<KoTextShapeDataBase*>(textShape->userData());
if (shapeData && !shapeData->document()->isEmpty()) {
shapeData->saveOdf(context);
}
}
void KoTosContainer::setPlainText(const QString &text)
{
KoShape *textShape = this->textShape();
if (textShape == 0) {
warnFlake << "No text shape present in KoTosContainer";
return;
}
KoTextShapeDataBase *shapeData = qobject_cast<KoTextShapeDataBase*>(textShape->userData());
Q_ASSERT(shapeData->document());
shapeData->document()->setPlainText(text);
}
void KoTosContainer::setResizeBehavior(ResizeBehavior resizeBehavior)
{
if (d->resizeBehavior == resizeBehavior) {
return;
}
d->resizeBehavior = resizeBehavior;
if (model()) {
model()->containerChanged(this, KoShape::SizeChanged);
}
}
KoTosContainer::ResizeBehavior KoTosContainer::resizeBehavior() const
{
return d->resizeBehavior;
}
void KoTosContainer::setTextAlignment(Qt::Alignment alignment)
{
KoShape *textShape = this->textShape();
if (textShape == 0) {
warnFlake << "No text shape present in KoTosContainer";
return;
}
// vertical
KoTextShapeDataBase *shapeData = qobject_cast<KoTextShapeDataBase*>(textShape->userData());
shapeData->setVerticalAlignment(alignment);
// horizontal
Q_ASSERT(shapeData->document());
QTextBlockFormat bf;
bf.setAlignment(alignment & Qt::AlignHorizontal_Mask);
QTextCursor cursor(shapeData->document());
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cursor.mergeBlockFormat(bf);
d->alignment = alignment;
}
Qt::Alignment KoTosContainer::textAlignment() const
{
KoShape *textShape = this->textShape();
if (textShape == 0) {
warnFlake << "No text shape present in KoTosContainer";
return Qt::AlignTop;
}
// vertical
KoTextShapeDataBase *shapeData = qobject_cast<KoTextShapeDataBase*>(textShape->userData());
// the model makes sure it contains a shape that has a KoTextShapeDataBase set so no need to check that
Qt::Alignment answer = shapeData->verticalAlignment() & Qt::AlignVertical_Mask;
// horizontal
Q_ASSERT(shapeData->document());
QTextCursor cursor(shapeData->document());
answer = answer | (cursor.blockFormat().alignment() & Qt::AlignHorizontal_Mask);
return answer;
}
void KoTosContainer::setPreferredTextRect(const QRectF &rect)
{
d->preferredTextRect = rect;
KoShape *textShape = this->textShape();
//debugFlake << rect << textShape << d->resizeBehavior;
if (d->resizeBehavior == TextFollowsPreferredTextRect && textShape) {
//debugFlake << rect;
textShape->setPosition(rect.topLeft());
textShape->setSize(rect.size());
}
}
QRectF KoTosContainer::preferredTextRect() const
{
return d->preferredTextRect;
}
KoShape *KoTosContainer::createTextShape(KoDocumentResourceManager *documentResources)
{
if (!documentResources) {
warnFlake << "KoDocumentResourceManager not found";
return 0;
}
delete textShape();
delete model();
setModel(new KoTosContainerModel());
QSet<KoShape*> delegates;
delegates << this;
KoShape *textShape = 0;
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get("TextShapeID");
if (factory) { // not installed, that's too bad, but allowed
textShape = factory->createDefaultShape(documentResources);
Q_ASSERT(textShape); // would be a bug in the text shape;
if (d->resizeBehavior == TextFollowsPreferredTextRect) {
textShape->setSize(d->preferredTextRect.size());
} else {
textShape->setSize(size());
}
if (d->resizeBehavior == TextFollowsPreferredTextRect) {
textShape->setPosition(d->preferredTextRect.topLeft());
} else {
textShape->setPosition(QPointF(0, 0));
}
textShape->setSelectable(false);
textShape->setRunThrough(runThrough());
KoTextShapeDataBase *shapeData = qobject_cast<KoTextShapeDataBase*>(textShape->userData());
Q_ASSERT(shapeData); // would be a bug in kotext
// TODO check if that is correct depending on the resize mode
shapeData->setVerticalAlignment(Qt::AlignVCenter);
addShape(textShape);
// textShape->setZIndex(zIndex() + 1); // not needed as there as the text shape is the only sub shape
delegates << textShape;
} else {
warnFlake << "Text shape factory not found";
}
setToolDelegates(delegates);
return textShape;
}
KoShape *KoTosContainer::textShape() const
{
const QList<KoShape*> subShapes = shapes();
return subShapes.isEmpty() ? 0 : subShapes.at(0);
}
void KoTosContainer::shapeChanged(ChangeType type, KoShape *shape)
{
Q_UNUSED(shape);
if (model() == 0) {
return;
}
if (type == SizeChanged || type == ContentChanged) {
model()->containerChanged(this, type);
}
// TODO is this needed?
#if 0
Q_FOREACH (KoShape *shape, model()->shapes())
shape->notifyChanged();
#endif
}
void KoTosContainer::setRunThrough(short int runThrough)
{
KoShape::setRunThrough(runThrough);
KoShape *textShape = this->textShape();
if (textShape) {
textShape->setRunThrough(runThrough);
}
}
diff --git a/libs/flake/KoTosContainer.h b/libs/flake/KoTosContainer.h
index 5a7c2fc33f..7a94999bb6 100644
--- a/libs/flake/KoTosContainer.h
+++ b/libs/flake/KoTosContainer.h
@@ -1,131 +1,131 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 KO GmbH <boud@kogbmh.com>
* Copyright (C) 2010 Thorsten Zachmann <zachmann@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 KOTOSCONTAINER_H
#define KOTOSCONTAINER_H
#include "KoShapeContainer.h"
#include "kritaflake_export.h"
class KoDocumentResourceManager;
/**
* Container that is used to wrap a shape with a text on top.
* Path shapes inherit from this class to make it possible to have text associated
* with them.
*/
class KRITAFLAKE_EXPORT KoTosContainer : public KoShapeContainer
{
public:
KoTosContainer();
~KoTosContainer() override;
// reimplemented
- void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
+ void paintComponent(QPainter &painter, KoShapePaintingContext &paintcontext) const override;
// reimplemented
virtual bool loadText(const KoXmlElement &element, KoShapeLoadingContext &context);
// reimplemented
virtual void saveText(KoShapeSavingContext &context) const;
/// different kinds of resizing behavior to determine how to treat text overflow
enum ResizeBehavior {
TextFollowsSize, ///< Text area is same size as content, extra text will be clipped
FollowTextSize, ///< Content shape will get resized if text grows/shrinks
IndependentSizes, ///< The text can get bigger than the content
TextFollowsPreferredTextRect ///< The size/position of the text area will follow the preferredTextRect property
};
/**
* Set the behavior that is used to resize the text or content.
* In order to determine what to do when there is too much text to fit or suddenly less
* text the user can define the wanted behavior using the ResizeBehavior
* @param resizeBehavior the new ResizeBehavior
*/
void setResizeBehavior(ResizeBehavior resizeBehavior);
/**
* Returns the current ResizeBehavior.
*/
ResizeBehavior resizeBehavior() const;
/** Sets the alignment of the text. */
void setTextAlignment(Qt::Alignment alignment);
/** Returns the alignment of all text */
Qt::Alignment textAlignment() const;
/**
* Set some plain text to be displayed on the shape.
* @param text the full text.
*/
void setPlainText(const QString &text);
/**
* Add text the current shape with the specified document resource manager.
*
* @param documentResources
* @return The created text shape or 0 in case it failed
*/
KoShape *createTextShape(KoDocumentResourceManager *documentResources = 0);
void setRunThrough(short int runThrough) override;
protected:
KoTosContainer(const KoTosContainer &rhs);
//reimplemented
void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) override;
//reimplemented
QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const override;
/**
* Set the current preferred text rectangle. This rect contains the coordinates of
* the embedded text shape relative to the content shape. This value is ignored if
* resizeBehavior is not TextFollowsPreferredTextRect.
* @param rect the new preferred text rectangle
*/
void setPreferredTextRect(const QRectF &rect);
/**
* Returns the current preferred text rectangle.
*/
QRectF preferredTextRect() const;
/**
* Returns the text shape
*
* @returns textshape if set or 0 in case it is not yet set
*/
KoShape *textShape() const;
void shapeChanged(ChangeType type, KoShape *shape = 0) override;
private:
class Private;
QSharedDataPointer<Private> d;
};
#endif
diff --git a/libs/flake/KoVectorPatternBackground.cpp b/libs/flake/KoVectorPatternBackground.cpp
index 7a295eac5e..a07e721141 100644
--- a/libs/flake/KoVectorPatternBackground.cpp
+++ b/libs/flake/KoVectorPatternBackground.cpp
@@ -1,165 +1,162 @@
/*
* 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 "KoVectorPatternBackground.h"
#include <QTransform>
#include <KoShape.h>
#include <KoShapePainter.h>
#include <KoBakedShapeRenderer.h>
-#include <KoViewConverter.h>
class KoVectorPatternBackground::Private : public QSharedData
{
public:
Private()
: QSharedData()
{
}
~Private()
{
qDeleteAll(shapes);
shapes.clear();
}
QList<KoShape*> shapes;
KoFlake::CoordinateSystem referenceCoordinates =
KoFlake::ObjectBoundingBox;
KoFlake::CoordinateSystem contentCoordinates =
KoFlake::UserSpaceOnUse;
QRectF referenceRect;
QTransform patternTransform;
};
KoVectorPatternBackground::KoVectorPatternBackground()
: KoShapeBackground()
, d(new Private)
{
}
KoVectorPatternBackground::~KoVectorPatternBackground()
{
}
bool KoVectorPatternBackground::compareTo(const KoShapeBackground *other) const
{
Q_UNUSED(other);
return false;
}
void KoVectorPatternBackground::setReferenceCoordinates(KoFlake::CoordinateSystem value)
{
d->referenceCoordinates = value;
}
KoFlake::CoordinateSystem KoVectorPatternBackground::referenceCoordinates() const
{
return d->referenceCoordinates;
}
void KoVectorPatternBackground::setContentCoordinates(KoFlake::CoordinateSystem value)
{
d->contentCoordinates = value;
}
KoFlake::CoordinateSystem KoVectorPatternBackground::contentCoordinates() const
{
return d->contentCoordinates;
}
void KoVectorPatternBackground::setReferenceRect(const QRectF &value)
{
d->referenceRect = value;
}
QRectF KoVectorPatternBackground::referenceRect() const
{
return d->referenceRect;
}
void KoVectorPatternBackground::setPatternTransform(const QTransform &value)
{
d->patternTransform = value;
}
QTransform KoVectorPatternBackground::patternTransform() const
{
return d->patternTransform;
}
void KoVectorPatternBackground::setShapes(const QList<KoShape*> value)
{
qDeleteAll(d->shapes);
d->shapes.clear();
d->shapes = value;
}
QList<KoShape *> KoVectorPatternBackground::shapes() const
{
return d->shapes;
}
-void KoVectorPatternBackground::paint(QPainter &painter, const KoViewConverter &converter_Unused, KoShapePaintingContext &context_Unused, const QPainterPath &fillPath) const
+void KoVectorPatternBackground::paint(QPainter &painter, KoShapePaintingContext &context_Unused, const QPainterPath &fillPath) const
{
Q_UNUSED(context_Unused);
- Q_UNUSED(converter_Unused);
const QPainterPath dstShapeOutline = fillPath;
const QRectF dstShapeBoundingBox = dstShapeOutline.boundingRect();
KoBakedShapeRenderer renderer(dstShapeOutline, QTransform(),
QTransform(),
d->referenceRect,
d->contentCoordinates != KoFlake::UserSpaceOnUse,
dstShapeBoundingBox,
d->referenceCoordinates != KoFlake::UserSpaceOnUse,
d->patternTransform);
QPainter *patchPainter = renderer.bakeShapePainter();
- KoViewConverter converter;
KoShapePainter p;
p.setShapes(d->shapes);
- p.paint(*patchPainter, converter);
+ p.paint(*patchPainter);
// uncomment for debug
// renderer.patchImage().save("dd_patch_image.png");
painter.setPen(Qt::NoPen);
renderer.renderShape(painter);
}
bool KoVectorPatternBackground::hasTransparency() const
{
return true;
}
void KoVectorPatternBackground::fillStyle(KoGenStyle &, KoShapeSavingContext &)
{
// noop
}
bool KoVectorPatternBackground::loadStyle(KoOdfLoadingContext &, const QSizeF &Size)
{
Q_UNUSED(Size);
return true;
}
diff --git a/libs/flake/KoVectorPatternBackground.h b/libs/flake/KoVectorPatternBackground.h
index 1abdbc28f7..d60d7d8f59 100644
--- a/libs/flake/KoVectorPatternBackground.h
+++ b/libs/flake/KoVectorPatternBackground.h
@@ -1,67 +1,67 @@
/*
* 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 KOVECTORPATTERNBACKGROUND_H
#define KOVECTORPATTERNBACKGROUND_H
#include <KoShapeBackground.h>
#include <KoFlakeCoordinateSystem.h>
#include <QSharedDataPointer>
class KoShape;
class QPointF;
class QRectF;
class QTransform;
class KoVectorPatternBackground : public KoShapeBackground
{
public:
KoVectorPatternBackground();
~KoVectorPatternBackground() override;
bool compareTo(const KoShapeBackground *other) const override;
void setReferenceCoordinates(KoFlake::CoordinateSystem value);
KoFlake::CoordinateSystem referenceCoordinates() const;
/**
* In ViewBox just use the same mode as for referenceCoordinates
*/
void setContentCoordinates(KoFlake::CoordinateSystem value);
KoFlake::CoordinateSystem contentCoordinates() const;
void setReferenceRect(const QRectF &value);
QRectF referenceRect() const;
void setPatternTransform(const QTransform &value);
QTransform patternTransform() const;
void setShapes(const QList<KoShape*> value);
QList<KoShape*> shapes() const;
- void paint(QPainter &painter, const KoViewConverter &converter_Unused, KoShapePaintingContext &context_Unused, const QPainterPath &fillPath) const override;
+ void paint(QPainter &painter, KoShapePaintingContext &context_Unused, const QPainterPath &fillPath) const override;
bool hasTransparency() const override;
void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override;
bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override;
private:
class Private;
QSharedDataPointer<Private> d;
};
#endif // KOVECTORPATTERNBACKGROUND_H
diff --git a/libs/flake/KoViewConverter.cpp b/libs/flake/KoViewConverter.cpp
index 55d3020674..a56492dfe1 100644
--- a/libs/flake/KoViewConverter.cpp
+++ b/libs/flake/KoViewConverter.cpp
@@ -1,108 +1,125 @@
/*
* Copyright (C) 2006, 2008-2009 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 "KoViewConverter.h"
#include <QPointF>
#include <QRectF>
+#include <QTransform>
KoViewConverter::KoViewConverter()
: m_zoomLevel(1.0)
{
}
QPointF KoViewConverter::documentToView(const QPointF &documentPoint) const
{
if (qFuzzyCompare(m_zoomLevel, 1))
return documentPoint;
return QPointF(documentToViewX(documentPoint.x()), documentToViewY(documentPoint.y()));
}
QPointF KoViewConverter::viewToDocument(const QPointF &viewPoint) const
{
if (qFuzzyCompare(m_zoomLevel, 1))
return viewPoint;
return QPointF(viewToDocumentX(viewPoint.x()), viewToDocumentY(viewPoint.y()));
}
QRectF KoViewConverter::documentToView(const QRectF &documentRect) const
{
if (qFuzzyCompare(m_zoomLevel, 1))
return documentRect;
return QRectF(documentToView(documentRect.topLeft()), documentToView(documentRect.size()));
}
QRectF KoViewConverter::viewToDocument(const QRectF &viewRect) const
{
if (qFuzzyCompare(m_zoomLevel, 1))
return viewRect;
return QRectF(viewToDocument(viewRect.topLeft()), viewToDocument(viewRect.size()));
}
QSizeF KoViewConverter::documentToView(const QSizeF &documentSize) const
{
if (qFuzzyCompare(m_zoomLevel, 1))
return documentSize;
return QSizeF(documentToViewX(documentSize.width()), documentToViewY(documentSize.height()));
}
QSizeF KoViewConverter::viewToDocument(const QSizeF &viewSize) const
{
if (qFuzzyCompare(m_zoomLevel, 1))
return viewSize;
return QSizeF(viewToDocumentX(viewSize.width()), viewToDocumentY(viewSize.height()));
}
void KoViewConverter::zoom(qreal *zoomX, qreal *zoomY) const
{
*zoomX = m_zoomLevel;
*zoomY = m_zoomLevel;
}
qreal KoViewConverter::documentToViewX(qreal documentX) const
{
return documentX * m_zoomLevel;
}
qreal KoViewConverter::documentToViewY(qreal documentY) const
{
return documentY * m_zoomLevel;
}
qreal KoViewConverter::viewToDocumentX(qreal viewX) const
{
return viewX / m_zoomLevel;
}
qreal KoViewConverter::viewToDocumentY(qreal viewY) const
{
return viewY / m_zoomLevel;
}
+
+
void KoViewConverter::setZoom(qreal zoom)
{
if (qFuzzyCompare(zoom, qreal(0.0)) || qFuzzyCompare(zoom, qreal(1.0))) {
zoom = 1;
}
m_zoomLevel = zoom;
}
qreal KoViewConverter::zoom() const
{
return m_zoomLevel;
}
+
+QTransform KoViewConverter::documentToView() const
+{
+ qreal zoomX, zoomY;
+ zoom(&zoomX, &zoomY);
+ return QTransform::fromScale(zoomX, zoomY);
+}
+
+QTransform KoViewConverter::viewToDocument() const
+{
+ qreal zoomX, zoomY;
+ zoom(&zoomX, &zoomY);
+ return QTransform::fromScale(1.0 / zoomX, 1.0 / zoomY);
+}
diff --git a/libs/flake/KoViewConverter.h b/libs/flake/KoViewConverter.h
index 729272fc42..92c1dc0d1e 100644
--- a/libs/flake/KoViewConverter.h
+++ b/libs/flake/KoViewConverter.h
@@ -1,135 +1,140 @@
/* This file is part of the KDE project
* Copyright (C) 2006 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 KOVIEWCONVERTER_H
#define KOVIEWCONVERTER_H
#include "kritaflake_export.h"
#include <QtGlobal>
class QPointF;
class QRectF;
class QSizeF;
+class QTransform;
/**
* The interface for view conversions.
*
* All KoShape based objects are using a postscript-point (pt) based measurement system
* which requires a conversion to view coordinates (in pixel sizes) at the moment
* we are painting, and a conversion to the normalized coordinate system if we
* receive mouse events so we can figure out which KoShape object was touched.
*
* The zoom level is expressed on a scale of 0.0 to 1.0 to infinite, where 1.0 is
* 100%
*/
class KRITAFLAKE_EXPORT KoViewConverter
{
public:
KoViewConverter();
virtual ~KoViewConverter() {}
/**
* Convert a coordinate in pt to pixels.
* @param documentPoint the point in the document coordinate system of a KoShape.
*/
virtual QPointF documentToView(const QPointF &documentPoint) const;
/**
* Convert a coordinate in pixels to pt.
* @param viewPoint the point in the coordinate system of the widget, or window.
*/
virtual QPointF viewToDocument(const QPointF &viewPoint) const;
/**
* Convert a rectangle in pt to pixels.
* @param documentRect the rect in the document coordinate system of a KoShape.
*/
virtual QRectF documentToView(const QRectF &documentRect) const;
/**
* Convert a rectangle in pixels to pt.
* @param viewRect the rect in the coordinate system of the widget, or window.
*/
virtual QRectF viewToDocument(const QRectF &viewRect) const;
/**
* Convert a size in pt to pixels.
* @param documentSize the size in pt.
* @return the size in pixels.
*/
virtual QSizeF documentToView(const QSizeF& documentSize) const;
/**
* Convert a size in pixels to pt.
* @param viewSize the size in pixels.
* @return the size in pt.
*/
virtual QSizeF viewToDocument(const QSizeF& viewSize) const;
/**
* Convert a single x coordinate in pt to pixels.
* @param documentX the x coordinate in pt.
* @return the x coordinate in pixels.
*/
virtual qreal documentToViewX(qreal documentX) const;
/**
* Convert a single y coordinate in pt to pixels.
* @param documentY the y coordinate in pt.
* @return the y coordinate in pixels.
*/
virtual qreal documentToViewY(qreal documentY) const;
/**
* Convert a single x coordinate in pixels to pt.
* @param viewX the x coordinate in pixels.
* @return the x coordinate in pt.
*/
virtual qreal viewToDocumentX(qreal viewX) const;
/**
* Convert a single y coordinate in pixels to pt.
* @param viewY the y coordinate in pixels.
* @return the y coordinate in pt.
*/
virtual qreal viewToDocumentY(qreal viewY) const;
/**
* Retrieve the zoom levels of the individual x and y axes.
* @param zoomX a pointer to a qreal which will be modified to the horizontal zoom.
* @param zoomY a pointer to a qreal which will be modified to the vertical zoom.
*/
virtual void zoom(qreal *zoomX, qreal *zoomY) const;
/**
* Set the zoom level. 1.0 is 100%.
*/
virtual void setZoom(qreal zoom);
/**
* Return the current zoom level. 1.0 is 100%.
*/
qreal zoom() const;
+ QTransform documentToView() const;
+ QTransform viewToDocument() const;
+
+
private:
qreal m_zoomLevel; // 1.0 is 100%
};
#endif
diff --git a/libs/flake/commands/KoShapeGroupCommand.cpp b/libs/flake/commands/KoShapeGroupCommand.cpp
index f6dd4737a6..b76e901bfe 100644
--- a/libs/flake/commands/KoShapeGroupCommand.cpp
+++ b/libs/flake/commands/KoShapeGroupCommand.cpp
@@ -1,207 +1,207 @@
/* This file is part of the KDE project
* Copyright (C) 2006,2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2006,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 "KoShapeGroupCommand.h"
#include "KoShape.h"
#include "KoShapeGroup.h"
#include "KoShapeContainer.h"
#include <commands/KoShapeReorderCommand.h>
#include <klocalizedstring.h>
// static
KoShapeGroupCommand * KoShapeGroupCommand::createCommand(KoShapeContainer *container, const QList<KoShape *> &shapes, bool shouldNormalize)
{
QList<KoShape*> orderedShapes(shapes);
if (!orderedShapes.isEmpty()) {
KoShape * top = orderedShapes.last();
container->setParent(top->parent());
container->setZIndex(top->zIndex());
}
return new KoShapeGroupCommand(container, orderedShapes, shouldNormalize, 0);
}
class KoShapeGroupCommandPrivate
{
public:
KoShapeGroupCommandPrivate(KoShapeContainer *container, const QList<KoShape *> &shapes, bool _shouldNormalize);
QRectF containerBoundingRect();
QList<KoShape*> shapes; ///<list of shapes to be grouped
bool shouldNormalize; ///< Adjust the coordinate system of the group to its origin into the topleft of the group
KoShapeContainer *container; ///< the container where the grouping should be for.
QList<KoShapeContainer*> oldParents; ///< the old parents of the shapes
QScopedPointer<KUndo2Command> shapesReorderCommand;
};
KoShapeGroupCommandPrivate::KoShapeGroupCommandPrivate(KoShapeContainer *c, const QList<KoShape *> &s, bool _shouldNormalize)
: shapes(s),
shouldNormalize(_shouldNormalize),
container(c)
{
std::stable_sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
}
KoShapeGroupCommand::KoShapeGroupCommand(KoShapeContainer *container, const QList<KoShape *> &shapes, KUndo2Command *parent)
: KoShapeGroupCommand(container, shapes, false, parent)
{
}
KoShapeGroupCommand::KoShapeGroupCommand(KoShapeContainer *container, const QList<KoShape *> &shapes,
bool shouldNormalize, KUndo2Command *parent)
: KUndo2Command(parent),
d(new KoShapeGroupCommandPrivate(container, shapes, shouldNormalize))
{
Q_FOREACH (KoShape* shape, d->shapes) {
d->oldParents.append(shape->parent());
}
if (d->container->shapes().isEmpty()) {
setText(kundo2_i18n("Group shapes"));
} else {
setText(kundo2_i18n("Add shapes to group"));
}
}
KoShapeGroupCommand::~KoShapeGroupCommand()
{
}
void KoShapeGroupCommand::redo()
{
KUndo2Command::redo();
if (d->shouldNormalize && dynamic_cast<KoShapeGroup*>(d->container)) {
QRectF bound = d->containerBoundingRect();
QPointF oldGroupPosition = d->container->absolutePosition(KoFlake::TopLeft);
d->container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeft);
d->container->setSize(bound.size());
if (d->container->shapeCount() > 0) {
// the group has changed position and so have the group child shapes
// -> we need compensate the group position change
QPointF positionOffset = oldGroupPosition - bound.topLeft();
Q_FOREACH (KoShape * child, d->container->shapes())
child->setAbsolutePosition(child->absolutePosition() + positionOffset);
}
}
- QTransform groupTransform = d->container->absoluteTransformation(0).inverted();
+ QTransform groupTransform = d->container->absoluteTransformation().inverted();
QList<KoShape*> containerShapes(d->container->shapes());
std::stable_sort(containerShapes.begin(), containerShapes.end(), KoShape::compareShapeZIndex);
QList<KoShapeReorderCommand::IndexedShape> indexedShapes;
Q_FOREACH (KoShape *shape, containerShapes) {
indexedShapes.append(KoShapeReorderCommand::IndexedShape(shape));
}
QList<KoShapeReorderCommand::IndexedShape> prependIndexedShapes;
Q_FOREACH (KoShape *shape, d->shapes) {
// test if they inherit the same parent
if (!shape->hasCommonParent(d->container) ||
!KoShape::compareShapeZIndex(shape, d->container)) {
indexedShapes.append(KoShapeReorderCommand::IndexedShape(shape));
} else {
prependIndexedShapes.append(KoShapeReorderCommand::IndexedShape(shape));
}
}
indexedShapes = prependIndexedShapes + indexedShapes;
indexedShapes = KoShapeReorderCommand::homogenizeZIndexesLazy(indexedShapes);
if (!indexedShapes.isEmpty()) {
d->shapesReorderCommand.reset(new KoShapeReorderCommand(indexedShapes));
d->shapesReorderCommand->redo();
}
uint shapeCount = d->shapes.count();
for (uint i = 0; i < shapeCount; ++i) {
KoShape * shape = d->shapes[i];
shape->applyAbsoluteTransformation(groupTransform);
d->container->addShape(shape);
}
}
void KoShapeGroupCommand::undo()
{
KUndo2Command::undo();
- QTransform ungroupTransform = d->container->absoluteTransformation(0);
+ QTransform ungroupTransform = d->container->absoluteTransformation();
for (int i = 0; i < d->shapes.count(); i++) {
KoShape * shape = d->shapes[i];
d->container->removeShape(shape);
if (d->oldParents.at(i)) {
d->oldParents.at(i)->addShape(shape);
}
shape->applyAbsoluteTransformation(ungroupTransform);
}
if (d->shapesReorderCommand) {
d->shapesReorderCommand->undo();
d->shapesReorderCommand.reset();
}
if (d->shouldNormalize && dynamic_cast<KoShapeGroup*>(d->container)) {
QPointF oldGroupPosition = d->container->absolutePosition(KoFlake::TopLeft);
if (d->container->shapeCount() > 0) {
bool boundingRectInitialized = false;
QRectF bound;
Q_FOREACH (KoShape * shape, d->container->shapes()) {
if (! boundingRectInitialized) {
bound = shape->boundingRect();
boundingRectInitialized = true;
} else
bound = bound.united(shape->boundingRect());
}
// the group has changed position and so have the group child shapes
// -> we need compensate the group position change
QPointF positionOffset = oldGroupPosition - bound.topLeft();
Q_FOREACH (KoShape * child, d->container->shapes())
child->setAbsolutePosition(child->absolutePosition() + positionOffset);
d->container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeft);
d->container->setSize(bound.size());
}
}
}
QRectF KoShapeGroupCommandPrivate::containerBoundingRect()
{
QRectF bound;
if (container->shapeCount() > 0) {
- bound = container->absoluteTransformation(0).mapRect(container->outlineRect());
+ bound = container->absoluteTransformation().mapRect(container->outlineRect());
}
Q_FOREACH (KoShape *shape, shapes) {
- bound |= shape->absoluteTransformation(0).mapRect(shape->outlineRect());
+ bound |= shape->absoluteTransformation().mapRect(shape->outlineRect());
}
return bound;
}
diff --git a/libs/flake/commands/KoShapeUngroupCommand.cpp b/libs/flake/commands/KoShapeUngroupCommand.cpp
index 7d8dfdd93a..69804d1e50 100644
--- a/libs/flake/commands/KoShapeUngroupCommand.cpp
+++ b/libs/flake/commands/KoShapeUngroupCommand.cpp
@@ -1,122 +1,122 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* Copyright (C) 2006 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 "KoShapeUngroupCommand.h"
#include "KoShapeContainer.h"
#include "KoShapeReorderCommand.h"
#include <klocalizedstring.h>
#include "kis_assert.h"
struct KoShapeUngroupCommand::Private
{
Private(KoShapeContainer *_container,
const QList<KoShape *> &_shapes,
const QList<KoShape*> &_topLevelShapes)
: container(_container),
shapes(_shapes),
topLevelShapes(_topLevelShapes)
{
std::stable_sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
std::sort(topLevelShapes.begin(), topLevelShapes.end(), KoShape::compareShapeZIndex);
}
KoShapeContainer *container;
QList<KoShape*> shapes;
QList<KoShape*> topLevelShapes;
QScopedPointer<KUndo2Command> shapesReorderCommand;
};
KoShapeUngroupCommand::KoShapeUngroupCommand(KoShapeContainer *container, const QList<KoShape *> &shapes,
const QList<KoShape*> &topLevelShapes, KUndo2Command *parent)
: KUndo2Command(parent),
m_d(new Private(container, shapes, topLevelShapes))
{
setText(kundo2_i18n("Ungroup shapes"));
}
KoShapeUngroupCommand::~KoShapeUngroupCommand()
{
}
void KoShapeUngroupCommand::redo()
{
using IndexedShape = KoShapeReorderCommand::IndexedShape;
KoShapeContainer *newParent = m_d->container->parent();
QList<IndexedShape> indexedSiblings;
QList<KoShape*> perspectiveSiblings;
if (newParent) {
perspectiveSiblings = newParent->shapes();
std::sort(perspectiveSiblings.begin(), perspectiveSiblings.end(), KoShape::compareShapeZIndex);
} else {
perspectiveSiblings = m_d->topLevelShapes;
}
Q_FOREACH (KoShape *shape, perspectiveSiblings) {
indexedSiblings.append(shape);
}
// find the place where the ungrouped shapes should be inserted
// (right on the top of their current container)
auto insertIt = std::upper_bound(indexedSiblings.begin(),
indexedSiblings.end(),
IndexedShape(m_d->container));
std::copy(m_d->shapes.begin(), m_d->shapes.end(),
std::inserter(indexedSiblings, insertIt));
indexedSiblings = KoShapeReorderCommand::homogenizeZIndexesLazy(indexedSiblings);
- const QTransform ungroupTransform = m_d->container->absoluteTransformation(0);
+ const QTransform ungroupTransform = m_d->container->absoluteTransformation();
for (auto it = m_d->shapes.begin(); it != m_d->shapes.end(); ++it) {
KoShape *shape = *it;
KIS_SAFE_ASSERT_RECOVER(shape->parent() == m_d->container) { continue; }
shape->setParent(newParent);
shape->applyAbsoluteTransformation(ungroupTransform);
}
if (!indexedSiblings.isEmpty()) {
m_d->shapesReorderCommand.reset(new KoShapeReorderCommand(indexedSiblings));
m_d->shapesReorderCommand->redo();
}
}
void KoShapeUngroupCommand::undo()
{
- const QTransform groupTransform = m_d->container->absoluteTransformation(0).inverted();
+ const QTransform groupTransform = m_d->container->absoluteTransformation().inverted();
for (auto it = m_d->shapes.begin(); it != m_d->shapes.end(); ++it) {
KoShape *shape = *it;
shape->setParent(m_d->container);
shape->applyAbsoluteTransformation(groupTransform);
}
if (m_d->shapesReorderCommand) {
m_d->shapesReorderCommand->undo();
m_d->shapesReorderCommand.reset();
}
}
diff --git a/libs/flake/resources/KoGamutMask.cpp b/libs/flake/resources/KoGamutMask.cpp
index 40f4a4e175..b22342c558 100644
--- a/libs/flake/resources/KoGamutMask.cpp
+++ b/libs/flake/resources/KoGamutMask.cpp
@@ -1,432 +1,439 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@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 "KoGamutMask.h"
#include <cstring>
#include <QVector>
#include <QString>
#include <QFile>
#include <QList>
#include <QDomDocument>
#include <QDomElement>
#include <QByteArray>
#include <QBuffer>
#include <QScopedPointer>
#include <FlakeDebug.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoDocumentResourceManager.h>
#include <SvgParser.h>
#include <SvgWriter.h>
#include <KoShape.h>
-#include <KisGamutMaskViewConverter.h>
#include <kis_assert.h>
+#include <QTransform>
+
+//#include <kis_debug.h>
KoGamutMaskShape::KoGamutMaskShape(KoShape* shape)
: m_maskShape(shape)
, m_shapePaintingContext(KoShapePaintingContext())
{
}
KoGamutMaskShape::KoGamutMaskShape() {};
KoGamutMaskShape::~KoGamutMaskShape() {};
KoShape* KoGamutMaskShape::koShape()
{
return m_maskShape;
}
-bool KoGamutMaskShape::coordIsClear(const QPointF& coord, const KoViewConverter& viewConverter, int maskRotation) const
+bool KoGamutMaskShape::coordIsClear(const QPointF& coord) const
{
- // apply mask rotation to coord
- const KisGamutMaskViewConverter& converter = dynamic_cast<const KisGamutMaskViewConverter&>(viewConverter);
- QPointF centerPoint(converter.viewSize().width()*0.5, converter.viewSize().height()*0.5);
-
- QTransform rotationTransform;
- rotationTransform.translate(centerPoint.x(), centerPoint.y());
- rotationTransform.rotate(-maskRotation);
- rotationTransform.translate(-centerPoint.x(), -centerPoint.y());
-
- QPointF rotatedCoord = rotationTransform.map(coord);
- QPointF translatedPoint = viewConverter.viewToDocument(rotatedCoord);
-
- bool isClear = m_maskShape->hitTest(translatedPoint);
+ bool isClear = m_maskShape->hitTest(coord);
return isClear;
}
-void KoGamutMaskShape::paint(QPainter &painter, const KoViewConverter& viewConverter, int maskRotation)
+void KoGamutMaskShape::paint(QPainter &painter)
{
painter.save();
-
- // apply mask rotation before drawing
- QPointF centerPoint(painter.viewport().width()*0.5, painter.viewport().height()*0.5);
- painter.translate(centerPoint);
- painter.rotate(maskRotation);
- painter.translate(-centerPoint);
- painter.setTransform(m_maskShape->absoluteTransformation(&viewConverter) * painter.transform());
- m_maskShape->paint(painter, viewConverter, m_shapePaintingContext);
+ painter.setTransform(m_maskShape->absoluteTransformation(), true);
+ m_maskShape->paint(painter, m_shapePaintingContext);
painter.restore();
}
-void KoGamutMaskShape::paintStroke(QPainter &painter, const KoViewConverter &viewConverter, int maskRotation)
+void KoGamutMaskShape::paintStroke(QPainter &painter)
{
painter.save();
-
- // apply mask rotation before drawing
- QPointF centerPoint(painter.viewport().width()*0.5, painter.viewport().height()*0.5);
- painter.translate(centerPoint);
- painter.rotate(maskRotation);
- painter.translate(-centerPoint);
-
- painter.setTransform(m_maskShape->absoluteTransformation(&viewConverter) * painter.transform());
- m_maskShape->paintStroke(painter, viewConverter, m_shapePaintingContext);
+ painter.setTransform(m_maskShape->absoluteTransformation(), true);
+ m_maskShape->paintStroke(painter, m_shapePaintingContext);
painter.restore();
-
}
struct KoGamutMask::Private {
QString name;
QString title;
QString description;
QByteArray data;
QVector<KoGamutMaskShape*> maskShapes;
QVector<KoGamutMaskShape*> previewShapes;
QSizeF maskSize;
int rotation {0};
};
KoGamutMask::KoGamutMask(const QString& filename)
: KoResource(filename)
, d(new Private)
{
d->maskSize = QSizeF(144.0,144.0);
setRotation(0);
}
KoGamutMask::KoGamutMask()
: KoResource(QString())
, d(new Private)
{
d->maskSize = QSizeF(144.0,144.0);
setRotation(0);
}
KoGamutMask::KoGamutMask(KoGamutMask* rhs)
: QObject(0)
, KoResource(QString())
, d(new Private)
{
setFilename(rhs->filename());
setTitle(rhs->title());
setDescription(rhs->description());
d->maskSize = rhs->d->maskSize;
QList<KoShape*> newShapes;
for(KoShape* sh: rhs->koShapes()) {
newShapes.append(sh->cloneShape());
}
setMaskShapes(newShapes);
setValid(true);
}
KoGamutMask::~KoGamutMask()
{
delete d;
}
-bool KoGamutMask::coordIsClear(const QPointF& coord, KoViewConverter &viewConverter, bool preview)
+bool KoGamutMask::coordIsClear(const QPointF& coord, bool preview)
{
QVector<KoGamutMaskShape*>* shapeVector;
if (preview && !d->previewShapes.isEmpty()) {
shapeVector = &d->previewShapes;
} else {
shapeVector = &d->maskShapes;
}
for(KoGamutMaskShape* shape: *shapeVector) {
- if (shape->coordIsClear(coord, viewConverter, rotation()) == true) {
+ if (shape->coordIsClear(coord) == true) {
return true;
}
}
return false;
}
-void KoGamutMask::paint(QPainter &painter, KoViewConverter& viewConverter, bool preview)
+void KoGamutMask::paint(QPainter &painter, bool preview)
{
QVector<KoGamutMaskShape*>* shapeVector;
if (preview && !d->previewShapes.isEmpty()) {
shapeVector = &d->previewShapes;
} else {
shapeVector = &d->maskShapes;
}
for(KoGamutMaskShape* shape: *shapeVector) {
- shape->paint(painter, viewConverter, rotation());
+ shape->paint(painter);
}
}
-void KoGamutMask::paintStroke(QPainter &painter, KoViewConverter &viewConverter, bool preview)
+void KoGamutMask::paintStroke(QPainter &painter, bool preview)
{
QVector<KoGamutMaskShape*>* shapeVector;
if (preview && !d->previewShapes.isEmpty()) {
shapeVector = &d->previewShapes;
} else {
shapeVector = &d->maskShapes;
}
for(KoGamutMaskShape* shape: *shapeVector) {
- shape->paintStroke(painter, viewConverter, rotation());
+ shape->paintStroke(painter);
}
}
+QTransform KoGamutMask::maskToViewTransform(quint8 viewSize)
+{
+ // apply mask rotation before drawing
+ QPointF centerPoint(viewSize*0.5, viewSize*0.5);
+
+ QTransform transform;
+ transform.translate(centerPoint.x(), centerPoint.y());
+ transform.rotate(rotation());
+ transform.translate(-centerPoint.x(), -centerPoint.y());
+
+ qreal scale = viewSize/(maskSize().width());
+ transform.scale(scale, scale);
+
+ return transform;
+}
+
+QTransform KoGamutMask::viewToMaskTransform(quint8 viewSize)
+{
+ QPointF centerPoint(viewSize*0.5, viewSize*0.5);
+
+ QTransform transform;
+ qreal scale = viewSize/(maskSize().width());
+ transform.scale(1/scale, 1/scale);
+
+ transform.translate(centerPoint.x(), centerPoint.y());
+ transform.rotate(-rotation());
+ transform.translate(-centerPoint.x(), -centerPoint.y());
+
+ return transform;
+}
+
bool KoGamutMask::load()
{
QFile file(filename());
if (file.size() == 0) return false;
if (!file.open(QIODevice::ReadOnly)) {
warnFlake << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&file);
file.close();
return res;
}
bool KoGamutMask::loadFromDevice(QIODevice *dev)
{
if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
d->data = dev->readAll();
// TODO: test
KIS_ASSERT_RECOVER_RETURN_VALUE(d->data.size() != 0, false);
if (filename().isNull()) {
warnFlake << "Cannot load gamut mask" << name() << "there is no filename set";
return false;
}
if (d->data.isNull()) {
QFile file(filename());
if (file.size() == 0) {
warnFlake << "Cannot load gamut mask" << name() << "there is no data available";
return false;
}
file.open(QIODevice::ReadOnly);
d->data = file.readAll();
file.close();
}
QBuffer buf(&d->data);
buf.open(QBuffer::ReadOnly);
QScopedPointer<KoStore> store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-gamutmask", KoStore::Zip));
if (!store || store->bad()) return false;
bool storeOpened = store->open("gamutmask.svg");
if (!storeOpened) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
KoXmlDocument xmlDocument = SvgParser::createDocumentFromSvg(ba, &errorMsg, &errorLine, &errorColumn);
if (xmlDocument.isNull()) {
errorFlake << "Parsing error in " << filename() << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
errorFlake << "Parsing error in the main document at line" << errorLine
<< ", column" << errorColumn << endl
<< "Error message: " << errorMsg;
return false;
}
KoDocumentResourceManager manager;
SvgParser parser(&manager);
parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values
QSizeF fragmentSize;
QList<KoShape*> shapes = parser.parseSvg(xmlDocument.documentElement(), &fragmentSize);
d->maskSize = fragmentSize;
d->title = parser.documentTitle();
setName(d->title);
d->description = parser.documentDescription();
setMaskShapes(shapes);
if (store->open("preview.png")) {
KoStoreDevice previewDev(store.data());
previewDev.open(QIODevice::ReadOnly);
QImage preview = QImage();
preview.load(&previewDev, "PNG");
setImage(preview);
(void)store->close();
}
buf.close();
setValid(true);
return true;
}
void KoGamutMask::setMaskShapes(QList<KoShape*> shapes)
{
setMaskShapesToVector(shapes, d->maskShapes);
}
bool KoGamutMask::save()
{
QFile file(filename());
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
return false;
}
saveToDevice(&file);
file.close();
return true;
}
QList<KoShape*> KoGamutMask::koShapes() const
{
QList<KoShape*> shapes;
for(KoGamutMaskShape* maskShape: d->maskShapes) {
shapes.append(maskShape->koShape());
}
return shapes;
}
bool KoGamutMask::saveToDevice(QIODevice *dev) const
{
KoStore* store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-gamutmask", KoStore::Zip));
if (!store || store->bad()) return false;
QList<KoShape*> shapes = koShapes();
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
if (!store->open("gamutmask.svg")) {
return false;
}
KoStoreDevice storeDev(store);
storeDev.open(QIODevice::WriteOnly);
SvgWriter writer(shapes);
writer.setDocumentTitle(d->title);
writer.setDocumentDescription(d->description);
writer.save(storeDev, d->maskSize);
if (!store->close()) { return false; }
if (!store->open("preview.png")) {
return false;
}
KoStoreDevice previewDev(store);
previewDev.open(QIODevice::WriteOnly);
image().save(&previewDev, "PNG");
if (!store->close()) { return false; }
return store->finalize();
}
QString KoGamutMask::title()
{
return d->title;
}
void KoGamutMask::setTitle(QString title)
{
d->title = title;
setName(title);
}
QString KoGamutMask::description()
{
return d->description;
}
void KoGamutMask::setDescription(QString description)
{
d->description = description;
}
int KoGamutMask::rotation()
{
return d->rotation;
}
void KoGamutMask::setRotation(int rotation)
{
d->rotation = rotation;
}
QSizeF KoGamutMask::maskSize()
{
return d->maskSize;
}
void KoGamutMask::setPreviewMaskShapes(QList<KoShape*> shapes)
{
setMaskShapesToVector(shapes, d->previewShapes);
}
void KoGamutMask::setMaskShapesToVector(QList<KoShape *> shapes, QVector<KoGamutMaskShape *> &targetVector)
{
targetVector.clear();
for(KoShape* sh: shapes) {
KoGamutMaskShape* maskShape = new KoGamutMaskShape(sh);
targetVector.append(maskShape);
}
}
// clean up when ending mask preview
void KoGamutMask::clearPreview()
{
d->previewShapes.clear();
}
diff --git a/libs/flake/resources/KoGamutMask.h b/libs/flake/resources/KoGamutMask.h
index b359066ecc..d2372842e9 100644
--- a/libs/flake/resources/KoGamutMask.h
+++ b/libs/flake/resources/KoGamutMask.h
@@ -1,101 +1,104 @@
/*
* Copyright (c) 2018 Anna Medonosova <anna.medonosova@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 KOGAMUTMASK_H
#define KOGAMUTMASK_H
#include <QPainter>
#include <QString>
#include <QVector>
#include <cmath>
#include <FlakeDebug.h>
#include <resources/KoResource.h>
#include <KoShape.h>
-#include <KisGamutMaskViewConverter.h>
#include <KoShapePaintingContext.h>
-class KoViewConverter;
+//class KoViewConverter;
+class QTransform;
class KoGamutMaskShape
{
public:
KoGamutMaskShape(KoShape* shape);
KoGamutMaskShape();
~KoGamutMaskShape();
- bool coordIsClear(const QPointF& coord, const KoViewConverter& viewConverter, int maskRotation) const;
+ bool coordIsClear(const QPointF& coord) const;
QPainterPath outline();
- void paint(QPainter &painter, const KoViewConverter& viewConverter, int maskRotation);
- void paintStroke(QPainter &painter, const KoViewConverter& viewConverter, int maskRotation);
+ void paint(QPainter &painter);
+ void paintStroke(QPainter &painter);
KoShape* koShape();
private:
KoShape* m_maskShape;
KoShapePaintingContext m_shapePaintingContext;
};
/**
* @brief The resource type for gamut masks used by the artistic color selector
*/
class KRITAFLAKE_EXPORT KoGamutMask : public QObject, public KoResource
{
Q_OBJECT
public:
KoGamutMask(const QString &filename);
KoGamutMask();
KoGamutMask(KoGamutMask *rhs);
~KoGamutMask() override;
- bool coordIsClear(const QPointF& coord, KoViewConverter& viewConverter, bool preview);
+ bool coordIsClear(const QPointF& coord, bool preview);
bool load() override;
bool loadFromDevice(QIODevice *dev) override;
bool save() override;
bool saveToDevice(QIODevice* dev) const override;
- void paint(QPainter &painter, KoViewConverter& viewConverter, bool preview);
- void paintStroke(QPainter &painter, KoViewConverter& viewConverter, bool preview);
+ void paint(QPainter &painter, bool preview);
+ void paintStroke(QPainter &painter, bool preview);
+
+ QTransform maskToViewTransform(quint8 viewSize);
+ QTransform viewToMaskTransform(quint8 viewSize);
QString title();
void setTitle(QString title);
QString description();
void setDescription(QString description);
int rotation();
void setRotation(int rotation);
QSizeF maskSize();
void setMaskShapes(QList<KoShape*> shapes);
void setPreviewMaskShapes(QList<KoShape*> shapes);
QList<KoShape*> koShapes() const;
void clearPreview();
private:
void setMaskShapesToVector(QList<KoShape*> shapes, QVector<KoGamutMaskShape*>& targetVector);
struct Private;
Private* const d;
};
#endif // KOGAMUTMASK_H
diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp
index 8140c0f5bd..f2acf5d46e 100644
--- a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp
+++ b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp
@@ -1,255 +1,233 @@
/* This file is part of the KDE project
- Copyright (c) 2017 L. E. Segovia <leo.segovia@siggraph.org>
+ Copyright (c) 2017 L. E. Segovia <amy@amyspark.me>
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>
#include <FlakeDebug.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(false))
- 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);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(group, QImage());
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;
// debugFlake << "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);
+ KoShapeManager::renderSingleShape(group, gc, 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;
}
bool KoSvgSymbolCollectionResource::loadFromDevice(QIODevice *dev)
{
if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
QString errorMsg;
int errorLine = 0;
int errorColumn;
KoXmlDocument doc = SvgParser::createDocumentFromSvg(dev, &errorMsg, &errorLine, &errorColumn);
if (doc.isNull()) {
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();
// debugFlake << "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);
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 91ab13cd75..fab2275371 100644
--- a/libs/flake/resources/KoSvgSymbolCollectionResource.h
+++ b/libs/flake/resources/KoSvgSymbolCollectionResource.h
@@ -1,105 +1,104 @@
/* 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 <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 KRITAFLAKE_EXPORT KoSvgSymbol {
KoSvgSymbol() {}
KoSvgSymbol(const QString &_title)
: title(_title) {}
~KoSvgSymbol()
{
delete shape;
}
QString id;
QString title;
KoShape *shape;
QImage icon();
bool operator==(const KoSvgSymbol& rhs) const {
return title == rhs.title;
}
};
/**
* Loads an svg file that contains "symbol" objects and creates a collection of those objects.
*/
class KRITAFLAKE_EXPORT KoSvgSymbolCollectionResource : public QObject, public KoResource
{
Q_OBJECT
public:
/**
*/
explicit KoSvgSymbolCollectionResource(const QString &filename);
/// Create an empty color set
KoSvgSymbolCollectionResource();
/// Explicit copy constructor (KoResource copy constructor is private)
KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs);
~KoSvgSymbolCollectionResource() override;
bool load() override;
bool loadFromDevice(QIODevice *dev) override;
bool save() override;
bool saveToDevice(QIODevice* dev) const override;
QString defaultFileExtension() const override;
QString title() const;
QString description() const;
QString creator() const;
QString rights() const;
QString language() const;
QStringList subjects() const;
QString license() const;
QStringList permits() const;
QVector<KoSvgSymbol *> symbols() const;
private:
struct Private;
const QScopedPointer<Private> d;
};
#endif // KoSvgSymbolCollectionResource
diff --git a/libs/flake/resources/tests/KoGamutMaskTest.cpp b/libs/flake/resources/tests/KoGamutMaskTest.cpp
index a7763c74e7..ff275ed606 100644
--- a/libs/flake/resources/tests/KoGamutMaskTest.cpp
+++ b/libs/flake/resources/tests/KoGamutMaskTest.cpp
@@ -1,135 +1,133 @@
/*
* Copyright (c) 2019 Anna Medonosova <anna.medonosova@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 <QTest>
#include <resources/KoGamutMask.h>
-#include <KisGamutMaskViewConverter.h>
#include <testutil.h>
#include "KoGamutMaskTest.h"
KoGamutMaskTest::KoGamutMaskTest(QObject *parent) : QObject(parent)
{
}
void KoGamutMaskTest::testCoordIsClear()
{
QFETCH(QString, maskFile);
QFETCH(QPointF, coord);
QFETCH(int, maskRotation);
QFETCH(bool, expectedOutput);
QScopedPointer<KoGamutMask> mask(new KoGamutMask(TestUtil::fetchDataFileLazy(maskFile)));
mask->load();
Q_ASSERT(mask->valid());
mask->setRotation(maskRotation);
- KisGamutMaskViewConverter* converter = new KisGamutMaskViewConverter();
- converter->setMaskSize(mask->maskSize());
- converter->setViewSize(QSize(100.0, 100.0));
+ // for this test we have a hardcoded view size of 100
+ QPointF translatedPoint = mask->viewToMaskTransform(100).map(coord);
- bool maskOutput = mask->coordIsClear(coord, *converter, false);
+ bool maskOutput = mask->coordIsClear(translatedPoint, false);
QCOMPARE(maskOutput, expectedOutput);
}
void KoGamutMaskTest::testCoordIsClear_data()
{
QTest::addColumn<QString>("maskFile");
QTest::addColumn<QPointF>("coord");
QTest::addColumn<int>("maskRotation");
QTest::addColumn<bool>("expectedOutput");
// single shape mask
QTest::addRow("Atmospheric_Triad.kgm: disallowed coordinate, no rotation") << "Atmospheric_Triad.kgm"
<< QPointF(0.0, 0.0) << 0
<< false;
QTest::addRow("Atmospheric_Triad.kgm: allowed coordinate, no rotation") << "Atmospheric_Triad.kgm"
<< QPointF(33.0, 71.0) << 0
<< true;
QTest::addRow("Atmospheric_Triad.kgm: disallowed coordinate, with rotation") << "Atmospheric_Triad.kgm"
<< QPointF(33.0, 71.0) << 180
<< false;
QTest::addRow("Atmospheric_Triad.kgm: allowed coordinate, with rotation") << "Atmospheric_Triad.kgm"
<< QPointF(76.4,60.9) << 180
<< true;
// multiple shapes mask
QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 1, no rotation")
<< "Dominant_Hue_With_Accent.kgm"
<< QPointF(71.0, 49.0) << 0
<< true;
QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 2, no rotation")
<< "Dominant_Hue_With_Accent.kgm"
<< QPointF(11.0, 51.0) << 0
<< true;
QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 1, with rotation")
<< "Dominant_Hue_With_Accent.kgm"
<< QPointF(40.0, 21.0) << 256
<< true;
QTest::addRow("Dominant_Hue_With_Accent.kgm: allowed coordinate, shape 2, with rotation")
<< "Dominant_Hue_With_Accent.kgm"
<< QPointF(57.0, 82.0) << 256
<< true;
}
void KoGamutMaskTest::testLoad()
{
QFETCH(QString, maskFile);
QFETCH(QString, expectedTitle);
QFETCH(QString, expectedDescription);
QFETCH(int, expectedShapeCount);
QScopedPointer<KoGamutMask> mask(new KoGamutMask(TestUtil::fetchDataFileLazy(maskFile)));
mask->load();
Q_ASSERT(mask->valid());
QCOMPARE(mask->title(), expectedTitle);
QCOMPARE(mask->description(), expectedDescription);
QCOMPARE(mask->koShapes().size(), expectedShapeCount);
}
void KoGamutMaskTest::testLoad_data()
{
QTest::addColumn<QString>("maskFile");
QTest::addColumn<QString>("expectedTitle");
QTest::addColumn<QString>("expectedDescription");
QTest::addColumn<int>("expectedShapeCount");
QTest::addRow("single shape mask")
<< "Atmospheric_Triad.kgm"
<< "Atmospheric Triad" << "test gamut mask description"
<< 1;
QTest::addRow("multiple shape mask")
<< "Dominant_Hue_With_Accent.kgm"
<< "Dominant Hue With Accent" << ""
<< 2;
}
QTEST_MAIN(KoGamutMaskTest);
diff --git a/libs/flake/svg/KoShapePainter.cpp b/libs/flake/svg/KoShapePainter.cpp
index b9380e8cf8..f0961165a1 100644
--- a/libs/flake/svg/KoShapePainter.cpp
+++ b/libs/flake/svg/KoShapePainter.cpp
@@ -1,217 +1,217 @@
/* This file is part of the KDE project
*
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2009 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 "KoShapePainter.h"
#include "KoCanvasBase.h"
#include "KoSelectedShapesProxySimple.h"
#include "KoShapeManager.h"
#include "KoShape.h"
-#include "KoViewConverter.h"
#include "KoShapeStrokeModel.h"
#include "KoShapeGroup.h"
#include "KoShapeContainer.h"
-
+#include <KoViewConverter.h>
#include <KoUnit.h>
#include <QPainter>
#include <QImage>
class SimpleCanvas : public KoCanvasBase
{
public:
SimpleCanvas()
: KoCanvasBase(0),
m_shapeManager(new KoShapeManager(this)),
m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
{
}
~SimpleCanvas() override
{
}
void gridSize(QPointF *offset, QSizeF *spacing) const override
{
*offset = QPointF();
*spacing = QSizeF();
};
bool snapToGrid() const override
{
return false;
}
void addCommand(KUndo2Command *) override
{
}
KoShapeManager *shapeManager() const override
{
return m_shapeManager.data();
}
KoSelectedShapesProxy *selectedShapesProxy() const override
{
return m_selectedShapesProxy.data();
}
void updateCanvas(const QRectF&) override
{
}
KoToolProxy *toolProxy() const override
{
return 0;
}
KoViewConverter *viewConverter() const override
{
return 0;
}
QWidget *canvasWidget() override
{
return 0;
}
const QWidget *canvasWidget() const override
{
return 0;
}
KoUnit unit() const override
{
return KoUnit(KoUnit::Point);
}
void updateInputMethodInfo() override {}
void setCursor(const QCursor &) override {}
private:
QScopedPointer<KoShapeManager> m_shapeManager;
QScopedPointer<KoSelectedShapesProxySimple> m_selectedShapesProxy;
};
class Q_DECL_HIDDEN KoShapePainter::Private
{
public:
Private()
: canvas(new SimpleCanvas())
{
}
~Private() { delete canvas; }
SimpleCanvas * canvas;
};
KoShapePainter::KoShapePainter()
: d(new Private())
{
}
KoShapePainter::~KoShapePainter()
{
delete d;
}
void KoShapePainter::setShapes(const QList<KoShape*> &shapes)
{
d->canvas->shapeManager()->setShapes(shapes, KoShapeManager::AddWithoutRepaint);
}
-void KoShapePainter::paint(QPainter &painter, KoViewConverter &converter)
+void KoShapePainter::paint(QPainter &painter)
{
foreach (KoShape *shape, d->canvas->shapeManager()->shapes()) {
- shape->waitUntilReady(converter, false);
+ shape->waitUntilReady(false);
}
- d->canvas->shapeManager()->paint(painter, converter, true);
+ d->canvas->shapeManager()->paint(painter, true);
}
void KoShapePainter::paint(QPainter &painter, const QRect &painterRect, const QRectF &documentRect)
{
if (documentRect.width() == 0.0f || documentRect.height() == 0.0f)
return;
KoViewConverter converter;
// calculate the painter destination rectangle size in document coordinates
QRectF paintBox = converter.viewToDocument(QRectF(QPointF(), painterRect.size()));
// compute the zoom factor based on the bounding rects in document coordinates
// so that the content fits into the image
qreal zoomW = paintBox.width() / documentRect.width();
qreal zoomH = paintBox.height() / documentRect.height();
qreal zoom = qMin(zoomW, zoomH);
// now set the zoom into the zoom handler used for painting the shape
converter.setZoom(zoom);
painter.save();
// initialize painter
painter.setPen(QPen(Qt::NoPen));
painter.setBrush(Qt::NoBrush);
painter.setRenderHint(QPainter::Antialiasing);
painter.setClipRect(painterRect.adjusted(-1,-1,1,1));
// convert document rectangle to view coordinates
QRectF zoomedBound = converter.documentToView(documentRect);
// calculate offset between painter rectangle and converted document rectangle
QPointF offset = QRectF(painterRect).center() - zoomedBound.center();
// center content in painter rectangle
painter.translate(offset.x(), offset.y());
+ painter.setTransform(converter.documentToView(), true);
// finally paint the shapes
- paint(painter, converter);
+ paint(painter);
painter.restore();
}
void KoShapePainter::paint(QImage &image)
{
if (image.isNull())
return;
QPainter painter(&image);
paint(painter, image.rect(), contentRect());
}
QRectF KoShapePainter::contentRect() const
{
QRectF bound;
foreach (KoShape *shape, d->canvas->shapeManager()->shapes()) {
if (!shape->isVisible())
continue;
if (dynamic_cast<KoShapeGroup*>(shape))
continue;
QRectF shapeRect = shape->boundingRect();
if (bound.isEmpty())
bound = shapeRect;
else
bound = bound.united(shapeRect);
}
return bound;
}
diff --git a/libs/flake/svg/KoShapePainter.h b/libs/flake/svg/KoShapePainter.h
index a76153acfb..4d34e2f1c7 100644
--- a/libs/flake/svg/KoShapePainter.h
+++ b/libs/flake/svg/KoShapePainter.h
@@ -1,83 +1,82 @@
/* 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.
*/
#ifndef KOSHAPEPAINTER_H
#define KOSHAPEPAINTER_H
#include <QList>
#include <QRectF>
#include "kritaflake_export.h"
class KoShape;
-class KoViewConverter;
class QPainter;
class QImage;
/**
* A utility class to paint a subset of shapes onto a QPainter.
* Notice that using setShapes repeatedly is very expensive, as it populates
* the shapeManager and all its caching every time. If at all possible use
* a shapeManager directly and avoid losing the cache between usages.
*/
class KRITAFLAKE_EXPORT KoShapePainter
{
public:
explicit KoShapePainter();
~KoShapePainter();
/**
* Sets the shapes to be painted.
* @param shapes the shapes to paint
*/
void setShapes(const QList<KoShape*> &shapes);
/**
* Paints the shapes on the given painter and using the zoom handler.
* @param painter the painter to paint on
* @param converter the view converter defining the zoom to use
*/
- void paint(QPainter &painter, KoViewConverter &converter);
+ void paint(QPainter &painter);
/**
* Paints the shapes on the given painter.
* The given document rectangle is painted to fit into the given painter rectangle.
*
* @param painter the painter to paint on
* @param painterRect the destination rectangle on the painter
* @param documentRect the document region to paint
*/
void paint(QPainter &painter, const QRect &painterRect, const QRectF &documentRect);
/**
* Paints shapes to the given image, so that all shapes fit onto it.
* @param image the image to paint into
* @return false if image is empty, else true
*/
void paint(QImage &image);
/// Returns the bounding rect of the shapes to paint
QRectF contentRect() const;
private:
class Private;
Private * const d;
};
#endif // KOSHAPEPAINTER_H
diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp
index 4f29f6f072..4707ced8ca 100644
--- a/libs/flake/svg/SvgParser.cpp
+++ b/libs/flake/svg/SvgParser.cpp
@@ -1,1932 +1,1952 @@
/* This file is part of the KDE project
* Copyright (C) 2002-2005,2007 Rob Buis <buis@kde.org>
* Copyright (C) 2002-2004 Nicolas Goutte <nicolasg@snafu.de>
* Copyright (C) 2005-2006 Tim Beaulen <tbscope@gmail.com>
* Copyright (C) 2005-2009 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2005,2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2007 Inge Wallin <inge@lysator.liu.se>
* Copyright (C) 2007-2008,2010 Thorsten Zachmann <zachmann@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 "SvgParser.h"
#include <cmath>
#include <FlakeDebug.h>
#include <QColor>
#include <QPainter>
#include <QDir>
#include <KoShape.h>
#include <KoShapeRegistry.h>
#include <KoShapeFactoryBase.h>
#include <KoShapeGroup.h>
#include <KoPathShape.h>
#include <KoDocumentResourceManager.h>
#include <KoPathShapeLoader.h>
#include <commands/KoShapeGroupCommand.h>
#include <commands/KoShapeUngroupCommand.h>
#include <KoXmlReader.h>
#include <KoImageCollection.h>
#include <KoColorBackground.h>
#include <KoGradientBackground.h>
#include <KoPatternBackground.h>
#include <KoFilterEffectRegistry.h>
#include <KoFilterEffect.h>
#include "KoFilterEffectStack.h"
#include "KoFilterEffectLoadingContext.h"
#include <KoClipPath.h>
#include <KoClipMask.h>
#include <KoXmlNS.h>
#include <QXmlSimpleReader>
#include "SvgUtil.h"
#include "SvgShape.h"
#include "SvgGraphicContext.h"
#include "SvgFilterHelper.h"
#include "SvgGradientHelper.h"
#include "SvgClipPathHelper.h"
#include "parsers/SvgTransformParser.h"
#include "kis_pointer_utils.h"
#include <KoVectorPatternBackground.h>
#include <KoMarker.h>
#include <text/KoSvgTextShape.h>
#include <text/KoSvgTextChunkShape.h>
#include "kis_dom_utils.h"
#include "kis_algebra_2d.h"
#include "kis_debug.h"
#include "kis_global.h"
#include <algorithm>
struct SvgParser::DeferredUseStore {
struct El {
El(const KoXmlElement* ue, const QString& key) :
m_useElement(ue), m_key(key) {
}
const KoXmlElement* m_useElement;
QString m_key;
};
DeferredUseStore(SvgParser* p) :
m_parse(p) {
}
void add(const KoXmlElement* useE, const QString& key) {
m_uses.push_back(El(useE, key));
}
bool empty() const {
return m_uses.empty();
}
void checkPendingUse(const KoXmlElement &b, QList<KoShape*>& shapes) {
KoShape* shape = 0;
const QString id = b.attribute("id");
if (id.isEmpty())
return;
// debugFlake << "Checking id: " << id;
auto i = std::partition(m_uses.begin(), m_uses.end(),
[&](const El& e) -> bool {return e.m_key != id;});
while (i != m_uses.end()) {
const El& el = m_uses.back();
if (m_parse->m_context.hasDefinition(el.m_key)) {
// debugFlake << "Found pending use for id: " << el.m_key;
shape = m_parse->resolveUse(*(el.m_useElement), el.m_key);
if (shape) {
shapes.append(shape);
}
}
m_uses.pop_back();
}
}
~DeferredUseStore() {
while (!m_uses.empty()) {
const El& el = m_uses.back();
debugFlake << "WARNING: could not find path in <use xlink:href=\"#xxxxx\" expression. Losing data here. Key:"
<< el.m_key;
m_uses.pop_back();
}
}
SvgParser* m_parse;
std::vector<El> m_uses;
};
SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager)
: m_context(documentResourceManager)
, m_documentResourceManager(documentResourceManager)
{
}
SvgParser::~SvgParser()
{
}
KoXmlDocument SvgParser::createDocumentFromSvg(QIODevice *device, QString *errorMsg, int *errorLine, int *errorColumn)
{
QXmlInputSource source(device);
return createDocumentFromSvg(&source, errorMsg, errorLine, errorColumn);
}
KoXmlDocument SvgParser::createDocumentFromSvg(const QByteArray &data, QString *errorMsg, int *errorLine, int *errorColumn)
{
QXmlInputSource source;
source.setData(data);
return createDocumentFromSvg(&source, errorMsg, errorLine, errorColumn);
}
KoXmlDocument SvgParser::createDocumentFromSvg(const QString &data, QString *errorMsg, int *errorLine, int *errorColumn)
{
QXmlInputSource source;
source.setData(data);
return createDocumentFromSvg(&source, errorMsg, errorLine, errorColumn);
}
KoXmlDocument SvgParser::createDocumentFromSvg(QXmlInputSource *source, QString *errorMsg, int *errorLine, int *errorColumn)
{
// we should read all spaces to parse text node correctly
QXmlSimpleReader reader;
reader.setFeature("http://qt-project.org/xml/features/report-whitespace-only-CharData", true);
reader.setFeature("http://xml.org/sax/features/namespaces", false);
reader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
QDomDocument doc;
if (!doc.setContent(source, &reader, errorMsg, errorLine, errorColumn)) {
return QDomDocument();
}
return doc;
}
void SvgParser::setXmlBaseDir(const QString &baseDir)
{
m_context.setInitialXmlBaseDir(baseDir);
setFileFetcher(
[this](const QString &name) {
const QString fileName = m_context.xmlBaseDir() + QDir::separator() + name;
QFile file(fileName);
if (!file.exists()) {
return QByteArray();
}
file.open(QIODevice::ReadOnly);
return file.readAll();
});
}
void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch)
{
KIS_ASSERT(!m_context.currentGC());
m_context.pushGraphicsContext();
m_context.currentGC()->isResolutionFrame = true;
m_context.currentGC()->pixelsPerInch = pixelsPerInch;
const qreal scale = 72.0 / pixelsPerInch;
const QTransform t = QTransform::fromScale(scale, scale);
m_context.currentGC()->currentBoundingBox = boundsInPixels;
m_context.currentGC()->matrix = t;
}
void SvgParser::setForcedFontSizeResolution(qreal value)
{
if (qFuzzyCompare(value, 0.0)) return;
m_context.currentGC()->forcedFontSizeCoeff = 72.0 / value;
}
QList<KoShape*> SvgParser::shapes() const
{
return m_shapes;
}
QVector<KoSvgSymbol *> SvgParser::takeSymbols()
{
QVector<KoSvgSymbol*> symbols = m_symbols.values().toVector();
m_symbols.clear();
return symbols;
}
// Helper functions
// ---------------------------------------------------------------------------------------
SvgGradientHelper* SvgParser::findGradient(const QString &id)
{
SvgGradientHelper *result = 0;
// check if gradient was already parsed, and return it
if (m_gradients.contains(id)) {
result = &m_gradients[ id ];
}
// check if gradient was stored for later parsing
if (!result && m_context.hasDefinition(id)) {
const KoXmlElement &e = m_context.definition(id);
if (e.tagName().contains("Gradient")) {
result = parseGradient(m_context.definition(id));
}
}
return result;
}
QSharedPointer<KoVectorPatternBackground> SvgParser::findPattern(const QString &id, const KoShape *shape)
{
QSharedPointer<KoVectorPatternBackground> result;
// check if gradient was stored for later parsing
if (m_context.hasDefinition(id)) {
const KoXmlElement &e = m_context.definition(id);
if (e.tagName() == "pattern") {
result = parsePattern(m_context.definition(id), shape);
}
}
return result;
}
SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href)
{
// check if filter was already parsed, and return it
if (m_filters.contains(id))
return &m_filters[ id ];
// check if filter was stored for later parsing
if (!m_context.hasDefinition(id))
return 0;
const KoXmlElement &e = m_context.definition(id);
if (KoXml::childNodesCount(e) == 0) {
QString mhref = e.attribute("xlink:href").mid(1);
if (m_context.hasDefinition(mhref))
return findFilter(mhref, id);
else
return 0;
} else {
// ok parse filter now
if (! parseFilter(m_context.definition(id), m_context.definition(href)))
return 0;
}
// return successfully parsed filter or 0
QString n;
if (href.isEmpty())
n = id;
else
n = href;
if (m_filters.contains(n))
return &m_filters[ n ];
else
return 0;
}
SvgClipPathHelper* SvgParser::findClipPath(const QString &id)
{
return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0;
}
// Parsing functions
// ---------------------------------------------------------------------------------------
qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox)
{
return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox);
}
qreal SvgParser::parseUnitX(const QString &unit)
{
return SvgUtil::parseUnitX(m_context.currentGC(), unit);
}
qreal SvgParser::parseUnitY(const QString &unit)
{
return SvgUtil::parseUnitY(m_context.currentGC(), unit);
}
qreal SvgParser::parseUnitXY(const QString &unit)
{
return SvgUtil::parseUnitXY(m_context.currentGC(), unit);
}
qreal SvgParser::parseAngular(const QString &unit)
{
return SvgUtil::parseUnitAngular(m_context.currentGC(), unit);
}
SvgGradientHelper* SvgParser::parseGradient(const KoXmlElement &e)
{
// IMPROVEMENTS:
// - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again.
// - A gradient inherits attributes it does not have from the referencing gradient.
// - Gradients with no color stops have no fill or stroke.
// - Gradients with one color stop have a solid color.
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc) return 0;
SvgGradientHelper gradHelper;
QString gradientId = e.attribute("id");
if (gradientId.isEmpty()) return 0;
// check if we have this gradient already parsed
// copy existing gradient if it exists
if (m_gradients.contains(gradientId)) {
return &m_gradients[gradientId];
}
if (e.hasAttribute("xlink:href")) {
// strip the '#' symbol
QString href = e.attribute("xlink:href").mid(1);
if (!href.isEmpty()) {
// copy the referenced gradient if found
SvgGradientHelper *pGrad = findGradient(href);
if (pGrad) {
gradHelper = *pGrad;
}
}
}
const QGradientStops defaultStops = gradHelper.gradient()->stops();
if (e.attribute("gradientUnits") == "userSpaceOnUse") {
gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse);
}
m_context.pushGraphicsContext(e);
uploadStyleToContext(e);
if (e.tagName() == "linearGradient") {
QLinearGradient *g = new QLinearGradient();
if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
g->setCoordinateMode(QGradient::ObjectBoundingMode);
g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")),
SvgUtil::fromPercentage(e.attribute("y1", "0%"))));
g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")),
SvgUtil::fromPercentage(e.attribute("y2", "0%"))));
} else {
g->setStart(QPointF(parseUnitX(e.attribute("x1")),
parseUnitY(e.attribute("y1"))));
g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")),
parseUnitY(e.attribute("y2"))));
}
gradHelper.setGradient(g);
} else if (e.tagName() == "radialGradient") {
QRadialGradient *g = new QRadialGradient();
if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
g->setCoordinateMode(QGradient::ObjectBoundingMode);
g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")),
SvgUtil::fromPercentage(e.attribute("cy", "50%"))));
g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%")));
g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")),
SvgUtil::fromPercentage(e.attribute("fy", "50%"))));
} else {
g->setCenter(QPointF(parseUnitX(e.attribute("cx")),
parseUnitY(e.attribute("cy"))));
g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")),
parseUnitY(e.attribute("fy"))));
g->setRadius(parseUnitXY(e.attribute("r")));
}
gradHelper.setGradient(g);
} else {
debugFlake << "WARNING: Failed to parse gradient with tag" << e.tagName();
}
// handle spread method
QGradient::Spread spreadMethod = QGradient::PadSpread;
QString spreadMethodStr = e.attribute("spreadMethod");
if (!spreadMethodStr.isEmpty()) {
if (spreadMethodStr == "reflect") {
spreadMethod = QGradient::ReflectSpread;
} else if (spreadMethodStr == "repeat") {
spreadMethod = QGradient::RepeatSpread;
}
}
gradHelper.setSpreadMode(spreadMethod);
// Parse the color stops.
m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops);
if (e.hasAttribute("gradientTransform")) {
SvgTransformParser p(e.attribute("gradientTransform"));
if (p.isValid()) {
gradHelper.setTransform(p.transform());
}
}
m_context.popGraphicsContext();
m_gradients.insert(gradientId, gradHelper);
return &m_gradients[gradientId];
}
inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset)
{
QTransform result =
patternTransform *
QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) *
patternTransform.inverted();
KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate);
return QPointF(result.dx(), result.dy());
}
QSharedPointer<KoVectorPatternBackground> SvgParser::parsePattern(const KoXmlElement &e, const KoShape *shape)
{
/**
* Unlike the gradient parsing function, this method is called every time we
* *reference* the pattern, not when we define it. Therefore we can already
* use the coordinate system of the destination.
*/
QSharedPointer<KoVectorPatternBackground> pattHelper;
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc) return pattHelper;
const QString patternId = e.attribute("id");
if (patternId.isEmpty()) return pattHelper;
pattHelper = toQShared(new KoVectorPatternBackground);
if (e.hasAttribute("xlink:href")) {
// strip the '#' symbol
QString href = e.attribute("xlink:href").mid(1);
if (!href.isEmpty() &&href != patternId) {
// copy the referenced pattern if found
QSharedPointer<KoVectorPatternBackground> pPatt = findPattern(href, shape);
if (pPatt) {
pattHelper = pPatt;
}
}
}
pattHelper->setReferenceCoordinates(
KoFlake::coordinatesFromString(e.attribute("patternUnits"),
pattHelper->referenceCoordinates()));
pattHelper->setContentCoordinates(
KoFlake::coordinatesFromString(e.attribute("patternContentUnits"),
pattHelper->contentCoordinates()));
if (e.hasAttribute("patternTransform")) {
SvgTransformParser p(e.attribute("patternTransform"));
if (p.isValid()) {
pattHelper->setPatternTransform(p.transform());
}
}
if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) {
QRectF referenceRect(
SvgUtil::fromPercentage(e.attribute("x", "0%")),
SvgUtil::fromPercentage(e.attribute("y", "0%")),
SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why!
SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why!
pattHelper->setReferenceRect(referenceRect);
} else {
QRectF referenceRect(
parseUnitX(e.attribute("x", "0")),
parseUnitY(e.attribute("y", "0")),
parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why!
parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why!
pattHelper->setReferenceRect(referenceRect);
}
/**
* In Krita shapes X,Y coordinates are baked into the shape global transform, but
* the pattern should be painted in "user" coordinates. Therefore, we should handle
* this offfset separately.
*
* TODO: Please also note that this offset is different from extraShapeOffset(),
* because A.inverted() * B != A * B.inverted(). I'm not sure which variant is
* correct (DK)
*/
- const QTransform dstShapeTransform = shape->absoluteTransformation(0);
+ const QTransform dstShapeTransform = shape->absoluteTransformation();
const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted();
KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate);
const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy());
m_context.pushGraphicsContext(e);
gc = m_context.currentGC();
gc->workaroundClearInheritedFillProperties(); // HACK!
// start building shape tree from scratch
gc->matrix = QTransform();
const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/;
const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
boundingRect.x(), boundingRect.y());
// WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes!
// although we expect the pattern be reusable, but it is not so!
// WARNING2: the pattern shapes are stored in *User* coordinate system, although
// the "official" content system might be either OBB or User. It means that
// this baked transform should be stripped before writing the shapes back
// into SVG
if (e.hasAttribute("viewBox")) {
gc->currentBoundingBox =
pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ?
relativeToShape.mapRect(pattHelper->referenceRect()) :
pattHelper->referenceRect();
applyViewBoxTransform(e);
pattHelper->setContentCoordinates(pattHelper->referenceCoordinates());
} else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) {
gc->matrix = relativeToShape * gc->matrix;
}
// We do *not* apply patternTransform here! Here we only bake the untransformed
// version of the shape. The transformed one will be done in the very end while rendering.
QList<KoShape*> patternShapes = parseContainer(e);
if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) {
// In Krita we normalize the shapes, bake this transform into the pattern shapes
const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
Q_FOREACH (KoShape *shape, patternShapes) {
shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y()));
}
}
if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) {
// In Krita we normalize the shapes, bake this transform into reference rect
// NOTE: this is possible *only* when pattern transform is not perspective
// (which is always true for SVG)
const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
QRectF ref = pattHelper->referenceRect();
ref.translate(offset);
pattHelper->setReferenceRect(ref);
}
m_context.popGraphicsContext();
gc = m_context.currentGC();
if (!patternShapes.isEmpty()) {
pattHelper->setShapes(patternShapes);
}
return pattHelper;
}
bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy)
{
SvgFilterHelper filter;
// Use the filter that is referencing, or if there isn't one, the original filter
KoXmlElement b;
if (!referencedBy.isNull())
b = referencedBy;
else
b = e;
// check if we are referencing another filter
if (e.hasAttribute("xlink:href")) {
QString href = e.attribute("xlink:href").mid(1);
if (! href.isEmpty()) {
// copy the referenced filter if found
SvgFilterHelper *refFilter = findFilter(href);
if (refFilter)
filter = *refFilter;
}
} else {
filter.setContent(b);
}
if (b.attribute("filterUnits") == "userSpaceOnUse")
filter.setFilterUnits(KoFlake::UserSpaceOnUse);
if (b.attribute("primitiveUnits") == "objectBoundingBox")
filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox);
// parse filter region rectangle
if (filter.filterUnits() == KoFlake::UserSpaceOnUse) {
filter.setPosition(QPointF(parseUnitX(b.attribute("x")),
parseUnitY(b.attribute("y"))));
filter.setSize(QSizeF(parseUnitX(b.attribute("width")),
parseUnitY(b.attribute("height"))));
} else {
// x, y, width, height are in percentages of the object referencing the filter
// so we just parse the percentages
filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")),
SvgUtil::fromPercentage(b.attribute("y", "-0.1"))));
filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")),
SvgUtil::fromPercentage(b.attribute("height", "1.2"))));
}
m_filters.insert(b.attribute("id"), filter);
return true;
}
bool SvgParser::parseMarker(const KoXmlElement &e)
{
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
QScopedPointer<KoMarker> marker(new KoMarker());
marker->setCoordinateSystem(
KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth")));
marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")),
parseUnitY(e.attribute("refY"))));
marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")),
parseUnitY(e.attribute("markerHeight", "3"))));
const QString orientation = e.attribute("orient", "0");
if (orientation == "auto") {
marker->setAutoOrientation(true);
} else {
marker->setExplicitOrientation(parseAngular(orientation));
}
// ensure that the clip path is loaded in local coordinates system
m_context.pushGraphicsContext(e, false);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize());
KoShape *markerShape = parseGroup(e);
m_context.popGraphicsContext();
if (!markerShape) return false;
marker->setShapes({markerShape});
m_markers.insert(id, QExplicitlySharedDataPointer<KoMarker>(marker.take()));
return true;
}
bool SvgParser::parseSymbol(const KoXmlElement &e)
{
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
QScopedPointer<KoSvgSymbol> svgSymbol(new KoSvgSymbol());
// ensure that the clip path is loaded in local coordinates system
m_context.pushGraphicsContext(e, false);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->currentBoundingBox = QRectF(0.0, 0.0, 1.0, 1.0);
QString title = e.firstChildElement("title").toElement().text();
QScopedPointer<KoShape> symbolShape(parseGroup(e));
m_context.popGraphicsContext();
if (!symbolShape) return false;
svgSymbol->shape = symbolShape.take();
svgSymbol->title = title;
svgSymbol->id = id;
if (title.isEmpty()) svgSymbol->title = id;
if (svgSymbol->shape->boundingRect() == QRectF(0.0, 0.0, 0.0, 0.0)) {
debugFlake << "Symbol" << id << "seems to be empty, discarding";
return false;
}
m_symbols.insert(id, svgSymbol.take());
return true;
}
bool SvgParser::parseClipPath(const KoXmlElement &e)
{
SvgClipPathHelper clipPath;
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
clipPath.setClipPathUnits(
KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse));
// ensure that the clip path is loaded in local coordinates system
m_context.pushGraphicsContext(e);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK!
KoShape *clipShape = parseGroup(e);
m_context.popGraphicsContext();
if (!clipShape) return false;
clipPath.setShapes({clipShape});
m_clipPaths.insert(id, clipPath);
return true;
}
bool SvgParser::parseClipMask(const KoXmlElement &e)
{
QSharedPointer<KoClipMask> clipMask(new KoClipMask);
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox));
clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse));
QRectF maskRect;
if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) {
maskRect.setRect(
SvgUtil::fromPercentage(e.attribute("x", "-10%")),
SvgUtil::fromPercentage(e.attribute("y", "-10%")),
SvgUtil::fromPercentage(e.attribute("width", "120%")),
SvgUtil::fromPercentage(e.attribute("height", "120%")));
} else {
maskRect.setRect(
parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case,
parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us...
parseUnitX(e.attribute("width", "120%")),
parseUnitY(e.attribute("height", "120%")));
}
clipMask->setMaskRect(maskRect);
// ensure that the clip mask is loaded in local coordinates system
m_context.pushGraphicsContext(e);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK!
KoShape *clipShape = parseGroup(e);
m_context.popGraphicsContext();
if (!clipShape) return false;
clipMask->setShapes({clipShape});
m_clipMasks.insert(id, clipMask);
return true;
}
void SvgParser::uploadStyleToContext(const KoXmlElement &e)
{
SvgStyles styles = m_context.styleParser().collectStyles(e);
m_context.styleParser().parseFont(styles);
m_context.styleParser().parseStyle(styles);
}
void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
{
if (!shape) return;
applyCurrentBasicStyle(shape);
if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape)) {
applyMarkers(pathShape);
}
applyFilter(shape);
applyClipping(shape, shapeToOriginalUserCoordinates);
applyMaskClipping(shape, shapeToOriginalUserCoordinates);
}
void SvgParser::applyCurrentBasicStyle(KoShape *shape)
{
if (!shape) return;
SvgGraphicsContext *gc = m_context.currentGC();
KIS_ASSERT(gc);
if (!dynamic_cast<KoShapeGroup*>(shape)) {
applyFillStyle(shape);
applyStrokeStyle(shape);
}
if (!gc->display || !gc->visible) {
/**
* WARNING: here is a small inconsistency with the standard:
* in the standard, 'display' is not inherited, but in
* flake it is!
*
* NOTE: though the standard says: "A value of 'display:none' indicates
* that the given element and ***its children*** shall not be
* rendered directly". Therefore, using setVisible(false) is fully
* legitimate here (DK 29.11.16).
*/
shape->setVisible(false);
}
shape->setTransparency(1.0 - gc->opacity);
}
void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e, const QPointF &shapeToOriginalUserCoordinates)
{
applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates);
}
void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc)
return;
m_context.styleParser().parseStyle(styles);
if (!obj)
return;
if (!dynamic_cast<KoShapeGroup*>(obj)) {
applyFillStyle(obj);
applyStrokeStyle(obj);
}
if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(obj)) {
applyMarkers(pathShape);
}
applyFilter(obj);
applyClipping(obj, shapeToOriginalUserCoordinates);
applyMaskClipping(obj, shapeToOriginalUserCoordinates);
if (!gc->display || !gc->visible) {
obj->setVisible(false);
}
obj->setTransparency(1.0 - gc->opacity);
}
QGradient* prepareGradientForShape(const SvgGradientHelper *gradient,
const KoShape *shape,
const SvgGraphicsContext *gc,
QTransform *transform)
{
QGradient *resultGradient = 0;
KIS_ASSERT(transform);
if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
resultGradient = KoFlake::cloneGradient(gradient->gradient());
*transform = gradient->transform();
} else {
if (gradient->gradient()->type() == QGradient::LinearGradient) {
/**
* Create a converted gradient that looks the same, but linked to the
* bounding rect of the shape, so it would be transformed with the shape
*/
const QRectF boundingRect = shape->outline().boundingRect();
const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
boundingRect.x(), boundingRect.y());
const QTransform relativeToUser =
relativeToShape * shape->transformation() * gc->matrix.inverted();
const QTransform userToRelative = relativeToUser.inverted();
const QLinearGradient *o = static_cast<const QLinearGradient*>(gradient->gradient());
QLinearGradient *g = new QLinearGradient();
g->setStart(userToRelative.map(o->start()));
g->setFinalStop(userToRelative.map(o->finalStop()));
g->setCoordinateMode(QGradient::ObjectBoundingMode);
g->setStops(o->stops());
g->setSpread(o->spread());
resultGradient = g;
*transform = relativeToUser * gradient->transform() * userToRelative;
} else if (gradient->gradient()->type() == QGradient::RadialGradient) {
// For radial and conical gradients such conversion is not possible
resultGradient = KoFlake::cloneGradient(gradient->gradient());
*transform = gradient->transform() * gc->matrix * shape->transformation().inverted();
const QRectF outlineRect = shape->outlineRect();
if (outlineRect.isEmpty()) return resultGradient;
/**
* If shape outline rect is valid, convert the gradient into OBB mode by
* doing some magic conversions: we compensate non-uniform size of the shape
* by applying an additional pre-transform
*/
QRadialGradient *rgradient = static_cast<QRadialGradient*>(resultGradient);
const qreal maxDimension = KisAlgebra2D::maxDimension(outlineRect);
const QRectF uniformSize(outlineRect.topLeft(), QSizeF(maxDimension, maxDimension));
const QTransform uniformizeTransform =
QTransform::fromTranslate(-outlineRect.x(), -outlineRect.y()) *
QTransform::fromScale(maxDimension / shape->outlineRect().width(),
maxDimension / shape->outlineRect().height()) *
QTransform::fromTranslate(outlineRect.x(), outlineRect.y());
const QPointF centerLocal = transform->map(rgradient->center());
const QPointF focalLocal = transform->map(rgradient->focalPoint());
const QPointF centerOBB = KisAlgebra2D::absoluteToRelative(centerLocal, uniformSize);
const QPointF focalOBB = KisAlgebra2D::absoluteToRelative(focalLocal, uniformSize);
rgradient->setCenter(centerOBB);
rgradient->setFocalPoint(focalOBB);
const qreal centerRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->centerRadius(), uniformSize);
const qreal focalRadiusOBB = KisAlgebra2D::absoluteToRelative(rgradient->focalRadius(), uniformSize);
rgradient->setCenterRadius(centerRadiusOBB);
rgradient->setFocalRadius(focalRadiusOBB);
rgradient->setCoordinateMode(QGradient::ObjectBoundingMode);
// Warning: should it really be pre-multiplication?
*transform = uniformizeTransform * gradient->transform();
}
}
return resultGradient;
}
void SvgParser::applyFillStyle(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->fillType == SvgGraphicsContext::None) {
shape->setBackground(QSharedPointer<KoShapeBackground>(0));
} else if (gc->fillType == SvgGraphicsContext::Solid) {
shape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(gc->fillColor)));
} else if (gc->fillType == SvgGraphicsContext::Complex) {
// try to find referenced gradient
SvgGradientHelper *gradient = findGradient(gc->fillId);
if (gradient) {
QTransform transform;
QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
if (result) {
QSharedPointer<KoGradientBackground> bg;
bg = toQShared(new KoGradientBackground(result));
bg->setTransform(transform);
shape->setBackground(bg);
}
} else {
QSharedPointer<KoVectorPatternBackground> pattern =
findPattern(gc->fillId, shape);
if (pattern) {
shape->setBackground(pattern);
} else {
// no referenced fill found, use fallback color
shape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(gc->fillColor)));
}
}
}
KoPathShape *path = dynamic_cast<KoPathShape*>(shape);
if (path)
path->setFillRule(gc->fillRule);
}
void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke)
{
const double lineWidth = srcStroke->lineWidth();
QVector<qreal> dashes = srcStroke->lineDashes();
// apply line width to dashes and dash offset
if (dashes.count() && lineWidth > 0.0) {
const double dashOffset = srcStroke->dashOffset();
QVector<qreal> dashes = srcStroke->lineDashes();
for (int i = 0; i < dashes.count(); ++i) {
dashes[i] /= lineWidth;
}
dstStroke->setLineStyle(Qt::CustomDashLine, dashes);
dstStroke->setDashOffset(dashOffset / lineWidth);
} else {
dstStroke->setLineStyle(Qt::SolidLine, QVector<qreal>());
}
}
void SvgParser::applyStrokeStyle(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->strokeType == SvgGraphicsContext::None) {
shape->setStroke(KoShapeStrokeModelSP());
} else if (gc->strokeType == SvgGraphicsContext::Solid) {
KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
applyDashes(gc->stroke, stroke);
shape->setStroke(stroke);
} else if (gc->strokeType == SvgGraphicsContext::Complex) {
// try to find referenced gradient
SvgGradientHelper *gradient = findGradient(gc->strokeId);
if (gradient) {
QTransform transform;
QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
if (result) {
QBrush brush = *result;
delete result;
brush.setTransform(transform);
KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
stroke->setLineBrush(brush);
applyDashes(gc->stroke, stroke);
shape->setStroke(stroke);
}
} else {
// no referenced stroke found, use fallback color
KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
applyDashes(gc->stroke, stroke);
shape->setStroke(stroke);
}
}
}
void SvgParser::applyFilter(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->filterId.isEmpty())
return;
SvgFilterHelper *filter = findFilter(gc->filterId);
if (! filter)
return;
KoXmlElement content = filter->content();
// parse filter region
QRectF bound(shape->position(), shape->size());
// work on bounding box without viewbox transformation applied
// so user space coordinates of bounding box and filter region match up
bound = gc->viewboxTransform.inverted().mapRect(bound);
QRectF filterRegion(filter->position(bound), filter->size(bound));
// convert filter region to boundingbox units
QRectF objectFilterRegion;
objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound));
objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound));
KoFilterEffectLoadingContext context(m_context.xmlBaseDir());
context.setShapeBoundingBox(bound);
// enable units conversion
context.enableFilterUnitsConversion(filter->filterUnits() == KoFlake::UserSpaceOnUse);
context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == KoFlake::UserSpaceOnUse);
KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance();
KoFilterEffectStack *filterStack = 0;
QSet<QString> stdInputs;
stdInputs << "SourceGraphic" << "SourceAlpha";
stdInputs << "BackgroundImage" << "BackgroundAlpha";
stdInputs << "FillPaint" << "StrokePaint";
QMap<QString, KoFilterEffect*> inputs;
// create the filter effects and add them to the shape
for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) {
KoXmlElement primitive = n.toElement();
KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context);
if (!filterEffect) {
debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet";
continue;
}
const QString input = primitive.attribute("in");
if (!input.isEmpty()) {
filterEffect->setInput(0, input);
}
const QString output = primitive.attribute("result");
if (!output.isEmpty()) {
filterEffect->setOutput(output);
}
QRectF subRegion;
// parse subregion
if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) {
const QString xa = primitive.attribute("x");
const QString ya = primitive.attribute("y");
const QString wa = primitive.attribute("width");
const QString ha = primitive.attribute("height");
if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) {
bool hasStdInput = false;
bool isFirstEffect = filterStack == 0;
// check if one of the inputs is a standard input
Q_FOREACH (const QString &input, filterEffect->inputs()) {
if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) {
hasStdInput = true;
break;
}
}
if (hasStdInput || primitive.tagName() == "feImage") {
// default to 0%, 0%, 100%, 100%
subRegion.setTopLeft(QPointF(0, 0));
subRegion.setSize(QSizeF(1, 1));
} else {
// defaults to bounding rect of all referenced nodes
Q_FOREACH (const QString &input, filterEffect->inputs()) {
if (!inputs.contains(input))
continue;
KoFilterEffect *inputFilter = inputs[input];
if (inputFilter)
subRegion |= inputFilter->filterRect();
}
}
} else {
const qreal x = parseUnitX(xa);
const qreal y = parseUnitY(ya);
const qreal w = parseUnitX(wa);
const qreal h = parseUnitY(ha);
subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound));
subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound));
}
} else {
// x, y, width, height are in percentages of the object referencing the filter
// so we just parse the percentages
const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0"));
const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0"));
const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1"));
const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1"));
subRegion = QRectF(QPointF(x, y), QSizeF(w, h));
}
filterEffect->setFilterRect(subRegion);
if (!filterStack)
filterStack = new KoFilterEffectStack();
filterStack->appendFilterEffect(filterEffect);
inputs[filterEffect->output()] = filterEffect;
}
if (filterStack) {
filterStack->setClipRect(objectFilterRegion);
shape->setFilterEffectStack(filterStack);
}
}
void SvgParser::applyMarkers(KoPathShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc)
return;
if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) {
shape->setMarker(m_markers[gc->markerStartId].data(), KoFlake::StartMarker);
}
if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) {
shape->setMarker(m_markers[gc->markerMidId].data(), KoFlake::MidMarker);
}
if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) {
shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker);
}
shape->setAutoFillMarkers(gc->autoFillMarkers);
}
void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->clipPathId.isEmpty())
return;
SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId);
if (!clipPath || clipPath->isEmpty())
return;
QList<KoShape*> shapes;
Q_FOREACH (KoShape *item, clipPath->shapes()) {
KoShape *clonedShape = item->cloneShape();
KIS_ASSERT_RECOVER(clonedShape) { continue; }
shapes.append(clonedShape);
}
if (!shapeToOriginalUserCoordinates.isNull()) {
const QTransform t =
QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(),
shapeToOriginalUserCoordinates.y());
Q_FOREACH(KoShape *s, shapes) {
s->applyAbsoluteTransformation(t);
}
}
KoClipPath *clipPathObject = new KoClipPath(shapes,
clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ?
KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse);
shape->setClipPath(clipPathObject);
}
void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc)
return;
if (gc->clipMaskId.isEmpty())
return;
QSharedPointer<KoClipMask> originalClipMask = m_clipMasks.value(gc->clipMaskId);
if (!originalClipMask || originalClipMask->isEmpty()) return;
KoClipMask *clipMask = originalClipMask->clone();
clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates);
shape->setClipMask(clipMask);
}
KoShape* SvgParser::parseUse(const KoXmlElement &e, DeferredUseStore* deferredUseStore)
{
QString href = e.attribute("xlink:href");
if (href.isEmpty())
return 0;
QString key = href.mid(1);
const bool gotDef = m_context.hasDefinition(key);
if (gotDef) {
return resolveUse(e, key);
} else if (deferredUseStore) {
deferredUseStore->add(&e, key);
return 0;
}
debugFlake << "WARNING: Did not find reference for svg 'use' element. Skipping. Id: "
<< key;
return 0;
}
KoShape* SvgParser::resolveUse(const KoXmlElement &e, const QString& key)
{
KoShape *result = 0;
SvgGraphicsContext *gc = m_context.pushGraphicsContext(e);
// TODO: parse 'width' and 'height' as well
gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")));
const KoXmlElement &referencedElement = m_context.definition(key);
- result = parseGroup(e, referencedElement);
+ result = parseGroup(e, referencedElement, false);
m_context.popGraphicsContext();
return result;
}
void SvgParser::addToGroup(QList<KoShape*> shapes, KoShapeContainer *group)
{
m_shapes += shapes;
if (!group || shapes.isEmpty())
return;
// not normalized
KoShapeGroupCommand cmd(group, shapes, false);
cmd.redo();
}
QList<KoShape*> SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize)
{
// check if we are the root svg element
const bool isRootSvg = m_context.isRootContext();
// parse 'transform' field if preset
SvgGraphicsContext *gc = m_context.pushGraphicsContext(e);
applyStyle(0, e, QPointF());
const QString w = e.attribute("width");
const QString h = e.attribute("height");
qreal width = w.isEmpty() ? 666.0 : parseUnitX(w);
qreal height = h.isEmpty() ? 555.0 : parseUnitY(h);
if (w.isEmpty() || h.isEmpty()) {
QRectF viewRect;
QTransform viewTransform_unused;
QRectF fakeBoundingRect(0.0, 0.0, 1.0, 1.0);
if (SvgUtil::parseViewBox(e, fakeBoundingRect,
&viewRect, &viewTransform_unused)) {
QSizeF estimatedSize = viewRect.size();
if (estimatedSize.isValid()) {
if (!w.isEmpty()) {
estimatedSize = QSizeF(width, width * estimatedSize.height() / estimatedSize.width());
} else if (!h.isEmpty()) {
estimatedSize = QSizeF(height * estimatedSize.width() / estimatedSize.height(), height);
}
width = estimatedSize.width();
height = estimatedSize.height();
}
}
}
QSizeF svgFragmentSize(QSizeF(width, height));
if (fragmentSize) {
*fragmentSize = svgFragmentSize;
}
gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize);
if (!isRootSvg) {
// x and y attribute has no meaning for outermost svg elements
const qreal x = parseUnit(e.attribute("x", "0"));
const qreal y = parseUnit(e.attribute("y", "0"));
QTransform move = QTransform::fromTranslate(x, y);
gc->matrix = move * gc->matrix;
}
applyViewBoxTransform(e);
QList<KoShape*> shapes;
// First find the metadata
for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
KoXmlElement b = n.toElement();
if (b.isNull())
continue;
if (b.tagName() == "title") {
m_documentTitle = b.text().trimmed();
}
else if (b.tagName() == "desc") {
m_documentDescription = b.text().trimmed();
}
else if (b.tagName() == "metadata") {
// TODO: parse the metadata
}
}
// SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy
// and as mother makes them -- if mother is inkscape.
if (gc->currentBoundingBox.normalized().isValid()) {
shapes = parseContainer(e);
}
m_context.popGraphicsContext();
return shapes;
}
void SvgParser::applyViewBoxTransform(const KoXmlElement &element)
{
SvgGraphicsContext *gc = m_context.currentGC();
QRectF viewRect = gc->currentBoundingBox;
QTransform viewTransform;
if (SvgUtil::parseViewBox(element, gc->currentBoundingBox,
&viewRect, &viewTransform)) {
gc->matrix = viewTransform * gc->matrix;
gc->currentBoundingBox = viewRect;
}
}
QList<QExplicitlySharedDataPointer<KoMarker> > SvgParser::knownMarkers() const
{
return m_markers.values();
}
QString SvgParser::documentTitle() const
{
return m_documentTitle;
}
QString SvgParser::documentDescription() const
{
return m_documentDescription;
}
void SvgParser::setFileFetcher(SvgParser::FileFetcherFunc func)
{
m_context.setFileFetcher(func);
}
inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading)
{
const QTransform shapeToOriginalUserCoordinates =
- shape->absoluteTransformation(0).inverted() *
+ shape->absoluteTransformation().inverted() *
coordinateSystemOnLoading;
KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate);
return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy());
}
-KoShape* SvgParser::parseGroup(const KoXmlElement &b, const KoXmlElement &overrideChildrenFrom)
+KoShape* SvgParser::parseGroup(const KoXmlElement &b, const KoXmlElement &overrideChildrenFrom, bool createContext)
{
- m_context.pushGraphicsContext(b);
+ if (createContext) {
+ m_context.pushGraphicsContext(b);
+ }
KoShapeGroup *group = new KoShapeGroup();
group->setZIndex(m_context.nextZIndex());
// groups should also have their own coordinate system!
group->applyAbsoluteTransformation(m_context.currentGC()->matrix);
const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix);
uploadStyleToContext(b);
QList<KoShape*> childShapes;
if (!overrideChildrenFrom.isNull()) {
// we upload styles from both: <use> and <defs>
uploadStyleToContext(overrideChildrenFrom);
childShapes = parseSingleElement(overrideChildrenFrom, 0);
} else {
childShapes = parseContainer(b);
}
// handle id
applyId(b.attribute("id"), group);
addToGroup(childShapes, group);
applyCurrentStyle(group, extraOffset); // apply style to this group after size is set
- m_context.popGraphicsContext();
+ if (createContext) {
+ m_context.popGraphicsContext();
+ }
return group;
}
KoShape* SvgParser::parseTextNode(const KoXmlText &e)
{
QScopedPointer<KoSvgTextChunkShape> textChunk(new KoSvgTextChunkShape());
textChunk->setZIndex(m_context.nextZIndex());
if (!textChunk->loadSvgTextNode(e, m_context)) {
return 0;
}
textChunk->applyAbsoluteTransformation(m_context.currentGC()->matrix);
applyCurrentBasicStyle(textChunk.data()); // apply style to this group after size is set
return textChunk.take();
}
KoXmlText getTheOnlyTextChild(const KoXmlElement &e)
{
KoXmlNode firstChild = e.firstChild();
return !firstChild.isNull() && firstChild == e.lastChild() && firstChild.isText() ?
firstChild.toText() : KoXmlText();
}
KoShape *SvgParser::parseTextElement(const KoXmlElement &e, KoSvgTextShape *mergeIntoShape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || e.tagName() == "tspan", 0);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_isInsideTextSubtree || e.tagName() == "text", 0);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(e.tagName() == "text" || !mergeIntoShape, 0);
KoSvgTextShape *rootTextShape = 0;
if (e.tagName() == "text") {
// XXX: Shapes need to be created by their factories
if (mergeIntoShape) {
rootTextShape = mergeIntoShape;
} else {
rootTextShape = new KoSvgTextShape();
const QString useRichText = e.attribute("krita:useRichText", "true");
rootTextShape->setRichTextPreferred(useRichText != "false");
}
}
if (rootTextShape) {
m_isInsideTextSubtree = true;
}
m_context.pushGraphicsContext(e);
uploadStyleToContext(e);
KoSvgTextChunkShape *textChunk = rootTextShape ? rootTextShape : new KoSvgTextChunkShape();
if (!mergeIntoShape) {
textChunk->setZIndex(m_context.nextZIndex());
}
textChunk->loadSvg(e, m_context);
// 1) apply transformation only in case we are not overriding the shape!
// 2) the transformation should be applied *before* the shape is added to the group!
if (!mergeIntoShape) {
// groups should also have their own coordinate system!
textChunk->applyAbsoluteTransformation(m_context.currentGC()->matrix);
const QPointF extraOffset = extraShapeOffset(textChunk, m_context.currentGC()->matrix);
// handle id
applyId(e.attribute("id"), textChunk);
applyCurrentStyle(textChunk, extraOffset); // apply style to this group after size is set
} else {
- m_context.currentGC()->matrix = mergeIntoShape->absoluteTransformation(0);
+ m_context.currentGC()->matrix = mergeIntoShape->absoluteTransformation();
applyCurrentBasicStyle(textChunk);
}
KoXmlText onlyTextChild = getTheOnlyTextChild(e);
if (!onlyTextChild.isNull()) {
textChunk->loadSvgTextNode(onlyTextChild, m_context);
} else {
QList<KoShape*> childShapes = parseContainer(e, true);
addToGroup(childShapes, textChunk);
}
m_context.popGraphicsContext();
textChunk->normalizeCharTransformations();
if (rootTextShape) {
textChunk->simplifyFillStrokeInheritance();
m_isInsideTextSubtree = false;
rootTextShape->relayout();
}
return textChunk;
}
QList<KoShape*> SvgParser::parseContainer(const KoXmlElement &e, bool parseTextNodes)
{
QList<KoShape*> shapes;
// are we parsing a switch container
bool isSwitch = e.tagName() == "switch";
DeferredUseStore deferredUseStore(this);
for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
KoXmlElement b = n.toElement();
if (b.isNull()) {
if (parseTextNodes && n.isText()) {
KoShape *shape = parseTextNode(n.toText());
if (shape) {
shapes += shape;
}
}
continue;
}
if (isSwitch) {
// if we are parsing a switch check the requiredFeatures, requiredExtensions
// and systemLanguage attributes
// TODO: evaluate feature list
if (b.hasAttribute("requiredFeatures")) {
continue;
}
if (b.hasAttribute("requiredExtensions")) {
// we do not support any extensions
continue;
}
if (b.hasAttribute("systemLanguage")) {
// not implemented yet
}
}
QList<KoShape*> currentShapes = parseSingleElement(b, &deferredUseStore);
shapes.append(currentShapes);
// if we are parsing a switch, stop after the first supported element
if (isSwitch && !currentShapes.isEmpty())
break;
}
return shapes;
}
void SvgParser::parseDefsElement(const KoXmlElement &e)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(e.tagName() == "defs");
parseSingleElement(e);
}
QList<KoShape*> SvgParser::parseSingleElement(const KoXmlElement &b, DeferredUseStore* deferredUseStore)
{
QList<KoShape*> shapes;
// save definition for later instantiation with 'use'
m_context.addDefinition(b);
if (deferredUseStore) {
deferredUseStore->checkPendingUse(b, shapes);
}
if (b.tagName() == "svg") {
shapes += parseSvg(b);
- } else if (b.tagName() == "g" || b.tagName() == "a") {
+ } else if (b.tagName() == "g" || b.tagName() == "a" || b.tagName() == "symbol") {
// treat svg link <a> as group so we don't miss its child elements
shapes += parseGroup(b);
+
+ if (b.tagName() == "symbol") {
+ parseSymbol(b);
+ }
+
} else if (b.tagName() == "switch") {
m_context.pushGraphicsContext(b);
shapes += parseContainer(b);
m_context.popGraphicsContext();
} else if (b.tagName() == "defs") {
if (KoXml::childNodesCount(b) > 0) {
/**
* WARNING: 'defs' are basically 'display:none' style, therefore they should not play
* any role in shapes outline calculation. But setVisible(false) shapes do!
* Should be fixed in the future!
*/
KoShape *defsShape = parseGroup(b);
defsShape->setVisible(false);
m_defsShapes << defsShape; // TODO: where to delete the shape!?
}
} else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") {
} else if (b.tagName() == "pattern") {
} else if (b.tagName() == "filter") {
parseFilter(b);
} else if (b.tagName() == "clipPath") {
parseClipPath(b);
} else if (b.tagName() == "mask") {
parseClipMask(b);
} else if (b.tagName() == "marker") {
parseMarker(b);
- } else if (b.tagName() == "symbol") {
- parseSymbol(b);
} else if (b.tagName() == "style") {
m_context.addStyleSheet(b);
} else if (b.tagName() == "text" ||
b.tagName() == "tspan") {
shapes += parseTextElement(b);
} else if (b.tagName() == "rect" ||
b.tagName() == "ellipse" ||
b.tagName() == "circle" ||
b.tagName() == "line" ||
b.tagName() == "polyline" ||
b.tagName() == "polygon" ||
b.tagName() == "path" ||
b.tagName() == "image") {
KoShape *shape = createObjectDirect(b);
- if (shape)
- shapes.append(shape);
+
+ if (shape) {
+ if (!shape->outlineRect().isNull() || !shape->boundingRect().isNull()) {
+ shapes.append(shape);
+ } else {
+ debugFlake << "WARNING: shape is totally empty!" << shape->shapeId() << ppVar(shape->outlineRect());
+ debugFlake << " " << shape->shapeId() << ppVar(shape->outline());
+ {
+ QString string;
+ QTextStream stream(&string);
+ stream << b;
+ debugFlake << " " << string;
+ }
+ }
+ }
} else if (b.tagName() == "use") {
KoShape* s = parseUse(b, deferredUseStore);
if (s) {
shapes += s;
}
} else if (b.tagName() == "color-profile") {
m_context.parseProfile(b);
} else {
// this is an unknown element, so try to load it anyway
// there might be a shape that handles that element
KoShape *shape = createObject(b);
if (shape) {
shapes.append(shape);
}
}
return shapes;
}
// Creating functions
// ---------------------------------------------------------------------------------------
KoShape * SvgParser::createPath(const KoXmlElement &element)
{
KoShape *obj = 0;
if (element.tagName() == "line") {
KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
if (path) {
double x1 = element.attribute("x1").isEmpty() ? 0.0 : parseUnitX(element.attribute("x1"));
double y1 = element.attribute("y1").isEmpty() ? 0.0 : parseUnitY(element.attribute("y1"));
double x2 = element.attribute("x2").isEmpty() ? 0.0 : parseUnitX(element.attribute("x2"));
double y2 = element.attribute("y2").isEmpty() ? 0.0 : parseUnitY(element.attribute("y2"));
path->clear();
path->moveTo(QPointF(x1, y1));
path->lineTo(QPointF(x2, y2));
path->normalize();
obj = path;
}
} else if (element.tagName() == "polyline" || element.tagName() == "polygon") {
KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
if (path) {
path->clear();
bool bFirst = true;
QStringList pointList = SvgUtil::simplifyList(element.attribute("points"));
for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) {
QPointF point;
point.setX(SvgUtil::fromUserSpace(KisDomUtils::toDouble(*it)));
++it;
if (it == pointList.end())
break;
point.setY(SvgUtil::fromUserSpace(KisDomUtils::toDouble(*it)));
if (bFirst) {
path->moveTo(point);
bFirst = false;
} else
path->lineTo(point);
}
if (element.tagName() == "polygon")
path->close();
path->setPosition(path->normalize());
obj = path;
}
} else if (element.tagName() == "path") {
KoPathShape *path = static_cast<KoPathShape*>(createShape(KoPathShapeId));
if (path) {
path->clear();
KoPathShapeLoader loader(path);
loader.parseSvg(element.attribute("d"), true);
path->setPosition(path->normalize());
QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()),
SvgUtil::fromUserSpace(path->position().y()));
QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()),
SvgUtil::fromUserSpace(path->size().height()));
path->setSize(newSize);
path->setPosition(newPosition);
obj = path;
}
}
return obj;
}
KoShape * SvgParser::createObjectDirect(const KoXmlElement &b)
{
m_context.pushGraphicsContext(b);
uploadStyleToContext(b);
KoShape *obj = createShapeFromElement(b, m_context);
if (obj) {
obj->applyAbsoluteTransformation(m_context.currentGC()->matrix);
const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
applyCurrentStyle(obj, extraOffset);
// handle id
applyId(b.attribute("id"), obj);
obj->setZIndex(m_context.nextZIndex());
}
m_context.popGraphicsContext();
return obj;
}
KoShape * SvgParser::createObject(const KoXmlElement &b, const SvgStyles &style)
{
m_context.pushGraphicsContext(b);
KoShape *obj = createShapeFromElement(b, m_context);
if (obj) {
obj->applyAbsoluteTransformation(m_context.currentGC()->matrix);
const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix);
SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style;
m_context.styleParser().parseFont(objStyle);
applyStyle(obj, objStyle, extraOffset);
// handle id
applyId(b.attribute("id"), obj);
obj->setZIndex(m_context.nextZIndex());
}
m_context.popGraphicsContext();
return obj;
}
KoShape * SvgParser::createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context)
{
KoShape *object = 0;
const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element);
QList<KoShapeFactoryBase*> factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, tagName);
foreach (KoShapeFactoryBase *f, factories) {
KoShape *shape = f->createDefaultShape(m_documentResourceManager);
if (!shape)
continue;
SvgShape *svgShape = dynamic_cast<SvgShape*>(shape);
if (!svgShape) {
delete shape;
continue;
}
// reset transformation that might come from the default shape
shape->setTransformation(QTransform());
// reset border
KoShapeStrokeModelSP oldStroke = shape->stroke();
shape->setStroke(KoShapeStrokeModelSP());
// reset fill
shape->setBackground(QSharedPointer<KoShapeBackground>(0));
if (!svgShape->loadSvg(element, context)) {
delete shape;
continue;
}
object = shape;
break;
}
if (!object) {
object = createPath(element);
}
return object;
}
KoShape *SvgParser::createShape(const QString &shapeID)
{
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(shapeID);
if (!factory) {
debugFlake << "Could not find factory for shape id" << shapeID;
return 0;
}
KoShape *shape = factory->createDefaultShape(m_documentResourceManager);
if (!shape) {
debugFlake << "Could not create Default shape for shape id" << shapeID;
return 0;
}
if (shape->shapeId().isEmpty()) {
shape->setShapeId(factory->id());
}
// reset transformation that might come from the default shape
shape->setTransformation(QTransform());
// reset border
// ??? KoShapeStrokeModelSP oldStroke = shape->stroke();
shape->setStroke(KoShapeStrokeModelSP());
// reset fill
shape->setBackground(QSharedPointer<KoShapeBackground>(0));
return shape;
}
void SvgParser::applyId(const QString &id, KoShape *shape)
{
if (id.isEmpty())
return;
shape->setName(id);
m_context.registerShape(id, shape);
}
diff --git a/libs/flake/svg/SvgParser.h b/libs/flake/svg/SvgParser.h
index 708a96649e..48d59315d2 100644
--- a/libs/flake/svg/SvgParser.h
+++ b/libs/flake/svg/SvgParser.h
@@ -1,227 +1,227 @@
/* This file is part of the KDE project
* Copyright (C) 2002-2003,2005 Rob Buis <buis@kde.org>
* Copyright (C) 2005-2006 Tim Beaulen <tbscope@gmail.com>
* Copyright (C) 2005,2007-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 Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef SVGPARSER_H
#define SVGPARSER_H
#include <KoXmlReader.h>
#include <QMap>
#include <QSizeF>
#include <QRectF>
#include <QSharedPointer>
#include <QExplicitlySharedDataPointer>
#include "kritaflake_export.h"
#include "SvgGradientHelper.h"
#include "SvgFilterHelper.h"
#include "SvgClipPathHelper.h"
#include "SvgLoadingContext.h"
#include "SvgStyleParser.h"
#include "KoClipMask.h"
#include <resources/KoSvgSymbolCollectionResource.h>
class KoShape;
class KoShapeGroup;
class KoShapeContainer;
class KoDocumentResourceManager;
class KoVectorPatternBackground;
class KoMarker;
class KoPathShape;
class KoSvgTextShape;
class KRITAFLAKE_EXPORT SvgParser
{
struct DeferredUseStore;
public:
explicit SvgParser(KoDocumentResourceManager *documentResourceManager);
virtual ~SvgParser();
static KoXmlDocument createDocumentFromSvg(QIODevice *device, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0);
static KoXmlDocument createDocumentFromSvg(const QByteArray &data, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0);
static KoXmlDocument createDocumentFromSvg(const QString &data, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0);
static KoXmlDocument createDocumentFromSvg(QXmlInputSource *source, QString *errorMsg = 0, int *errorLine = 0, int *errorColumn = 0);
/// Parses a svg fragment, returning the list of top level child shapes
QList<KoShape*> parseSvg(const KoXmlElement &e, QSizeF * fragmentSize = 0);
/// Sets the initial xml base directory (the directory form where the file is read)
void setXmlBaseDir(const QString &baseDir);
void setResolution(const QRectF boundsInPixels, qreal pixelsPerInch);
/// A special workaround coeff for using when loading old ODF-embedded SVG files,
/// which used hard-coded 96 ppi for font size
void setForcedFontSizeResolution(qreal value);
/// Returns the list of all shapes of the svg document
QList<KoShape*> shapes() const;
/// Takes the collection of symbols contained in the svg document. The parser will
/// no longer know about the symbols.
QVector<KoSvgSymbol*> takeSymbols();
QString documentTitle() const;
QString documentDescription() const;
typedef std::function<QByteArray(const QString&)> FileFetcherFunc;
void setFileFetcher(FileFetcherFunc func);
QList<QExplicitlySharedDataPointer<KoMarker>> knownMarkers() const;
void parseDefsElement(const KoXmlElement &e);
KoShape* parseTextElement(const KoXmlElement &e, KoSvgTextShape *mergeIntoShape = 0);
protected:
/// Parses a group-like element element, saving all its topmost properties
- KoShape* parseGroup(const KoXmlElement &e, const KoXmlElement &overrideChildrenFrom = KoXmlElement());
+ KoShape* parseGroup(const KoXmlElement &e, const KoXmlElement &overrideChildrenFrom = KoXmlElement(), bool createContext = true);
// XXX
KoShape* parseTextNode(const KoXmlText &e);
/// Parses a container element, returning a list of child shapes
QList<KoShape*> parseContainer(const KoXmlElement &, bool parseTextNodes = false);
/// XXX
QList<KoShape*> parseSingleElement(const KoXmlElement &b, DeferredUseStore* deferredUseStore = 0);
/// Parses a use element, returning a list of child shapes
KoShape* parseUse(const KoXmlElement &, DeferredUseStore* deferredUseStore);
KoShape* resolveUse(const KoXmlElement &e, const QString& key);
/// Parses a gradient element
SvgGradientHelper *parseGradient(const KoXmlElement &);
/// Parses a pattern element
QSharedPointer<KoVectorPatternBackground> parsePattern(const KoXmlElement &e, const KoShape *__shape);
/// Parses a filter element
bool parseFilter(const KoXmlElement &, const KoXmlElement &referencedBy = KoXmlElement());
/// Parses a clip path element
bool parseClipPath(const KoXmlElement &);
bool parseClipMask(const KoXmlElement &e);
bool parseMarker(const KoXmlElement &e);
bool parseSymbol(const KoXmlElement &e);
/// parses a length attribute
qreal parseUnit(const QString &, bool horiz = false, bool vert = false, const QRectF &bbox = QRectF());
/// parses a length attribute in x-direction
qreal parseUnitX(const QString &unit);
/// parses a length attribute in y-direction
qreal parseUnitY(const QString &unit);
/// parses a length attribute in xy-direction
qreal parseUnitXY(const QString &unit);
/// parses a angular attribute values, result in radians
qreal parseAngular(const QString &unit);
KoShape *createObjectDirect(const KoXmlElement &b);
/// Creates an object from the given xml element
KoShape * createObject(const KoXmlElement &, const SvgStyles &style = SvgStyles());
/// Create path object from the given xml element
KoShape * createPath(const KoXmlElement &);
/// find gradient with given id in gradient map
SvgGradientHelper* findGradient(const QString &id);
/// find pattern with given id in pattern map
QSharedPointer<KoVectorPatternBackground> findPattern(const QString &id, const KoShape *shape);
/// find filter with given id in filter map
SvgFilterHelper* findFilter(const QString &id, const QString &href = QString());
/// find clip path with given id in clip path map
SvgClipPathHelper* findClipPath(const QString &id);
/// Adds list of shapes to the given group shape
void addToGroup(QList<KoShape*> shapes, KoShapeContainer *group);
/// creates a shape from the given shape id
KoShape * createShape(const QString &shapeID);
/// Creates shape from specified svg element
KoShape * createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context);
/// Builds the document from the given shapes list
void buildDocument(QList<KoShape*> shapes);
void uploadStyleToContext(const KoXmlElement &e);
void applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates);
void applyCurrentBasicStyle(KoShape *shape);
/// Applies styles to the given shape
void applyStyle(KoShape *, const KoXmlElement &, const QPointF &shapeToOriginalUserCoordinates);
/// Applies styles to the given shape
void applyStyle(KoShape *, const SvgStyles &, const QPointF &shapeToOriginalUserCoordinates);
/// Applies the current fill style to the object
void applyFillStyle(KoShape * shape);
/// Applies the current stroke style to the object
void applyStrokeStyle(KoShape * shape);
/// Applies the current filter to the object
void applyFilter(KoShape * shape);
/// Applies the current clip path to the object
void applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates);
void applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates);
void applyMarkers(KoPathShape *shape);
/// Applies id to specified shape
void applyId(const QString &id, KoShape *shape);
/// Applies viewBox transformation to the current graphical context
/// NOTE: after applying the function currentBoundingBox can become null!
void applyViewBoxTransform(const KoXmlElement &element);
private:
QSizeF m_documentSize;
SvgLoadingContext m_context;
QMap<QString, SvgGradientHelper> m_gradients;
QMap<QString, SvgFilterHelper> m_filters;
QMap<QString, SvgClipPathHelper> m_clipPaths;
QMap<QString, QSharedPointer<KoClipMask>> m_clipMasks;
QMap<QString, QExplicitlySharedDataPointer<KoMarker>> m_markers;
KoDocumentResourceManager *m_documentResourceManager;
QList<KoShape*> m_shapes;
QMap<QString, KoSvgSymbol*> m_symbols;
QList<KoShape*> m_toplevelShapes;
QList<KoShape*> m_defsShapes;
bool m_isInsideTextSubtree = false;
QString m_documentTitle;
QString m_documentDescription;
};
#endif
diff --git a/libs/flake/svg/SvgStyleWriter.cpp b/libs/flake/svg/SvgStyleWriter.cpp
index 24bb92d43f..32838487e7 100644
--- a/libs/flake/svg/SvgStyleWriter.cpp
+++ b/libs/flake/svg/SvgStyleWriter.cpp
@@ -1,537 +1,537 @@
/* This file is part of the KDE project
Copyright (C) 2002 Lars Siebold <khandha5@gmx.net>
Copyright (C) 2002-2003,2005 Rob Buis <buis@kde.org>
Copyright (C) 2002,2005-2006 David Faure <faure@kde.org>
Copyright (C) 2002 Werner Trobin <trobin@kde.org>
Copyright (C) 2002 Lennart Kudling <kudling@kde.org>
Copyright (C) 2004 Nicolas Goutte <nicolasg@snafu.de>
Copyright (C) 2005 Boudewijn Rempt <boud@valdyas.org>
Copyright (C) 2005 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
Copyright (C) 2005 Thomas Zander <zander@kde.org>
Copyright (C) 2005,2007-2008 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2006 Inge Wallin <inge@lysator.liu.se>
Copyright (C) 2006 Martin Pfeiffer <hubipete@gmx.net>
Copyright (C) 2006 Gábor Lehel <illissius@gmail.com>
Copyright (C) 2006 Laurent Montel <montel@kde.org>
Copyright (C) 2006 Christian Mueller <cmueller@gmx.de>
Copyright (C) 2006 Ariya Hidayat <ariya@kde.org>
Copyright (C) 2010 Thorsten Zachmann <zachmann@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 "SvgStyleWriter.h"
#include "SvgSavingContext.h"
#include "SvgUtil.h"
#include <KoShape.h>
#include <KoPathShape.h>
#include <KoFilterEffect.h>
#include <KoFilterEffectStack.h>
#include <KoColorBackground.h>
#include <KoGradientBackground.h>
#include <KoPatternBackground.h>
#include <KoVectorPatternBackground.h>
#include <KoShapeStroke.h>
#include <KoClipPath.h>
#include <KoClipMask.h>
#include <KoMarker.h>
#include <KoXmlWriter.h>
#include <QBuffer>
#include <QGradient>
#include <QLinearGradient>
#include <QRadialGradient>
#include <KisMimeDatabase.h>
#include "kis_dom_utils.h"
#include "kis_algebra_2d.h"
#include <SvgWriter.h>
#include <KoFlakeCoordinateSystem.h>
void SvgStyleWriter::saveSvgStyle(KoShape *shape, SvgSavingContext &context)
{
saveSvgBasicStyle(shape, context);
saveSvgFill(shape, context);
saveSvgStroke(shape, context);
saveSvgEffects(shape, context);
saveSvgClipping(shape, context);
saveSvgMasking(shape, context);
saveSvgMarkers(shape, context);
}
void SvgStyleWriter::saveSvgBasicStyle(KoShape *shape, SvgSavingContext &context)
{
if (!shape->isVisible(false)) {
context.shapeWriter().addAttribute("display", "none");
} else if (shape->transparency() > 0.0) {
context.shapeWriter().addAttribute("opacity", 1.0 - shape->transparency());
}
}
void SvgStyleWriter::saveSvgFill(KoShape *shape, SvgSavingContext &context)
{
if (! shape->background()) {
context.shapeWriter().addAttribute("fill", "none");
}
QBrush fill(Qt::NoBrush);
QSharedPointer<KoColorBackground> cbg = qSharedPointerDynamicCast<KoColorBackground>(shape->background());
if (cbg) {
context.shapeWriter().addAttribute("fill", cbg->color().name());
if (cbg->color().alphaF() < 1.0)
context.shapeWriter().addAttribute("fill-opacity", cbg->color().alphaF());
}
QSharedPointer<KoGradientBackground> gbg = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
if (gbg) {
QString gradientId = saveSvgGradient(gbg->gradient(), gbg->transform(), context);
context.shapeWriter().addAttribute("fill", "url(#" + gradientId + ")");
}
QSharedPointer<KoPatternBackground> pbg = qSharedPointerDynamicCast<KoPatternBackground>(shape->background());
if (pbg) {
const QString patternId = saveSvgPattern(pbg, shape, context);
context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")");
}
QSharedPointer<KoVectorPatternBackground> vpbg = qSharedPointerDynamicCast<KoVectorPatternBackground>(shape->background());
if (vpbg) {
const QString patternId = saveSvgVectorPattern(vpbg, shape, context);
context.shapeWriter().addAttribute("fill", "url(#" + patternId + ")");
}
KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
if (path && shape->background()) {
// non-zero is default, so only write fillrule if evenodd is set
if (path->fillRule() == Qt::OddEvenFill)
context.shapeWriter().addAttribute("fill-rule", "evenodd");
}
}
void SvgStyleWriter::saveSvgStroke(KoShape *shape, SvgSavingContext &context)
{
const QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
if (! lineBorder)
return;
QString strokeStr("none");
if (lineBorder->lineBrush().gradient()) {
QString gradientId = saveSvgGradient(lineBorder->lineBrush().gradient(), lineBorder->lineBrush().transform(), context);
strokeStr = "url(#" + gradientId + ")";
} else {
strokeStr = lineBorder->color().name();
}
if (!strokeStr.isEmpty())
context.shapeWriter().addAttribute("stroke", strokeStr);
if (lineBorder->color().alphaF() < 1.0)
context.shapeWriter().addAttribute("stroke-opacity", lineBorder->color().alphaF());
context.shapeWriter().addAttribute("stroke-width", SvgUtil::toUserSpace(lineBorder->lineWidth()));
if (lineBorder->capStyle() == Qt::FlatCap)
context.shapeWriter().addAttribute("stroke-linecap", "butt");
else if (lineBorder->capStyle() == Qt::RoundCap)
context.shapeWriter().addAttribute("stroke-linecap", "round");
else if (lineBorder->capStyle() == Qt::SquareCap)
context.shapeWriter().addAttribute("stroke-linecap", "square");
if (lineBorder->joinStyle() == Qt::MiterJoin) {
context.shapeWriter().addAttribute("stroke-linejoin", "miter");
context.shapeWriter().addAttribute("stroke-miterlimit", lineBorder->miterLimit());
} else if (lineBorder->joinStyle() == Qt::RoundJoin)
context.shapeWriter().addAttribute("stroke-linejoin", "round");
else if (lineBorder->joinStyle() == Qt::BevelJoin)
context.shapeWriter().addAttribute("stroke-linejoin", "bevel");
// dash
if (lineBorder->lineStyle() > Qt::SolidLine) {
qreal dashFactor = lineBorder->lineWidth();
if (lineBorder->dashOffset() != 0)
context.shapeWriter().addAttribute("stroke-dashoffset", dashFactor * lineBorder->dashOffset());
QString dashStr;
const QVector<qreal> dashes = lineBorder->lineDashes();
int dashCount = dashes.size();
for (int i = 0; i < dashCount; ++i) {
if (i > 0)
dashStr += ",";
dashStr += QString("%1").arg(KisDomUtils::toString(dashes[i] * dashFactor));
}
context.shapeWriter().addAttribute("stroke-dasharray", dashStr);
}
}
void SvgStyleWriter::saveSvgEffects(KoShape *shape, SvgSavingContext &context)
{
KoFilterEffectStack * filterStack = shape->filterEffectStack();
if (!filterStack)
return;
QList<KoFilterEffect*> filterEffects = filterStack->filterEffects();
if (!filterEffects.count())
return;
const QString uid = context.createUID("filter");
filterStack->save(context.styleWriter(), uid);
context.shapeWriter().addAttribute("filter", "url(#" + uid + ")");
}
void embedShapes(const QList<KoShape*> &shapes, KoXmlWriter &outWriter)
{
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
{
SvgWriter shapesWriter(shapes);
shapesWriter.saveDetached(buffer);
}
buffer.close();
outWriter.addCompleteElement(&buffer);
}
void SvgStyleWriter::saveSvgClipping(KoShape *shape, SvgSavingContext &context)
{
KoClipPath *clipPath = shape->clipPath();
if (!clipPath)
return;
const QString uid = context.createUID("clippath");
context.styleWriter().startElement("clipPath");
context.styleWriter().addAttribute("id", uid);
context.styleWriter().addAttribute("clipPathUnits", KoFlake::coordinateToString(clipPath->coordinates()));
embedShapes(clipPath->clipShapes(), context.styleWriter());
context.styleWriter().endElement(); // clipPath
context.shapeWriter().addAttribute("clip-path", "url(#" + uid + ")");
if (clipPath->clipRule() != Qt::WindingFill)
context.shapeWriter().addAttribute("clip-rule", "evenodd");
}
void SvgStyleWriter::saveSvgMasking(KoShape *shape, SvgSavingContext &context)
{
KoClipMask*clipMask = shape->clipMask();
if (!clipMask)
return;
const QString uid = context.createUID("clipmask");
context.styleWriter().startElement("mask");
context.styleWriter().addAttribute("id", uid);
context.styleWriter().addAttribute("maskUnits", KoFlake::coordinateToString(clipMask->coordinates()));
context.styleWriter().addAttribute("maskContentUnits", KoFlake::coordinateToString(clipMask->contentCoordinates()));
const QRectF rect = clipMask->maskRect();
context.styleWriter().addAttribute("x", rect.x());
context.styleWriter().addAttribute("y", rect.y());
context.styleWriter().addAttribute("width", rect.width());
context.styleWriter().addAttribute("height", rect.height());
embedShapes(clipMask->shapes(), context.styleWriter());
context.styleWriter().endElement(); // clipMask
context.shapeWriter().addAttribute("mask", "url(#" + uid + ")");
}
namespace {
void writeMarkerStyle(KoXmlWriter &styleWriter, const KoMarker *marker, const QString &assignedId) {
styleWriter.startElement("marker");
styleWriter.addAttribute("id", assignedId);
styleWriter.addAttribute("markerUnits", KoMarker::coordinateSystemToString(marker->coordinateSystem()));
const QPointF refPoint = marker->referencePoint();
styleWriter.addAttribute("refX", refPoint.x());
styleWriter.addAttribute("refY", refPoint.y());
const QSizeF refSize = marker->referenceSize();
styleWriter.addAttribute("markerWidth", refSize.width());
styleWriter.addAttribute("markerHeight", refSize.height());
if (marker->hasAutoOtientation()) {
styleWriter.addAttribute("orient", "auto");
} else {
// no suffix means 'degrees'
styleWriter.addAttribute("orient", kisRadiansToDegrees(marker->explicitOrientation()));
}
embedShapes(marker->shapes(), styleWriter);
styleWriter.endElement(); // marker
}
void tryEmbedMarker(const KoPathShape *pathShape,
const QString &markerTag,
KoFlake::MarkerPosition markerPosition,
SvgSavingContext &context)
{
KoMarker *marker = pathShape->marker(markerPosition);
if (marker) {
const QString uid = context.createUID("lineMarker");
writeMarkerStyle(context.styleWriter(), marker, uid);
context.shapeWriter().addAttribute(markerTag.toLatin1().data(), "url(#" + uid + ")");
}
}
}
void SvgStyleWriter::saveSvgMarkers(KoShape *shape, SvgSavingContext &context)
{
KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
if (!pathShape || !pathShape->hasMarkers()) return;
tryEmbedMarker(pathShape, "marker-start", KoFlake::StartMarker, context);
tryEmbedMarker(pathShape, "marker-mid", KoFlake::MidMarker, context);
tryEmbedMarker(pathShape, "marker-end", KoFlake::EndMarker, context);
if (pathShape->autoFillMarkers()) {
context.shapeWriter().addAttribute("krita:marker-fill-method", "auto");
}
}
void SvgStyleWriter::saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context)
{
Q_FOREACH (const QGradientStop &stop, colorStops) {
context.styleWriter().startElement("stop");
context.styleWriter().addAttribute("stop-color", stop.second.name());
context.styleWriter().addAttribute("offset", stop.first);
context.styleWriter().addAttribute("stop-opacity", stop.second.alphaF());
context.styleWriter().endElement();
}
}
inline QString convertGradientMode(QGradient::CoordinateMode mode) {
KIS_ASSERT_RECOVER_NOOP(mode != QGradient::StretchToDeviceMode);
return
mode == QGradient::ObjectBoundingMode ?
"objectBoundingBox" :
"userSpaceOnUse";
}
QString SvgStyleWriter::saveSvgGradient(const QGradient *gradient, const QTransform &gradientTransform, SvgSavingContext &context)
{
if (! gradient)
return QString();
const QString spreadMethod[3] = {
QString("pad"),
QString("reflect"),
QString("repeat")
};
const QString uid = context.createUID("gradient");
if (gradient->type() == QGradient::LinearGradient) {
const QLinearGradient * g = static_cast<const QLinearGradient*>(gradient);
context.styleWriter().startElement("linearGradient");
context.styleWriter().addAttribute("id", uid);
SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter());
context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode()));
context.styleWriter().addAttribute("x1", g->start().x());
context.styleWriter().addAttribute("y1", g->start().y());
context.styleWriter().addAttribute("x2", g->finalStop().x());
context.styleWriter().addAttribute("y2", g->finalStop().y());
context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]);
// color stops
saveSvgColorStops(gradient->stops(), context);
context.styleWriter().endElement();
} else if (gradient->type() == QGradient::RadialGradient) {
const QRadialGradient * g = static_cast<const QRadialGradient*>(gradient);
context.styleWriter().startElement("radialGradient");
context.styleWriter().addAttribute("id", uid);
SvgUtil::writeTransformAttributeLazy("gradientTransform", gradientTransform, context.styleWriter());
context.styleWriter().addAttribute("gradientUnits", convertGradientMode(g->coordinateMode()));
context.styleWriter().addAttribute("cx", g->center().x());
context.styleWriter().addAttribute("cy", g->center().y());
context.styleWriter().addAttribute("fx", g->focalPoint().x());
context.styleWriter().addAttribute("fy", g->focalPoint().y());
context.styleWriter().addAttribute("r", g->radius());
context.styleWriter().addAttribute("spreadMethod", spreadMethod[g->spread()]);
// color stops
saveSvgColorStops(gradient->stops(), context);
context.styleWriter().endElement();
} else if (gradient->type() == QGradient::ConicalGradient) {
//const QConicalGradient * g = static_cast<const QConicalGradient*>( gradient );
// fake conical grad as radial.
// fugly but better than data loss.
/*
printIndentation( m_defs, m_indent2 );
*m_defs << "<radialGradient id=\"" << uid << "\" ";
*m_defs << "gradientUnits=\"userSpaceOnUse\" ";
*m_defs << "cx=\"" << g->center().x() << "\" ";
*m_defs << "cy=\"" << g->center().y() << "\" ";
*m_defs << "fx=\"" << grad.focalPoint().x() << "\" ";
*m_defs << "fy=\"" << grad.focalPoint().y() << "\" ";
double r = sqrt( pow( grad.vector().x() - grad.origin().x(), 2 ) + pow( grad.vector().y() - grad.origin().y(), 2 ) );
*m_defs << "r=\"" << QString().setNum( r ) << "\" ";
*m_defs << spreadMethod[g->spread()];
*m_defs << ">" << endl;
// color stops
getColorStops( gradient->stops() );
printIndentation( m_defs, m_indent2 );
*m_defs << "</radialGradient>" << endl;
*m_body << "url(#" << uid << ")";
*/
}
return uid;
}
QString SvgStyleWriter::saveSvgPattern(QSharedPointer<KoPatternBackground> pattern, KoShape *shape, SvgSavingContext &context)
{
const QString uid = context.createUID("pattern");
const QSizeF shapeSize = shape->size();
const QSizeF patternSize = pattern->patternDisplaySize();
const QSize imageSize = pattern->pattern().size();
// calculate offset in point
QPointF offset = pattern->referencePointOffset();
offset.rx() = 0.01 * offset.x() * patternSize.width();
offset.ry() = 0.01 * offset.y() * patternSize.height();
// now take the reference point into account
switch (pattern->referencePoint()) {
case KoPatternBackground::TopLeft:
break;
case KoPatternBackground::Top:
offset += QPointF(0.5 * shapeSize.width(), 0.0);
break;
case KoPatternBackground::TopRight:
offset += QPointF(shapeSize.width(), 0.0);
break;
case KoPatternBackground::Left:
offset += QPointF(0.0, 0.5 * shapeSize.height());
break;
case KoPatternBackground::Center:
offset += QPointF(0.5 * shapeSize.width(), 0.5 * shapeSize.height());
break;
case KoPatternBackground::Right:
offset += QPointF(shapeSize.width(), 0.5 * shapeSize.height());
break;
case KoPatternBackground::BottomLeft:
offset += QPointF(0.0, shapeSize.height());
break;
case KoPatternBackground::Bottom:
offset += QPointF(0.5 * shapeSize.width(), shapeSize.height());
break;
case KoPatternBackground::BottomRight:
offset += QPointF(shapeSize.width(), shapeSize.height());
break;
}
- offset = shape->absoluteTransformation(0).map(offset);
+ offset = shape->absoluteTransformation().map(offset);
context.styleWriter().startElement("pattern");
context.styleWriter().addAttribute("id", uid);
context.styleWriter().addAttribute("x", SvgUtil::toUserSpace(offset.x()));
context.styleWriter().addAttribute("y", SvgUtil::toUserSpace(offset.y()));
if (pattern->repeat() == KoPatternBackground::Stretched) {
context.styleWriter().addAttribute("width", "100%");
context.styleWriter().addAttribute("height", "100%");
context.styleWriter().addAttribute("patternUnits", "objectBoundingBox");
} else {
context.styleWriter().addAttribute("width", SvgUtil::toUserSpace(patternSize.width()));
context.styleWriter().addAttribute("height", SvgUtil::toUserSpace(patternSize.height()));
context.styleWriter().addAttribute("patternUnits", "userSpaceOnUse");
}
context.styleWriter().addAttribute("viewBox", QString("0 0 %1 %2").arg(KisDomUtils::toString(imageSize.width())).arg(KisDomUtils::toString(imageSize.height())));
//*m_defs << " patternContentUnits=\"userSpaceOnUse\"";
context.styleWriter().startElement("image");
context.styleWriter().addAttribute("x", "0");
context.styleWriter().addAttribute("y", "0");
context.styleWriter().addAttribute("width", QString("%1px").arg(KisDomUtils::toString(imageSize.width())));
context.styleWriter().addAttribute("height", QString("%1px").arg(KisDomUtils::toString(imageSize.height())));
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
if (pattern->pattern().save(&buffer, "PNG")) {
const QString mimeType = KisMimeDatabase::mimeTypeForSuffix("*.png");
context.styleWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + ba.toBase64());
}
context.styleWriter().endElement(); // image
context.styleWriter().endElement(); // pattern
return uid;
}
QString SvgStyleWriter::saveSvgVectorPattern(QSharedPointer<KoVectorPatternBackground> pattern, KoShape *parentShape, SvgSavingContext &context)
{
const QString uid = context.createUID("pattern");
context.styleWriter().startElement("pattern");
context.styleWriter().addAttribute("id", uid);
context.styleWriter().addAttribute("patternUnits", KoFlake::coordinateToString(pattern->referenceCoordinates()));
context.styleWriter().addAttribute("patternContentUnits", KoFlake::coordinateToString(pattern->contentCoordinates()));
const QRectF rect = pattern->referenceRect();
context.styleWriter().addAttribute("x", rect.x());
context.styleWriter().addAttribute("y", rect.y());
context.styleWriter().addAttribute("width", rect.width());
context.styleWriter().addAttribute("height", rect.height());
SvgUtil::writeTransformAttributeLazy("patternTransform", pattern->patternTransform(), context.styleWriter());
if (pattern->contentCoordinates() == KoFlake::ObjectBoundingBox) {
// TODO: move this normalization into the KoVectorPatternBackground itself
QList<KoShape*> shapes = pattern->shapes();
QList<KoShape*> clonedShapes;
const QRectF dstShapeBoundingRect = parentShape->outlineRect();
const QTransform relativeToShape = KisAlgebra2D::mapToRect(dstShapeBoundingRect);
const QTransform shapeToRelative = relativeToShape.inverted();
Q_FOREACH (KoShape *shape, shapes) {
KoShape *clone = shape->cloneShape();
clone->applyAbsoluteTransformation(shapeToRelative);
clonedShapes.append(clone);
}
embedShapes(clonedShapes, context.styleWriter());
qDeleteAll(clonedShapes);
} else {
QList<KoShape*> shapes = pattern->shapes();
embedShapes(shapes, context.styleWriter());
}
context.styleWriter().endElement(); // pattern
return uid;
}
diff --git a/libs/flake/svg/SvgUtil.cpp b/libs/flake/svg/SvgUtil.cpp
index 892b39db88..c59700f8f9 100644
--- a/libs/flake/svg/SvgUtil.cpp
+++ b/libs/flake/svg/SvgUtil.cpp
@@ -1,527 +1,527 @@
/* 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 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 "SvgUtil.h"
#include "SvgGraphicContext.h"
#include <KoUnit.h>
#include <KoXmlReader.h>
#include <QString>
#include <QRectF>
#include <QStringList>
#include <QFontMetrics>
#include <QRegExp>
#include <math.h>
#include "kis_debug.h"
#include "kis_global.h"
#include <KoXmlWriter.h>
#include "kis_dom_utils.h"
#define DPI 72.0
#define DEG2RAD(degree) degree/180.0*M_PI
double SvgUtil::fromUserSpace(double value)
{
return value;
}
double SvgUtil::toUserSpace(double value)
{
return value;
}
double SvgUtil::ptToPx(SvgGraphicsContext *gc, double value)
{
return value * gc->pixelsPerInch / DPI;
}
QPointF SvgUtil::toUserSpace(const QPointF &point)
{
return QPointF(toUserSpace(point.x()), toUserSpace(point.y()));
}
QRectF SvgUtil::toUserSpace(const QRectF &rect)
{
return QRectF(toUserSpace(rect.topLeft()), toUserSpace(rect.size()));
}
QSizeF SvgUtil::toUserSpace(const QSizeF &size)
{
return QSizeF(toUserSpace(size.width()), toUserSpace(size.height()));
}
QString SvgUtil::toPercentage(qreal value)
{
return KisDomUtils::toString(value * 100.0) + "%";
}
double SvgUtil::fromPercentage(QString s)
{
if (s.endsWith('%'))
return KisDomUtils::toDouble(s.remove('%')) / 100.0;
else
return KisDomUtils::toDouble(s);
}
QPointF SvgUtil::objectToUserSpace(const QPointF &position, const QRectF &objectBound)
{
qreal x = objectBound.left() + position.x() * objectBound.width();
qreal y = objectBound.top() + position.y() * objectBound.height();
return QPointF(x, y);
}
QSizeF SvgUtil::objectToUserSpace(const QSizeF &size, const QRectF &objectBound)
{
qreal w = size.width() * objectBound.width();
qreal h = size.height() * objectBound.height();
return QSizeF(w, h);
}
QPointF SvgUtil::userSpaceToObject(const QPointF &position, const QRectF &objectBound)
{
qreal x = 0.0;
if (objectBound.width() != 0)
x = (position.x() - objectBound.x()) / objectBound.width();
qreal y = 0.0;
if (objectBound.height() != 0)
y = (position.y() - objectBound.y()) / objectBound.height();
return QPointF(x, y);
}
QSizeF SvgUtil::userSpaceToObject(const QSizeF &size, const QRectF &objectBound)
{
qreal w = objectBound.width() != 0 ? size.width() / objectBound.width() : 0.0;
qreal h = objectBound.height() != 0 ? size.height() / objectBound.height() : 0.0;
return QSizeF(w, h);
}
QString SvgUtil::transformToString(const QTransform &transform)
{
if (transform.isIdentity())
return QString();
if (transform.type() == QTransform::TxTranslate) {
return QString("translate(%1, %2)")
.arg(KisDomUtils::toString(toUserSpace(transform.dx())))
.arg(KisDomUtils::toString(toUserSpace(transform.dy())));
} else {
return QString("matrix(%1 %2 %3 %4 %5 %6)")
.arg(KisDomUtils::toString(transform.m11()))
.arg(KisDomUtils::toString(transform.m12()))
.arg(KisDomUtils::toString(transform.m21()))
.arg(KisDomUtils::toString(transform.m22()))
.arg(KisDomUtils::toString(toUserSpace(transform.dx())))
.arg(KisDomUtils::toString(toUserSpace(transform.dy())));
}
}
void SvgUtil::writeTransformAttributeLazy(const QString &name, const QTransform &transform, KoXmlWriter &shapeWriter)
{
const QString value = transformToString(transform);
if (!value.isEmpty()) {
shapeWriter.addAttribute(name.toLatin1().data(), value);
}
}
bool SvgUtil::parseViewBox(const KoXmlElement &e,
const QRectF &elementBounds,
QRectF *_viewRect, QTransform *_viewTransform)
{
KIS_ASSERT(_viewRect);
KIS_ASSERT(_viewTransform);
QString viewBoxStr = e.attribute("viewBox");
if (viewBoxStr.isEmpty()) return false;
bool result = false;
QRectF viewBoxRect;
// this is a workaround for bug 260429 for a file generated by blender
// who has px in the viewbox which is wrong.
- // reported as bug http://projects.blender.org/tracker/?group_id=9&atid=498&func=detail&aid=30971
+ // reported as bug https://developer.blender.org/T30971
viewBoxStr.remove("px");
QStringList points = viewBoxStr.replace(',', ' ').simplified().split(' ');
if (points.count() == 4) {
viewBoxRect.setX(SvgUtil::fromUserSpace(points[0].toFloat()));
viewBoxRect.setY(SvgUtil::fromUserSpace(points[1].toFloat()));
viewBoxRect.setWidth(SvgUtil::fromUserSpace(points[2].toFloat()));
viewBoxRect.setHeight(SvgUtil::fromUserSpace(points[3].toFloat()));
result = true;
} else {
// TODO: WARNING!
}
if (!result) return false;
QTransform viewBoxTransform =
QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) *
QTransform::fromScale(elementBounds.width() / viewBoxRect.width(),
elementBounds.height() / viewBoxRect.height()) *
QTransform::fromTranslate(elementBounds.x(), elementBounds.y());
const QString aspectString = e.attribute("preserveAspectRatio");
// give initial value if value not defined
PreserveAspectRatioParser p( (!aspectString.isEmpty())? aspectString : QString("xMidYMid meet"));
parseAspectRatio(p, elementBounds, viewBoxRect, &viewBoxTransform);
*_viewRect = viewBoxRect;
*_viewTransform = viewBoxTransform;
return result;
}
void SvgUtil::parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewBoxRect, QTransform *_viewTransform)
{
if (p.mode != Qt::IgnoreAspectRatio) {
QTransform viewBoxTransform = *_viewTransform;
const qreal tan1 = viewBoxRect.height() / viewBoxRect.width();
const qreal tan2 = elementBounds.height() / elementBounds.width();
const qreal uniformScale =
(p.mode == Qt::KeepAspectRatioByExpanding) ^ (tan1 > tan2) ?
elementBounds.height() / viewBoxRect.height() :
elementBounds.width() / viewBoxRect.width();
viewBoxTransform =
QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) *
QTransform::fromScale(uniformScale, uniformScale) *
QTransform::fromTranslate(elementBounds.x(), elementBounds.y());
const QPointF viewBoxAnchor = viewBoxTransform.map(p.rectAnchorPoint(viewBoxRect));
const QPointF elementAnchor = p.rectAnchorPoint(elementBounds);
const QPointF offset = elementAnchor - viewBoxAnchor;
viewBoxTransform = viewBoxTransform * QTransform::fromTranslate(offset.x(), offset.y());
*_viewTransform = viewBoxTransform;
}
}
qreal SvgUtil::parseUnit(SvgGraphicsContext *gc, const QString &unit, bool horiz, bool vert, const QRectF &bbox)
{
if (unit.isEmpty())
return 0.0;
QByteArray unitLatin1 = unit.toLatin1();
// TODO : percentage?
const char *start = unitLatin1.data();
if (!start) {
return 0.0;
}
qreal value = 0.0;
const char *end = parseNumber(start, value);
if (int(end - start) < unit.length()) {
if (unit.right(2) == "px")
value = SvgUtil::fromUserSpace(value);
else if (unit.right(2) == "pt")
value = ptToPx(gc, value);
else if (unit.right(2) == "cm")
value = ptToPx(gc, CM_TO_POINT(value));
else if (unit.right(2) == "pc")
value = ptToPx(gc, PI_TO_POINT(value));
else if (unit.right(2) == "mm")
value = ptToPx(gc, MM_TO_POINT(value));
else if (unit.right(2) == "in")
value = ptToPx(gc, INCH_TO_POINT(value));
else if (unit.right(2) == "em")
// NOTE: all the fonts should be created with 'pt' size, not px!
value = ptToPx(gc, value * gc->font.pointSize());
else if (unit.right(2) == "ex") {
QFontMetrics metrics(gc->font);
value = ptToPx(gc, value * metrics.xHeight());
} else if (unit.right(1) == "%") {
if (horiz && vert)
value = (value / 100.0) * (sqrt(pow(bbox.width(), 2) + pow(bbox.height(), 2)) / sqrt(2.0));
else if (horiz)
value = (value / 100.0) * bbox.width();
else if (vert)
value = (value / 100.0) * bbox.height();
}
} else {
value = SvgUtil::fromUserSpace(value);
}
/*else
{
if( m_gc.top() )
{
if( horiz && vert )
value *= sqrt( pow( m_gc.top()->matrix.m11(), 2 ) + pow( m_gc.top()->matrix.m22(), 2 ) ) / sqrt( 2.0 );
else if( horiz )
value /= m_gc.top()->matrix.m11();
else if( vert )
value /= m_gc.top()->matrix.m22();
}
}*/
//value *= 90.0 / DPI;
return value;
}
qreal SvgUtil::parseUnitX(SvgGraphicsContext *gc, const QString &unit)
{
if (gc->forcePercentage) {
return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.width();
} else {
return SvgUtil::parseUnit(gc, unit, true, false, gc->currentBoundingBox);
}
}
qreal SvgUtil::parseUnitY(SvgGraphicsContext *gc, const QString &unit)
{
if (gc->forcePercentage) {
return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.height();
} else {
return SvgUtil::parseUnit(gc, unit, false, true, gc->currentBoundingBox);
}
}
qreal SvgUtil::parseUnitXY(SvgGraphicsContext *gc, const QString &unit)
{
if (gc->forcePercentage) {
const qreal value = SvgUtil::fromPercentage(unit);
return value * sqrt(pow(gc->currentBoundingBox.width(), 2) + pow(gc->currentBoundingBox.height(), 2)) / sqrt(2.0);
} else {
return SvgUtil::parseUnit(gc, unit, true, true, gc->currentBoundingBox);
}
}
qreal SvgUtil::parseUnitAngular(SvgGraphicsContext *gc, const QString &unit)
{
Q_UNUSED(gc);
qreal value = 0.0;
if (unit.isEmpty()) return value;
QByteArray unitLatin1 = unit.toLower().toLatin1();
const char *start = unitLatin1.data();
if (!start) return value;
const char *end = parseNumber(start, value);
if (int(end - start) < unit.length()) {
if (unit.right(3) == "deg") {
value = kisDegreesToRadians(value);
} else if (unit.right(4) == "grad") {
value *= M_PI / 200;
} else if (unit.right(3) == "rad") {
// noop!
} else {
value = kisDegreesToRadians(value);
}
} else {
value = kisDegreesToRadians(value);
}
return value;
}
qreal SvgUtil::parseNumber(const QString &string)
{
qreal value = 0.0;
if (string.isEmpty()) return value;
QByteArray unitLatin1 = string.toLatin1();
const char *start = unitLatin1.data();
if (!start) return value;
const char *end = parseNumber(start, value);
KIS_SAFE_ASSERT_RECOVER_NOOP(int(end - start) == string.length());
return value;
}
const char * SvgUtil::parseNumber(const char *ptr, qreal &number)
{
int integer, exponent;
qreal decimal, frac;
int sign, expsign;
exponent = 0;
integer = 0;
frac = 1.0;
decimal = 0;
sign = 1;
expsign = 1;
// read the sign
if (*ptr == '+') {
ptr++;
} else if (*ptr == '-') {
ptr++;
sign = -1;
}
// read the integer part
while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
integer = (integer * 10) + *(ptr++) - '0';
if (*ptr == '.') { // read the decimals
ptr++;
while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
decimal += (*(ptr++) - '0') * (frac *= 0.1);
}
if (*ptr == 'e' || *ptr == 'E') { // read the exponent part
ptr++;
// read the sign of the exponent
if (*ptr == '+') {
ptr++;
} else if (*ptr == '-') {
ptr++;
expsign = -1;
}
exponent = 0;
while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') {
exponent *= 10;
exponent += *ptr - '0';
ptr++;
}
}
number = integer + decimal;
number *= sign * pow((double)10, double(expsign * exponent));
return ptr;
}
QString SvgUtil::mapExtendedShapeTag(const QString &tagName, const KoXmlElement &element)
{
QString result = tagName;
if (tagName == "path") {
QString kritaType = element.attribute("krita:type", "");
QString sodipodiType = element.attribute("sodipodi:type", "");
if (kritaType == "arc") {
result = "krita:arc";
} else if (sodipodiType == "arc") {
result = "sodipodi:arc";
}
}
return result;
}
QStringList SvgUtil::simplifyList(const QString &str)
{
QString attribute = str;
attribute.replace(',', ' ');
attribute.remove('\r');
attribute.remove('\n');
return attribute.simplified().split(' ', QString::SkipEmptyParts);
}
SvgUtil::PreserveAspectRatioParser::PreserveAspectRatioParser(const QString &str)
{
QRegExp rexp("(defer)?\\s*(none|(x(Min|Max|Mid)Y(Min|Max|Mid)))\\s*(meet|slice)?", Qt::CaseInsensitive);
int index = rexp.indexIn(str.toLower());
if (index >= 0) {
if (rexp.cap(1) == "defer") {
defer = true;
}
if (rexp.cap(2) != "none") {
xAlignment = alignmentFromString(rexp.cap(4));
yAlignment = alignmentFromString(rexp.cap(5));
mode = rexp.cap(6) == "slice" ?
Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio;
}
}
}
QPointF SvgUtil::PreserveAspectRatioParser::rectAnchorPoint(const QRectF &rc) const
{
return QPointF(alignedValue(rc.x(), rc.x() + rc.width(), xAlignment),
alignedValue(rc.y(), rc.y() + rc.height(), yAlignment));
}
QString SvgUtil::PreserveAspectRatioParser::toString() const
{
QString result;
if (!defer &&
xAlignment == Middle &&
yAlignment == Middle &&
mode == Qt::KeepAspectRatio) {
return result;
}
if (defer) {
result += "defer ";
}
if (mode == Qt::IgnoreAspectRatio) {
result += "none";
} else {
result += QString("x%1Y%2")
.arg(alignmentToString(xAlignment))
.arg(alignmentToString(yAlignment));
if (mode == Qt::KeepAspectRatioByExpanding) {
result += " slice";
}
}
return result;
}
SvgUtil::PreserveAspectRatioParser::Alignment SvgUtil::PreserveAspectRatioParser::alignmentFromString(const QString &str) const {
return
str == "max" ? Max :
str == "mid" ? Middle : Min;
}
QString SvgUtil::PreserveAspectRatioParser::alignmentToString(SvgUtil::PreserveAspectRatioParser::Alignment alignment) const
{
return
alignment == Max ? "Max" :
alignment == Min ? "Min" :
"Mid";
}
qreal SvgUtil::PreserveAspectRatioParser::alignedValue(qreal min, qreal max, SvgUtil::PreserveAspectRatioParser::Alignment alignment)
{
qreal result = min;
switch (alignment) {
case Min:
result = min;
break;
case Middle:
result = 0.5 * (min + max);
break;
case Max:
result = max;
break;
}
return result;
}
diff --git a/libs/flake/svg/SvgWriter.cpp b/libs/flake/svg/SvgWriter.cpp
index 2e139c5bfd..eccbe1134a 100644
--- a/libs/flake/svg/SvgWriter.cpp
+++ b/libs/flake/svg/SvgWriter.cpp
@@ -1,321 +1,321 @@
/* This file is part of the KDE project
Copyright (C) 2002 Lars Siebold <khandha5@gmx.net>
Copyright (C) 2002-2003,2005 Rob Buis <buis@kde.org>
Copyright (C) 2002,2005-2006 David Faure <faure@kde.org>
Copyright (C) 2002 Werner Trobin <trobin@kde.org>
Copyright (C) 2002 Lennart Kudling <kudling@kde.org>
Copyright (C) 2004 Nicolas Goutte <nicolasg@snafu.de>
Copyright (C) 2005 Boudewijn Rempt <boud@valdyas.org>
Copyright (C) 2005 Raphael Langerhorst <raphael.langerhorst@kdemail.net>
Copyright (C) 2005 Thomas Zander <zander@kde.org>
Copyright (C) 2005,2007-2008 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2006 Inge Wallin <inge@lysator.liu.se>
Copyright (C) 2006 Martin Pfeiffer <hubipete@gmx.net>
Copyright (C) 2006 Gábor Lehel <illissius@gmail.com>
Copyright (C) 2006 Laurent Montel <montel@kde.org>
Copyright (C) 2006 Christian Mueller <cmueller@gmx.de>
Copyright (C) 2006 Ariya Hidayat <ariya@kde.org>
Copyright (C) 2010 Thorsten Zachmann <zachmann@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 "SvgWriter.h"
#include "SvgUtil.h"
#include "SvgSavingContext.h"
#include "SvgShape.h"
#include "SvgStyleWriter.h"
#include <KoShapeLayer.h>
#include <KoShapeGroup.h>
#include <KoPathShape.h>
#include <KoXmlWriter.h>
#include <KoShapePainter.h>
#include <KoXmlNS.h>
#include <QFile>
#include <QString>
#include <QTextStream>
#include <QBuffer>
#include <QPainter>
#include <QSvgGenerator>
#include <kis_debug.h>
SvgWriter::SvgWriter(const QList<KoShapeLayer*> &layers)
: m_writeInlineImages(true)
{
Q_FOREACH (KoShapeLayer *layer, layers)
m_toplevelShapes.append(layer);
}
SvgWriter::SvgWriter(const QList<KoShape*> &toplevelShapes)
: m_toplevelShapes(toplevelShapes)
, m_writeInlineImages(true)
{
}
SvgWriter::~SvgWriter()
{
}
bool SvgWriter::save(const QString &filename, const QSizeF &pageSize, bool writeInlineImages)
{
QFile fileOut(filename);
if (!fileOut.open(QIODevice::WriteOnly))
return false;
m_writeInlineImages = writeInlineImages;
const bool success = save(fileOut, pageSize);
m_writeInlineImages = true;
fileOut.close();
return success;
}
bool SvgWriter::save(QIODevice &outputDevice, const QSizeF &pageSize)
{
if (m_toplevelShapes.isEmpty()) {
return false;
}
QTextStream svgStream(&outputDevice);
svgStream.setCodec("UTF-8");
// standard header:
svgStream << "<?xml version=\"1.0\" standalone=\"no\"?>" << endl;
svgStream << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\" ";
svgStream << "\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">" << endl;
// add some PR. one line is more than enough.
- svgStream << "<!-- Created using Krita: http://krita.org -->" << endl;
+ svgStream << "<!-- Created using Krita: https://krita.org -->" << endl;
svgStream << "<svg xmlns=\"http://www.w3.org/2000/svg\" \n";
svgStream << " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n";
svgStream << QString(" xmlns:krita=\"%1\"\n").arg(KoXmlNS::krita);
svgStream << " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n";
svgStream << " width=\"" << pageSize.width() << "pt\"\n";
svgStream << " height=\"" << pageSize.height() << "pt\"\n";
svgStream << " viewBox=\"0 0 "
<< pageSize.width() << " " << pageSize.height()
<< "\"";
svgStream << ">" << endl;
if (!m_documentTitle.isNull() && !m_documentTitle.isEmpty()) {
svgStream << "<title>" << m_documentTitle << "</title>" << endl;
}
if (!m_documentDescription.isNull() && !m_documentDescription.isEmpty()) {
svgStream << "<desc>" << m_documentDescription << "</desc>" << endl;
}
{
SvgSavingContext savingContext(outputDevice, m_writeInlineImages);
saveShapes(m_toplevelShapes, savingContext);
}
// end tag:
svgStream << endl << "</svg>" << endl;
return true;
}
bool SvgWriter::saveDetached(QIODevice &outputDevice)
{
if (m_toplevelShapes.isEmpty())
return false;
SvgSavingContext savingContext(outputDevice, m_writeInlineImages);
saveShapes(m_toplevelShapes, savingContext);
return true;
}
bool SvgWriter::saveDetached(SvgSavingContext &savingContext)
{
if (m_toplevelShapes.isEmpty())
return false;
saveShapes(m_toplevelShapes, savingContext);
return true;
}
void SvgWriter::saveShapes(const QList<KoShape *> shapes, SvgSavingContext &savingContext)
{
// top level shapes
Q_FOREACH (KoShape *shape, shapes) {
KoShapeLayer *layer = dynamic_cast<KoShapeLayer*>(shape);
if(layer) {
saveLayer(layer, savingContext);
} else {
KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
if (group)
saveGroup(group, savingContext);
else
saveShape(shape, savingContext);
}
}
}
void SvgWriter::saveLayer(KoShapeLayer *layer, SvgSavingContext &context)
{
context.shapeWriter().startElement("g");
context.shapeWriter().addAttribute("id", context.getID(layer));
QList<KoShape*> sortedShapes = layer->shapes();
std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
Q_FOREACH (KoShape * shape, sortedShapes) {
KoShapeGroup * group = dynamic_cast<KoShapeGroup*>(shape);
if (group)
saveGroup(group, context);
else
saveShape(shape, context);
}
context.shapeWriter().endElement();
}
void SvgWriter::saveGroup(KoShapeGroup * group, SvgSavingContext &context)
{
context.shapeWriter().startElement("g");
context.shapeWriter().addAttribute("id", context.getID(group));
SvgUtil::writeTransformAttributeLazy("transform", group->transformation(), context.shapeWriter());
SvgStyleWriter::saveSvgStyle(group, context);
QList<KoShape*> sortedShapes = group->shapes();
std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
Q_FOREACH (KoShape * shape, sortedShapes) {
KoShapeGroup * childGroup = dynamic_cast<KoShapeGroup*>(shape);
if (childGroup)
saveGroup(childGroup, context);
else
saveShape(shape, context);
}
context.shapeWriter().endElement();
}
void SvgWriter::saveShape(KoShape *shape, SvgSavingContext &context)
{
SvgShape *svgShape = dynamic_cast<SvgShape*>(shape);
if (svgShape && svgShape->saveSvg(context))
return;
KoPathShape * path = dynamic_cast<KoPathShape*>(shape);
if (path) {
savePath(path, context);
} else {
// generic saving of shape via a switch element
saveGeneric(shape, context);
}
}
void SvgWriter::savePath(KoPathShape *path, SvgSavingContext &context)
{
context.shapeWriter().startElement("path");
context.shapeWriter().addAttribute("id", context.getID(path));
SvgUtil::writeTransformAttributeLazy("transform", path->transformation(), context.shapeWriter());
SvgStyleWriter::saveSvgStyle(path, context);
context.shapeWriter().addAttribute("d", path->toString(context.userSpaceTransform()));
context.shapeWriter().endElement();
}
void SvgWriter::saveGeneric(KoShape *shape, SvgSavingContext &context)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
const QRectF bbox = shape->boundingRect();
// paint shape to the image
KoShapePainter painter;
painter.setShapes(QList<KoShape*>()<< shape);
// generate svg from shape
QBuffer svgBuffer;
QSvgGenerator svgGenerator;
svgGenerator.setOutputDevice(&svgBuffer);
/**
* HACK ALERT: Qt (and Krita 3.x) has a weird bug, it assumes that all font sizes are
* defined in 96 ppi resolution, even though your the resolution in QSvgGenerator
* is manually set to 72 ppi. So here we do a tricky thing: we set a fake resolution
* to (72 * 72 / 96) = 54 ppi, which guarantees that the text, when painted in 96 ppi,
* will be actually painted in 72 ppi.
*
* BUG: 389802
*/
if (shape->shapeId() == "TextShapeID") {
svgGenerator.setResolution(54);
}
QPainter svgPainter;
svgPainter.begin(&svgGenerator);
painter.paint(svgPainter, SvgUtil::toUserSpace(bbox).toRect(), bbox);
svgPainter.end();
// remove anything before the start of the svg element from the buffer
int startOfContent = svgBuffer.buffer().indexOf("<svg");
if(startOfContent>0) {
svgBuffer.buffer().remove(0, startOfContent);
}
// check if painting to svg produced any output
if (svgBuffer.buffer().isEmpty()) {
// prepare a transparent image, make it twice as big as the original size
QImage image(2*bbox.size().toSize(), QImage::Format_ARGB32);
image.fill(0);
painter.paint(image);
context.shapeWriter().startElement("image");
context.shapeWriter().addAttribute("id", context.getID(shape));
context.shapeWriter().addAttribute("x", bbox.x());
context.shapeWriter().addAttribute("y", bbox.y());
context.shapeWriter().addAttribute("width", bbox.width());
context.shapeWriter().addAttribute("height", bbox.height());
context.shapeWriter().addAttribute("xlink:href", context.saveImage(image));
context.shapeWriter().endElement(); // image
} else {
context.shapeWriter().addCompleteElement(&svgBuffer);
}
// TODO: once we support saving single (flat) odf files
// we can embed these here to have full support for generic shapes
}
void SvgWriter::setDocumentTitle(QString title)
{
m_documentTitle = title;
}
void SvgWriter::setDocumentDescription(QString description)
{
m_documentDescription = description;
}
diff --git a/libs/flake/tests/CMakeLists.txt b/libs/flake/tests/CMakeLists.txt
index 905eed5224..8aa8bb3303 100644
--- a/libs/flake/tests/CMakeLists.txt
+++ b/libs/flake/tests/CMakeLists.txt
@@ -1,90 +1,86 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include(ECMAddTests)
include(KritaAddBrokenUnitTest)
macro_add_unittest_definitions()
ecm_add_tests(
TestPosition.cpp
TestSelection.cpp
TestPathTool.cpp
TestShapeAt.cpp
TestShapePainting.cpp
TestKoShapeFactory.cpp
TestKoShapeRegistry.cpp
TestShapeContainer.cpp
TestShapeGroupCommand.cpp
TestShapeReorderCommand.cpp
TestImageCollection.cpp
TestResourceManager.cpp
TestShapeBackgroundCommand.cpp
TestShapeStrokeCommand.cpp
TestShapeShadowCommand.cpp
TestInputDevice.cpp
TestSnapStrategy.cpp
TestPathShape.cpp
TestControlPointMoveCommand.cpp
TestPointTypeCommand.cpp
TestPointRemoveCommand.cpp
TestRemoveSubpathCommand.cpp
TestPathSegment.cpp
TestSegmentTypeCommand.cpp
TestKoDrag.cpp
TestKoMarkerCollection.cpp
- KisGamutMaskViewConverterTest.cpp
LINK_LIBRARIES kritaflake Qt5::Test
NAME_PREFIX "libs-flake-")
ecm_add_test(
TestSvgParser.cpp
TEST_NAME TestSvgParser
LINK_LIBRARIES kritaflake Qt5::Test
NAME_PREFIX "libs-flake-")
ecm_add_test(
TestSvgParser.cpp
TEST_NAME TestSvgParserCloned
LINK_LIBRARIES kritaflake Qt5::Test
NAME_PREFIX "libs-flake-")
set_property(TARGET TestSvgParserCloned
PROPERTY COMPILE_DEFINITIONS USE_CLONED_SHAPES)
ecm_add_test(
TestSvgParser.cpp
TEST_NAME TestSvgParserRoundTrip
LINK_LIBRARIES kritaflake Qt5::Test
NAME_PREFIX "libs-flake-")
set_property(TARGET TestSvgParserRoundTrip
PROPERTY COMPILE_DEFINITIONS USE_ROUND_TRIP)
############## broken tests ###############
krita_add_broken_unit_test(TestPointMergeCommand.cpp
TEST_NAME TestPointMergeCommand
LINK_LIBRARIES kritaflake Qt5::Test
NAME_PREFIX "libs-flake-")
-krita_add_broken_unit_test(
- TestSvgText.cpp
+krita_add_broken_unit_test( TestSvgText.cpp
TEST_NAME TestSvgText
LINK_LIBRARIES kritaflake Qt5::Test
NAME_PREFIX "libs-flake-")
-krita_add_broken_unit_test(
- TestSvgText.cpp
+krita_add_broken_unit_test( TestSvgText.cpp
TEST_NAME TestSvgTextCloned
LINK_LIBRARIES kritaflake Qt5::Test
NAME_PREFIX "libs-flake-")
set_property(TARGET TestSvgTextCloned
PROPERTY COMPILE_DEFINITIONS USE_CLONED_SHAPES)
-krita_add_broken_unit_test(
- TestSvgText.cpp
+krita_add_broken_unit_test( TestSvgText.cpp
TEST_NAME TestSvgTextRoundTrip
LINK_LIBRARIES kritaflake Qt5::Test
NAME_PREFIX "libs-flake-")
set_property(TARGET TestSvgTextRoundTrip
PROPERTY COMPILE_DEFINITIONS USE_ROUND_TRIP)
diff --git a/libs/flake/tests/KisGamutMaskViewConverterTest.cpp b/libs/flake/tests/KisGamutMaskViewConverterTest.cpp
deleted file mode 100644
index e3cd26ebf9..0000000000
--- a/libs/flake/tests/KisGamutMaskViewConverterTest.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (c) 2019 Anna Medonosova <anna.medonosova@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 <QScopedPointer>
-
-#include <KisGamutMaskViewConverter.h>
-#include <kis_assert_exception.h>
-
-#include "KisGamutMaskViewConverterTest.h"
-
-
-
-KisGamutMaskViewConverterTest::KisGamutMaskViewConverterTest(QObject *parent) : QObject(parent)
-{
-
-}
-
-void KisGamutMaskViewConverterTest::testDocumentToViewX()
-{
- QFETCH(qreal, input);
- QFETCH(qreal, expectedOutput);
- QFETCH(QSizeF, maskSize);
- QFETCH(QSize, viewSize);
-
- QScopedPointer<KisGamutMaskViewConverter> converter(new KisGamutMaskViewConverter());
- converter->setMaskSize(maskSize);
- converter->setViewSize(viewSize);
-
- qreal converterOutput = converter->documentToViewX(input);
- QCOMPARE(converterOutput, expectedOutput);
-}
-
-void KisGamutMaskViewConverterTest::testDocumentToViewX_data()
-{
- QTest::addColumn<qreal>("input");
- QTest::addColumn<qreal>("expectedOutput");
- QTest::addColumn<QSizeF>("maskSize");
- QTest::addColumn<QSize>("viewSize");
-
- QTest::newRow("mask < view") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 400);
- QTest::newRow("view < mask") << 2.0 << 1.0 << QSizeF(400.0, 400.0) << QSize(200, 200);
-
- QTest::newRow("view not square") << 2.0 << 4.0 << QSizeF(200.0, 300.0) << QSize(400, 400);
- QTest::newRow("mask not square") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 600);
-}
-
-void KisGamutMaskViewConverterTest::testDocumentToViewY()
-{
- QFETCH(qreal, input);
- QFETCH(qreal, expectedOutput);
- QFETCH(QSizeF, maskSize);
- QFETCH(QSize, viewSize);
-
- QScopedPointer<KisGamutMaskViewConverter> converter(new KisGamutMaskViewConverter());
- converter->setMaskSize(maskSize);
- converter->setViewSize(viewSize);
-
- qreal converterOutput = converter->documentToViewY(input);
- QCOMPARE(converterOutput, expectedOutput);
-}
-
-void KisGamutMaskViewConverterTest::testDocumentToViewY_data()
-{
-
- QTest::addColumn<qreal>("input");
- QTest::addColumn<qreal>("expectedOutput");
- QTest::addColumn<QSizeF>("maskSize");
- QTest::addColumn<QSize>("viewSize");
-
- QTest::newRow("mask < view") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 400);
- QTest::newRow("view < mask") << 2.0 << 1.0 << QSizeF(400.0, 400.0) << QSize(200, 200);
-
- QTest::newRow("view not square") << 2.0 << 4.0 << QSizeF(200.0, 300.0) << QSize(400, 400);
- QTest::newRow("mask not square") << 2.0 << 4.0 << QSizeF(200.0, 200.0) << QSize(400, 600);
-}
-
-void KisGamutMaskViewConverterTest::testViewToDocumentX()
-{
- QFETCH(qreal, input);
- QFETCH(qreal, expectedOutput);
- QFETCH(QSizeF, maskSize);
- QFETCH(QSize, viewSize);
-
- QScopedPointer<KisGamutMaskViewConverter> converter(new KisGamutMaskViewConverter());
- converter->setMaskSize(maskSize);
- converter->setViewSize(viewSize);
-
- qreal converterOutput = converter->viewToDocumentX(input);
- QCOMPARE(converterOutput, expectedOutput);
-}
-
-void KisGamutMaskViewConverterTest::testViewToDocumentX_data()
-{
- QTest::addColumn<qreal>("input");
- QTest::addColumn<qreal>("expectedOutput");
- QTest::addColumn<QSizeF>("maskSize");
- QTest::addColumn<QSize>("viewSize");
-
- QTest::newRow("mask < view") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 400);
- QTest::newRow("view < mask") << 1.0 << 2.0 << QSizeF(400.0, 400.0) << QSize(200, 200);
-
- QTest::newRow("view not square") << 4.0 << 2.0 << QSizeF(200.0, 300.0) << QSize(400, 400);
- QTest::newRow("mask not square") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 600);
-}
-
-void KisGamutMaskViewConverterTest::testViewToDocumentY()
-{
- QFETCH(qreal, input);
- QFETCH(qreal, expectedOutput);
- QFETCH(QSizeF, maskSize);
- QFETCH(QSize, viewSize);
-
- QScopedPointer<KisGamutMaskViewConverter> converter(new KisGamutMaskViewConverter());
- converter->setMaskSize(maskSize);
- converter->setViewSize(viewSize);
-
- qreal converterOutput = converter->viewToDocumentY(input);
- QCOMPARE(converterOutput, expectedOutput);
-}
-
-void KisGamutMaskViewConverterTest::testViewToDocumentY_data()
-{
- QTest::addColumn<qreal>("input");
- QTest::addColumn<qreal>("expectedOutput");
- QTest::addColumn<QSizeF>("maskSize");
- QTest::addColumn<QSize>("viewSize");
-
-
- QTest::newRow("mask < view") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 400);
- QTest::newRow("view < mask") << 1.0 << 2.0 << QSizeF(400.0, 400.0) << QSize(200, 200);
-
- QTest::newRow("view not square") << 4.0 << 2.0 << QSizeF(200.0, 300.0) << QSize(400, 400);
- QTest::newRow("mask not square") << 4.0 << 2.0 << QSizeF(200.0, 200.0) << QSize(400, 600);
-}
-
-QTEST_MAIN(KisGamutMaskViewConverterTest);
diff --git a/libs/flake/tests/MockShapes.h b/libs/flake/tests/MockShapes.h
index 4aae2108bc..d6d878d990 100644
--- a/libs/flake/tests/MockShapes.h
+++ b/libs/flake/tests/MockShapes.h
@@ -1,251 +1,248 @@
/*
* This file is part of Calligra tests
*
* Copyright (C) 2006-2010 Thomas Zander <zander@kde.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 MOCKSHAPES_H
#define MOCKSHAPES_H
#include <KoSelectedShapesProxySimple.h>
#include <KoShapeGroup.h>
#include <KoCanvasBase.h>
#include <KoShapeControllerBase.h>
#include <KoShapeContainerModel.h>
#include <QPainter>
#include "KoShapeManager.h"
#include "FlakeDebug.h"
#include "KoSnapData.h"
#include "KoUnit.h"
#include "kritaflake_export.h"
class KRITAFLAKE_EXPORT MockShape : public KoShape
{
public:
MockShape() : paintedCount(0) {}
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) override {
+ void paint(QPainter &painter, KoShapePaintingContext &) const override {
Q_UNUSED(painter);
- Q_UNUSED(converter);
//qDebug() << "Shape" << kBacktrace( 10 );
paintedCount++;
}
void saveOdf(KoShapeSavingContext &) const override {}
bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override {
return true;
}
- int paintedCount;
+ mutable int paintedCount;
};
class KRITAFLAKE_EXPORT MockContainer : public KoShapeContainer
{
public:
MockContainer(KoShapeContainerModel *model = 0) : KoShapeContainer(model), paintedCount(0) {}
- void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) override {
+ void paintComponent(QPainter &painter, KoShapePaintingContext &) const override {
Q_UNUSED(painter);
- Q_UNUSED(converter);
//qDebug() << "Container:" << kBacktrace( 10 );
paintedCount++;
}
void saveOdf(KoShapeSavingContext &) const override {}
bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) override {
return true;
}
- int paintedCount;
+ mutable int paintedCount;
};
class KRITAFLAKE_EXPORT MockGroup : public KoShapeGroup
{
- void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) override {
+ void paintComponent(QPainter &painter, KoShapePaintingContext &) const override {
Q_UNUSED(painter);
- Q_UNUSED(converter);
}
};
class KoToolProxy;
class KRITAFLAKE_EXPORT MockShapeController : public KoShapeControllerBase
{
public:
void addShapes(const QList<KoShape*> shapes) override {
Q_FOREACH (KoShape *shape, shapes) {
m_shapes.insert(shape);
if (m_shapeManager) {
m_shapeManager->addShape(shape);
}
}
}
void removeShape(KoShape* shape) override {
m_shapes.remove(shape);
if (m_shapeManager) {
m_shapeManager->remove(shape);
}
}
bool contains(KoShape* shape) {
return m_shapes.contains(shape);
}
void setShapeManager(KoShapeManager *shapeManager) {
m_shapeManager = shapeManager;
}
QRectF documentRectInPixels() const override {
return QRectF(0,0,100,100);
}
qreal pixelsPerInch() const override {
return 72.0;
}
private:
QSet<KoShape * > m_shapes;
KoShapeManager *m_shapeManager = 0;
};
class KRITAFLAKE_EXPORT MockCanvas : public KoCanvasBase
{
Q_OBJECT
public:
MockCanvas(KoShapeControllerBase *aKoShapeControllerBase =0)//made for TestSnapStrategy.cpp
: KoCanvasBase(aKoShapeControllerBase),
m_shapeManager(new KoShapeManager(this)),
m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
{
if (MockShapeController *controller = dynamic_cast<MockShapeController*>(aKoShapeControllerBase)) {
controller->setShapeManager(m_shapeManager.data());
}
}
~MockCanvas() override {}
void setHorz(qreal pHorz){
m_horz = pHorz;
}
void setVert(qreal pVert){
m_vert = pVert;
}
void gridSize(QPointF *offset, QSizeF *spacing) const override {
Q_UNUSED(offset);
spacing->setWidth(m_horz);
spacing->setHeight(m_vert);
}
bool snapToGrid() const override {
return true;
}
void addCommand(KUndo2Command*) override { }
KoShapeManager *shapeManager() const override {
return m_shapeManager.data();
}
KoSelectedShapesProxy *selectedShapesProxy() const override {
return m_selectedShapesProxy.data();
}
void updateCanvas(const QRectF&) override {}
KoToolProxy * toolProxy() const override {
return 0;
}
KoViewConverter *viewConverter() const override {
return 0;
}
QWidget* canvasWidget() override {
return 0;
}
const QWidget* canvasWidget() const override {
return 0;
}
KoUnit unit() const override {
return KoUnit(KoUnit::Millimeter);
}
void updateInputMethodInfo() override {}
void setCursor(const QCursor &) override {}
private:
QScopedPointer<KoShapeManager> m_shapeManager;
QScopedPointer<KoSelectedShapesProxy> m_selectedShapesProxy;
qreal m_horz;
qreal m_vert;
};
class KRITAFLAKE_EXPORT MockContainerModel : public KoShapeContainerModel
{
public:
MockContainerModel() {
resetCounts();
}
/// reimplemented
void add(KoShape *child) override {
m_children.append(child); // note that we explicitly do not check for duplicates here!
}
/// reimplemented
void remove(KoShape *child) override {
m_children.removeAll(child);
}
/// reimplemented
void setClipped(const KoShape *, bool) override { } // ignored
/// reimplemented
bool isClipped(const KoShape *) const override {
return false;
}// ignored
/// reimplemented
int count() const override {
return m_children.count();
}
/// reimplemented
QList<KoShape*> shapes() const override {
return m_children;
}
/// reimplemented
void containerChanged(KoShapeContainer *, KoShape::ChangeType) override {
m_containerChangedCalled++;
}
/// reimplemented
void proposeMove(KoShape *, QPointF &) override {
m_proposeMoveCalled++;
}
/// reimplemented
void childChanged(KoShape *, KoShape::ChangeType) override {
m_childChangedCalled++;
}
void setInheritsTransform(const KoShape *, bool) override {
}
bool inheritsTransform(const KoShape *) const override {
return false;
}
int containerChangedCalled() const {
return m_containerChangedCalled;
}
int childChangedCalled() const {
return m_childChangedCalled;
}
int proposeMoveCalled() const {
return m_proposeMoveCalled;
}
void resetCounts() {
m_containerChangedCalled = 0;
m_childChangedCalled = 0;
m_proposeMoveCalled = 0;
}
private:
QList<KoShape*> m_children;
int m_containerChangedCalled, m_childChangedCalled, m_proposeMoveCalled;
};
#endif
diff --git a/libs/flake/tests/SvgParserTestingUtils.h b/libs/flake/tests/SvgParserTestingUtils.h
index 072a7b3772..8cf105396d 100644
--- a/libs/flake/tests/SvgParserTestingUtils.h
+++ b/libs/flake/tests/SvgParserTestingUtils.h
@@ -1,221 +1,220 @@
/*
* 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 SVGPARSERTESTINGUTILS_H
#define SVGPARSERTESTINGUTILS_H
#include <svg/SvgParser.h>
#include <kis_debug.h>
#include <kis_global.h>
#include <KoViewConverter.h>
#include <KoDocumentResourceManager.h>
#include <KoShape.h>
#include <KoShapeGroup.h>
#include <QPainter>
#include <KoShapePainter.h>
#include "kis_algebra_2d.h"
#include <QXmlSimpleReader>
struct SvgTester
{
SvgTester (const QString &data)
: parser(&resourceManager),
doc(SvgParser::createDocumentFromSvg(data))
{
root = doc.documentElement();
parser.setXmlBaseDir("./");
savedData = data;
//printf("%s", savedData.toUtf8().data());
}
~SvgTester ()
{
qDeleteAll(shapes);
}
void run() {
shapes = parser.parseSvg(root, &fragmentSize);
}
KoShape* findShape(const QString &name, KoShape *parent = 0) {
if (parent && parent->name() == name) {
return parent;
}
QList<KoShape*> children;
if (!parent) {
children = shapes;
} else {
KoShapeContainer *cont = dynamic_cast<KoShapeContainer*>(parent);
if (cont) {
children = cont->shapes();
}
}
Q_FOREACH (KoShape *shape, children) {
KoShape *result = findShape(name, shape);
if (result) {
return result;
}
}
return 0;
}
KoShapeGroup* findGroup(const QString &name) {
KoShapeGroup *group = 0;
KoShape *shape = findShape(name);
if (shape) {
group = dynamic_cast<KoShapeGroup*>(shape);
}
return group;
}
KoDocumentResourceManager resourceManager;
SvgParser parser;
KoXmlDocument doc;
KoXmlElement root;
QSizeF fragmentSize;
QList<KoShape*> shapes;
QString savedData;
};
#include "../../sdk/tests/qimage_test_util.h"
#ifdef USE_ROUND_TRIP
#include "SvgWriter.h"
#include <QBuffer>
#include <QDomDocument>
#endif
struct SvgRenderTester : public SvgTester
{
SvgRenderTester(const QString &data)
: SvgTester(data),
m_fuzzyThreshold(0)
{
}
void setFuzzyThreshold(int fuzzyThreshold) {
m_fuzzyThreshold = fuzzyThreshold;
}
static void testRender(KoShape *shape, const QString &prefix, const QString &testName, const QSize canvasSize, int fuzzyThreshold = 0) {
QImage canvas(canvasSize, QImage::Format_ARGB32);
canvas.fill(0);
- KoViewConverter converter;
QPainter painter(&canvas);
KoShapePainter p;
p.setShapes({shape});
painter.setClipRect(canvas.rect());
- p.paint(painter, converter);
+ p.paint(painter);
QVERIFY(TestUtil::checkQImage(canvas, "svg_render", prefix, testName, fuzzyThreshold));
}
void test_standard_30px_72ppi(const QString &testName, bool verifyGeometry = true, const QSize &canvasSize = QSize(30,30)) {
test_standard_impl(testName, verifyGeometry, canvasSize, 72.0);
}
void test_standard(const QString &testName, const QSize &canvasSize, qreal pixelsPerInch) {
test_standard_impl(testName, false, canvasSize, pixelsPerInch);
}
void test_standard_impl(const QString &testName, bool verifyGeometry, const QSize &canvasSize, qreal pixelsPerInch) {
QSize sizeInPx = canvasSize;
QSizeF sizeInPt = QSizeF(canvasSize) * 72.0 / pixelsPerInch;
Q_UNUSED(sizeInPt); // used in some definitions only!
parser.setResolution(QRectF(QPointF(), sizeInPx) /* px */, pixelsPerInch /* ppi */);
run();
#ifdef USE_CLONED_SHAPES
{
QList<KoShape*> newShapes;
Q_FOREACH (KoShape *shape, shapes) {
KoShape *clonedShape = shape->cloneShape();
KIS_ASSERT(clonedShape);
newShapes << clonedShape;
}
qDeleteAll(shapes);
shapes = newShapes;
}
#endif /* USE_CLONED_SHAPES */
#ifdef USE_ROUND_TRIP
QBuffer writeBuf;
writeBuf.open(QIODevice::WriteOnly);
{
SvgWriter writer(shapes);
writer.save(writeBuf, sizeInPt);
}
QDomDocument prettyDoc;
prettyDoc.setContent(savedData);
qDebug();
printf("\n=== Original: ===\n\n%s\n", prettyDoc.toByteArray(4).data());
printf("\n=== Saved: ===\n\n%s\n", writeBuf.data().data());
qDebug();
QVERIFY(doc.setContent(writeBuf.data()));
root = doc.documentElement();
run();
#endif /* USE_ROUND_TRIP */
KoShape *shape = findShape("testRect");
KIS_ASSERT(shape);
if (verifyGeometry) {
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,5));
const QPointF bottomRight= shape->absolutePosition(KoFlake::BottomRight);
const QPointF expectedBottomRight(15,25);
if (KisAlgebra2D::norm(bottomRight - expectedBottomRight) > 0.0001 ) {
QCOMPARE(bottomRight, expectedBottomRight);
}
}
testRender(shape, "load", testName, canvasSize, m_fuzzyThreshold);
}
private:
int m_fuzzyThreshold;
};
#endif // SVGPARSERTESTINGUTILS_H
diff --git a/libs/flake/tests/TestKoDrag.cpp b/libs/flake/tests/TestKoDrag.cpp
index 285d783f11..e73bded4c1 100644
--- a/libs/flake/tests/TestKoDrag.cpp
+++ b/libs/flake/tests/TestKoDrag.cpp
@@ -1,86 +1,86 @@
/*
* 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 "TestKoDrag.h"
#include <KoDrag.h>
#include <KoSvgPaste.h>
#include <kis_debug.h>
#include <kis_global.h>
#include <svg/SvgParser.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeGroup.h>
#include "../../sdk/tests/qimage_test_util.h"
void TestKoDrag::test()
{
const QString fileName = TestUtil::fetchDataFileLazy("test_svg_file.svg");
QVERIFY(!fileName.isEmpty());
QFile testShapes(fileName);
testShapes.open(QIODevice::ReadOnly);
KoXmlDocument doc = SvgParser::createDocumentFromSvg(&testShapes);
KoDocumentResourceManager resourceManager;
SvgParser parser(&resourceManager);
parser.setResolution(QRectF(0, 0, 30, 30) /* px */, 72 /* ppi */);
QSizeF fragmentSize;
QList<KoShape*> shapes = parser.parseSvg(doc.documentElement(), &fragmentSize);
QCOMPARE(fragmentSize, QSizeF(30,30));
{
QCOMPARE(shapes.size(), 1);
KoShapeGroup *layer = dynamic_cast<KoShapeGroup*>(shapes.first());
QVERIFY(layer);
QCOMPARE(layer->shapeCount(), 2);
- QCOMPARE(KoShape::boundingRect(shapes).toAlignedRect(), QRect(3,2,25,26));
+ QCOMPARE(KoShape::absoluteOutlineRect(shapes).toAlignedRect(), QRect(6,6,19,18));
}
KoDrag drag;
drag.setSvg(shapes);
drag.addToClipboard();
KoSvgPaste paste;
QVERIFY(paste.hasShapes());
QList<KoShape*> newShapes = paste.fetchShapes(QRectF(0,0,15,15) /* px */, 144 /* ppi */, &fragmentSize);
{
QCOMPARE(newShapes.size(), 1);
KoShapeGroup *layer = dynamic_cast<KoShapeGroup*>(newShapes.first());
QVERIFY(layer);
QCOMPARE(layer->shapeCount(), 2);
- QCOMPARE(fragmentSize.toSize(), QSize(56, 54));
- QCOMPARE(KoShape::boundingRect(newShapes).toAlignedRect(), QRect(3,2,25,26));
+ QCOMPARE(fragmentSize.toSize(), QSize(62, 61));
+ QCOMPARE(KoShape::absoluteOutlineRect(newShapes).toAlignedRect(), QRect(6,6,19,18));
}
qDeleteAll(shapes);
qDeleteAll(newShapes);
}
QTEST_MAIN(TestKoDrag)
diff --git a/libs/flake/tests/TestKoMarkerCollection.cpp b/libs/flake/tests/TestKoMarkerCollection.cpp
index de05a13edc..bec1f20bf0 100644
--- a/libs/flake/tests/TestKoMarkerCollection.cpp
+++ b/libs/flake/tests/TestKoMarkerCollection.cpp
@@ -1,111 +1,111 @@
/*
* 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 "TestKoMarkerCollection.h"
#include <QTest>
#include <QFileInfo>
#include <QPainter>
#include <KoMarker.h>
#include <KoMarkerCollection.h>
#include <KoPathShape.h>
#include <KoColorBackground.h>
#include "kis_debug.h"
#include "../../sdk/tests/qimage_test_util.h"
#include <sdk/tests/kistest.h>
#include <cmath>
void initMarkerCollection(KoMarkerCollection *collection)
{
QCOMPARE(collection->markers().size(), 1);
const QString fileName = TestUtil::fetchDataFileLazy("test_markers.svg");
QVERIFY(QFileInfo(fileName).exists());
collection->loadMarkersFromFile(fileName);
QCOMPARE(collection->markers().size(), 10);
}
void TestKoMarkerCollection::testLoadMarkersFromFile()
{
KoMarkerCollection collection;
initMarkerCollection(&collection);
}
void TestKoMarkerCollection::testDeduplication()
{
QPainterPath path1;
path1.addRect(QRect(5,5,15,15));
KoPathShape *shape1(KoPathShape::createShapeFromPainterPath(path1));
shape1->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(Qt::blue)));
KoMarker *marker(new KoMarker());
marker->setAutoOrientation(true);
marker->setShapes({shape1});
KoMarkerCollection collection;
QCOMPARE(collection.markers().size(), 1);
KoMarker *clonedMarker = new KoMarker(*marker);
collection.addMarker(marker);
QCOMPARE(collection.markers().size(), 2);
collection.addMarker(marker);
QCOMPARE(collection.markers().size(), 2);
collection.addMarker(clonedMarker);
QCOMPARE(collection.markers().size(), 2);
}
void testOneMarkerPosition(KoMarker *marker, KoFlake::MarkerPosition position, const QString &testName)
{
QImage image(30,30, QImage::Format_ARGB32);
image.fill(0);
QPainter painter(&image);
QPen pen(Qt::black, 2);
marker->drawPreview(&painter, image.rect(), pen, position);
QVERIFY(TestUtil::checkQImage(image, "marker_collection", "preview", testName));
}
void TestKoMarkerCollection::testMarkerBounds()
{
KoMarkerCollection collection;
initMarkerCollection(&collection);
QList<KoMarker*> allMarkers = collection.markers();
KoMarker *marker = allMarkers[3];
- QCOMPARE(marker->boundingRect(1, 0).toAlignedRect(), QRect(-7,-3,9,6));
- QCOMPARE(marker->boundingRect(1, M_PI).toAlignedRect(), QRect(-2,-3,9,6));
+ QCOMPARE(marker->boundingRect(1, 0).toAlignedRect(), QRect(-8,-4,11,8));
+ QCOMPARE(marker->boundingRect(1, M_PI).toAlignedRect(), QRect(-3,-4,11,8));
QCOMPARE(marker->outline(1, 0).boundingRect().toAlignedRect(), QRect(-6,-2,7,4));
QCOMPARE(marker->outline(1, M_PI).boundingRect().toAlignedRect(), QRect(-1,-2,7,4));
testOneMarkerPosition(marker, KoFlake::StartMarker, "start_marker");
testOneMarkerPosition(marker, KoFlake::MidMarker, "mid_marker");
testOneMarkerPosition(marker, KoFlake::EndMarker, "end_marker");
}
KISTEST_MAIN(TestKoMarkerCollection)
diff --git a/libs/flake/tests/TestKoShapeRegistry.cpp b/libs/flake/tests/TestKoShapeRegistry.cpp
index 8fc3a4c4dd..fc96f26f12 100644
--- a/libs/flake/tests/TestKoShapeRegistry.cpp
+++ b/libs/flake/tests/TestKoShapeRegistry.cpp
@@ -1,213 +1,214 @@
/* This file is part of the KDE project
* Copyright (c) 2007 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 "TestKoShapeRegistry.h"
#include <QTest>
#include <QBuffer>
#include <QFile>
#include <QDateTime>
#include <QProcess>
#include <QString>
#include <QTextStream>
#include <QtXml>
#include <KoOdfLoadingContext.h>
#include <KoOdfStylesReader.h>
#include "KoShapeRegistry.h"
#include "KoShape.h"
#include "KoPathShape.h"
#include "KoShapeLoadingContext.h"
#include <KoXmlReader.h>
#include <FlakeDebug.h>
#include "kis_debug.h"
#include <sdk/tests/kistest.h>
void TestKoShapeRegistry::testGetKoShapeRegistryInstance()
{
KoShapeRegistry * registry = KoShapeRegistry::instance();
QVERIFY(registry != 0);
}
void TestKoShapeRegistry::testCreateShapes()
{
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream.setCodec("UTF-8");
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\" xmlns:math=\"http://www.w3.org/1998/Math/MathML\" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:calligra=\"http://www.calligra.org/2005/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">";
xmlstream << "<office:body>";
xmlstream << "<office:text>";
xmlstream << "<draw:path svg:d=\"M0,0L100,100\"></draw:path>";
xmlstream << "</office:text>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc;
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement contentElement = doc.documentElement();
KoXmlElement bodyElement = contentElement.firstChild().toElement();
KoShapeRegistry * registry = KoShapeRegistry::instance();
// XXX: When loading is implemented, these no doubt have to be
// sensibly filled.
KoOdfStylesReader stylesReader;
KoOdfLoadingContext odfContext(stylesReader, 0);
KoShapeLoadingContext shapeContext(odfContext, 0);
KoShape * shape = registry->createShapeFromOdf(bodyElement, shapeContext);
QVERIFY(shape == 0);
KoXmlElement pathElement = bodyElement.firstChild().firstChild().toElement();
shape = registry->createShapeFromOdf(pathElement, shapeContext);
QVERIFY(shape != 0);
QVERIFY(shape->shapeId() == KoPathShapeId);
}
void TestKoShapeRegistry::testCreateFramedShapes()
{
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream.setCodec("UTF-8");
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\" xmlns:math=\"http://www.w3.org/1998/Math/MathML\" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:calligra=\"http://www.calligra.org/2005/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">";
xmlstream << "<office:body>";
xmlstream << "<office:text>";
xmlstream << "<draw:path svg:d=\"M0,0L100,100\"></draw:path>";
xmlstream << "</office:text>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc;
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement contentElement = doc.documentElement();
KoXmlElement bodyElement = contentElement.firstChild().toElement();
KoShapeRegistry * registry = KoShapeRegistry::instance();
// XXX: When loading is implemented, these no doubt have to be
// sensibly filled.
KoOdfStylesReader stylesReader;
KoOdfLoadingContext odfContext(stylesReader, 0);
KoShapeLoadingContext shapeContext(odfContext, 0);
KoShape * shape = registry->createShapeFromOdf(bodyElement, shapeContext);
QVERIFY(shape == 0);
KoXmlElement pathElement = bodyElement.firstChild().firstChild().toElement();
shape = registry->createShapeFromOdf(pathElement, shapeContext);
QVERIFY(shape != 0);
QVERIFY(shape->shapeId() == KoPathShapeId);
}
#include <KoStore.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeController.h>
#include <MockShapes.h>
#include "../../sdk/tests/qimage_test_util.h"
void TestKoShapeRegistry::testFramedSvgShapes()
{
QBuffer xmldevice;
xmldevice.open(QIODevice::WriteOnly);
QTextStream xmlstream(&xmldevice);
xmlstream.setCodec("UTF-8");
xmlstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
xmlstream << "<office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\" xmlns:math=\"http://www.w3.org/1998/Math/MathML\" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:calligra=\"http://www.calligra.org/2005/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">";
xmlstream << "<office:body>";
xmlstream << "<office:text>";
xmlstream << "<draw:frame xml:id=\"shape-1\" draw:z-index=\"2\" draw:id=\"shape-1\" draw:layer=\"\" svg:width=\"226pt\" svg:height=\"141pt\" svg:x=\"83pt\" svg:y=\"41pt\">";
xmlstream << " <draw:image xlink:type=\"simple\" draw:z-index=\"3\" xlink:show=\"embed\" xlink:actuate=\"onLoad\" xlink:href=\"VectorImages/Image1\"/>";
xmlstream << "</draw:frame>";
xmlstream << "</office:text>";
xmlstream << "</office:body>";
xmlstream << "</office:document-content>";
xmldevice.close();
KoXmlDocument doc;
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
QCOMPARE(doc.setContent(&xmldevice, true, &errorMsg, &errorLine, &errorColumn), true);
QCOMPARE(errorMsg.isEmpty(), true);
QCOMPARE(errorLine, 0);
QCOMPARE(errorColumn, 0);
KoXmlElement contentElement = doc.documentElement();
KoXmlElement bodyElement = contentElement.firstChild().toElement();
KoShapeRegistry * registry = KoShapeRegistry::instance();
// XXX: When loading is implemented, these no doubt have to be
// sensibly filled.
KoOdfStylesReader stylesReader;
const QString resourcesBlob = TestUtil::fetchDataFileLazy("odf_frame_resource_store.zip");
QScopedPointer<KoStore> store(KoStore::createStore(resourcesBlob, KoStore::Read, "krita", KoStore::Zip));
QScopedPointer<KoDocumentResourceManager> resourceManager(new KoDocumentResourceManager());
+ resourceManager->setResource(KoDocumentResourceManager::DocumentRectInPixels, QRect(0,0,1000,1000));
QScopedPointer<MockShapeController> document(new MockShapeController());
QScopedPointer<MockCanvas> canvas(new MockCanvas(document.data()));
QScopedPointer<KoShapeController> shapeController(new KoShapeController(canvas.data(), document.data()));
resourceManager->setGlobalShapeController(shapeController.data());
KoOdfLoadingContext odfContext(stylesReader, store.data());
KoShapeLoadingContext shapeContext(odfContext, resourceManager.data());
KoXmlElement frameElement = bodyElement.firstChild().firstChild().toElement();
QCOMPARE(frameElement.tagName(), QString("frame"));
KoShape *shape = registry->createShapeFromOdf(frameElement, shapeContext);
QVERIFY(shape);
- QCOMPARE(shape->absoluteOutlineRect(0), QRectF(83, 41, 226,141));
+ QCOMPARE(shape->absoluteOutlineRect(), QRectF(83, 41, 226,141));
}
KISTEST_MAIN(TestKoShapeRegistry)
diff --git a/libs/flake/tests/TestPointMergeCommand.cpp b/libs/flake/tests/TestPointMergeCommand.cpp
index ee6c6477bb..f735ccd75c 100644
--- a/libs/flake/tests/TestPointMergeCommand.cpp
+++ b/libs/flake/tests/TestPointMergeCommand.cpp
@@ -1,577 +1,577 @@
/* 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 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 "TestPointMergeCommand.h"
#include "KoPathPointMergeCommand.h"
#include "KoPathShape.h"
#include "KoPathPoint.h"
#include "KoPathPointData.h"
#include <sdk/tests/kistest.h>
#include <QTest>
#include <FlakeDebug.h>
void TestPointMergeCommand::closeSingleLinePath()
{
KoPathShape path1;
path1.moveTo(QPointF(40, 0));
path1.lineTo(QPointF(60, 0));
path1.lineTo(QPointF(60, 30));
path1.lineTo(QPointF(0, 30));
path1.lineTo(QPointF(0, 0));
path1.lineTo(QPointF(20, 0));
KoPathPointIndex index1(0,0);
KoPathPointIndex index2(0,5);
KoPathPointData pd1(&path1, index1);
KoPathPointData pd2(&path1, index2);
KoPathPoint * p1 = path1.pointByIndex(index1);
KoPathPoint * p2 = path1.pointByIndex(index2);
QVERIFY(!path1.isClosedSubpath(0));
QCOMPARE(path1.subpathPointCount(0), 6);
QCOMPARE(p1->point(), QPointF(40,0));
QCOMPARE(p2->point(), QPointF(20,0));
KoPathPointMergeCommand cmd1(pd1,pd2);
cmd1.redo();
QVERIFY(path1.isClosedSubpath(0));
QCOMPARE(path1.subpathPointCount(0), 5);
QCOMPARE(p2->point(), QPointF(20,0));
cmd1.undo();
QVERIFY(!path1.isClosedSubpath(0));
QCOMPARE(path1.subpathPointCount(0), 6);
QCOMPARE(p1->point(), QPointF(40,0));
QCOMPARE(p2->point(), QPointF(20,0));
KoPathPointMergeCommand cmd2(pd2,pd1);
cmd2.redo();
QVERIFY(path1.isClosedSubpath(0));
QCOMPARE(path1.subpathPointCount(0), 5);
QCOMPARE(p2->point(), QPointF(20,0));
cmd2.undo();
QVERIFY(!path1.isClosedSubpath(0));
QCOMPARE(path1.subpathPointCount(0), 6);
QCOMPARE(p1->point(), QPointF(40,0));
QCOMPARE(p2->point(), QPointF(20,0));
}
void TestPointMergeCommand::closeSingleCurvePath()
{
KoPathShape path1;
path1.moveTo(QPointF(40, 0));
path1.curveTo(QPointF(60, 0), QPointF(60,0), QPointF(60,60));
path1.lineTo(QPointF(0, 60));
path1.curveTo(QPointF(0, 0), QPointF(0,0), QPointF(20,0));
KoPathPointIndex index1(0,0);
KoPathPointIndex index2(0,3);
KoPathPointData pd1(&path1, index1);
KoPathPointData pd2(&path1, index2);
KoPathPoint * p1 = path1.pointByIndex(index1);
KoPathPoint * p2 = path1.pointByIndex(index2);
QVERIFY(!path1.isClosedSubpath(0));
QCOMPARE(path1.subpathPointCount(0), 4);
QCOMPARE(p1->point(), QPointF(40,0));
QVERIFY(!p1->activeControlPoint1());
QCOMPARE(p2->point(), QPointF(20,0));
QVERIFY(!p2->activeControlPoint2());
KoPathPointMergeCommand cmd1(pd1,pd2);
cmd1.redo();
QVERIFY(path1.isClosedSubpath(0));
QCOMPARE(path1.subpathPointCount(0), 3);
QCOMPARE(p2->point(), QPointF(20,0));
QVERIFY(p2->activeControlPoint1());
QVERIFY(!p2->activeControlPoint2());
cmd1.undo();
QVERIFY(!path1.isClosedSubpath(0));
QCOMPARE(path1.subpathPointCount(0), 4);
QCOMPARE(p1->point(), QPointF(40,0));
QVERIFY(!p1->activeControlPoint1());
QCOMPARE(p2->point(), QPointF(20,0));
QVERIFY(!p2->activeControlPoint2());
KoPathPointMergeCommand cmd2(pd2,pd1);
cmd2.redo();
QVERIFY(path1.isClosedSubpath(0));
QCOMPARE(path1.subpathPointCount(0), 3);
QCOMPARE(p2->point(), QPointF(20,0));
QVERIFY(p2->activeControlPoint1());
QVERIFY(!p2->activeControlPoint2());
cmd2.undo();
QVERIFY(!path1.isClosedSubpath(0));
QCOMPARE(path1.subpathPointCount(0), 4);
QCOMPARE(p1->point(), QPointF(40,0));
QVERIFY(!p1->activeControlPoint1());
QCOMPARE(p2->point(), QPointF(20,0));
QVERIFY(!p2->activeControlPoint2());
}
void TestPointMergeCommand::connectLineSubpaths()
{
KoPathShape path1;
path1.moveTo(QPointF(0,0));
path1.lineTo(QPointF(10,0));
path1.moveTo(QPointF(20,0));
path1.lineTo(QPointF(30,0));
KoPathPointIndex index1(0,1);
KoPathPointIndex index2(1,0);
KoPathPointData pd1(&path1, index1);
KoPathPointData pd2(&path1, index2);
QCOMPARE(path1.subpathCount(), 2);
QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0));
QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0));
KoPathPointMergeCommand cmd1(pd1, pd2);
cmd1.redo();
QCOMPARE(path1.subpathCount(), 1);
QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(15,0));
cmd1.undo();
QCOMPARE(path1.subpathCount(), 2);
QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0));
QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0));
KoPathPointMergeCommand cmd2(pd2, pd1);
cmd2.redo();
QCOMPARE(path1.subpathCount(), 1);
QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(15,0));
cmd2.undo();
QCOMPARE(path1.subpathCount(), 2);
QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(10,0));
QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(20,0));
}
void TestPointMergeCommand::connectCurveSubpaths()
{
KoPathShape path1;
path1.moveTo(QPointF(0,0));
path1.curveTo(QPointF(20,0),QPointF(0,20),QPointF(20,20));
path1.moveTo(QPointF(50,0));
path1.curveTo(QPointF(30,0), QPointF(50,20), QPointF(30,20));
KoPathPointIndex index1(0,1);
KoPathPointIndex index2(1,1);
KoPathPointData pd1(&path1, index1);
KoPathPointData pd2(&path1, index2);
QCOMPARE(path1.subpathCount(), 2);
QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20));
QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20));
QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20));
QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20));
QVERIFY(path1.pointByIndex(index1)->activeControlPoint1());
QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2());
KoPathPointMergeCommand cmd1(pd1, pd2);
cmd1.redo();
QCOMPARE(path1.subpathCount(), 1);
QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(25,20));
QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(5,20));
QCOMPARE(path1.pointByIndex(index1)->controlPoint2(), QPointF(45,20));
QVERIFY(path1.pointByIndex(index1)->activeControlPoint1());
QVERIFY(path1.pointByIndex(index1)->activeControlPoint2());
cmd1.undo();
QCOMPARE(path1.subpathCount(), 2);
QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20));
QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20));
QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20));
QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20));
QVERIFY(path1.pointByIndex(index1)->activeControlPoint1());
QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2());
KoPathPointMergeCommand cmd2(pd2, pd1);
cmd2.redo();
QCOMPARE(path1.subpathCount(), 1);
QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(25,20));
QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(5,20));
QCOMPARE(path1.pointByIndex(index1)->controlPoint2(), QPointF(45,20));
QVERIFY(path1.pointByIndex(index1)->activeControlPoint1());
QVERIFY(path1.pointByIndex(index1)->activeControlPoint2());
cmd2.undo();
QCOMPARE(path1.subpathCount(), 2);
QCOMPARE(path1.pointByIndex(index1)->point(), QPointF(20,20));
QCOMPARE(path1.pointByIndex(index1)->controlPoint1(), QPointF(0,20));
QCOMPARE(path1.pointByIndex(index2)->point(), QPointF(30,20));
QCOMPARE(path1.pointByIndex(index2)->controlPoint1(), QPointF(50,20));
QVERIFY(path1.pointByIndex(index1)->activeControlPoint1());
QVERIFY(!path1.pointByIndex(index1)->activeControlPoint2());
}
#include <MockShapes.h>
#include <commands/KoPathCombineCommand.h>
#include "kis_debug.h"
void TestPointMergeCommand::testCombineShapes()
{
MockShapeController mockController;
MockCanvas canvas(&mockController);
QList<KoPathShape*> shapesToCombine;
for (int i = 0; i < 3; i++) {
const QPointF step(15,15);
const QRectF rect = QRectF(5,5,10,10).translated(step * i);
QPainterPath p;
p.addRect(rect);
KoPathShape *shape = KoPathShape::createShapeFromPainterPath(p);
QCOMPARE(shape->absoluteOutlineRect(), rect);
shapesToCombine << shape;
mockController.addShape(shape);
}
KoPathCombineCommand cmd(&mockController, shapesToCombine);
cmd.redo();
QCOMPARE(canvas.shapeManager()->shapes().size(), 1);
KoPathShape *combinedShape = dynamic_cast<KoPathShape*>(canvas.shapeManager()->shapes().first());
QCOMPARE(combinedShape, cmd.combinedPath());
QCOMPARE(combinedShape->subpathCount(), 3);
QCOMPARE(combinedShape->absoluteOutlineRect(), QRectF(5,5,40,40));
QList<KoPathPointData> tstPoints;
QList<KoPathPointData> expPoints;
tstPoints << KoPathPointData(shapesToCombine[0], KoPathPointIndex(0,1));
expPoints << KoPathPointData(combinedShape, KoPathPointIndex(0,1));
tstPoints << KoPathPointData(shapesToCombine[1], KoPathPointIndex(0,2));
expPoints << KoPathPointData(combinedShape, KoPathPointIndex(1,2));
tstPoints << KoPathPointData(shapesToCombine[2], KoPathPointIndex(0,3));
expPoints << KoPathPointData(combinedShape, KoPathPointIndex(2,3));
for (int i = 0; i < tstPoints.size(); i++) {
KoPathPointData convertedPoint = cmd.originalToCombined(tstPoints[i]);
QCOMPARE(convertedPoint, expPoints[i]);
}
Q_FOREACH (KoShape *shape, canvas.shapeManager()->shapes()) {
mockController.removeShape(shape);
shape->setParent(0);
delete shape;
}
// 'shapesToCombine' will be deleted by KoPathCombineCommand
}
#include <commands/KoMultiPathPointMergeCommand.h>
#include <commands/KoMultiPathPointJoinCommand.h>
#include <KoSelection.h>
#include "kis_algebra_2d.h"
inline QPointF fetchPoint(KoPathShape *shape, int subpath, int pointIndex) {
- return shape->absoluteTransformation(0).map(
+ return shape->absoluteTransformation().map(
shape->pointByIndex(KoPathPointIndex(subpath, pointIndex))->point());
}
void dumpShape(KoPathShape *shape, const QString &fileName)
{
QImage tmp(50,50, QImage::Format_ARGB32);
tmp.fill(0);
QPainter p(&tmp);
- p.drawPath(shape->absoluteTransformation(0).map(shape->outline()));
+ p.drawPath(shape->absoluteTransformation().map(shape->outline()));
tmp.save(fileName);
}
template <class MergeCommand = KoMultiPathPointMergeCommand>
void testMultipathMergeShapesImpl(const int srcPointIndex1,
const int srcPointIndex2,
const QList<QPointF> &expectedResultPoints,
bool singleShape = false)
{
MockShapeController mockController;
MockCanvas canvas(&mockController);
QList<KoPathShape*> shapes;
for (int i = 0; i < 3; i++) {
const QPointF step(15,15);
const QRectF rect = QRectF(5,5,10,10).translated(step * i);
QPainterPath p;
p.moveTo(rect.topLeft());
p.lineTo(rect.bottomRight());
p.lineTo(rect.topRight());
KoPathShape *shape = KoPathShape::createShapeFromPainterPath(p);
QCOMPARE(shape->absoluteOutlineRect(), rect);
shapes << shape;
mockController.addShape(shape);
}
{
KoPathPointData pd1(shapes[0], KoPathPointIndex(0,srcPointIndex1));
KoPathPointData pd2(shapes[singleShape ? 0 : 1], KoPathPointIndex(0,srcPointIndex2));
MergeCommand cmd(pd1, pd2, &mockController, canvas.shapeManager()->selection());
cmd.redo();
const int expectedShapesCount = singleShape ? 3 : 2;
QCOMPARE(canvas.shapeManager()->shapes().size(), expectedShapesCount);
KoPathShape *combinedShape = 0;
if (!singleShape) {
combinedShape = dynamic_cast<KoPathShape*>(canvas.shapeManager()->shapes()[1]);
QCOMPARE(combinedShape, cmd.testingCombinedPath());
} else {
combinedShape = dynamic_cast<KoPathShape*>(canvas.shapeManager()->shapes()[0]);
QCOMPARE(combinedShape, shapes[0]);
}
QCOMPARE(combinedShape->subpathCount(), 1);
QRectF expectedOutlineRect;
KisAlgebra2D::accumulateBounds(expectedResultPoints, &expectedOutlineRect);
QVERIFY(KisAlgebra2D::fuzzyCompareRects(combinedShape->absoluteOutlineRect(), expectedOutlineRect, 0.01));
if (singleShape) {
QCOMPARE(combinedShape->isClosedSubpath(0), true);
}
QCOMPARE(combinedShape->subpathPointCount(0), expectedResultPoints.size());
for (int i = 0; i < expectedResultPoints.size(); i++) {
if (fetchPoint(combinedShape, 0, i) != expectedResultPoints[i]) {
qDebug() << ppVar(i);
qDebug() << ppVar(fetchPoint(combinedShape, 0, i));
qDebug() << ppVar(expectedResultPoints[i]);
QFAIL("Resulting shape points are different!");
}
}
QList<KoShape*> shapes = canvas.shapeManager()->selection()->selectedEditableShapes();
QCOMPARE(shapes.size(), 1);
QCOMPARE(shapes.first(), combinedShape);
//dumpShape(combinedShape, "tmp_0_seq.png");
cmd.undo();
QCOMPARE(canvas.shapeManager()->shapes().size(), 3);
}
Q_FOREACH (KoShape *shape, canvas.shapeManager()->shapes()) {
mockController.removeShape(shape);
shape->setParent(0);
delete shape;
}
// combined shapes will be deleted by the corresponding commands
}
void TestPointMergeCommand::testMultipathMergeShapesBothSequential()
{
// both sequential
testMultipathMergeShapesImpl(2, 0,
{
QPointF(5,5),
QPointF(15,15),
QPointF(17.5,12.5), // merged by melding the points!
QPointF(30,30),
QPointF(30,20)
});
}
void TestPointMergeCommand::testMultipathMergeShapesFirstReversed()
{
// first reversed
testMultipathMergeShapesImpl(0, 0,
{
QPointF(15,5),
QPointF(15,15),
QPointF(12.5,12.5), // merged by melding the points!
QPointF(30,30),
QPointF(30,20)
});
}
void TestPointMergeCommand::testMultipathMergeShapesSecondReversed()
{
// second reversed
testMultipathMergeShapesImpl(2, 2,
{
QPointF(5,5),
QPointF(15,15),
QPointF(22.5,12.5), // merged by melding the points!
QPointF(30,30),
QPointF(20,20)
});
}
void TestPointMergeCommand::testMultipathMergeShapesBothReversed()
{
// both reversed
testMultipathMergeShapesImpl(0, 2,
{
QPointF(15,5),
QPointF(15,15),
QPointF(17.5,12.5), // merged by melding the points!
QPointF(30,30),
QPointF(20,20)
});
}
void TestPointMergeCommand::testMultipathMergeShapesSingleShapeEndToStart()
{
// close end->start
testMultipathMergeShapesImpl(2, 0,
{
QPointF(10,5),
QPointF(15,15)
}, true);
}
void TestPointMergeCommand::testMultipathMergeShapesSingleShapeStartToEnd()
{
// close start->end
testMultipathMergeShapesImpl(0, 2,
{
QPointF(10,5),
QPointF(15,15)
}, true);
}
void TestPointMergeCommand::testMultipathJoinShapesBothSequential()
{
// both sequential
testMultipathMergeShapesImpl<KoMultiPathPointJoinCommand>
(2, 0,
{
QPointF(5,5),
QPointF(15,15),
QPointF(15,5),
QPointF(20,20),
QPointF(30,30),
QPointF(30,20)
});
}
void TestPointMergeCommand::testMultipathJoinShapesFirstReversed()
{
// first reversed
testMultipathMergeShapesImpl<KoMultiPathPointJoinCommand>
(0, 0,
{
QPointF(15,5),
QPointF(15,15),
QPointF(5,5),
QPointF(20,20),
QPointF(30,30),
QPointF(30,20)
});
}
void TestPointMergeCommand::testMultipathJoinShapesSecondReversed()
{
// second reversed
testMultipathMergeShapesImpl<KoMultiPathPointJoinCommand>
(2, 2,
{
QPointF(5,5),
QPointF(15,15),
QPointF(15,5),
QPointF(30,20),
QPointF(30,30),
QPointF(20,20)
});
}
void TestPointMergeCommand::testMultipathJoinShapesBothReversed()
{
// both reversed
testMultipathMergeShapesImpl<KoMultiPathPointJoinCommand>
(0, 2,
{
QPointF(15,5),
QPointF(15,15),
QPointF(5,5),
QPointF(30,20),
QPointF(30,30),
QPointF(20,20)
});
}
void TestPointMergeCommand::testMultipathJoinShapesSingleShapeEndToStart()
{
// close end->start
testMultipathMergeShapesImpl<KoMultiPathPointJoinCommand>
(2, 0,
{
QPointF(5,5),
QPointF(15,15),
QPointF(15,5)
}, true);
}
void TestPointMergeCommand::testMultipathJoinShapesSingleShapeStartToEnd()
{
// close start->end
testMultipathMergeShapesImpl<KoMultiPathPointJoinCommand>
(0, 2,
{
QPointF(5,5),
QPointF(15,15),
QPointF(15,5)
}, true);
}
KISTEST_MAIN(TestPointMergeCommand)
diff --git a/libs/flake/tests/TestShapeBackgroundCommand.cpp b/libs/flake/tests/TestShapeBackgroundCommand.cpp
index a3a44cd46d..46f6928a98 100644
--- a/libs/flake/tests/TestShapeBackgroundCommand.cpp
+++ b/libs/flake/tests/TestShapeBackgroundCommand.cpp
@@ -1,72 +1,70 @@
/* This file is part of the KDE project
* Copyright (C) 2008 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 "TestShapeBackgroundCommand.h"
#include <MockShapes.h>
#include "KoShapeBackgroundCommand.h"
#include "KoColorBackground.h"
#include "KoShapePaintingContext.h"
-#include "KoViewConverter.h"
#include <QTest>
void TestShapeBackgroundCommand::refCounting()
{
MockShape * shape1 = new MockShape();
QSharedPointer<KoShapeBackground> whiteFill(new KoColorBackground(QColor(Qt::white)));
QSharedPointer<KoShapeBackground> blackFill(new KoColorBackground(QColor(Qt::black)));
QSharedPointer<KoShapeBackground> redFill (new KoColorBackground(QColor(Qt::red)));
shape1->setBackground(whiteFill);
QVERIFY(shape1->background() == whiteFill);
// old fill is white, new fill is black
KUndo2Command *cmd1 = new KoShapeBackgroundCommand(shape1, blackFill);
cmd1->redo();
QVERIFY(shape1->background() == blackFill);
// change fill back to white fill
cmd1->undo();
QVERIFY(shape1->background() == whiteFill);
// old fill is white, new fill is red
KUndo2Command *cmd2 = new KoShapeBackgroundCommand(shape1, redFill);
cmd2->redo();
QVERIFY(shape1->background() == redFill);
// this command has the white fill as the old fill
delete cmd1;
// set fill back to white fill
cmd2->undo();
QVERIFY(shape1->background() == whiteFill);
// if white is deleted when deleting cmd1 this will crash
QPainter p;
QPainterPath path;
path.addRect( QRectF(0,0,100,100) );
- KoViewConverter converter;
KoShapePaintingContext context;
- whiteFill->paint( p, converter, context, path );
+ whiteFill->paint( p, context, path );
delete cmd2;
delete shape1;
}
QTEST_MAIN(TestShapeBackgroundCommand)
diff --git a/libs/flake/tests/TestShapeContainer.cpp b/libs/flake/tests/TestShapeContainer.cpp
index facaf89706..4ada3a4b79 100644
--- a/libs/flake/tests/TestShapeContainer.cpp
+++ b/libs/flake/tests/TestShapeContainer.cpp
@@ -1,225 +1,225 @@
/*
* This file is part of Calligra tests
*
* Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 Adam Celarek <kdedev@xibo.at>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "TestShapeContainer.h"
#include <MockShapes.h>
#include <KoShape.h>
#include <KoShapeGroupCommand.h>
#include <KoShapeUngroupCommand.h>
#include <KoShapeTransformCommand.h>
#include <KoShapeGroup.h>
#include <KoSelection.h>
#include <QTest>
void TestShapeContainer::testModel()
{
MockContainerModel *model = new MockContainerModel();
MockContainer container(model);
MockShape *shape1 = new MockShape();
container.addShape(shape1);
QCOMPARE(model->containerChangedCalled(), 0);
QCOMPARE(model->childChangedCalled(), 1);
QCOMPARE(model->proposeMoveCalled(), 0);
shape1->setPosition(QPointF(300, 300));
QCOMPARE(model->containerChangedCalled(), 0);
QCOMPARE(model->childChangedCalled(), 2);
QCOMPARE(model->proposeMoveCalled(), 0);
shape1->rotate(10);
QCOMPARE(model->containerChangedCalled(), 0);
QCOMPARE(model->childChangedCalled(), 3);
QCOMPARE(model->proposeMoveCalled(), 0);
shape1->setAbsolutePosition(shape1->absolutePosition() + QPointF(10., 40.));
QCOMPARE(model->containerChangedCalled(), 0);
QCOMPARE(model->childChangedCalled(), 5); // we get a generic Matrix as well as a position change...
QCOMPARE(model->proposeMoveCalled(), 0);
shape1->setTransformation(QTransform());
QCOMPARE(model->containerChangedCalled(), 0);
QCOMPARE(model->childChangedCalled(), 6);
QCOMPARE(model->proposeMoveCalled(), 0);
model->resetCounts();
container.setPosition(QPointF(30, 30));
QCOMPARE(model->containerChangedCalled(), 1);
QCOMPARE(model->childChangedCalled(), 0);
QCOMPARE(model->proposeMoveCalled(), 0);
}
void TestShapeContainer::testSetParent()
{
MockContainerModel *model1 = new MockContainerModel();
MockContainer container1(model1);
MockContainerModel *model2 = new MockContainerModel();
MockContainer container2(model2);
MockShape shape;
// init test
QCOMPARE(model1->shapes().count(), 0);
QCOMPARE(model2->shapes().count(), 0);
shape.setParent(&container1);
QCOMPARE(model1->shapes().count(), 1);
QCOMPARE(model2->shapes().count(), 0);
QCOMPARE(shape.parent(), &container1);
shape.setParent(&container2);
QCOMPARE(model1->shapes().count(), 0);
QCOMPARE(container1.shapes().count(), 0);
QCOMPARE(model2->shapes().count(), 1);
QCOMPARE(container2.shapes().count(), 1);
QCOMPARE(shape.parent(), &container2);
// on destruction shape should have no parent
shape.setParent(0);
}
void TestShapeContainer::testSetParent2()
{
MockContainerModel *model = new MockContainerModel();
MockContainer container(model);
MockShape *shape = new MockShape();
shape->setParent(&container);
QCOMPARE(model->shapes().count(), 1);
shape->setParent(0);
QCOMPARE(model->shapes().count(), 0);
}
void TestShapeContainer::testScaling()
{
KoShape *shape1 = new MockShape();
KoShape *shape2 = new MockShape();
shape1->setSize(QSizeF(10., 10.));
shape1->setPosition(QPointF(20., 20.));
shape2->setSize(QSizeF(30., 10.));
shape2->setPosition(QPointF(10., 40.));
QList<KoShape*> groupedShapes;
groupedShapes.append(shape1);
groupedShapes.append(shape2);
KoShapeGroup *group = new KoShapeGroup();
KoShapeGroupCommand* groupCommand = KoShapeGroupCommand::createCommand(group, groupedShapes);
groupCommand->redo();
QList<KoShape*> transformShapes;
transformShapes.append(groupedShapes);
QTransform matrix;
matrix.scale(0.5, 0.5);
QList<QTransform> oldTransformations;
QList<QTransform> newTransformations;
Q_FOREACH (const KoShape* shape, transformShapes) {
QTransform oldTransform = shape->transformation();
oldTransformations.append(oldTransform);
- QTransform globalTransform = shape->absoluteTransformation(0);
+ QTransform globalTransform = shape->absoluteTransformation();
QTransform localTransform = globalTransform * matrix * globalTransform.inverted();
newTransformations.append(localTransform*oldTransform);
}
QList<QPointF> oldPositions;
for(int i=0; i< transformShapes.size(); i++) {
oldPositions.append(transformShapes.at(i)->absolutePosition(KoFlake::TopLeft));
}
KoShapeTransformCommand* transformCommand;
transformCommand = new KoShapeTransformCommand(transformShapes, oldTransformations, newTransformations);
transformCommand->redo();
for(int i=0; i< transformShapes.size(); i++) {
QCOMPARE(transformShapes.at(i)->absolutePosition(KoFlake::TopLeft), oldPositions.at(i)*0.5);
}
transformShapes.takeLast();
KoShapeUngroupCommand* ungroupCmd = new KoShapeUngroupCommand(group, transformShapes);
ungroupCmd->redo();
for(int i=0; i< transformShapes.size(); i++) {
QCOMPARE(transformShapes.at(i)->absolutePosition(KoFlake::TopLeft), oldPositions.at(i)*0.5);
}
}
void TestShapeContainer::testScaling2()
{
KoShape *shape1 = new MockShape();
KoShape *shape2 = new MockShape();
shape1->setPosition(QPointF(20., 20.));
shape1->setSize(QSizeF(10., 10.));
shape2->setPosition(QPointF(10., 40.));
shape2->setSize(QSizeF(30., 10.));
QList<KoShape*> groupedShapes;
groupedShapes.append(shape1);
groupedShapes.append(shape2);
QScopedPointer<KoShapeGroup> group(new KoShapeGroup());
QScopedPointer<KoShapeGroupCommand> groupCommand(
KoShapeGroupCommand::createCommand(group.data(), groupedShapes));
groupCommand->redo();
QScopedPointer<KoSelection> selection(new KoSelection());
// the topmost shape is selected, not shape1!
selection->select(shape1);
QList<KoShape*> transformShapes;
transformShapes.append(selection->selectedShapes());
QTransform matrix;
matrix.scale(0.5, 0.5);
QList<QTransform> oldTransformations;
QList<QTransform> newTransformations;
Q_FOREACH (const KoShape* shape, transformShapes) {
QTransform oldTransform = shape->transformation();
oldTransformations.append(oldTransform);
newTransformations.append(oldTransform*matrix);
}
QList<QPointF> oldPositions;
for(int i=0; i< transformShapes.size(); i++) {
oldPositions.append(transformShapes.at(i)->absolutePosition(KoFlake::TopLeft));
}
QScopedPointer<KoShapeTransformCommand> transformCommand(
new KoShapeTransformCommand(transformShapes, oldTransformations, newTransformations));
transformCommand->redo();
QCOMPARE(selection->boundingRect(), group->boundingRect());
selection->deselectAll();
// the topmost shape is selected, not shape1!
selection->select(shape1);
QCOMPARE(selection->boundingRect(), group->boundingRect());
}
QTEST_MAIN(TestShapeContainer)
diff --git a/libs/flake/tests/TestShapePainting.cpp b/libs/flake/tests/TestShapePainting.cpp
index 8faff819d1..e5bfb25157 100644
--- a/libs/flake/tests/TestShapePainting.cpp
+++ b/libs/flake/tests/TestShapePainting.cpp
@@ -1,322 +1,317 @@
/*
* This file is part of Calligra tests
*
* Copyright (C) 2006-2010 Thomas Zander <zander@kde.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 "TestShapePainting.h"
#include <QtGui>
#include "KoShapeContainer.h"
#include "KoShapeManager.h"
#include "KoShapePaintingContext.h"
-#include "KoViewConverter.h"
#include <sdk/tests/kistest.h>
#include <MockShapes.h>
#include <QTest>
void TestShapePainting::testPaintShape()
{
MockShape *shape1 = new MockShape();
MockShape *shape2 = new MockShape();
QScopedPointer<MockContainer> container(new MockContainer());
container->addShape(shape1);
container->addShape(shape2);
QCOMPARE(shape1->parent(), container.data());
QCOMPARE(shape2->parent(), container.data());
container->setClipped(shape1, false);
container->setClipped(shape2, false);
QCOMPARE(container->isClipped(shape1), false);
QCOMPARE(container->isClipped(shape2), false);
MockCanvas canvas;
KoShapeManager manager(&canvas);
manager.addShape(container.data());
QCOMPARE(manager.shapes().count(), 3);
QImage image(100, 100, QImage::Format_Mono);
QPainter painter(&image);
- KoViewConverter vc;
- manager.paint(painter, vc, false);
+ manager.paint(painter, false);
// with the shape not being clipped, the shapeManager will paint it for us.
QCOMPARE(shape1->paintedCount, 1);
QCOMPARE(shape2->paintedCount, 1);
QCOMPARE(container->paintedCount, 1);
// the container should thus not paint the shape
shape1->paintedCount = 0;
shape2->paintedCount = 0;
container->paintedCount = 0;
KoShapePaintingContext paintContext;
- container->paint(painter, vc, paintContext);
+ container->paint(painter, paintContext);
QCOMPARE(shape1->paintedCount, 0);
QCOMPARE(shape2->paintedCount, 0);
QCOMPARE(container->paintedCount, 1);
container->setClipped(shape1, false);
container->setClipped(shape2, true);
QCOMPARE(container->isClipped(shape1), false);
QCOMPARE(container->isClipped(shape2), true);
shape1->paintedCount = 0;
shape2->paintedCount = 0;
container->paintedCount = 0;
- manager.paint(painter, vc, false);
+ manager.paint(painter, false);
// with this shape not being clipped, the shapeManager will paint the container and this shape
QCOMPARE(shape1->paintedCount, 1);
// with this shape being clipped, the container will paint it for us.
QCOMPARE(shape2->paintedCount, 1);
QCOMPARE(container->paintedCount, 1);
}
void TestShapePainting::testPaintHiddenShape()
{
QScopedPointer<MockContainer> top(new MockContainer());
MockShape *shape = new MockShape();
MockContainer *fourth = new MockContainer();
MockContainer *thirth = new MockContainer();
MockContainer *second = new MockContainer();
top->addShape(second);
second->addShape(thirth);
thirth->addShape(fourth);
fourth->addShape(shape);
second->setVisible(false);
MockCanvas canvas;
KoShapeManager manager(&canvas);
manager.addShape(top.data());
QCOMPARE(manager.shapes().count(), 5);
QImage image(100, 100, QImage::Format_Mono);
QPainter painter(&image);
- KoViewConverter vc;
- manager.paint(painter, vc, false);
+ manager.paint(painter, false);
QCOMPARE(top->paintedCount, 1);
QCOMPARE(second->paintedCount, 0);
QCOMPARE(thirth->paintedCount, 0);
QCOMPARE(fourth->paintedCount, 0);
QCOMPARE(shape->paintedCount, 0);
}
void TestShapePainting::testPaintOrder()
{
// the stacking order determines the painting order so things on top
// get their paint called last.
// Each shape has a zIndex and within the children a container has
// it determines the stacking order. Its important to realize that
// the zIndex is thus local to a container, if you have layer1 and layer2
// with both various child shapes the stacking order of the layer shapes
// is most important, then within this the child shape index is used.
class OrderedMockShape : public MockShape {
public:
- OrderedMockShape(QList<MockShape*> &list) : order(list) {}
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override {
- order.append(this);
- MockShape::paint(painter, converter, paintcontext);
+ OrderedMockShape(QList<const MockShape*> *list) : order(list) {}
+ void paint(QPainter &painter, KoShapePaintingContext &paintcontext) const override {
+ order->append(this);
+ MockShape::paint(painter, paintcontext);
}
- QList<MockShape*> &order;
+ mutable QList<const MockShape*> *order;
};
- QList<MockShape*> order;
+ QList<const MockShape*> order;
{
QScopedPointer<MockContainer> top(new MockContainer());
top->setZIndex(2);
- OrderedMockShape *shape1 = new OrderedMockShape(order);
+ OrderedMockShape *shape1 = new OrderedMockShape(&order);
shape1->setZIndex(5);
- OrderedMockShape *shape2 = new OrderedMockShape(order);
+ OrderedMockShape *shape2 = new OrderedMockShape(&order);
shape2->setZIndex(0);
top->addShape(shape1);
top->addShape(shape2);
QScopedPointer<MockContainer> bottom(new MockContainer());
bottom->setZIndex(1);
- OrderedMockShape *shape3 = new OrderedMockShape(order);
+ OrderedMockShape *shape3 = new OrderedMockShape(&order);
shape3->setZIndex(-1);
- OrderedMockShape *shape4 = new OrderedMockShape(order);
+ OrderedMockShape *shape4 = new OrderedMockShape(&order);
shape4->setZIndex(9);
bottom->addShape(shape3);
bottom->addShape(shape4);
MockCanvas canvas;
KoShapeManager manager(&canvas);
manager.addShape(top.data());
manager.addShape(bottom.data());
QCOMPARE(manager.shapes().count(), 6);
QImage image(100, 100, QImage::Format_Mono);
QPainter painter(&image);
- KoViewConverter vc;
- manager.paint(painter, vc, false);
+ manager.paint(painter, false);
QCOMPARE(top->paintedCount, 1);
QCOMPARE(bottom->paintedCount, 1);
QCOMPARE(shape1->paintedCount, 1);
QCOMPARE(shape2->paintedCount, 1);
QCOMPARE(shape3->paintedCount, 1);
QCOMPARE(shape4->paintedCount, 1);
QCOMPARE(order.count(), 4);
QVERIFY(order[0] == shape3); // lowest first
QVERIFY(order[1] == shape4);
QVERIFY(order[2] == shape2);
QVERIFY(order[3] == shape1);
// again, with clipping.
order.clear();
painter.setClipRect(0, 0, 100, 100);
- manager.paint(painter, vc, false);
+ manager.paint(painter, false);
QCOMPARE(top->paintedCount, 2);
QCOMPARE(bottom->paintedCount, 2);
QCOMPARE(shape1->paintedCount, 2);
QCOMPARE(shape2->paintedCount, 2);
QCOMPARE(shape3->paintedCount, 2);
QCOMPARE(shape4->paintedCount, 2);
QCOMPARE(order.count(), 4);
QVERIFY(order[0] == shape3); // lowest first
QVERIFY(order[1] == shape4);
QVERIFY(order[2] == shape2);
QVERIFY(order[3] == shape1);
}
order.clear();
{
QScopedPointer<MockContainer> root(new MockContainer());
root->setZIndex(0);
MockContainer *branch1 = new MockContainer();
branch1->setZIndex(1);
- OrderedMockShape *child1_1 = new OrderedMockShape(order);
+ OrderedMockShape *child1_1 = new OrderedMockShape(&order);
child1_1->setZIndex(1);
- OrderedMockShape *child1_2 = new OrderedMockShape(order);
+ OrderedMockShape *child1_2 = new OrderedMockShape(&order);
child1_2->setZIndex(2);
branch1->addShape(child1_1);
branch1->addShape(child1_2);
MockContainer *branch2 = new MockContainer();
branch2->setZIndex(2);
- OrderedMockShape *child2_1 = new OrderedMockShape(order);
+ OrderedMockShape *child2_1 = new OrderedMockShape(&order);
child2_1->setZIndex(1);
- OrderedMockShape *child2_2 = new OrderedMockShape(order);
+ OrderedMockShape *child2_2 = new OrderedMockShape(&order);
child2_2->setZIndex(2);
branch2->addShape(child2_1);
branch2->addShape(child2_2);
root->addShape(branch1);
root->addShape(branch2);
QList<KoShape*> sortedShapes;
sortedShapes.append(root.data());
sortedShapes.append(branch1);
sortedShapes.append(branch2);
sortedShapes.append(branch1->shapes());
sortedShapes.append(branch2->shapes());
std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(sortedShapes.count(), 7);
QVERIFY(sortedShapes[0] == root.data());
QVERIFY(sortedShapes[1] == branch1);
QVERIFY(sortedShapes[2] == child1_1);
QVERIFY(sortedShapes[3] == child1_2);
QVERIFY(sortedShapes[4] == branch2);
QVERIFY(sortedShapes[5] == child2_1);
QVERIFY(sortedShapes[6] == child2_2);
}
}
#include <kundo2command.h>
#include <KoShapeController.h>
#include <KoShapeGroupCommand.h>
#include <KoShapeUngroupCommand.h>
#include "kis_debug.h"
void TestShapePainting::testGroupUngroup()
{
QScopedPointer<MockContainer> shapesFakeLayer(new MockContainer);
MockShape *shape1(new MockShape());
MockShape *shape2(new MockShape());
shape1->setName("shape1");
shape2->setName("shape2");
shape1->setParent(shapesFakeLayer.data());
shape2->setParent(shapesFakeLayer.data());
QList<KoShape*> groupedShapes = {shape1, shape2};
MockShapeController controller;
MockCanvas canvas(&controller);
KoShapeManager *manager = canvas.shapeManager();
controller.addShape(shape1);
controller.addShape(shape2);
QImage image(100, 100, QImage::Format_Mono);
QPainter painter(&image);
painter.setClipRect(image.rect());
- KoViewConverter vc;
for (int i = 0; i < 3; i++) {
KoShapeGroup *group = new KoShapeGroup();
group->setParent(shapesFakeLayer.data());
{
group->setName("group");
KUndo2Command groupingCommand;
canvas.shapeController()->addShapeDirect(group, 0, &groupingCommand);
new KoShapeGroupCommand(group, groupedShapes, true, &groupingCommand);
groupingCommand.redo();
- manager->paint(painter, vc, false);
+ manager->paint(painter, false);
QCOMPARE(shape1->paintedCount, 2 * i + 1);
QCOMPARE(shape2->paintedCount, 2 * i + 1);
QCOMPARE(manager->shapes().size(), 3);
}
{
KUndo2Command ungroupingCommand;
new KoShapeUngroupCommand(group, group->shapes(), QList<KoShape*>(), &ungroupingCommand);
canvas.shapeController()->removeShape(group, &ungroupingCommand);
// NOTE: group will be deleted in ungroupingCommand's d-tor
ungroupingCommand.redo();
- manager->paint(painter, vc, false);
+ manager->paint(painter, false);
QCOMPARE(shape1->paintedCount, 2 * i + 2);
QCOMPARE(shape2->paintedCount, 2 * i + 2);
QCOMPARE(manager->shapes().size(), 2);
}
}
}
KISTEST_MAIN(TestShapePainting)
diff --git a/libs/flake/tests/TestSvgParser.cpp b/libs/flake/tests/TestSvgParser.cpp
index b03ac84435..61b706ef6f 100644
--- a/libs/flake/tests/TestSvgParser.cpp
+++ b/libs/flake/tests/TestSvgParser.cpp
@@ -1,3226 +1,3268 @@
/*
* 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 "TestSvgParser.h"
#include <QTest>
#include <svg/SvgUtil.h>
#include <KoShapeStrokeModel.h>
#include "SvgParserTestingUtils.h"
#include "../../sdk/tests/qimage_test_util.h"
#ifdef USE_ROUND_TRIP
#include "SvgWriter.h"
#include <QBuffer>
#include <QDomDocument>
#endif
void TestSvgParser::testUnitPx()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,10,20), 0.5));
- QCOMPARE(shape->absoluteTransformation(0), QTransform());
+ QCOMPARE(shape->boundingRect(), QRectF(0,0,10,20));
+ QCOMPARE(shape->absoluteTransformation(), QTransform());
QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(10,20));
}
void TestSvgParser::testUnitPxResolution()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,5,10), 0.25));
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(0.5, 0.5));
+ QCOMPARE(shape->boundingRect(), QRectF(0,0,5,10));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromScale(0.5, 0.5));
QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(5,10));
}
void TestSvgParser::testUnitPt()
{
const QString data =
"<svg width=\"10pt\" height=\"20pt\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 666 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,10,20), 0.5));
- QCOMPARE(shape->absoluteTransformation(0), QTransform());
+ QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,10,20), 0));
+ QCOMPARE(shape->absoluteTransformation(), QTransform());
QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(10,20));
}
void TestSvgParser::testUnitIn()
{
const QString data =
"<svg width=\"10in\" height=\"20in\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 666 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,720,1440), 36));
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(72, 72));
+ QCOMPARE(shape->boundingRect(), QRectF(0,0,720,1440));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromScale(72, 72));
QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(720,1440));
}
void TestSvgParser::testUnitPercentInitial()
{
const QString data =
"<svg width=\"12.5%\" height=\"25%\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 80, 80) /* px */, 144 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(0,0,5,10), 0.25));
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromScale(0.5, 0.5));
+ QCOMPARE(shape->boundingRect(), QRectF(0,0,5,10));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromScale(0.5, 0.5));
QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(5,10));
}
void TestSvgParser::testScalingViewport()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"60 70 20 40\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"64\" y=\"74\" width=\"12\" height=\"32\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2));
QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2));
QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18));
}
void TestSvgParser::testScalingViewportKeepMeet1()
{
const QString data =
"<svg width=\"10px\" height=\"30px\" viewBox=\"60 70 20 40\""
" preserveAspectRatio=\" xMinYMin meet\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"64\" y=\"74\" width=\"12\" height=\"32\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2));
QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2));
QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18));
}
void TestSvgParser::testScalingViewportKeepMeet2()
{
const QString data =
"<svg width=\"15px\" height=\"20px\" viewBox=\"60 70 20 40\""
" preserveAspectRatio=\" xMinYMin meet\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"64\" y=\"74\" width=\"12\" height=\"32\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2));
QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2));
QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18));
}
void TestSvgParser::testScalingViewportKeepMeetAlign()
{
const QString data =
"<svg width=\"10px\" height=\"30px\" viewBox=\"60 70 20 40\""
" preserveAspectRatio=\" xMaxYMax meet\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"64\" y=\"74\" width=\"12\" height=\"32\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 24) * QTransform::fromScale(0.5, 0.5));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromTranslate(4, 24) * QTransform::fromScale(0.5, 0.5));
QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,12));
QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,12));
QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,28));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,28));
}
void TestSvgParser::testScalingViewportKeepSlice1()
{
const QString data =
"<svg width=\"5px\" height=\"20px\" viewBox=\"60 70 20 40\""
" preserveAspectRatio=\" xMinYMin slice\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"64\" y=\"74\" width=\"12\" height=\"32\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2));
QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2));
QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18));
}
void TestSvgParser::testScalingViewportKeepSlice2()
{
const QString data =
"<svg width=\"10px\" height=\"15px\" viewBox=\"60 70 20 40\""
" preserveAspectRatio=\" xMinYMin slice\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"64\" y=\"74\" width=\"12\" height=\"32\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2));
QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2));
QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18));
}
void TestSvgParser::testScalingViewportResolution()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"60 70 20 40\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"64\" y=\"74\" width=\"12\" height=\"32\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.25, 0.25));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.25, 0.25));
QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(1,1));
QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(4,1));
QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(1,9));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(4,9));
}
void TestSvgParser::testScalingViewportPercentInternal()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"60 70 20 40\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"320%\" y=\"185%\" width=\"60%\" height=\"80%\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromTranslate(4, 4) * QTransform::fromScale(0.5, 0.5));
QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(2,2));
QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(8,2));
QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(2,18));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(8,18));
}
void TestSvgParser::testParsePreserveAspectRatio()
{
{
SvgUtil::PreserveAspectRatioParser p(" defer xMinYMax meet");
QCOMPARE(p.defer, true);
QCOMPARE(p.mode, Qt::KeepAspectRatio);
QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min);
QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Max);
}
{
SvgUtil::PreserveAspectRatioParser p(" xMinYMid slice");
QCOMPARE(p.defer, false);
QCOMPARE(p.mode, Qt::KeepAspectRatioByExpanding);
QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min);
QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Middle);
}
{
SvgUtil::PreserveAspectRatioParser p(" xmidYMid ");
QCOMPARE(p.defer, false);
QCOMPARE(p.mode, Qt::KeepAspectRatio);
QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Middle);
QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Middle);
}
{
SvgUtil::PreserveAspectRatioParser p(" NoNe ");
QCOMPARE(p.defer, false);
QCOMPARE(p.mode, Qt::IgnoreAspectRatio);
QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min);
QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min);
}
{
SvgUtil::PreserveAspectRatioParser p("defer NoNe ");
QCOMPARE(p.defer, true);
QCOMPARE(p.mode, Qt::IgnoreAspectRatio);
QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min);
QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min);
}
{
SvgUtil::PreserveAspectRatioParser p("sweet brown fox jumps over a nice svg file");
QCOMPARE(p.defer, false);
QCOMPARE(p.mode, Qt::IgnoreAspectRatio);
QCOMPARE(p.xAlignment, SvgUtil::PreserveAspectRatioParser::Min);
QCOMPARE(p.yAlignment, SvgUtil::PreserveAspectRatioParser::Min);
}
}
#include "parsers/SvgTransformParser.h"
void TestSvgParser::testParseTransform()
{
{
QString str("translate(-111.0, 33) translate(-111.0, 33) matrix (1 1 0 0 1, 3), translate(1)"
"scale(0.5) rotate(10) rotate(10, 3 3) skewX(1) skewY(2)");
SvgTransformParser p(str);
QCOMPARE(p.isValid(), true);
}
{
// forget about one brace
QString str("translate(-111.0, 33) translate(-111.0, 33 matrix (1 1 0 0 1, 3), translate(1)"
"scale(0.5) rotate(10) rotate(10, 3 3) skewX(1) skewY(2)");
SvgTransformParser p(str);
QCOMPARE(p.isValid(), false);
}
{
SvgTransformParser p("translate(100, 50)");
QCOMPARE(p.isValid(), true);
QCOMPARE(p.transform(), QTransform::fromTranslate(100, 50));
}
{
SvgTransformParser p("translate(100 50)");
QCOMPARE(p.isValid(), true);
QCOMPARE(p.transform(), QTransform::fromTranslate(100, 50));
}
{
SvgTransformParser p("translate(100)");
QCOMPARE(p.isValid(), true);
QCOMPARE(p.transform(), QTransform::fromTranslate(100, 0));
}
{
SvgTransformParser p("scale(100, 50)");
QCOMPARE(p.isValid(), true);
QCOMPARE(p.transform(), QTransform::fromScale(100, 50));
}
{
SvgTransformParser p("scale(100)");
QCOMPARE(p.isValid(), true);
QCOMPARE(p.transform(), QTransform::fromScale(100, 100));
}
{
SvgTransformParser p("rotate(90 70 74.0)");
QCOMPARE(p.isValid(), true);
QTransform t;
t.rotate(90);
t = QTransform::fromTranslate(-70, -74) * t * QTransform::fromTranslate(70, 74);
qDebug() << ppVar(p.transform());
QCOMPARE(p.transform(), t);
}
}
void TestSvgParser::testScalingViewportTransform()
{
/**
* Note: 'transform' affects all the attributes of the *current*
* element, while 'viewBox' affects only the descendants!
*/
const QString data =
"<svg width=\"5px\" height=\"10px\" viewBox=\"60 70 20 40\""
" transform=\"scale(2)\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"64\" y=\"74\" width=\"12\" height=\"32\""
" transform=\"translate(6)\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->absoluteTransformation(0), QTransform::fromTranslate(10, 4) * QTransform::fromScale(0.5, 0.5));
+ QCOMPARE(shape->absoluteTransformation(), QTransform::fromTranslate(10, 4) * QTransform::fromScale(0.5, 0.5));
QCOMPARE(shape->outlineRect(), QRectF(0,0,12,32));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,2));
QCOMPARE(shape->absolutePosition(KoFlake::TopRight), QPointF(11,2));
QCOMPARE(shape->absolutePosition(KoFlake::BottomLeft), QPointF(5,18));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(11,18));
}
void TestSvgParser::testTransformNesting()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" transform=\"translate(10,10), scale(2, 1)\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->boundingRect(), QRectF(10 - 1,10 - 0.5, 20 + 2, 20 + 1));
+ QCOMPARE(shape->boundingRect(), QRectF(10,10,20,20));
QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(10,10));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(30,30));
}
void TestSvgParser::testTransformNestingGroups()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<g transform=\"translate(10,10)\">"
" <rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" transform=\"scale(2, 1)\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</g>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->boundingRect(), QRectF(10 - 1,10 - 0.5, 20 + 2, 20 + 1));
+ QCOMPARE(shape->boundingRect(), QRectF(10,10, 20, 20));
QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(10,10));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(30,30));
}
void TestSvgParser::testTransformRotation1()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" transform=\"rotate(90)\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(-20,0,20,10), 0.5));
+ QCOMPARE(shape->boundingRect(), QRectF(-20,0,20,10));
QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(0,0));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(-20,10));
}
void TestSvgParser::testTransformRotation2()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" transform=\"rotate(-90 10 5)\""
" fill=\"none\" stroke=\"none\" stroke-width=\"10\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 72 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
- QCOMPARE(shape->boundingRect(), kisGrowRect(QRectF(5,5,20,10), 0.5));
+ QCOMPARE(shape->boundingRect(), QRectF(5,5,20,10));
QCOMPARE(shape->outlineRect(), QRectF(0,0,10,20));
QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,15));
QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(25,5));
}
void TestSvgParser::testRenderStrokeNone()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"none\" stroke-width=\"1\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_none");
}
void TestSvgParser::testRenderStrokeColorName()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"blue\" stroke-width=\"1\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue");
}
void TestSvgParser::testRenderStrokeColorHex3()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"#00f\" stroke-width=\"1\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue");
}
void TestSvgParser::testRenderStrokeColorHex6()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"#0000ff\" stroke-width=\"1\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue");
}
void TestSvgParser::testRenderStrokeColorRgbValues()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"rgb(0, 0 ,255)\" stroke-width=\"1\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue");
}
void TestSvgParser::testRenderStrokeColorRgbPercent()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"rgb(0, 0 ,100%)\" stroke-width=\"1\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue");
}
void TestSvgParser::testRenderStrokeColorCurrent()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<g color=\"blue\">"
" <rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"currentColor\" stroke-width=\"1\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue");
}
void TestSvgParser::testRenderStrokeColorNonexistentIri()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"url(notexists) blue\" stroke-width=\"1\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue");
}
void TestSvgParser::testRenderStrokeWidth()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"blue\" stroke-width=\"2\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue_width_2");
}
void TestSvgParser::testRenderStrokeZeroWidth()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"blue\" stroke-width=\"0\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_none");
}
void TestSvgParser::testRenderStrokeOpacity()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"blue\" stroke-width=\"4\" stroke-opacity=\"0.3\"/>"
"</svg>";
SvgRenderTester t (data);
t.setFuzzyThreshold(1);
t.test_standard_30px_72ppi("stroke_blue_0_3_opacity");
}
void TestSvgParser::testRenderStrokeJointRound()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"blue\" stroke-width=\"4\" stroke-linejoin=\"round\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue_join_round");
}
void TestSvgParser::testRenderStrokeLinecap()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<polyline id=\"testRect\" points=\"5,5 10,25 15,5\""
" fill=\"none\" stroke=\"blue\" stroke-width=\"5\" stroke-linecap=\"round\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue_linecap_round");
}
void TestSvgParser::testRenderStrokeMiterLimit()
{
// TODO:seems like doesn't work!!
qWarning() << "WARNING: Miter limit test is skipped!!!";
return;
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<polyline id=\"testRect\" points=\"5,5 10,25 15,5\""
" fill=\"none\" stroke=\"blue\" stroke-width=\"5\" stroke-miterlimit=\"1.114\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue_miter_limit");
}
void TestSvgParser::testRenderStrokeDashArrayEven()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"blue\" stroke-width=\"2\" stroke-dasharray=\"3 2, 5 2\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue_dasharray_even");
}
void TestSvgParser::testRenderStrokeDashArrayEvenOffset()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"blue\" stroke-width=\"2\" stroke-dasharray=\"3 2, 5 2\""
" stroke-dashoffset=\"1\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue_dasharray_even_offset");
}
void TestSvgParser::testRenderStrokeDashArrayOdd()
{
// SVG 1.1: if the dasharray is odd, repeat it
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"blue\" stroke-width=\"2\" stroke-dasharray=\"3 2, 5\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue_dasharray_odd");
}
void TestSvgParser::testRenderStrokeDashArrayRelative()
{
// SVG 1.1: relative to view box
// (40 x 50) * sqrt(2) => dash length = 5 px
const QString data =
"<svg width=\"42.4264px\" height=\"56.56854px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" stroke=\"blue\" stroke-width=\"2\" stroke-dasharray=\"10% 10%\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue_dasharray_relative");
}
void TestSvgParser::testRenderFillDefault()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_black");
}
void TestSvgParser::testRenderFillRuleNonZero()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<polyline id=\"testRect\" points=\"5,5 15,11 15,19 5,25 5,19 15,5 15,25 5,11 5,5\""
" fill=\"black\" fill-rule=\"nonzero\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_non_zero");
}
void TestSvgParser::testRenderFillRuleEvenOdd()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<polyline id=\"testRect\" points=\"5,5 15,11 15,19 5,25 5,19 15,5 15,25 5,11 5,5\""
" fill=\"black\" fill-rule=\"evenodd\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_even_odd");
}
void TestSvgParser::testRenderFillOpacity()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" fill=\"cyan\" fill-opacity=\"0.3\"/>"
"</svg>";
SvgRenderTester t (data);
t.setFuzzyThreshold(1);
t.test_standard_30px_72ppi("fill_opacity_0_3");
}
void TestSvgParser::testRenderDisplayAttribute()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"black\" display=\"none\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
QCOMPARE(shape->isVisible(), false);
}
void TestSvgParser::testRenderVisibilityAttribute()
{
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"black\" visibility=\"visible\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
QCOMPARE(shape->isVisible(), true);
}
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"black\" visibility=\"hidden\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
QCOMPARE(shape->isVisible(), false);
}
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"black\" visibility=\"collapse\"/>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
QCOMPARE(shape->isVisible(), false);
}
}
void TestSvgParser::testRenderVisibilityInheritance()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<g visibility=\"none\">"
" <rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"black\" visibility=\"visible\"/>"
"</g>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
QCOMPARE(shape->isVisible(false), true);
QCOMPARE(shape->isVisible(true), false);
}
void TestSvgParser::testRenderDisplayInheritance()
{
const QString data =
"<svg width=\"10px\" height=\"20px\" viewBox=\"0 0 10 20\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<g display=\"none\">"
" <rect id=\"testRect\" x=\"0\" y=\"0\" width=\"10\" height=\"20\""
" fill=\"black\" visibility=\"visible\"/>"
"</g>"
"</svg>";
SvgTester t (data);
t.parser.setResolution(QRectF(0, 0, 600, 400) /* px */, 144 /* ppi */);
t.run();
KoShape *shape = t.findShape("testRect");
QVERIFY(shape);
QCOMPARE(shape->isVisible(false), true);
QEXPECT_FAIL("", "TODO: Fix 'display' attribute not to be inherited in shapes hierarchy!", Continue);
QCOMPARE(shape->isVisible(true), true);
}
void TestSvgParser::testRenderStrokeWithInlineStyle()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" style = \"fill: cyan; stroke :blue; stroke-width:2;\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_blue_width_2");
}
void TestSvgParser::testIccColor()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<g xml:base=\"icc\">"
" <color-profile xlink:href=\"sRGB-elle-V4-srgbtrc.icc\""
" local=\"133a66607cffeebdd64dd433ada9bf4e\" name=\"default-profile\"/>"
" <color-profile xlink:href=\"sRGB-elle-V4-srgbtrc.icc\""
" local=\"133a66607cffeebdd64dd433ada9bf4e\" name=\"some-other-name\"/>"
" <rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" style = \"fill: cyan; stroke :blue; stroke-width:2;\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
int numFetches = 0;
t.parser.setFileFetcher(
[&numFetches](const QString &name) {
numFetches++;
const QString fileName = TestUtil::fetchDataFileLazy(name);
QFile file(fileName);
KIS_ASSERT(file.exists());
file.open(QIODevice::ReadOnly);
return file.readAll();
});
t.test_standard_30px_72ppi("stroke_blue_width_2");
QCOMPARE(numFetches, 1);
}
void TestSvgParser::testRenderFillLinearGradientRelativePercent()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<defs>"
" <linearGradient id=\"testGrad\" x1=\"0%\" y1=\"50%\" x2=\"100%\" y2=\"50%\">"
" <stop offset=\"20%\" stop-color=\"#F60\" />"
" <stop offset=\"80%\" stop-color=\"#FF6\" />"
" </linearGradient>"
"</defs>"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" style = \"fill:url(#testGrad) magenta; stroke:none; stroke-width:2;\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_gradient");
}
void TestSvgParser::testRenderFillLinearGradientRelativePortion()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<linearGradient id=\"testGrad\" x1=\"0\" y1=\"0.5\" x2=\"1.0\" y2=\"0.5\">"
" <stop offset=\"20%\" stop-color=\"#F60\" />"
" <stop offset=\"80%\" stop-color=\"#FF6\" />"
"</linearGradient>"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" style = \"fill:url(#testGrad) magenta; stroke:none; stroke-width:2;\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_gradient");
}
void TestSvgParser::testRenderFillLinearGradientUserCoord()
{
const QString data =
"<svg width=\"30px\" height=\"30px\" viewBox=\"60 70 60 90\""
" preserveAspectRatio=\"none meet\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<linearGradient id=\"testGrad\" x1=\"70\" y1=\"115\" x2=\"90\" y2=\"115\""
" gradientUnits=\"userSpaceOnUse\">"
" <stop offset=\"20%\" stop-color=\"#F60\" />"
" <stop offset=\"80%\" stop-color=\"#FF6\" />"
"</linearGradient>"
"<rect id=\"testRect\" x=\"70\" y=\"85\" width=\"20\" height=\"60\""
" fill=\"url(#testGrad) magenta\" stroke=\"none\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_gradient");
}
void TestSvgParser::testRenderFillLinearGradientStopPortion()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<linearGradient id=\"testGrad\" x1=\"0\" y1=\"0.5\" x2=\"1.0\" y2=\"0.5\">"
" <stop offset=\"0.2\" stop-color=\"#F60\" />"
" <stop offset=\"0.8\" stop-color=\"#FF6\" />"
"</linearGradient>"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" style = \"fill:url(#testGrad) magenta; stroke:none; stroke-width:2;\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_gradient");
}
void TestSvgParser::testRenderFillLinearGradientTransform()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<linearGradient id=\"testGrad\" x1=\"0\" y1=\"0.5\" x2=\"1.0\" y2=\"0.5\""
" gradientTransform=\"rotate(90, 0.5, 0.5)\">"
" <stop offset=\"0.2\" stop-color=\"#F60\" />"
" <stop offset=\"0.8\" stop-color=\"#FF6\" />"
"</linearGradient>"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" style = \"fill:url(#testGrad) magenta; stroke:none; stroke-width:2;\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_gradient_vertical");
}
void TestSvgParser::testRenderFillLinearGradientTransformUserCoord()
{
const QString data =
"<svg width=\"30px\" height=\"30px\" viewBox=\"60 70 60 90\""
" preserveAspectRatio=\"none meet\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<linearGradient id=\"testGrad\" x1=\"70\" y1=\"115\" x2=\"90\" y2=\"115\""
" gradientUnits=\"userSpaceOnUse\""
" gradientTransform=\"rotate(90, 80, 115)\">"
" <stop offset=\"20%\" stop-color=\"#F60\" />"
" <stop offset=\"80%\" stop-color=\"#FF6\" />"
"</linearGradient>"
"<rect id=\"testRect\" x=\"70\" y=\"85\" width=\"20\" height=\"60\""
" fill=\"url(#testGrad) magenta\" stroke=\"none\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_gradient_vertical_in_user");
}
void TestSvgParser::testRenderFillLinearGradientRotatedShape()
{
// DK: I'm not sure I fully understand if it is a correct transformation,
// but inkscape opens the file in the same way...
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<linearGradient id=\"testGrad\" x1=\"0\" y1=\"0.5\" x2=\"1.0\" y2=\"0.5\""
" gradientTransform=\"rotate(90, 0.5, 0.5)\">"
" <stop offset=\"0.2\" stop-color=\"#F60\" />"
" <stop offset=\"0.8\" stop-color=\"#FF6\" />"
"</linearGradient>"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" style = \"fill:url(#testGrad) magenta; stroke:none; stroke-width:2;\""
" transform=\"rotate(90, 10, 12.5)\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_gradient_shape_rotated", false);
}
void TestSvgParser::testRenderFillLinearGradientRotatedShapeUserCoord()
{
// DK: I'm not sure I fully understand if it is a correct transformation,
// but inkscape opens the file in the same way...
const QString data =
"<svg width=\"30px\" height=\"30px\" viewBox=\"60 70 60 90\""
" preserveAspectRatio=\"none meet\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<linearGradient id=\"testGrad\" x1=\"70\" y1=\"115\" x2=\"90\" y2=\"115\""
" gradientUnits=\"userSpaceOnUse\">"
" <stop offset=\"20%\" stop-color=\"#F60\" />"
" <stop offset=\"80%\" stop-color=\"#FF6\" />"
"</linearGradient>"
"<rect id=\"testRect\" x=\"70\" y=\"85\" width=\"20\" height=\"60\""
" fill=\"url(#testGrad) magenta\" stroke=\"none\""
" transform=\"rotate(90, 80, 115)\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_gradient_shape_rotated_in_user", false);
}
void TestSvgParser::testRenderFillRadialGradient()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<radialGradient id=\"testGrad\" cx=\"0.5\" cy=\"0.5\" fx=\"0.2\" fy=\"0.2\" r=\"0.5\">"
" <stop offset=\"0.2\" stop-color=\"#F60\" />"
" <stop offset=\"0.8\" stop-color=\"#FF6\" />"
"</radialGradient>"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" style = \"fill:url(#testGrad) magenta; stroke:none; stroke-width:2;\"/>"
"</svg>";
SvgRenderTester t (data);
t.setFuzzyThreshold(1);
t.test_standard_30px_72ppi("fill_gradient_radial");
}
void TestSvgParser::testRenderFillRadialGradientUserCoord()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<radialGradient id=\"testGrad\" cx=\"10\" cy=\"12.5\" fx=\"7\" fy=\"9\" r=\"5\""
" gradientUnits=\"userSpaceOnUse\">"
" <stop offset=\"0.2\" stop-color=\"#F60\" />"
" <stop offset=\"0.8\" stop-color=\"#FF6\" />"
"</radialGradient>"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" style = \"fill:url(#testGrad) magenta; stroke:none; stroke-width:2;\"/>"
"</svg>";
SvgRenderTester t (data);
t.setFuzzyThreshold(1);
t.test_standard_30px_72ppi("fill_gradient_radial_in_user");
}
void TestSvgParser::testRenderFillLinearGradientUserCoordPercent()
{
const QString data =
"<svg width=\"30px\" height=\"30px\" viewBox=\"60 70 60 90\""
" preserveAspectRatio=\"none meet\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<linearGradient id=\"testGrad\" x1=\"116.667%\" y1=\"127.778%\" x2=\"150%\" y2=\"127.778%\""
" gradientUnits=\"userSpaceOnUse\">"
" <stop offset=\"20%\" stop-color=\"#F60\" />"
" <stop offset=\"80%\" stop-color=\"#FF6\" />"
"</linearGradient>"
"<rect id=\"testRect\" x=\"70\" y=\"85\" width=\"20\" height=\"60\""
" fill=\"url(#testGrad) magenta\" stroke=\"none\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_gradient");
}
void TestSvgParser::testRenderStrokeLinearGradient()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<defs>"
" <linearGradient id=\"testGrad\" x1=\"0%\" y1=\"50%\" x2=\"100%\" y2=\"50%\">"
" <stop offset=\"20%\" stop-color=\"#F60\" />"
" <stop offset=\"80%\" stop-color=\"#FF6\" />"
" </linearGradient>"
"</defs>"
"<rect id=\"testRect\" x=\"5\" y=\"5\" width=\"10\" height=\"20\""
" style = \"grey; stroke:url(#testGrad); stroke-width:3; stroke-dasharray:3,1\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("stroke_gradient_dashed");
}
QTransform rotateTransform(qreal degree, const QPointF &center) {
QTransform rotate;
rotate.rotate(degree);
return
QTransform::fromTranslate(-center.x(), -center.y()) *
rotate *
QTransform::fromTranslate(center.x(), center.y());
}
QTransform viewTransform(const QRectF &src, const QRectF &dst) {
return QTransform::fromTranslate(-src.x(), -src.y()) *
QTransform::fromScale(dst.width() / src.width(),
dst.height() / src.height()) *
QTransform::fromTranslate(dst.x(), dst.y());
}
QPainterPath bakeShape(const QPainterPath &path,
const QTransform &bakeTransform,
bool contentIsObb = false, const QRectF &shapeBoundingRect = QRectF(),
bool contentIsViewBox = false, const QRectF &viewBoxRect= QRectF(), const QRectF &refRect = QRectF())
{
const QTransform relativeToShape(shapeBoundingRect.width(), 0, 0, shapeBoundingRect.height(),
shapeBoundingRect.x(), shapeBoundingRect.y());
QTransform newTransform = bakeTransform;
if (contentIsObb) {
newTransform = relativeToShape * newTransform;
}
if (contentIsViewBox) {
newTransform = viewTransform(viewBoxRect, refRect) * newTransform;
}
return newTransform.map(path);
}
#include <KoBakedShapeRenderer.h>
void renderBakedPath(QPainter &painter,
const QPainterPath &bakedFillPath, const QTransform &bakedTransform,
const QRect &shapeOutline, const QTransform &shapeTransform,
const QRectF &referenceRect,
bool contentIsObb, const QRectF &bakedShapeBoundingRect,
bool referenceIsObb,
const QTransform &patternTransform,
QImage *stampResult)
{
painter.setTransform(QTransform());
painter.setPen(Qt::NoPen);
QPainterPath shapeOutlinePath;
shapeOutlinePath.addRect(shapeOutline);
KoBakedShapeRenderer renderer(
shapeOutlinePath,
shapeTransform,
bakedTransform,
referenceRect,
contentIsObb, bakedShapeBoundingRect,
referenceIsObb,
patternTransform);
QPainter *patchPainter = renderer.bakeShapePainter();
patchPainter->fillPath(bakedFillPath, Qt::blue);
patchPainter->end();
renderer.renderShape(painter);
if (stampResult) {
*stampResult = renderer.patchImage();
}
}
void TestSvgParser::testManualRenderPattern_ContentUser_RefObb()
{
const QRectF referenceRect(0, 0, 1.0, 0.5);
QPainterPath fillPath;
fillPath.addRect(QRect(2, 2, 6, 6));
fillPath.addRect(QRect(8, 4, 3, 2));
QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2);
QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform);
QRect shape1OutlineRect(0,0,10,20);
QImage stampResult;
QImage fillResult(QSize(60,60), QImage::Format_ARGB32);
QPainter gc(&fillResult);
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
shape1OutlineRect, bakedTransform,
referenceRect,
false, QRectF(),
true,
QTransform(),
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_obb_patch1"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_obb_fill1"));
QRect shape2OutlineRect(5,5,20,10);
QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5);
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
shape2OutlineRect, shape2Transform,
referenceRect,
false, QRectF(),
true,
QTransform(),
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_obb_patch2"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_obb_fill2"));
}
void TestSvgParser::testManualRenderPattern_ContentObb_RefObb()
{
const QRectF referenceRect(0.3, 0.3, 0.4, 0.4);
QPainterPath fillPath;
fillPath.addRect(QRectF(0.4, 0.4, 0.2, 0.2));
fillPath.addRect(QRectF(0.6, 0.5, 0.1, 0.1));
fillPath.addRect(QRectF(0.3, 0.4, 0.1, 0.1));
const QRect bakedShapeRect(2,2,10,10);
QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2);
QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, bakedShapeRect);
QImage stampResult;
QImage fillResult(QSize(60,60), QImage::Format_ARGB32);
QPainter gc(&fillResult);
// Round trip to the same shape
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
bakedShapeRect, bakedTransform,
referenceRect,
true, bakedShapeRect,
true,
QTransform(),
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_patch1"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_fill1"));
// Move to a different shape
QRect shape2OutlineRect(5,5,20,10);
QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5);
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
shape2OutlineRect, shape2Transform,
referenceRect,
true, bakedShapeRect,
true,
QTransform(),
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_patch2"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_fill2"));
}
void TestSvgParser::testManualRenderPattern_ContentUser_RefUser()
{
const QRectF referenceRect(5, 2, 8, 8);
QPainterPath fillPath;
fillPath.addRect(QRect(2, 2, 6, 6));
fillPath.addRect(QRect(8, 4, 3, 2));
QTransform bakedTransform = QTransform::fromTranslate(10, 10) * QTransform::fromScale(2, 2);
QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform);
QRect shape1OutlineRect(0,0,10,20);
QImage stampResult;
QImage fillResult(QSize(60,60), QImage::Format_ARGB32);
QPainter gc(&fillResult);
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
shape1OutlineRect, bakedTransform,
referenceRect,
false, QRectF(),
false,
QTransform(),
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_user_patch1"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_user_fill1"));
QRect shape2OutlineRect(5,5,20,10);
QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5);
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
shape2OutlineRect, shape2Transform,
referenceRect,
false, QRectF(),
false,
QTransform(),
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_user_r_user_patch2"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_user_r_user_fill2"));
}
void TestSvgParser::testManualRenderPattern_ContentObb_RefObb_Transform_Rotate()
{
const QRectF referenceRect(0.0, 0.0, 0.4, 0.2);
QPainterPath fillPath;
fillPath.addRect(QRectF(0.0, 0.0, 0.5, 0.1));
fillPath.addRect(QRectF(0.0, 0.1, 0.1, 0.1));
const QRect bakedShapeRect(2,1,10,10);
QTransform bakedTransform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(10,10);
QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform, true, bakedShapeRect);
QImage stampResult;
QImage fillResult(QSize(60,60), QImage::Format_ARGB32);
QPainter gc(&fillResult);
QTransform patternTransform;
patternTransform.rotate(90);
patternTransform = patternTransform * QTransform::fromTranslate(0.5, 0.0);
// Round trip to the same shape
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
bakedShapeRect, bakedTransform,
referenceRect,
true, bakedShapeRect,
true,
patternTransform,
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_patch1"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_fill1"));
QRect shape2OutlineRect(5,5,20,10);
QTransform shape2Transform = QTransform::fromScale(2, 2) * QTransform::fromTranslate(5, 5);
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
shape2OutlineRect, shape2Transform,
referenceRect,
true, bakedShapeRect,
true,
patternTransform,
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_patch2"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_obb_r_obb_rotate_fill2"));
}
void TestSvgParser::testManualRenderPattern_ContentView_RefObb()
{
const QRectF referenceRect(0, 0, 0.5, 1.0/3.0);
const QRectF viewRect(10,10,60,90);
QPainterPath fillPath;
fillPath.addRect(QRect(30, 10, 20, 60));
fillPath.addRect(QRect(50, 40, 20, 30));
QRect shape1OutlineRect(10,20,40,120);
QTransform bakedTransform = QTransform::fromScale(2, 0.5) * QTransform::fromTranslate(40,30);
QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform,
true, shape1OutlineRect,
true, viewRect, referenceRect);
QImage stampResult;
QImage fillResult(QSize(220,160), QImage::Format_ARGB32);
QPainter gc(&fillResult);
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
shape1OutlineRect, bakedTransform,
referenceRect,
true, shape1OutlineRect,
true,
QTransform(),
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_obb_patch1"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_obb_fill1"));
QRect shape2OutlineRect(20,10,60,90);
QTransform shape2Transform = QTransform::fromScale(2, 1) * QTransform::fromTranslate(50, 50);
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
shape2OutlineRect, shape2Transform,
referenceRect,
true, shape1OutlineRect,
true,
rotateTransform(90, QPointF(0, 1.0 / 3.0)),
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_obb_patch2"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_obb_fill2"));
}
void TestSvgParser::testManualRenderPattern_ContentView_RefUser()
{
const QRectF referenceRect(60, 0, 30, 20);
const QRectF viewRect(10,10,60,90);
QPainterPath fillPath;
fillPath.addRect(QRect(30, 10, 20, 60));
fillPath.addRect(QRect(50, 40, 20, 30));
QRect shape1OutlineRect(10,20,40,120);
QTransform bakedTransform = QTransform::fromScale(2, 0.5) * QTransform::fromTranslate(40,30);
QPainterPath bakedFillPath = bakeShape(fillPath, bakedTransform,
false, shape1OutlineRect,
true, viewRect, referenceRect);
QImage stampResult;
QImage fillResult(QSize(220,160), QImage::Format_ARGB32);
QPainter gc(&fillResult);
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
shape1OutlineRect, bakedTransform,
referenceRect,
false, shape1OutlineRect,
false,
QTransform(),
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_user_patch1"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_user_fill1"));
QRect shape2OutlineRect(20,10,60,90);
QTransform shape2Transform = QTransform::fromScale(2, 1) * QTransform::fromTranslate(50, 50);
QTransform patternTransform2 = rotateTransform(90, QPointF()) * QTransform::fromTranslate(40, 10);
fillResult.fill(0);
renderBakedPath(gc,
bakedFillPath, bakedTransform,
shape2OutlineRect, shape2Transform,
referenceRect,
false, shape1OutlineRect,
false,
patternTransform2,
&stampResult);
QVERIFY(TestUtil::checkQImage(stampResult, "svg_render", "render", "pattern_c_view_r_user_patch2"));
QVERIFY(TestUtil::checkQImage(fillResult, "svg_render", "render", "pattern_c_view_r_user_fill2"));
}
void TestSvgParser::testRenderPattern_r_User_c_User()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<defs>"
" <pattern id=\"TestPattern\" patternUnits=\"userSpaceOnUse\""
" patternContentUnits=\"userSpaceOnUse\""
" x=\"60\" y=\"0\" width=\"30\" height=\"20\">"
" <g id=\"patternRect\">"
" <rect id=\"patternRect1\" x=\"70\" y=\"0\" width=\"10\" height=\"13.3333\""
" fill=\"red\" stroke=\"none\" />"
" <rect id=\"patternRect2\" x=\"80\" y=\"6.3333\" width=\"10\" height=\"6.6666\""
" fill=\"red\" stroke=\"none\" />"
" </g>"
" </pattern>"
"</defs>"
"<g>"
" <rect id=\"testRect\" x=\"10\" y=\"20\" width=\"40\" height=\"120\""
" transform=\"translate(40 30) scale(2 0.5)\""
" fill=\"url(#TestPattern)blue\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160));
}
void TestSvgParser::testRenderPattern_InfiniteRecursionWhenInherited()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<defs>"
" <pattern id=\"TestPattern\" patternUnits=\"userSpaceOnUse\""
" patternContentUnits=\"userSpaceOnUse\""
" x=\"60\" y=\"0\" width=\"30\" height=\"20\">"
" <g id=\"patternRect\">"
" <rect id=\"patternRect1\" x=\"70\" y=\"0\" width=\"10\" height=\"13.3333\""
" stroke=\"none\" />"
" <rect id=\"patternRect2\" x=\"80\" y=\"6.3333\" width=\"10\" height=\"6.6666\""
" stroke=\"none\" />"
" </g>"
" </pattern>"
"</defs>"
"<g>"
" <rect id=\"testRect\" x=\"10\" y=\"20\" width=\"40\" height=\"120\""
" transform=\"translate(40 30) scale(2 0.5)\""
" fill=\"url(#TestPattern)blue\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_pattern_base_black", false, QSize(160, 160));
}
void TestSvgParser::testRenderPattern_r_User_c_View()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<defs>"
" <pattern id=\"TestPattern\" patternUnits=\"userSpaceOnUse\""
" viewBox=\"10 10 60 90\""
" preserveAspectRatio=\"none meet\""
" x=\"60\" y=\"0\" width=\"30\" height=\"20\">"
" <g id=\"patternRect\">"
" <rect id=\"patternRect1\" x=\"30\" y=\"10\" width=\"20\" height=\"60\""
" fill=\"red\" stroke=\"none\" />"
// y is changed to 39 from 40 to fix a rounding issue!
" <rect id=\"patternRect2\" x=\"50\" y=\"39\" width=\"20\" height=\"30\""
" fill=\"red\" stroke=\"none\" />"
" </g>"
" </pattern>"
"</defs>"
"<g>"
" <rect id=\"testRect\" x=\"10\" y=\"20\" width=\"40\" height=\"120\""
" transform=\"translate(40 30) scale(2 0.5)\""
" fill=\"url(#TestPattern)blue\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160));
}
void TestSvgParser::testRenderPattern_r_User_c_Obb()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<defs>"
" <pattern id=\"TestPattern\" patternUnits=\"userSpaceOnUse\""
" patternContentUnits=\"objectBoundingBox\""
" x=\"60\" y=\"0\" width=\"30\" height=\"20\">"
" <g id=\"patternRect\">"
" <rect id=\"patternRect1\" x=\"1.5\" y=\"-0.1666\" width=\"0.25\" height=\"0.111\""
" fill=\"red\" stroke=\"none\" />"
" <rect id=\"patternRect2\" x=\"1.75\" y=\"-0.12\" width=\"0.25\" height=\"0.07\""
" fill=\"red\" stroke=\"none\" />"
" </g>"
" </pattern>"
"</defs>"
"<g>"
" <rect id=\"testRect\" x=\"10\" y=\"20\" width=\"40\" height=\"120\""
" transform=\"translate(40 30) scale(2 0.5)\""
" fill=\"url(#TestPattern)blue\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_pattern_base", false, QSize(160, 160));
}
void TestSvgParser::testRenderPattern_r_User_c_View_Rotated()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<defs>"
" <pattern id=\"TestPattern\" patternUnits=\"userSpaceOnUse\""
" viewBox=\"10 10 60 90\""
" preserveAspectRatio=\"none meet\""
" x=\"60\" y=\"0\" width=\"30\" height=\"20\""
" patternTransform=\"translate(40 10) rotate(90)\">"
" <g id=\"patternRect\">"
" <rect id=\"patternRect1\" x=\"30\" y=\"10\" width=\"20\" height=\"60\""
" fill=\"red\" stroke=\"none\" />"
" <rect id=\"patternRect2\" x=\"50\" y=\"40\" width=\"20\" height=\"30\""
" fill=\"red\" stroke=\"none\" />"
" </g>"
" </pattern>"
"</defs>"
"<g>"
" <rect id=\"testRect\" x=\"20\" y=\"10\" width=\"60\" height=\"90\""
" transform=\"translate(50 50) scale(2 1)\""
" fill=\"url(#TestPattern)blue\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_pattern_rotated", false, QSize(220, 160));
}
void TestSvgParser::testRenderPattern_r_Obb_c_View_Rotated()
{
/**
* This test case differs from any application existent in the world :(
*
* Chrome and Firefox premultiply the patternTransform instead of doing post-
* multiplication. Photoshop forgets to multiply the reference rect on it.
*
* So...
*/
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<defs>"
" <pattern id=\"TestPattern\" patternUnits=\"objectBoundingBox\""
" viewBox=\"10 10 60 90\""
" preserveAspectRatio=\"none meet\""
" x=\"0\" y=\"0\" width=\"0.5\" height=\"0.333\""
" patternTransform=\"translate(0 0) rotate(90)\">"
" <g id=\"patternRect\">"
" <rect id=\"patternRect1\" x=\"30\" y=\"10\" width=\"20\" height=\"60\""
" fill=\"red\" stroke=\"none\" />"
" <rect id=\"patternRect2\" x=\"50\" y=\"40\" width=\"20\" height=\"30\""
" fill=\"red\" stroke=\"none\" />"
" </g>"
" </pattern>"
"</defs>"
"<g>"
" <rect id=\"testRect\" x=\"20\" y=\"10\" width=\"60\" height=\"90\""
" transform=\"translate(50 50) scale(1 1)\""
" fill=\"url(#TestPattern)blue\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("fill_pattern_rotated_odd", false, QSize(220, 160));
}
#include <KoPathShape.h>
#include <KoColorBackground.h>
#include <KoClipPath.h>
#include <commands/KoShapeGroupCommand.h>
void TestSvgParser::testKoClipPathRendering()
{
QPainterPath path1;
path1.addRect(QRect(5,5,15,15));
QPainterPath path2;
path2.addRect(QRect(10,10,15,15));
QPainterPath clipPath1;
clipPath1.addRect(QRect(10, 0, 10, 30));
QPainterPath clipPath2;
clipPath2.moveTo(0,7);
clipPath2.lineTo(30,7);
clipPath2.lineTo(15,30);
clipPath2.lineTo(0,7);
QScopedPointer<KoPathShape> shape1(KoPathShape::createShapeFromPainterPath(path1));
shape1->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(Qt::blue)));
QScopedPointer<KoPathShape> shape2(KoPathShape::createShapeFromPainterPath(path2));
shape2->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(Qt::red)));
QScopedPointer<KoPathShape> clipShape1(KoPathShape::createShapeFromPainterPath(clipPath1));
KoClipPath *koClipPath1 = new KoClipPath({clipShape1.take()}, KoFlake::UserSpaceOnUse);
koClipPath1->setClipRule(Qt::WindingFill);
shape1->setClipPath(koClipPath1);
QScopedPointer<KoShapeGroup> group(new KoShapeGroup());
{
QList<KoShape*> shapes({shape1.take(), shape2.take()});
KoShapeGroupCommand cmd(group.data(), shapes, false);
cmd.redo();
}
QScopedPointer<KoPathShape> clipShape2(KoPathShape::createShapeFromPainterPath(clipPath2));
KoClipPath *koClipPath2 = new KoClipPath({clipShape2.take()}, KoFlake::UserSpaceOnUse);
koClipPath2->setClipRule(Qt::WindingFill);
group->setClipPath(koClipPath2);
SvgRenderTester::testRender(group.take(), "load", "clip_render_test", QSize(30,30));
}
void TestSvgParser::testKoClipPathRelativeRendering()
{
QPainterPath path1;
path1.addRect(QRect(5,5,15,15));
QPainterPath path2;
path2.addRect(QRect(10,10,15,15));
QPainterPath clipPath1;
clipPath1.addRect(QRect(10, 0, 10, 30));
QPainterPath clipPath2;
clipPath2.moveTo(0,0);
clipPath2.lineTo(1,0);
clipPath2.lineTo(0.5,1);
clipPath2.lineTo(0,0);
QScopedPointer<KoPathShape> shape1(KoPathShape::createShapeFromPainterPath(path1));
shape1->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(Qt::blue)));
QScopedPointer<KoPathShape> shape2(KoPathShape::createShapeFromPainterPath(path2));
shape2->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(Qt::red)));
QScopedPointer<KoPathShape> clipShape1(KoPathShape::createShapeFromPainterPath(clipPath1));
KoClipPath *koClipPath1 = new KoClipPath({clipShape1.take()}, KoFlake::UserSpaceOnUse);
koClipPath1->setClipRule(Qt::WindingFill);
shape1->setClipPath(koClipPath1);
QScopedPointer<KoShapeGroup> group(new KoShapeGroup());
{
QList<KoShape*> shapes({shape1.take(), shape2.take()});
KoShapeGroupCommand cmd(group.data(), shapes, false);
cmd.redo();
}
QScopedPointer<KoPathShape> clipShape2(KoPathShape::createShapeFromPainterPath(clipPath2));
KoClipPath *koClipPath2 = new KoClipPath({clipShape2.take()}, KoFlake::ObjectBoundingBox);
koClipPath2->setClipRule(Qt::WindingFill);
group->setClipPath(koClipPath2);
SvgRenderTester::testRender(group.take(), "load", "relative_clip_render_test", QSize(30,30));
}
void TestSvgParser::testRenderClipPath_User()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<clipPath id=\"clip1\" clipPathUnits=\"userSpaceOnUse\">"
" <rect id=\"clipRect1\" x=\"10\" y=\"0\" width=\"10\" height=\"30\""
" fill=\"red\" stroke=\"none\" />"
" <rect id=\"clipRect1\" x=\"10\" y=\"0\" width=\"10\" height=\"30\""
" fill=\"yellow\" stroke=\"none\" />"
"</clipPath>"
"<clipPath id=\"clip2\" clipPathUnits=\"userSpaceOnUse\">"
" <path id=\"clipRect1\" d=\"M 0 7 L 30 7 15 30 0 7 z\""
" fill=\"red\" stroke=\"none\" />"
"</clipPath>"
"<g id=\"testRect\" clip-path=\"url(#clip2)\">"
" <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"15\""
" fill=\"blue\" stroke=\"none\" clip-path=\"url(#clip1)\"/>"
" <rect id=\"testRect2\" x=\"10\" y=\"10\" width=\"15\" height=\"15\""
" fill=\"red\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("clip_render_test", false);
}
void TestSvgParser::testRenderClipPath_Obb()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<clipPath id=\"clip1\" clipPathUnits=\"userSpaceOnUse\">"
" <rect id=\"clipRect1\" x=\"10\" y=\"0\" width=\"10\" height=\"30\""
" fill=\"red\" stroke=\"none\" />"
" <rect id=\"clipRect1\" x=\"10\" y=\"0\" width=\"10\" height=\"30\""
" fill=\"yellow\" stroke=\"none\" />"
"</clipPath>"
"<clipPath id=\"clip2\" clipPathUnits=\"objectBoundingBox\">"
" <path id=\"clipRect1\" d=\"M 0 0 L 1 0 0.5 1 0 0 z\""
" fill=\"red\" stroke=\"none\" />"
"</clipPath>"
"<g id=\"testRect\" clip-path=\"url(#clip2)\">"
" <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"15\""
" fill=\"blue\" stroke=\"none\" clip-path=\"url(#clip1)\"/>"
" <rect id=\"testRect2\" x=\"10\" y=\"10\" width=\"15\" height=\"15\""
" fill=\"red\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("relative_clip_render_test", false);
}
void TestSvgParser::testRenderClipPath_Obb_Transform()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<clipPath id=\"clip1\" clipPathUnits=\"userSpaceOnUse\""
" transform=\"rotate(90,15,15)\">"
" <rect id=\"clipRect1\" x=\"10\" y=\"0\" width=\"10\" height=\"30\""
" fill=\"red\" stroke=\"none\" />"
" <rect id=\"clipRect1\" x=\"10\" y=\"0\" width=\"10\" height=\"30\""
" fill=\"yellow\" stroke=\"none\" />"
"</clipPath>"
"<clipPath id=\"clip2\" clipPathUnits=\"objectBoundingBox\""
" transform=\"rotate(90 0.5 0.5)\">"
" <path id=\"clipRect1\" d=\"M 0 0 L 1 0 0.5 1 0 0 z\""
" fill=\"red\" stroke=\"none\" />"
"</clipPath>"
"<g id=\"testRect\" clip-path=\"url(#clip2)\">"
" <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"15\""
" fill=\"blue\" stroke=\"none\" clip-path=\"url(#clip1)\"/>"
" <rect id=\"testRect2\" x=\"10\" y=\"10\" width=\"15\" height=\"15\""
" fill=\"red\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("clip_render_test_rotated", false);
}
void TestSvgParser::testRenderClipMask_Obb()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
- //"<defs>"
+ "<defs>"
" <linearGradient id=\"Gradient\" gradientUnits=\"objectBoundingBox\""
" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">"
" <stop offset=\"0\" stop-color=\"white\" stop-opacity=\"0\" />"
" <stop offset=\"1\" stop-color=\"white\" stop-opacity=\"1\" />"
" </linearGradient>"
" <mask id=\"Mask\" maskUnits=\"objectBoundingBox\""
" maskContentUnits=\"objectBoundingBox\""
" x=\"0.2\" y=\"0.2\" width=\"0.6\" height=\"0.6\">"
" <rect x=\"0\" y=\"0\" width=\"1\" height=\"1\" fill=\"url(#Gradient)\" />"
" </mask>"
- //"</defs>"
+ "</defs>"
"<g id=\"testRect\">"
" <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"15\""
" fill=\"blue\" stroke=\"none\"/>"
" <rect id=\"testRect2\" x=\"10\" y=\"10\" width=\"15\" height=\"15\""
" fill=\"red\" stroke=\"none\" mask=\"url(#Mask)\" />"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("clip_mask_obb", false);
}
+void TestSvgParser::testRenderClipMaskOnGroup_Obb()
+{
+ const QString data =
+ "<svg width=\"30px\" height=\"30px\""
+ " xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
+
+ "<defs>"
+
+ " <linearGradient id=\"Gradient\" gradientUnits=\"objectBoundingBox\""
+ " x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">"
+
+ " <stop offset=\"0\" stop-color=\"white\" stop-opacity=\"0\" />"
+ " <stop offset=\"1\" stop-color=\"white\" stop-opacity=\"1\" />"
+
+ " </linearGradient>"
+
+ " <mask id=\"Mask\" maskUnits=\"objectBoundingBox\""
+ " maskContentUnits=\"objectBoundingBox\""
+ " x=\"0.2\" y=\"0.2\" width=\"0.6\" height=\"0.6\">"
+
+ " <rect x=\"0\" y=\"0\" width=\"1\" height=\"1\" fill=\"url(#Gradient)\" />"
+
+ " </mask>"
+
+ "</defs>"
+
+
+ "<g id=\"testRect\" mask=\"url(#Mask)\">"
+ " <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"15\""
+ " fill=\"blue\" stroke=\"none\"/>"
+
+ " <rect id=\"testRect2\" x=\"10\" y=\"10\" width=\"15\" height=\"15\""
+ " fill=\"red\" stroke=\"none\" />"
+ "</g>"
+
+ "</svg>";
+
+ SvgRenderTester t (data);
+
+ t.test_standard_30px_72ppi("clip_mask_on_group_obb", false);
+}
+
+QByteArray fileFetcherFunc(const QString &name)
+{
+ const QString fileName = TestUtil::fetchDataFileLazy(name);
+ QFile file(fileName);
+ KIS_ASSERT(file.exists());
+ file.open(QIODevice::ReadOnly);
+ return file.readAll();
+}
+
void TestSvgParser::testRenderClipMask_User_Clip_Obb()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
//"<defs>"
" <linearGradient id=\"Gradient\" gradientUnits=\"objectBoundingBox\""
" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">"
" <stop offset=\"0\" stop-color=\"white\" stop-opacity=\"0\" />"
" <stop offset=\"1\" stop-color=\"white\" stop-opacity=\"1\" />"
" </linearGradient>"
" <mask id=\"Mask\" maskUnits=\"objectBoundingBox\""
" maskContentUnits=\"userSpaceOnUse\""
" x=\"0.2\" y=\"0.2\" width=\"0.6\" height=\"0.6\">"
" <rect x=\"10\" y=\"10\" width=\"15\" height=\"15\" fill=\"url(#Gradient)\" />"
" </mask>"
//"</defs>"
"<g id=\"testRect\">"
" <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"15\""
" fill=\"blue\" stroke=\"none\"/>"
" <rect id=\"testRect2\" x=\"10\" y=\"10\" width=\"15\" height=\"15\""
" fill=\"red\" stroke=\"none\" mask=\"url(#Mask)\" />"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("clip_mask_obb", false);
}
void TestSvgParser::testRenderClipMask_User_Clip_User()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
//"<defs>"
" <linearGradient id=\"Gradient\" gradientUnits=\"objectBoundingBox\""
" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">"
" <stop offset=\"0\" stop-color=\"white\" stop-opacity=\"0\" />"
" <stop offset=\"1\" stop-color=\"white\" stop-opacity=\"1\" />"
" </linearGradient>"
" <mask id=\"Mask\" maskUnits=\"userSpaceOnUse\""
" maskContentUnits=\"userSpaceOnUse\""
" x=\"13\" y=\"13\" width=\"9\" height=\"9\">"
" <rect x=\"10\" y=\"10\" width=\"15\" height=\"15\" fill=\"url(#Gradient)\" />"
" </mask>"
//"</defs>"
"<g id=\"testRect\">"
" <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"15\""
" fill=\"blue\" stroke=\"none\"/>"
" <rect id=\"testRect2\" x=\"10\" y=\"10\" width=\"15\" height=\"15\""
" fill=\"red\" stroke=\"none\" mask=\"url(#Mask)\" />"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("clip_mask_obb", false);
}
-QByteArray fileFetcherFunc(const QString &name)
-{
- const QString fileName = TestUtil::fetchDataFileLazy(name);
- QFile file(fileName);
- KIS_ASSERT(file.exists());
- file.open(QIODevice::ReadOnly);
- return file.readAll();
-}
-
void TestSvgParser::testRenderImage_AspectDefault()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<g id=\"testRect\">"
" <rect id=\"testRect1\" x=\"2\" y=\"2\" width=\"26\" height=\"26\""
" fill=\"green\" stroke=\"none\"/>"
" <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"10\""
" fill=\"white\" stroke=\"none\"/>"
" <image x=\"5\" y=\"5\" width=\"15\" height=\"10\""
" xlink:href=\"svg_render/testing_ref_image.png\">"
" <title>My image</title>"
" </image>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.parser.setFileFetcher(fileFetcherFunc);
t.test_standard_30px_72ppi("image_aspect_default", false);
}
void TestSvgParser::testRenderImage_AspectNone()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<g id=\"testRect\">"
" <rect id=\"testRect1\" x=\"2\" y=\"2\" width=\"26\" height=\"26\""
" fill=\"green\" stroke=\"none\"/>"
" <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"10\""
" fill=\"white\" stroke=\"none\"/>"
" <image x=\"5\" y=\"5\" width=\"15\" height=\"10\""
" preserveAspectRatio=\"none\""
" xlink:href=\"svg_render/testing_ref_image.png\">"
" <title>My image</title>"
" </image>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.setFuzzyThreshold(5);
t.parser.setFileFetcher(fileFetcherFunc);
t.test_standard_30px_72ppi("image_aspect_none", false);
}
void TestSvgParser::testRenderImage_AspectMeet()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<g id=\"testRect\">"
" <rect id=\"testRect1\" x=\"2\" y=\"2\" width=\"26\" height=\"26\""
" fill=\"green\" stroke=\"none\"/>"
" <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"10\""
" fill=\"white\" stroke=\"none\"/>"
" <image x=\"5\" y=\"5\" width=\"15\" height=\"10\""
" preserveAspectRatio=\"xMinYMin meet\""
" xlink:href=\"svg_render/testing_ref_image.png\">"
" <title>My image</title>"
" </image>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.parser.setFileFetcher(fileFetcherFunc);
t.test_standard_30px_72ppi("image_aspect_meet", false);
}
void TestSvgParser::testRectShapeRoundUniformX()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
" <rect id=\"testRect\" x=\"5\" y=\"5\" width=\"20\" height=\"20\""
" rx=\"5\""
" fill=\"blue\" stroke=\"none\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("rect_5_5", false);
}
void TestSvgParser::testRectShapeRoundUniformY()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
" <rect id=\"testRect\" x=\"5\" y=\"5\" width=\"20\" height=\"20\""
" rx=\"5\""
" fill=\"blue\" stroke=\"none\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("rect_5_5", false);
}
void TestSvgParser::testRectShapeRoundXY()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
" <rect id=\"testRect\" x=\"5\" y=\"5\" width=\"20\" height=\"20\""
" rx=\"5\" ry=\"10\""
" fill=\"blue\" stroke=\"none\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("rect_5_10", false);
}
void TestSvgParser::testRectShapeRoundXYOverflow()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
" <rect id=\"testRect\" x=\"5\" y=\"5\" width=\"20\" height=\"20\""
" rx=\"5\" ry=\"25\""
" fill=\"blue\" stroke=\"none\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("rect_5_10", false);
}
void TestSvgParser::testCircleShape()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
" <circle id=\"testRect\" cx=\"15\" cy=\"15\" r=\"10\""
" fill=\"blue\" stroke=\"white\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("circle", false);
}
void TestSvgParser::testEllipseShape()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
" <ellipse id=\"testRect\" cx=\"15\" cy=\"15\" rx=\"10\" ry=\"5\""
" fill=\"blue\" stroke=\"white\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("ellipse", false);
}
void TestSvgParser::testLineShape()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
" <line id=\"testRect\" x1=\"5\" y1=\"5\" x2=\"25\" y2=\"15\""
" fill=\"blue\" stroke=\"white\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("line", false);
}
void TestSvgParser::testPolylineShape()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
" <polyline id=\"testRect\" points=\"5,5 10, 5 10,10 5,10 \n"
" 5 ,15 15 , 15 15,20 20,20 20,10 25,10 25,25 5,25\""
" fill=\"red\" stroke=\"white\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("polyline", false);
}
void TestSvgParser::testPolygonShape()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
" <polygon id=\"testRect\" points=\"5,5 10, 5 10,10 5,10 \n"
" 5 ,15 15 , 15 15,20 20,20 20,10 25,10 25,25 5,25\""
" fill=\"red\" stroke=\"white\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("polygon", false);
}
void TestSvgParser::testPathShape()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
" <path id=\"testRect\""
" d=\"M 5,5 h 5 l 0,5 L5,10 l 0,5 l10,0 v5 l 5, 0 L20, 10 l 5,0 L 25,25 L 5,25 z\""
" fill=\"red\" stroke=\"white\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("polygon", false);
}
void TestSvgParser::testDefsHidden()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<g id=\"testRect\">"
" <defs>"
" <rect id=\"testRect1\" x=\"5\" y=\"5\" width=\"15\" height=\"15\""
" fill=\"blue\" stroke=\"none\"/>"
" </defs>"
" <rect id=\"testRect2\" x=\"10\" y=\"10\" width=\"15\" height=\"15\""
" fill=\"red\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("test_defs_hidden", false);
}
void TestSvgParser::testDefsUseInheritance()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<g id=\"unrenderedRect\" fill=\"green\" >"
" <defs>"
" <rect id=\"testRect1\" x=\"1\" y=\"1\" width=\"15\" height=\"15\""
" stroke=\"none\"/>"
" </defs>"
"</g>"
/**
* NOTES:
* 1) width/height attributes for <use> are not implemented yet
* 2) x and y are summed up
* 3) stroke="white" is overridden by the original templated object
* 4) fill="green" attribute from <defs> is not inherited
*/
"<g id=\"testRect\" fill=\"blue\" >"
" <use x=\"4\" y=\"4\" xlink:href=\"#testRect1\""
" stroke=\"white\" stroke-width=\"1\" />"
" <rect id=\"testRect2\" x=\"10\" y=\"10\" width=\"15\" height=\"15\""
" fill=\"red\" stroke=\"none\"/>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("defs_use_inheritance", false);
}
void TestSvgParser::testUseWithoutDefs()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
// technical rect for rendering
"<g id=\"testRect\">"
"<g id=\"renderedRect1\" fill=\"green\" >"
" <rect id=\"testRect1\" x=\"1\" y=\"1\" width=\"15\" height=\"15\""
" stroke=\"none\"/>"
"</g>"
/**
* NOTES:
* 1) width/height attributes for <use> are not implemented yet
* 2) x and y are summed up
* 3) stroke="white" is overridden by the original templated object
* 4) fill="green" attribute from <defs> is not inherited
*/
"<g id=\"renderedRect2\" fill=\"blue\" >"
" <use x=\"4\" y=\"4\" xlink:href=\"#testRect1\""
" stroke=\"white\" stroke-width=\"1\" />"
" <rect id=\"testRect2\" x=\"10\" y=\"10\" width=\"15\" height=\"15\""
" fill=\"red\" stroke=\"none\"/>"
"</g>"
"</g>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("use_without_defs", false);
}
void TestSvgParser::testMarkersAutoOrientation()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<marker id=\"SimpleRectMarker\""
" orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"red\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<path id=\"testRect\""
" style=\"fill:none;stroke:#000000;stroke-width:1px;marker-start:url(#SimpleRectMarker);marker-end:url(#SimpleRectMarker);marker-mid:url(#SimpleRectMarker)\""
" d=\"M5,15 C5,5 25,5 25,15 L15,25\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("markers", false);
}
void TestSvgParser::testMarkersAutoOrientationScaled()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<marker id=\"SimpleRectMarker\""
" orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"red\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<path id=\"testRect\""
" style=\"fill:none;stroke:#000000;stroke-width:2px;marker-start:url(#SimpleRectMarker);marker-end:url(#SimpleRectMarker);marker-mid:url(#SimpleRectMarker)\""
" d=\"M5,15 C5,5 25,5 25,15 L15,25\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("markers_scaled", false);
}
void TestSvgParser::testMarkersAutoOrientationScaledUserCoordinates()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<marker id=\"SimpleRectMarker\""
" markerUnits = \"userSpaceOnUse\""
" orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"red\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<path id=\"testRect\""
" style=\"fill:none;stroke:#000000;stroke-width:2px;marker-start:url(#SimpleRectMarker);marker-end:url(#SimpleRectMarker);marker-mid:url(#SimpleRectMarker)\""
" d=\"M5,15 C5,5 25,5 25,15 L15,25\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("markers_user_coordinates", false);
}
void TestSvgParser::testMarkersCustomOrientation()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<marker id=\"SimpleRectMarker\""
" orient=\"45\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"red\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<path id=\"testRect\""
" style=\"fill:none;stroke:#000000;stroke-width:1px;marker-start:url(#SimpleRectMarker);marker-end:url(#SimpleRectMarker);marker-mid:url(#SimpleRectMarker)\""
" d=\"M5,15 C5,5 25,5 25,15 L15,25\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("markers_custom_orientation", false);
}
void TestSvgParser::testMarkersDifferent()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<marker id=\"SimpleRectMarker1\""
" orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"red\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<marker id=\"SimpleRectMarker2\""
" orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"green\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<marker id=\"SimpleRectMarker3\""
" orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"blue\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<path id=\"testRect\""
" style=\"fill:none;stroke:#000000;stroke-width:1px;marker-start:url(#SimpleRectMarker1);marker-end:url(#SimpleRectMarker2);marker-mid:url(#SimpleRectMarker3)\""
" d=\"M5,15 C5,5 25,5 25,15 L15,25\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("markers_different", false);
}
void TestSvgParser::testMarkersFillAsShape()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<defs>"
" <linearGradient id=\"testGrad\" x1=\"0%\" y1=\"50%\" x2=\"100%\" y2=\"50%\">"
" <stop offset=\"5%\" stop-color=\"#F60\" />"
" <stop offset=\"80%\" stop-color=\"#FF6\" />"
" </linearGradient>"
" <marker id=\"SimpleRectMarker\""
" orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"red\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
" </marker>"
"</defs>"
"<path id=\"testRect\""
" style=\"fill:none;stroke:url(#testGrad) magenta;stroke-width:2px;marker-start:url(#SimpleRectMarker);marker-end:url(#SimpleRectMarker);marker-mid:url(#SimpleRectMarker);krita|marker-fill-method:auto\""
" d=\"M5,15 C5,5 25,5 25,15 L15,25\"/>"
//" d=\"M5,15 L25,15\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("markers_scaled_fill_as_shape", false);
}
void TestSvgParser::testMarkersOnClosedPath()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<marker id=\"SimpleRectMarker1\""
" orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"9\" y=\"9\" width=\"7\" height=\"7\""
" fill=\"red\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<marker id=\"SimpleRectMarker2\""
" orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"green\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<marker id=\"SimpleRectMarker3\""
" orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"blue\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<path id=\"testRect\""
" style=\"fill:none;stroke:#000000;stroke-width:1px;marker-start:url(#SimpleRectMarker1);marker-end:url(#SimpleRectMarker2);marker-mid:url(#SimpleRectMarker3)\""
" d=\"M5,15 C5,5 25,5 25,15 L15,25 z\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("markers_on_closed_path", false);
}
void TestSvgParser::testGradientRecoveringTransform()
{
// used for experimenting purposes only!
QImage image(100,100,QImage::Format_ARGB32);
image.fill(0);
QPainter painter(&image);
painter.setPen(QPen(Qt::black, 0));
QLinearGradient gradient(0, 0.5, 1, 0.5);
gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
//QLinearGradient gradient(0, 50, 100, 50);
//gradient.setCoordinateMode(QGradient::LogicalMode);
gradient.setColorAt(0.0, Qt::red);
gradient.setColorAt(1.0, Qt::blue);
QTransform gradientTransform;
gradientTransform.shear(0.2, 0);
{
QBrush brush(gradient);
brush.setTransform(gradientTransform);
painter.setBrush(brush);
}
QRect mainShape(3,3,94,94);
painter.drawRect(mainShape);
QTransform gradientToUser(mainShape.width(), 0, 0, mainShape.height(),
mainShape.x(), mainShape.y());
QRect smallShape(0,0,20,20);
QTransform smallShapeTransform;
{
smallShapeTransform =
QTransform::fromTranslate(-smallShape.center().x(), -smallShape.center().y());
QTransform r; r.rotate(90);
smallShapeTransform *= r;
smallShapeTransform *=
QTransform::fromTranslate(mainShape.center().x(), mainShape.center().y());
}
{
gradient.setCoordinateMode(QGradient::LogicalMode);
QBrush brush(gradient);
brush.setTransform(gradientTransform * gradientToUser * smallShapeTransform.inverted());
painter.setBrush(brush);
painter.setPen(Qt::NoPen);
}
painter.setTransform(smallShapeTransform);
painter.drawRect(smallShape);
//image.save("gradient_recovering_transform.png");
}
void TestSvgParser::testMarkersAngularUnits()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
// start
"<marker id=\"SimpleRectMarker1\""
" orient=\"45deg\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"red\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
// end
"<marker id=\"SimpleRectMarker2\""
" orient=\"200grad\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"green\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
// mid
"<marker id=\"SimpleRectMarker3\""
" orient=\"-1.57rad\" refY=\"12.5\" refX=\"12.5\" >"
" <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
" fill=\"blue\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"yellow\" stroke=\"none\"/>"
" <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
" fill=\"white\" stroke=\"none\"/>"
"</marker>"
"<path id=\"testRect\""
" style=\"fill:none;stroke:#000000;stroke-width:1px;marker-start:url(#SimpleRectMarker1);marker-end:url(#SimpleRectMarker2);marker-mid:url(#SimpleRectMarker3)\""
" d=\"M5,15 C5,5 25,5 25,15 L15,25\"/>"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("markers_angular_units", false);
}
#include "KoParameterShape.h"
void TestSvgParser::testSodipodiArcShape()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\""
" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\""
">"
"<path"
" fill=\"red\" stroke=\"black\""
" id=\"testRect\""
" sodipodi:type=\"arc\""
" sodipodi:cx=\"15.464287\""
" sodipodi:cy=\"14.517863\""
" sodipodi:rx=\"6.25\""
" sodipodi:ry=\"8.5\""
" sodipodi:start=\"5.5346039\""
" sodipodi:end=\"4.0381334\""
//" d=\"m 20.043381,8.7327624 a 6.25,8.5 0 0 1 1.053863,9.4677386 6.25,8.5 0 0 1 -6.094908,4.794113 6.25,8.5 0 0 1 -5.5086474,-5.963709 6.25,8.5 0 0 1 2.0686234,-9.1530031 l 3.901975,6.6399611 z\" />"
" d=\" some weird unparsable text \" />"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("sodipodi_closed_arc", false);
KoShape *shape = t.findShape("testRect");
QVERIFY(dynamic_cast<KoParameterShape*>(shape));
}
void TestSvgParser::testSodipodiArcShapeOpen()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\""
" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\""
">"
"<path"
" fill=\"red\" stroke=\"black\""
" id=\"testRect\""
" sodipodi:type=\"arc\""
" sodipodi:open=\"true\""
" sodipodi:cx=\"15.464287\""
" sodipodi:cy=\"14.517863\""
" sodipodi:rx=\"6.25\""
" sodipodi:ry=\"8.5\""
" sodipodi:start=\"5.5346039\""
" sodipodi:end=\"4.0381334\""
//" d=\"m 20.043381,8.7327624 a 6.25,8.5 0 0 1 1.053863,9.4677386 6.25,8.5 0 0 1 -6.094908,4.794113 6.25,8.5 0 0 1 -5.5086474,-5.963709 6.25,8.5 0 0 1 2.0686234,-9.1530031 l 3.901975,6.6399611 z\" />"
" d=\" some weird unparsable text \" />"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("sodipodi_open_arc", false);
KoShape *shape = t.findShape("testRect");
QVERIFY(dynamic_cast<KoParameterShape*>(shape));
}
void TestSvgParser::testKritaChordShape()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\""
" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\""
">"
"<path"
" fill=\"red\" stroke=\"black\""
" id=\"testRect\""
" krita:type=\"arc\""
" krita:arcType=\"chord\""
" krita:cx=\"15.464287\""
" krita:cy=\"14.517863\""
" krita:rx=\"6.25\""
" krita:ry=\"8.5\""
" krita:start=\"5.5346039\""
" krita:end=\"4.0381334\""
//" d=\"m 20.043381,8.7327624 a 6.25,8.5 0 0 1 1.053863,9.4677386 6.25,8.5 0 0 1 -6.094908,4.794113 6.25,8.5 0 0 1 -5.5086474,-5.963709 6.25,8.5 0 0 1 2.0686234,-9.1530031 l 3.901975,6.6399611 z\" />"
" d=\" some weird unparsable text \" />"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("sodipodi_chord_arc", false);
KoShape *shape = t.findShape("testRect");
QVERIFY(dynamic_cast<KoParameterShape*>(shape));
}
void TestSvgParser::testSodipodiChordShape()
{
const QString data =
"<svg width=\"30px\" height=\"30px\""
" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\""
" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\""
">"
"<path"
" fill=\"red\" stroke=\"black\""
" id=\"testRect\""
" sodipodi:type=\"arc\""
" sodipodi:arc-type=\"chord\""
" sodipodi:open=\"true\""
" sodipodi:cx=\"15.464287\""
" sodipodi:cy=\"14.517863\""
" sodipodi:rx=\"6.25\""
" sodipodi:ry=\"8.5\""
" sodipodi:start=\"5.5346039\""
" sodipodi:end=\"4.0381334\""
//" d=\"m 20.043381,8.7327624 a 6.25,8.5 0 0 1 1.053863,9.4677386 6.25,8.5 0 0 1 -6.094908,4.794113 6.25,8.5 0 0 1 -5.5086474,-5.963709 6.25,8.5 0 0 1 2.0686234,-9.1530031 l 3.901975,6.6399611 z\" />"
" d=\" some weird unparsable text \" />"
"</svg>";
SvgRenderTester t (data);
t.test_standard_30px_72ppi("sodipodi_chord_arc", false);
KoShape *shape = t.findShape("testRect");
QVERIFY(dynamic_cast<KoParameterShape*>(shape));
}
QTEST_MAIN(TestSvgParser)
diff --git a/libs/flake/tests/TestSvgParser.h b/libs/flake/tests/TestSvgParser.h
index 9e5292c76d..5a24e9b8d3 100644
--- a/libs/flake/tests/TestSvgParser.h
+++ b/libs/flake/tests/TestSvgParser.h
@@ -1,175 +1,176 @@
/*
* 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 TESTSVGPARSER_H
#define TESTSVGPARSER_H
#include <QtTest>
class TestSvgParser : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testUnitPx();
void testUnitPxResolution();
void testUnitPt();
void testUnitIn();
void testUnitPercentInitial();
void testScalingViewport();
void testScalingViewportKeepMeet1();
void testScalingViewportKeepMeet2();
void testScalingViewportKeepMeetAlign();
void testScalingViewportKeepSlice1();
void testScalingViewportKeepSlice2();
void testScalingViewportResolution();
void testScalingViewportPercentInternal();
void testParsePreserveAspectRatio();
void testParseTransform();
void testScalingViewportTransform();
void testTransformNesting();
void testTransformNestingGroups();
void testTransformRotation1();
void testTransformRotation2();
void testRenderStrokeNone();
void testRenderStrokeColorName();
void testRenderStrokeColorHex3();
void testRenderStrokeColorHex6();
void testRenderStrokeColorRgbValues();
void testRenderStrokeColorRgbPercent();
void testRenderStrokeColorCurrent();
void testRenderStrokeColorNonexistentIri();
void testRenderStrokeWidth();
void testRenderStrokeZeroWidth();
void testRenderStrokeOpacity();
void testRenderStrokeJointRound();
void testRenderStrokeLinecap();
void testRenderStrokeMiterLimit();
void testRenderStrokeDashArrayEven();
void testRenderStrokeDashArrayEvenOffset();
void testRenderStrokeDashArrayOdd();
void testRenderStrokeDashArrayRelative();
void testRenderFillDefault();
void testRenderFillRuleNonZero();
void testRenderFillRuleEvenOdd();
void testRenderFillOpacity();
void testRenderDisplayAttribute();
void testRenderVisibilityAttribute();
void testRenderVisibilityInheritance();
void testRenderDisplayInheritance();
void testRenderStrokeWithInlineStyle();
void testIccColor();
void testRenderFillLinearGradientRelativePercent();
void testRenderFillLinearGradientRelativePortion();
void testRenderFillLinearGradientUserCoord();
void testRenderFillLinearGradientStopPortion();
void testRenderFillLinearGradientTransform();
void testRenderFillLinearGradientTransformUserCoord();
void testRenderFillLinearGradientRotatedShape();
void testRenderFillLinearGradientRotatedShapeUserCoord();
void testRenderFillRadialGradient();
void testRenderFillRadialGradientUserCoord();
void testRenderFillLinearGradientUserCoordPercent();
void testRenderStrokeLinearGradient();
void testManualRenderPattern_ContentUser_RefObb();
void testManualRenderPattern_ContentObb_RefObb();
void testManualRenderPattern_ContentUser_RefUser();
void testManualRenderPattern_ContentObb_RefObb_Transform_Rotate();
void testManualRenderPattern_ContentView_RefObb();
void testManualRenderPattern_ContentView_RefUser();
void testRenderPattern_r_User_c_User();
void testRenderPattern_InfiniteRecursionWhenInherited();
void testRenderPattern_r_User_c_View();
void testRenderPattern_r_User_c_Obb();
void testRenderPattern_r_User_c_View_Rotated();
void testRenderPattern_r_Obb_c_View_Rotated();
void testKoClipPathRendering();
void testKoClipPathRelativeRendering();
void testRenderClipPath_User();
void testRenderClipPath_Obb();
void testRenderClipPath_Obb_Transform();
void testRenderClipMask_Obb();
+ void testRenderClipMaskOnGroup_Obb();
void testRenderClipMask_User_Clip_Obb();
void testRenderClipMask_User_Clip_User();
void testRenderImage_AspectDefault();
void testRenderImage_AspectNone();
void testRenderImage_AspectMeet();
void testRectShapeRoundUniformX();
void testRectShapeRoundUniformY();
void testRectShapeRoundXY();
void testRectShapeRoundXYOverflow();
void testCircleShape();
void testEllipseShape();
void testLineShape();
void testPolylineShape();
void testPolygonShape();
void testPathShape();
void testDefsHidden();
void testDefsUseInheritance();
void testUseWithoutDefs();
void testMarkersAutoOrientation();
void testMarkersAutoOrientationScaled();
void testMarkersAutoOrientationScaledUserCoordinates();
void testMarkersCustomOrientation();
void testMarkersDifferent();
void testGradientRecoveringTransform();
void testMarkersOnClosedPath();
void testMarkersAngularUnits();
void testSodipodiArcShape();
void testSodipodiArcShapeOpen();
void testKritaChordShape();
void testSodipodiChordShape();
void testMarkersFillAsShape();
private:
};
#endif // TESTSVGPARSER_H
diff --git a/libs/flake/tests/data/svg_render/load_clip_mask_on_group_obb.png b/libs/flake/tests/data/svg_render/load_clip_mask_on_group_obb.png
new file mode 100644
index 0000000000..91604d4884
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_clip_mask_on_group_obb.png differ
diff --git a/libs/flake/text/KoSvgTextChunkShape.cpp b/libs/flake/text/KoSvgTextChunkShape.cpp
index 54a6760394..fde66bb73c 100644
--- a/libs/flake/text/KoSvgTextChunkShape.cpp
+++ b/libs/flake/text/KoSvgTextChunkShape.cpp
@@ -1,985 +1,985 @@
/*
* 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
v * GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KoSvgTextChunkShape.h"
#include "KoSvgTextChunkShape_p.h"
#include "KoSvgText.h"
#include "KoSvgTextProperties.h"
#include "kis_debug.h"
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoXmlNS.h>
#include <SvgLoadingContext.h>
#include <SvgGraphicContext.h>
#include <SvgUtil.h>
#include <SimpleShapeContainerModel.h>
#include <SvgSavingContext.h>
#include <SvgStyleWriter.h>
#include <kis_dom_utils.h>
#include <text/KoSvgTextChunkShapeLayoutInterface.h>
#include <commands/KoShapeUngroupCommand.h>
#include <html/HtmlSavingContext.h>
#include <FlakeDebug.h>
namespace {
void appendLazy(QVector<qreal> *list, boost::optional<qreal> value, int iteration, bool hasDefault = true, qreal defaultValue = 0.0)
{
if (!value) return;
if (value && *value == defaultValue && hasDefault == true && list->isEmpty()) return;
while (list->size() < iteration) {
list->append(defaultValue);
}
list->append(*value);
}
void fillTransforms(QVector<qreal> *xPos, QVector<qreal> *yPos, QVector<qreal> *dxPos, QVector<qreal> *dyPos, QVector<qreal> *rotate,
QVector<KoSvgText::CharTransformation> localTransformations)
{
for (int i = 0; i < localTransformations.size(); i++) {
const KoSvgText::CharTransformation &t = localTransformations[i];
appendLazy(xPos, t.xPos, i, false);
appendLazy(yPos, t.yPos, i, false);
appendLazy(dxPos, t.dxPos, i);
appendLazy(dyPos, t.dyPos, i);
appendLazy(rotate, t.rotate, i);
}
}
QVector<qreal> parseListAttributeX(const QString &value, SvgLoadingContext &context)
{
QVector<qreal> result;
QStringList list = SvgUtil::simplifyList(value);
Q_FOREACH (const QString &str, list) {
result << SvgUtil::parseUnitX(context.currentGC(), str);
}
return result;
}
QVector<qreal> parseListAttributeY(const QString &value, SvgLoadingContext &context)
{
QVector<qreal> result;
QStringList list = SvgUtil::simplifyList(value);
Q_FOREACH (const QString &str, list) {
result << SvgUtil::parseUnitY(context.currentGC(), str);
}
return result;
}
QVector<qreal> parseListAttributeAngular(const QString &value, SvgLoadingContext &context)
{
QVector<qreal> result;
QStringList list = SvgUtil::simplifyList(value);
Q_FOREACH (const QString &str, list) {
result << SvgUtil::parseUnitAngular(context.currentGC(), str);
}
return result;
}
QString convertListAttribute(const QVector<qreal> &values) {
QStringList stringValues;
Q_FOREACH (qreal value, values) {
stringValues.append(KisDomUtils::toString(value));
}
return stringValues.join(',');
}
}
struct KoSvgTextChunkShape::Private::LayoutInterface : public KoSvgTextChunkShapeLayoutInterface
{
LayoutInterface(KoSvgTextChunkShape *_q) : q(_q) {}
KoSvgText::AutoValue textLength() const override {
- return q->d->textLength;
+ return q->s->textLength;
}
KoSvgText::LengthAdjust lengthAdjust() const override {
- return q->d->lengthAdjust;
+ return q->s->lengthAdjust;
}
int numChars() const override {
- KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d->text.isEmpty(), 0);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->s->text.isEmpty(), 0);
int result = 0;
if (!q->shapeCount()) {
- result = q->d->text.size();
+ result = q->s->text.size();
} else {
Q_FOREACH (KoShape *shape, q->shapes()) {
KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(shape);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(chunkShape, 0);
result += chunkShape->layoutInterface()->numChars();
}
}
return result;
}
int relativeCharPos(KoSvgTextChunkShape *child, int pos) const override {
QList<KoShape*> childShapes = q->shapes();
int result = -1;
int numCharsPassed = 0;
Q_FOREACH (KoShape *shape, q->shapes()) {
KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(shape);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(chunkShape, 0);
if (chunkShape == child) {
result = pos + numCharsPassed;
break;
} else {
numCharsPassed += chunkShape->layoutInterface()->numChars();
}
}
return result;
}
bool isTextNode() const override {
- KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d->text.isEmpty(), false);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->s->text.isEmpty(), false);
return !q->shapeCount();
}
QString nodeText() const override {
- KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d->text.isEmpty(), 0);
- return !q->shapeCount() ? q->d->text : QString();
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->s->text.isEmpty(), 0);
+ return !q->shapeCount() ? q->s->text : QString();
}
QVector<KoSvgText::CharTransformation> localCharTransformations() const override {
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(isTextNode(), QVector<KoSvgText::CharTransformation>());
- const QVector<KoSvgText::CharTransformation> t = q->d->localTransformations;
- return t.mid(0, qMin(t.size(), q->d->text.size()));
+ const QVector<KoSvgText::CharTransformation> t = q->s->localTransformations;
+ return t.mid(0, qMin(t.size(), q->s->text.size()));
}
static QString getBidiOpening(KoSvgText::Direction direction, KoSvgText::UnicodeBidi bidi) {
using namespace KoSvgText;
QString result;
if (bidi == BidiEmbed) {
result = direction == DirectionLeftToRight ? "\u202a" : "\u202b";
} else if (bidi == BidiOverride) {
result = direction == DirectionLeftToRight ? "\u202d" : "\u202e";
}
return result;
}
QVector<SubChunk> collectSubChunks() const override {
QVector<SubChunk> result;
if (isTextNode()) {
- const QString text = q->d->text;
+ const QString text = q->s->text;
const KoSvgText::KoSvgCharChunkFormat format = q->fetchCharFormat();
- QVector<KoSvgText::CharTransformation> transforms = q->d->localTransformations;
+ QVector<KoSvgText::CharTransformation> transforms = q->s->localTransformations;
/**
* Sometimes SVG can contain the X,Y offsets for the pieces of text that
* do not exist, just skip them.
*/
if (text.size() <= transforms.size()) {
transforms.resize(text.size());
}
- KoSvgText::UnicodeBidi bidi = KoSvgText::UnicodeBidi(q->d->properties.propertyOrDefault(KoSvgTextProperties::UnicodeBidiId).toInt());
- KoSvgText::Direction direction = KoSvgText::Direction(q->d->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
+ KoSvgText::UnicodeBidi bidi = KoSvgText::UnicodeBidi(q->s->properties.propertyOrDefault(KoSvgTextProperties::UnicodeBidiId).toInt());
+ KoSvgText::Direction direction = KoSvgText::Direction(q->s->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
const QString bidiOpening = getBidiOpening(direction, bidi);
if (!bidiOpening.isEmpty()) {
result << SubChunk(bidiOpening, format);
}
if (transforms.isEmpty()) {
result << SubChunk(text, format);
} else {
for (int i = 0; i < transforms.size(); i++) {
const KoSvgText::CharTransformation baseTransform = transforms[i];
int subChunkLength = 1;
for (int j = i + 1; j < transforms.size(); j++) {
if (transforms[j].isNull()) {
subChunkLength++;
} else {
break;
}
}
if (i + subChunkLength >= transforms.size()) {
subChunkLength = text.size() - i;
}
result << SubChunk(text.mid(i, subChunkLength), format, baseTransform);
i += subChunkLength - 1;
}
}
if (!bidiOpening.isEmpty()) {
result << SubChunk("\u202c", format);
}
} else {
Q_FOREACH (KoShape *shape, q->shapes()) {
KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(shape);
KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape);
result += chunkShape->layoutInterface()->collectSubChunks();
}
}
return result;
}
void addAssociatedOutline(const QRectF &rect) override {
KIS_SAFE_ASSERT_RECOVER_RETURN(isTextNode());
QPainterPath path;
path.addRect(rect);
- path |= q->d->associatedOutline;
+ path |= q->s->associatedOutline;
path.setFillRule(Qt::WindingFill);
path = path.simplified();
- q->d->associatedOutline = path;
+ q->s->associatedOutline = path;
q->setSize(path.boundingRect().size());
q->notifyChanged();
q->shapeChangedPriv(KoShape::SizeChanged);
}
void clearAssociatedOutline() override {
- q->d->associatedOutline = QPainterPath();
+ q->s->associatedOutline = QPainterPath();
q->setSize(QSizeF());
q->notifyChanged();
q->shapeChangedPriv(KoShape::SizeChanged);
}
private:
KoSvgTextChunkShape *q;
};
KoSvgTextChunkShape::KoSvgTextChunkShape()
: KoShapeContainer()
, d(new Private)
+ , s(new SharedData)
{
d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this));
}
KoSvgTextChunkShape::KoSvgTextChunkShape(const KoSvgTextChunkShape &rhs)
: KoShapeContainer(rhs)
- , d(rhs.d)
+ , d(new Private)
+ , s(rhs.s)
{
if (rhs.model()) {
SimpleShapeContainerModel *otherModel = dynamic_cast<SimpleShapeContainerModel*>(rhs.model());
KIS_ASSERT_RECOVER_RETURN(otherModel);
setModelInit(new SimpleShapeContainerModel(*otherModel));
}
- // XXX: this will immediately lead to a detach
d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this));
}
KoSvgTextChunkShape::~KoSvgTextChunkShape()
{
}
KoShape *KoSvgTextChunkShape::cloneShape() const
{
return new KoSvgTextChunkShape(*this);
}
QSizeF KoSvgTextChunkShape::size() const
{
return outlineRect().size();
}
void KoSvgTextChunkShape::setSize(const QSizeF &size)
{
Q_UNUSED(size);
// we do not support resizing!
}
QRectF KoSvgTextChunkShape::outlineRect() const
{
return outline().boundingRect();
}
QPainterPath KoSvgTextChunkShape::outline() const
{
QPainterPath result;
result.setFillRule(Qt::WindingFill);
if (d->layoutInterface->isTextNode()) {
- result = d->associatedOutline;
+ result = s->associatedOutline;
} else {
Q_FOREACH (KoShape *shape, shapes()) {
KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(shape);
KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape);
result |= chunkShape->outline();
}
}
return result.simplified();
}
-void KoSvgTextChunkShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
+void KoSvgTextChunkShape::paintComponent(QPainter &painter, KoShapePaintingContext &paintContext) const
{
Q_UNUSED(painter);
- Q_UNUSED(converter);
Q_UNUSED(paintContext);
}
void KoSvgTextChunkShape::saveOdf(KoShapeSavingContext &context) const
{
Q_UNUSED(context);
}
bool KoSvgTextChunkShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
Q_UNUSED(element);
Q_UNUSED(context);
return false;
}
bool KoSvgTextChunkShape::saveHtml(HtmlSavingContext &context)
{
// Should we add a newline? Check for vertical movement if we're using rtl or ltr text
// XXX: if vertical text, check horizontal movement.
QVector<qreal> xPos;
QVector<qreal> yPos;
QVector<qreal> dxPos;
QVector<qreal> dyPos;
QVector<qreal> rotate;
- fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, d->localTransformations);
+ fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, s->localTransformations);
- for (int i = 0; i < d->localTransformations.size(); i++) {
- const KoSvgText::CharTransformation &t = d->localTransformations[i];
+ for (int i = 0; i < s->localTransformations.size(); i++) {
+ const KoSvgText::CharTransformation &t = s->localTransformations[i];
appendLazy(&xPos, t.xPos, i, false);
appendLazy(&yPos, t.yPos, i, false);
appendLazy(&dxPos, t.dxPos, i);
appendLazy(&dyPos, t.dyPos, i);
}
KoSvgTextChunkShape *parent = !isRootTextNode() ? dynamic_cast<KoSvgTextChunkShape*>(this->parent()) : 0;
KoSvgTextProperties parentProperties =
parent ? parent->textProperties() : KoSvgTextProperties::defaultProperties();
// XXX: we don't save fill, stroke, text length, length adjust or spacing and glyphs.
KoSvgTextProperties ownProperties = textProperties().ownProperties(parentProperties);
if (isRootTextNode()) {
context.shapeWriter().startElement("body", false);
if (layoutInterface()->isTextNode()) {
context.shapeWriter().startElement("p", false);
}
// XXX: Save the style?
} else if (parent && parent->isRootTextNode()) {
context.shapeWriter().startElement("p", false);
} else {
context.shapeWriter().startElement("span", false);
// XXX: Save the style?
}
QMap<QString, QString> attributes = ownProperties.convertToSvgTextAttributes();
if (attributes.size() > 0) {
QString styleString;
for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) {
if (QString(it.key().toLatin1().data()).contains("text-anchor")) {
QString val = it.value();
if (it.value()=="middle") {
val = "center";
} else if (it.value()=="end") {
val = "right";
} else {
val = "left";
}
styleString.append("text-align")
.append(": ")
.append(val)
.append(";" );
} else if (QString(it.key().toLatin1().data()).contains("fill")){
styleString.append("color")
.append(": ")
.append(it.value())
.append(";" );
} else if (QString(it.key().toLatin1().data()).contains("font-size")){
QString val = it.value();
if (QRegExp ("\\d*").exactMatch(val)) {
val.append("pt");
}
styleString.append(it.key().toLatin1().data())
.append(": ")
.append(val)
.append(";" );
} else {
styleString.append(it.key().toLatin1().data())
.append(": ")
.append(it.value())
.append(";" );
}
}
context.shapeWriter().addAttribute("style", styleString);
}
if (layoutInterface()->isTextNode()) {
- debugFlake << "saveHTML" << this << d->text << xPos << yPos << dxPos << dyPos;
+ debugFlake << "saveHTML" << this << s->text << xPos << yPos << dxPos << dyPos;
// After adding all the styling to the <p> element, add the text
- context.shapeWriter().addTextNode(d->text);
+ context.shapeWriter().addTextNode(s->text);
}
else {
Q_FOREACH (KoShape *child, this->shapes()) {
KoSvgTextChunkShape *childText = dynamic_cast<KoSvgTextChunkShape*>(child);
KIS_SAFE_ASSERT_RECOVER(childText) { continue; }
childText->saveHtml(context);
}
}
if (isRootTextNode() && layoutInterface()->isTextNode()) {
context.shapeWriter().endElement(); // body
}
context.shapeWriter().endElement(); // p or span
return true;
}
void writeTextListAttribute(const QString &attribute, const QVector<qreal> &values, KoXmlWriter &writer)
{
const QString value = convertListAttribute(values);
if (!value.isEmpty()) {
writer.addAttribute(attribute.toLatin1().data(), value);
}
}
bool KoSvgTextChunkShape::saveSvg(SvgSavingContext &context)
{
if (isRootTextNode()) {
context.shapeWriter().startElement("text", false);
if (!context.strippedTextMode()) {
context.shapeWriter().addAttribute("id", context.getID(this));
- context.shapeWriter().addAttribute("krita:useRichText", d->isRichTextPreferred ? "true" : "false");
+ context.shapeWriter().addAttribute("krita:useRichText", s->isRichTextPreferred ? "true" : "false");
SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter());
SvgStyleWriter::saveSvgStyle(this, context);
} else {
SvgStyleWriter::saveSvgFill(this, context);
SvgStyleWriter::saveSvgStroke(this, context);
}
} else {
context.shapeWriter().startElement("tspan", false);
if (!context.strippedTextMode()) {
SvgStyleWriter::saveSvgBasicStyle(this, context);
}
}
if (layoutInterface()->isTextNode()) {
QVector<qreal> xPos;
QVector<qreal> yPos;
QVector<qreal> dxPos;
QVector<qreal> dyPos;
QVector<qreal> rotate;
- fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, d->localTransformations);
+ fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, s->localTransformations);
writeTextListAttribute("x", xPos, context.shapeWriter());
writeTextListAttribute("y", yPos, context.shapeWriter());
writeTextListAttribute("dx", dxPos, context.shapeWriter());
writeTextListAttribute("dy", dyPos, context.shapeWriter());
writeTextListAttribute("rotate", rotate, context.shapeWriter());
}
- if (!d->textLength.isAuto) {
- context.shapeWriter().addAttribute("textLength", KisDomUtils::toString(d->textLength.customValue));
+ if (!s->textLength.isAuto) {
+ context.shapeWriter().addAttribute("textLength", KisDomUtils::toString(s->textLength.customValue));
- if (d->lengthAdjust == KoSvgText::LengthAdjustSpacingAndGlyphs) {
+ if (s->lengthAdjust == KoSvgText::LengthAdjustSpacingAndGlyphs) {
context.shapeWriter().addAttribute("lengthAdjust", "spacingAndGlyphs");
}
}
KoSvgTextChunkShape *parent = !isRootTextNode() ? dynamic_cast<KoSvgTextChunkShape*>(this->parent()) : 0;
KoSvgTextProperties parentProperties =
parent ? parent->textProperties() : KoSvgTextProperties::defaultProperties();
KoSvgTextProperties ownProperties = textProperties().ownProperties(parentProperties);
// we write down stroke/fill iff they are different from the parent's value
if (!isRootTextNode()) {
if (ownProperties.hasProperty(KoSvgTextProperties::FillId)) {
SvgStyleWriter::saveSvgFill(this, context);
}
if (ownProperties.hasProperty(KoSvgTextProperties::StrokeId)) {
SvgStyleWriter::saveSvgStroke(this, context);
}
}
QMap<QString, QString> attributes = ownProperties.convertToSvgTextAttributes();
for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) {
context.shapeWriter().addAttribute(it.key().toLatin1().data(), it.value());
}
if (layoutInterface()->isTextNode()) {
- context.shapeWriter().addTextNode(d->text);
+ context.shapeWriter().addTextNode(s->text);
} else {
Q_FOREACH (KoShape *child, this->shapes()) {
KoSvgTextChunkShape *childText = dynamic_cast<KoSvgTextChunkShape*>(child);
KIS_SAFE_ASSERT_RECOVER(childText) { continue; }
childText->saveSvg(context);
}
}
context.shapeWriter().endElement();
return true;
}
-void KoSvgTextChunkShape::Private::loadContextBasedProperties(SvgGraphicsContext *gc)
+void KoSvgTextChunkShape::SharedData::loadContextBasedProperties(SvgGraphicsContext *gc)
{
properties = gc->textProperties;
font = gc->font;
fontFamiliesList = gc->fontFamiliesList;
}
void KoSvgTextChunkShape::resetTextShape()
{
using namespace KoSvgText;
- d->properties = KoSvgTextProperties();
- d->font = QFont();
- d->fontFamiliesList = QStringList();
+ s->properties = KoSvgTextProperties();
+ s->font = QFont();
+ s->fontFamiliesList = QStringList();
- d->textLength = AutoValue();
- d->lengthAdjust = LengthAdjustSpacing;
+ s->textLength = AutoValue();
+ s->lengthAdjust = LengthAdjustSpacing;
- d->localTransformations.clear();
- d->text.clear();
+ s->localTransformations.clear();
+ s->text.clear();
// all the subchunks are destroyed!
// (first detach, then destroy)
QList<KoShape*> shapesToReset = shapes();
Q_FOREACH (KoShape *shape, shapesToReset) {
shape->setParent(0);
delete shape;
}
}
bool KoSvgTextChunkShape::loadSvg(const KoXmlElement &e, SvgLoadingContext &context)
{
SvgGraphicsContext *gc = context.currentGC();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(gc, false);
- d->loadContextBasedProperties(gc);
+ s->loadContextBasedProperties(gc);
- d->textLength = KoSvgText::parseAutoValueXY(e.attribute("textLength", ""), context, "");
- d->lengthAdjust = KoSvgText::parseLengthAdjust(e.attribute("lengthAdjust", "spacing"));
+ s->textLength = KoSvgText::parseAutoValueXY(e.attribute("textLength", ""), context, "");
+ s->lengthAdjust = KoSvgText::parseLengthAdjust(e.attribute("lengthAdjust", "spacing"));
QVector<qreal> xPos = parseListAttributeX(e.attribute("x", ""), context);
QVector<qreal> yPos = parseListAttributeY(e.attribute("y", ""), context);
QVector<qreal> dxPos = parseListAttributeX(e.attribute("dx", ""), context);
QVector<qreal> dyPos = parseListAttributeY(e.attribute("dy", ""), context);
QVector<qreal> rotate = parseListAttributeAngular(e.attribute("rotate", ""), context);
const int numLocalTransformations =
std::max({xPos.size(), yPos.size(),
dxPos.size(), dyPos.size(),
rotate.size()});
- d->localTransformations.resize(numLocalTransformations);
+ s->localTransformations.resize(numLocalTransformations);
for (int i = 0; i < numLocalTransformations; i++) {
if (i < xPos.size()) {
- d->localTransformations[i].xPos = xPos[i];
+ s->localTransformations[i].xPos = xPos[i];
}
if (i < yPos.size()) {
- d->localTransformations[i].yPos = yPos[i];
+ s->localTransformations[i].yPos = yPos[i];
}
if (i < dxPos.size() && dxPos[i] != 0.0) {
- d->localTransformations[i].dxPos = dxPos[i];
+ s->localTransformations[i].dxPos = dxPos[i];
}
if (i < dyPos.size() && dyPos[i] != 0.0) {
- d->localTransformations[i].dyPos = dyPos[i];
+ s->localTransformations[i].dyPos = dyPos[i];
}
if (i < rotate.size()) {
- d->localTransformations[i].rotate = rotate[i];
+ s->localTransformations[i].rotate = rotate[i];
}
}
return true;
}
namespace {
QString cleanUpString(QString text) {
text.replace(QRegExp("[\\r\\n\u2028]"), "");
text.replace(QRegExp(" {2,}"), " ");
return text;
}
enum Result {
FoundNothing,
FoundText,
FoundSpace
};
Result hasPreviousSibling(KoXmlNode node)
{
while (!node.isNull()) {
if (node.isElement()) {
KoXmlElement element = node.toElement();
if (element.tagName() == "text") break;
}
while (!node.previousSibling().isNull()) {
node = node.previousSibling();
while (!node.lastChild().isNull()) {
node = node.lastChild();
}
if (node.isText()) {
KoXmlText textNode = node.toText();
const QString text = cleanUpString(textNode.data());
if (!text.isEmpty()) {
// if we are the leading whitespace, we should report that
// we are the last
if (text == " ") {
return hasPreviousSibling(node) == FoundNothing ? FoundNothing : FoundSpace;
}
return text[text.size() - 1] != ' ' ? FoundText : FoundSpace;
}
}
}
node = node.parentNode();
}
return FoundNothing;
}
Result hasNextSibling(KoXmlNode node)
{
while (!node.isNull()) {
while (!node.nextSibling().isNull()) {
node = node.nextSibling();
while (!node.firstChild().isNull()) {
node = node.firstChild();
}
if (node.isText()) {
KoXmlText textNode = node.toText();
const QString text = cleanUpString(textNode.data());
// if we are the trailing whitespace, we should report that
// we are the last
if (text == " ") {
return hasNextSibling(node) == FoundNothing ? FoundNothing : FoundSpace;
}
if (!text.isEmpty()) {
return text[0] != ' ' ? FoundText : FoundSpace;
}
}
}
node = node.parentNode();
}
return FoundNothing;
}
}
bool KoSvgTextChunkShape::loadSvgTextNode(const KoXmlText &text, SvgLoadingContext &context)
{
SvgGraphicsContext *gc = context.currentGC();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(gc, false);
- d->loadContextBasedProperties(gc);
+ s->loadContextBasedProperties(gc);
QString data = cleanUpString(text.data());
const Result leftBorder = hasPreviousSibling(text);
const Result rightBorder = hasNextSibling(text);
if (data.startsWith(' ') && leftBorder == FoundNothing) {
data.remove(0, 1);
}
if (data.endsWith(' ') && rightBorder != FoundText) {
data.remove(data.size() - 1, 1);
}
if (data == " " && (leftBorder == FoundNothing || rightBorder == FoundNothing)) {
data = "";
}
//ENTER_FUNCTION() << text.data() << "-->" << data;
- d->text = data;
+ s->text = data;
return !data.isEmpty();
}
void KoSvgTextChunkShape::normalizeCharTransformations()
{
- applyParentCharTransformations(d->localTransformations);
+ applyParentCharTransformations(s->localTransformations);
}
void KoSvgTextChunkShape::simplifyFillStrokeInheritance()
{
if (!isRootTextNode()) {
KoShape *parentShape = parent();
KIS_SAFE_ASSERT_RECOVER_RETURN(parentShape);
QSharedPointer<KoShapeBackground> bg = background();
QSharedPointer<KoShapeBackground> parentBg = parentShape->background();
if (!inheritBackground() &&
((!bg && !parentBg) ||
(bg && parentBg &&
bg->compareTo(parentShape->background().data())))) {
setInheritBackground(true);
}
KoShapeStrokeModelSP stroke = this->stroke();
KoShapeStrokeModelSP parentStroke= parentShape->stroke();
if (!inheritStroke() &&
((!stroke && !parentStroke) ||
(stroke && parentStroke &&
stroke->compareFillTo(parentShape->stroke().data()) &&
stroke->compareStyleTo(parentShape->stroke().data())))) {
setInheritStroke(true);
}
}
Q_FOREACH (KoShape *shape, shapes()) {
KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(shape);
KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape);
chunkShape->simplifyFillStrokeInheritance();
}
}
KoSvgTextProperties KoSvgTextChunkShape::textProperties() const
{
- KoSvgTextProperties properties = d->properties;
+ KoSvgTextProperties properties = s->properties;
properties.setProperty(KoSvgTextProperties::FillId, QVariant::fromValue(KoSvgText::BackgroundProperty(background())));
properties.setProperty(KoSvgTextProperties::StrokeId, QVariant::fromValue(KoSvgText::StrokeProperty(stroke())));
return properties;
}
bool KoSvgTextChunkShape::isTextNode() const
{
return d->layoutInterface->isTextNode();
}
-KoSvgTextChunkShapeLayoutInterface *KoSvgTextChunkShape::layoutInterface()
+KoSvgTextChunkShapeLayoutInterface *KoSvgTextChunkShape::layoutInterface() const
{
return d->layoutInterface.data();
}
bool KoSvgTextChunkShape::isRichTextPreferred() const
{
- return isRootTextNode() && d->isRichTextPreferred;
+ return isRootTextNode() && s->isRichTextPreferred;
}
void KoSvgTextChunkShape::setRichTextPreferred(bool value)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(isRootTextNode());
- d->isRichTextPreferred = value;
+ s->isRichTextPreferred = value;
}
bool KoSvgTextChunkShape::isRootTextNode() const
{
return false;
}
/**************************************************************************************************/
/* KoSvgTextChunkShape::Private */
/**************************************************************************************************/
#include "SimpleShapeContainerModel.h"
-KoSvgTextChunkShape::Private::Private()
+KoSvgTextChunkShape::SharedData::SharedData()
: QSharedData()
{
}
-KoSvgTextChunkShape::Private::Private(const Private &rhs)
+KoSvgTextChunkShape::SharedData::SharedData(const SharedData &rhs)
: QSharedData()
, properties(rhs.properties)
, font(rhs.font)
, fontFamiliesList(rhs.fontFamiliesList)
, localTransformations(rhs.localTransformations)
, textLength(rhs.textLength)
, lengthAdjust(rhs.lengthAdjust)
, text(rhs.text)
, isRichTextPreferred(rhs.isRichTextPreferred)
{
}
-KoSvgTextChunkShape::Private::~Private()
+KoSvgTextChunkShape::SharedData::~SharedData()
{
}
#include <QBrush>
#include <KoColorBackground.h>
#include <KoShapeStroke.h>
KoSvgText::KoSvgCharChunkFormat KoSvgTextChunkShape::fetchCharFormat() const
{
KoSvgText::KoSvgCharChunkFormat format;
- format.setFont(d->font);
- format.setTextAnchor(KoSvgText::TextAnchor(d->properties.propertyOrDefault(KoSvgTextProperties::TextAnchorId).toInt()));
+ format.setFont(s->font);
+ format.setTextAnchor(KoSvgText::TextAnchor(s->properties.propertyOrDefault(KoSvgTextProperties::TextAnchorId).toInt()));
KoSvgText::Direction direction =
- KoSvgText::Direction(d->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
+ KoSvgText::Direction(s->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt());
format.setLayoutDirection(direction == KoSvgText::DirectionLeftToRight ? Qt::LeftToRight : Qt::RightToLeft);
KoSvgText::BaselineShiftMode shiftMode =
- KoSvgText::BaselineShiftMode(d->properties.propertyOrDefault(KoSvgTextProperties::BaselineShiftModeId).toInt());
+ KoSvgText::BaselineShiftMode(s->properties.propertyOrDefault(KoSvgTextProperties::BaselineShiftModeId).toInt());
// FIXME: we support only 'none', 'sub' and 'super' shifts at the moment.
// Please implement 'percentage' as well!
// WARNING!!! Qt's setVerticalAlignment() also changes the size of the font! And SVG does not(!) imply it!
if (shiftMode == KoSvgText::ShiftSub) {
format.setVerticalAlignment(QTextCharFormat::AlignSubScript);
} else if (shiftMode == KoSvgText::ShiftSuper) {
format.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
}
- KoSvgText::AutoValue letterSpacing = d->properties.propertyOrDefault(KoSvgTextProperties::LetterSpacingId).value<KoSvgText::AutoValue>();
+ KoSvgText::AutoValue letterSpacing = s->properties.propertyOrDefault(KoSvgTextProperties::LetterSpacingId).value<KoSvgText::AutoValue>();
if (!letterSpacing.isAuto) {
format.setFontLetterSpacingType(QFont::AbsoluteSpacing);
format.setFontLetterSpacing(letterSpacing.customValue);
}
- KoSvgText::AutoValue wordSpacing = d->properties.propertyOrDefault(KoSvgTextProperties::WordSpacingId).value<KoSvgText::AutoValue>();
+ KoSvgText::AutoValue wordSpacing = s->properties.propertyOrDefault(KoSvgTextProperties::WordSpacingId).value<KoSvgText::AutoValue>();
if (!wordSpacing.isAuto) {
format.setFontWordSpacing(wordSpacing.customValue);
}
- KoSvgText::AutoValue kerning = d->properties.propertyOrDefault(KoSvgTextProperties::KerningId).value<KoSvgText::AutoValue>();
+ KoSvgText::AutoValue kerning = s->properties.propertyOrDefault(KoSvgTextProperties::KerningId).value<KoSvgText::AutoValue>();
if (!kerning.isAuto) {
format.setFontKerning(false);
format.setFontLetterSpacingType(QFont::AbsoluteSpacing);
format.setFontLetterSpacing(format.fontLetterSpacing() + kerning.customValue);
}
QBrush textBrush = Qt::NoBrush;
if (background()) {
KoColorBackground *colorBackground = dynamic_cast<KoColorBackground*>(background().data());
if (!colorBackground) {
qWarning() << "TODO: support gradient and pattern backgrounds for text";
textBrush = Qt::red;
}
if (colorBackground) {
textBrush = colorBackground->brush();
}
}
format.setForeground(textBrush);
QPen textPen = Qt::NoPen;
if (stroke()) {
KoShapeStroke *stroke = dynamic_cast<KoShapeStroke*>(this->stroke().data());
if (stroke) {
textPen = stroke->resultLinePen();
}
}
format.setTextOutline(textPen);
// TODO: avoid const_cast somehow...
format.setAssociatedShape(const_cast<KoSvgTextChunkShape*>(this));
return format;
}
void KoSvgTextChunkShape::applyParentCharTransformations(const QVector<KoSvgText::CharTransformation> transformations)
{
if (shapeCount()) {
int numCharsPassed = 0;
Q_FOREACH (KoShape *shape, shapes()) {
KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(shape);
KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape);
const int numCharsInSubtree = chunkShape->layoutInterface()->numChars();
QVector<KoSvgText::CharTransformation> t = transformations.mid(numCharsPassed, numCharsInSubtree);
if (t.isEmpty()) break;
chunkShape->applyParentCharTransformations(t);
numCharsPassed += numCharsInSubtree;
if (numCharsPassed >= transformations.size()) break;
}
} else {
- for (int i = 0; i < qMin(transformations.size(), d->text.size()); i++) {
- KIS_SAFE_ASSERT_RECOVER_RETURN(d->localTransformations.size() >= i);
+ for (int i = 0; i < qMin(transformations.size(), s->text.size()); i++) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(s->localTransformations.size() >= i);
- if (d->localTransformations.size() == i) {
- d->localTransformations.append(transformations[i]);
+ if (s->localTransformations.size() == i) {
+ s->localTransformations.append(transformations[i]);
} else {
- d->localTransformations[i].mergeInParentTransformation(transformations[i]);
+ s->localTransformations[i].mergeInParentTransformation(transformations[i]);
}
}
}
}
diff --git a/libs/flake/text/KoSvgTextChunkShape.h b/libs/flake/text/KoSvgTextChunkShape.h
index cd310b9d48..87f4dbb1d0 100644
--- a/libs/flake/text/KoSvgTextChunkShape.h
+++ b/libs/flake/text/KoSvgTextChunkShape.h
@@ -1,181 +1,184 @@
/*
* 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 KOSVGTEXTCHUNKSHAPE_H
#define KOSVGTEXTCHUNKSHAPE_H
#include "kritaflake_export.h"
#include <KoShapeContainer.h>
#include <SvgShape.h>
#include <KoSvgText.h>
class HtmlSavingContext;
class KoSvgTextProperties;
class KoSvgTextChunkShapePrivate;
class KoSvgTextChunkShapeLayoutInterface;
/**
* KoSvgTextChunkShape is an elementary block of SVG text object.
*
* KoSvgTextChunkShape represents either a \<tspan\> or \<text\> element of SVG.
* The chunk shape uses flake hierarchy to represent the DOM hierarchy of the
* supplied text. All the attributes of text blocks can be fetched using
* textProperties() method.
*
* KoSvgTextChunkShape uses special text properties object to overcome the
* flake's "property inheritance" limitation. Basically, flake doesn't support
* the inheritance of shape properties: every shape stores all the properties
* that were defined at the stage of loading/creation. KoSvgTextProperties is a
* wrapper that allows the user to compare the properties of the two shapes and
* return only the ones that are unique for a child shape. That allows us to
* generate a correct SVG/markup code that can be edited by the user easily.
*
* WARNING: beware the difference between "svg-text-chunk" and
* KoSvgTextChunkShape! The chunk shape is **not** a "text chunk" in SVG's
* definition. According to SVG, "text chunk" is a set of characters anchored to
* a specific absolute position on canvas. And KoSvgTextChunkShape is just one
* \<tspan\> or \<text\> element. Obviously, one \<tspan\> can contain multiple "text
* chunks" and, vice versa, a "text chunk" can spread onto multiple \<span\>'s.
*/
class KRITAFLAKE_EXPORT KoSvgTextChunkShape : public KoShapeContainer, public SvgShape
{
public:
KoSvgTextChunkShape();
KoSvgTextChunkShape(const KoSvgTextChunkShape &rhs);
~KoSvgTextChunkShape() override;
KoShape* cloneShape() const override;
QSizeF size() const override;
void setSize(const QSizeF &size) override;
QRectF outlineRect() const override;
QPainterPath outline() const override;
- void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override;
+ void paintComponent(QPainter &painter, KoShapePaintingContext &paintContext) const override;
void saveOdf(KoShapeSavingContext &Context) const override;
bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &Context) override;
bool saveHtml(HtmlSavingContext &context);
/**
* Reset the text shape into initial state, removing all the child shapes.
* This method is used by text-updating code to upload the updated text
* into shape. The uploading code first calls resetTextShape() and then adds
* new children.
*/
virtual void resetTextShape();
bool saveSvg(SvgSavingContext &context) override;
bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context) override;
bool loadSvgTextNode(const KoXmlText &text, SvgLoadingContext &context);
/**
* Normalize the SVG character transformations
*
* In SVG x,y,dx,dy,rotate attributes are inherited from the parent shapes
* in a curious ways. We do not want to unwind the links on the fly, so we
* just parse and adjust them right when the loading completed. After
* normalizeCharTransformations() is finished, all the child shapes will
* have local transformations set according to the parent's lists.
*/
void normalizeCharTransformations();
/**
* Compress the inheritance of 'fill' and 'stroke'.
*
* The loading code makes no difference if the fill or stroke was inherited
* or not. That is not a problem for normal shapes, but it cannot work for
* text shapes. The text has different inheritance rules: if the parent
* text chunk has a gradient fill, its inheriting descendants will
* **smoothly continue** this gradient. They will not start a new gradient
* in their local coordinate system.
*
* Therefore, after loading, the loading code calls
* simplifyFillStrokeInheritance() which marks the coinciding strokes and
* fills so that the rendering code will be able to distinguish them in the
* future.
*
* Proper fill inheritance is also needed for the GUI. When the user
* changes the color of the parent text chunk, all the inheriting children
* should update its color automatically, without GUI recursively
* traversing the shapes.
*
*/
void simplifyFillStrokeInheritance();
/**
* SVG properties of the text chunk
* @return the properties object with fill and stroke included as a property
*/
KoSvgTextProperties textProperties() const;
/**
* Return the type of the chunk.
*
* The chunk can be either a "text chunk", that contains a text string
* itself, of an "intermediate chunk" that doesn't contain any text itself,
* but works as a group for a set of child chunks, which might be either
* text (leaf) or intermediate chunks. Such groups are needed to define a
* common text style for a group of '\<tspan\>' objects.
*
* @return true if the chunk is a "text chunk" false if it is "intermediate chunk"
*/
bool isTextNode() const;
/**
* A special interface for KoSvgTextShape's layout code. Don't use it
* unless you are KoSvgTextShape.
*/
- KoSvgTextChunkShapeLayoutInterface* layoutInterface();
+ KoSvgTextChunkShapeLayoutInterface* layoutInterface() const;
/**
* WARNING: this propperty is available only if isRootTextNode() is true
*
* @return true if the shape should be edited in a rich-text editor
*/
bool isRichTextPreferred() const;
/**
* WARNING: this propperty is available only if isRootTextNode() is true
*
* Sets whether the shape should be edited in rich-text editor
*/
void setRichTextPreferred(bool value);
protected:
/**
* Show if the shape is a root of the text hierarchy. Always true for
* KoSvgTextShape and always false for KoSvgTextChunkShape
*/
virtual bool isRootTextNode() const;
protected:
KoSvgTextChunkShape(KoSvgTextChunkShapePrivate *dd);
private:
KoSvgText::KoSvgCharChunkFormat fetchCharFormat() const;
void applyParentCharTransformations(const QVector<KoSvgText::CharTransformation> transformations);
private:
class Private;
- QSharedDataPointer<Private> d;
+ QScopedPointer<Private> d;
+
+ class SharedData;
+ QSharedDataPointer<SharedData> s;
};
#endif // KOSVGTEXTCHUNKSHAPE_H
diff --git a/libs/flake/text/KoSvgTextChunkShape_p.h b/libs/flake/text/KoSvgTextChunkShape_p.h
index 602a932336..3c440efedb 100644
--- a/libs/flake/text/KoSvgTextChunkShape_p.h
+++ b/libs/flake/text/KoSvgTextChunkShape_p.h
@@ -1,55 +1,59 @@
/*
* 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 "KoSvgTextChunkShape.h"
#include "KoSvgText.h"
#include "KoSvgTextProperties.h"
#include <QSharedData>
#include <QTextCharFormat>
class SvgGraphicsContext;
-class KoSvgTextChunkShape::Private : public QSharedData
+class KoSvgTextChunkShape::SharedData : public QSharedData
{
public:
- Private();
- Private(const Private &rhs);
- ~Private();
+ SharedData();
+ SharedData(const SharedData &rhs);
+ ~SharedData();
KoSvgTextProperties properties;
QFont font;
QStringList fontFamiliesList;
QVector<KoSvgText::CharTransformation> localTransformations;
KoSvgText::AutoValue textLength;
KoSvgText::LengthAdjust lengthAdjust = KoSvgText::LengthAdjustSpacing;
QString text;
- struct LayoutInterface;
- QScopedPointer<LayoutInterface> layoutInterface;
-
QPainterPath associatedOutline;
void loadContextBasedProperties(SvgGraphicsContext *gc);
bool isRichTextPreferred = true;
};
+class KoSvgTextChunkShape::Private
+{
+public:
+ struct LayoutInterface;
+ QScopedPointer<LayoutInterface> layoutInterface;
+};
+
diff --git a/libs/flake/text/KoSvgTextShape.cpp b/libs/flake/text/KoSvgTextShape.cpp
index 8299aeb791..0b87a0e9ae 100644
--- a/libs/flake/text/KoSvgTextShape.cpp
+++ b/libs/flake/text/KoSvgTextShape.cpp
@@ -1,649 +1,641 @@
/*
* 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 "KoSvgTextShape.h"
#include <QTextLayout>
#include <klocalizedstring.h>
#include "KoSvgText.h"
#include "KoSvgTextProperties.h"
#include <KoShapeContainer_p.h>
#include <text/KoSvgTextChunkShape_p.h>
#include <text/KoSvgTextShapeMarkupConverter.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeController.h>
#include "kis_debug.h"
#include <KoXmlReader.h>
#include <KoXmlNS.h>
#include <KoShapeLoadingContext.h>
#include <KoOdfLoadingContext.h>
#include <KoIcon.h>
#include <KoProperties.h>
#include <KoColorBackground.h>
#include <SvgLoadingContext.h>
#include <SvgGraphicContext.h>
#include <SvgUtil.h>
#include <QApplication>
#include <QThread>
#include <vector>
#include <memory>
#include <QPainter>
#include <boost/optional.hpp>
#include <text/KoSvgTextChunkShapeLayoutInterface.h>
#include <FlakeDebug.h>
#include <QSharedData>
-class KoSvgTextShape::Private : public QSharedData
+class KoSvgTextShape::Private
{
public:
- Private()
- : QSharedData()
- {
- }
-
- Private(const Private &)
- : QSharedData()
- {
- }
- std::vector<std::unique_ptr<QTextLayout>> cachedLayouts;
+ // NOTE: the cache data is shared between all the instances of
+ // the shape, though it will be reset locally if the
+ // accessing thread changes
+ std::vector<std::shared_ptr<QTextLayout>> cachedLayouts;
std::vector<QPointF> cachedLayoutsOffsets;
QThread *cachedLayoutsWorkingThread = 0;
- void clearAssociatedOutlines(KoShape *rootShape);
+ void clearAssociatedOutlines(const KoShape *rootShape);
};
KoSvgTextShape::KoSvgTextShape()
: KoSvgTextChunkShape()
, d(new Private)
{
setShapeId(KoSvgTextShape_SHAPEID);
}
KoSvgTextShape::KoSvgTextShape(const KoSvgTextShape &rhs)
: KoSvgTextChunkShape(rhs)
- , d(rhs.d)
+ , d(new Private)
{
setShapeId(KoSvgTextShape_SHAPEID);
// QTextLayout has no copy-ctor, so just relayout everything!
relayout();
}
KoSvgTextShape::~KoSvgTextShape()
{
}
KoShape *KoSvgTextShape::cloneShape() const
{
return new KoSvgTextShape(*this);
}
void KoSvgTextShape::shapeChanged(ChangeType type, KoShape *shape)
{
KoSvgTextChunkShape::shapeChanged(type, shape);
if (type == StrokeChanged || type == BackgroundChanged || type == ContentChanged) {
relayout();
}
}
-void KoSvgTextShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
+void KoSvgTextShape::paintComponent(QPainter &painter, KoShapePaintingContext &paintContext) const
{
Q_UNUSED(paintContext);
/**
* HACK ALERT:
* QTextLayout should only be accessed from the thread it has been created in.
* If the cached layout has been created in a different thread, we should just
* recreate the layouts in the current thread to be able to render them.
*/
if (QThread::currentThread() != d->cachedLayoutsWorkingThread) {
relayout();
}
- applyConversion(painter, converter);
for (int i = 0; i < (int)d->cachedLayouts.size(); i++) {
d->cachedLayouts[i]->draw(&painter, d->cachedLayoutsOffsets[i]);
}
/**
* HACK ALERT:
* The layouts of non-gui threads must be destroyed in the same thread
* they have been created. Because the thread might be restarted in the
* meantime or just destroyed, meaning that the per-thread freetype data
* will not be available.
*/
if (QThread::currentThread() != qApp->thread()) {
d->cachedLayouts.clear();
d->cachedLayoutsOffsets.clear();
d->cachedLayoutsWorkingThread = 0;
}
}
-void KoSvgTextShape::paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
+void KoSvgTextShape::paintStroke(QPainter &painter, KoShapePaintingContext &paintContext) const
{
Q_UNUSED(painter);
- Q_UNUSED(converter);
Q_UNUSED(paintContext);
// do nothing! everything is painted in paintComponent()
}
QPainterPath KoSvgTextShape::textOutline()
{
QPainterPath result;
result.setFillRule(Qt::WindingFill);
for (int i = 0; i < (int)d->cachedLayouts.size(); i++) {
const QPointF layoutOffset = d->cachedLayoutsOffsets[i];
const QTextLayout *layout = d->cachedLayouts[i].get();
for (int j = 0; j < layout->lineCount(); j++) {
QTextLine line = layout->lineAt(j);
Q_FOREACH (const QGlyphRun &run, line.glyphRuns()) {
const QVector<quint32> indexes = run.glyphIndexes();
const QVector<QPointF> positions = run.positions();
const QRawFont font = run.rawFont();
KIS_SAFE_ASSERT_RECOVER(indexes.size() == positions.size()) { continue; }
for (int k = 0; k < indexes.size(); k++) {
QPainterPath glyph = font.pathForGlyph(indexes[k]);
glyph.translate(positions[k] + layoutOffset);
result += glyph;
}
const qreal thickness = font.lineThickness();
const QRectF runBounds = run.boundingRect();
if (run.overline()) {
// the offset is calculated to be consistent with the way how Qt renders the text
const qreal y = line.y();
QRectF overlineBlob(runBounds.x(), y, runBounds.width(), thickness);
overlineBlob.translate(layoutOffset);
QPainterPath path;
path.addRect(overlineBlob);
// don't use direct addRect, because it doesn't care about Qt::WindingFill
result += path;
}
if (run.strikeOut()) {
// the offset is calculated to be consistent with the way how Qt renders the text
const qreal y = line.y() + 0.5 * line.height();
QRectF strikeThroughBlob(runBounds.x(), y, runBounds.width(), thickness);
strikeThroughBlob.translate(layoutOffset);
QPainterPath path;
path.addRect(strikeThroughBlob);
// don't use direct addRect, because it doesn't care about Qt::WindingFill
result += path;
}
if (run.underline()) {
const qreal y = line.y() + line.ascent() + font.underlinePosition();
QRectF underlineBlob(runBounds.x(), y, runBounds.width(), thickness);
underlineBlob.translate(layoutOffset);
QPainterPath path;
path.addRect(underlineBlob);
// don't use direct addRect, because it doesn't care about Qt::WindingFill
result += path;
}
}
}
}
return result;
}
void KoSvgTextShape::resetTextShape()
{
KoSvgTextChunkShape::resetTextShape();
relayout();
}
struct TextChunk {
QString text;
QVector<QTextLayout::FormatRange> formats;
Qt::LayoutDirection direction = Qt::LeftToRight;
Qt::Alignment alignment = Qt::AlignLeading;
struct SubChunkOffset {
QPointF offset;
int start = 0;
};
QVector<SubChunkOffset> offsets;
boost::optional<qreal> xStartPos;
boost::optional<qreal> yStartPos;
QPointF applyStartPosOverride(const QPointF &pos) const {
QPointF result = pos;
if (xStartPos) {
result.rx() = *xStartPos;
}
if (yStartPos) {
result.ry() = *yStartPos;
}
return result;
}
};
QVector<TextChunk> mergeIntoChunks(const QVector<KoSvgTextChunkShapeLayoutInterface::SubChunk> &subChunks)
{
QVector<TextChunk> chunks;
for (auto it = subChunks.begin(); it != subChunks.end(); ++it) {
if (it->transformation.startsNewChunk() || it == subChunks.begin()) {
TextChunk newChunk = TextChunk();
newChunk.direction = it->format.layoutDirection();
newChunk.alignment = it->format.calculateAlignment();
newChunk.xStartPos = it->transformation.xPos;
newChunk.yStartPos = it->transformation.yPos;
chunks.append(newChunk);
}
TextChunk &currentChunk = chunks.last();
if (it->transformation.hasRelativeOffset()) {
TextChunk::SubChunkOffset o;
o.start = currentChunk.text.size();
o.offset = it->transformation.relativeOffset();
KIS_SAFE_ASSERT_RECOVER_NOOP(!o.offset.isNull());
currentChunk.offsets.append(o);
}
QTextLayout::FormatRange formatRange;
formatRange.start = currentChunk.text.size();
formatRange.length = it->text.size();
formatRange.format = it->format;
currentChunk.formats.append(formatRange);
currentChunk.text += it->text;
}
return chunks;
}
/**
* Qt's QTextLayout has a weird trait, it doesn't count space characters as
* distinct characters in QTextLayout::setNumColumns(), that is, if we want to
* position a block of text that starts with a space character in a specific
* position, QTextLayout will drop this space and will move the text to the left.
*
* That is why we have a special wrapper object that ensures that no spaces are
* dropped and their horizontal advance parameter is taken into account.
*/
struct LayoutChunkWrapper
{
LayoutChunkWrapper(QTextLayout *layout)
: m_layout(layout)
{
}
QPointF addTextChunk(int startPos, int length, const QPointF &textChunkStartPos)
{
QPointF currentTextPos = textChunkStartPos;
const int lastPos = startPos + length - 1;
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(startPos == m_addedChars, currentTextPos);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastPos < m_layout->text().size(), currentTextPos);
// qDebug() << m_layout->text();
QTextLine line;
std::swap(line, m_danglingLine);
if (!line.isValid()) {
line = m_layout->createLine();
}
// skip all the space characters that were not included into the Qt's text line
const int currentLineStart = line.isValid() ? line.textStart() : startPos + length;
while (startPos < currentLineStart && startPos <= lastPos) {
currentTextPos.rx() += skipSpaceCharacter(startPos);
startPos++;
}
if (startPos <= lastPos) {
// defines the number of columns to look for glyphs
const int numChars = lastPos - startPos + 1;
// Tabs break the normal column flow
// grow to avoid missing glyphs
int charOffset = 0;
int noChangeCount = 0;
while (line.textLength() < numChars) {
int tl = line.textLength();
line.setNumColumns(numChars + charOffset);
if (tl == line.textLength()) {
noChangeCount++;
// 5 columns max are needed to discover tab char. Set to 10 to be safe.
if (noChangeCount > 10) break;
} else {
noChangeCount = 0;
}
charOffset++;
}
line.setPosition(currentTextPos - QPointF(0, line.ascent()));
currentTextPos.rx() += line.horizontalAdvance();
// skip all the space characters that were not included into the Qt's text line
for (int i = line.textStart() + line.textLength(); i < lastPos; i++) {
currentTextPos.rx() += skipSpaceCharacter(i);
}
} else {
// keep the created but unused line for future use
std::swap(line, m_danglingLine);
}
m_addedChars += length;
return currentTextPos;
}
private:
qreal skipSpaceCharacter(int pos) {
const QTextCharFormat format =
formatForPos(pos, m_layout->formats());
const QChar skippedChar = m_layout->text()[pos];
KIS_SAFE_ASSERT_RECOVER_NOOP(skippedChar.isSpace() || !skippedChar.isPrint());
QFontMetrics metrics(format.font());
#if QT_VERSION >= QT_VERSION_CHECK(5,11,0)
return metrics.horizontalAdvance(skippedChar);
#else
return metrics.width(skippedChar);
#endif
}
static QTextCharFormat formatForPos(int pos, const QVector<QTextLayout::FormatRange> &formats)
{
Q_FOREACH (const QTextLayout::FormatRange &range, formats) {
if (pos >= range.start && pos < range.start + range.length) {
return range.format;
}
}
KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "pos should be within the bounds of the layouted text");
return QTextCharFormat();
}
private:
int m_addedChars = 0;
QTextLayout *m_layout;
QTextLine m_danglingLine;
};
-void KoSvgTextShape::relayout()
+void KoSvgTextShape::relayout() const
{
d->cachedLayouts.clear();
d->cachedLayoutsOffsets.clear();
d->cachedLayoutsWorkingThread = QThread::currentThread();
QPointF currentTextPos;
QVector<TextChunk> textChunks = mergeIntoChunks(layoutInterface()->collectSubChunks());
Q_FOREACH (const TextChunk &chunk, textChunks) {
- std::unique_ptr<QTextLayout> layout(new QTextLayout());
+ std::shared_ptr<QTextLayout> layout(new QTextLayout());
QTextOption option;
// WARNING: never activate this option! It breaks the RTL text layout!
//option.setFlags(QTextOption::ShowTabsAndSpaces);
option.setWrapMode(QTextOption::WrapAnywhere);
option.setUseDesignMetrics(true); // TODO: investigate if it is needed?
option.setTextDirection(chunk.direction);
layout->setText(chunk.text);
layout->setTextOption(option);
layout->setFormats(chunk.formats);
layout->setCacheEnabled(true);
layout->beginLayout();
currentTextPos = chunk.applyStartPosOverride(currentTextPos);
const QPointF anchorPointPos = currentTextPos;
int lastSubChunkStart = 0;
QPointF lastSubChunkOffset;
LayoutChunkWrapper wrapper(layout.get());
for (int i = 0; i <= chunk.offsets.size(); i++) {
const bool isFinalPass = i == chunk.offsets.size();
const int length =
!isFinalPass ?
chunk.offsets[i].start - lastSubChunkStart :
chunk.text.size() - lastSubChunkStart;
if (length > 0) {
currentTextPos += lastSubChunkOffset;
currentTextPos = wrapper.addTextChunk(lastSubChunkStart,
length,
currentTextPos);
}
if (!isFinalPass) {
lastSubChunkOffset = chunk.offsets[i].offset;
lastSubChunkStart = chunk.offsets[i].start;
}
}
layout->endLayout();
QPointF diff;
if (chunk.alignment & Qt::AlignTrailing || chunk.alignment & Qt::AlignHCenter) {
if (chunk.alignment & Qt::AlignTrailing) {
diff = currentTextPos - anchorPointPos;
} else if (chunk.alignment & Qt::AlignHCenter) {
diff = 0.5 * (currentTextPos - anchorPointPos);
}
// TODO: fix after t2b text implemented
diff.ry() = 0;
}
- d->cachedLayouts.push_back(std::move(layout));
+ d->cachedLayouts.push_back(layout);
d->cachedLayoutsOffsets.push_back(-diff);
}
d->clearAssociatedOutlines(this);
for (int i = 0; i < int(d->cachedLayouts.size()); i++) {
const QTextLayout &layout = *d->cachedLayouts[i];
const QPointF layoutOffset = d->cachedLayoutsOffsets[i];
using namespace KoSvgText;
Q_FOREACH (const QTextLayout::FormatRange &range, layout.formats()) {
const KoSvgCharChunkFormat &format =
static_cast<const KoSvgCharChunkFormat&>(range.format);
AssociatedShapeWrapper wrapper = format.associatedShapeWrapper();
const int rangeStart = range.start;
const int safeRangeLength = range.length > 0 ? range.length : layout.text().size() - rangeStart;
if (safeRangeLength <= 0) continue;
const int rangeEnd = range.start + safeRangeLength - 1;
const int firstLineIndex = layout.lineForTextPosition(rangeStart).lineNumber();
const int lastLineIndex = layout.lineForTextPosition(rangeEnd).lineNumber();
for (int i = firstLineIndex; i <= lastLineIndex; i++) {
const QTextLine line = layout.lineAt(i);
// It might happen that the range contains only one (or two)
// symbol that is a whitespace symbol. In such a case we should
// just skip this (invalid) line.
if (!line.isValid()) continue;
const int posStart = qMax(line.textStart(), rangeStart);
const int posEnd = qMin(line.textStart() + line.textLength() - 1, rangeEnd);
const QList<QGlyphRun> glyphRuns = line.glyphRuns(posStart, posEnd - posStart + 1);
Q_FOREACH (const QGlyphRun &run, glyphRuns) {
const QPointF firstPosition = run.positions().first();
const quint32 firstGlyphIndex = run.glyphIndexes().first();
const QPointF lastPosition = run.positions().last();
const quint32 lastGlyphIndex = run.glyphIndexes().last();
const QRawFont rawFont = run.rawFont();
const QRectF firstGlyphRect = rawFont.boundingRect(firstGlyphIndex).translated(firstPosition);
const QRectF lastGlyphRect = rawFont.boundingRect(lastGlyphIndex).translated(lastPosition);
QRectF rect = run.boundingRect();
/**
* HACK ALERT: there is a bug in a way how Qt calculates boundingRect()
* of the glyph run. It doesn't care about left and right bearings
* of the border chars in the run, therefore it becomes cropped.
*
* Here we just add a half-char width margin to both sides
* of the glyph run to make sure the glyphs are fully painted.
*
* BUG: 389528
* BUG: 392068
*/
rect.setLeft(qMin(rect.left(), lastGlyphRect.left()) - 0.5 * firstGlyphRect.width());
rect.setRight(qMax(rect.right(), lastGlyphRect.right()) + 0.5 * lastGlyphRect.width());
wrapper.addCharacterRect(rect.translated(layoutOffset));
}
}
}
}
}
-void KoSvgTextShape::Private::clearAssociatedOutlines(KoShape *rootShape)
+void KoSvgTextShape::Private::clearAssociatedOutlines(const KoShape *rootShape)
{
- KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(rootShape);
+ const KoSvgTextChunkShape *chunkShape = dynamic_cast<const KoSvgTextChunkShape*>(rootShape);
KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape);
chunkShape->layoutInterface()->clearAssociatedOutline();
Q_FOREACH (KoShape *child, chunkShape->shapes()) {
clearAssociatedOutlines(child);
}
}
bool KoSvgTextShape::isRootTextNode() const
{
return true;
}
KoSvgTextShapeFactory::KoSvgTextShapeFactory()
: KoShapeFactoryBase(KoSvgTextShape_SHAPEID, i18n("Text"))
{
setToolTip(i18n("SVG Text Shape"));
setIconName(koIconNameCStr("x-shape-text"));
setLoadingPriority(5);
setXmlElementNames(KoXmlNS::svg, QStringList("text"));
KoShapeTemplate t;
t.name = i18n("SVG Text");
t.iconName = koIconName("x-shape-text");
t.toolTip = i18n("SVG Text Shape");
addTemplate(t);
}
KoShape *KoSvgTextShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const
{
debugFlake << "Create default svg text shape";
KoSvgTextShape *shape = new KoSvgTextShape();
shape->setShapeId(KoSvgTextShape_SHAPEID);
KoSvgTextShapeMarkupConverter converter(shape);
converter.convertFromSvg("<text>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</text>",
"<defs/>",
QRectF(0, 0, 200, 60),
documentResources->documentResolution());
debugFlake << converter.errors() << converter.warnings();
return shape;
}
KoShape *KoSvgTextShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *documentResources) const
{
KoSvgTextShape *shape = new KoSvgTextShape();
shape->setShapeId(KoSvgTextShape_SHAPEID);
QString svgText = params->stringProperty("svgText", "<text>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</text>");
QString defs = params->stringProperty("defs" , "<defs/>");
QRectF shapeRect = QRectF(0, 0, 200, 60);
QVariant rect = params->property("shapeRect");
if (rect.type()==QVariant::RectF) {
shapeRect = rect.toRectF();
}
KoSvgTextShapeMarkupConverter converter(shape);
converter.convertFromSvg(svgText,
defs,
shapeRect,
documentResources->documentResolution());
shape->setPosition(shapeRect.topLeft());
return shape;
}
bool KoSvgTextShapeFactory::supports(const KoXmlElement &/*e*/, KoShapeLoadingContext &/*context*/) const
{
return false;
}
diff --git a/libs/flake/text/KoSvgTextShape.h b/libs/flake/text/KoSvgTextShape.h
index df71aaf783..b078929ad3 100644
--- a/libs/flake/text/KoSvgTextShape.h
+++ b/libs/flake/text/KoSvgTextShape.h
@@ -1,94 +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.
*/
#ifndef KOSVGTEXTSHAPE_H
#define KOSVGTEXTSHAPE_H
#include "kritaflake_export.h"
#include <KoShapeFactoryBase.h>
#include <KoSvgTextChunkShape.h>
#include <SvgShape.h>
class KoSvgTextProperties;
#define KoSvgTextShape_SHAPEID "KoSvgTextShapeID"
/**
* KoSvgTextShape is a root chunk of the \<text\> element subtree.
*/
class KRITAFLAKE_EXPORT KoSvgTextShape : public KoSvgTextChunkShape
{
public:
KoSvgTextShape();
KoSvgTextShape(const KoSvgTextShape &rhs);
~KoSvgTextShape() override;
KoShape* cloneShape() const override;
- void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override;
- void paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override;
+ void paintComponent(QPainter &painter, KoShapePaintingContext &paintContext) const override;
+ void paintStroke(QPainter &painter, KoShapePaintingContext &paintContext) const override;
/**
* Reset the text shape into initial shape, removing all the child shapes
* and precalculated layouts. This method is used by text-updating code to
* upload the updated text into shape. The upload code first calls
* resetTextShape() and then adds new children.
*/
void resetTextShape() override;
/**
* Create a new text layout for the current content of the text shape
* chunks tree. The user should always call relayout() after every change
* in the text shapes hierarchy.
*/
- void relayout();
+ void relayout() const;
QPainterPath textOutline();
protected:
/**
* Show if the shape is a root of the text hierarchy. Always true for
* KoSvgTextShape and always false for KoSvgTextChunkShape
*/
bool isRootTextNode() const override;
void shapeChanged(ChangeType type, KoShape *shape) override;
private:
class Private;
- QSharedDataPointer<Private> d;
+ QScopedPointer<Private> d;
};
class KoSvgTextShapeFactory : public KoShapeFactoryBase
{
public:
/// constructor
KoSvgTextShapeFactory();
~KoSvgTextShapeFactory() {}
KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const override;
KoShape *createShape(const KoProperties *params, KoDocumentResourceManager *documentResources = 0) const override;
/// Reimplemented
bool supports(const KoXmlElement &e, KoShapeLoadingContext &context) const override;
};
#endif // KOSVGTEXTSHAPE_H
diff --git a/libs/flake/text/KoSvgTextShapeMarkupConverter.h b/libs/flake/text/KoSvgTextShapeMarkupConverter.h
index 9b58ce2468..c57c21fa5b 100644
--- a/libs/flake/text/KoSvgTextShapeMarkupConverter.h
+++ b/libs/flake/text/KoSvgTextShapeMarkupConverter.h
@@ -1,144 +1,144 @@
/*
* 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 KOSVGTEXTSHAPEMARKUPCONVERTER_H
#define KOSVGTEXTSHAPEMARKUPCONVERTER_H
#include "kritaflake_export.h"
#include <QScopedPointer>
#include <QList>
#include <QTextDocument>
#include <QTextCharFormat>
class QRectF;
class KoSvgTextShape;
/**
* KoSvgTextShapeMarkupConverter is a utility class for converting a
* KoSvgTextShape to/from user-editable markup/svg representation.
*
* Please note that the converted SVG is **not** the same as when saved into
* .kra! Some attributes are dropped to make the editing is easier for the
* user.
*/
class KRITAFLAKE_EXPORT KoSvgTextShapeMarkupConverter
{
public:
KoSvgTextShapeMarkupConverter(KoSvgTextShape *shape);
~KoSvgTextShapeMarkupConverter();
/**
* Convert the text shape into two strings: text and styles. Styles string
* is non-empty only when the text has some gradient/pattern attached. It is
* intended to be places into a separate tab in the GUI.
*
* @return true on success
*/
bool convertToSvg(QString *svgText, QString *stylesText);
/**
* @brief upload the svg representation of text into the shape
* @param svgText \<text\> part of SVG
* @param stylesText \<defs\> part of SVG (used only for gradients and patterns)
* @param boundsInPixels bounds of the entire image in pixel. Used for parsing percentage units.
* @param pixelsPerInch resolution of the image where we load the shape to
*
* @return true if the text was parsed successfully. Check `errors()` and `warnings()` for details.
*/
bool convertFromSvg(const QString &svgText, const QString &stylesText, const QRectF &boundsInPixels, qreal pixelsPerInch);
/**
* @brief convertToHtml convert the text in the text shape to html
* @param htmlText will be filled with correct html representing the text in the shape
* @return @c true on success
*/
bool convertToHtml(QString *htmlText);
/**
- * @brief convertFromHtml converted Qt rich text html (and no other: http://doc.qt.io/qt-5/richtext-html-subset.html) to SVG
+ * @brief convertFromHtml converted Qt rich text html (and no other: https://doc.qt.io/qt-5/richtext-html-subset.html) to SVG
* @param htmlText the input html
* @param svgText the converted svg text element
* @param styles
* @return @c true if the conversion was successful
*/
bool convertFromHtml(const QString &htmlText, QString *svgText, QString *styles);
/**
* @brief convertDocumentToSvg
* @param doc the QTextDocument to convert.
* @param svgText the converted svg text element
* @return @c true if the conversion was successful
*/
bool convertDocumentToSvg(const QTextDocument *doc, QString *svgText);
/**
* @brief convertSvgToDocument
* @param svgText the \<text\> element and it's children as a string.
* @param doc the QTextDocument that the conversion is written to.
* @return @c true if the conversion was successful
*/
bool convertSvgToDocument(const QString &svgText, QTextDocument *doc);
/**
* A list of errors happened during loading the user's text
*/
QStringList errors() const;
/**
* A list of warnings produced during loading the user's text
*/
QStringList warnings() const;
/**
* @brief style
* creates a style string based on the blockformat and the format.
* @param format the textCharFormat of the current text.
* @param blockFormat the block format of the current text.
* @param mostCommon the most common format to compare the format to.
* @return a string that can be written into a style element.
*/
QString style(QTextCharFormat format, QTextBlockFormat blockFormat, QTextCharFormat mostCommon = QTextCharFormat());
/**
* @brief stylesFromString
* returns a qvector with two textformats:
* at 0 is the QTextCharFormat
* at 1 is the QTextBlockFormat
* @param styles a style string split at ";"
* @param currentCharFormat the current charformat to compare against.
* @param currentBlockFormat the current blockformat to compare against.
* @return A QVector with at 0 a QTextCharFormat and at 1 a QBlockCharFormat.
*/
static QVector<QTextFormat> stylesFromString(QStringList styles, QTextCharFormat currentCharFormat, QTextBlockFormat currentBlockFormat);
/**
* @brief formatDifference
* A class to get the difference between two text-char formats.
* @param test the format to test
* @param reference the format to test against.
* @return the difference between the two.
*/
QTextFormat formatDifference(QTextFormat test, QTextFormat reference);
private:
struct Private;
const QScopedPointer<Private> d;
};
#endif // KOSVGTEXTSHAPEMARKUPCONVERTER_H
diff --git a/libs/flake/tools/KoPathConnectionPointStrategy.cpp b/libs/flake/tools/KoPathConnectionPointStrategy.cpp
index 42356fad02..7c23db8a5c 100644
--- a/libs/flake/tools/KoPathConnectionPointStrategy.cpp
+++ b/libs/flake/tools/KoPathConnectionPointStrategy.cpp
@@ -1,144 +1,144 @@
/* This file is part of the KDE project
*
* Copyright (C) 2007 Boudewijn Rempt <boud@kde.org>
* Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007,2009,2011 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 "KoPathConnectionPointStrategy.h"
#include "KoPathConnectionPointStrategy_p.h"
#include "KoConnectionShape.h"
#include "KoCanvasBase.h"
#include "KoShapeManager.h"
#include "KoShapeConnectionChangeCommand.h"
#include <float.h>
#include <math.h>
const int InvalidConnectionPointId = INT_MIN;
KoPathConnectionPointStrategy::KoPathConnectionPointStrategy(KoToolBase *tool, KoConnectionShape *shape, int handleId)
: KoParameterChangeStrategy(*(new KoPathConnectionPointStrategyPrivate(tool, shape, handleId)))
{
}
KoPathConnectionPointStrategy::~KoPathConnectionPointStrategy()
{
}
void KoPathConnectionPointStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers)
{
Q_D(KoPathConnectionPointStrategy);
const qreal MAX_DISTANCE = 20.0; // TODO make user definable
const qreal MAX_DISTANCE_SQR = MAX_DISTANCE * MAX_DISTANCE;
d->newConnectionShape = 0;
d->newConnectionId = InvalidConnectionPointId;
QRectF roi(mouseLocation - QPointF(MAX_DISTANCE, MAX_DISTANCE), QSizeF(2*MAX_DISTANCE, 2*MAX_DISTANCE));
QList<KoShape*> shapes = d->tool->canvas()->shapeManager()->shapesAt(roi, true);
if (shapes.count() < 2) {
// we are not near any other shape, so remove the corresponding connection
if (d->handleId == 0)
d->connectionShape->connectFirst(0, InvalidConnectionPointId);
else
d->connectionShape->connectSecond(0, InvalidConnectionPointId);
KoParameterChangeStrategy::handleMouseMove(mouseLocation, modifiers);
} else {
qreal minimalDistance = DBL_MAX;
QPointF nearestPoint;
KoShape *nearestShape = 0;
int nearestPointId = InvalidConnectionPointId;
Q_FOREACH (KoShape* shape, shapes) {
// we do not want to connect to ourself
if (shape == d->connectionShape)
continue;
KoConnectionPoints connectionPoints = shape->connectionPoints();
if (! connectionPoints.count()) {
QSizeF size = shape->size();
connectionPoints[-1] = QPointF(0.0, 0.0);
connectionPoints[-2] = QPointF(size.width(), 0.0);
connectionPoints[-3] = QPointF(size.width(), size.height());
connectionPoints[-4] = QPointF(0.0, size.height());
connectionPoints[-5] = 0.5 * (connectionPoints[-1].position + connectionPoints[-2].position);
connectionPoints[-6] = 0.5 * (connectionPoints[-2].position + connectionPoints[-3].position);
connectionPoints[-7] = 0.5 * (connectionPoints[-3].position + connectionPoints[-4].position);
connectionPoints[-8] = 0.5 * (connectionPoints[-4].position + connectionPoints[-1].position);
}
- QPointF localMousePosition = shape->absoluteTransformation(0).inverted().map(mouseLocation);
+ QPointF localMousePosition = shape->absoluteTransformation().inverted().map(mouseLocation);
KoConnectionPoints::const_iterator cp = connectionPoints.constBegin();
KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd();
for(; cp != lastCp; ++cp) {
QPointF difference = localMousePosition - cp.value().position;
qreal distance = difference.x() * difference.x() + difference.y() * difference.y();
if (distance > MAX_DISTANCE_SQR)
continue;
if (distance < minimalDistance) {
nearestShape = shape;
nearestPoint = cp.value().position;
nearestPointId = cp.key();
minimalDistance = distance;
}
}
}
if (nearestShape) {
- nearestPoint = nearestShape->absoluteTransformation(0).map(nearestPoint);
+ nearestPoint = nearestShape->absoluteTransformation().map(nearestPoint);
} else {
nearestPoint = mouseLocation;
}
d->newConnectionShape = nearestShape;
d->newConnectionId = nearestPointId;
if (d->handleId == 0)
d->connectionShape->connectFirst(nearestShape, nearestPointId);
else
d->connectionShape->connectSecond(nearestShape, nearestPointId);
KoParameterChangeStrategy::handleMouseMove(nearestPoint, modifiers);
}
}
void KoPathConnectionPointStrategy::finishInteraction(Qt::KeyboardModifiers modifiers)
{
KoParameterChangeStrategy::finishInteraction(modifiers);
}
KUndo2Command* KoPathConnectionPointStrategy::createCommand()
{
Q_D(KoPathConnectionPointStrategy);
// check if we connect to a shape and if the connection point is already present
if (d->newConnectionShape && d->newConnectionId < 0 && d->newConnectionId != InvalidConnectionPointId) {
// map handle position into document coordinates
QPointF p = d->connectionShape->shapeToDocument(d->connectionShape->handlePosition(d->handleId));
// and add as connection point in shape coordinates
- d->newConnectionId = d->newConnectionShape->addConnectionPoint(d->newConnectionShape->absoluteTransformation(0).inverted().map(p));
+ d->newConnectionId = d->newConnectionShape->addConnectionPoint(d->newConnectionShape->absoluteTransformation().inverted().map(p));
}
KUndo2Command *cmd = KoParameterChangeStrategy::createCommand();
if (!cmd)
return 0;
// change connection
new KoShapeConnectionChangeCommand(d->connectionShape, static_cast<KoConnectionShape::HandleId>(d->handleId),
d->oldConnectionShape, d->oldConnectionId, d->newConnectionShape, d->newConnectionId, cmd);
return cmd;
}
diff --git a/libs/flake/tools/KoPathTool.cpp b/libs/flake/tools/KoPathTool.cpp
index f33a0e3e2f..405597b0e9 100644
--- a/libs/flake/tools/KoPathTool.cpp
+++ b/libs/flake/tools/KoPathTool.cpp
@@ -1,1308 +1,1308 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2012 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 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 "KoPathTool.h"
#include "KoToolBase_p.h"
#include "KoPathShape_p.h"
#include "KoPathToolHandle.h"
#include "KoCanvasBase.h"
#include "KoShapeManager.h"
#include "KoSelectedShapesProxy.h"
#include "KoDocumentResourceManager.h"
#include "KoViewConverter.h"
#include "KoSelection.h"
#include "KoPointerEvent.h"
#include "commands/KoPathPointTypeCommand.h"
#include "commands/KoPathPointInsertCommand.h"
#include "commands/KoPathPointRemoveCommand.h"
#include "commands/KoPathSegmentTypeCommand.h"
#include "commands/KoPathBreakAtPointCommand.h"
#include "commands/KoPathSegmentBreakCommand.h"
#include "commands/KoParameterToPathCommand.h"
#include "commands/KoSubpathJoinCommand.h"
#include <commands/KoMultiPathPointMergeCommand.h>
#include <commands/KoMultiPathPointJoinCommand.h>
#include <commands/KoKeepShapesSelectedCommand.h>
#include "KoParameterShape.h"
#include <text/KoSvgTextShape.h>
#include "KoPathPoint.h"
#include "KoPathPointRubberSelectStrategy.h"
#include "KoPathSegmentChangeStrategy.h"
#include "KoPathConnectionPointStrategy.h"
#include "KoParameterChangeStrategy.h"
#include "PathToolOptionWidget.h"
#include "KoConnectionShape.h"
#include "KoSnapGuide.h"
#include "KoShapeController.h"
#include "kis_action_registry.h"
#include <KisHandlePainterHelper.h>
#include <KoShapeStrokeModel.h>
#include "kis_command_utils.h"
#include "kis_pointer_utils.h"
#include <KoIcon.h>
#include <QMenu>
#include <QAction>
#include <FlakeDebug.h>
#include <klocalizedstring.h>
#include <QPainter>
#include <QBitmap>
#include <QTabWidget>
#include <math.h>
static const unsigned char needle_bits[] = {
0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01,
0x80, 0x03, 0x80, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3e, 0x00, 0x7e,
0x00, 0x7c, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x00
};
static const unsigned char needle_move_bits[] = {
0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x01,
0x80, 0x03, 0x80, 0x07, 0x10, 0x0f, 0x38, 0x1f, 0x54, 0x3e, 0xfe, 0x7e,
0x54, 0x7c, 0x38, 0x1c, 0x10, 0x18, 0x00, 0x00
};
// helper function to calculate the squared distance between two points
qreal squaredDistance(const QPointF& p1, const QPointF &p2)
{
qreal dx = p1.x()-p2.x();
qreal dy = p1.y()-p2.y();
return dx*dx + dy*dy;
}
struct KoPathTool::PathSegment {
PathSegment()
: path(0), segmentStart(0), positionOnSegment(0)
{
}
bool isValid() {
return path && segmentStart;
}
KoPathShape *path;
KoPathPoint *segmentStart;
qreal positionOnSegment;
};
KoPathTool::KoPathTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_pointSelection(this)
, m_activeHandle(0)
, m_handleRadius(3)
, m_activeSegment(0)
, m_currentStrategy(0)
, m_activatedTemporarily(false)
{
m_points = new QActionGroup(this);
// m_pointTypeGroup->setExclusive(true);
m_actionPathPointCorner = action("pathpoint-corner");
if (m_actionPathPointCorner) {
m_actionPathPointCorner->setData(KoPathPointTypeCommand::Corner);
m_points->addAction(m_actionPathPointCorner);
}
m_actionPathPointSmooth = action("pathpoint-smooth");
if (m_actionPathPointSmooth) {
m_actionPathPointSmooth->setData(KoPathPointTypeCommand::Smooth);
m_points->addAction(m_actionPathPointSmooth);
}
m_actionPathPointSymmetric = action("pathpoint-symmetric");
if (m_actionPathPointSymmetric) {
m_actionPathPointSymmetric->setData(KoPathPointTypeCommand::Symmetric);
m_points->addAction(m_actionPathPointSymmetric);
}
m_actionCurvePoint = action("pathpoint-curve");
m_actionLinePoint = action("pathpoint-line");
m_actionLineSegment = action("pathsegment-line");
m_actionCurveSegment = action("pathsegment-curve");
m_actionAddPoint = action("pathpoint-insert");
m_actionRemovePoint = action("pathpoint-remove");
m_actionBreakPoint = action("path-break-point");
m_actionBreakSegment = action("path-break-segment");
m_actionJoinSegment = action("pathpoint-join");
m_actionMergePoints = action("pathpoint-merge");
m_actionConvertToPath = action("convert-to-path");
m_contextMenu.reset(new QMenu());
QBitmap b = QBitmap::fromData(QSize(16, 16), needle_bits);
QBitmap m = b.createHeuristicMask(false);
m_selectCursor = QCursor(b, m, 2, 0);
b = QBitmap::fromData(QSize(16, 16), needle_move_bits);
m = b.createHeuristicMask(false);
m_moveCursor = QCursor(b, m, 2, 0);
}
KoPathTool::~KoPathTool()
{
delete m_activeHandle;
delete m_activeSegment;
delete m_currentStrategy;
}
QList<QPointer<QWidget> > KoPathTool::createOptionWidgets()
{
QList<QPointer<QWidget> > list;
PathToolOptionWidget * toolOptions = new PathToolOptionWidget(this);
connect(this, SIGNAL(typeChanged(int)), toolOptions, SLOT(setSelectionType(int)));
connect(this, SIGNAL(singleShapeChanged(KoPathShape*)), toolOptions, SLOT(setCurrentShape(KoPathShape*)));
connect(toolOptions, SIGNAL(sigRequestUpdateActions()), this, SLOT(updateActions()));
updateOptionsWidget();
toolOptions->setWindowTitle(i18n("Edit Shape"));
list.append(toolOptions);
return list;
}
void KoPathTool::pointTypeChanged(QAction *type)
{
Q_D(KoToolBase);
if (m_pointSelection.hasSelection()) {
QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
KUndo2Command *initialConversionCommand = createPointToCurveCommand(selectedPoints);
// conversion should happen before the c-tor
// of KoPathPointTypeCommand is executed!
if (initialConversionCommand) {
initialConversionCommand->redo();
}
KUndo2Command *command =
new KoPathPointTypeCommand(selectedPoints,
static_cast<KoPathPointTypeCommand::PointType>(type->data().toInt()));
if (initialConversionCommand) {
using namespace KisCommandUtils;
CompositeCommand *parent = new CompositeCommand();
parent->setText(command->text());
parent->addCommand(new SkipFirstRedoWrapper(initialConversionCommand));
parent->addCommand(command);
command = parent;
}
d->canvas->addCommand(command);
}
}
void KoPathTool::insertPoints()
{
Q_D(KoToolBase);
QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
if (segments.size() == 1) {
qreal positionInSegment = 0.5;
if (m_activeSegment && m_activeSegment->isValid()) {
positionInSegment = m_activeSegment->positionOnSegment;
}
KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, positionInSegment);
d->canvas->addCommand(cmd);
// TODO: this construction is dangerous. The canvas can remove the command right after
// it has been added to it!
m_pointSelection.clear();
foreach (KoPathPoint * p, cmd->insertedPoints()) {
m_pointSelection.add(p, false);
}
}
}
void KoPathTool::removePoints()
{
Q_D(KoToolBase);
if (m_pointSelection.size() > 0) {
KUndo2Command *cmd = KoPathPointRemoveCommand::createCommand(m_pointSelection.selectedPointsData(), d->canvas->shapeController());
PointHandle *pointHandle = dynamic_cast<PointHandle*>(m_activeHandle);
if (pointHandle && m_pointSelection.contains(pointHandle->activePoint())) {
delete m_activeHandle;
m_activeHandle = 0;
}
clearActivePointSelectionReferences();
d->canvas->addCommand(cmd);
}
}
void KoPathTool::pointToLine()
{
Q_D(KoToolBase);
if (m_pointSelection.hasSelection()) {
QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
QList<KoPathPointData> pointToChange;
QList<KoPathPointData>::const_iterator it(selectedPoints.constBegin());
for (; it != selectedPoints.constEnd(); ++it) {
KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex);
if (point && (point->activeControlPoint1() || point->activeControlPoint2()))
pointToChange.append(*it);
}
if (! pointToChange.isEmpty()) {
d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Line));
}
}
}
void KoPathTool::pointToCurve()
{
Q_D(KoToolBase);
if (m_pointSelection.hasSelection()) {
QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
KUndo2Command *command = createPointToCurveCommand(selectedPoints);
if (command) {
d->canvas->addCommand(command);
}
}
}
KUndo2Command* KoPathTool::createPointToCurveCommand(const QList<KoPathPointData> &points)
{
KUndo2Command *command = 0;
QList<KoPathPointData> pointToChange;
QList<KoPathPointData>::const_iterator it(points.constBegin());
for (; it != points.constEnd(); ++it) {
KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex);
if (point && (! point->activeControlPoint1() || ! point->activeControlPoint2()))
pointToChange.append(*it);
}
if (!pointToChange.isEmpty()) {
command = new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Curve);
}
return command;
}
void KoPathTool::segmentToLine()
{
Q_D(KoToolBase);
if (m_pointSelection.size() > 1) {
QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
if (segments.size() > 0) {
d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Line));
}
}
}
void KoPathTool::segmentToCurve()
{
Q_D(KoToolBase);
if (m_pointSelection.size() > 1) {
QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
if (segments.size() > 0) {
d->canvas->addCommand(new KoPathSegmentTypeCommand(segments, KoPathSegmentTypeCommand::Curve));
}
}
}
void KoPathTool::convertToPath()
{
Q_D(KoToolBase);
KoSelection *selection = canvas()->selectedShapesProxy()->selection();
QList<KoParameterShape*> parameterShapes;
Q_FOREACH (KoShape *shape, m_pointSelection.selectedShapes()) {
KoParameterShape * parameteric = dynamic_cast<KoParameterShape*>(shape);
if (parameteric && parameteric->isParametricShape()) {
parameterShapes.append(parameteric);
}
}
if (!parameterShapes.isEmpty()) {
d->canvas->addCommand(new KoParameterToPathCommand(parameterShapes));
}
QList<KoSvgTextShape*> textShapes;
Q_FOREACH (KoShape *shape, selection->selectedEditableShapes()) {
if (KoSvgTextShape *text = dynamic_cast<KoSvgTextShape*>(shape)) {
textShapes.append(text);
}
}
if (!textShapes.isEmpty()) {
KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Convert to Path")); // TODO: reuse the text from KoParameterToPathCommand
const QList<KoShape*> oldSelectedShapes = implicitCastList<KoShape*>(textShapes);
new KoKeepShapesSelectedCommand(oldSelectedShapes, {}, canvas()->selectedShapesProxy(),
KisCommandUtils::FlipFlopCommand::State::INITIALIZING, cmd);
QList<KoShape*> newSelectedShapes;
Q_FOREACH (KoSvgTextShape *shape, textShapes) {
const QPainterPath path = shape->textOutline();
if (path.isEmpty()) continue;
KoPathShape *pathShape = KoPathShape::createShapeFromPainterPath(path);
pathShape->setBackground(shape->background());
pathShape->setStroke(shape->stroke());
pathShape->setZIndex(shape->zIndex());
pathShape->setTransformation(shape->transformation());
KoShapeContainer *parent = shape->parent();
canvas()->shapeController()->addShapeDirect(pathShape, parent, cmd);
newSelectedShapes << pathShape;
}
canvas()->shapeController()->removeShapes(oldSelectedShapes, cmd);
new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(),
KisCommandUtils::FlipFlopCommand::State::FINALIZING, cmd);
canvas()->addCommand(cmd);
}
updateOptionsWidget();
}
namespace {
bool checkCanJoinToPoints(const KoPathPointData & pd1, const KoPathPointData & pd2)
{
const KoPathPointIndex & index1 = pd1.pointIndex;
const KoPathPointIndex & index2 = pd2.pointIndex;
KoPathShape *path1 = pd1.pathShape;
KoPathShape *path2 = pd2.pathShape;
// check if subpaths are already closed
if (path1->isClosedSubpath(index1.first) || path2->isClosedSubpath(index2.first))
return false;
// check if first point is an endpoint
if (index1.second != 0 && index1.second != path1->subpathPointCount(index1.first)-1)
return false;
// check if second point is an endpoint
if (index2.second != 0 && index2.second != path2->subpathPointCount(index2.first)-1)
return false;
return true;
}
}
void KoPathTool::mergePointsImpl(bool doJoin)
{
Q_D(KoToolBase);
if (m_pointSelection.size() != 2)
return;
QList<KoPathPointData> pointData = m_pointSelection.selectedPointsData();
if (pointData.size() != 2) return;
const KoPathPointData & pd1 = pointData.at(0);
const KoPathPointData & pd2 = pointData.at(1);
if (!checkCanJoinToPoints(pd1, pd2)) {
return;
}
clearActivePointSelectionReferences();
KUndo2Command *cmd = 0;
if (doJoin) {
cmd = new KoMultiPathPointJoinCommand(pd1, pd2, d->canvas->shapeController()->documentBase(), d->canvas->shapeManager()->selection());
} else {
cmd = new KoMultiPathPointMergeCommand(pd1, pd2, d->canvas->shapeController()->documentBase(), d->canvas->shapeManager()->selection());
}
d->canvas->addCommand(cmd);
}
void KoPathTool::joinPoints()
{
mergePointsImpl(true);
}
void KoPathTool::mergePoints()
{
mergePointsImpl(false);
}
void KoPathTool::breakAtPoint()
{
Q_D(KoToolBase);
if (m_pointSelection.hasSelection()) {
d->canvas->addCommand(new KoPathBreakAtPointCommand(m_pointSelection.selectedPointsData()));
}
}
void KoPathTool::breakAtSegment()
{
Q_D(KoToolBase);
// only try to break a segment when 2 points of the same object are selected
if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) {
QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
if (segments.size() == 1) {
d->canvas->addCommand(new KoPathSegmentBreakCommand(segments.at(0)));
}
}
}
void KoPathTool::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_D(KoToolBase);
Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) {
KisHandlePainterHelper helper =
- KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius);
+ KoShape::createHandlePainterHelperView(&painter, shape, converter, m_handleRadius);
helper.setHandleStyle(KisHandleStyle::primarySelection());
KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
if (parameterShape && parameterShape->isParametricShape()) {
parameterShape->paintHandles(helper);
} else {
shape->paintPoints(helper);
}
if (!shape->stroke() || !shape->stroke()->isVisible()) {
helper.setHandleStyle(KisHandleStyle::secondarySelection());
helper.drawPath(shape->outline());
}
}
if (m_currentStrategy) {
painter.save();
m_currentStrategy->paint(painter, converter);
painter.restore();
}
m_pointSelection.paint(painter, converter, m_handleRadius);
if (m_activeHandle) {
if (m_activeHandle->check(m_pointSelection.selectedShapes())) {
m_activeHandle->paint(painter, converter, m_handleRadius);
} else {
delete m_activeHandle;
m_activeHandle = 0;
}
} else if (m_activeSegment && m_activeSegment->isValid()) {
KoPathShape *shape = m_activeSegment->path;
// if the stroke is invisible, then we already painted the outline of the shape!
if (shape->stroke() && shape->stroke()->isVisible()) {
KoPathPointIndex index = shape->pathPointIndex(m_activeSegment->segmentStart);
KoPathSegment segment = shape->segmentByIndex(index).toCubic();
KIS_SAFE_ASSERT_RECOVER_RETURN(segment.isValid());
KisHandlePainterHelper helper =
- KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius);
+ KoShape::createHandlePainterHelperView(&painter, shape, converter, m_handleRadius);
helper.setHandleStyle(KisHandleStyle::secondarySelection());
QPainterPath path;
path.moveTo(segment.first()->point());
path.cubicTo(segment.first()->controlPoint2(),
segment.second()->controlPoint1(),
segment.second()->point());
helper.drawPath(path);
}
}
if (m_currentStrategy) {
painter.save();
- KoShape::applyConversion(painter, converter);
+ painter.setTransform(converter.documentToView(), true);
d->canvas->snapGuide()->paint(painter, converter);
painter.restore();
}
}
void KoPathTool::repaintDecorations()
{
Q_FOREACH (KoShape *shape, m_pointSelection.selectedShapes()) {
repaint(shape->boundingRect());
}
m_pointSelection.repaint();
updateOptionsWidget();
}
void KoPathTool::mousePressEvent(KoPointerEvent *event)
{
// we are moving if we hit a point and use the left mouse button
event->ignore();
if (m_activeHandle) {
m_currentStrategy = m_activeHandle->handleMousePress(event);
event->accept();
} else {
if (event->button() & Qt::LeftButton) {
// check if we hit a path segment
if (m_activeSegment && m_activeSegment->isValid()) {
KoPathShape *shape = m_activeSegment->path;
KoPathPointIndex index = shape->pathPointIndex(m_activeSegment->segmentStart);
KoPathSegment segment = shape->segmentByIndex(index);
m_pointSelection.add(segment.first(), !(event->modifiers() & Qt::ShiftModifier));
m_pointSelection.add(segment.second(), false);
KoPathPointData data(shape, index);
m_currentStrategy = new KoPathSegmentChangeStrategy(this, event->point, data, m_activeSegment->positionOnSegment);
event->accept();
} else {
KoShapeManager *shapeManager = canvas()->shapeManager();
KoSelection *selection = shapeManager->selection();
KoShape *shape = shapeManager->shapeAt(event->point, KoFlake::ShapeOnTop);
if (shape && !selection->isSelected(shape)) {
if (!(event->modifiers() & Qt::ShiftModifier)) {
selection->deselectAll();
}
selection->select(shape);
} else {
KIS_ASSERT_RECOVER_RETURN(m_currentStrategy == 0);
m_currentStrategy = new KoPathPointRubberSelectStrategy(this, event->point);
event->accept();
}
}
}
}
}
void KoPathTool::mouseMoveEvent(KoPointerEvent *event)
{
if (event->button() & Qt::RightButton)
return;
if (m_currentStrategy) {
m_lastPoint = event->point;
m_currentStrategy->handleMouseMove(event->point, event->modifiers());
// repaint new handle positions
m_pointSelection.repaint();
if (m_activeHandle) {
m_activeHandle->repaint();
}
if (m_activeSegment) {
repaintSegment(m_activeSegment);
}
return;
}
if (m_activeSegment) {
KoPathPointIndex index = m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart);
KoPathSegment segment = m_activeSegment->path->segmentByIndex(index);
repaint(segment.boundingRect());
delete m_activeSegment;
m_activeSegment = 0;
}
Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) {
QRectF roi = handleGrabRect(shape->documentToShape(event->point));
KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
if (parameterShape && parameterShape->isParametricShape()) {
int handleId = parameterShape->handleIdAt(roi);
if (handleId != -1) {
useCursor(m_moveCursor);
emit statusTextChanged(i18n("Drag to move handle."));
if (m_activeHandle)
m_activeHandle->repaint();
delete m_activeHandle;
if (KoConnectionShape * connectionShape = dynamic_cast<KoConnectionShape*>(parameterShape)) {
//debugFlake << "handleId" << handleId;
m_activeHandle = new ConnectionHandle(this, connectionShape, handleId);
m_activeHandle->repaint();
return;
} else {
//debugFlake << "handleId" << handleId;
m_activeHandle = new ParameterHandle(this, parameterShape, handleId);
m_activeHandle->repaint();
return;
}
}
} else {
QList<KoPathPoint*> points = shape->pointsAt(roi);
if (! points.empty()) {
// find the nearest control point from all points within the roi
KoPathPoint * bestPoint = 0;
KoPathPoint::PointType bestPointType = KoPathPoint::Node;
qreal minDistance = HUGE_VAL;
Q_FOREACH (KoPathPoint *p, points) {
// the node point must be hit if the point is not selected yet
if (! m_pointSelection.contains(p) && ! roi.contains(p->point()))
continue;
// check for the control points first as otherwise it is no longer
// possible to change the control points when they are the same as the point
if (p->activeControlPoint1() && roi.contains(p->controlPoint1())) {
qreal dist = squaredDistance(roi.center(), p->controlPoint1());
if (dist < minDistance) {
bestPoint = p;
bestPointType = KoPathPoint::ControlPoint1;
minDistance = dist;
}
}
if (p->activeControlPoint2() && roi.contains(p->controlPoint2())) {
qreal dist = squaredDistance(roi.center(), p->controlPoint2());
if (dist < minDistance) {
bestPoint = p;
bestPointType = KoPathPoint::ControlPoint2;
minDistance = dist;
}
}
// check the node point at last
qreal dist = squaredDistance(roi.center(), p->point());
if (dist < minDistance) {
bestPoint = p;
bestPointType = KoPathPoint::Node;
minDistance = dist;
}
}
if (! bestPoint)
return;
useCursor(m_moveCursor);
if (bestPointType == KoPathPoint::Node)
emit statusTextChanged(i18n("Drag to move point. Shift click to change point type."));
else
emit statusTextChanged(i18n("Drag to move control point."));
PointHandle *prev = dynamic_cast<PointHandle*>(m_activeHandle);
if (prev && prev->activePoint() == bestPoint && prev->activePointType() == bestPointType)
return; // no change;
if (m_activeHandle)
m_activeHandle->repaint();
delete m_activeHandle;
m_activeHandle = new PointHandle(this, bestPoint, bestPointType);
m_activeHandle->repaint();
return;
}
}
}
useCursor(m_selectCursor);
if (m_activeHandle) {
m_activeHandle->repaint();
}
delete m_activeHandle;
m_activeHandle = 0;
PathSegment *hoveredSegment = segmentAtPoint(event->point);
if(hoveredSegment) {
useCursor(Qt::PointingHandCursor);
emit statusTextChanged(i18n("Drag to change curve directly. Double click to insert new path point."));
m_activeSegment = hoveredSegment;
repaintSegment(m_activeSegment);
} else {
uint selectedPointCount = m_pointSelection.size();
if (selectedPointCount == 0)
emit statusTextChanged(QString());
else if (selectedPointCount == 1)
emit statusTextChanged(i18n("Press B to break path at selected point."));
else
emit statusTextChanged(i18n("Press B to break path at selected segments."));
}
}
void KoPathTool::repaintSegment(PathSegment *pathSegment)
{
if (!pathSegment || !pathSegment->isValid()) return;
KoPathPointIndex index = pathSegment->path->pathPointIndex(pathSegment->segmentStart);
KoPathSegment segment = pathSegment->path->segmentByIndex(index);
repaint(segment.boundingRect());
}
void KoPathTool::mouseReleaseEvent(KoPointerEvent *event)
{
Q_D(KoToolBase);
if (m_currentStrategy) {
const bool hadNoSelection = !m_pointSelection.hasSelection();
m_currentStrategy->finishInteraction(event->modifiers());
KUndo2Command *command = m_currentStrategy->createCommand();
if (command)
d->canvas->addCommand(command);
if (hadNoSelection && dynamic_cast<KoPathPointRubberSelectStrategy*>(m_currentStrategy)
&& !m_pointSelection.hasSelection()) {
// the click didn't do anything at all. Allow it to be used by others.
event->ignore();
}
delete m_currentStrategy;
m_currentStrategy = 0;
}
}
void KoPathTool::keyPressEvent(QKeyEvent *event)
{
if (m_currentStrategy) {
switch (event->key()) {
case Qt::Key_Control:
case Qt::Key_Alt:
case Qt::Key_Shift:
case Qt::Key_Meta:
if (! event->isAutoRepeat()) {
m_currentStrategy->handleMouseMove(m_lastPoint, event->modifiers());
}
break;
case Qt::Key_Escape:
m_currentStrategy->cancelInteraction();
delete m_currentStrategy;
m_currentStrategy = 0;
break;
default:
event->ignore();
return;
}
} else {
switch (event->key()) {
#ifndef NDEBUG
// case Qt::Key_D:
// if (m_pointSelection.objectCount() == 1) {
// QList<KoPathPointData> selectedPoints = m_pointSelection.selectedPointsData();
// KoPathShapePrivate *p = static_cast<KoPathShapePrivate*>(selectedPoints[0].pathShape->priv());
// p->debugPath();
// }
// break;
#endif
case Qt::Key_B:
if (m_pointSelection.size() == 1)
breakAtPoint();
else if (m_pointSelection.size() >= 2)
breakAtSegment();
break;
default:
event->ignore();
return;
}
}
event->accept();
}
void KoPathTool::keyReleaseEvent(QKeyEvent *event)
{
if (m_currentStrategy) {
switch (event->key()) {
case Qt::Key_Control:
case Qt::Key_Alt:
case Qt::Key_Shift:
case Qt::Key_Meta:
if (! event->isAutoRepeat()) {
m_currentStrategy->handleMouseMove(m_lastPoint, Qt::NoModifier);
}
break;
default:
break;
}
}
event->accept();
}
void KoPathTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
Q_D(KoToolBase);
event->ignore();
// check if we are doing something else at the moment
if (m_currentStrategy) return;
if (!m_activeHandle && m_activeSegment && m_activeSegment->isValid()) {
QList<KoPathPointData> segments;
segments.append(
KoPathPointData(m_activeSegment->path,
m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart)));
KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, m_activeSegment->positionOnSegment);
d->canvas->addCommand(cmd);
m_pointSelection.clear();
foreach (KoPathPoint * p, cmd->insertedPoints()) {
m_pointSelection.add(p, false);
}
updateActions();
event->accept();
} else if (!m_activeHandle && !m_activeSegment && m_activatedTemporarily) {
emit done();
event->accept();
} else if (!m_activeHandle && !m_activeSegment) {
KoShapeManager *shapeManager = canvas()->shapeManager();
KoSelection *selection = shapeManager->selection();
selection->deselectAll();
event->accept();
}
}
KoPathTool::PathSegment* KoPathTool::segmentAtPoint(const QPointF &point)
{
// the max allowed distance from a segment
const QRectF grabRoi = handleGrabRect(point);
const qreal distanceThreshold = 0.5 * KisAlgebra2D::maxDimension(grabRoi);
QScopedPointer<PathSegment> segment(new PathSegment);
Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) {
KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
if (parameterShape && parameterShape->isParametricShape())
continue;
// convert document point to shape coordinates
const QPointF p = shape->documentToShape(point);
// our region of interest, i.e. a region around our mouse position
const QRectF roi = shape->documentToShape(grabRoi);
qreal minDistance = std::numeric_limits<qreal>::max();
// check all segments of this shape which intersect the region of interest
const QList<KoPathSegment> segments = shape->segmentsAt(roi);
foreach (const KoPathSegment &s, segments) {
const qreal nearestPointParam = s.nearestPoint(p);
const QPointF nearestPoint = s.pointAt(nearestPointParam);
const qreal distance = kisDistance(p, nearestPoint);
// are we within the allowed distance ?
if (distance > distanceThreshold)
continue;
// are we closer to the last closest point ?
if (distance < minDistance) {
segment->path = shape;
segment->segmentStart = s.first();
segment->positionOnSegment = nearestPointParam;
}
}
}
if (!segment->isValid()) {
segment.reset();
}
return segment.take();
}
void KoPathTool::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
KoToolBase::activate(activation, shapes);
Q_D(KoToolBase);
m_activatedTemporarily = activation == TemporaryActivation;
// retrieve the actual global handle radius
m_handleRadius = handleRadius();
d->canvas->snapGuide()->reset();
useCursor(m_selectCursor);
m_canvasConnections.addConnection(d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));
m_canvasConnections.addConnection(d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(updateActions()));
m_shapeFillResourceConnector.connectToCanvas(d->canvas);
initializeWithShapes(shapes.toList());
connect(m_actionCurvePoint, SIGNAL(triggered()), this, SLOT(pointToCurve()), Qt::UniqueConnection);
connect(m_actionLinePoint, SIGNAL(triggered()), this, SLOT(pointToLine()), Qt::UniqueConnection);
connect(m_actionLineSegment, SIGNAL(triggered()), this, SLOT(segmentToLine()), Qt::UniqueConnection);
connect(m_actionCurveSegment, SIGNAL(triggered()), this, SLOT(segmentToCurve()), Qt::UniqueConnection);
connect(m_actionAddPoint, SIGNAL(triggered()), this, SLOT(insertPoints()), Qt::UniqueConnection);
connect(m_actionRemovePoint, SIGNAL(triggered()), this, SLOT(removePoints()), Qt::UniqueConnection);
connect(m_actionBreakPoint, SIGNAL(triggered()), this, SLOT(breakAtPoint()), Qt::UniqueConnection);
connect(m_actionBreakSegment, SIGNAL(triggered()), this, SLOT(breakAtSegment()), Qt::UniqueConnection);
connect(m_actionJoinSegment, SIGNAL(triggered()), this, SLOT(joinPoints()), Qt::UniqueConnection);
connect(m_actionMergePoints, SIGNAL(triggered()), this, SLOT(mergePoints()), Qt::UniqueConnection);
connect(m_actionConvertToPath, SIGNAL(triggered()), this, SLOT(convertToPath()), Qt::UniqueConnection);
connect(m_points, SIGNAL(triggered(QAction*)), this, SLOT(pointTypeChanged(QAction*)), Qt::UniqueConnection);
connect(&m_pointSelection, SIGNAL(selectionChanged()), this, SLOT(pointSelectionChanged()), Qt::UniqueConnection);
}
void KoPathTool::slotSelectionChanged()
{
Q_D(KoToolBase);
QList<KoShape*> shapes =
d->canvas->selectedShapesProxy()->selection()->selectedEditableShapesAndDelegates();
initializeWithShapes(shapes);
}
void KoPathTool::notifyPathPointsChanged(KoPathShape *shape)
{
Q_UNUSED(shape);
// active handle and selection might have already become invalid, so just
// delete them without dereferencing anything...
delete m_activeHandle;
m_activeHandle = 0;
delete m_activeSegment;
m_activeSegment = 0;
}
void KoPathTool::clearActivePointSelectionReferences()
{
delete m_activeHandle;
m_activeHandle = 0;
delete m_activeSegment;
m_activeSegment = 0;
m_pointSelection.clear();
}
void KoPathTool::initializeWithShapes(const QList<KoShape*> shapes)
{
QList<KoPathShape*> selectedShapes;
Q_FOREACH (KoShape *shape, shapes) {
KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
if (pathShape && pathShape->isShapeEditable()) {
selectedShapes.append(pathShape);
}
}
const QRectF oldBoundingRect =
KoShape::boundingRect(implicitCastList<KoShape*>(m_pointSelection.selectedShapes()));
if (selectedShapes != m_pointSelection.selectedShapes()) {
clearActivePointSelectionReferences();
m_pointSelection.setSelectedShapes(selectedShapes);
repaintDecorations();
}
Q_FOREACH (KoPathShape *shape, selectedShapes) {
// as the tool is just in activation repaintDecorations does not yet get called
// so we need to use repaint of the tool and it is only needed to repaint the
// current canvas
repaint(shape->boundingRect());
}
repaint(oldBoundingRect);
updateOptionsWidget();
updateActions();
}
void KoPathTool::updateOptionsWidget()
{
PathToolOptionWidget::Types type;
QList<KoPathShape*> selectedShapes = m_pointSelection.selectedShapes();
Q_FOREACH (KoPathShape *shape, selectedShapes) {
KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
type |= parameterShape && parameterShape->isParametricShape() ?
PathToolOptionWidget::ParametricShape : PathToolOptionWidget::PlainPath;
}
emit singleShapeChanged(selectedShapes.size() == 1 ? selectedShapes.first() : 0);
emit typeChanged(type);
}
void KoPathTool::updateActions()
{
QList<KoPathPointData> pointData = m_pointSelection.selectedPointsData();
bool canBreakAtPoint = false;
bool hasNonSmoothPoints = false;
bool hasNonSymmetricPoints = false;
bool hasNonSplitPoints = false;
bool hasNonLinePoints = false;
bool hasNonCurvePoints = false;
bool canJoinSubpaths = false;
if (!pointData.isEmpty()) {
Q_FOREACH (const KoPathPointData &pd, pointData) {
const int subpathIndex = pd.pointIndex.first;
const int pointIndex = pd.pointIndex.second;
canBreakAtPoint |= pd.pathShape->isClosedSubpath(subpathIndex) ||
(pointIndex > 0 && pointIndex < pd.pathShape->subpathPointCount(subpathIndex) - 1);
KoPathPoint *point = pd.pathShape->pointByIndex(pd.pointIndex);
hasNonSmoothPoints |= !(point->properties() & KoPathPoint::IsSmooth);
hasNonSymmetricPoints |= !(point->properties() & KoPathPoint::IsSymmetric);
hasNonSplitPoints |=
point->properties() & KoPathPoint::IsSymmetric ||
point->properties() & KoPathPoint::IsSmooth;
hasNonLinePoints |= point->activeControlPoint1() || point->activeControlPoint2();
hasNonCurvePoints |= !point->activeControlPoint1() && !point->activeControlPoint2();
}
if (pointData.size() == 2) {
const KoPathPointData & pd1 = pointData.at(0);
const KoPathPointData & pd2 = pointData.at(1);
canJoinSubpaths = checkCanJoinToPoints(pd1, pd2);
}
}
m_actionPathPointCorner->setEnabled(hasNonSplitPoints);
m_actionPathPointSmooth->setEnabled(hasNonSmoothPoints);
m_actionPathPointSymmetric->setEnabled(hasNonSymmetricPoints);
m_actionRemovePoint->setEnabled(!pointData.isEmpty());
m_actionBreakPoint->setEnabled(canBreakAtPoint);
m_actionCurvePoint->setEnabled(hasNonCurvePoints);
m_actionLinePoint->setEnabled(hasNonLinePoints);
m_actionJoinSegment->setEnabled(canJoinSubpaths);
m_actionMergePoints->setEnabled(canJoinSubpaths);
QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
bool canSplitAtSegment = false;
bool canConvertSegmentToLine = false;
bool canConvertSegmentToCurve= false;
if (!segments.isEmpty()) {
canSplitAtSegment = segments.size() == 1;
bool hasLines = false;
bool hasCurves = false;
Q_FOREACH (const KoPathPointData &pd, segments) {
KoPathSegment segment = pd.pathShape->segmentByIndex(pd.pointIndex);
hasLines |= segment.degree() == 1;
hasCurves |= segment.degree() > 1;
}
canConvertSegmentToLine = !segments.isEmpty() && hasCurves;
canConvertSegmentToCurve= !segments.isEmpty() && hasLines;
}
m_actionAddPoint->setEnabled(canSplitAtSegment);
m_actionLineSegment->setEnabled(canConvertSegmentToLine);
m_actionCurveSegment->setEnabled(canConvertSegmentToCurve);
m_actionBreakSegment->setEnabled(canSplitAtSegment);
KoSelection *selection = canvas()->selectedShapesProxy()->selection();
bool haveConvertibleShapes = false;
Q_FOREACH (KoShape *shape, selection->selectedEditableShapes()) {
KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shape);
if (textShape ||
(parameterShape && parameterShape->isParametricShape())) {
haveConvertibleShapes = true;
break;
}
}
m_actionConvertToPath->setEnabled(haveConvertibleShapes);
}
void KoPathTool::deactivate()
{
Q_D(KoToolBase);
m_shapeFillResourceConnector.disconnect();
m_canvasConnections.clear();
m_pointSelection.clear();
m_pointSelection.setSelectedShapes(QList<KoPathShape*>());
delete m_activeHandle;
m_activeHandle = 0;
delete m_activeSegment;
m_activeSegment = 0;
delete m_currentStrategy;
m_currentStrategy = 0;
d->canvas->snapGuide()->reset();
disconnect(m_actionCurvePoint, 0, this, 0);
disconnect(m_actionLinePoint, 0, this, 0);
disconnect(m_actionLineSegment, 0, this, 0);
disconnect(m_actionCurveSegment, 0, this, 0);
disconnect(m_actionAddPoint, 0, this, 0);
disconnect(m_actionRemovePoint, 0, this, 0);
disconnect(m_actionBreakPoint, 0, this, 0);
disconnect(m_actionBreakSegment, 0, this, 0);
disconnect(m_actionJoinSegment, 0, this, 0);
disconnect(m_actionMergePoints, 0, this, 0);
disconnect(m_actionConvertToPath, 0, this, 0);
disconnect(m_points, 0, this, 0);
disconnect(&m_pointSelection, 0, this, 0);
KoToolBase::deactivate();
}
void KoPathTool::documentResourceChanged(int key, const QVariant & res)
{
if (key == KoDocumentResourceManager::HandleRadius) {
int oldHandleRadius = m_handleRadius;
m_handleRadius = res.toUInt();
// repaint with the bigger of old and new handle radius
int maxRadius = qMax(m_handleRadius, oldHandleRadius);
Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) {
- QRectF controlPointRect = shape->absoluteTransformation(0).map(shape->outline()).controlPointRect();
+ QRectF controlPointRect = shape->absoluteTransformation().map(shape->outline()).controlPointRect();
repaint(controlPointRect.adjusted(-maxRadius, -maxRadius, maxRadius, maxRadius));
}
}
}
void KoPathTool::pointSelectionChanged()
{
Q_D(KoToolBase);
updateActions();
d->canvas->snapGuide()->setIgnoredPathPoints(m_pointSelection.selectedPoints().toList());
emit selectionChanged(m_pointSelection.hasSelection());
}
void KoPathTool::repaint(const QRectF &repaintRect)
{
Q_D(KoToolBase);
//debugFlake <<"KoPathTool::repaint(" << repaintRect <<")" << m_handleRadius;
// widen border to take antialiasing into account
qreal radius = m_handleRadius + 1;
d->canvas->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius));
}
namespace {
void addActionsGroupIfEnabled(QMenu *menu, QAction *a1, QAction *a2)
{
if (a1->isEnabled() || a2->isEnabled()) {
menu->addAction(a1);
menu->addAction(a2);
menu->addSeparator();
}
}
void addActionsGroupIfEnabled(QMenu *menu, QAction *a1, QAction *a2, QAction *a3)
{
if (a1->isEnabled() || a2->isEnabled()) {
menu->addAction(a1);
menu->addAction(a2);
menu->addAction(a3);
menu->addSeparator();
}
}
}
QMenu *KoPathTool::popupActionsMenu()
{
if (m_activeHandle) {
m_activeHandle->trySelectHandle();
}
if (m_activeSegment && m_activeSegment->isValid()) {
KoPathShape *shape = m_activeSegment->path;
KoPathSegment segment = shape->segmentByIndex(shape->pathPointIndex(m_activeSegment->segmentStart));
m_pointSelection.add(segment.first(), true);
m_pointSelection.add(segment.second(), false);
}
if (m_contextMenu) {
m_contextMenu->clear();
addActionsGroupIfEnabled(m_contextMenu.data(),
m_actionPathPointCorner,
m_actionPathPointSmooth,
m_actionPathPointSymmetric);
addActionsGroupIfEnabled(m_contextMenu.data(),
m_actionCurvePoint,
m_actionLinePoint);
addActionsGroupIfEnabled(m_contextMenu.data(),
m_actionAddPoint,
m_actionRemovePoint);
addActionsGroupIfEnabled(m_contextMenu.data(),
m_actionLineSegment,
m_actionCurveSegment);
addActionsGroupIfEnabled(m_contextMenu.data(),
m_actionBreakPoint,
m_actionBreakSegment);
addActionsGroupIfEnabled(m_contextMenu.data(),
m_actionJoinSegment,
m_actionMergePoints);
m_contextMenu->addAction(m_actionConvertToPath);
m_contextMenu->addSeparator();
}
return m_contextMenu.data();
}
void KoPathTool::deleteSelection()
{
removePoints();
}
KoToolSelection * KoPathTool::selection()
{
return &m_pointSelection;
}
void KoPathTool::requestUndoDuringStroke()
{
// noop!
}
void KoPathTool::requestStrokeCancellation()
{
explicitUserStrokeEndRequest();
}
void KoPathTool::requestStrokeEnd()
{
// noop!
}
void KoPathTool::explicitUserStrokeEndRequest()
{
if (m_activatedTemporarily) {
emit done();
}
}
diff --git a/libs/flake/tools/KoPathToolHandle.cpp b/libs/flake/tools/KoPathToolHandle.cpp
index 872a1ea1a7..57caea09bf 100644
--- a/libs/flake/tools/KoPathToolHandle.cpp
+++ b/libs/flake/tools/KoPathToolHandle.cpp
@@ -1,237 +1,237 @@
/* This file is part of the KDE project
* Copyright (C) 2006,2008 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007,2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 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 "KoPathToolHandle.h"
#include "KoPathTool.h"
#include "KoPathPointMoveStrategy.h"
#include "KoPathControlPointMoveStrategy.h"
#include "KoPathConnectionPointStrategy.h"
#include "KoSelection.h"
#include "commands/KoPathPointTypeCommand.h"
#include "KoParameterChangeStrategy.h"
#include "KoParameterShape.h"
#include "KoCanvasBase.h"
#include "KoDocumentResourceManager.h"
#include "KoConnectionShape.h"
#include "KoViewConverter.h"
#include "KoPointerEvent.h"
#include "KoShapeController.h"
#include <QPainter>
#include <KisHandlePainterHelper.h>
KoPathToolHandle::KoPathToolHandle(KoPathTool *tool)
: m_tool(tool)
{
}
KoPathToolHandle::~KoPathToolHandle()
{
}
uint KoPathToolHandle::handleRadius() const
{
return m_tool->canvas()->shapeController()->resourceManager()->handleRadius();
}
PointHandle::PointHandle(KoPathTool *tool, KoPathPoint *activePoint, KoPathPoint::PointType activePointType)
: KoPathToolHandle(tool)
, m_activePoint(activePoint)
, m_activePointType(activePointType)
{
}
void PointHandle::paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius)
{
KoPathToolSelection * selection = dynamic_cast<KoPathToolSelection*>(m_tool->selection());
KoPathPoint::PointTypes allPaintedTypes = KoPathPoint::Node;
if (selection && selection->contains(m_activePoint)) {
allPaintedTypes = KoPathPoint::All;
}
- KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, m_activePoint->parent(), converter, handleRadius);
+ KisHandlePainterHelper helper = KoShape::createHandlePainterHelperView(&painter, m_activePoint->parent(), converter, handleRadius);
if (allPaintedTypes != m_activePointType) {
KoPathPoint::PointTypes nonHighlightedType = allPaintedTypes & ~m_activePointType;
KoPathPoint::PointTypes nonNodeType = nonHighlightedType & ~KoPathPoint::Node;
if (nonNodeType != KoPathPoint::None) {
helper.setHandleStyle(KisHandleStyle::selectedPrimaryHandles());
m_activePoint->paint(helper, nonHighlightedType);
}
if (nonHighlightedType & KoPathPoint::Node) {
helper.setHandleStyle(KisHandleStyle::partiallyHighlightedPrimaryHandles());
m_activePoint->paint(helper, KoPathPoint::Node);
}
}
helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
m_activePoint->paint(helper, m_activePointType);
}
void PointHandle::repaint() const
{
m_tool->repaint(m_oldRepaintedRect);
bool active = false;
KoPathToolSelection * selection = dynamic_cast<KoPathToolSelection*>(m_tool->selection());
if (selection && selection->contains(m_activePoint))
active = true;
m_oldRepaintedRect = m_activePoint->boundingRect(!active);
m_tool->repaint(m_oldRepaintedRect);
}
KoInteractionStrategy * PointHandle::handleMousePress(KoPointerEvent *event)
{
if ((event->button() & Qt::LeftButton) == 0)
return 0;
if ((event->modifiers() & Qt::ControlModifier) == 0) { // no shift pressed.
KoPathToolSelection * selection = dynamic_cast<KoPathToolSelection*>(m_tool->selection());
// control select adds/removes points to/from the selection
if (event->modifiers() & Qt::ShiftModifier) {
if (selection->contains(m_activePoint)) {
selection->remove(m_activePoint);
} else {
selection->add(m_activePoint, false);
}
m_tool->repaint(m_activePoint->boundingRect(false));
} else {
// no control modifier, so clear selection and select active point
if (!selection->contains(m_activePoint)) {
selection->add(m_activePoint, true);
m_tool->repaint(m_activePoint->boundingRect(false));
}
}
// TODO remove canvas from call ?
if (m_activePointType == KoPathPoint::Node) {
QPointF startPoint = m_activePoint->parent()->shapeToDocument(m_activePoint->point());
return new KoPathPointMoveStrategy(m_tool, startPoint);
} else {
KoPathShape * pathShape = m_activePoint->parent();
KoPathPointData pd(pathShape, pathShape->pathPointIndex(m_activePoint));
return new KoPathControlPointMoveStrategy(m_tool, pd, m_activePointType, event->point);
}
} else {
KoPathPoint::PointProperties props = m_activePoint->properties();
if (! m_activePoint->activeControlPoint1() || ! m_activePoint->activeControlPoint2())
return 0;
KoPathPointTypeCommand::PointType pointType = KoPathPointTypeCommand::Smooth;
// cycle the smooth->symmetric->unsmooth state of the path point
if (props & KoPathPoint::IsSmooth)
pointType = KoPathPointTypeCommand::Symmetric;
else if (props & KoPathPoint::IsSymmetric)
pointType = KoPathPointTypeCommand::Corner;
QList<KoPathPointData> pointData;
pointData.append(KoPathPointData(m_activePoint->parent(), m_activePoint->parent()->pathPointIndex(m_activePoint)));
m_tool->canvas()->addCommand(new KoPathPointTypeCommand(pointData, pointType));
}
return 0;
}
bool PointHandle::check(const QList<KoPathShape*> &selectedShapes)
{
if (selectedShapes.contains(m_activePoint->parent())) {
return m_activePoint->parent()->pathPointIndex(m_activePoint) != KoPathPointIndex(-1, -1);
}
return false;
}
KoPathPoint * PointHandle::activePoint() const
{
return m_activePoint;
}
KoPathPoint::PointType PointHandle::activePointType() const
{
return m_activePointType;
}
void PointHandle::trySelectHandle()
{
KoPathToolSelection * selection = dynamic_cast<KoPathToolSelection*>(m_tool->selection());
if (!selection->contains(m_activePoint) && m_activePointType == KoPathPoint::Node) {
selection->clear();
selection->add(m_activePoint, false);
}
}
ParameterHandle::ParameterHandle(KoPathTool *tool, KoParameterShape *parameterShape, int handleId)
: KoPathToolHandle(tool)
, m_parameterShape(parameterShape)
, m_handleId(handleId)
{
}
void ParameterHandle::paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius)
{
- KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, m_parameterShape, converter, handleRadius);
+ KisHandlePainterHelper helper = KoShape::createHandlePainterHelperView(&painter, m_parameterShape, converter, handleRadius);
helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
m_parameterShape->paintHandle(helper, m_handleId);
}
void ParameterHandle::repaint() const
{
m_tool->repaint(m_parameterShape->shapeToDocument(QRectF(m_parameterShape->handlePosition(m_handleId), QSize(1, 1))));
}
KoInteractionStrategy * ParameterHandle::handleMousePress(KoPointerEvent *event)
{
if (event->button() & Qt::LeftButton) {
KoPathToolSelection * selection = dynamic_cast<KoPathToolSelection*>(m_tool->selection());
if (selection)
selection->clear();
return new KoParameterChangeStrategy(m_tool, m_parameterShape, m_handleId);
}
return 0;
}
bool ParameterHandle::check(const QList<KoPathShape*> &selectedShapes)
{
return selectedShapes.contains(m_parameterShape);
}
ConnectionHandle::ConnectionHandle(KoPathTool *tool, KoParameterShape *parameterShape, int handleId)
: ParameterHandle(tool, parameterShape, handleId)
{
}
KoInteractionStrategy * ConnectionHandle::handleMousePress(KoPointerEvent *event)
{
if (event->button() & Qt::LeftButton) {
KoPathToolSelection * selection = dynamic_cast<KoPathToolSelection*>(m_tool->selection());
if (selection)
selection->clear();
KoConnectionShape * shape = dynamic_cast<KoConnectionShape*>(m_parameterShape);
if (! shape)
return 0;
return new KoPathConnectionPointStrategy(m_tool, shape, m_handleId);
}
return 0;
}
diff --git a/libs/flake/tools/KoPathToolSelection.cpp b/libs/flake/tools/KoPathToolSelection.cpp
index 40d7011c0e..e80927fc37 100644
--- a/libs/flake/tools/KoPathToolSelection.cpp
+++ b/libs/flake/tools/KoPathToolSelection.cpp
@@ -1,306 +1,306 @@
/* This file is part of the KDE project
* Copyright (C) 2006,2008 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 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 "KoPathToolSelection.h"
#include "KoPathTool.h"
#include <KoParameterShape.h>
#include <KoPathPoint.h>
#include <KoPathPointData.h>
#include <KoViewConverter.h>
#include <KoCanvasBase.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeController.h>
#include <QPainter>
#include <KisHandlePainterHelper.h>
KoPathToolSelection::KoPathToolSelection(KoPathTool * tool)
: m_tool(tool)
{
}
KoPathToolSelection::~KoPathToolSelection()
{
}
void KoPathToolSelection::paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius)
{
PathShapePointMap::iterator it(m_shapePointMap.begin());
for (; it != m_shapePointMap.end(); ++it) {
KisHandlePainterHelper helper =
- KoShape::createHandlePainterHelper(&painter, it.key(), converter, handleRadius);
+ KoShape::createHandlePainterHelperView(&painter, it.key(), converter, handleRadius);
helper.setHandleStyle(KisHandleStyle::selectedPrimaryHandles());
Q_FOREACH (KoPathPoint *p, it.value()) {
p->paint(helper, KoPathPoint::All);
}
}
}
void KoPathToolSelection::add(KoPathPoint * point, bool clear)
{
if(! point)
return;
bool allreadyIn = false;
if (clear) {
if (size() == 1 && m_selectedPoints.contains(point)) {
allreadyIn = true;
} else {
this->clear();
}
} else {
allreadyIn = m_selectedPoints.contains(point);
}
if (!allreadyIn) {
m_selectedPoints.insert(point);
KoPathShape * pathShape = point->parent();
PathShapePointMap::iterator it(m_shapePointMap.find(pathShape));
if (it == m_shapePointMap.end()) {
it = m_shapePointMap.insert(pathShape, QSet<KoPathPoint *>());
}
it.value().insert(point);
m_tool->repaint(point->boundingRect());
emit selectionChanged();
}
}
void KoPathToolSelection::remove(KoPathPoint * point)
{
if (m_selectedPoints.remove(point)) {
KoPathShape * pathShape = point->parent();
m_shapePointMap[pathShape].remove(point);
if (m_shapePointMap[pathShape].size() == 0) {
m_shapePointMap.remove(pathShape);
}
emit selectionChanged();
}
m_tool->repaint(point->boundingRect());
}
void KoPathToolSelection::clear()
{
repaint();
m_selectedPoints.clear();
m_shapePointMap.clear();
emit selectionChanged();
}
void KoPathToolSelection::selectPoints(const QRectF &rect, bool clearSelection)
{
if (clearSelection) {
clear();
}
blockSignals(true);
Q_FOREACH (KoPathShape* shape, m_selectedShapes) {
KoParameterShape *parameterShape = dynamic_cast<KoParameterShape*>(shape);
if (parameterShape && parameterShape->isParametricShape())
continue;
Q_FOREACH (KoPathPoint* point, shape->pointsAt(shape->documentToShape(rect)))
add(point, false);
}
blockSignals(false);
emit selectionChanged();
}
int KoPathToolSelection::objectCount() const
{
return m_shapePointMap.size();
}
int KoPathToolSelection::size() const
{
return m_selectedPoints.size();
}
bool KoPathToolSelection::contains(KoPathPoint * point)
{
return m_selectedPoints.contains(point);
}
const QSet<KoPathPoint *> & KoPathToolSelection::selectedPoints() const
{
return m_selectedPoints;
}
QList<KoPathPointData> KoPathToolSelection::selectedPointsData() const
{
QList<KoPathPointData> pointData;
Q_FOREACH (KoPathPoint* p, m_selectedPoints) {
KoPathShape * pathShape = p->parent();
pointData.append(KoPathPointData(pathShape, pathShape->pathPointIndex(p)));
}
return pointData;
}
QList<KoPathPointData> KoPathToolSelection::selectedSegmentsData() const
{
QList<KoPathPointData> pointData;
QList<KoPathPointData> pd(selectedPointsData());
std::sort(pd.begin(), pd.end());
KoPathPointData last(0, KoPathPointIndex(-1, -1));
KoPathPointData lastSubpathStart(0, KoPathPointIndex(-1, -1));
QList<KoPathPointData>::const_iterator it(pd.constBegin());
for (; it != pd.constEnd(); ++it) {
if (it->pointIndex.second == 0)
lastSubpathStart = *it;
if (last.pathShape == it->pathShape
&& last.pointIndex.first == it->pointIndex.first
&& last.pointIndex.second + 1 == it->pointIndex.second) {
pointData.append(last);
}
if (lastSubpathStart.pathShape == it->pathShape
&& it->pathShape->pointByIndex(it->pointIndex)->properties() & KoPathPoint::CloseSubpath
&& (it->pathShape->pointByIndex(it->pointIndex)->properties() & KoPathPoint::StartSubpath) == 0) {
pointData.append(*it);
}
last = *it;
}
return pointData;
}
QList<KoPathShape*> KoPathToolSelection::selectedShapes() const
{
return m_selectedShapes;
}
void KoPathToolSelection::setSelectedShapes(const QList<KoPathShape*> shapes)
{
Q_FOREACH(KoPathShape *shape, m_selectedShapes) {
shape->removeShapeChangeListener(this);
}
m_selectedShapes = shapes;
Q_FOREACH(KoPathShape *shape, m_selectedShapes) {
shape->addShapeChangeListener(this);
}
}
void KoPathToolSelection::repaint()
{
update();
Q_FOREACH (KoPathPoint *p, m_selectedPoints) {
m_tool->repaint(p->boundingRect(false));
}
}
void KoPathToolSelection::update()
{
bool selectionHasChanged = false;
PathShapePointMap::iterator it(m_shapePointMap.begin());
while (it != m_shapePointMap.end()) {
KoParameterShape *parameterShape = dynamic_cast<KoParameterShape*>(it.key());
bool isParametricShape = parameterShape && parameterShape->isParametricShape();
if (! m_selectedShapes.contains(it.key()) || isParametricShape) {
QSet<KoPathPoint *>::iterator pointIt(it.value().begin());
for (; pointIt != it.value().end(); ++pointIt) {
m_selectedPoints.remove(*pointIt);
}
it = m_shapePointMap.erase(it);
selectionHasChanged = true;
} else {
QSet<KoPathPoint *>::iterator pointIt(it.value().begin());
while (pointIt != it.value().end()) {
if ((*pointIt)->parent()->pathPointIndex(*pointIt) == KoPathPointIndex(-1, -1)) {
m_selectedPoints.remove(*pointIt);
pointIt = it.value().erase(pointIt);
selectionHasChanged = true;
} else {
++pointIt;
}
}
++it;
}
}
if (selectionHasChanged)
emit selectionChanged();
}
bool KoPathToolSelection::hasSelection()
{
return !m_selectedPoints.isEmpty();
}
void KoPathToolSelection::recommendPointSelectionChange(KoPathShape *shape, const QList<KoPathPointIndex> &newSelection)
{
QSet<KoPathPoint*> selectedShapePoints = m_shapePointMap.value(shape, QSet<KoPathPoint*>());
Q_FOREACH (KoPathPoint *point, selectedShapePoints) {
remove(point);
}
Q_FOREACH (const KoPathPointIndex &index, newSelection) {
KoPathPoint *point = shape->pointByIndex(index);
KIS_SAFE_ASSERT_RECOVER(point) { continue; }
add(point, false);
}
repaint();
emit selectionChanged();
}
void KoPathToolSelection::notifyPathPointsChanged(KoPathShape *shape)
{
QSet<KoPathPoint*> selectedShapePoints = m_shapePointMap.value(shape, QSet<KoPathPoint*>());
Q_FOREACH (KoPathPoint *point, selectedShapePoints) {
m_selectedPoints.remove(point);
}
m_shapePointMap.remove(shape);
m_tool->notifyPathPointsChanged(shape);
repaint();
emit selectionChanged();
}
void KoPathToolSelection::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
{
if (type == KoShape::Deleted) {
// we cannot select non-path shapes, so static cast is safe here
KIS_SAFE_ASSERT_RECOVER_NOOP(shape->shapeId() == KoPathShapeId);
if (KoPathShape *pathShape = static_cast<KoPathShape*>(shape)) {
QSet<KoPathPoint*> selectedShapePoints = m_shapePointMap.value(pathShape, QSet<KoPathPoint*>());
Q_FOREACH (KoPathPoint *point, selectedShapePoints) {
m_selectedPoints.remove(point);
}
m_shapePointMap.remove(pathShape);
m_selectedShapes.removeAll(pathShape);
}
}
KoPathShape::PointSelectionChangeListener::notifyShapeChanged(type, shape);
}
diff --git a/libs/image/kis_fast_math.h b/libs/global/KisCppQuirks.h
similarity index 55%
copy from libs/image/kis_fast_math.h
copy to libs/global/KisCppQuirks.h
index a5f88008d3..4283839cd2 100644
--- a/libs/image/kis_fast_math.h
+++ b/libs/global/KisCppQuirks.h
@@ -1,38 +1,45 @@
/*
- * Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
+ * Copyright (c) 2019 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.
- *
- * adopted from here http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx
*/
+#ifndef KISCPPQUIRKS_H
+#define KISCPPQUIRKS_H
+namespace std {
-#ifndef _KIS_IMAGE_FAST_
-#define _KIS_IMAGE_FAST_
-
-#include <QtGlobal>
+#if __cplusplus < 201402L
-#include "kritaimage_export.h"
+template <typename Cont>
+inline auto rbegin(Cont &cont) -> decltype(declval<Cont>().rbegin()) {
+ return cont.rbegin();
+}
-/**
- * This namespace contains fast but inaccurate version of mathematical function.
- */
-namespace KisFastMath {
+template <typename Cont>
+inline auto rend(Cont &cont) -> decltype(declval<Cont>().rend()) {
+ return cont.rend();
+}
- /// atan2 replacement
- KRITAIMAGE_EXPORT qreal atan2(qreal y, qreal x);
+template <class BidirectionalIterator>
+inline reverse_iterator<BidirectionalIterator> make_reverse_iterator(BidirectionalIterator x)
+{
+ return reverse_iterator<BidirectionalIterator>(x);
}
#endif
+
+}
+
+#endif // KISCPPQUIRKS_H
diff --git a/libs/global/KisForest.h b/libs/global/KisForest.h
new file mode 100644
index 0000000000..13776a7377
--- /dev/null
+++ b/libs/global/KisForest.h
@@ -0,0 +1,914 @@
+/*
+ * Copyright (c) 2019 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 KISFOREST_H
+#define KISFOREST_H
+
+#include <utility>
+
+#include <boost/iterator/iterator_facade.hpp>
+#include <boost/iterator/iterator_adaptor.hpp>
+
+#include "KisCppQuirks.h"
+
+#include "kis_assert.h"
+#include "kis_debug.h"
+
+namespace KisForestDetail {
+
+template <typename Base>
+struct RootNodeImpl
+{
+ RootNodeImpl<Base> *parent = nullptr;
+ Base *firstChild = nullptr;
+ Base *lastChild = nullptr;
+
+ inline bool isRoot() const {
+ return !parent;
+ }
+};
+
+
+template <typename Base>
+struct BaseNode : RootNodeImpl<Base>
+{
+ Base *nextSibling = nullptr;
+ Base *prevSibling = nullptr;
+};
+
+template <typename T>
+struct Node : public BaseNode<Node<T>>
+{
+ template <typename X>
+ explicit Node(X &&newValue)
+ : value(std::forward<X>(newValue))
+ {
+ }
+
+ T value;
+};
+
+template <typename T>
+using RootNode = RootNodeImpl<Node<T>>;
+
+
+/*************************************************************************/
+/* BaseIterator */
+/*************************************************************************/
+
+template <typename BaseClass, typename T, typename Tag>
+class BaseIterator :
+ public boost::iterator_facade <BaseClass,
+ T,
+ Tag>
+{
+public:
+ using value_type = T;
+
+ BaseIterator(Node<T> *node)
+ : m_node(node)
+ {
+ }
+
+ Node<T>* node() const {
+ return m_node;
+ }
+
+private:
+ friend class boost::iterator_core_access;
+
+ typename std::iterator_traits<BaseIterator>::reference dereference() const {
+ KIS_ASSERT(m_node);
+ return m_node->value;
+ }
+
+ bool equal(const BaseClass &other) const {
+ return m_node == other.m_node;
+ }
+
+protected:
+ Node<T> *m_node;
+};
+
+
+/*************************************************************************/
+/* ChildIterator */
+/*************************************************************************/
+
+/**
+ * Child iterator is used to traverse all the the children of the current node.
+ * It models \c BidirectionalIterator concept, so you can traverse with it in both
+ * directions.
+ *
+ * \code{.cpp}
+ *
+ * // Forest:
+ * //
+ * // 0 1
+ * // 2
+ * // 3 5 6
+ * // 7
+ * // 4
+ * // 8 9
+ * // 10
+ *
+ * KisForest<int>::iterator it0 = findValue(forest, 0);
+ *
+ * // iterate through all the children of '0'
+ * for (auto it = childBegin(it0); it != childEnd(it0); ++it) {
+ * qDebug() << *it;
+ * }
+ * // prints: 1,2,3,4
+ *
+ * \endcode
+ *
+ * It is also possible to convert any iterator type into child iterator
+ * via siblingCurrent() function.
+ *
+ * \code{.cpp}
+ *
+ * // Forest:
+ * //
+ * // 0 1
+ * // 2
+ * // 3 5 6
+ * // 7
+ * // 4
+ * // 8 9
+ * // 10
+ *
+ * KisForest<int>::iterator it2 = findValue(forest, 2);
+ *
+ * // iterate the children of '0' from '2' to '4'
+ * for (auto it = siblingCurrent(it2); it != siblingEnd(it2); ++it) {
+ * qDebug() << *it;
+ * }
+ * // prints: 2,3,4
+ *
+ * \endcode
+ *
+ * WARNING: converting end() iterator to other iterator types currently leads
+ * to undefined behavior.
+ */
+
+template <typename T>
+class ChildIterator :
+ public BaseIterator <ChildIterator<T>,
+ T,
+ boost::bidirectional_traversal_tag>
+{
+public:
+ ChildIterator(Node<T> *node, RootNode<T> *parent)
+ : BaseIterator<ChildIterator<T>, T, boost::bidirectional_traversal_tag>(node),
+ m_parent(parent)
+ {
+ }
+
+private:
+ friend class boost::iterator_core_access;
+ template <typename X>
+ friend ChildIterator<X> parent(const ChildIterator<X> &it);
+ template <typename X> friend class Forest;
+
+ void increment() {
+ this->m_node = this->m_node->nextSibling;
+ }
+
+ void decrement() {
+ this->m_node =
+ this->m_node ?
+ this->m_node->prevSibling :
+ m_parent ? m_parent->lastChild : nullptr;
+ }
+
+ bool equal(const ChildIterator<T> &other) const {
+ return this->m_node == other.m_node &&
+ (this->m_node || this->m_parent == other.m_parent);
+ }
+
+private:
+ RootNode<T> *m_parent;
+};
+
+template <typename Iterator,
+ typename ResultIterator = ChildIterator<typename Iterator::value_type>>
+ResultIterator siblingBegin(Iterator it)
+{
+ using RootNodeType = RootNode<typename Iterator::value_type>;
+
+ RootNodeType *parent = it.node() ? it.node()->parent : nullptr;
+ return ResultIterator(parent ? parent->firstChild : nullptr, parent);
+}
+
+
+template <typename Iterator,
+ typename ResultIterator = ChildIterator<typename Iterator::value_type>>
+ResultIterator siblingCurrent(Iterator it)
+{
+ return ResultIterator(it.node(), it.node() ? it.node()->parent : nullptr);
+}
+
+template <typename Iterator,
+ typename ResultIterator = ChildIterator<typename Iterator::value_type>>
+ResultIterator siblingEnd(Iterator it)
+{
+ return ResultIterator(nullptr, it.node() ? it.node()->parent : nullptr);
+}
+
+template <typename Iterator,
+ typename ResultIterator = ChildIterator<typename Iterator::value_type>>
+ResultIterator childBegin(Iterator it)
+{
+ return ResultIterator(it.node() ? it.node()->firstChild : nullptr, it.node());
+}
+
+template <typename Iterator,
+ typename ResultIterator = ChildIterator<typename Iterator::value_type>>
+ResultIterator childEnd(Iterator it)
+{
+ return ResultIterator(nullptr, it.node());
+}
+
+template <typename T>
+ChildIterator<T> parent(const ChildIterator<T> &it)
+{
+ Node<T> *parent = static_cast<Node<T>*>(it.m_parent && !it.m_parent->isRoot() ? it.m_parent : nullptr);
+ return ChildIterator<T>(parent, parent ? parent->parent : 0);
+}
+
+template <typename T>
+inline bool isEnd(const ChildIterator<T> &it) {
+ return !it.node();
+}
+
+
+/*************************************************************************/
+/* HierarchyIterator */
+/*************************************************************************/
+
+/**
+ * Hierarchy iterator is used to traverse from the current node to the root
+ * of the the current subtree of the forest. It models \c ForwardIterator concept.
+ *
+ * \code{.cpp}
+ *
+ * // Forest:
+ * //
+ * // 0 1
+ * // 2
+ * // 3 5 6
+ * // 7
+ * // 4
+ * // 8 9
+ * // 10
+ *
+ * KisForest<int>::iterator nodeIt = findValue(forest, 5);
+ *
+ * // print all the parent nodes for nodeIt, including nodeIt itself
+ * for (auto it = hierarchyBegin(nodeIt); it != hierarchyEnd(nodeIt); ++it) {
+ * qDebug() << *it;
+ * }
+ * // prints: 5,3,0
+ *
+ * \endcode
+ *
+ * WARNING: converting end() iterator to other iterator types currently leads
+ * to undefined behavior.
+ */
+
+template <typename T>
+class HierarchyIterator :
+ public BaseIterator <HierarchyIterator<T>,
+ T,
+ boost::forward_traversal_tag>
+{
+public:
+ HierarchyIterator(Node<T> *node)
+ : BaseIterator<HierarchyIterator<T>, T, boost::forward_traversal_tag>(node)
+ {
+ }
+
+
+private:
+ friend class boost::iterator_core_access;
+
+ void increment() {
+ RootNode<T> *parent = this->m_node->parent;
+ this->m_node =
+ static_cast<Node<T>*>(
+ parent && !parent->isRoot() ?
+ parent : nullptr);
+ }
+};
+
+template <typename Iterator,
+ typename ResultIterator = HierarchyIterator<typename Iterator::value_type>>
+ResultIterator hierarchyBegin(Iterator it)
+{
+ return ResultIterator(it.node());
+}
+
+template <typename Iterator,
+ typename ResultIterator = HierarchyIterator<typename Iterator::value_type>>
+ResultIterator hierarchyEnd(Iterator it)
+{
+ Q_UNUSED(it);
+ return ResultIterator(nullptr);
+}
+
+
+/*************************************************************************/
+/* CompositionIterator */
+/*************************************************************************/
+
+/**
+ * Composition iterator is used to traverse entire child-subtree of the node
+ * recursively in depth-first order. Every node it entered twice: first time, when
+ * subtree is entered; second time, when subtree is left. To check the current
+ * state of the iterator (Enter or Leave) use \c it.state() call.
+ *
+ * Iterator models \c ForwardIterator concept.
+ *
+ * \code{.cpp}
+ *
+ * // Forest:
+ * //
+ * // 0 1
+ * // 2
+ * // 3 5 6
+ * // 7
+ * // 4
+ * // 8 9
+ * // 10
+ *
+ * KisForest<int>::iterator it3 = findValue(forest, 3);
+ *
+ * // iterate through all the children of '3' recursively
+ * for (auto it = compositionBegin(it3); it != compositionEnd(it3); ++it) {
+ * qDebug() << *it << it.state();
+ * }
+ * // prints: (3, Enter)
+ * // (5, Enter)
+ * // (6, Enter)
+ * // (6, Leave)
+ * // (7, Enter)
+ * // (7, Leave)
+ * // (5, Leave)
+ * // (3, Leave)
+ *
+ * \endcode
+ *
+ * WARNING: converting end() iterator to other iterator types currently leads
+ * to undefined behavior.
+ */
+
+enum TraversalState {
+ Enter,
+ Leave
+};
+
+template <typename T>
+class CompositionIterator :
+ public boost::iterator_adaptor<
+ CompositionIterator<T>,
+ ChildIterator<T>,
+ boost::use_default,
+ boost::forward_traversal_tag>
+{
+ using BaseClass = boost::iterator_adaptor<
+ CompositionIterator<T>,
+ ChildIterator<T>,
+ boost::use_default,
+ boost::forward_traversal_tag>;
+
+public:
+ using traversal_state = TraversalState;
+
+ Node<T>* node() const {
+ return this->base().node();
+ }
+
+public:
+ CompositionIterator(Node<T> *node, traversal_state state = Enter)
+ : BaseClass(ChildIterator<T>(node, node ? node->parent : nullptr)),
+ m_state(state)
+ {
+ }
+
+ traversal_state state() const {
+ return m_state;
+ }
+
+
+private:
+ friend class boost::iterator_core_access;
+
+ bool tryJumpToPos(typename CompositionIterator::base_type it,
+ TraversalState newState) {
+ bool result = false;
+
+ if (!isEnd(it)) {
+ this->base_reference() = it;
+ result = true;
+ m_state = newState;
+ }
+
+ return result;
+ }
+
+ void increment() {
+ switch (m_state) {
+ case Enter:
+ if (tryJumpToPos(childBegin(this->base()), Enter)) return;
+ if (tryJumpToPos(this->base(), Leave)) return;
+ break;
+ case Leave:
+ if (tryJumpToPos(std::next(this->base()), Enter)) return;
+ if (tryJumpToPos(parent(this->base()), Leave)) return;
+ break;
+ }
+
+ this->base_reference() = ChildIterator<T>(nullptr, nullptr);
+ }
+
+private:
+ traversal_state m_state = Enter;
+};
+
+template <typename Iterator>
+CompositionIterator<typename Iterator::value_type> compositionBegin(Iterator it)
+{
+ using CompositionIterator = CompositionIterator<typename Iterator::value_type>;
+ return CompositionIterator(it.node(), Enter);
+}
+
+template <typename Iterator>
+CompositionIterator<typename Iterator::value_type> compositionEnd(Iterator it)
+{
+ using CompositionIterator = CompositionIterator<typename Iterator::value_type>;
+ return it.node() ?
+ std::next(CompositionIterator(it.node(), Leave)) :
+ CompositionIterator(nullptr, Leave);
+}
+
+
+/*************************************************************************/
+/* DepthFirstIterator */
+/*************************************************************************/
+
+/**
+ * Depth-first iterator is used to traverse entire child-subtree of the node
+ * recursively in depth-first order. Every node is entered only once. It
+ * implements standard recursion iteration:
+ *
+ * * \c DepthFirstIterator<T, Enter> implements head-recursion, that is,
+ * the node is visited *before* its children
+ *
+ * * \c DepthFirstIterator<T, Leave> implements tail-recursion, that is,
+ * the node is visited *after* its children
+ *
+ * Use \c subtreeBegin() and \c subtreeEnd() for head-recursion and \c tailSubtreeBegin() and
+ * \c tailSubtreeEnd() for tail-recursion.
+ *
+ * Iterator models \c ForwardIterator concept.
+ *
+ * \code{.cpp}
+ *
+ * // Forest:
+ * //
+ * // 0 1
+ * // 2
+ * // 3 5 6
+ * // 7
+ * // 4
+ * // 8 9
+ * // 10
+ *
+ * KisForest<int>::iterator it3 = findValue(forest, 3);
+ *
+ * // iterate through all the children of '3' in head-recursive way
+ * for (auto it = subtreeBegin(it3); it != subtreeEnd(it3); ++it) {
+ * qDebug() << *it << it.state();
+ * }
+ * // prints: 3, 5, 6, 7
+ *
+ * // iterate through all the children of '3' in tail-recursive way
+ * for (auto it = tailSubtreeBegin(it3); it != tailSubtreeEnd(it3); ++it) {
+ * qDebug() << *it << it.state();
+ * }
+ * // prints: 6, 7, 5, 3
+ *
+ * \endcode
+ *
+ * WARNING: converting end() iterator to other iterator types currently leads
+ * to undefined behavior.
+ */
+
+template <typename T, TraversalState visit_on>
+class DepthFirstIterator :
+ public boost::iterator_adaptor<
+ DepthFirstIterator<T, visit_on>,
+ CompositionIterator<T>,
+ boost::use_default,
+ boost::forward_traversal_tag>
+{
+ using BaseClass = boost::iterator_adaptor<
+ DepthFirstIterator<T, visit_on>,
+ CompositionIterator<T>,
+ boost::use_default,
+ boost::forward_traversal_tag>;
+
+public:
+ using traversal_state = TraversalState;
+
+ DepthFirstIterator(Node<T> *node,
+ traversal_state state = traversal_state::Enter,
+ bool shouldSkipToBegin = false)
+ : BaseClass(CompositionIterator<T>(node, state))
+ {
+ if (shouldSkipToBegin) {
+ skipToState(visit_on);
+ }
+ }
+
+ Node<T>* node() const {
+ return this->base().node();
+ }
+
+private:
+ friend class boost::iterator_core_access;
+
+ void increment() {
+ this->base_reference()++;
+ skipToState(visit_on);
+ }
+
+ void skipToState(TraversalState state) {
+ while (this->base().node() && this->base().state() != state) {
+ this->base_reference()++;
+ }
+ }
+
+};
+
+template <typename Iterator,
+ typename ResultIterator = DepthFirstIterator<typename Iterator::value_type, Enter>>
+ResultIterator subtreeBegin(Iterator it)
+{
+ return ResultIterator(it.node(), Enter);
+}
+
+template <typename Iterator,
+ typename ResultIterator = DepthFirstIterator<typename Iterator::value_type, Enter>>
+ResultIterator subtreeEnd(Iterator it)
+{
+ return it.node() ?
+ std::next(ResultIterator(it.node(), Leave)) :
+ ResultIterator(nullptr, Leave);
+}
+
+template <typename Iterator,
+ typename ResultIterator = DepthFirstIterator<typename Iterator::value_type, Leave>>
+ResultIterator tailSubtreeBegin(Iterator it)
+{
+ return ResultIterator(it.node(), Enter, true);
+}
+
+template <typename Iterator,
+ typename ResultIterator = DepthFirstIterator<typename Iterator::value_type, Leave>>
+ResultIterator tailSubtreeEnd(Iterator it)
+{
+ return it.node() ?
+ std::next(ResultIterator(it.node(), Leave)) :
+ ResultIterator(nullptr, Leave);
+}
+
+
+/*************************************************************************/
+/* Forest */
+/*************************************************************************/
+
+/**
+ * Forest implements a container for composing tree-like structures from
+ * arbitrary objects.
+ *
+ * All add/remove operations are implemented via child_iterator. You can
+ * convert any iterator type into a child iterator via \c siblingCurrent()
+ * function.
+ *
+ * * \code{.cpp}
+ *
+ * // Forest:
+ * //
+ * // 0 1
+ * // 2
+ * // 3 5 6
+ * // 7
+ * // 4
+ * // 8 9
+ * // 10
+ *
+ * KisForest<int> forest;
+ *
+ * auto it0 = forest.insert(childBegin(forest), 0);
+ * auto it8 = forest.insert(childEnd(forest), 8);
+ *
+ * auto it1 = forest.insert(childEnd(it0), 1);
+ * auto it2 = forest.insert(childEnd(it0), 2);
+ * auto it3 = forest.insert(childEnd(it0), 3);
+ * auto it4 = forest.insert(childEnd(it0), 4);
+ *
+ * auto it5 = forest.insert(childEnd(it3), 5);
+ *
+ * auto it6 = forest.insert(childEnd(it5), 6);
+ * auto it7 = forest.insert(childEnd(it5), 7);
+ *
+ * auto it9 = forest.insert(childEnd(it8), 9);
+ * auto it10 = forest.insert(childEnd(it8), 10);
+ *
+ * // iterate through all elements of the forest
+ * for (auto it = subtreeBegin(forest); it != subtreeEnd(forest); ++it) {
+ * qDebug() << *it << it.state();
+ * }
+ * // prints: 0,1,2,3,5,6,7,4,8,9,10
+ *
+ * \endcode
+ *
+ *
+ */
+
+template <typename T>
+class Forest
+{
+public:
+ Forest()
+ {
+ }
+
+ ~Forest() {
+ erase(childBegin(), childEnd());
+ }
+
+ using child_iterator = ChildIterator<T>;
+ using composition_iterator = CompositionIterator<T>;
+ using hierarchy_iterator = HierarchyIterator<T>;
+ using iterator = DepthFirstIterator<T, Enter>;
+ using depth_first_tail_iterator = DepthFirstIterator<T, Leave>;
+
+ iterator begin() {
+ return iterator(m_root.firstChild);
+ }
+
+ iterator end() {
+ return iterator(nullptr);
+ }
+
+ depth_first_tail_iterator depthFirstTailBegin() {
+ return tailSubtreeBegin(begin());
+ }
+
+ depth_first_tail_iterator depthFirstTailEnd() {
+ return tailSubtreeEnd(end());
+ }
+
+ child_iterator childBegin() {
+ return child_iterator(m_root.firstChild, &m_root);
+ }
+
+ child_iterator childEnd() {
+ return child_iterator(nullptr, &m_root);
+ }
+
+ composition_iterator compositionBegin() {
+ return composition_iterator(m_root.firstChild);
+ }
+
+ composition_iterator compositionEnd() {
+ return composition_iterator(nullptr);
+ }
+
+ /**
+ * @brief Inserts element \p value into position \p pos.
+ * \p value becomes the child of the same parent as \p pos
+ * and is placed right before \p pos.
+ * @return iterator pointing to the inserted element
+ */
+ template <typename X>
+ child_iterator insert(child_iterator pos, X &&value) {
+ Node<T> *node = new Node<T>(std::forward<X>(value));
+
+ linkNode(pos, node);
+
+ return child_iterator(node, node->parent);
+ }
+
+ /**
+ * @brief Removes element at position \p pos.
+ * If \p pos is 'end', then result is undefined.
+ * @return the element following \p pos
+ */
+ child_iterator erase(child_iterator pos) {
+ child_iterator nextNode = std::next(pos);
+ Node<T> *node = pos.node();
+
+ Node<T> *lastNode = node;
+ for (auto it = tailSubtreeBegin(pos); it != tailSubtreeEnd(pos); ++it) {
+
+ if (lastNode != node) {
+ delete lastNode;
+ }
+ lastNode = it.node();
+ }
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastNode == node, pos);
+
+ unlinkNode(node);
+ delete node;
+
+ return nextNode;
+ }
+
+ /**
+ * @brief erases all elements from \p pos to \p end (excluding \p end)
+ * @return \p end
+ */
+ child_iterator erase(child_iterator pos, child_iterator end) {
+ while (pos != end) {
+ pos = erase(pos);
+ }
+ return pos;
+ }
+
+ /**
+ * @brief move a subtree to new position
+ * Moves \p subtree into a new position pointer by \p newPos. \p newPos
+ * must not be inside the subtree, otherwise cycling links may appear.
+ * @return iterator to a new position of \p subtree
+ */
+ child_iterator move(child_iterator subtree, child_iterator newPos) {
+ // sanity check for cycling move
+ for (auto it = hierarchyBegin(newPos); it != hierarchyEnd(newPos); ++it) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it.node() != subtree.node(), subtree);
+ }
+
+ Node<T> *node = subtree.node();
+ unlinkNode(node);
+
+ node->prevSibling = nullptr;
+ node->nextSibling = nullptr;
+ node->parent = nullptr;
+
+ linkNode(newPos, node);
+ return child_iterator(node, node->parent);
+ }
+
+private:
+
+ inline void linkBefore(Node<T> *node, Node<T> *beforeMe) {
+ if (beforeMe) {
+ node->nextSibling = beforeMe;
+ beforeMe->prevSibling = node;
+ }
+ }
+
+ inline void linkAfter(Node<T> *node, Node<T> *afterMe) {
+ if (afterMe) {
+ node->prevSibling = afterMe;
+ afterMe->nextSibling = node;
+ }
+ }
+
+ inline void linkNode(child_iterator pos, Node<T> *node) {
+ Node<T> *nextNode = pos.node();
+ RootNode<T> *parentNode = pos.m_parent;
+
+ Node<T> *prevNode = nextNode ?
+ nextNode->prevSibling :
+ parentNode ? parentNode->lastChild : m_root.lastChild;
+
+ linkAfter(node, prevNode);
+ linkBefore(node, nextNode);
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN(parentNode);
+ if (!nextNode) {
+ parentNode->lastChild = node;
+ }
+
+ if (nextNode == parentNode->firstChild) {
+ parentNode->firstChild = node;
+ }
+ node->parent = parentNode;
+ }
+
+ inline void unlinkNode(Node<T> *node) {
+ RootNode<T> *parentNode = node->parent;
+
+ if (node->nextSibling) {
+ node->nextSibling->prevSibling = node->prevSibling;
+ }
+
+ if (node->prevSibling) {
+ node->prevSibling->nextSibling = node->nextSibling;
+ }
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN(parentNode);
+ if (parentNode->firstChild == node) {
+ parentNode->firstChild = node->nextSibling;
+ }
+
+ if (parentNode->lastChild == node) {
+ parentNode->lastChild = node->prevSibling;
+ }
+ }
+private:
+ RootNode<T> m_root;
+};
+
+template <typename T>
+typename Forest<T>::child_iterator childBegin(Forest<T> &forest)
+{
+ return forest.childBegin();
+}
+
+template <typename T>
+typename Forest<T>::child_iterator childEnd(Forest<T> &forest)
+{
+ return forest.childEnd();
+}
+
+template <typename T>
+typename Forest<T>::composition_iterator compositionBegin(Forest<T> &forest)
+{
+ return forest.compositionBegin();
+}
+
+template <typename T>
+typename Forest<T>::composition_iterator compositionEnd(Forest<T> &forest)
+{
+ return forest.compositionEnd();
+}
+
+template <typename T>
+typename Forest<T>::depth_first_tail_iterator tailSubtreeBegin(Forest<T> &forest)
+{
+ return forest.depthFirstTailBegin();
+}
+
+template <typename T>
+typename Forest<T>::depth_first_tail_iterator tailSubtreeEnd(Forest<T> &forest)
+{
+ return forest.depthFirstTailEnd();
+}
+
+template <typename T>
+int depth(typename Forest<T>::child_iterator beginIt,
+ typename Forest<T>::child_iterator endIt)
+{
+
+ int currentDepth = 0;
+
+ for (auto it = beginIt; it != endIt; ++it) {
+ currentDepth = std::max(currentDepth, 1 + depth<T>(childBegin(it), childEnd(it)));
+ }
+
+ return currentDepth;
+}
+
+template <typename T>
+int depth(Forest<T> &forest) {
+ return depth<T>(childBegin(forest), childEnd(forest));
+}
+
+template <typename T>
+int size(Forest<T> &forest) {
+ return std::distance(begin(forest), end(forest));
+}
+
+using std::begin;
+using std::end;
+using std::make_reverse_iterator;
+
+using std::find;
+using std::find_if;
+using std::find_if_not;
+}
+
+template<typename T>
+using KisForest = KisForestDetail::Forest<T>;
+
+
+#endif // KISFOREST_H
diff --git a/libs/global/KisUsageLogger.cpp b/libs/global/KisUsageLogger.cpp
index fdce7c3901..682ca524ec 100644
--- a/libs/global/KisUsageLogger.cpp
+++ b/libs/global/KisUsageLogger.cpp
@@ -1,206 +1,250 @@
/*
* Copyright (c) 2019 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 "KisUsageLogger.h"
+#include <QScreen>
#include <QGlobalStatic>
#include <QDebug>
#include <QDateTime>
#include <QSysInfo>
#include <QStandardPaths>
#include <QFile>
#include <QFileInfo>
#include <QDesktopWidget>
#include <QClipboard>
#include <QThread>
#include <QApplication>
#include <klocalizedstring.h>
#include <KritaVersionWrapper.h>
-
+#include <QGuiApplication>
Q_GLOBAL_STATIC(KisUsageLogger, s_instance)
const QString KisUsageLogger::s_sectionHeader("================================================================================\n");
struct KisUsageLogger::Private {
bool active {false};
QFile logFile;
+ QFile sysInfoFile;
};
KisUsageLogger::KisUsageLogger()
: d(new Private)
{
d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log");
+ d->sysInfoFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita-sysinfo.log");
rotateLog();
+
d->logFile.open(QFile::Append | QFile::Text);
+ d->sysInfoFile.open(QFile::WriteOnly | QFile::Text);
}
KisUsageLogger::~KisUsageLogger()
{
if (d->active) {
close();
}
}
void KisUsageLogger::initialize()
{
s_instance->d->active = true;
+
+ QString systemInfo = basicSystemInfo();
+ s_instance->d->sysInfoFile.write(systemInfo.toUtf8());
+}
+
+QString KisUsageLogger::basicSystemInfo()
+{
+ QString systemInfo;
+
+ // NOTE: This is intentionally not translated!
+
+ // Krita version info
+ systemInfo.append("Krita\n");
+ systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true));
+ systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", "));
+ systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false");
+ systemInfo.append("\n\n");
+
+ systemInfo.append("Qt\n");
+ systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR);
+ systemInfo.append("\n Version (loaded): ").append(qVersion());
+ systemInfo.append("\n\n");
+
+ // OS information
+ systemInfo.append("OS Information\n");
+ systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi());
+ systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture());
+ systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture());
+ systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType());
+ systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion());
+ systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName());
+ systemInfo.append("\n Product Type: ").append(QSysInfo::productType());
+ systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion());
+ systemInfo.append("\n\n");
+
+ return systemInfo;
}
void KisUsageLogger::close()
{
log("CLOSING SESSION");
s_instance->d->active = false;
s_instance->d->logFile.flush();
s_instance->d->logFile.close();
+ s_instance->d->sysInfoFile.flush();
+ s_instance->d->sysInfoFile.close();
}
void KisUsageLogger::log(const QString &message)
{
if (!s_instance->d->active) return;
if (!s_instance->d->logFile.isOpen()) return;
s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8());
s_instance->d->logFile.write(": ");
write(message);
}
void KisUsageLogger::write(const QString &message)
{
if (!s_instance->d->active) return;
if (!s_instance->d->logFile.isOpen()) return;
s_instance->d->logFile.write(message.toUtf8());
s_instance->d->logFile.write("\n");
s_instance->d->logFile.flush();
}
-void KisUsageLogger::writeSectionHeader()
+void KisUsageLogger::writeSysInfo(const QString &message)
{
- s_instance->d->logFile.write(s_sectionHeader.toUtf8());
+ if (!s_instance->d->active) return;
+ if (!s_instance->d->sysInfoFile.isOpen()) return;
+
+ s_instance->d->sysInfoFile.write(message.toUtf8());
+ s_instance->d->sysInfoFile.write("\n");
+
+ s_instance->d->sysInfoFile.flush();
+
}
+
void KisUsageLogger::writeHeader()
{
- Q_ASSERT(s_instance->d->logFile.isOpen());
+ Q_ASSERT(s_instance->d->sysInfoFile.isOpen());
+ s_instance->d->logFile.write(s_sectionHeader.toUtf8());
QString sessionHeader = QString("SESSION: %1. Executing %2\n\n")
.arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))
.arg(qApp->arguments().join(' '));
- QString disclaimer = i18n("WARNING: This file contains information about your system and the\n"
- "images you have been working with.\n"
- "\n"
- "If you have problems with Krita, the Krita developers might ask\n"
- "you to share this file with them. The information in this file is\n"
- "not shared automatically with the Krita developers in any way. You\n"
- "can disable logging to this file in Krita's Configure Krita Dialog.\n"
- "\n"
- "Please review the contents of this file before sharing this file with\n"
- "anyone.\n\n");
-
- QString systemInfo;
-
- // NOTE: This is intentionally not translated!
-
- // Krita version info
- systemInfo.append("Krita\n");
- systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true));
- systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", "));
- systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false");
- systemInfo.append("\n\n");
-
- systemInfo.append("Qt\n");
- systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR);
- systemInfo.append("\n Version (loaded): ").append(qVersion());
- systemInfo.append("\n\n");
-
- // OS information
- systemInfo.append("OS Information\n");
- systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi());
- systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture());
- systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture());
- systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType());
- systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion());
- systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName());
- systemInfo.append("\n Product Type: ").append(QSysInfo::productType());
- systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion());
- systemInfo.append("\n\n");
-
- writeSectionHeader();
s_instance->d->logFile.write(sessionHeader.toUtf8());
- s_instance->d->logFile.write(disclaimer.toUtf8());
- s_instance->d->logFile.write(systemInfo.toUtf8());
+ QString KritaAndQtVersion;
+ KritaAndQtVersion.append("Krita Version: ").append(KritaVersionWrapper::versionString(true))
+ .append(", Qt version compiled: ").append(QT_VERSION_STR)
+ .append(", loaded: ").append(qVersion())
+ .append(". Process ID: ")
+ .append(QString::number(qApp->applicationPid())).append("\n");
+ KritaAndQtVersion.append("-- -- -- -- -- -- -- --\n");
+ s_instance->d->logFile.write(KritaAndQtVersion.toUtf8());
+ s_instance->d->logFile.flush();
+}
+
+QString KisUsageLogger::screenInformation()
+{
+ QList<QScreen*> screens = qApp->screens();
+
+ QString info;
+ info.append("Display Information");
+ info.append("\nNumber of screens: ").append(QString::number(screens.size()));
+
+ for (int i = 0; i < screens.size(); ++i ) {
+ QScreen *screen = screens[i];
+ info.append("\n\tScreen: ").append(QString::number(i));
+ info.append("\n\t\tName: ").append(screen->name());
+ info.append("\n\t\tDepth: ").append(QString::number(screen->depth()));
+ info.append("\n\t\tScale: ").append(QString::number(screen->devicePixelRatio()));
+ info.append("\n\t\tResolution in pixels: ").append(QString::number(screen->geometry().width()))
+ .append("x")
+ .append(QString::number(screen->geometry().height()));
+ info.append("\n\t\tManufacturer: ").append(screen->manufacturer());
+ info.append("\n\t\tModel: ").append(screen->model());
+ info.append("\n\t\tRefresh Rate: ").append(QString::number(screen->refreshRate()));
+ }
+ info.append("\n");
+ return info;
}
void KisUsageLogger::rotateLog()
{
if (d->logFile.exists()) {
{
// Check for CLOSING SESSION
d->logFile.open(QFile::ReadOnly);
QString log = QString::fromUtf8(d->logFile.readAll());
- if (!log.split("\n").last().contains("CLOSING SESSION")) {
+ if (!log.split(s_sectionHeader).last().contains("CLOSING SESSION")) {
log.append("\nKRITA DID NOT CLOSE CORRECTLY\n");
QString crashLog = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/kritacrash.log");
if (QFileInfo(crashLog).exists()) {
QFile f(crashLog);
f.open(QFile::ReadOnly);
QString crashes = QString::fromUtf8(f.readAll());
f.close();
QStringList crashlist = crashes.split("-------------------");
log.append(QString("\nThere were %1 crashes in total in the crash log.\n").arg(crashlist.size()));
if (crashes.size() > 0) {
log.append(crashlist.last());
}
}
d->logFile.close();
d->logFile.open(QFile::WriteOnly);
d->logFile.write(log.toUtf8());
}
d->logFile.flush();
d->logFile.close();
}
{
// Rotate
d->logFile.open(QFile::ReadOnly);
QString log = QString::fromUtf8(d->logFile.readAll());
int sectionCount = log.count(s_sectionHeader);
int nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length());
while(sectionCount >= s_maxLogs) {
log = log.remove(0, log.indexOf(s_sectionHeader, nextSectionIndex));
nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length());
sectionCount = log.count(s_sectionHeader);
}
d->logFile.close();
d->logFile.open(QFile::WriteOnly);
d->logFile.write(log.toUtf8());
d->logFile.flush();
d->logFile.close();
}
}
}
diff --git a/libs/global/KisUsageLogger.h b/libs/global/KisUsageLogger.h
index 34dd84755f..4ac7fc8ded 100644
--- a/libs/global/KisUsageLogger.h
+++ b/libs/global/KisUsageLogger.h
@@ -1,64 +1,73 @@
/*
* Copyright (c) 2019 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 KISUSAGELOGGER_H
#define KISUSAGELOGGER_H
#include <QString>
#include <QScopedPointer>
#include "kritaglobal_export.h"
/**
* @brief The KisUsageLogger class logs messages to a logfile
*/
class KRITAGLOBAL_EXPORT KisUsageLogger
{
public:
KisUsageLogger();
~KisUsageLogger();
static void initialize();
static void close();
+ /// basic system information
+ /// (there is other information spreaded in the code
+ /// check usages of writeSysInfo for details)
+ static QString basicSystemInfo();
+
/// Logs with date/time
static void log(const QString &message);
/// Writes without date/time
static void write(const QString &message);
- static void writeSectionHeader();
+ /// Writes to the system information file and Krita log
+ static void writeSysInfo(const QString &message);
static void writeHeader();
+ /// Returns information about all available screens
+ static QString screenInformation();
+
private:
void rotateLog();
Q_DISABLE_COPY(KisUsageLogger)
struct Private;
const QScopedPointer<Private> d;
static const QString s_sectionHeader;
static const int s_maxLogs {10};
};
#endif // KISUSAGELOGGER_H
diff --git a/libs/global/kis_shared_ptr.h b/libs/global/kis_shared_ptr.h
index b9f1c93602..833d2128ca 100644
--- a/libs/global/kis_shared_ptr.h
+++ b/libs/global/kis_shared_ptr.h
@@ -1,526 +1,526 @@
/*
* Copyright (c) 2005 Frerich Raabe <raabe@kde.org>
* Copyright (c) 2006,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_SHAREDPTR_H
#define KIS_SHAREDPTR_H
#include <QtGlobal>
#include <kis_debug.h>
#include "kis_memory_leak_tracker.h"
template<class T>
class KisWeakSharedPtr;
/**
* KisSharedPtr is a shared pointer similar to KSharedPtr and
* boost::shared_ptr. The difference with KSharedPtr is that our
* constructor is not explicit.
*
* A shared pointer is a wrapper around a real pointer. The shared
* pointer keeps a reference count, and when the reference count drops
* to 0 the contained pointer is deleted. You can use the shared
* pointer just as you would use a real pointer.
*
* See also also item 28 and 29 of More Effective C++ and
- * http://bugs.kde.org/show_bug.cgi?id=52261 as well as
- * http://www.boost.org/libs/smart_ptr/shared_ptr.htm.
+ * https://bugs.kde.org/show_bug.cgi?id=52261 as well as
+ * https://www.boost.org/libs/smart_ptr/shared_ptr.htm.
*
* Advantage of KisSharedPtr over boost pointer or QSharedPointer?
*
* The difference with boost share pointer is that in
* boost::shared_ptr, the counter is kept inside the smart pointer,
* meaning that you should never never remove the pointer from its
* smart pointer object, because if you do that, and somewhere in the
* code, the pointer is put back in a smart pointer, then you have two
* counters, and when one reach zero, then the object gets deleted
* while some other code thinks the pointer is still valid.
*
* Disadvantage of KisSharedPtr compared to boost pointer?
*
* KisSharedPtr requires the class to inherits KisShared.
*
* Difference with QSharedPointer
*
* QSharedPointer and KisSharedPtr are very similar, but
* QSharedPointer has an explicit constructor which makes it more
* painful to use in some constructions. And QSharedPointer
* doesn't offer a weak pointer.
*/
template<class T>
class KisSharedPtr
{
friend class KisWeakSharedPtr<T>;
public:
/**
* Creates a null pointer.
*/
inline KisSharedPtr()
: d(0) { }
/**
* Creates a new pointer.
* @param p the pointer
*/
inline KisSharedPtr(T* p)
: d(p) {
ref();
}
inline KisSharedPtr(const KisWeakSharedPtr<T>& o);
// Free the pointer and set it to new value
void attach(T* p);
// Free the pointer
void clear();
/**
* Copies a pointer.
* @param o the pointer to copy
*/
inline KisSharedPtr<T>(const KisSharedPtr<T>& o)
: d(o.d) {
ref();
}
/**
* Dereferences the object that this pointer points to. If it was
* the last reference, the object will be deleted.
*/
inline ~KisSharedPtr() {
deref();
}
inline KisSharedPtr<T>& operator= (const KisSharedPtr& o) {
attach(o.d);
return *this;
}
inline bool operator== (const T* p) const {
return (d == p);
}
inline bool operator!= (const T* p) const {
return (d != p);
}
inline bool operator== (const KisSharedPtr& o) const {
return (d == o.d);
}
inline bool operator!= (const KisSharedPtr& o) const {
return (d != o.d);
}
inline KisSharedPtr<T>& operator= (T* p) {
attach(p);
return *this;
}
inline operator const T*() const {
return d;
}
template< class T2> inline operator KisSharedPtr<T2>() const {
return KisSharedPtr<T2>(d);
}
/**
* @return the contained pointer. If you delete the contained
* pointer, you will make KisSharedPtr very unhappy. It is
* perfectly save to put the contained pointer in another
* KisSharedPtr, though.
*/
inline T* data() {
return d;
}
/**
* @return the pointer
*/
inline const T* data() const {
return d;
}
/**
* @return a const pointer to the shared object.
*/
inline const T* constData() const {
return d;
}
inline const T& operator*() const {
Q_ASSERT(d);
return *d;
}
inline T& operator*() {
Q_ASSERT(d);
return *d;
}
inline const T* operator->() const {
Q_ASSERT(d);
return d;
}
inline T* operator->() {
Q_ASSERT(d);
return d;
}
/**
* @return true if the pointer is null
*/
inline bool isNull() const {
return (d == 0);
}
inline static void ref(const KisSharedPtr<T>* sp, T* t)
{
#ifndef HAVE_MEMORY_LEAK_TRACKER
Q_UNUSED(sp);
#else
KisMemoryLeakTracker::instance()->reference(t, sp);
#endif
if (t) {
t->ref();
}
}
inline static bool deref(const KisSharedPtr<T>* sp, T* t)
{
#ifndef HAVE_MEMORY_LEAK_TRACKER
Q_UNUSED(sp);
#else
KisMemoryLeakTracker::instance()->dereference(t, sp);
#endif
if (t && !t->deref()) {
delete t;
return false;
}
return true;
}
private:
inline void ref() const
{
ref(this, d);
}
inline void deref() const
{
bool v = deref(this, d);
#ifndef NDEBUG
if (!v) {
d = 0;
}
#else
Q_UNUSED(v);
#endif
}
private:
mutable T* d;
};
/**
* A weak shared ptr is an ordinary shared ptr, with two differences:
* it doesn't delete the contained pointer if the refcount drops to
* zero and it doesn't prevent the contained pointer from being
* deleted if the last strong shared pointer goes out of scope.
*/
template<class T>
class KisWeakSharedPtr
{
friend class KisSharedPtr<T>;
public:
/**
* Creates a null pointer.
*/
inline KisWeakSharedPtr()
: d(0), weakReference(0) { }
/**
* Creates a new pointer.
* @param p the pointer
*/
inline KisWeakSharedPtr(T* p) {
load(p);
}
inline KisWeakSharedPtr<T>(const KisSharedPtr<T>& o) {
load(o.d);
}
/**
* Copies a pointer.
* @param o the pointer to copy
*/
inline KisWeakSharedPtr<T>(const KisWeakSharedPtr<T>& o) {
if (o.isConsistent()) {
load(o.d);
}
else {
d = 0;
weakReference = 0;
}
}
inline ~KisWeakSharedPtr() {
detach();
}
inline KisWeakSharedPtr<T>& operator= (const KisWeakSharedPtr& o) {
attach(o);
return *this;
}
inline bool operator== (const T* p) const {
return (d == p);
}
inline bool operator!= (const T* p) const {
return (d != p);
}
inline bool operator== (const KisWeakSharedPtr& o) const {
return (d == o.d);
}
inline bool operator!= (const KisWeakSharedPtr& o) const {
return (d != o.d);
}
inline KisWeakSharedPtr<T>& operator= (T* p) {
attach(p);
return *this;
}
template< class T2> inline operator KisWeakSharedPtr<T2>() const {
return KisWeakSharedPtr<T2>(d);
}
/**
* Note that if you use this function, the pointer might be destroyed
* if KisSharedPtr pointing to this pointer are deleted, resulting in
* a segmentation fault. Use with care.
* @return a const pointer to the shared object.
*/
inline T* data() {
if (!isConsistent()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return d;
}
/**
* @see data()
*/
inline const T* data() const {
if (!isConsistent()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return d;
}
/**
* @see data()
*/
inline const T* constData() const {
if (!isConsistent()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return d;
}
/**
* @see data()
*/
inline operator const T*() const {
/**
* This operator is used in boolean expressions where we check
* for pointer consistency, so return 0 instead of asserting.
*/
return isConsistent() ? d : 0;
}
inline const T& operator*() const {
if (!isValid()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return *d;
}
inline T& operator*() {
if (!isValid()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return *d;
}
inline const T* operator->() const {
if (!isValid()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return d;
}
inline T* operator->() {
if (!isValid()) {
warnKrita << kisBacktrace();
Q_ASSERT_X(0, "KisWeakSharedPtr", "Weak pointer is not valid!");
}
return d;
}
/**
* @return true if the pointer is null
*/
inline bool isNull() const {
return (d == 0);
}
/**
* @return true if the weak pointer points to a valid pointer
* and false if the data has been deleted or is null
*/
inline bool isValid() const {
Q_ASSERT(!d || weakReference);
return d && weakReference && isOdd((int)*weakReference);
}
/**
* @brief toStrongRef returns a KisSharedPtr which may be dereferenced.
*
* Weak pointers should only be used to track ownership but never be used as pointers.
* This has historically not been the case, but in new API this function should be used
* instead of directly using a weak pointer as pointer.
* @return a KisSharedPtr, which may be null
*/
inline KisSharedPtr<T> toStrongRef() const {
return KisSharedPtr<T>(*this);
}
private:
static const qint32 WEAK_REF = 2;
static inline bool isOdd(const qint32 &x) {
return x & 0x01;
}
inline bool isConsistent() const {
Q_ASSERT(!d || weakReference);
return !d || (weakReference && isOdd((int)*weakReference));
}
void load(T* newValue) {
d = newValue;
if (d) {
weakReference = d->sharedWeakReference();
weakReference->fetchAndAddOrdered(WEAK_REF);
}
else {
weakReference = 0;
}
}
// see note in kis_shared.cc
inline void attach(T* newValue) {
detach();
load(newValue);
}
inline void attach(const KisWeakSharedPtr& o) {
detach();
if (o.isConsistent()) {
load(o.d);
}
else {
d = 0;
weakReference = 0;
}
}
// see note in kis_shared.cc
void detach() {
d = 0;
if (weakReference &&
weakReference->fetchAndAddOrdered(-WEAK_REF) <= WEAK_REF) {
// sanity check:
Q_ASSERT((int)*weakReference == 0);
delete weakReference;
weakReference = 0;
}
}
mutable T* d;
QAtomicInt *weakReference;
};
template <class T>
Q_INLINE_TEMPLATE KisSharedPtr<T>::KisSharedPtr(const KisWeakSharedPtr<T>& o)
: d(o.d)
{
if (o.isValid()) {
ref();
/**
* Thread safety:
* Is the object we have just referenced still valid?
*/
Q_ASSERT(o.isConsistent());
}
else {
d = 0;
}
}
template <class T>
Q_INLINE_TEMPLATE void KisSharedPtr<T>::attach(T* p)
{
if (d != p) {
ref(this, p);
T* old = d;
d = p;
deref(this, old);
}
}
template <class T>
Q_INLINE_TEMPLATE void KisSharedPtr<T>::clear()
{
attach((T*)0);
}
#endif
diff --git a/libs/global/tests/CMakeLists.txt b/libs/global/tests/CMakeLists.txt
index 48093cba83..f8c7126e44 100644
--- a/libs/global/tests/CMakeLists.txt
+++ b/libs/global/tests/CMakeLists.txt
@@ -1,10 +1,11 @@
include(ECMAddTests)
include(KritaAddBrokenUnitTest)
macro_add_unittest_definitions()
ecm_add_tests(KisSharedThreadPoolAdapterTest.cpp
KisSignalAutoConnectionTest.cpp
KisSignalCompressorTest.cpp
+ KisForestTest.cpp
NAME_PREFIX libs-global-
LINK_LIBRARIES kritaglobal Qt5::Test)
diff --git a/libs/global/tests/KisForestTest.cpp b/libs/global/tests/KisForestTest.cpp
new file mode 100644
index 0000000000..330d82c370
--- /dev/null
+++ b/libs/global/tests/KisForestTest.cpp
@@ -0,0 +1,884 @@
+/*
+ * Copyright (c) 2019 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 "KisForestTest.h"
+
+#include "KisCppQuirks.h"
+#include "KisForest.h"
+#include <vector>
+
+struct IteratorToValue
+{
+ using value_type = int;
+
+ template <typename Iterator>
+ value_type operator() (Iterator it) const {
+ return *it;
+ }
+};
+
+template <typename Iterator, typename IteratorValuePolicy = IteratorToValue>
+bool testForestIteration(Iterator begin, Iterator end,
+ std::vector<typename IteratorValuePolicy::value_type> reference,
+ IteratorValuePolicy iteratorToValue = IteratorValuePolicy())
+{
+ using value_type = typename IteratorValuePolicy::value_type;
+
+ bool result = true;
+ std::vector<value_type> values;
+
+ std::size_t index = 0;
+ for (auto it = begin; it != end; ++it, ++index) {
+ value_type value = iteratorToValue(it);
+
+ if (index >= reference.size() || value != reference[index]) {
+ result = false;
+ }
+ values.push_back(value);
+
+ // emergency exit in case of infinite loop :)
+ // "40 forest items must be enough for everyone!" (c)
+ if (index > 40) {
+ result = false;
+ break;
+ }
+ }
+
+ result &= values.size() == reference.size();
+
+ if (!result) {
+ qDebug() << "Failed forest iteration:";
+ {
+ QDebug deb = qDebug();
+ deb << " result:";
+ Q_FOREACH(value_type value, values) {
+ deb << value;
+ }
+ }
+ {
+ QDebug deb = qDebug();
+ deb << " ref. :";
+ Q_FOREACH(value_type value, reference) {
+ deb << value;
+ }
+ }
+ }
+
+ return result;
+}
+
+void KisForestTest::testAddToRoot()
+{
+ KisForest<int> forest;
+
+ forest.insert(childBegin(forest), 2);
+ forest.insert(childBegin(forest), 1);
+ forest.insert(childBegin(forest), 0);
+ forest.insert(childEnd(forest), 3);
+
+ QVERIFY(testForestIteration(childBegin(forest), childEnd(forest), {0,1,2,3}));
+
+}
+
+void KisForestTest::testAddToRootChained()
+{
+ KisForest<int> forest;
+
+ auto it = forest.insert(childBegin(forest), 3);
+ it = forest.insert(it, 2);
+ it = forest.insert(it, 1);
+ it = forest.insert(it, 0);
+
+ QVERIFY(testForestIteration(childBegin(forest), childEnd(forest), {0,1,2,3}));
+}
+
+void KisForestTest::testAddToLeaf()
+{
+ KisForest<int> forest;
+
+ auto root = forest.insert(childBegin(forest), 0);
+ forest.insert(childBegin(root), 2);
+ forest.insert(childBegin(root), 1);
+ forest.insert(childEnd(root), 3);
+ forest.insert(childEnd(root), 4);
+
+ QVERIFY(testForestIteration(childBegin(forest), childEnd(forest), {0}));
+ QVERIFY(testForestIteration(childBegin(root), childEnd(root), {1,2,3,4}));
+
+}
+
+void KisForestTest::testAddToLeafChained()
+{
+ KisForest<int> forest;
+
+ auto root = forest.insert(childBegin(forest), 0);
+ auto it = forest.insert(childBegin(root), 4);
+ it = forest.insert(it, 3);
+ it = forest.insert(it, 2);
+ it = forest.insert(it, 1);
+
+ QVERIFY(testForestIteration(childBegin(forest), childEnd(forest), {0}));
+ QVERIFY(testForestIteration(childBegin(root), childEnd(root), {1,2,3,4}));
+
+}
+
+void KisForestTest::testDFSIteration()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+ QVERIFY(testForestIteration(childBegin(forest), childEnd(forest), {0, 8}));
+ QVERIFY(testForestIteration(childBegin(it0), childEnd(it0), {1,2,3,4}));
+ QVERIFY(testForestIteration(childBegin(it8), childEnd(it8), {9,10}));
+ QVERIFY(testForestIteration(childBegin(it3), childEnd(it3), {5}));
+ QVERIFY(testForestIteration(childBegin(it5), childEnd(it5), {6,7}));
+
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,1,2,3,5,6,7,4,8,9,10}));
+}
+
+void KisForestTest::testHierarchyIteration()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+ QVERIFY(testForestIteration(childBegin(forest), childEnd(forest), {0, 8}));
+ QVERIFY(testForestIteration(childBegin(it0), childEnd(it0), {1,2,3,4}));
+ QVERIFY(testForestIteration(childBegin(it8), childEnd(it8), {9,10}));
+ QVERIFY(testForestIteration(childBegin(it3), childEnd(it3), {5}));
+ QVERIFY(testForestIteration(childBegin(it5), childEnd(it5), {6,7}));
+
+ QVERIFY(testForestIteration(hierarchyBegin(it0), hierarchyEnd(it0), {0}));
+ QVERIFY(testForestIteration(hierarchyBegin(forest.end()), hierarchyEnd(forest.end()), {}));
+
+ QVERIFY(testForestIteration(hierarchyBegin(it7), hierarchyEnd(it7), {7,5,3,0}));
+ QVERIFY(testForestIteration(hierarchyBegin(it5), hierarchyEnd(it5), {5,3,0}));
+ QVERIFY(testForestIteration(hierarchyBegin(it4), hierarchyEnd(it4), {4,0}));
+ QVERIFY(testForestIteration(hierarchyBegin(it10), hierarchyEnd(it10), {10,8}));
+}
+
+void KisForestTest::testSiblingIteration()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+ /**
+ * Test if all types of iterators are convertible into a child iterator
+ */
+ QVERIFY(testForestIteration(siblingCurrent(it2), siblingEnd(it2), {2,3,4}));
+ QVERIFY(testForestIteration(siblingCurrent(hierarchyBegin(it2)), siblingEnd(hierarchyBegin(it2)), {2,3,4}));
+ QVERIFY(testForestIteration(siblingCurrent(subtreeBegin(it2)), siblingEnd(subtreeBegin(it2)), {2,3,4}));
+ QVERIFY(testForestIteration(siblingCurrent(tailSubtreeBegin(it2)), siblingEnd(tailSubtreeBegin(it2)), {2,3,4}));
+ QVERIFY(testForestIteration(siblingCurrent(compositionBegin(it2)), siblingEnd(compositionBegin(it2)), {2,3,4}));
+
+ QVERIFY(testForestIteration(siblingCurrent(compositionBegin(childEnd(it0))),
+ siblingEnd(compositionBegin(childEnd(it0))), {}));
+}
+
+void KisForestTest::testCompositionIteration()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+ QVERIFY(testForestIteration(compositionBegin(forest), compositionEnd(forest), {0, 1, 1, 2, 2, 3, 5, 6, 6, 7, 7, 5, 3, 4, 4, 0, 8, 9, 9, 10, 10, 8}));
+
+}
+
+struct CompositonIteratorPairedValue
+{
+ using value_type = std::pair<int, KisForest<int>::composition_iterator::traversal_state>;
+
+ template <typename Iterator>
+ value_type operator() (Iterator it) const {
+ return std::make_pair(*it, it.state());
+ }
+};
+
+void KisForestTest::testCompositionIterationSubtree()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+ QVERIFY(testForestIteration(compositionBegin(it3), compositionEnd(it3), {3, 5, 6, 6, 7, 7, 5, 3}));
+ QVERIFY(testForestIteration(compositionBegin(it5), compositionEnd(it5), {5, 6, 6, 7, 7, 5}));
+ QVERIFY(testForestIteration(compositionBegin(it8), compositionEnd(it8), {8, 9, 9, 10, 10, 8}));
+
+ using traversal_direction = KisForest<int>::composition_iterator::traversal_state;
+
+ std::vector<std::pair<int, traversal_direction>> references;
+
+ references = {{5, traversal_direction::Enter},
+ {6, traversal_direction::Enter},
+ {6, traversal_direction::Leave},
+ {7, traversal_direction::Enter},
+ {7, traversal_direction::Leave},
+ {5, traversal_direction::Leave}};
+
+ QVERIFY(testForestIteration(compositionBegin(it5), compositionEnd(it5),
+ references, CompositonIteratorPairedValue()));
+
+ references = {{3, traversal_direction::Enter},
+ {5, traversal_direction::Enter},
+ {6, traversal_direction::Enter},
+ {6, traversal_direction::Leave},
+ {7, traversal_direction::Enter},
+ {7, traversal_direction::Leave},
+ {5, traversal_direction::Leave},
+ {3, traversal_direction::Leave}};
+
+ QVERIFY(testForestIteration(compositionBegin(it3), compositionEnd(it3),
+ references, CompositonIteratorPairedValue()));
+
+}
+
+void KisForestTest::testSubtreeIteration()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+
+ QVERIFY(testForestIteration(subtreeBegin(it3), subtreeEnd(it3), {3,5,6,7}));
+ QVERIFY(testForestIteration(subtreeBegin(it0), subtreeEnd(it0), {0,1,2,3,5,6,7,4}));
+ QVERIFY(testForestIteration(subtreeBegin(it8), subtreeEnd(it8), {8,9,10}));
+}
+
+void KisForestTest::testSubtreeTailIteration()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+
+ QVERIFY(testForestIteration(tailSubtreeBegin(it3), tailSubtreeEnd(it3), {6,7,5,3}));
+ QVERIFY(testForestIteration(tailSubtreeBegin(it0), tailSubtreeEnd(it0), {1,2,6,7,5,3,4,0}));
+ QVERIFY(testForestIteration(tailSubtreeBegin(it8), tailSubtreeEnd(it8), {9,10,8}));
+
+ QVERIFY(testForestIteration(tailSubtreeBegin(forest), tailSubtreeEnd(forest), {1,2,6,7,5,3,4,0,9,10,8}));
+}
+
+void KisForestTest::testEraseNode()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+
+ QCOMPARE(forest.erase(it6), it7);
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,1,2,3,5,7,4,8,9,10}));
+
+ QCOMPARE(forest.erase(it7), childEnd(it5));
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,1,2,3,5,4,8,9,10}));
+
+ QCOMPARE(forest.erase(it10), childEnd(it8));
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,1,2,3,5,4,8,9}));
+
+ QCOMPARE(forest.erase(it9), childEnd(it8));
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,1,2,3,5,4,8}));
+
+ QCOMPARE(forest.erase(it8), childEnd(forest));
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,1,2,3,5,4}));
+}
+
+void KisForestTest::testEraseSubtree()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+
+ QCOMPARE(forest.erase(it3), it4);
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,1,2,4,8,9,10}));
+
+ QCOMPARE(forest.erase(it8), childEnd(forest));
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,1,2,4}));
+
+ QCOMPARE(forest.erase(it0), childEnd(forest));
+ QVERIFY(testForestIteration(begin(forest), end(forest), {}));
+}
+
+void KisForestTest::testEraseRange()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ */
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+
+ QCOMPARE(forest.erase(it1, it4), it4);
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,4,8,9,10}));
+
+ QCOMPARE(forest.erase(it4, childEnd(it0)), childEnd(it0));
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,8,9,10}));
+}
+
+void KisForestTest::testMoveSubtree()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ */
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+
+ auto newPos = forest.move(it3, it9);
+ QCOMPARE(newPos, childBegin(it8));
+
+ QVERIFY(testForestIteration(begin(forest), end(forest), {0,1,2,4,8,3,5,6,7,9,10}));
+
+ newPos = forest.move(it0, childEnd(it8));
+ QCOMPARE(newPos, std::prev(childEnd(it8)));
+
+ QVERIFY(testForestIteration(begin(forest), end(forest), {8,3,5,6,7,9,10,0,1,2,4}));
+}
+
+void KisForestTest::testReversedChildIteration()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+ QVERIFY(testForestIteration(make_reverse_iterator(childEnd(forest)),
+ make_reverse_iterator(childBegin(forest)), {8, 0}));
+
+ QVERIFY(testForestIteration(make_reverse_iterator(childEnd(it0)),
+ make_reverse_iterator(childBegin(it0)), {4,3,2,1}));
+
+ QVERIFY(testForestIteration(make_reverse_iterator(childEnd(it3)),
+ make_reverse_iterator(childBegin(it3)), {5}));
+}
+
+void KisForestTest::testConversionsFromEnd()
+{
+ KisForest<int> forest;
+
+ /**
+ * 0 1
+ * 2
+ * 3 5 6
+ * 7
+ * 4
+ * 8 9
+ * 10
+ **/
+
+
+ auto it0 = forest.insert(childBegin(forest), 0);
+ auto it8 = forest.insert(childEnd(forest), 8);
+
+ auto it1 = forest.insert(childEnd(it0), 1);
+ auto it2 = forest.insert(childEnd(it0), 2);
+ auto it3 = forest.insert(childEnd(it0), 3);
+ auto it4 = forest.insert(childEnd(it0), 4);
+
+ auto it5 = forest.insert(childEnd(it3), 5);
+
+ auto it6 = forest.insert(childEnd(it5), 6);
+ auto it7 = forest.insert(childEnd(it5), 7);
+
+ auto it9 = forest.insert(childEnd(it8), 9);
+ auto it10 = forest.insert(childEnd(it8), 10);
+
+ Q_UNUSED(it1);
+ Q_UNUSED(it2);
+ Q_UNUSED(it6);
+ Q_UNUSED(it7);
+ Q_UNUSED(it4);
+ Q_UNUSED(it9);
+ Q_UNUSED(it10);
+
+ /**
+ * Currently, all operations on end-iterators are declared as "undefined behavior",
+ * but, ideally, we should care about them. Like, it should be possible to get children
+ * of the forest by calling childBegin/End(hierarchyEnd(it0)). I (DK) am not sure if
+ * it is possible to implement without overhead. So I just added this test to document
+ * "desired" behavior. But for now, noone should rely on this behavior, just consider
+ * all operations with end-iterators as UB.
+ */
+
+#define HIDE_UB_NOISE
+
+ QVERIFY(testForestIteration(childBegin(childEnd(it0)),
+ childEnd(childEnd(it0)), {}));
+
+#ifndef HIDE_UB_NOISE
+ QEXPECT_FAIL("", "Fetching children of root node should return roots of the forest?", Continue);
+ QVERIFY(testForestIteration(childBegin(hierarchyEnd(it0)),
+ childEnd(hierarchyEnd(it0)), {0, 8}));
+ QEXPECT_FAIL("", "End of composition should not (?) point to any existing node", Continue);
+ QVERIFY(testForestIteration(childBegin(compositionEnd(it0)),
+ childEnd(compositionEnd(it0)), {}));
+#endif
+
+ QVERIFY(testForestIteration(hierarchyBegin(childEnd(it0)),
+ hierarchyEnd(childEnd(it0)), {}));
+ QVERIFY(testForestIteration(hierarchyBegin(hierarchyEnd(it0)),
+ hierarchyEnd(hierarchyEnd(it0)), {}));
+#ifndef HIDE_UB_NOISE
+ QEXPECT_FAIL("", "End of composition should not (?) point to any existing node", Continue);
+ QVERIFY(testForestIteration(hierarchyBegin(compositionEnd(it0)),
+ hierarchyEnd(compositionEnd(it0)), {}));
+#endif
+
+ QVERIFY(testForestIteration(compositionBegin(childEnd(it0)),
+ compositionEnd(childEnd(it0)), {}));
+#ifndef HIDE_UB_NOISE
+ QEXPECT_FAIL("", "Starting composition on the root node should behave like we do it for the forest itself?", Continue);
+ QVERIFY(testForestIteration(compositionBegin(hierarchyEnd(it0)),
+ compositionEnd(hierarchyEnd(it0)), {0, 1, 1, 2, 2, 3, 5, 6, 6, 7, 7, 5, 3, 4, 4, 0, 8, 9, 9, 10, 10, 8}));
+ QEXPECT_FAIL("", "End of composition should not (?) point to any existing node", Continue);
+ QVERIFY(testForestIteration(compositionBegin(compositionEnd(it0)),
+ compositionEnd(compositionEnd(it0)), {}));
+#endif
+
+#ifndef HIDE_UB_NOISE
+ QEXPECT_FAIL("", "Fetching siblings from child-end should work?", Continue);
+ QVERIFY(testForestIteration(siblingBegin(childEnd(it0)),
+ siblingEnd(childEnd(it0)), {1,2,3,4}));
+#endif
+ QVERIFY(testForestIteration(siblingBegin(hierarchyEnd(it0)),
+ siblingEnd(hierarchyEnd(it0)), {}));
+ QVERIFY(testForestIteration(siblingBegin(compositionEnd(it0)),
+ siblingEnd(compositionEnd(it0)), {0,8}));
+
+
+
+ QVERIFY(testForestIteration(childBegin(childEnd(forest)),
+ childEnd(childEnd(forest)), {}));
+ QVERIFY(testForestIteration(childBegin(compositionEnd(forest)),
+ childEnd(compositionEnd(forest)), {}));
+
+ QVERIFY(testForestIteration(hierarchyBegin(childEnd(forest)),
+ hierarchyEnd(childEnd(forest)), {}));
+ QVERIFY(testForestIteration(hierarchyBegin(compositionEnd(forest)),
+ hierarchyEnd(compositionEnd(forest)), {}));
+
+ QVERIFY(testForestIteration(compositionBegin(childEnd(forest)),
+ compositionEnd(childEnd(forest)), {}));
+ QVERIFY(testForestIteration(compositionBegin(compositionEnd(forest)),
+ compositionEnd(compositionEnd(forest)), {}));
+
+#ifndef HIDE_UB_NOISE
+ QEXPECT_FAIL("", "Fetching siblings from forest's child-end should work?", Continue);
+ QVERIFY(testForestIteration(siblingBegin(childEnd(forest)),
+ siblingEnd(childEnd(forest)), {0,8}));
+ QEXPECT_FAIL("", "Fetching siblings from forest's composition-end should work?", Continue);
+ QVERIFY(testForestIteration(siblingBegin(compositionEnd(forest)),
+ siblingEnd(compositionEnd(forest)), {0,8}));
+#endif
+
+#undef HIDE_UB_NOISE
+}
+
+QTEST_MAIN(KisForestTest)
diff --git a/libs/flake/tests/KisGamutMaskViewConverterTest.h b/libs/global/tests/KisForestTest.h
similarity index 51%
copy from libs/flake/tests/KisGamutMaskViewConverterTest.h
copy to libs/global/tests/KisForestTest.h
index fdde941568..97a8765360 100644
--- a/libs/flake/tests/KisGamutMaskViewConverterTest.h
+++ b/libs/global/tests/KisForestTest.h
@@ -1,45 +1,55 @@
/*
- * Copyright (c) 2019 Anna Medonosova <anna.medonosova@gmail.com>
+ * Copyright (c) 2019 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 KISFORESTTEST_H
+#define KISFORESTTEST_H
-#ifndef KISGAMUTMASKVIEWCONVERTERTEST_H
-#define KISGAMUTMASKVIEWCONVERTERTEST_H
+#include <QtTest>
#include <QObject>
-#include <QTest>
-class KisGamutMaskViewConverterTest : public QObject
+class KisForestTest : public QObject
{
Q_OBJECT
-public:
- explicit KisGamutMaskViewConverterTest(QObject *parent = nullptr);
private Q_SLOTS:
- void testDocumentToViewX();
- void testDocumentToViewX_data();
+ void testAddToRoot();
+ void testAddToRootChained();
+ void testAddToLeaf();
+ void testAddToLeafChained();
- void testDocumentToViewY();
- void testDocumentToViewY_data();
+ void testDFSIteration();
+ void testHierarchyIteration();
+ void testSiblingIteration();
+ void testCompositionIteration();
+ void testCompositionIterationSubtree();
- void testViewToDocumentX();
- void testViewToDocumentX_data();
+ void testSubtreeIteration();
+ void testSubtreeTailIteration();
- void testViewToDocumentY();
- void testViewToDocumentY_data();
+ void testEraseNode();
+ void testEraseSubtree();
+ void testEraseRange();
+
+ void testMoveSubtree();
+
+ void testReversedChildIteration();
+
+ void testConversionsFromEnd();
};
-#endif // KISGAMUTMASKVIEWCONVERTERTEST_H
+#endif // KISFORESTTEST_H
diff --git a/libs/image/KisSelectionUpdateCompressor.cpp b/libs/image/KisSelectionUpdateCompressor.cpp
index d9fd32e272..129e6d471b 100644
--- a/libs/image/KisSelectionUpdateCompressor.cpp
+++ b/libs/image/KisSelectionUpdateCompressor.cpp
@@ -1,76 +1,75 @@
/*
* Copyright (c) 2018 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 "KisSelectionUpdateCompressor.h"
#include "kis_image.h"
#include "kis_selection.h"
#include "kis_layer_utils.h"
#include "kis_update_selection_job.h"
KisSelectionUpdateCompressor::KisSelectionUpdateCompressor(KisSelection *selection)
- : m_parentSelection(selection),
- m_updateSignalCompressor(new KisThreadSafeSignalCompressor(100, KisSignalCompressor::POSTPONE)),
- m_hasStalledUpdate(false)
+ : m_parentSelection(selection)
+ , m_updateSignalCompressor(new KisThreadSafeSignalCompressor(100, KisSignalCompressor::POSTPONE))
{
connect(m_updateSignalCompressor, SIGNAL(timeout()), this, SLOT(startUpdateJob()));
this->moveToThread(m_updateSignalCompressor->thread());
}
KisSelectionUpdateCompressor::~KisSelectionUpdateCompressor()
{
m_updateSignalCompressor->deleteLater();
}
void KisSelectionUpdateCompressor::requestUpdate(const QRect &updateRect)
{
m_fullUpdateRequested |= updateRect.isEmpty();
m_updateRect = !m_fullUpdateRequested ? m_updateRect | updateRect : QRect();
m_updateSignalCompressor->start();
}
void KisSelectionUpdateCompressor::tryProcessStalledUpdate()
{
if (m_hasStalledUpdate) {
m_updateSignalCompressor->start();
}
}
void KisSelectionUpdateCompressor::startUpdateJob()
{
KisNodeSP parentNode = m_parentSelection->parentNode();
if (!parentNode) {
m_hasStalledUpdate = true;
return;
}
KisImageSP image = KisLayerUtils::findImageByHierarchy(parentNode);
if (!image) {
m_hasStalledUpdate = true;
return;
}
if (image) {
image->addSpontaneousJob(new KisUpdateSelectionJob(m_parentSelection, m_updateRect));
}
m_updateRect = QRect();
m_fullUpdateRequested = false;
m_hasStalledUpdate = false;
}
diff --git a/libs/image/KisSelectionUpdateCompressor.h b/libs/image/KisSelectionUpdateCompressor.h
index e003c3dd88..4ab98c5054 100644
--- a/libs/image/KisSelectionUpdateCompressor.h
+++ b/libs/image/KisSelectionUpdateCompressor.h
@@ -1,52 +1,52 @@
/*
* Copyright (c) 2018 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 KISSELECTIONUPDATECOMPRESSOR_H
#define KISSELECTIONUPDATECOMPRESSOR_H
#include "kritaimage_export.h"
#include "kis_thread_safe_signal_compressor.h"
#include "kis_types.h"
#include <QRect>
class KisSelectionUpdateCompressor : public QObject
{
Q_OBJECT
public:
KisSelectionUpdateCompressor(KisSelection *selection);
~KisSelectionUpdateCompressor();
public Q_SLOTS:
void requestUpdate(const QRect &updateRect);
void tryProcessStalledUpdate();
private Q_SLOTS:
void startUpdateJob();
private:
- KisSelection *m_parentSelection;
- KisThreadSafeSignalCompressor *m_updateSignalCompressor;
+ KisSelection *m_parentSelection {0};
+ KisThreadSafeSignalCompressor *m_updateSignalCompressor {0};
QRect m_updateRect;
- bool m_fullUpdateRequested;
+ bool m_fullUpdateRequested {false};
- bool m_hasStalledUpdate;
+ bool m_hasStalledUpdate {false};
};
#endif // KISSELECTIONUPDATECOMPRESSOR_H
diff --git a/libs/image/brushengine/kis_paintop.cc b/libs/image/brushengine/kis_paintop.cc
index ac28205c37..c896dbb605 100644
--- a/libs/image/brushengine/kis_paintop.cc
+++ b/libs/image/brushengine/kis_paintop.cc
@@ -1,211 +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 <QtMath>
#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 qint32 i = qFloor(coordinate);
const qreal f = coordinate - i;
*whole = i;
*fraction = f;
}
std::pair<int, bool> KisPaintOp::doAsyncronousUpdate(QVector<KisRunnableStrokeJobData *> &jobs)
{
Q_UNUSED(jobs);
return std::make_pair(40, false);
}
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/commands_new/kis_change_projection_color_command.cpp b/libs/image/commands_new/kis_change_projection_color_command.cpp
index 5d4c0d8bbf..d32934af51 100644
--- a/libs/image/commands_new/kis_change_projection_color_command.cpp
+++ b/libs/image/commands_new/kis_change_projection_color_command.cpp
@@ -1,78 +1,78 @@
/*
* 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_change_projection_color_command.h"
#include "kis_image.h"
#include "kis_image_animation_interface.h"
KisChangeProjectionColorCommand::KisChangeProjectionColorCommand(KisImageSP image, const KoColor &newColor, KUndo2Command *parent)
: KUndo2Command(kundo2_noi18n("CHANGE_PROJECTION_COLOR_COMMAND"), parent),
m_image(image),
m_oldColor(image->defaultProjectionColor()),
m_newColor(newColor)
{
}
KisChangeProjectionColorCommand::~KisChangeProjectionColorCommand()
{
}
int KisChangeProjectionColorCommand::id() const
{
// we don't have a common commands id source in Krita yet, so
// just use a random one ;)
- // http://www.scientificamerican.com/article/most-popular-numbers-grapes-of-math/
+ // https://www.scientificamerican.com/article/most-popular-numbers-grapes-of-math/
return 142857;
}
bool KisChangeProjectionColorCommand::mergeWith(const KUndo2Command* command)
{
const KisChangeProjectionColorCommand *other =
dynamic_cast<const KisChangeProjectionColorCommand*>(command);
if (!other || other->id() != id()) {
return false;
}
m_newColor = other->m_newColor;
return true;
}
void KisChangeProjectionColorCommand::redo()
{
KisImageSP image = m_image.toStrongRef();
if (!image) {
return;
}
image->setDefaultProjectionColor(m_newColor);
image->animationInterface()->setDefaultProjectionColor(m_newColor);
}
void KisChangeProjectionColorCommand::undo()
{
KisImageSP image = m_image.toStrongRef();
if (!image) {
return;
}
image->setDefaultProjectionColor(m_oldColor);
image->animationInterface()->setDefaultProjectionColor(m_oldColor);
}
diff --git a/libs/image/commands_new/kis_saved_commands.cpp b/libs/image/commands_new/kis_saved_commands.cpp
index 3308ed5765..17c4065f1e 100644
--- a/libs/image/commands_new/kis_saved_commands.cpp
+++ b/libs/image/commands_new/kis_saved_commands.cpp
@@ -1,319 +1,314 @@
/*
* 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_saved_commands.h"
#include <QVector>
#include "kis_image_interfaces.h"
#include "kis_stroke_strategy_undo_command_based.h"
KisSavedCommandBase::KisSavedCommandBase(const KUndo2MagicString &name,
KisStrokesFacade *strokesFacade)
: KUndo2Command(name),
m_strokesFacade(strokesFacade),
m_skipOneRedo(true)
{
}
KisSavedCommandBase::~KisSavedCommandBase()
{
}
KisStrokesFacade* KisSavedCommandBase::strokesFacade()
{
return m_strokesFacade;
}
void KisSavedCommandBase::runStroke(bool undo)
{
KisStrokeStrategyUndoCommandBased *strategy =
new KisStrokeStrategyUndoCommandBased(text(), undo, 0);
strategy->setUsedWhileUndoRedo(true);
KisStrokeId id = m_strokesFacade->startStroke(strategy);
addCommands(id, undo);
m_strokesFacade->endStroke(id);
}
void KisSavedCommandBase::undo()
{
runStroke(true);
}
void KisSavedCommandBase::redo()
{
/**
* All the commands are first executed in the stroke and then
* added to the undo stack. It means that the first redo should be
* skipped
*/
if(m_skipOneRedo) {
m_skipOneRedo = false;
return;
}
runStroke(false);
}
KisSavedCommand::KisSavedCommand(KUndo2CommandSP command,
KisStrokesFacade *strokesFacade)
: KisSavedCommandBase(command->text(), strokesFacade),
m_command(command)
{
}
int KisSavedCommand::id() const
{
return m_command->id();
}
bool KisSavedCommand::mergeWith(const KUndo2Command* command)
{
const KisSavedCommand *other =
dynamic_cast<const KisSavedCommand*>(command);
if (other) {
command = other->m_command.data();
}
return m_command->mergeWith(command);
}
void KisSavedCommand::addCommands(KisStrokeId id, bool undo)
{
strokesFacade()->
addJob(id, new KisStrokeStrategyUndoCommandBased::Data(m_command, undo));
}
int KisSavedCommand::timedId()
{
return m_command->timedId();
}
void KisSavedCommand::setTimedID(int timedID)
{
m_command->setTimedID(timedID);
}
bool KisSavedCommand::timedMergeWith(KUndo2Command *other)
{
return m_command->timedMergeWith(other);
}
QVector<KUndo2Command*> KisSavedCommand::mergeCommandsVector()
{
return m_command->mergeCommandsVector();
}
void KisSavedCommand::setTime()
{
m_command->setTime();
}
QTime KisSavedCommand::time()
{
return m_command->time();
}
void KisSavedCommand::setEndTime()
{
m_command->setEndTime();
}
QTime KisSavedCommand::endTime()
{
return m_command->endTime();
}
bool KisSavedCommand::isMerged()
{
return m_command->isMerged();
}
struct KisSavedMacroCommand::Private
{
struct SavedCommand {
KUndo2CommandSP command;
KisStrokeJobData::Sequentiality sequentiality;
KisStrokeJobData::Exclusivity exclusivity;
};
QVector<SavedCommand> commands;
int macroId = -1;
const KisSavedMacroCommand *overriddenCommand = 0;
QVector<const KUndo2Command*> skipWhenOverride;
};
KisSavedMacroCommand::KisSavedMacroCommand(const KUndo2MagicString &name,
KisStrokesFacade *strokesFacade)
: KisSavedCommandBase(name, strokesFacade),
m_d(new Private())
{
}
KisSavedMacroCommand::~KisSavedMacroCommand()
{
delete m_d;
}
void KisSavedMacroCommand::setMacroId(int value)
{
m_d->macroId = value;
}
int KisSavedMacroCommand::id() const
{
return m_d->macroId;
}
bool KisSavedMacroCommand::mergeWith(const KUndo2Command* command)
{
const KisSavedMacroCommand *other =
dynamic_cast<const KisSavedMacroCommand*>(command);
if (!other || other->id() != id() || id() < 0 || other->id() < 0) return false;
QVector<Private::SavedCommand> &otherCommands = other->m_d->commands;
if (other->m_d->overriddenCommand == this) {
m_d->commands.clear();
Q_FOREACH (Private::SavedCommand cmd, other->m_d->commands) {
if (!other->m_d->skipWhenOverride.contains(cmd.command.data())) {
m_d->commands.append(cmd);
}
}
if (other->extraData()) {
setExtraData(other->extraData()->clone());
} else {
setExtraData(0);
}
return true;
}
if (m_d->commands.size() != otherCommands.size()) return false;
auto it = m_d->commands.constBegin();
auto end = m_d->commands.constEnd();
auto otherIt = otherCommands.constBegin();
auto otherEnd = otherCommands.constEnd();
bool sameCommands = true;
while (it != end && otherIt != otherEnd) {
if (it->command->id() < 0 ||
otherIt->command->id() < 0 ||
it->command->id() != otherIt->command->id() ||
it->sequentiality != otherIt->sequentiality ||
it->exclusivity != otherIt->exclusivity) {
sameCommands = false;
break;
}
++it;
++otherIt;
}
if (!sameCommands) return false;
it = m_d->commands.constBegin();
otherIt = otherCommands.constBegin();
while (it != end && otherIt != otherEnd) {
if (it->command->id() != -1) {
bool result = it->command->mergeWith(otherIt->command.data());
KIS_ASSERT_RECOVER(result) { return false; }
}
++it;
++otherIt;
}
if (other->extraData()) {
setExtraData(other->extraData()->clone());
} else {
setExtraData(0);
}
return true;
}
void KisSavedMacroCommand::addCommand(KUndo2CommandSP command,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity)
{
Private::SavedCommand item;
item.command = command;
item.sequentiality = sequentiality;
item.exclusivity = exclusivity;
m_d->commands.append(item);
}
-void KisSavedMacroCommand::performCancel(KisStrokeId id, bool strokeUndo)
-{
- addCommands(id, !strokeUndo);
-}
-
void KisSavedMacroCommand::getCommandExecutionJobs(QVector<KisStrokeJobData *> *jobs, bool undo, bool shouldGoToHistory) const
{
QVector<Private::SavedCommand>::iterator it;
if(!undo) {
for(it = m_d->commands.begin(); it != m_d->commands.end(); it++) {
*jobs << new KisStrokeStrategyUndoCommandBased::
Data(it->command,
undo,
it->sequentiality,
it->exclusivity,
shouldGoToHistory);
}
}
else {
for(it = m_d->commands.end(); it != m_d->commands.begin();) {
--it;
*jobs << new KisStrokeStrategyUndoCommandBased::
Data(it->command,
undo,
it->sequentiality,
it->exclusivity,
shouldGoToHistory);
}
}
}
void KisSavedMacroCommand::setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector<const KUndo2Command*> &skipWhileOverride)
{
m_d->overriddenCommand = overriddenCommand;
m_d->skipWhenOverride = skipWhileOverride;
}
void KisSavedMacroCommand::addCommands(KisStrokeId id, bool undo)
{
QVector<KisStrokeJobData *> jobs;
getCommandExecutionJobs(&jobs, undo);
Q_FOREACH (KisStrokeJobData *job, jobs) {
strokesFacade()->addJob(id, job);
}
}
diff --git a/libs/image/commands_new/kis_saved_commands.h b/libs/image/commands_new/kis_saved_commands.h
index 4f23de43fc..021e4f96d2 100644
--- a/libs/image/commands_new/kis_saved_commands.h
+++ b/libs/image/commands_new/kis_saved_commands.h
@@ -1,105 +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_SAVED_COMMANDS_H
#define __KIS_SAVED_COMMANDS_H
#include <kundo2command.h>
#include "kis_types.h"
#include "kis_stroke_job_strategy.h"
class KisStrokesFacade;
class KRITAIMAGE_EXPORT KisSavedCommandBase : public KUndo2Command
{
public:
KisSavedCommandBase(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade);
~KisSavedCommandBase() override;
void undo() override;
void redo() override;
protected:
virtual void addCommands(KisStrokeId id, bool undo) = 0;
KisStrokesFacade* strokesFacade();
private:
void runStroke(bool undo);
private:
KisStrokesFacade *m_strokesFacade;
bool m_skipOneRedo;
};
class KRITAIMAGE_EXPORT KisSavedCommand : public KisSavedCommandBase
{
public:
KisSavedCommand(KUndo2CommandSP command, KisStrokesFacade *strokesFacade);
int timedId() override;
void setTimedID(int timedID) override;
int id() const override;
bool mergeWith(const KUndo2Command* command) override;
bool timedMergeWith(KUndo2Command *other) override;
QVector<KUndo2Command*> mergeCommandsVector() override;
void setTime() override;
QTime time() override;
void setEndTime() override;
QTime endTime() override;
bool isMerged() override;
protected:
void addCommands(KisStrokeId id, bool undo) override;
private:
KUndo2CommandSP m_command;
};
class KRITAIMAGE_EXPORT KisSavedMacroCommand : public KisSavedCommandBase
{
public:
KisSavedMacroCommand(const KUndo2MagicString &name, KisStrokesFacade *strokesFacade);
~KisSavedMacroCommand() override;
int id() const override;
bool mergeWith(const KUndo2Command* command) override;
void setMacroId(int value);
void addCommand(KUndo2CommandSP command,
KisStrokeJobData::Sequentiality sequentiality = KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::Exclusivity exclusivity = KisStrokeJobData::NORMAL);
- void performCancel(KisStrokeId id, bool strokeUndo);
-
void getCommandExecutionJobs(QVector<KisStrokeJobData*> *jobs, bool undo, bool shouldGoToHistory = true) const;
void setOverrideInfo(const KisSavedMacroCommand *overriddenCommand, const QVector<const KUndo2Command *> &skipWhileOverride);
protected:
void addCommands(KisStrokeId id, bool undo) override;
private:
struct Private;
Private * const m_d;
};
#endif /* __KIS_SAVED_COMMANDS_H */
diff --git a/libs/image/floodfill/kis_scanline_fill.cpp b/libs/image/floodfill/kis_scanline_fill.cpp
index 26ce86a28f..60d43fc2b7 100644
--- a/libs/image/floodfill/kis_scanline_fill.cpp
+++ b/libs/image/floodfill/kis_scanline_fill.cpp
@@ -1,732 +1,732 @@
/*
* 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_scanline_fill.h"
#include <KoAlwaysInline.h>
#include <QStack>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include "kis_image.h"
#include "kis_fill_interval_map.h"
#include "kis_pixel_selection.h"
#include "kis_random_accessor_ng.h"
#include "kis_fill_sanity_checks.h"
template <class BaseClass>
class CopyToSelection : public BaseClass
{
public:
typedef KisRandomConstAccessorSP SourceAccessorType;
SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) {
return device->createRandomConstAccessorNG(0, 0);
}
public:
void setDestinationSelection(KisPaintDeviceSP pixelSelection) {
m_pixelSelection = pixelSelection;
m_it = m_pixelSelection->createRandomAccessorNG(0,0);
}
ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) {
Q_UNUSED(dstPtr);
m_it->moveTo(x, y);
*m_it->rawData() = opacity;
}
private:
KisPaintDeviceSP m_pixelSelection;
KisRandomAccessorSP m_it;
};
template <class BaseClass>
class FillWithColor : public BaseClass
{
public:
typedef KisRandomAccessorSP SourceAccessorType;
SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) {
return device->createRandomAccessorNG(0, 0);
}
public:
FillWithColor() : m_pixelSize(0) {}
void setFillColor(const KoColor &sourceColor) {
m_sourceColor = sourceColor;
m_pixelSize = sourceColor.colorSpace()->pixelSize();
m_data = m_sourceColor.data();
}
ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) {
Q_UNUSED(x);
Q_UNUSED(y);
if (opacity == MAX_SELECTED) {
memcpy(dstPtr, m_data, m_pixelSize);
}
}
private:
KoColor m_sourceColor;
const quint8 *m_data;
int m_pixelSize;
};
template <class BaseClass>
class FillWithColorExternal : public BaseClass
{
public:
typedef KisRandomConstAccessorSP SourceAccessorType;
SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) {
return device->createRandomConstAccessorNG(0, 0);
}
public:
void setDestinationDevice(KisPaintDeviceSP device) {
m_externalDevice = device;
m_it = m_externalDevice->createRandomAccessorNG(0,0);
}
void setFillColor(const KoColor &sourceColor) {
m_sourceColor = sourceColor;
m_pixelSize = sourceColor.colorSpace()->pixelSize();
m_data = m_sourceColor.data();
}
ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) {
Q_UNUSED(dstPtr);
m_it->moveTo(x, y);
if (opacity == MAX_SELECTED) {
memcpy(m_it->rawData(), m_data, m_pixelSize);
}
}
private:
KisPaintDeviceSP m_externalDevice;
KisRandomAccessorSP m_it;
KoColor m_sourceColor;
const quint8 *m_data {0};
- int m_pixelSize;
+ int m_pixelSize {0};
};
class DifferencePolicySlow
{
public:
ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) {
m_colorSpace = device->colorSpace();
m_srcPixel = srcPixel;
m_srcPixelPtr = m_srcPixel.data();
m_threshold = threshold;
}
ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) {
if (m_threshold == 1) {
if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) {
return 0;
}
return quint8_MAX;
}
else {
return m_colorSpace->difference(m_srcPixelPtr, pixelPtr);
}
}
private:
const KoColorSpace *m_colorSpace;
KoColor m_srcPixel;
const quint8 *m_srcPixelPtr;
int m_threshold;
};
template <typename SrcPixelType>
class DifferencePolicyOptimized
{
typedef SrcPixelType HashKeyType;
typedef QHash<HashKeyType, quint8> HashType;
public:
ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) {
m_colorSpace = device->colorSpace();
m_srcPixel = srcPixel;
m_srcPixelPtr = m_srcPixel.data();
m_threshold = threshold;
}
ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) {
HashKeyType key = *reinterpret_cast<HashKeyType*>(pixelPtr);
quint8 result;
typename HashType::iterator it = m_differences.find(key);
if (it != m_differences.end()) {
result = *it;
} else {
if (m_threshold == 1) {
if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) {
result = 0;
}
else {
result = quint8_MAX;
}
}
else {
result = m_colorSpace->difference(m_srcPixelPtr, pixelPtr);
}
m_differences.insert(key, result);
}
return result;
}
private:
HashType m_differences;
const KoColorSpace *m_colorSpace;
KoColor m_srcPixel;
const quint8 *m_srcPixelPtr;
int m_threshold;
};
template <bool useSmoothSelection,
class DifferencePolicy,
template <class> class PixelFiller>
class SelectionPolicy : public PixelFiller<DifferencePolicy>
{
public:
typename PixelFiller<DifferencePolicy>::SourceAccessorType m_srcIt;
public:
SelectionPolicy(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold)
: m_threshold(threshold)
{
this->initDifferences(device, srcPixel, threshold);
m_srcIt = this->createSourceDeviceAccessor(device);
}
ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) {
quint8 diff = this->calculateDifference(pixelPtr);
if (!useSmoothSelection) {
return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED;
} else {
quint8 selectionValue = qMax(0, m_threshold - diff);
quint8 result = MIN_SELECTED;
if (selectionValue > 0) {
qreal selectionNorm = qreal(selectionValue) / m_threshold;
result = MAX_SELECTED * selectionNorm;
}
return result;
}
}
private:
int m_threshold;
};
class IsNonNullPolicySlow
{
public:
- ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) {
+ ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/)
+ {
Q_UNUSED(srcPixel);
-
m_pixelSize = device->pixelSize();
m_testPixel.resize(m_pixelSize);
}
ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) {
if (memcmp(m_testPixel.data(), pixelPtr, m_pixelSize) == 0) {
return 0;
}
return quint8_MAX;
}
private:
- int m_pixelSize;
+ int m_pixelSize {0};
QByteArray m_testPixel;
};
template <typename SrcPixelType>
class IsNonNullPolicyOptimized
{
public:
ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) {
Q_UNUSED(device);
Q_UNUSED(srcPixel);
}
ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) {
SrcPixelType *pixel = reinterpret_cast<SrcPixelType*>(pixelPtr);
return *pixel == 0;
}
};
class GroupSplitPolicy
{
public:
typedef KisRandomAccessorSP SourceAccessorType;
SourceAccessorType m_srcIt;
public:
GroupSplitPolicy(KisPaintDeviceSP scribbleDevice,
KisPaintDeviceSP groupMapDevice,
qint32 groupIndex,
quint8 referenceValue, int threshold)
: m_threshold(threshold),
m_groupIndex(groupIndex),
m_referenceValue(referenceValue)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(m_groupIndex > 0);
m_srcIt = scribbleDevice->createRandomAccessorNG(0,0);
m_groupMapIt = groupMapDevice->createRandomAccessorNG(0,0);
}
ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) {
// TODO: either threshold should always be null, or there should be a special
// case for *pixelPtr == 0, which is different from all the other groups,
// whatever the threshold is
int diff = qAbs(int(*pixelPtr) - m_referenceValue);
return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED;
}
ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) {
Q_UNUSED(opacity);
// erase the scribble
*dstPtr = 0;
// write group index into the map
m_groupMapIt->moveTo(x, y);
qint32 *groupMapPtr = reinterpret_cast<qint32*>(m_groupMapIt->rawData());
if (*groupMapPtr != 0) {
dbgImage << ppVar(*groupMapPtr) << ppVar(m_groupIndex);
}
KIS_SAFE_ASSERT_RECOVER_NOOP(*groupMapPtr == 0);
*groupMapPtr = m_groupIndex;
}
private:
int m_threshold;
qint32 m_groupIndex;
quint8 m_referenceValue;
KisRandomAccessorSP m_groupMapIt;
};
struct Q_DECL_HIDDEN KisScanlineFill::Private
{
KisPaintDeviceSP device;
KisRandomAccessorSP it;
QPoint startPoint;
QRect boundingRect;
int threshold;
int rowIncrement;
KisFillIntervalMap backwardMap;
QStack<KisFillInterval> forwardStack;
inline void swapDirection() {
rowIncrement *= -1;
KIS_SAFE_ASSERT_RECOVER_NOOP(forwardStack.isEmpty() &&
"FATAL: the forward stack must be empty "
"on a direction swap");
forwardStack = QStack<KisFillInterval>(backwardMap.fetchAllIntervals(rowIncrement));
backwardMap.clear();
}
};
KisScanlineFill::KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect)
: m_d(new Private)
{
m_d->device = device;
m_d->it = device->createRandomAccessorNG(startPoint.x(), startPoint.y());
m_d->startPoint = startPoint;
m_d->boundingRect = boundingRect;
m_d->rowIncrement = 1;
m_d->threshold = 0;
}
KisScanlineFill::~KisScanlineFill()
{
}
void KisScanlineFill::setThreshold(int threshold)
{
m_d->threshold = threshold;
}
template <class T>
void KisScanlineFill::extendedPass(KisFillInterval *currentInterval, int srcRow, bool extendRight, T &pixelPolicy)
{
int x;
int endX;
int columnIncrement;
int *intervalBorder;
int *backwardIntervalBorder;
KisFillInterval backwardInterval(currentInterval->start, currentInterval->end, srcRow);
if (extendRight) {
x = currentInterval->end;
endX = m_d->boundingRect.right();
if (x >= endX) return;
columnIncrement = 1;
intervalBorder = &currentInterval->end;
backwardInterval.start = currentInterval->end + 1;
backwardIntervalBorder = &backwardInterval.end;
} else {
x = currentInterval->start;
endX = m_d->boundingRect.left();
if (x <= endX) return;
columnIncrement = -1;
intervalBorder = &currentInterval->start;
backwardInterval.end = currentInterval->start - 1;
backwardIntervalBorder = &backwardInterval.start;
}
do {
x += columnIncrement;
pixelPolicy.m_srcIt->moveTo(x, srcRow);
quint8 *pixelPtr = const_cast<quint8*>(pixelPolicy.m_srcIt->rawDataConst()); // TODO: avoid doing const_cast
quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr);
if (opacity) {
*intervalBorder = x;
*backwardIntervalBorder = x;
pixelPolicy.fillPixel(pixelPtr, opacity, x, srcRow);
} else {
break;
}
} while (x != endX);
if (backwardInterval.isValid()) {
m_d->backwardMap.insertInterval(backwardInterval);
}
}
template <class T>
void KisScanlineFill::processLine(KisFillInterval interval, const int rowIncrement, T &pixelPolicy)
{
m_d->backwardMap.cropInterval(&interval);
if (!interval.isValid()) return;
int firstX = interval.start;
int lastX = interval.end;
int x = firstX;
int row = interval.row;
int nextRow = row + rowIncrement;
KisFillInterval currentForwardInterval;
int numPixelsLeft = 0;
quint8 *dataPtr = 0;
const int pixelSize = m_d->device->pixelSize();
while(x <= lastX) {
// a bit of optimzation for not calling slow random accessor
// methods too often
if (numPixelsLeft <= 0) {
pixelPolicy.m_srcIt->moveTo(x, row);
numPixelsLeft = pixelPolicy.m_srcIt->numContiguousColumns(x) - 1;
dataPtr = const_cast<quint8*>(pixelPolicy.m_srcIt->rawDataConst());
} else {
numPixelsLeft--;
dataPtr += pixelSize;
}
quint8 *pixelPtr = dataPtr;
quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr);
if (opacity) {
if (!currentForwardInterval.isValid()) {
currentForwardInterval.start = x;
currentForwardInterval.end = x;
currentForwardInterval.row = nextRow;
} else {
currentForwardInterval.end = x;
}
pixelPolicy.fillPixel(pixelPtr, opacity, x, row);
if (x == firstX) {
extendedPass(&currentForwardInterval, row, false, pixelPolicy);
}
if (x == lastX) {
extendedPass(&currentForwardInterval, row, true, pixelPolicy);
}
} else {
if (currentForwardInterval.isValid()) {
m_d->forwardStack.push(currentForwardInterval);
currentForwardInterval.invalidate();
}
}
x++;
}
if (currentForwardInterval.isValid()) {
m_d->forwardStack.push(currentForwardInterval);
}
}
template <class T>
void KisScanlineFill::runImpl(T &pixelPolicy)
{
KIS_ASSERT_RECOVER_RETURN(m_d->forwardStack.isEmpty());
KisFillInterval startInterval(m_d->startPoint.x(), m_d->startPoint.x(), m_d->startPoint.y());
m_d->forwardStack.push(startInterval);
/**
* In the end of the first pass we should add an interval
* containing the starting pixel, but directed into the opposite
* direction. We cannot do it in the very beginning because the
* intervals are offset by 1 pixel during every swap operation.
*/
bool firstPass = true;
while (!m_d->forwardStack.isEmpty()) {
while (!m_d->forwardStack.isEmpty()) {
KisFillInterval interval = m_d->forwardStack.pop();
if (interval.row > m_d->boundingRect.bottom() ||
interval.row < m_d->boundingRect.top()) {
continue;
}
processLine(interval, m_d->rowIncrement, pixelPolicy);
}
m_d->swapDirection();
if (firstPass) {
startInterval.row--;
m_d->forwardStack.push(startInterval);
firstPass = false;
}
}
}
void KisScanlineFill::fillColor(const KoColor &originalFillColor)
{
KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y());
KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace());
KoColor fillColor(originalFillColor);
fillColor.convertTo(m_d->device->colorSpace());
const int pixelSize = m_d->device->pixelSize();
if (pixelSize == 1) {
SelectionPolicy<false, DifferencePolicyOptimized<quint8>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 2) {
SelectionPolicy<false, DifferencePolicyOptimized<quint16>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 4) {
SelectionPolicy<false, DifferencePolicyOptimized<quint32>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 8) {
SelectionPolicy<false, DifferencePolicyOptimized<quint64>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
runImpl(policy);
} else {
SelectionPolicy<false, DifferencePolicySlow, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
runImpl(policy);
}
}
void KisScanlineFill::fillColor(const KoColor &originalFillColor, KisPaintDeviceSP externalDevice)
{
KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y());
KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace());
KoColor fillColor(originalFillColor);
fillColor.convertTo(m_d->device->colorSpace());
const int pixelSize = m_d->device->pixelSize();
if (pixelSize == 1) {
SelectionPolicy<false, DifferencePolicyOptimized<quint8>, FillWithColorExternal>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationDevice(externalDevice);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 2) {
SelectionPolicy<false, DifferencePolicyOptimized<quint16>, FillWithColorExternal>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationDevice(externalDevice);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 4) {
SelectionPolicy<false, DifferencePolicyOptimized<quint32>, FillWithColorExternal>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationDevice(externalDevice);
policy.setFillColor(fillColor);
runImpl(policy);
} else if (pixelSize == 8) {
SelectionPolicy<false, DifferencePolicyOptimized<quint64>, FillWithColorExternal>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationDevice(externalDevice);
policy.setFillColor(fillColor);
runImpl(policy);
} else {
SelectionPolicy<false, DifferencePolicySlow, FillWithColorExternal>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationDevice(externalDevice);
policy.setFillColor(fillColor);
runImpl(policy);
}
}
void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection)
{
KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y());
KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace());
const int pixelSize = m_d->device->pixelSize();
if (pixelSize == 1) {
SelectionPolicy<true, DifferencePolicyOptimized<quint8>, CopyToSelection>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationSelection(pixelSelection);
runImpl(policy);
} else if (pixelSize == 2) {
SelectionPolicy<true, DifferencePolicyOptimized<quint16>, CopyToSelection>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationSelection(pixelSelection);
runImpl(policy);
} else if (pixelSize == 4) {
SelectionPolicy<true, DifferencePolicyOptimized<quint32>, CopyToSelection>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationSelection(pixelSelection);
runImpl(policy);
} else if (pixelSize == 8) {
SelectionPolicy<true, DifferencePolicyOptimized<quint64>, CopyToSelection>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationSelection(pixelSelection);
runImpl(policy);
} else {
SelectionPolicy<true, DifferencePolicySlow, CopyToSelection>
policy(m_d->device, srcColor, m_d->threshold);
policy.setDestinationSelection(pixelSelection);
runImpl(policy);
}
}
void KisScanlineFill::clearNonZeroComponent()
{
const int pixelSize = m_d->device->pixelSize();
KoColor srcColor(Qt::transparent, m_d->device->colorSpace());
if (pixelSize == 1) {
SelectionPolicy<false, IsNonNullPolicyOptimized<quint8>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(srcColor);
runImpl(policy);
} else if (pixelSize == 2) {
SelectionPolicy<false, IsNonNullPolicyOptimized<quint16>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(srcColor);
runImpl(policy);
} else if (pixelSize == 4) {
SelectionPolicy<false, IsNonNullPolicyOptimized<quint32>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(srcColor);
runImpl(policy);
} else if (pixelSize == 8) {
SelectionPolicy<false, IsNonNullPolicyOptimized<quint64>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(srcColor);
runImpl(policy);
} else {
SelectionPolicy<false, IsNonNullPolicySlow, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(srcColor);
runImpl(policy);
}
}
void KisScanlineFill::fillContiguousGroup(KisPaintDeviceSP groupMapDevice, qint32 groupIndex)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->device->pixelSize() == 1);
KIS_SAFE_ASSERT_RECOVER_RETURN(groupMapDevice->pixelSize() == 4);
KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y());
const quint8 referenceValue = *it->rawDataConst();
GroupSplitPolicy policy(m_d->device, groupMapDevice, groupIndex, referenceValue, m_d->threshold);
runImpl(policy);
}
void KisScanlineFill::testingProcessLine(const KisFillInterval &processInterval)
{
KoColor srcColor(QColor(0,0,0,0), m_d->device->colorSpace());
KoColor fillColor(QColor(200,200,200,200), m_d->device->colorSpace());
SelectionPolicy<false, DifferencePolicyOptimized<quint32>, FillWithColor>
policy(m_d->device, srcColor, m_d->threshold);
policy.setFillColor(fillColor);
processLine(processInterval, 1, policy);
}
QVector<KisFillInterval> KisScanlineFill::testingGetForwardIntervals() const
{
return QVector<KisFillInterval>(m_d->forwardStack);
}
KisFillIntervalMap* KisScanlineFill::testingGetBackwardIntervals() const
{
return &m_d->backwardMap;
}
diff --git a/libs/image/kis_async_merger.cpp b/libs/image/kis_async_merger.cpp
index c0790e8986..b20080f160 100644
--- a/libs/image/kis_async_merger.cpp
+++ b/libs/image/kis_async_merger.cpp
@@ -1,380 +1,384 @@
/* Copyright (c) Dmitry Kazakov <dimula73@gmail.com>, 2009
*
* 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_async_merger.h"
#include <kis_debug.h>
#include <QBitArray>
#include <KoChannelInfo.h>
#include <KoCompositeOpRegistry.h>
#include "kis_node_visitor.h"
#include "kis_painter.h"
#include "kis_layer.h"
#include "kis_group_layer.h"
#include "kis_adjustment_layer.h"
#include "generator/kis_generator_layer.h"
#include "kis_external_layer_iface.h"
#include "kis_paint_layer.h"
#include "filter/kis_filter.h"
#include "filter/kis_filter_configuration.h"
#include "filter/kis_filter_registry.h"
#include "kis_selection.h"
#include "kis_clone_layer.h"
#include "kis_processing_information.h"
#include "kis_busy_progress_indicator.h"
#include "kis_merge_walker.h"
#include "kis_refresh_subtree_walker.h"
#include "kis_abstract_projection_plane.h"
//#define DEBUG_MERGER
#ifdef DEBUG_MERGER
#define DEBUG_NODE_ACTION(message, type, leaf, rect) \
qDebug() << message << type << ":" << leaf->node()->name() << rect
#else
#define DEBUG_NODE_ACTION(message, type, leaf, rect)
#endif
class KisUpdateOriginalVisitor : public KisNodeVisitor
{
public:
KisUpdateOriginalVisitor(const QRect &updateRect, KisPaintDeviceSP projection, const QRect &cropRect)
: m_updateRect(updateRect),
m_cropRect(cropRect),
m_projection(projection)
{
}
~KisUpdateOriginalVisitor() override {
}
public:
using KisNodeVisitor::visit;
bool visit(KisAdjustmentLayer* layer) override {
if (!layer->visible()) return true;
if (!m_projection) {
warnImage << "ObligeChild mechanism has been activated for "
"an adjustment layer! Do nothing...";
layer->original()->clear();
return true;
}
const QRect originalUpdateRect =
layer->projectionPlane()->needRectForOriginal(m_updateRect);
KisPaintDeviceSP originalDevice = layer->original();
originalDevice->clear(originalUpdateRect);
const QRect applyRect = originalUpdateRect & m_projection->extent();
// If the intersection of the updaterect and the projection extent is
// null, we are finish here.
if(applyRect.isNull()) return true;
KisFilterConfigurationSP filterConfig = layer->filter();
if (!filterConfig) {
/**
* When an adjustment layer is just created, it may have no
* filter inside. Then the layer has work as a pass-through
* node. Just copy the merged data to the layer's original.
*/
KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect);
return true;
}
KisSelectionSP selection = layer->fetchComposedInternalSelection(applyRect);
const QRect filterRect = selection ? applyRect & selection->selectedRect() : applyRect;
KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name());
if (!filter) return false;
KisPaintDeviceSP dstDevice = originalDevice;
if (selection) {
dstDevice = new KisPaintDevice(originalDevice->colorSpace());
}
if (!filterRect.isEmpty()) {
KIS_ASSERT_RECOVER_NOOP(layer->busyProgressIndicator());
layer->busyProgressIndicator()->update();
// We do not create a transaction here, as srcDevice != dstDevice
filter->process(m_projection, dstDevice, 0, filterRect, filterConfig.data(), 0);
}
if (selection) {
KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect);
KisPainter::copyAreaOptimized(filterRect.topLeft(), dstDevice, originalDevice, filterRect, selection);
}
return true;
}
bool visit(KisExternalLayer*) override {
return true;
}
bool visit(KisGeneratorLayer*) override {
return true;
}
bool visit(KisPaintLayer*) override {
return true;
}
bool visit(KisGroupLayer*) override {
return true;
}
bool visit(KisCloneLayer *layer) override {
QRect emptyRect;
KisRefreshSubtreeWalker walker(emptyRect);
KisAsyncMerger merger;
KisLayerSP srcLayer = layer->copyFrom();
QRect srcRect = m_updateRect.translated(-layer->x(), -layer->y());
QRegion prepareRegion(srcRect);
prepareRegion -= m_cropRect;
/**
* If a clone has complicated masks, we should prepare additional
* source area to ensure the rect is prepared.
*/
QRect needRectOnSource = layer->needRectOnSourceForMasks(srcRect);
if (!needRectOnSource.isEmpty()) {
prepareRegion += needRectOnSource;
}
+ if (srcLayer.isNull()) {
+ return true;
+ }
+
Q_FOREACH (const QRect &rect, prepareRegion.rects()) {
walker.collectRects(srcLayer, rect);
merger.startMerge(walker, false);
}
return true;
}
bool visit(KisNode*) override {
return true;
}
bool visit(KisFilterMask*) override {
return true;
}
bool visit(KisTransformMask*) override {
return true;
}
bool visit(KisTransparencyMask*) override {
return true;
}
bool visit(KisSelectionMask*) override {
return true;
}
bool visit(KisColorizeMask*) override {
return true;
}
private:
QRect m_updateRect;
QRect m_cropRect;
KisPaintDeviceSP m_projection;
};
/*********************************************************************/
/* KisAsyncMerger */
/*********************************************************************/
void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) {
KisMergeWalker::LeafStack &leafStack = walker.leafStack();
const bool useTempProjections = walker.needRectVaries();
while(!leafStack.isEmpty()) {
KisMergeWalker::JobItem item = leafStack.pop();
KisProjectionLeafSP currentLeaf = item.m_leaf;
/**
* In some unidentified cases teh nodes might be removed
* while the updates are still running. We have no proof
* of it yet, so just add a safety assert here.
*/
KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf);
KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf->node());
// All the masks should be filtered by the walkers
KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf->isLayer());
QRect applyRect = item.m_applyRect;
if (currentLeaf->isRoot()) {
currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode());
continue;
}
if(item.m_position & KisMergeWalker::N_EXTRA) {
// The type of layers that will not go to projection.
DEBUG_NODE_ACTION("Updating", "N_EXTRA", currentLeaf, applyRect);
KisUpdateOriginalVisitor originalVisitor(applyRect,
m_currentProjection,
walker.cropRect());
currentLeaf->accept(originalVisitor);
currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node());
continue;
}
if (!m_currentProjection) {
setupProjection(currentLeaf, applyRect, useTempProjections);
}
KisUpdateOriginalVisitor originalVisitor(applyRect,
m_currentProjection,
walker.cropRect());
if(item.m_position & KisMergeWalker::N_FILTHY) {
DEBUG_NODE_ACTION("Updating", "N_FILTHY", currentLeaf, applyRect);
if (currentLeaf->visible() || currentLeaf->hasClones()) {
currentLeaf->accept(originalVisitor);
currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode());
}
}
else if(item.m_position & KisMergeWalker::N_ABOVE_FILTHY) {
DEBUG_NODE_ACTION("Updating", "N_ABOVE_FILTHY", currentLeaf, applyRect);
if(currentLeaf->dependsOnLowerNodes()) {
if (currentLeaf->visible() || currentLeaf->hasClones()) {
currentLeaf->accept(originalVisitor);
currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node());
}
}
}
else if(item.m_position & KisMergeWalker::N_FILTHY_PROJECTION) {
DEBUG_NODE_ACTION("Updating", "N_FILTHY_PROJECTION", currentLeaf, applyRect);
if (currentLeaf->visible() || currentLeaf->hasClones()) {
currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode());
}
}
else /*if(item.m_position & KisMergeWalker::N_BELOW_FILTHY)*/ {
DEBUG_NODE_ACTION("Updating", "N_BELOW_FILTHY", currentLeaf, applyRect);
/* nothing to do */
}
compositeWithProjection(currentLeaf, applyRect);
if(item.m_position & KisMergeWalker::N_TOPMOST) {
writeProjection(currentLeaf, useTempProjections, applyRect);
resetProjection();
}
// FIXME: remove it from the inner loop and/or change to a warning!
Q_ASSERT(currentLeaf->projection()->defaultBounds()->currentLevelOfDetail() ==
walker.levelOfDetail());
}
if(notifyClones) {
doNotifyClones(walker);
}
if(m_currentProjection) {
warnImage << "BUG: The walker hasn't reached the root layer!";
warnImage << " Start node:" << walker.startNode() << "Requested rect:" << walker.requestedRect();
warnImage << " An inconsistency in the walkers occurred!";
warnImage << " Please report a bug describing how you got this message.";
// reset projection to avoid artifacts in next merges and allow people to work further
resetProjection();
}
}
void KisAsyncMerger::resetProjection() {
m_currentProjection = 0;
m_finalProjection = 0;
}
void KisAsyncMerger::setupProjection(KisProjectionLeafSP currentLeaf, const QRect& rect, bool useTempProjection) {
KisPaintDeviceSP parentOriginal = currentLeaf->parent()->original();
if (parentOriginal != currentLeaf->projection()) {
if (useTempProjection) {
if(!m_cachedPaintDevice)
m_cachedPaintDevice = new KisPaintDevice(parentOriginal->colorSpace());
m_currentProjection = m_cachedPaintDevice;
m_currentProjection->prepareClone(parentOriginal);
m_finalProjection = parentOriginal;
}
else {
parentOriginal->clear(rect);
m_finalProjection = m_currentProjection = parentOriginal;
}
}
else {
/**
* It happened so that our parent uses our own projection as
* its original. It means obligeChild mechanism works.
* We won't initialise m_currentProjection. This will cause
* writeProjection() and compositeWithProjection() do nothing
* when called.
*/
/* NOP */
}
}
void KisAsyncMerger::writeProjection(KisProjectionLeafSP topmostLeaf, bool useTempProjection, const QRect &rect) {
Q_UNUSED(useTempProjection);
Q_UNUSED(topmostLeaf);
if (!m_currentProjection) return;
if(m_currentProjection != m_finalProjection) {
KisPainter::copyAreaOptimized(rect.topLeft(), m_currentProjection, m_finalProjection, rect);
}
DEBUG_NODE_ACTION("Writing projection", "", topmostLeaf->parent(), rect);
}
bool KisAsyncMerger::compositeWithProjection(KisProjectionLeafSP leaf, const QRect &rect) {
if (!m_currentProjection) return true;
if (!leaf->visible()) return true;
KisPainter gc(m_currentProjection);
leaf->projectionPlane()->apply(&gc, rect);
DEBUG_NODE_ACTION("Compositing projection", "", leaf, rect);
return true;
}
void KisAsyncMerger::doNotifyClones(KisBaseRectsWalker &walker) {
KisBaseRectsWalker::CloneNotificationsVector &vector =
walker.cloneNotifications();
KisBaseRectsWalker::CloneNotificationsVector::iterator it;
for(it = vector.begin(); it != vector.end(); ++it) {
(*it).notify();
}
}
diff --git a/libs/image/kis_brush_mask_applicator_factories.cpp b/libs/image/kis_brush_mask_applicator_factories.cpp
index 860440aead..8fefa3fde1 100644
--- a/libs/image/kis_brush_mask_applicator_factories.cpp
+++ b/libs/image/kis_brush_mask_applicator_factories.cpp
@@ -1,648 +1,645 @@
/*
* 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_brush_mask_applicator_factories.h"
#include "vc_extra_math.h"
#include "kis_circle_mask_generator.h"
#include "kis_circle_mask_generator_p.h"
#include "kis_gauss_circle_mask_generator_p.h"
#include "kis_curve_circle_mask_generator_p.h"
#include "kis_gauss_rect_mask_generator_p.h"
#include "kis_curve_rect_mask_generator_p.h"
#include "kis_rect_mask_generator_p.h"
#include "kis_brush_mask_applicators.h"
#include "kis_brush_mask_applicator_base.h"
#define a(_s) #_s
#define b(_s) a(_s)
template<>
template<>
MaskApplicatorFactory<KisMaskGenerator, KisBrushMaskScalarApplicator>::ReturnType
MaskApplicatorFactory<KisMaskGenerator, KisBrushMaskScalarApplicator>::create<Vc::CurrentImplementation::current()>(ParamType maskGenerator)
{
return new KisBrushMaskScalarApplicator<KisMaskGenerator,Vc::CurrentImplementation::current()>(maskGenerator);
}
template<>
template<>
MaskApplicatorFactory<KisCircleMaskGenerator, KisBrushMaskVectorApplicator>::ReturnType
MaskApplicatorFactory<KisCircleMaskGenerator, KisBrushMaskVectorApplicator>::create<Vc::CurrentImplementation::current()>(ParamType maskGenerator)
{
return new KisBrushMaskVectorApplicator<KisCircleMaskGenerator,Vc::CurrentImplementation::current()>(maskGenerator);
}
template<>
template<>
MaskApplicatorFactory<KisGaussCircleMaskGenerator, KisBrushMaskVectorApplicator>::ReturnType
MaskApplicatorFactory<KisGaussCircleMaskGenerator, KisBrushMaskVectorApplicator>::create<Vc::CurrentImplementation::current()>(ParamType maskGenerator)
{
return new KisBrushMaskVectorApplicator<KisGaussCircleMaskGenerator,Vc::CurrentImplementation::current()>(maskGenerator);
}
template<>
template<>
MaskApplicatorFactory<KisCurveCircleMaskGenerator, KisBrushMaskVectorApplicator>::ReturnType
MaskApplicatorFactory<KisCurveCircleMaskGenerator, KisBrushMaskVectorApplicator>::create<Vc::CurrentImplementation::current()>(ParamType maskGenerator)
{
return new KisBrushMaskVectorApplicator<KisCurveCircleMaskGenerator,Vc::CurrentImplementation::current()>(maskGenerator);
}
template<>
template<>
MaskApplicatorFactory<KisRectangleMaskGenerator, KisBrushMaskVectorApplicator>::ReturnType
MaskApplicatorFactory<KisRectangleMaskGenerator, KisBrushMaskVectorApplicator>::create<Vc::CurrentImplementation::current()>(ParamType maskGenerator)
{
return new KisBrushMaskVectorApplicator<KisRectangleMaskGenerator,Vc::CurrentImplementation::current()>(maskGenerator);
}
template<>
template<>
MaskApplicatorFactory<KisGaussRectangleMaskGenerator, KisBrushMaskVectorApplicator>::ReturnType
MaskApplicatorFactory<KisGaussRectangleMaskGenerator, KisBrushMaskVectorApplicator>::create<Vc::CurrentImplementation::current()>(ParamType maskGenerator)
{
return new KisBrushMaskVectorApplicator<KisGaussRectangleMaskGenerator,Vc::CurrentImplementation::current()>(maskGenerator);
}
template<>
template<>
MaskApplicatorFactory<KisCurveRectangleMaskGenerator, KisBrushMaskVectorApplicator>::ReturnType
MaskApplicatorFactory<KisCurveRectangleMaskGenerator, KisBrushMaskVectorApplicator>::create<Vc::CurrentImplementation::current()>(ParamType maskGenerator)
{
return new KisBrushMaskVectorApplicator<KisCurveRectangleMaskGenerator,Vc::CurrentImplementation::current()>(maskGenerator);
}
#if defined HAVE_VC
struct KisCircleMaskGenerator::FastRowProcessor
{
FastRowProcessor(KisCircleMaskGenerator *maskGenerator)
: d(maskGenerator->d.data()) {}
template<Vc::Implementation _impl>
void process(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY);
KisCircleMaskGenerator::Private *d;
};
template<> void KisCircleMaskGenerator::
FastRowProcessor::process<Vc::CurrentImplementation::current()>(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY)
{
const bool useSmoothing = d->copyOfAntialiasEdges;
float y_ = y - centerY;
float sinay_ = sina * y_;
float cosay_ = cosa * y_;
float* bufferPointer = buffer;
Vc::float_v currentIndices = Vc::float_v::IndexesFromZero();
Vc::float_v increment((float)Vc::float_v::size());
Vc::float_v vCenterX(centerX);
Vc::float_v vCosa(cosa);
Vc::float_v vSina(sina);
Vc::float_v vCosaY_(cosay_);
Vc::float_v vSinaY_(sinay_);
Vc::float_v vXCoeff(d->xcoef);
Vc::float_v vYCoeff(d->ycoef);
Vc::float_v vTransformedFadeX(d->transformedFadeX);
Vc::float_v vTransformedFadeY(d->transformedFadeY);
Vc::float_v vOne(Vc::One);
for (int i=0; i < width; i+= Vc::float_v::size()){
Vc::float_v x_ = currentIndices - vCenterX;
Vc::float_v xr = x_ * vCosa - vSinaY_;
Vc::float_v yr = x_ * vSina + vCosaY_;
Vc::float_v n = pow2(xr * vXCoeff) + pow2(yr * vYCoeff);
Vc::float_m outsideMask = n > vOne;
if (!outsideMask.isFull()) {
if (useSmoothing) {
xr = Vc::abs(xr) + vOne;
yr = Vc::abs(yr) + vOne;
}
Vc::float_v vNormFade = pow2(xr * vTransformedFadeX) + pow2(yr * vTransformedFadeY);
Vc::float_m vNormLowMask = vNormFade < vOne;
vNormFade.setZero(vNormLowMask);
//255 * n * (normeFade - 1) / (normeFade - n)
Vc::float_v vFade = n * (vNormFade - vOne) / (vNormFade - n);
// Mask in the inner circle of the mask
Vc::float_m mask = vNormFade < vOne;
vFade.setZero(mask);
// Mask out the outer circle of the mask
vFade(outsideMask) = vOne;
vFade.store(bufferPointer, Vc::Aligned);
} else {
// Mask out everything outside the circle
vOne.store(bufferPointer, Vc::Aligned);
}
currentIndices = currentIndices + increment;
bufferPointer += Vc::float_v::size();
}
}
struct KisGaussCircleMaskGenerator::FastRowProcessor
{
FastRowProcessor(KisGaussCircleMaskGenerator *maskGenerator)
: d(maskGenerator->d.data()) {}
template<Vc::Implementation _impl>
void process(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY);
KisGaussCircleMaskGenerator::Private *d;
};
template<> void KisGaussCircleMaskGenerator::
FastRowProcessor::process<Vc::CurrentImplementation::current()>(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY)
{
float y_ = y - centerY;
float sinay_ = sina * y_;
float cosay_ = cosa * y_;
float* bufferPointer = buffer;
Vc::float_v currentIndices = Vc::float_v::IndexesFromZero();
Vc::float_v increment((float)Vc::float_v::size());
Vc::float_v vCenterX(centerX);
Vc::float_v vCenter(d->center);
Vc::float_v vCosa(cosa);
Vc::float_v vSina(sina);
Vc::float_v vCosaY_(cosay_);
Vc::float_v vSinaY_(sinay_);
Vc::float_v vYCoeff(d->ycoef);
Vc::float_v vDistfactor(d->distfactor);
Vc::float_v vAlphafactor(d->alphafactor);
Vc::float_v vZero(Vc::Zero);
Vc::float_v vValMax(255.f);
for (int i=0; i < width; i+= Vc::float_v::size()){
Vc::float_v x_ = currentIndices - vCenterX;
Vc::float_v xr = x_ * vCosa - vSinaY_;
Vc::float_v yr = x_ * vSina + vCosaY_;
Vc::float_v dist = sqrt(pow2(xr) + pow2(yr * vYCoeff));
// Apply FadeMaker mask and operations
Vc::float_m excludeMask = d->fadeMaker.needFade(dist);
if (!excludeMask.isFull()) {
Vc::float_v valDist = dist * vDistfactor;
Vc::float_v fullFade = vAlphafactor * ( VcExtraMath::erf(valDist + vCenter) - VcExtraMath::erf(valDist - vCenter));
Vc::float_m mask;
// Mask in the inner circle of the mask
mask = fullFade < vZero;
fullFade.setZero(mask);
// Mask the outer circle
mask = fullFade > 254.974f;
fullFade(mask) = vValMax;
// Mask (value - value), precision errors.
Vc::float_v vFade = (vValMax - fullFade) / vValMax;
// return original dist values before vFade transform
vFade(excludeMask) = dist;
vFade.store(bufferPointer, Vc::Aligned);
} else {
dist.store(bufferPointer, Vc::Aligned);
}
currentIndices = currentIndices + increment;
bufferPointer += Vc::float_v::size();
}
}
struct KisCurveCircleMaskGenerator::FastRowProcessor
{
FastRowProcessor(KisCurveCircleMaskGenerator *maskGenerator)
: d(maskGenerator->d.data()) {}
template<Vc::Implementation _impl>
void process(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY);
KisCurveCircleMaskGenerator::Private *d;
};
template<> void KisCurveCircleMaskGenerator::
FastRowProcessor::process<Vc::CurrentImplementation::current()>(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY)
{
float y_ = y - centerY;
float sinay_ = sina * y_;
float cosay_ = cosa * y_;
float* bufferPointer = buffer;
qreal* curveDataPointer = d->curveData.data();
Vc::float_v currentIndices = Vc::float_v::IndexesFromZero();
Vc::float_v increment((float)Vc::float_v::size());
Vc::float_v vCenterX(centerX);
Vc::float_v vCosa(cosa);
Vc::float_v vSina(sina);
Vc::float_v vCosaY_(cosay_);
Vc::float_v vSinaY_(sinay_);
Vc::float_v vYCoeff(d->ycoef);
Vc::float_v vXCoeff(d->xcoef);
Vc::float_v vCurveResolution(d->curveResolution);
Vc::float_v vCurvedData(Vc::Zero);
Vc::float_v vCurvedData1(Vc::Zero);
Vc::float_v vOne(Vc::One);
Vc::float_v vZero(Vc::Zero);
for (int i=0; i < width; i+= Vc::float_v::size()){
Vc::float_v x_ = currentIndices - vCenterX;
Vc::float_v xr = x_ * vCosa - vSinaY_;
Vc::float_v yr = x_ * vSina + vCosaY_;
Vc::float_v dist = pow2(xr * vXCoeff) + pow2(yr * vYCoeff);
// Apply FadeMaker mask and operations
Vc::float_m excludeMask = d->fadeMaker.needFade(dist);
if (!excludeMask.isFull()) {
Vc::float_v valDist = dist * vCurveResolution;
// truncate
Vc::float_v::IndexType vAlphaValue(valDist);
Vc::float_v vFloatAlphaValue = vAlphaValue;
Vc::float_v alphaValueF = valDist - vFloatAlphaValue;
vCurvedData.gather(curveDataPointer,vAlphaValue);
vCurvedData1.gather(curveDataPointer,vAlphaValue + 1);
// Vc::float_v vCurvedData1(curveDataPointer,vAlphaValue + 1);
// vAlpha
Vc::float_v fullFade = (
(vOne - alphaValueF) * vCurvedData +
alphaValueF * vCurvedData1);
Vc::float_m mask;
// Mask in the inner circle of the mask
mask = fullFade < vZero;
fullFade.setZero(mask);
// Mask outer circle of mask
mask = fullFade >= vOne;
Vc::float_v vFade = (vOne - fullFade);
vFade.setZero(mask);
// return original dist values before vFade transform
vFade(excludeMask) = dist;
vFade.store(bufferPointer, Vc::Aligned);
} else {
dist.store(bufferPointer, Vc::Aligned);
}
currentIndices = currentIndices + increment;
bufferPointer += Vc::float_v::size();
}
}
struct KisGaussRectangleMaskGenerator::FastRowProcessor
{
FastRowProcessor(KisGaussRectangleMaskGenerator *maskGenerator)
: d(maskGenerator->d.data()) {}
template<Vc::Implementation _impl>
void process(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY);
KisGaussRectangleMaskGenerator::Private *d;
};
struct KisRectangleMaskGenerator::FastRowProcessor
{
FastRowProcessor(KisRectangleMaskGenerator *maskGenerator)
: d(maskGenerator->d.data()) {}
template<Vc::Implementation _impl>
void process(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY);
KisRectangleMaskGenerator::Private *d;
};
template<> void KisRectangleMaskGenerator::
FastRowProcessor::process<Vc::CurrentImplementation::current()>(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY)
{
const bool useSmoothing = d->copyOfAntialiasEdges;
float y_ = y - centerY;
float sinay_ = sina * y_;
float cosay_ = cosa * y_;
float* bufferPointer = buffer;
Vc::float_v currentIndices = Vc::float_v::IndexesFromZero();
Vc::float_v increment((float)Vc::float_v::size());
Vc::float_v vCenterX(centerX);
Vc::float_v vCosa(cosa);
Vc::float_v vSina(sina);
Vc::float_v vCosaY_(cosay_);
Vc::float_v vSinaY_(sinay_);
Vc::float_v vXCoeff(d->xcoeff);
Vc::float_v vYCoeff(d->ycoeff);
Vc::float_v vTransformedFadeX(d->transformedFadeX);
Vc::float_v vTransformedFadeY(d->transformedFadeY);
Vc::float_v vOne(Vc::One);
Vc::float_v vZero(Vc::Zero);
Vc::float_v vTolerance(10000.f);
for (int i=0; i < width; i+= Vc::float_v::size()){
Vc::float_v x_ = currentIndices - vCenterX;
Vc::float_v xr = Vc::abs(x_ * vCosa - vSinaY_);
Vc::float_v yr = Vc::abs(x_ * vSina + vCosaY_);
Vc::float_v nxr = xr * vXCoeff;
Vc::float_v nyr = yr * vYCoeff;
Vc::float_m outsideMask = (nxr > vOne) || (nyr > vOne);
if (!outsideMask.isFull()) {
if (useSmoothing) {
xr = Vc::abs(xr) + vOne;
yr = Vc::abs(yr) + vOne;
}
Vc::float_v fxr = xr * vTransformedFadeX;
Vc::float_v fyr = yr * vTransformedFadeY;
Vc::float_v fxrNorm = nxr * (fxr - vOne) / (fxr - nxr);
Vc::float_v fyrNorm = nyr * (fyr - vOne) / (fyr - nyr);
Vc::float_v vFade(vZero);
- Vc::float_v::IndexType fxrInt(fxr * vTolerance);
- Vc::float_v::IndexType fyrInt(fyr * vTolerance);
-
- Vc::float_m fadeXMask = (fxr > vOne) && ((fxrInt >= fyrInt) || fyr < vOne);
- Vc::float_m fadeYMask = (fyr > vOne) && ((fyrInt > fxrInt) || fxr < vOne);
-
- vFade(fadeXMask) = fxrNorm;
- vFade(!fadeXMask && fadeYMask) = fyrNorm;
+ Vc::float_m vFadeMask = fxrNorm < fyrNorm;
+ Vc::float_v vMaxVal = vFade;
+ vMaxVal(fxr > vOne) = fxrNorm;
+ vMaxVal(vFadeMask && fyr > vOne) = fyrNorm;
+ vFade = vMaxVal;
// Mask out the outer circle of the mask
vFade(outsideMask) = vOne;
vFade.store(bufferPointer, Vc::Aligned);
} else {
// Mask out everything outside the circle
vOne.store(bufferPointer, Vc::Aligned);
}
currentIndices = currentIndices + increment;
bufferPointer += Vc::float_v::size();
}
}
template<> void KisGaussRectangleMaskGenerator::
FastRowProcessor::process<Vc::CurrentImplementation::current()>(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY)
{
float y_ = y - centerY;
float sinay_ = sina * y_;
float cosay_ = cosa * y_;
float* bufferPointer = buffer;
Vc::float_v currentIndices = Vc::float_v::IndexesFromZero();
Vc::float_v increment((float)Vc::float_v::size());
Vc::float_v vCenterX(centerX);
Vc::float_v vCosa(cosa);
Vc::float_v vSina(sina);
Vc::float_v vCosaY_(cosay_);
Vc::float_v vSinaY_(sinay_);
Vc::float_v vhalfWidth(d->halfWidth);
Vc::float_v vhalfHeight(d->halfHeight);
Vc::float_v vXFade(d->xfade);
Vc::float_v vYFade(d->yfade);
Vc::float_v vAlphafactor(d->alphafactor);
Vc::float_v vOne(Vc::One);
Vc::float_v vZero(Vc::Zero);
Vc::float_v vValMax(255.f);
for (int i=0; i < width; i+= Vc::float_v::size()){
Vc::float_v x_ = currentIndices - vCenterX;
Vc::float_v xr = x_ * vCosa - vSinaY_;
Vc::float_v yr = Vc::abs(x_ * vSina + vCosaY_);
Vc::float_v vValue;
// check if we need to apply fader on values
Vc::float_m excludeMask = d->fadeMaker.needFade(xr,yr);
vValue(excludeMask) = vOne;
if (!excludeMask.isFull()) {
Vc::float_v fullFade = vValMax - (vAlphafactor * (VcExtraMath::erf((vhalfWidth + xr) * vXFade) + VcExtraMath::erf((vhalfWidth - xr) * vXFade))
* (VcExtraMath::erf((vhalfHeight + yr) * vYFade) + VcExtraMath::erf((vhalfHeight - yr) * vYFade)));
// apply antialias fader
d->fadeMaker.apply2DFader(fullFade,excludeMask,xr,yr);
Vc::float_m mask;
// Mask in the inner circle of the mask
mask = fullFade < vZero;
fullFade.setZero(mask);
// Mask the outer circle
mask = fullFade > 254.974f;
fullFade(mask) = vValMax;
// Mask (value - value), precision errors.
Vc::float_v vFade = fullFade / vValMax;
// return original vValue values before vFade transform
vFade(excludeMask) = vValue;
vFade.store(bufferPointer, Vc::Aligned);
} else {
vValue.store(bufferPointer, Vc::Aligned);
}
currentIndices = currentIndices + increment;
bufferPointer += Vc::float_v::size();
}
}
struct KisCurveRectangleMaskGenerator::FastRowProcessor
{
FastRowProcessor(KisCurveRectangleMaskGenerator *maskGenerator)
: d(maskGenerator->d.data()) {}
template<Vc::Implementation _impl>
void process(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY);
KisCurveRectangleMaskGenerator::Private *d;
};
template<> void KisCurveRectangleMaskGenerator::
FastRowProcessor::process<Vc::CurrentImplementation::current()>(float* buffer, int width, float y, float cosa, float sina,
float centerX, float centerY)
{
float y_ = y - centerY;
float sinay_ = sina * y_;
float cosay_ = cosa * y_;
float* bufferPointer = buffer;
qreal* curveDataPointer = d->curveData.data();
Vc::float_v currentIndices = Vc::float_v::IndexesFromZero();
Vc::float_v increment((float)Vc::float_v::size());
Vc::float_v vCenterX(centerX);
Vc::float_v vCosa(cosa);
Vc::float_v vSina(sina);
Vc::float_v vCosaY_(cosay_);
Vc::float_v vSinaY_(sinay_);
Vc::float_v vYCoeff(d->ycoeff);
Vc::float_v vXCoeff(d->xcoeff);
Vc::float_v vCurveResolution(d->curveResolution);
Vc::float_v vOne(Vc::One);
Vc::float_v vZero(Vc::Zero);
Vc::float_v vValMax(255.f);
for (int i=0; i < width; i+= Vc::float_v::size()){
Vc::float_v x_ = currentIndices - vCenterX;
Vc::float_v xr = x_ * vCosa - vSinaY_;
Vc::float_v yr = Vc::abs(x_ * vSina + vCosaY_);
Vc::float_v vValue;
// check if we need to apply fader on values
Vc::float_m excludeMask = d->fadeMaker.needFade(xr,yr);
vValue(excludeMask) = vOne;
if (!excludeMask.isFull()) {
// We need to mask the extra area given for aliniation
// the next operation should never give values above 1
Vc::float_v preSIndex = Vc::abs(xr) * vXCoeff;
Vc::float_v preTIndex = Vc::abs(yr) * vYCoeff;
preSIndex(preSIndex > vOne) = vOne;
preTIndex(preTIndex > vOne) = vOne;
Vc::float_v::IndexType sIndex( round(preSIndex * vCurveResolution));
Vc::float_v::IndexType tIndex( round(preTIndex * vCurveResolution));
Vc::float_v::IndexType sIndexInverted = vCurveResolution - sIndex;
Vc::float_v::IndexType tIndexInverted = vCurveResolution - tIndex;
Vc::float_v vCurvedDataSIndex(curveDataPointer, sIndex);
Vc::float_v vCurvedDataTIndex(curveDataPointer, tIndex);
Vc::float_v vCurvedDataSIndexInv(curveDataPointer, sIndexInverted);
Vc::float_v vCurvedDataTIndexInv(curveDataPointer, tIndexInverted);
Vc::float_v fullFade = vValMax * (vOne - (vCurvedDataSIndex * (vOne - vCurvedDataSIndexInv) *
vCurvedDataTIndex * (vOne - vCurvedDataTIndexInv)));
// apply antialias fader
d->fadeMaker.apply2DFader(fullFade,excludeMask,xr,yr);
Vc::float_m mask;
// Mask in the inner circle of the mask
mask = fullFade < vZero;
fullFade.setZero(mask);
// Mask the outer circle
mask = fullFade > 254.974f;
fullFade(mask) = vValMax;
// Mask (value - value), precision errors.
Vc::float_v vFade = fullFade / vValMax;
// return original vValue values before vFade transform
vFade(excludeMask) = vValue;
vFade.store(bufferPointer, Vc::Aligned);
} else {
vValue.store(bufferPointer, Vc::Aligned);
}
currentIndices = currentIndices + increment;
bufferPointer += Vc::float_v::size();
}
}
#endif /* defined HAVE_VC */
diff --git a/libs/image/kis_convolution_worker_spatial.h b/libs/image/kis_convolution_worker_spatial.h
index cf3a101ee5..c5516deb72 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_khalfWidth = (m_kw > 0) ? (m_kw - 1) / 2 : m_kw;
+ m_khalfHeight = (m_kh > 0) ? (m_kh - 1) / 2 : m_kh;
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().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_edge_detection_kernel.cpp b/libs/image/kis_edge_detection_kernel.cpp
index 839c88d07d..2596140852 100644
--- a/libs/image/kis_edge_detection_kernel.cpp
+++ b/libs/image/kis_edge_detection_kernel.cpp
@@ -1,428 +1,428 @@
/*
* 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_kernel.h"
#include "kis_global.h"
#include "kis_convolution_kernel.h"
#include <kis_convolution_painter.h>
#include <KoCompositeOpRegistry.h>
#include <QRect>
#include <KoColorSpace.h>
#include <kis_iterator_ng.h>
#include <QVector3D>
KisEdgeDetectionKernel::KisEdgeDetectionKernel()
{
}
/*
* This code is very similar to the gaussian kernel code, except unlike the gaussian code,
* edge-detection kernels DO use the diagonals.
- * Except for the simple mode. We implement the simple mode because it is an analogue to
+ * Except for the simple mode. We implement the simple mode because it is an analog to
* the old sobel filter.
*/
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> KisEdgeDetectionKernel::createHorizontalMatrix(qreal radius,
FilterType type,
bool reverse)
{
int kernelSize = kernelSizeFromRadius(radius);
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize);
KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
const int center = kernelSize / 2;
if (type==Prewit) {
for (int x = 0; x < kernelSize; x++) {
for (int y=0; y<kernelSize; y++) {
qreal xDistance;
if (reverse) {
xDistance = x - center;
} else {
xDistance = center - x;
}
matrix(x, y) = xDistance;
}
}
} else if(type==Simple) {
matrix.resize(kernelSize, 1);
for (int x = 0; x < kernelSize; x++) {
qreal xDistance;
if (reverse) {
xDistance = x - center;
} else {
xDistance = center - x;
}
if (x==center) {
matrix(x, 0) = 0;
} else {
matrix(x, 0) = (1/xDistance);
}
}
} else {
for (int x = 0; x < kernelSize; x++) {
for (int y=0; y<kernelSize; y++) {
if (x==center && y==center) {
matrix(x, y) = 0;
} else {
qreal xD, yD;
if (reverse) {
xD = x - center;
yD = y - center;
} else {
xD = center - x;
yD = center - y;
}
matrix(x, y) = xD / (xD*xD + yD*yD);
}
}
}
}
return matrix;
}
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> KisEdgeDetectionKernel::createVerticalMatrix(qreal radius,
FilterType type,
bool reverse)
{
int kernelSize = kernelSizeFromRadius(radius);
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize);
KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
const int center = kernelSize / 2;
if (type==Prewit) {
for (int y = 0; y < kernelSize; y++) {
for (int x=0; x<kernelSize; x++) {
qreal yDistance;
if (reverse) {
yDistance = y - center;
} else {
yDistance = center - y;
}
matrix(x, y) = yDistance;
}
}
} else if(type==Simple) {
matrix.resize(1, kernelSize);
for (int y = 0; y < kernelSize; y++) {
qreal yDistance;
if (reverse) {
yDistance = y - center;
} else {
yDistance = center - y;
}
if (y==center) {
matrix(0, y) = 0;
} else {
matrix(0, y) = (1/yDistance);
}
}
} else {
for (int y = 0; y < kernelSize; y++) {
for (int x=0; x<kernelSize; x++) {
if (x==center && y==center) {
matrix(x, y) = 0;
} else {
qreal xD, yD;
if (reverse) {
xD = x - center;
yD = y - center;
} else {
xD = center - x;
yD = center - y;
}
matrix(x, y) = yD / (xD*xD + yD*yD);
}
}
}
}
return matrix;
}
KisConvolutionKernelSP KisEdgeDetectionKernel::createHorizontalKernel(qreal radius,
KisEdgeDetectionKernel::FilterType type,
bool denormalize,
bool reverse)
{
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createHorizontalMatrix(radius, type, reverse);
if (denormalize) {
return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1);
} else {
return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum());
}
}
KisConvolutionKernelSP KisEdgeDetectionKernel::createVerticalKernel(qreal radius,
KisEdgeDetectionKernel::FilterType type,
bool denormalize,
bool reverse)
{
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createVerticalMatrix(radius, type, reverse);
if (denormalize) {
return KisConvolutionKernel::fromMatrix(matrix, 0.5, 1);
} else {
return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum());
}
}
int KisEdgeDetectionKernel::kernelSizeFromRadius(qreal radius)
{
return qMax((int)(2 * ceil(sigmaFromRadius(radius)) + 1), 3);
}
qreal KisEdgeDetectionKernel::sigmaFromRadius(qreal radius)
{
return 0.3 * radius + 0.3;
}
void KisEdgeDetectionKernel::applyEdgeDetection(KisPaintDeviceSP device,
const QRect &rect,
qreal xRadius,
qreal yRadius,
KisEdgeDetectionKernel::FilterType type,
const QBitArray &channelFlags,
KoUpdater *progressUpdater,
FilterOutput output,
bool writeToAlpha)
{
QPoint srcTopLeft = rect.topLeft();
KisPainter finalPainter(device);
finalPainter.setChannelFlags(channelFlags);
finalPainter.setProgress(progressUpdater);
if (output == pythagorean || output == radian) {
KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace());
KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace());
x_denormalised->prepareClone(device);
y_denormalised->prepareClone(device);
KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type);
KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type);
qreal horizontalCenter = qreal(kernelHorizLeftRight->width()) / 2.0;
qreal verticalCenter = qreal(kernelVerticalTopBottom->height()) / 2.0;
KisConvolutionPainter horizPainterLR(x_denormalised);
horizPainterLR.setChannelFlags(channelFlags);
horizPainterLR.setProgress(progressUpdater);
horizPainterLR.applyMatrix(kernelHorizLeftRight, device,
srcTopLeft - QPoint(0, ceil(horizontalCenter)),
srcTopLeft - QPoint(0, ceil(horizontalCenter)),
rect.size() + QSize(0, 2 * ceil(horizontalCenter)), BORDER_REPEAT);
KisConvolutionPainter verticalPainterTB(y_denormalised);
verticalPainterTB.setChannelFlags(channelFlags);
verticalPainterTB.setProgress(progressUpdater);
verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device,
srcTopLeft - QPoint(0, ceil(verticalCenter)),
srcTopLeft - QPoint(0, ceil(verticalCenter)),
rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT);
KisSequentialIterator yItterator(y_denormalised, rect);
KisSequentialIterator xItterator(x_denormalised, rect);
KisSequentialIterator finalIt(device, rect);
const int pixelSize = device->colorSpace()->pixelSize();
const int channels = device->colorSpace()->channelCount();
const int alphaPos = device->colorSpace()->alphaPos();
KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0);
QVector<float> yNormalised(channels);
QVector<float> xNormalised(channels);
QVector<float> finalNorm(channels);
while(yItterator.nextPixel() && xItterator.nextPixel() && finalIt.nextPixel()) {
device->colorSpace()->normalisedChannelsValue(yItterator.rawData(), yNormalised);
device->colorSpace()->normalisedChannelsValue(xItterator.rawData(), xNormalised);
device->colorSpace()->normalisedChannelsValue(finalIt.rawData(), finalNorm);
if (output == pythagorean) {
for (int c = 0; c<channels; c++) {
finalNorm[c] = 2 * sqrt( ((xNormalised[c]-0.5)*(xNormalised[c]-0.5)) + ((yNormalised[c]-0.5)*(yNormalised[c]-0.5)));
}
} else { //radian
for (int c = 0; c<channels; c++) {
finalNorm[c] = atan2(xNormalised[c]-0.5, yNormalised[c]-0.5);
}
}
if (writeToAlpha) {
KoColor col(finalIt.rawData(), device->colorSpace());
qreal alpha = 0;
for (int c = 0; c<(channels-1); c++) {
alpha = alpha+finalNorm[c];
}
alpha = qMin(alpha/(channels-1), col.opacityF());
col.setOpacity(alpha);
memcpy(finalIt.rawData(), col.data(), pixelSize);
} else {
quint8* f = finalIt.rawData();
finalNorm[alphaPos] = 1.0;
device->colorSpace()->fromNormalisedChannelsValue(f, finalNorm);
memcpy(finalIt.rawData(), f, pixelSize);
}
}
} else {
KisConvolutionKernelSP kernel;
qreal center = 0;
bool denormalize = !writeToAlpha;
if (output == xGrowth) {
kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize);
center = qreal(kernel->width()) / 2.0;
} else if (output == xFall) {
kernel = KisEdgeDetectionKernel::createHorizontalKernel(xRadius, type, denormalize, true);
center = qreal(kernel->width()) / 2.0;
} else if (output == yGrowth) {
kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize);
center = qreal(kernel->height()) / 2.0;
} else { //yFall
kernel = KisEdgeDetectionKernel::createVerticalKernel(yRadius, type, denormalize, true);
center = qreal(kernel->height()) / 2.0;
}
if (writeToAlpha) {
KisPaintDeviceSP denormalised = new KisPaintDevice(device->colorSpace());
denormalised->prepareClone(device);
KisConvolutionPainter kernelP(denormalised);
kernelP.setChannelFlags(channelFlags);
kernelP.setProgress(progressUpdater);
kernelP.applyMatrix(kernel, device,
srcTopLeft - QPoint(0, ceil(center)),
srcTopLeft - QPoint(0, ceil(center)),
rect.size() + QSize(0, 2 * ceil(center)), BORDER_REPEAT);
KisSequentialIterator iterator(denormalised, rect);
KisSequentialIterator finalIt(device, rect);
const int pixelSize = device->colorSpace()->pixelSize();
const int channels = device->colorSpace()->colorChannelCount();
QVector<float> normalised(channels);
while (iterator.nextPixel() && finalIt.nextPixel()) {
device->colorSpace()->normalisedChannelsValue(iterator.rawData(), normalised);
KoColor col(finalIt.rawData(), device->colorSpace());
qreal alpha = 0;
for (int c = 0; c<channels; c++) {
alpha = alpha+normalised[c];
}
alpha = qMin(alpha/channels, col.opacityF());
col.setOpacity(alpha);
memcpy(finalIt.rawData(), col.data(), pixelSize);
}
} else {
KisConvolutionPainter kernelP(device);
kernelP.setChannelFlags(channelFlags);
kernelP.setProgress(progressUpdater);
kernelP.applyMatrix(kernel, device,
srcTopLeft - QPoint(0, ceil(center)),
srcTopLeft - QPoint(0, ceil(center)),
rect.size() + QSize(0, 2 * ceil(center)), BORDER_REPEAT);
KisSequentialIterator finalIt(device, rect);
int numConseqPixels = finalIt.nConseqPixels();
while (finalIt.nextPixels(numConseqPixels)) {
numConseqPixels = finalIt.nConseqPixels();
device->colorSpace()->setOpacity(finalIt.rawData(), 1.0, numConseqPixels);
}
}
}
}
void KisEdgeDetectionKernel::convertToNormalMap(KisPaintDeviceSP device,
const QRect &rect,
qreal xRadius,
qreal yRadius,
KisEdgeDetectionKernel::FilterType type,
int channelToConvert,
QVector<int> channelOrder,
QVector<bool> channelFlip,
const QBitArray &channelFlags,
KoUpdater *progressUpdater)
{
QPoint srcTopLeft = rect.topLeft();
KisPainter finalPainter(device);
finalPainter.setChannelFlags(channelFlags);
finalPainter.setProgress(progressUpdater);
KisPaintDeviceSP x_denormalised = new KisPaintDevice(device->colorSpace());
KisPaintDeviceSP y_denormalised = new KisPaintDevice(device->colorSpace());
x_denormalised->prepareClone(device);
y_denormalised->prepareClone(device);
KisConvolutionKernelSP kernelHorizLeftRight = KisEdgeDetectionKernel::createHorizontalKernel(yRadius, type, true, !channelFlip[1]);
KisConvolutionKernelSP kernelVerticalTopBottom = KisEdgeDetectionKernel::createVerticalKernel(xRadius, type, true, !channelFlip[0]);
qreal horizontalCenter = qreal(kernelHorizLeftRight->width()) / 2.0;
qreal verticalCenter = qreal(kernelVerticalTopBottom->height()) / 2.0;
KisConvolutionPainter horizPainterLR(y_denormalised);
horizPainterLR.setChannelFlags(channelFlags);
horizPainterLR.setProgress(progressUpdater);
horizPainterLR.applyMatrix(kernelHorizLeftRight, device,
srcTopLeft - QPoint(ceil(horizontalCenter), 0),
srcTopLeft - QPoint(ceil(horizontalCenter), 0),
rect.size() + QSize(2 * ceil(horizontalCenter), 0), BORDER_REPEAT);
KisConvolutionPainter verticalPainterTB(x_denormalised);
verticalPainterTB.setChannelFlags(channelFlags);
verticalPainterTB.setProgress(progressUpdater);
verticalPainterTB.applyMatrix(kernelVerticalTopBottom, device,
srcTopLeft - QPoint(0, ceil(verticalCenter)),
srcTopLeft - QPoint(0, ceil(verticalCenter)),
rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT);
KisSequentialIterator yItterator(y_denormalised, rect);
KisSequentialIterator xItterator(x_denormalised, rect);
KisSequentialIterator finalIt(device, rect);
const int pixelSize = device->colorSpace()->pixelSize();
const int channels = device->colorSpace()->channelCount();
const int alphaPos = device->colorSpace()->alphaPos();
KIS_SAFE_ASSERT_RECOVER_RETURN(alphaPos >= 0);
QVector<float> yNormalised(channels);
QVector<float> xNormalised(channels);
QVector<float> finalNorm(channels);
while(yItterator.nextPixel() && xItterator.nextPixel() && finalIt.nextPixel()) {
device->colorSpace()->normalisedChannelsValue(yItterator.rawData(), yNormalised);
device->colorSpace()->normalisedChannelsValue(xItterator.rawData(), xNormalised);
qreal z = 1.0;
if (channelFlip[2]==true){
z=-1.0;
}
QVector3D normal = QVector3D((xNormalised[channelToConvert]-0.5)*2, (yNormalised[channelToConvert]-0.5)*2, z);
normal.normalize();
finalNorm.fill(1.0);
for (int c = 0; c<3; c++) {
finalNorm[device->colorSpace()->channels().at(channelOrder[c])->displayPosition()] = (normal[channelOrder[c]]/2)+0.5;
}
finalNorm[alphaPos]= 1.0;
quint8* pixel = finalIt.rawData();
device->colorSpace()->fromNormalisedChannelsValue(pixel, finalNorm);
memcpy(finalIt.rawData(), pixel, pixelSize);
}
}
diff --git a/libs/image/kis_fast_math.cpp b/libs/image/kis_fast_math.cpp
index 4b6fe8df74..980f14cbce 100644
--- a/libs/image/kis_fast_math.cpp
+++ b/libs/image/kis_fast_math.cpp
@@ -1,132 +1,132 @@
/*
* 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.
*
- * adopted from here http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx
+ * adopted from here http://web.archive.org/web/20090728150504/http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx
*/
#include "kis_fast_math.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <QtGlobal>
#include <QGlobalStatic>
-// Algorithm from http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx
+// Algorithm from http://web.archive.org/web/20090728150504/http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx
const qreal MAX_SECOND_DERIV_IN_RANGE = 0.6495;
/// precision
const qreal MAX_ERROR = 0.0001;
struct KisATanTable {
KisATanTable() {
qreal nf = ::sqrt(MAX_SECOND_DERIV_IN_RANGE / (8 * MAX_ERROR));
NUM_ATAN_ENTRIES = int(nf) + 1;
// Build table
qreal y = 10.0;
qreal x;
ATanTable = new qreal[NUM_ATAN_ENTRIES + 1];
ATanTable[0] = 0.0;
for (quint32 i = 1; i <= NUM_ATAN_ENTRIES; i++) {
x = (y / i) * NUM_ATAN_ENTRIES;
ATanTable[i] = (qreal)::atan2(y, x);
}
}
~KisATanTable() {
delete [] ATanTable;
}
quint32 NUM_ATAN_ENTRIES;
qreal* ATanTable;
};
Q_GLOBAL_STATIC(KisATanTable, kisATanTable)
/// private functions
inline qreal interp(qreal r, qreal a, qreal b)
{
return r*(b - a) + a;
}
inline qreal calcAngle(qreal x, qreal y)
{
static qreal af = kisATanTable->NUM_ATAN_ENTRIES;
static int ai = kisATanTable->NUM_ATAN_ENTRIES;
static qreal* ATanTable = kisATanTable->ATanTable;
qreal di = (y / x) * af;
int i = (int)(di);
if (i >= ai) return ::atan2(y, x);
return interp(di - i, ATanTable[i], ATanTable[i+1]);
}
qreal KisFastMath::atan2(qreal y, qreal x)
{
if (y == 0.0) { // the line is horizontal
if (x >= 0.0) { // towards the right
return(0.0);// the angle is 0
}
// toward the left
return qreal(M_PI);
} // we now know that y is not 0 check x
if (x == 0.0) { // the line is vertical
if (y > 0.0) {
return M_PI_2;
}
return -M_PI_2;
}
// from here on we know that niether x nor y is 0
if (x > 0.0) {
// we are in quadrant 1 or 4
if (y > 0.0) {
// we are in quadrant 1
// now figure out which side of the 45 degree line
if (x > y) {
return(calcAngle(x, y));
}
return(M_PI_2 - calcAngle(y, x));
}
// we are in quadrant 4
y = -y;
// now figure out which side of the 45 degree line
if (x > y) {
return(-calcAngle(x, y));
}
return(-M_PI_2 + calcAngle(y, x));
}
// we are in quadrant 2 or 3
x = -x;
// flip x so we can use it as a positive
if (y > 0.0) {
// we are in quadrant 2
// now figure out which side of the 45 degree line
if (x > y) {
return(M_PI - calcAngle(x, y));
} return(M_PI_2 + calcAngle(y, x));
}
// we are in quadrant 3
y = -y;
// flip y so we can use it as a positive
// now figure out which side of the 45 degree line
if (x > y) {
return(-M_PI + calcAngle(x, y));
} return(-M_PI_2 - calcAngle(y, x));
}
diff --git a/libs/image/kis_fast_math.h b/libs/image/kis_fast_math.h
index a5f88008d3..fbf626a381 100644
--- a/libs/image/kis_fast_math.h
+++ b/libs/image/kis_fast_math.h
@@ -1,38 +1,38 @@
/*
* 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.
*
- * adopted from here http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx
+ * adopted from here http://web.archive.org/web/20090728150504/http://www.snippetcenter.org/en/a-fast-atan2-function-s1868.aspx
*/
#ifndef _KIS_IMAGE_FAST_
#define _KIS_IMAGE_FAST_
#include <QtGlobal>
#include "kritaimage_export.h"
/**
* This namespace contains fast but inaccurate version of mathematical function.
*/
namespace KisFastMath {
/// atan2 replacement
KRITAIMAGE_EXPORT qreal atan2(qreal y, qreal x);
}
#endif
diff --git a/libs/image/kis_gaussian_kernel.cpp b/libs/image/kis_gaussian_kernel.cpp
index d6966f1d49..1770ac00ff 100644
--- a/libs/image/kis_gaussian_kernel.cpp
+++ b/libs/image/kis_gaussian_kernel.cpp
@@ -1,400 +1,401 @@
/*
* 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_gaussian_kernel.h"
#include "kis_global.h"
#include "kis_convolution_kernel.h"
#include <kis_convolution_painter.h>
#include <kis_transaction.h>
#include <QRect>
qreal KisGaussianKernel::sigmaFromRadius(qreal radius)
{
return 0.3 * radius + 0.3;
}
int KisGaussianKernel::kernelSizeFromRadius(qreal radius)
{
return 6 * ceil(sigmaFromRadius(radius)) + 1;
}
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic>
KisGaussianKernel::createHorizontalMatrix(qreal radius)
{
int kernelSize = kernelSizeFromRadius(radius);
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(1, kernelSize);
const qreal sigma = sigmaFromRadius(radius);
const qreal multiplicand = 1 / (sqrt(2 * M_PI * sigma * sigma));
const qreal exponentMultiplicand = 1 / (2 * sigma * sigma);
/**
* The kernel size should always be odd, then the position of the
* central pixel can be easily calculated
*/
KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
const int center = kernelSize / 2;
for (int x = 0; x < kernelSize; x++) {
qreal xDistance = center - x;
matrix(0, x) = multiplicand * exp( -xDistance * xDistance * exponentMultiplicand );
}
return matrix;
}
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic>
KisGaussianKernel::createVerticalMatrix(qreal radius)
{
int kernelSize = kernelSizeFromRadius(radius);
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, 1);
const qreal sigma = sigmaFromRadius(radius);
const qreal multiplicand = 1 / (sqrt(2 * M_PI * sigma * sigma));
const qreal exponentMultiplicand = 1 / (2 * sigma * sigma);
/**
* The kernel size should always be odd, then the position of the
* central pixel can be easily calculated
*/
KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
const int center = kernelSize / 2;
for (int y = 0; y < kernelSize; y++) {
qreal yDistance = center - y;
matrix(y, 0) = multiplicand * exp( -yDistance * yDistance * exponentMultiplicand );
}
return matrix;
}
KisConvolutionKernelSP
KisGaussianKernel::createHorizontalKernel(qreal radius)
{
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createHorizontalMatrix(radius);
return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum());
}
KisConvolutionKernelSP
KisGaussianKernel::createVerticalKernel(qreal radius)
{
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createVerticalMatrix(radius);
return KisConvolutionKernel::fromMatrix(matrix, 0, matrix.sum());
}
KisConvolutionKernelSP
KisGaussianKernel::createUniform2DKernel(qreal xRadius, qreal yRadius)
{
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> h = createHorizontalMatrix(xRadius);
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> v = createVerticalMatrix(yRadius);
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> uni = v * h;
return KisConvolutionKernel::fromMatrix(uni, 0, uni.sum());
}
void KisGaussianKernel::applyGaussian(KisPaintDeviceSP device,
const QRect& rect,
qreal xRadius, qreal yRadius,
const QBitArray &channelFlags,
KoUpdater *progressUpdater,
- bool createTransaction)
+ bool createTransaction,
+ KisConvolutionBorderOp borderOp)
{
QPoint srcTopLeft = rect.topLeft();
if (KisConvolutionPainter::supportsFFTW()) {
KisConvolutionPainter painter(device, KisConvolutionPainter::FFTW);
painter.setChannelFlags(channelFlags);
painter.setProgress(progressUpdater);
KisConvolutionKernelSP kernel2D = KisGaussianKernel::createUniform2DKernel(xRadius, yRadius);
QScopedPointer<KisTransaction> transaction;
if (createTransaction && painter.needsTransaction(kernel2D)) {
transaction.reset(new KisTransaction(device));
}
- painter.applyMatrix(kernel2D, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
+ painter.applyMatrix(kernel2D, device, srcTopLeft, srcTopLeft, rect.size(), borderOp);
} else if (xRadius > 0.0 && yRadius > 0.0) {
KisPaintDeviceSP interm = new KisPaintDevice(device->colorSpace());
interm->prepareClone(device);
KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(xRadius);
KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(yRadius);
qreal verticalCenter = qreal(kernelVertical->height()) / 2.0;
KisConvolutionPainter horizPainter(interm);
horizPainter.setChannelFlags(channelFlags);
horizPainter.setProgress(progressUpdater);
horizPainter.applyMatrix(kernelHoriz, device,
srcTopLeft - QPoint(0, ceil(verticalCenter)),
srcTopLeft - QPoint(0, ceil(verticalCenter)),
- rect.size() + QSize(0, 2 * ceil(verticalCenter)), BORDER_REPEAT);
+ rect.size() + QSize(0, 2 * ceil(verticalCenter)), borderOp);
KisConvolutionPainter verticalPainter(device);
verticalPainter.setChannelFlags(channelFlags);
verticalPainter.setProgress(progressUpdater);
- verticalPainter.applyMatrix(kernelVertical, interm, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
+ verticalPainter.applyMatrix(kernelVertical, interm, srcTopLeft, srcTopLeft, rect.size(), borderOp);
} else if (xRadius > 0.0) {
KisConvolutionPainter painter(device);
painter.setChannelFlags(channelFlags);
painter.setProgress(progressUpdater);
KisConvolutionKernelSP kernelHoriz = KisGaussianKernel::createHorizontalKernel(xRadius);
QScopedPointer<KisTransaction> transaction;
if (createTransaction && painter.needsTransaction(kernelHoriz)) {
transaction.reset(new KisTransaction(device));
}
- painter.applyMatrix(kernelHoriz, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
+ painter.applyMatrix(kernelHoriz, device, srcTopLeft, srcTopLeft, rect.size(), borderOp);
} else if (yRadius > 0.0) {
KisConvolutionPainter painter(device);
painter.setChannelFlags(channelFlags);
painter.setProgress(progressUpdater);
KisConvolutionKernelSP kernelVertical = KisGaussianKernel::createVerticalKernel(yRadius);
QScopedPointer<KisTransaction> transaction;
if (createTransaction && painter.needsTransaction(kernelVertical)) {
transaction.reset(new KisTransaction(device));
}
- painter.applyMatrix(kernelVertical, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
+ painter.applyMatrix(kernelVertical, device, srcTopLeft, srcTopLeft, rect.size(), borderOp);
}
}
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic>
KisGaussianKernel::createLoGMatrix(qreal radius, qreal coeff, bool zeroCentered, bool includeWrappedArea)
{
int kernelSize = 2 * (includeWrappedArea ? 2 : 1) * std::ceil(radius) + 1;
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize);
const qreal sigma = radius/* / sqrt(2)*/;
const qreal multiplicand = -1.0 / (M_PI * pow2(pow2(sigma)));
const qreal exponentMultiplicand = 1 / (2 * pow2(sigma));
/**
* The kernel size should always be odd, then the position of the
* central pixel can be easily calculated
*/
KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
const int center = kernelSize / 2;
for (int y = 0; y < kernelSize; y++) {
const qreal yDistance = center - y;
for (int x = 0; x < kernelSize; x++) {
const qreal xDistance = center - x;
const qreal distance = pow2(xDistance) + pow2(yDistance);
const qreal normalizedDistance = exponentMultiplicand * distance;
matrix(x, y) = multiplicand *
(1.0 - normalizedDistance) *
exp(-normalizedDistance);
}
}
qreal lateral = matrix.sum() - matrix(center, center);
matrix(center, center) = -lateral;
qreal totalSum = 0;
if (zeroCentered) {
for (int y = 0; y < kernelSize; y++) {
for (int x = 0; x < kernelSize; x++) {
const qreal value = matrix(x, y);
totalSum += value;
}
}
}
qreal positiveSum = 0;
qreal sideSum = 0;
qreal quarterSum = 0;
totalSum = 0;
const qreal offset = totalSum / pow2(qreal(kernelSize));
for (int y = 0; y < kernelSize; y++) {
for (int x = 0; x < kernelSize; x++) {
qreal value = matrix(x, y);
value -= offset;
matrix(x, y) = value;
if (value > 0) {
positiveSum += value;
}
if (x > center) {
sideSum += value;
}
if (x > center && y > center) {
quarterSum += value;
}
totalSum += value;
}
}
const qreal scale = coeff * 2.0 / positiveSum;
matrix *= scale;
positiveSum *= scale;
sideSum *= scale;
quarterSum *= scale;
//qDebug() << ppVar(positiveSum) << ppVar(sideSum) << ppVar(quarterSum);
return matrix;
}
void KisGaussianKernel::applyLoG(KisPaintDeviceSP device,
const QRect& rect,
qreal radius, qreal coeff,
const QBitArray &channelFlags,
KoUpdater *progressUpdater)
{
QPoint srcTopLeft = rect.topLeft();
KisConvolutionPainter painter(device);
painter.setChannelFlags(channelFlags);
painter.setProgress(progressUpdater);
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix =
createLoGMatrix(radius, coeff, false, true);
KisConvolutionKernelSP kernel =
KisConvolutionKernel::fromMatrix(matrix,
0,
0);
painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
}
void KisGaussianKernel::applyTightLoG(KisPaintDeviceSP device,
const QRect& rect,
qreal radius, qreal coeff,
const QBitArray &channelFlags,
KoUpdater *progressUpdater)
{
QPoint srcTopLeft = rect.topLeft();
KisConvolutionPainter painter(device);
painter.setChannelFlags(channelFlags);
painter.setProgress(progressUpdater);
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix =
createLoGMatrix(radius, coeff, true, false);
KisConvolutionKernelSP kernel =
KisConvolutionKernel::fromMatrix(matrix,
0,
0);
painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
}
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> KisGaussianKernel::createDilateMatrix(qreal radius)
{
const int kernelSize = 2 * std::ceil(radius) + 1;
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize);
const qreal fadeStart = qMax(1.0, radius - 1.0);
/**
* The kernel size should always be odd, then the position of the
* central pixel can be easily calculated
*/
KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1);
const int center = kernelSize / 2;
for (int y = 0; y < kernelSize; y++) {
const qreal yDistance = center - y;
for (int x = 0; x < kernelSize; x++) {
const qreal xDistance = center - x;
const qreal distance = std::sqrt(pow2(xDistance) + pow2(yDistance));
qreal value = 1.0;
if (distance > radius + 1e-3) {
value = 0.0;
} else if (distance > fadeStart) {
value = qMax(0.0, radius - distance);
}
matrix(x, y) = value;
}
}
return matrix;
}
void KisGaussianKernel::applyDilate(KisPaintDeviceSP device, const QRect &rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->pixelSize() == 1);
QPoint srcTopLeft = rect.topLeft();
KisConvolutionPainter painter(device);
painter.setChannelFlags(channelFlags);
painter.setProgress(progressUpdater);
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createDilateMatrix(radius);
KisConvolutionKernelSP kernel =
KisConvolutionKernel::fromMatrix(matrix,
0,
1.0);
QScopedPointer<KisTransaction> transaction;
if (createTransaction && painter.needsTransaction(kernel)) {
transaction.reset(new KisTransaction(device));
}
painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
}
#include "kis_sequential_iterator.h"
void KisGaussianKernel::applyErodeU8(KisPaintDeviceSP device, const QRect &rect, qreal radius, const QBitArray &channelFlags, KoUpdater *progressUpdater, bool createTransaction)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(device->colorSpace()->pixelSize() == 1);
{
KisSequentialIterator dstIt(device, rect);
while (dstIt.nextPixel()) {
quint8 *dstPtr = dstIt.rawData();
*dstPtr = 255 - *dstPtr;
}
}
applyDilate(device, rect, radius, channelFlags, progressUpdater, createTransaction);
{
KisSequentialIterator dstIt(device, rect);
while (dstIt.nextPixel()) {
quint8 *dstPtr = dstIt.rawData();
*dstPtr = 255 - *dstPtr;
}
}
}
diff --git a/libs/image/kis_gaussian_kernel.h b/libs/image/kis_gaussian_kernel.h
index d1ae29e8c4..725a647bc4 100644
--- a/libs/image/kis_gaussian_kernel.h
+++ b/libs/image/kis_gaussian_kernel.h
@@ -1,90 +1,92 @@
/*
* 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_GAUSSIAN_KERNEL_H
#define __KIS_GAUSSIAN_KERNEL_H
#include "kritaimage_export.h"
#include "kis_types.h"
+#include "kis_convolution_painter.h"
#include <Eigen/Core>
class QRect;
class KRITAIMAGE_EXPORT KisGaussianKernel
{
public:
static Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic>
createHorizontalMatrix(qreal radius);
static Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic>
createVerticalMatrix(qreal radius);
static KisConvolutionKernelSP
createHorizontalKernel(qreal radius);
static KisConvolutionKernelSP
createVerticalKernel(qreal radius);
static KisConvolutionKernelSP
createUniform2DKernel(qreal xRadius, qreal yRadius);
static qreal sigmaFromRadius(qreal radius);
static int kernelSizeFromRadius(qreal radius);
static void applyGaussian(KisPaintDeviceSP device,
const QRect& rect,
qreal xRadius, qreal yRadius,
const QBitArray &channelFlags,
KoUpdater *updater,
- bool createTransaction = false);
+ bool createTransaction = false,
+ KisConvolutionBorderOp borderOp = BORDER_REPEAT);
static Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> createLoGMatrix(qreal radius, qreal coeff, bool zeroCentered, bool includeWrappedArea);
static void applyLoG(KisPaintDeviceSP device,
const QRect& rect,
qreal radius,
qreal coeff,
const QBitArray &channelFlags,
KoUpdater *progressUpdater);
static void applyTightLoG(KisPaintDeviceSP device,
const QRect& rect,
qreal radius, qreal coeff,
const QBitArray &channelFlags,
KoUpdater *progressUpdater);
static Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> createDilateMatrix(qreal radius);
static void applyDilate(KisPaintDeviceSP device,
const QRect& rect,
qreal radius,
const QBitArray &channelFlags,
KoUpdater *progressUpdater,
bool createTransaction = false);
static void applyErodeU8(KisPaintDeviceSP device,
const QRect& rect,
qreal radius,
const QBitArray &channelFlags,
KoUpdater *progressUpdater,
bool createTransaction = false);
};
#endif /* __KIS_GAUSSIAN_KERNEL_H */
diff --git a/libs/image/kis_keyframe.cpp b/libs/image/kis_keyframe.cpp
index cb230cc17c..3f200bbab2 100644
--- a/libs/image/kis_keyframe.cpp
+++ b/libs/image/kis_keyframe.cpp
@@ -1,132 +1,133 @@
/*
* 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_image_config.h"
#include "kis_keyframe.h"
#include "kis_keyframe_channel.h"
#include "kis_types.h"
#include <QPointer>
struct KisKeyframeSPStaticRegistrar {
KisKeyframeSPStaticRegistrar() {
qRegisterMetaType<KisKeyframeSP>("KisKeyframeSP");
}
};
static KisKeyframeSPStaticRegistrar __registrar;
struct KisKeyframe::Private
{
QPointer<KisKeyframeChannel> channel;
int time;
- InterpolationMode interpolationMode;
- InterpolationTangentsMode tangentsMode;
+ InterpolationMode interpolationMode {InterpolationMode::Constant};
+ InterpolationTangentsMode tangentsMode {InterpolationTangentsMode::Smooth};
QPointF leftTangent;
QPointF rightTangent;
int colorLabel{0};
Private(KisKeyframeChannel *channel, int time)
- : channel(channel), time(time), interpolationMode(Constant)
+ : channel(channel)
+ , time(time)
{}
};
KisKeyframe::KisKeyframe(KisKeyframeChannel *channel, int time)
: m_d(new Private(channel, time))
{
m_d->colorLabel = KisImageConfig(true).defaultFrameColorLabel();
}
KisKeyframe::KisKeyframe(const KisKeyframe *rhs, KisKeyframeChannel *channel)
: m_d(new Private(channel, rhs->time()))
{
m_d->interpolationMode = rhs->m_d->interpolationMode;
m_d->tangentsMode = rhs->m_d->tangentsMode;
m_d->leftTangent = rhs->m_d->leftTangent;
m_d->rightTangent = rhs->m_d->rightTangent;
m_d->colorLabel = rhs->m_d->colorLabel;
}
KisKeyframe::~KisKeyframe()
{}
int KisKeyframe::time() const
{
return m_d->time;
}
void KisKeyframe::setTime(int time)
{
m_d->time = time;
}
void KisKeyframe::setInterpolationMode(KisKeyframe::InterpolationMode mode)
{
m_d->interpolationMode = mode;
}
KisKeyframe::InterpolationMode KisKeyframe::interpolationMode() const
{
return m_d->interpolationMode;
}
void KisKeyframe::setTangentsMode(KisKeyframe::InterpolationTangentsMode mode)
{
m_d->tangentsMode = mode;
}
KisKeyframe::InterpolationTangentsMode KisKeyframe::tangentsMode() const
{
return m_d->tangentsMode;
}
void KisKeyframe::setInterpolationTangents(QPointF leftTangent, QPointF rightTangent)
{
m_d->leftTangent = leftTangent;
m_d->rightTangent = rightTangent;
}
QPointF KisKeyframe::leftTangent() const
{
return m_d->leftTangent;
}
QPointF KisKeyframe::rightTangent() const
{
return m_d->rightTangent;
}
int KisKeyframe::colorLabel() const
{
return m_d->colorLabel;
}
void KisKeyframe::setColorLabel(int label)
{
m_d->colorLabel = label;
}
bool KisKeyframe::hasContent() const {
return true;
}
KisKeyframeChannel *KisKeyframe::channel() const
{
return m_d->channel;
}
diff --git a/libs/image/kis_onion_skin_compositor.cpp b/libs/image/kis_onion_skin_compositor.cpp
index 9e408005e7..b96609819c 100644
--- a/libs/image/kis_onion_skin_compositor.cpp
+++ b/libs/image/kis_onion_skin_compositor.cpp
@@ -1,227 +1,237 @@
/*
* 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_onion_skin_compositor.h"
#include "kis_paint_device.h"
#include "kis_painter.h"
#include "KoColor.h"
#include "KoColorSpace.h"
#include "KoCompositeOpRegistry.h"
#include "KoColorSpaceConstants.h"
#include "kis_image_config.h"
#include "kis_raster_keyframe_channel.h"
Q_GLOBAL_STATIC(KisOnionSkinCompositor, s_instance)
struct KisOnionSkinCompositor::Private
{
int numberOfSkins = 0;
int tintFactor = 0;
QColor backwardTintColor;
QColor forwardTintColor;
QVector<int> backwardOpacities;
QVector<int> forwardOpacities;
int configSeqNo = 0;
QList<int> colorLabelFilter;
int skinOpacity(int offset)
{
const QVector<int> &bo = backwardOpacities;
const QVector<int> &fo = forwardOpacities;
return offset > 0 ? fo[qAbs(offset) - 1] : bo[qAbs(offset) - 1];
}
KisPaintDeviceSP setUpTintDevice(const QColor &tintColor, const KoColorSpace *colorSpace)
{
KisPaintDeviceSP tintDevice = new KisPaintDevice(colorSpace);
KoColor color = KoColor(tintColor, colorSpace);
tintDevice->setDefaultPixel(color);
return tintDevice;
}
KisKeyframeSP getNextFrameToComposite(KisKeyframeChannel *channel, KisKeyframeSP keyframe, bool backwards)
{
while (!keyframe.isNull()) {
keyframe = backwards ? channel->previousKeyframe(keyframe) : channel->nextKeyframe(keyframe);
if (colorLabelFilter.isEmpty()) {
return keyframe;
} else if (!keyframe.isNull()) {
if (colorLabelFilter.contains(keyframe->colorLabel())) {
return keyframe;
}
}
}
return keyframe;
}
void tryCompositeFrame(KisRasterKeyframeChannel *keyframes, KisKeyframeSP keyframe, KisPainter &gcFrame, KisPainter &gcDest, KisPaintDeviceSP tintSource, int opacity, const QRect &rect)
{
if (keyframe.isNull() || opacity == OPACITY_TRANSPARENT_U8) return;
keyframes->fetchFrame(keyframe, gcFrame.device());
gcFrame.bitBlt(rect.topLeft(), tintSource, rect);
gcDest.setOpacity(opacity);
gcDest.bitBlt(rect.topLeft(), gcFrame.device(), rect);
}
void refreshConfig()
{
KisImageConfig config(true);
numberOfSkins = config.numberOfOnionSkins();
tintFactor = config.onionSkinTintFactor();
backwardTintColor = config.onionSkinTintColorBackward();
forwardTintColor = config.onionSkinTintColorForward();
backwardOpacities.resize(numberOfSkins);
forwardOpacities.resize(numberOfSkins);
const int mainState = (int) config.onionSkinState(0);
const qreal scaleFactor = mainState * config.onionSkinOpacity(0) / 255.0;
for (int i = 0; i < numberOfSkins; i++) {
int backwardState = (int) config.onionSkinState(-(i + 1));
int forwardState = (int) config.onionSkinState(i + 1);
backwardOpacities[i] = scaleFactor * backwardState * config.onionSkinOpacity(-(i + 1));
forwardOpacities[i] = scaleFactor * forwardState * config.onionSkinOpacity(i + 1);
}
configSeqNo++;
}
};
KisOnionSkinCompositor *KisOnionSkinCompositor::instance()
{
return s_instance;
}
KisOnionSkinCompositor::KisOnionSkinCompositor()
: m_d(new Private)
{
m_d->refreshConfig();
}
KisOnionSkinCompositor::~KisOnionSkinCompositor()
{}
int KisOnionSkinCompositor::configSeqNo() const
{
return m_d->configSeqNo;
}
void KisOnionSkinCompositor::setColorLabelFilter(QList<int> colors)
{
m_d->colorLabelFilter = colors;
}
void KisOnionSkinCompositor::composite(const KisPaintDeviceSP sourceDevice, KisPaintDeviceSP targetDevice, const QRect& rect)
{
KisRasterKeyframeChannel *keyframes = sourceDevice->keyframeChannel();
KisPaintDeviceSP frameDevice = new KisPaintDevice(sourceDevice->colorSpace());
KisPainter gcFrame(frameDevice);
QBitArray channelFlags = targetDevice->colorSpace()->channelFlags(true, false);
gcFrame.setChannelFlags(channelFlags);
gcFrame.setOpacity(m_d->tintFactor);
KisPaintDeviceSP backwardTintDevice = m_d->setUpTintDevice(m_d->backwardTintColor, sourceDevice->colorSpace());
KisPaintDeviceSP forwardTintDevice = m_d->setUpTintDevice(m_d->forwardTintColor, sourceDevice->colorSpace());
KisPainter gcDest(targetDevice);
gcDest.setCompositeOp(sourceDevice->colorSpace()->compositeOp(COMPOSITE_BEHIND));
KisKeyframeSP keyframeBck;
KisKeyframeSP keyframeFwd;
int time = sourceDevice->defaultBounds()->currentTime();
+
+ if (!keyframes) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
+ return;
+ }
+
keyframeBck = keyframeFwd = keyframes->activeKeyframeAt(time);
for (int offset = 1; offset <= m_d->numberOfSkins; offset++) {
keyframeBck = m_d->getNextFrameToComposite(keyframes, keyframeBck, true);
keyframeFwd = m_d->getNextFrameToComposite(keyframes, keyframeFwd, false);
if (!keyframeBck.isNull()) {
m_d->tryCompositeFrame(keyframes, keyframeBck, gcFrame, gcDest, backwardTintDevice, m_d->skinOpacity(-offset), rect);
}
if (!keyframeFwd.isNull()) {
m_d->tryCompositeFrame(keyframes, keyframeFwd, gcFrame, gcDest, forwardTintDevice, m_d->skinOpacity(offset), rect);
}
}
}
QRect KisOnionSkinCompositor::calculateFullExtent(const KisPaintDeviceSP device)
{
QRect rect;
KisRasterKeyframeChannel *channel = device->keyframeChannel();
if (!channel) return rect;
KisKeyframeSP keyframe = channel->firstKeyframe();
while (keyframe) {
rect |= channel->frameExtents(keyframe);
keyframe = channel->nextKeyframe(keyframe);
}
return rect;
}
QRect KisOnionSkinCompositor::calculateExtent(const KisPaintDeviceSP device)
{
QRect rect;
KisKeyframeSP keyframeBck;
KisKeyframeSP keyframeFwd;
KisRasterKeyframeChannel *channel = device->keyframeChannel();
+
+ if (!channel) { // it happens when you try to show onion skins on non-animated layer with opacity keyframes
+ return rect;
+ }
+
keyframeBck = keyframeFwd = channel->activeKeyframeAt(device->defaultBounds()->currentTime());
for (int offset = 1; offset <= m_d->numberOfSkins; offset++) {
if (!keyframeBck.isNull()) {
keyframeBck = channel->previousKeyframe(keyframeBck);
if (!keyframeBck.isNull()) {
rect |= channel->frameExtents(keyframeBck);
}
}
if (!keyframeFwd.isNull()) {
keyframeFwd = channel->nextKeyframe(keyframeFwd);
if (!keyframeFwd.isNull()) {
rect |= channel->frameExtents(keyframeFwd);
}
}
}
return rect;
}
void KisOnionSkinCompositor::configChanged()
{
m_d->refreshConfig();
emit sigOnionSkinChanged();
}
diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc
index 7e8cc21aef..473a69fade 100644
--- a/libs/image/kis_painter.cc
+++ b/libs/image/kis_painter.cc
@@ -1,3014 +1,3022 @@
/*
* 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 "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 "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
#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 QRect srcExtent = src->extent();
const QRect dstExtent = dst->extent();
const QRect srcSampleRect = srcExtent & srcRect;
const QRect dstSampleRect = dstExtent & dstRect;
const bool srcEmpty = srcSampleRect.isEmpty();
const bool dstEmpty = dstSampleRect.isEmpty();
if (!srcEmpty || !dstEmpty) {
if (srcEmpty) {
dst->clear(dstRect);
} else {
QRect srcCopyRect = srcRect;
QRect dstCopyRect = dstRect;
if (!srcExtent.contains(srcRect)) {
if (src->defaultPixel() == dst->defaultPixel()) {
const QRect dstSampleInSrcCoords = dstSampleRect.translated(srcRect.topLeft() - dstPt);
if (dstSampleInSrcCoords.isEmpty() || srcSampleRect.contains(dstSampleInSrcCoords)) {
srcCopyRect = srcSampleRect;
} else {
srcCopyRect = srcSampleRect | dstSampleInSrcCoords;
}
dstCopyRect = QRect(dstPt + srcCopyRect.topLeft() - srcRect.topLeft(), srcCopyRect.size());
}
}
KisPainter gc(dst);
gc.setCompositeOp(dst->colorSpace()->compositeOp(COMPOSITE_COPY));
if (useOldData) {
gc.bitBltOldData(dstCopyRect.topLeft(), src, srcCopyRect);
} else {
gc.bitBlt(dstCopyRect.topLeft(), src, srcCopyRect);
}
}
}
}
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()));
if (processRect.isEmpty()) return dst;
KisSequentialConstIterator srcIt(src, processRect);
KisSequentialIterator dstIt(dst, processRect);
while (srcIt.nextPixel() && dstIt.nextPixel()) {
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);
}
return dst;
}
KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src)
{
const KoColorSpace *srcCS = src->colorSpace();
const QRect processRect = src->extent();
KisPaintDeviceSP dst(new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()));
if (processRect.isEmpty()) return dst;
KisSequentialConstIterator srcIt(src, processRect);
KisSequentialIterator dstIt(dst, processRect);
while (srcIt.nextPixel() && dstIt.nextPixel()) {
const quint8 *srcPtr = srcIt.rawDataConst();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->intensity8(srcPtr);
}
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);
while(it.nextPixel()) {
if (cs->opacityU8(it.rawDataConst()) != OPACITY_OPAQUE_U8) {
return true;
}
}
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);
}
}
void KisPainter::addDirtyRects(const QVector<QRect> &rects)
{
d->dirtyRects.reserve(d->dirtyRects.size() + rects.size());
Q_FOREACH (const QRect &rc, rects) {
const 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 (const 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 (const 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 (const 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. */
KIS_SAFE_ASSERT_RECOVER_RETURN(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 (const 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 (const 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 (const 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 (const 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 (d->fillStyle != FillStyleNone) {
fillPolygon(points, d->fillStyle);
}
if (d->strokeStyle == StrokeStyleNone) return;
if (index >= points.count())
return;
if (numPoints < 0)
numPoints = points.count();
if (index + numPoints > points.count())
numPoints = points.count() - index;
if (numPoints > 1) {
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],
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:
Q_FALLTHROUGH();
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()
KIS_SAFE_ASSERT_RECOVER_NOOP(_pen.color() == Qt::white);
QPen pen(_pen);
pen.setColor(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 = qFloor(start.x());
int y1 = qFloor(start.y());
int x2 = qFloor(end.x());
int y2 = qFloor(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 = qFloor(start.x());
int y = qFloor(start.y());
int x2 = qFloor(end.x());
int y2 = qFloor(end.y());
// Width and height of the line
int xd = x2 - x;
int yd = y2 - y;
- float m = (float)yd / (float)xd;
+ float m = 0;
+ bool lockAxis = true;
+
+ if (xd == 0) {
+ m = 2.0;
+ } else if ( yd != 0) {
+ lockAxis = false;
+ 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 = (lockAxis)? 0 : 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)
{
KoColor mycolor(d->paintColor);
int x1 = qFloor(start.x());
int y1 = qFloor(start.y());
int x2 = qFloor(end.x());
int y2 = qFloor(end.y());
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1);
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1);
}
// 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 = qFloor(fx + 1) - fx;
float br2 = fx - qFloor(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 = qFloor(fy + 1) - fy;
float br2 = fy - qFloor(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)
{
KoColor lineColor(d->paintColor);
int x1 = qFloor(start.x());
int y1 = qFloor(start.y());
int x2 = qFloor(end.x());
int y2 = qFloor(end.y());
KisRandomAccessorSP accessor = d->device->createRandomAccessorNG(x1, y1);
KisRandomConstAccessorSP selectionAccessor;
if (d->selection) {
selectionAccessor = d->selection->projection()->createRandomConstAccessorNG(x1, y1);
}
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 = x1;
ix2 = x2;
iy1 = 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 = y1;
iy2 = y2;
ix1 = 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) {
std::swap(x1, x2);
std::swap(y1, y2);
xd = (x2 - x1);
yd = (y2 - y1);
}
grad = yd / xd;
// nearest X,Y integer coordinates
xend = x1;
yend = y1 + grad * (xend - x1);
xgap = invertFrac(x1 + 0.5f);
ix1 = x1;
iy1 = qFloor(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 = x2;
yend = y2 + grad * (xend - x2);
xgap = invertFrac(x2 - 0.5f);
ix2 = x2;
iy2 = qFloor(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, qFloor(yf));
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(yf));
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(x, qFloor(yf) + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(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) {
std::swap(x1, x2);
std::swap(y1, y2);
xd = (x2 - x1);
yd = (y2 - y1);
}
grad = xd / yd;
// nearest X,Y integer coordinates
yend = y1;
xend = x1 + grad * (yend - y1);
ygap = y1;
ix1 = qFloor(xend);
iy1 = y1;
// 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 = y2;
xend = x2 + grad * (yend - y2);
ygap = invertFrac(y2 - 0.5f);
ix2 = qFloor(xend);
iy2 = y2;
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(qFloor(xf), y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(xf), y);
if (!selectionAccessor || *selectionAccessor->oldRawData() > SELECTION_THRESHOLD) {
lineColor.setOpacity(c1);
compositeOnePixel(accessor->rawData(), lineColor);
}
accessor->moveTo(qFloor(xf) + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(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 - qFloor(yfa);
b1a = 1 - fraca;
b2a = fraca;
fracb = yfb - qFloor(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, qFloor(yfa));
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(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, qFloor(yfb));
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(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, qFloor(yfa) + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(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, qFloor(yfb) + 1);
if (selectionAccessor) selectionAccessor->moveTo(x, qFloor(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 = qFloor(yfa) + 1; i <= qFloor(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 = qFloor(yfa) + 1; i >= qFloor(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 - qFloor(xfa);
b1a = 1 - fraca;
b2a = fraca;
fracb = xfb - qFloor(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(qFloor(xfa), y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(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(qFloor(xfb), y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(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(qFloor(xfa) + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(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(qFloor(xfb) + 1, y);
if (selectionAccessor) selectionAccessor->moveTo(qFloor(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 = qFloor(xfa) + 1; i <= qFloor(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 = qFloor(xfb); i <= qFloor(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::copyMirrorInformationFrom(const KisPainter *other)
{
d->axesCenter = other->d->axesCenter;
d->mirrorHorizontally = other->d->mirrorHorizontally;
d->mirrorVertically = other->d->mirrorVertically;
}
bool KisPainter::hasMirroring() const
{
return d->mirrorHorizontally || d->mirrorVertically;
}
bool KisPainter::hasHorizontalMirroring() const
{
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);
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);
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->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->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);
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);
}
}
bool KisPainter::hasDirtyRegion() const
{
return !d->dirtyRects.isEmpty();
}
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);
}
namespace {
inline void mirrorOneObject(Qt::Orientation dir, const QPoint &center, QRect *rc) {
KritaUtils::mirrorRect(dir, center, rc);
}
inline void mirrorOneObject(Qt::Orientation dir, const QPoint &center, QPointF *pt) {
KritaUtils::mirrorPoint(dir, center, pt);
}
inline void mirrorOneObject(Qt::Orientation dir, const QPoint &center, QPair<QPointF, QPointF> *pair) {
KritaUtils::mirrorPoint(dir, center, &pair->first);
KritaUtils::mirrorPoint(dir, center, &pair->second);
}
}
template<class T> QVector<T> KisPainter::Private::calculateMirroredObjects(const T &object)
{
QVector<T> result;
KisLodTransform t(this->device);
const QPoint effectiveAxesCenter = t.map(this->axesCenter).toPoint();
T baseObject = object;
result << baseObject;
if (this->mirrorHorizontally && this->mirrorVertically){
mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject);
result << baseObject;
mirrorOneObject(Qt::Vertical, effectiveAxesCenter, &baseObject);
result << baseObject;
mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject);
result << baseObject;
} else if (this->mirrorHorizontally) {
mirrorOneObject(Qt::Horizontal, effectiveAxesCenter, &baseObject);
result << baseObject;
} else if (this->mirrorVertically) {
mirrorOneObject(Qt::Vertical, effectiveAxesCenter, &baseObject);
result << baseObject;
}
return result;
}
const QVector<QRect> KisPainter::calculateAllMirroredRects(const QRect &rc)
{
return d->calculateMirroredObjects(rc);
}
const QVector<QPointF> KisPainter::calculateAllMirroredPoints(const QPointF &pos)
{
return d->calculateMirroredObjects(pos);
}
const QVector<QPair<QPointF, QPointF>> KisPainter::calculateAllMirroredPoints(const QPair<QPointF, QPointF> &pair)
{
return d->calculateMirroredObjects(pair);
}
diff --git a/libs/image/kis_properties_configuration.h b/libs/image/kis_properties_configuration.h
index 60ef3727e7..886d5c283b 100644
--- a/libs/image/kis_properties_configuration.h
+++ b/libs/image/kis_properties_configuration.h
@@ -1,224 +1,224 @@
/*
* 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_PROPERTIES_CONFIGURATION_H_
#define _KIS_PROPERTIES_CONFIGURATION_H_
#include <QString>
#include <QMap>
#include <QVariant>
#include <kis_debug.h>
#include <kis_cubic_curve.h>
#include <KoColor.h>
class QDomElement;
class QDomDocument;
#include "kis_serializable_configuration.h"
#include "kritaimage_export.h"
#include "kis_types.h"
/**
* KisPropertiesConfiguration is a map-based properties class that can
* be serialized and deserialized.
*
* It differs from the base class KisSerializableConfiguration in that
* it provides a number of convenience methods to get at the data and
*/
class KRITAIMAGE_EXPORT KisPropertiesConfiguration : public KisSerializableConfiguration
{
public:
/**
* Create a new properties config.
*/
KisPropertiesConfiguration();
~KisPropertiesConfiguration() override;
/**
* Deep copy the properties \p rhs
*/
KisPropertiesConfiguration(const KisPropertiesConfiguration& rhs);
/**
* Deep copy the properties \p rhs
*/
KisPropertiesConfiguration& operator=(const KisPropertiesConfiguration& rhs);
public:
/**
* Fill the properties configuration object from the XML encoded representation in s.
* This function use the "Legacy" style XML of the 1.x .kra file format.
* @param xml the string that will be parsed as xml
* @param clear if true, the properties map will be emptied.
* @return true is the xml document could be parsed
*/
bool fromXML(const QString& xml, bool clear = true) override;
/**
* Fill the properties configuration object from the XML encoded representation in s.
* This function use the "Legacy" style XML of the 1.x .kra file format.
*
* Note: the existing properties will not be cleared
*/
void fromXML(const QDomElement&) override;
/**
* Create a serialized version of this properties config
* This function use the "Legacy" style XML of the 1.x .kra file format.
*/
void toXML(QDomDocument&, QDomElement&) const override;
/**
* Create a serialized version of this properties config
* This function use the "Legacy" style XML of the 1.x .kra file format.
*/
QString toXML() const override;
/**
* @return true if the map contains a property with the specified name
*/
virtual bool hasProperty(const QString& name) const;
/**
* Set the property with name to value.
*/
virtual void setProperty(const QString & name, const QVariant & value);
/**
* Set value to the value associated with property name
*
* XXX: API alert: a setter that is prefixed with get?
*
* @return false if the specified property did not exist.
*/
virtual bool getProperty(const QString & name, QVariant & value) const;
virtual QVariant getProperty(const QString & name) const;
template <typename T>
T getPropertyLazy(const QString & name, const T &defaultValue) const {
QVariant value = getProperty(name);
return value.isValid() ? value.value<T>() : defaultValue;
}
QString getPropertyLazy(const QString & name, const char *defaultValue) const {
return getPropertyLazy(name, QString(defaultValue));
}
int getInt(const QString & name, int def = 0) const;
double getDouble(const QString & name, double def = 0.0) const;
float getFloat(const QString& name, float def = 0.0) const;
bool getBool(const QString & name, bool def = false) const;
QString getString(const QString & name, const QString & def = QString()) const;
KisCubicCurve getCubicCurve(const QString & name, const KisCubicCurve & curve = KisCubicCurve()) const;
/**
* @brief getColor fetch the given property as a KoColor.
*
* The color can be stored as
* <ul>
* <li>A KoColor
* <li>A QColor
* <li>A string that can be parsed as an XML color definition
- * <li>A string that QColor can convert to a color (see http://doc.qt.io/qt-5/qcolor.html#setNamedColor)
+ * <li>A string that QColor can convert to a color (see https://doc.qt.io/qt-5/qcolor.html#setNamedColor)
* <li>An integer that QColor can convert to a color
* </ul>
*
* @param name the name of the property
* @param color the default value to be returned if the @param name does not exist.
* @return returns the named property as a KoColor if the value can be converted to a color,
* otherwise a empty KoColor is returned.
*/
KoColor getColor(const QString& name, const KoColor& color = KoColor()) const;
QMap<QString, QVariant> getProperties() const;
/// Clear the map of properties
void clearProperties();
/// Marks a property that should not be saved by toXML
void setPropertyNotSaved(const QString & name);
void removeProperty(const QString & name);
/**
* Get the keys of all the properties in the object
*/
virtual QList<QString> getPropertiesKeys() const;
/**
* Get a set of properties, which keys are prefixed with \p prefix. The settings object
* \p config will have all these properties with the prefix stripped from them.
*/
void getPrefixedProperties(const QString &prefix, KisPropertiesConfiguration *config) const;
/**
* A convenience override
*/
void getPrefixedProperties(const QString &prefix, KisPropertiesConfigurationSP config) const;
/**
* Takes all the properties from \p config, adds \p prefix to all their keys and puths them
* into this properties object
*/
void setPrefixedProperties(const QString &prefix, const KisPropertiesConfiguration *config);
/**
* A convenience override
*/
void setPrefixedProperties(const QString &prefix, const KisPropertiesConfigurationSP config);
static QString escapeString(const QString &string);
static QString unescapeString(const QString &string);
void setProperty(const QString &name, const QStringList &value);
QStringList getStringList(const QString &name, const QStringList &defaultValue = QStringList()) const;
QStringList getPropertyLazy(const QString &name, const QStringList &defaultValue) const;
public:
void dump() const;
private:
struct Private;
Private* const d;
};
class KRITAIMAGE_EXPORT KisPropertiesConfigurationFactory : public KisSerializableConfigurationFactory
{
public:
KisPropertiesConfigurationFactory();
~KisPropertiesConfigurationFactory() override;
KisSerializableConfigurationSP createDefault() override;
KisSerializableConfigurationSP create(const QDomElement& e) override;
private:
struct Private;
Private* const d;
};
#endif
diff --git a/libs/image/kis_psd_layer_style.h b/libs/image/kis_psd_layer_style.h
index a536b19d8c..2711f296ff 100644
--- a/libs/image/kis_psd_layer_style.h
+++ b/libs/image/kis_psd_layer_style.h
@@ -1,101 +1,101 @@
/*
* 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.
*/
#ifndef KIS_PSD_LAYER_STYLE_H
#define KIS_PSD_LAYER_STYLE_H
class QIODevice;
class QUuid;
#include <QVector>
#include <psd.h>
#include "kis_types.h"
#include "kritaimage_export.h"
class KisPSDLayerStyle;
typedef QSharedPointer<KisPSDLayerStyle> KisPSDLayerStyleSP;
/**
* @brief The KisPSDLayerStyle class implements loading, saving and applying
* the PSD layer effects.
*
- * See http://www.tonton-pixel.com/Photoshop%20Additional%20File%20Formats/styles-file-format.html
+ * See https://www.tonton-pixel.com/Photoshop%20Additional%20File%20Formats/styles-file-format.html
*
*/
class KRITAIMAGE_EXPORT KisPSDLayerStyle
{
public:
explicit KisPSDLayerStyle();
virtual ~KisPSDLayerStyle();
KisPSDLayerStyle(const KisPSDLayerStyle& rhs);
KisPSDLayerStyle operator=(const KisPSDLayerStyle& rhs);
KisPSDLayerStyleSP clone() const;
void clear();
QString name() const;
void setName(const QString &value);
QUuid uuid() const;
void setUuid(const QUuid &value) const;
QString psdUuid() const;
void setPsdUuid(const QString &value) const;
/**
* \return true if all the styles are disabled
*/
bool isEmpty() const;
bool isEnabled() const;
void setEnabled(bool value);
const psd_layer_effects_context* context() const;
const psd_layer_effects_drop_shadow* dropShadow() const;
const psd_layer_effects_inner_shadow* innerShadow() const;
const psd_layer_effects_outer_glow* outerGlow() const;
const psd_layer_effects_inner_glow* innerGlow() const;
const psd_layer_effects_satin* satin() const;
const psd_layer_effects_color_overlay* colorOverlay() const;
const psd_layer_effects_gradient_overlay* gradientOverlay() const;
const psd_layer_effects_pattern_overlay* patternOverlay() const;
const psd_layer_effects_stroke* stroke() const;
const psd_layer_effects_bevel_emboss* bevelAndEmboss() const;
psd_layer_effects_context* context();
psd_layer_effects_drop_shadow* dropShadow();
psd_layer_effects_inner_shadow* innerShadow();
psd_layer_effects_outer_glow* outerGlow();
psd_layer_effects_inner_glow* innerGlow();
psd_layer_effects_satin* satin();
psd_layer_effects_color_overlay* colorOverlay();
psd_layer_effects_gradient_overlay* gradientOverlay();
psd_layer_effects_pattern_overlay* patternOverlay();
psd_layer_effects_stroke* stroke();
psd_layer_effects_bevel_emboss* bevelAndEmboss();
private:
struct Private;
Private * const d;
};
#endif // KIS_PSD_LAYER_STYLE_H
diff --git a/libs/image/kis_rect_mask_generator.cpp b/libs/image/kis_rect_mask_generator.cpp
index 1d6200a39b..7b98fcad8f 100644
--- a/libs/image/kis_rect_mask_generator.cpp
+++ b/libs/image/kis_rect_mask_generator.cpp
@@ -1,158 +1,160 @@
/*
* Copyright (c) 2004,2007,2008,2009.2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2018 Ivan Santa Maria <ghevan@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 <cmath>
#include <config-vc.h>
#ifdef HAVE_VC
#if defined(__clang__)
#pragma GCC diagnostic ignored "-Wundef"
#pragma GCC diagnostic ignored "-Wlocal-type-template-args"
#endif
#if defined _MSC_VER
// Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false'
#pragma warning ( push )
#pragma warning ( disable : 4244 )
#pragma warning ( disable : 4800 )
#endif
#include <Vc/Vc>
#include <Vc/IO>
#if defined _MSC_VER
#pragma warning ( pop )
#endif
#endif
#include <QDomDocument>
#include "kis_fast_math.h"
#include "kis_rect_mask_generator.h"
#include "kis_rect_mask_generator_p.h"
#include "kis_base_mask_generator.h"
#include "kis_brush_mask_applicator_factories.h"
#include "kis_brush_mask_applicator_base.h"
#include <qnumeric.h>
KisRectangleMaskGenerator::KisRectangleMaskGenerator(qreal radius, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges)
: KisMaskGenerator(radius, ratio, fh, fv, spikes, antialiasEdges, RECTANGLE, DefaultId), d(new Private)
{
setScale(1.0, 1.0);
// store the variable locally to allow vector implementation read it easily
d->copyOfAntialiasEdges = antialiasEdges;
d->applicator.reset(createOptimizedClass<MaskApplicatorFactory<KisRectangleMaskGenerator, KisBrushMaskVectorApplicator> >(this));
}
KisRectangleMaskGenerator::KisRectangleMaskGenerator(const KisRectangleMaskGenerator &rhs)
: KisMaskGenerator(rhs),
d(new Private(*rhs.d))
{
d->applicator.reset(createOptimizedClass<MaskApplicatorFactory<KisRectangleMaskGenerator, KisBrushMaskVectorApplicator> >(this));
}
KisMaskGenerator* KisRectangleMaskGenerator::clone() const
{
return new KisRectangleMaskGenerator(*this);
}
KisRectangleMaskGenerator::~KisRectangleMaskGenerator()
{
}
void KisRectangleMaskGenerator::setScale(qreal scaleX, qreal scaleY)
{
KisMaskGenerator::setScale(scaleX, scaleY);
d->xcoeff = 2.0 / effectiveSrcWidth();
d->ycoeff = 2.0 / effectiveSrcHeight();
d->xfadecoeff = (horizontalFade() == 0) ? 1 : (2.0 / (horizontalFade() * effectiveSrcWidth()));
d->yfadecoeff = (verticalFade() == 0) ? 1 : (2.0 / (verticalFade() * effectiveSrcHeight()));
setSoftness(this->softness());
}
void KisRectangleMaskGenerator::setSoftness(qreal softness)
{
KisMaskGenerator::setSoftness(softness);
qreal safeSoftnessCoeff = qreal(1.0) / qMax(qreal(0.01), softness);
d->transformedFadeX = d->xfadecoeff * safeSoftnessCoeff;
d->transformedFadeY = d->yfadecoeff * safeSoftnessCoeff;
}
bool KisRectangleMaskGenerator::shouldSupersample() const
{
return effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10;
}
bool KisRectangleMaskGenerator::shouldVectorize() const
{
return !shouldSupersample() && spikes() == 2;
}
KisBrushMaskApplicatorBase* KisRectangleMaskGenerator::applicator()
{
return d->applicator.data();
}
void KisRectangleMaskGenerator::resetMaskApplicator(bool forceScalar)
{
d->applicator.reset(createOptimizedClass<MaskApplicatorFactory<KisRectangleMaskGenerator, KisBrushMaskVectorApplicator> >(this,forceScalar));
}
quint8 KisRectangleMaskGenerator::valueAt(qreal x, qreal y) const
{
if (isEmpty()) return 255;
qreal xr = qAbs(x /*- m_xcenter*/);
qreal yr = qAbs(y /*- m_ycenter*/);
fixRotation(xr, yr);
xr = qAbs(xr);
yr = qAbs(yr);
qreal nxr = xr * d->xcoeff;
qreal nyr = yr * d->ycoeff;
if (nxr > 1.0 || nyr > 1.0) return 255;
if (antialiasEdges()) {
xr += 1.0;
yr += 1.0;
}
qreal fxr = xr * d->transformedFadeX;
qreal fyr = yr * d->transformedFadeY;
- int fxrInt = fxr * 1e4;
- int fyrInt = fyr * 1e4;
+ qreal fxnorm = nxr * (fxr - 1.0) / (fxr - nxr);
+ qreal fynorm = nyr * (fyr - 1.0) / (fyr - nyr);
- if (fxr > 1.0 && (fxrInt >= fyrInt || fyr < 1.0)) {
- return 255 * nxr * (fxr - 1.0) / (fxr - nxr);
- }
+ qreal retValue = 0;
- if (fyr > 1.0 && (fyrInt > fxrInt || fxr < 1.0)) {
- return 255 * nyr * (fyr - 1.0) / (fyr - nyr);
- }
+ if(fxr > 1.0) {
+ retValue = fxnorm;
+ }
+
+ if (fxnorm < fynorm && fyr > 1.0) {
+ retValue = fynorm;
+ }
- return 0;
+ return retValue * 255;
}
diff --git a/libs/image/kis_selection_filters.cpp b/libs/image/kis_selection_filters.cpp
index 5c2a4ebacc..dd80a72161 100644
--- a/libs/image/kis_selection_filters.cpp
+++ b/libs/image/kis_selection_filters.cpp
@@ -1,875 +1,875 @@
/*
* Copyright (c) 2005 Michael Thaler
* 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_selection_filters.h"
#include <klocalizedstring.h>
#include <KoColorSpace.h>
#include "kis_convolution_painter.h"
#include "kis_convolution_kernel.h"
#include "kis_pixel_selection.h"
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define RINT(x) floor ((x) + 0.5)
KisSelectionFilter::~KisSelectionFilter()
{
}
KUndo2MagicString KisSelectionFilter::name()
{
return KUndo2MagicString();
}
QRect KisSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds)
{
Q_UNUSED(defaultBounds);
return rect;
}
void KisSelectionFilter::computeBorder(qint32* circ, qint32 xradius, qint32 yradius)
{
qint32 i;
qint32 diameter = xradius * 2 + 1;
double tmp;
for (i = 0; i < diameter; i++) {
if (i > xradius)
tmp = (i - xradius) - 0.5;
else if (i < xradius)
tmp = (xradius - i) - 0.5;
else
tmp = 0.0;
- double divisor = (double) xradius * sqrt(xradius * xradius - tmp * tmp);
+ double divisor = (double) xradius;
if (divisor == 0.0) {
divisor = 1.0;
}
- circ[i] = (qint32) RINT(yradius / divisor);
+ circ[i] = (qint32) RINT(yradius * sqrt(xradius * xradius - tmp * tmp) / divisor);
}
}
void KisSelectionFilter::rotatePointers(quint8** p, quint32 n)
{
quint32 i;
quint8 *p0 = p[0];
for (i = 0; i < n - 1; i++) {
p[i] = p[i + 1];
}
p[i] = p0;
}
void KisSelectionFilter::computeTransition(quint8* transition, quint8** buf, qint32 width)
{
qint32 x = 0;
if (width == 1) {
if (buf[1][x] > 127 && (buf[0][x] < 128 || buf[2][x] < 128))
transition[x] = 255;
else
transition[x] = 0;
return;
}
if (buf[1][x] > 127) {
if (buf[0][x] < 128 || buf[0][x + 1] < 128 ||
buf[1][x + 1] < 128 ||
buf[2][x] < 128 || buf[2][x + 1] < 128)
transition[x] = 255;
else
transition[x] = 0;
} else
transition[x] = 0;
for (qint32 x = 1; x < width - 1; x++) {
if (buf[1][x] >= 128) {
if (buf[0][x - 1] < 128 || buf[0][x] < 128 || buf[0][x + 1] < 128 ||
buf[1][x - 1] < 128 || buf[1][x + 1] < 128 ||
buf[2][x - 1] < 128 || buf[2][x] < 128 || buf[2][x + 1] < 128)
transition[x] = 255;
else
transition[x] = 0;
} else
transition[x] = 0;
}
if (buf[1][x] >= 128) {
if (buf[0][x - 1] < 128 || buf[0][x] < 128 ||
buf[1][x - 1] < 128 ||
buf[2][x - 1] < 128 || buf[2][x] < 128)
transition[x] = 255;
else
transition[x] = 0;
} else
transition[x] = 0;
}
KUndo2MagicString KisErodeSelectionFilter::name()
{
return kundo2_i18n("Erode Selection");
}
QRect KisErodeSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds)
{
Q_UNUSED(defaultBounds);
const qint32 radius = 1;
return rect.adjusted(-radius, -radius, radius, radius);
}
void KisErodeSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect)
{
// Erode (radius 1 pixel) a mask (1bpp)
quint8* buf[3];
qint32 width = rect.width();
qint32 height = rect.height();
quint8* out = new quint8[width];
for (qint32 i = 0; i < 3; i++)
buf[i] = new quint8[width + 2];
// load top of image
pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1);
buf[0][0] = buf[0][1];
buf[0][width + 1] = buf[0][width];
memcpy(buf[1], buf[0], width + 2);
for (qint32 y = 0; y < height; y++) {
if (y + 1 < height) {
pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1);
buf[2][0] = buf[2][1];
buf[2][width + 1] = buf[2][width];
} else {
memcpy(buf[2], buf[1], width + 2);
}
for (qint32 x = 0 ; x < width; x++) {
qint32 min = 255;
if (buf[0][x+1] < min) min = buf[0][x+1];
if (buf[1][x] < min) min = buf[1][x];
if (buf[1][x+1] < min) min = buf[1][x+1];
if (buf[1][x+2] < min) min = buf[1][x+2];
if (buf[2][x+1] < min) min = buf[2][x+1];
out[x] = min;
}
pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1);
rotatePointers(buf, 3);
}
for (qint32 i = 0; i < 3; i++)
delete[] buf[i];
delete[] out;
}
KUndo2MagicString KisDilateSelectionFilter::name()
{
return kundo2_i18n("Dilate Selection");
}
QRect KisDilateSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds)
{
Q_UNUSED(defaultBounds);
const qint32 radius = 1;
return rect.adjusted(-radius, -radius, radius, radius);
}
void KisDilateSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect)
{
// dilate (radius 1 pixel) a mask (1bpp)
quint8* buf[3];
qint32 width = rect.width();
qint32 height = rect.height();
quint8* out = new quint8[width];
for (qint32 i = 0; i < 3; i++)
buf[i] = new quint8[width + 2];
// load top of image
pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1);
buf[0][0] = buf[0][1];
buf[0][width + 1] = buf[0][width];
memcpy(buf[1], buf[0], width + 2);
for (qint32 y = 0; y < height; y++) {
if (y + 1 < height) {
pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1);
buf[2][0] = buf[2][1];
buf[2][width + 1] = buf[2][width];
} else {
memcpy(buf[2], buf[1], width + 2);
}
for (qint32 x = 0 ; x < width; x++) {
qint32 max = 0;
if (buf[0][x+1] > max) max = buf[0][x+1];
if (buf[1][x] > max) max = buf[1][x];
if (buf[1][x+1] > max) max = buf[1][x+1];
if (buf[1][x+2] > max) max = buf[1][x+2];
if (buf[2][x+1] > max) max = buf[2][x+1];
out[x] = max;
}
pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1);
rotatePointers(buf, 3);
}
for (qint32 i = 0; i < 3; i++)
delete[] buf[i];
delete[] out;
}
KisBorderSelectionFilter::KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius)
: m_xRadius(xRadius),
m_yRadius(yRadius)
{
}
KUndo2MagicString KisBorderSelectionFilter::name()
{
return kundo2_i18n("Border Selection");
}
QRect KisBorderSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds)
{
Q_UNUSED(defaultBounds);
return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius);
}
void KisBorderSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect)
{
if (m_xRadius <= 0 || m_yRadius <= 0) return;
quint8 *buf[3];
quint8 **density;
quint8 **transition;
if (m_xRadius == 1 && m_yRadius == 1) {
// optimize this case specifically
quint8* source[3];
for (qint32 i = 0; i < 3; i++)
source[i] = new quint8[rect.width()];
quint8* transition = new quint8[rect.width()];
pixelSelection->readBytes(source[0], rect.x(), rect.y(), rect.width(), 1);
memcpy(source[1], source[0], rect.width());
if (rect.height() > 1)
pixelSelection->readBytes(source[2], rect.x(), rect.y() + 1, rect.width(), 1);
else
memcpy(source[2], source[1], rect.width());
computeTransition(transition, source, rect.width());
pixelSelection->writeBytes(transition, rect.x(), rect.y(), rect.width(), 1);
for (qint32 y = 1; y < rect.height(); y++) {
rotatePointers(source, 3);
if (y + 1 < rect.height())
pixelSelection->readBytes(source[2], rect.x(), rect.y() + y + 1, rect.width(), 1);
else
memcpy(source[2], source[1], rect.width());
computeTransition(transition, source, rect.width());
pixelSelection->writeBytes(transition, rect.x(), rect.y() + y, rect.width(), 1);
}
for (qint32 i = 0; i < 3; i++)
delete[] source[i];
delete[] transition;
return;
}
qint32* max = new qint32[rect.width() + 2 * m_xRadius];
for (qint32 i = 0; i < (rect.width() + 2 * m_xRadius); i++)
max[i] = m_yRadius + 2;
max += m_xRadius;
for (qint32 i = 0; i < 3; i++)
buf[i] = new quint8[rect.width()];
transition = new quint8*[m_yRadius + 1];
for (qint32 i = 0; i < m_yRadius + 1; i++) {
transition[i] = new quint8[rect.width() + 2 * m_xRadius];
memset(transition[i], 0, rect.width() + 2 * m_xRadius);
transition[i] += m_xRadius;
}
quint8* out = new quint8[rect.width()];
density = new quint8*[2 * m_xRadius + 1];
density += m_xRadius;
for (qint32 x = 0; x < (m_xRadius + 1); x++) { // allocate density[][]
density[ x] = new quint8[2 * m_yRadius + 1];
density[ x] += m_yRadius;
density[-x] = density[x];
}
for (qint32 x = 0; x < (m_xRadius + 1); x++) { // compute density[][]
double tmpx, tmpy, dist;
quint8 a;
tmpx = x > 0.0 ? x - 0.5 : 0.0;
for (qint32 y = 0; y < (m_yRadius + 1); y++) {
tmpy = y > 0.0 ? y - 0.5 : 0.0;
dist = ((tmpy * tmpy) / (m_yRadius * m_yRadius) +
(tmpx * tmpx) / (m_xRadius * m_xRadius));
if (dist < 1.0)
a = (quint8)(255 * (1.0 - sqrt(dist)));
else
a = 0;
density[ x][ y] = a;
density[ x][-y] = a;
density[-x][ y] = a;
density[-x][-y] = a;
}
}
pixelSelection->readBytes(buf[0], rect.x(), rect.y(), rect.width(), 1);
memcpy(buf[1], buf[0], rect.width());
if (rect.height() > 1)
pixelSelection->readBytes(buf[2], rect.x(), rect.y() + 1, rect.width(), 1);
else
memcpy(buf[2], buf[1], rect.width());
computeTransition(transition[1], buf, rect.width());
for (qint32 y = 1; y < m_yRadius && y + 1 < rect.height(); y++) { // set up top of image
rotatePointers(buf, 3);
pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + 1, rect.width(), 1);
computeTransition(transition[y + 1], buf, rect.width());
}
for (qint32 x = 0; x < rect.width(); x++) { // set up max[] for top of image
max[x] = -(m_yRadius + 7);
for (qint32 j = 1; j < m_yRadius + 1; j++)
if (transition[j][x]) {
max[x] = j;
break;
}
}
for (qint32 y = 0; y < rect.height(); y++) { // main calculation loop
rotatePointers(buf, 3);
rotatePointers(transition, m_yRadius + 1);
if (y < rect.height() - (m_yRadius + 1)) {
pixelSelection->readBytes(buf[2], rect.x(), rect.y() + y + m_yRadius + 1, rect.width(), 1);
computeTransition(transition[m_yRadius], buf, rect.width());
} else
memcpy(transition[m_yRadius], transition[m_yRadius - 1], rect.width());
for (qint32 x = 0; x < rect.width(); x++) { // update max array
if (max[x] < 1) {
if (max[x] <= -m_yRadius) {
if (transition[m_yRadius][x])
max[x] = m_yRadius;
else
max[x]--;
} else if (transition[-max[x]][x])
max[x] = -max[x];
else if (transition[-max[x] + 1][x])
max[x] = -max[x] + 1;
else
max[x]--;
} else
max[x]--;
if (max[x] < -m_yRadius - 1)
max[x] = -m_yRadius - 1;
}
quint8 last_max = max[0][density[-1]];
qint32 last_index = 1;
for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line
last_index--;
if (last_index >= 0) {
last_max = 0;
for (qint32 i = m_xRadius; i >= 0; i--)
if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x+i]] > last_max) {
last_max = density[i][max[x + i]];
last_index = i;
}
out[x] = last_max;
} else {
last_max = 0;
for (qint32 i = m_xRadius; i >= -m_xRadius; i--)
if (max[x + i] <= m_yRadius && max[x + i] >= -m_yRadius && density[i][max[x + i]] > last_max) {
last_max = density[i][max[x + i]];
last_index = i;
}
out[x] = last_max;
}
if (last_max == 0) {
qint32 i;
for (i = x + 1; i < rect.width(); i++) {
if (max[i] >= -m_yRadius)
break;
}
if (i - x > m_xRadius) {
for (; x < i - m_xRadius; x++)
out[x] = 0;
x--;
}
last_index = m_xRadius;
}
}
pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1);
}
delete [] out;
for (qint32 i = 0; i < 3; i++)
delete[] buf[i];
max -= m_xRadius;
delete[] max;
for (qint32 i = 0; i < m_yRadius + 1; i++) {
transition[i] -= m_xRadius;
delete transition[i];
}
delete[] transition;
for (qint32 i = 0; i < m_xRadius + 1 ; i++) {
density[i] -= m_yRadius;
delete density[i];
}
density -= m_xRadius;
delete[] density;
}
KisFeatherSelectionFilter::KisFeatherSelectionFilter(qint32 radius)
: m_radius(radius)
{
}
KUndo2MagicString KisFeatherSelectionFilter::name()
{
return kundo2_i18n("Feather Selection");
}
QRect KisFeatherSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds)
{
Q_UNUSED(defaultBounds);
return rect.adjusted(-m_radius, -m_radius,
m_radius, m_radius);
}
void KisFeatherSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect)
{
// compute horizontal kernel
const uint kernelSize = m_radius * 2 + 1;
Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> gaussianMatrix(1, kernelSize);
const qreal multiplicand = 1.0 / (2.0 * M_PI * m_radius * m_radius);
const qreal exponentMultiplicand = 1.0 / (2.0 * m_radius * m_radius);
for (uint x = 0; x < kernelSize; x++) {
uint xDistance = qAbs((int)m_radius - (int)x);
gaussianMatrix(0, x) = multiplicand * exp( -(qreal)((xDistance * xDistance) + (m_radius * m_radius)) * exponentMultiplicand );
}
KisConvolutionKernelSP kernelHoriz = KisConvolutionKernel::fromMatrix(gaussianMatrix, 0, gaussianMatrix.sum());
KisConvolutionKernelSP kernelVertical = KisConvolutionKernel::fromMatrix(gaussianMatrix.transpose(), 0, gaussianMatrix.sum());
KisPaintDeviceSP interm = new KisPaintDevice(pixelSelection->colorSpace());
interm->prepareClone(pixelSelection);
KisConvolutionPainter horizPainter(interm);
horizPainter.setChannelFlags(interm->colorSpace()->channelFlags(false, true));
horizPainter.applyMatrix(kernelHoriz, pixelSelection, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT);
horizPainter.end();
KisConvolutionPainter verticalPainter(pixelSelection);
verticalPainter.setChannelFlags(pixelSelection->colorSpace()->channelFlags(false, true));
verticalPainter.applyMatrix(kernelVertical, interm, rect.topLeft(), rect.topLeft(), rect.size(), BORDER_REPEAT);
verticalPainter.end();
}
KisGrowSelectionFilter::KisGrowSelectionFilter(qint32 xRadius, qint32 yRadius)
: m_xRadius(xRadius),
m_yRadius(yRadius)
{
}
KUndo2MagicString KisGrowSelectionFilter::name()
{
return kundo2_i18n("Grow Selection");
}
QRect KisGrowSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds)
{
Q_UNUSED(defaultBounds);
return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius);
}
void KisGrowSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect)
{
if (m_xRadius <= 0 || m_yRadius <= 0) return;
/**
* Much code resembles Shrink filter, so please fix bugs
* in both filters
*/
quint8 **buf; // caches the region's pixel data
quint8 **max; // caches the largest values for each column
max = new quint8* [rect.width() + 2 * m_xRadius];
buf = new quint8* [m_yRadius + 1];
for (qint32 i = 0; i < m_yRadius + 1; i++) {
buf[i] = new quint8[rect.width()];
}
quint8* buffer = new quint8[(rect.width() + 2 * m_xRadius) *(m_yRadius + 1)];
for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) {
if (i < m_xRadius)
max[i] = buffer;
else if (i < rect.width() + m_xRadius)
max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)];
else
max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)];
for (qint32 j = 0; j < m_xRadius + 1; j++)
max[i][j] = 0;
}
/* offset the max pointer by m_xRadius so the range of the array
is [-m_xRadius] to [region->w + m_xRadius] */
max += m_xRadius;
quint8* out = new quint8[ rect.width()]; // holds the new scan line we are computing
qint32* circ = new qint32[ 2 * m_xRadius + 1 ]; // holds the y coords of the filter's mask
computeBorder(circ, m_xRadius, m_yRadius);
/* offset the circ pointer by m_xRadius so the range of the array
is [-m_xRadius] to [m_xRadius] */
circ += m_xRadius;
memset(buf[0], 0, rect.width());
for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) { // load top of image
pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1);
}
for (qint32 x = 0; x < rect.width() ; x++) { // set up max for top of image
max[x][0] = 0; // buf[0][x] is always 0
max[x][1] = buf[1][x]; // MAX (buf[1][x], max[x][0]) always = buf[1][x]
for (qint32 j = 2; j < m_yRadius + 1; j++) {
max[x][j] = MAX(buf[j][x], max[x][j-1]);
}
}
for (qint32 y = 0; y < rect.height(); y++) {
rotatePointers(buf, m_yRadius + 1);
if (y < rect.height() - (m_yRadius))
pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1);
else
memset(buf[m_yRadius], 0, rect.width());
for (qint32 x = 0; x < rect.width(); x++) { /* update max array */
for (qint32 i = m_yRadius; i > 0; i--) {
max[x][i] = MAX(MAX(max[x][i - 1], buf[i - 1][x]), buf[i][x]);
}
max[x][0] = buf[0][x];
}
qint32 last_max = max[0][circ[-1]];
qint32 last_index = 1;
for (qint32 x = 0; x < rect.width(); x++) { /* render scan line */
last_index--;
if (last_index >= 0) {
if (last_max == 255)
out[x] = 255;
else {
last_max = 0;
for (qint32 i = m_xRadius; i >= 0; i--)
if (last_max < max[x + i][circ[i]]) {
last_max = max[x + i][circ[i]];
last_index = i;
}
out[x] = last_max;
}
} else {
last_index = m_xRadius;
last_max = max[x + m_xRadius][circ[m_xRadius]];
for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--)
if (last_max < max[x + i][circ[i]]) {
last_max = max[x + i][circ[i]];
last_index = i;
}
out[x] = last_max;
}
}
pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1);
}
/* undo the offsets to the pointers so we can free the malloced memory */
circ -= m_xRadius;
max -= m_xRadius;
delete[] circ;
delete[] buffer;
delete[] max;
for (qint32 i = 0; i < m_yRadius + 1; i++)
delete[] buf[i];
delete[] buf;
delete[] out;
}
KisShrinkSelectionFilter::KisShrinkSelectionFilter(qint32 xRadius, qint32 yRadius, bool edgeLock)
: m_xRadius(xRadius),
m_yRadius(yRadius),
m_edgeLock(edgeLock)
{
}
KUndo2MagicString KisShrinkSelectionFilter::name()
{
return kundo2_i18n("Shrink Selection");
}
QRect KisShrinkSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds)
{
Q_UNUSED(defaultBounds);
return rect.adjusted(-m_xRadius, -m_yRadius, m_xRadius, m_yRadius);
}
void KisShrinkSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect)
{
if (m_xRadius <= 0 || m_yRadius <= 0) return;
/*
pretty much the same as fatten_region only different
blame all bugs in this function on jaycox@gimp.org
*/
/* If edge_lock is true we assume that pixels outside the region
we are passed are identical to the edge pixels.
If edge_lock is false, we assume that pixels outside the region are 0
*/
quint8 **buf; // caches the region's pixels
quint8 **max; // caches the smallest values for each column
qint32 last_max, last_index;
max = new quint8* [rect.width() + 2 * m_xRadius];
buf = new quint8* [m_yRadius + 1];
for (qint32 i = 0; i < m_yRadius + 1; i++) {
buf[i] = new quint8[rect.width()];
}
qint32 buffer_size = (rect.width() + 2 * m_xRadius + 1) * (m_yRadius + 1);
quint8* buffer = new quint8[buffer_size];
if (m_edgeLock)
memset(buffer, 255, buffer_size);
else
memset(buffer, 0, buffer_size);
for (qint32 i = 0; i < rect.width() + 2 * m_xRadius; i++) {
if (i < m_xRadius)
if (m_edgeLock)
max[i] = buffer;
else
max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)];
else if (i < rect.width() + m_xRadius)
max[i] = &buffer[(m_yRadius + 1) * (i - m_xRadius)];
else if (m_edgeLock)
max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius - 1)];
else
max[i] = &buffer[(m_yRadius + 1) * (rect.width() + m_xRadius)];
}
if (!m_edgeLock)
for (qint32 j = 0 ; j < m_xRadius + 1; j++) max[0][j] = 0;
// offset the max pointer by m_xRadius so the range of the array is [-m_xRadius] to [region->w + m_xRadius]
max += m_xRadius;
quint8* out = new quint8[rect.width()]; // holds the new scan line we are computing
qint32* circ = new qint32[2 * m_xRadius + 1]; // holds the y coords of the filter's mask
computeBorder(circ, m_xRadius, m_yRadius);
// offset the circ pointer by m_xRadius so the range of the array is [-m_xRadius] to [m_xRadius]
circ += m_xRadius;
for (qint32 i = 0; i < m_yRadius && i < rect.height(); i++) // load top of image
pixelSelection->readBytes(buf[i + 1], rect.x(), rect.y() + i, rect.width(), 1);
if (m_edgeLock)
memcpy(buf[0], buf[1], rect.width());
else
memset(buf[0], 0, rect.width());
for (qint32 x = 0; x < rect.width(); x++) { // set up max for top of image
max[x][0] = buf[0][x];
for (qint32 j = 1; j < m_yRadius + 1; j++)
max[x][j] = MIN(buf[j][x], max[x][j-1]);
}
for (qint32 y = 0; y < rect.height(); y++) {
rotatePointers(buf, m_yRadius + 1);
if (y < rect.height() - m_yRadius)
pixelSelection->readBytes(buf[m_yRadius], rect.x(), rect.y() + y + m_yRadius, rect.width(), 1);
else if (m_edgeLock)
memcpy(buf[m_yRadius], buf[m_yRadius - 1], rect.width());
else
memset(buf[m_yRadius], 0, rect.width());
for (qint32 x = 0 ; x < rect.width(); x++) { // update max array
for (qint32 i = m_yRadius; i > 0; i--) {
max[x][i] = MIN(MIN(max[x][i - 1], buf[i - 1][x]), buf[i][x]);
}
max[x][0] = buf[0][x];
}
last_max = max[0][circ[-1]];
last_index = 0;
for (qint32 x = 0 ; x < rect.width(); x++) { // render scan line
last_index--;
if (last_index >= 0) {
if (last_max == 0)
out[x] = 0;
else {
last_max = 255;
for (qint32 i = m_xRadius; i >= 0; i--)
if (last_max > max[x + i][circ[i]]) {
last_max = max[x + i][circ[i]];
last_index = i;
}
out[x] = last_max;
}
} else {
last_index = m_xRadius;
last_max = max[x + m_xRadius][circ[m_xRadius]];
for (qint32 i = m_xRadius - 1; i >= -m_xRadius; i--)
if (last_max > max[x + i][circ[i]]) {
last_max = max[x + i][circ[i]];
last_index = i;
}
out[x] = last_max;
}
}
pixelSelection->writeBytes(out, rect.x(), rect.y() + y, rect.width(), 1);
}
// undo the offsets to the pointers so we can free the malloced memory
circ -= m_xRadius;
max -= m_xRadius;
delete[] circ;
delete[] buffer;
delete[] max;
for (qint32 i = 0; i < m_yRadius + 1; i++)
delete[] buf[i];
delete[] buf;
delete[] out;
}
KUndo2MagicString KisSmoothSelectionFilter::name()
{
return kundo2_i18n("Smooth Selection");
}
QRect KisSmoothSelectionFilter::changeRect(const QRect& rect, KisDefaultBoundsBaseSP defaultBounds)
{
Q_UNUSED(defaultBounds);
const qint32 radius = 1;
return rect.adjusted(-radius, -radius, radius, radius);
}
void KisSmoothSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect)
{
// Simple convolution filter to smooth a mask (1bpp)
quint8 *buf[3];
qint32 width = rect.width();
qint32 height = rect.height();
quint8* out = new quint8[width];
for (qint32 i = 0; i < 3; i++)
buf[i] = new quint8[width + 2];
// load top of image
pixelSelection->readBytes(buf[0] + 1, rect.x(), rect.y(), width, 1);
buf[0][0] = buf[0][1];
buf[0][width + 1] = buf[0][width];
memcpy(buf[1], buf[0], width + 2);
for (qint32 y = 0; y < height; y++) {
if (y + 1 < height) {
pixelSelection->readBytes(buf[2] + 1, rect.x(), rect.y() + y + 1, width, 1);
buf[2][0] = buf[2][1];
buf[2][width + 1] = buf[2][width];
} else {
memcpy(buf[2], buf[1], width + 2);
}
for (qint32 x = 0 ; x < width; x++) {
qint32 value = (buf[0][x] + buf[0][x+1] + buf[0][x+2] +
buf[1][x] + buf[2][x+1] + buf[1][x+2] +
buf[2][x] + buf[1][x+1] + buf[2][x+2]);
out[x] = value / 9;
}
pixelSelection->writeBytes(out, rect.x(), rect.y() + y, width, 1);
rotatePointers(buf, 3);
}
for (qint32 i = 0; i < 3; i++)
delete[] buf[i];
delete[] out;
}
KUndo2MagicString KisInvertSelectionFilter::name()
{
return kundo2_i18n("Invert Selection");
}
QRect KisInvertSelectionFilter::changeRect(const QRect &rect, KisDefaultBoundsBaseSP defaultBounds)
{
Q_UNUSED(rect);
return defaultBounds->bounds();
}
void KisInvertSelectionFilter::process(KisPixelSelectionSP pixelSelection, const QRect& rect)
{
Q_UNUSED(rect);
pixelSelection->invert();
}
diff --git a/libs/image/kis_stroke.cpp b/libs/image/kis_stroke.cpp
index 3563333d6a..47fbdbc4a8 100644
--- a/libs/image/kis_stroke.cpp
+++ b/libs/image/kis_stroke.cpp
@@ -1,336 +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_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);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(!m_strokeEnded);
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_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()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!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();
}
qreal KisStroke::balancingRatioOverride() const
{
return m_strokeStrategy->balancingRatioOverride();
}
KisStrokeJobData::Sequentiality KisStroke::nextJobSequentiality() const
{
return !m_jobsQueue.isEmpty() ?
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 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(), 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_strategy.cpp b/libs/image/kis_stroke_strategy.cpp
index cec29bd135..aa75ac8161 100644
--- a/libs/image/kis_stroke_strategy.cpp
+++ b/libs/image/kis_stroke_strategy.cpp
@@ -1,226 +1,227 @@
/*
* 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(const QLatin1String &id, const KUndo2MagicString &name)
: m_exclusive(false),
m_supportsWrapAroundMode(false),
m_clearsRedoOnStart(true),
m_requestsOtherStrokesToEnd(true),
m_canForgetAboutMe(false),
m_needsExplicitCancel(false),
m_balancingRatioOverride(-1.0),
m_id(id),
m_name(name),
m_mutatedJobsInterface(0)
{
}
KisStrokeStrategy::KisStrokeStrategy(const KisStrokeStrategy &rhs)
: m_exclusive(rhs.m_exclusive),
m_supportsWrapAroundMode(rhs.m_supportsWrapAroundMode),
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_mutatedJobsInterface(0)
{
- KIS_ASSERT_RECOVER_NOOP(!rhs.m_cancelStrokeId && !m_mutatedJobsInterface &&
+ KIS_ASSERT_RECOVER_NOOP(!rhs.m_strokeId && !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;
}
QString KisStrokeStrategy::id() const
{
return m_id;
}
KUndo2MagicString KisStrokeStrategy::name() const
{
return m_name;
}
-void KisStrokeStrategy::setMutatedJobsInterface(KisStrokesQueueMutatedJobInterface *mutatedJobsInterface)
+void KisStrokeStrategy::setMutatedJobsInterface(KisStrokesQueueMutatedJobInterface *mutatedJobsInterface, KisStrokeId strokeId)
{
m_mutatedJobsInterface = mutatedJobsInterface;
+ m_strokeId = strokeId;
}
void KisStrokeStrategy::addMutatedJobs(const QVector<KisStrokeJobData *> list)
{
- KIS_SAFE_ASSERT_RECOVER(m_mutatedJobsInterface && m_cancelStrokeId) {
+ KIS_SAFE_ASSERT_RECOVER(m_mutatedJobsInterface && m_strokeId) {
qDeleteAll(list);
return;
}
- m_mutatedJobsInterface->addMutatedJobs(m_cancelStrokeId, list);
+ m_mutatedJobsInterface->addMutatedJobs(m_strokeId, list);
}
void KisStrokeStrategy::addMutatedJob(KisStrokeJobData *data)
{
addMutatedJobs({data});
}
void KisStrokeStrategy::setExclusive(bool value)
{
m_exclusive = value;
}
void KisStrokeStrategy::setSupportsWrapAroundMode(bool value)
{
m_supportsWrapAroundMode = value;
}
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 b5dce84c68..dd21d269ed 100644
--- a/libs/image/kis_stroke_strategy.h
+++ b/libs/image/kis_stroke_strategy.h
@@ -1,209 +1,194 @@
/*
* 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(const QLatin1String &id, 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;
/**
* 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);
+ void setMutatedJobsInterface(KisStrokesQueueMutatedJobInterface *mutatedJobsInterface, KisStrokeId strokeId);
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 execution of 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 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_clearsRedoOnStart;
bool m_requestsOtherStrokesToEnd;
bool m_canForgetAboutMe;
bool m_needsExplicitCancel;
qreal m_balancingRatioOverride;
QLatin1String m_id;
KUndo2MagicString m_name;
- KisStrokeId m_cancelStrokeId;
+ KisStrokeId m_strokeId;
KisStrokesQueueMutatedJobInterface *m_mutatedJobsInterface;
};
#endif /* __KIS_STROKE_STRATEGY_H */
diff --git a/libs/image/kis_stroke_strategy_undo_command_based.cpp b/libs/image/kis_stroke_strategy_undo_command_based.cpp
index 32ef12c95c..cae7a750dd 100644
--- a/libs/image/kis_stroke_strategy_undo_command_based.cpp
+++ b/libs/image/kis_stroke_strategy_undo_command_based.cpp
@@ -1,189 +1,192 @@
/*
* 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_undo_command_based.h"
#include <QMutexLocker>
#include "kis_image_interfaces.h"
#include "kis_post_execution_undo_adapter.h"
#include "commands_new/kis_saved_commands.h"
KisStrokeStrategyUndoCommandBased::
KisStrokeStrategyUndoCommandBased(const KUndo2MagicString &name,
bool undo,
KisStrokeUndoFacade *undoFacade,
KUndo2CommandSP initCommand,
KUndo2CommandSP finishCommand)
: KisRunnableBasedStrokeStrategy(QLatin1String("STROKE_UNDO_COMMAND_BASED"), name),
m_undo(undo),
m_initCommand(initCommand),
m_finishCommand(finishCommand),
m_undoFacade(undoFacade),
m_macroId(-1),
m_macroCommand(0)
{
enableJob(KisSimpleStrokeStrategy::JOB_INIT);
enableJob(KisSimpleStrokeStrategy::JOB_FINISH);
enableJob(KisSimpleStrokeStrategy::JOB_CANCEL);
enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE);
}
KisStrokeStrategyUndoCommandBased::
KisStrokeStrategyUndoCommandBased(const KisStrokeStrategyUndoCommandBased &rhs)
: KisRunnableBasedStrokeStrategy(rhs),
m_undo(false),
m_initCommand(rhs.m_initCommand),
m_finishCommand(rhs.m_finishCommand),
m_undoFacade(rhs.m_undoFacade),
m_macroCommand(0)
{
KIS_ASSERT_RECOVER_NOOP(!rhs.m_macroCommand &&
!rhs.m_undo &&
"After the stroke has been started, no copying must happen");
}
void KisStrokeStrategyUndoCommandBased::setUsedWhileUndoRedo(bool value)
{
setClearsRedoOnStart(!value);
}
void KisStrokeStrategyUndoCommandBased::executeCommand(KUndo2CommandSP command, bool undo)
{
if(!command) return;
if (MutatedCommandInterface *mutatedCommand = dynamic_cast<MutatedCommandInterface*>(command.data())) {
mutatedCommand->setRunnableJobsInterface(this->runnableJobsInterface());
}
if(undo) {
command->undo();
} else {
command->redo();
}
}
void KisStrokeStrategyUndoCommandBased::initStrokeCallback()
{
if(m_undoFacade) {
m_macroCommand = m_undoFacade->postExecutionUndoAdapter()->createMacro(name());
}
executeCommand(m_initCommand, m_undo);
notifyCommandDone(m_initCommand,
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
}
void KisStrokeStrategyUndoCommandBased::finishStrokeCallback()
{
executeCommand(m_finishCommand, m_undo);
notifyCommandDone(m_finishCommand,
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
QMutexLocker locker(&m_mutex);
if(m_macroCommand) {
Q_ASSERT(m_undoFacade);
postProcessToplevelCommand(m_macroCommand);
m_undoFacade->postExecutionUndoAdapter()->addMacro(m_macroCommand);
m_macroCommand = 0;
}
}
void KisStrokeStrategyUndoCommandBased::cancelStrokeCallback()
{
QMutexLocker locker(&m_mutex);
if(m_macroCommand) {
- m_macroCommand->performCancel(cancelStrokeId(), m_undo);
+ QVector<KisStrokeJobData *> jobs;
+ m_macroCommand->getCommandExecutionJobs(&jobs, !m_undo);
+ addMutatedJobs(jobs);
+
delete m_macroCommand;
m_macroCommand = 0;
}
}
void KisStrokeStrategyUndoCommandBased::doStrokeCallback(KisStrokeJobData *data)
{
Data *d = dynamic_cast<Data*>(data);
if (d) {
executeCommand(d->command, d->undo);
if (d->shouldGoToHistory) {
notifyCommandDone(d->command,
d->sequentiality(),
d->exclusivity());
}
} else {
KisRunnableBasedStrokeStrategy::doStrokeCallback(data);
}
}
void KisStrokeStrategyUndoCommandBased::runAndSaveCommand(KUndo2CommandSP command,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity)
{
if (!command) return;
executeCommand(command, false);
notifyCommandDone(command, sequentiality, exclusivity);
}
void KisStrokeStrategyUndoCommandBased::notifyCommandDone(KUndo2CommandSP command,
KisStrokeJobData::Sequentiality sequentiality,
KisStrokeJobData::Exclusivity exclusivity)
{
if(!command) return;
QMutexLocker locker(&m_mutex);
if(m_macroCommand) {
m_macroCommand->addCommand(command, sequentiality, exclusivity);
}
}
void KisStrokeStrategyUndoCommandBased::setCommandExtraData(KUndo2CommandExtraData *data)
{
if (m_undoFacade && m_macroCommand) {
warnKrita << "WARNING: KisStrokeStrategyUndoCommandBased::setCommandExtraData():"
<< "the extra data is set while the stroke has already been started!"
<< "The result is undefined, continued actions may not work!";
}
m_commandExtraData.reset(data);
}
void KisStrokeStrategyUndoCommandBased::setMacroId(int value)
{
m_macroId = value;
}
void KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(KUndo2Command *command)
{
if (m_commandExtraData) {
command->setExtraData(m_commandExtraData.take());
}
KisSavedMacroCommand *savedCommand = dynamic_cast<KisSavedMacroCommand*>(command);
if (savedCommand) {
savedCommand->setMacroId(m_macroId);
}
}
KisStrokeUndoFacade* KisStrokeStrategyUndoCommandBased::undoFacade() const
{
return m_undoFacade;
}
diff --git a/libs/image/kis_strokes_queue.cpp b/libs/image/kis_strokes_queue.cpp
index f7e08484ff..51ec106387 100644
--- a/libs/image/kis_strokes_queue.cpp
+++ b/libs/image/kis_strokes_queue.cpp
@@ -1,814 +1,810 @@
/*
* 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, KisStrokesQueueMutatedJobInterface *mutatedJobsInterface) {
KisStrokeStrategy *strategy = pair.first;
QList<KisStrokeJobData*> jobsData = pair.second;
KisStrokeSP stroke(new KisStroke(strategy, type, levelOfDetail));
- strategy->setCancelStrokeId(stroke);
- strategy->setMutatedJobsInterface(mutatedJobsInterface);
+ strategy->setMutatedJobsInterface(mutatedJobsInterface, stroke);
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, 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) {
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) {
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::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);
+ strokeStrategy->setMutatedJobsInterface(this, buddy);
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);
+ lodBuddyStrategy->setMutatedJobsInterface(this, buddy);
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, this);
it = m_d->strokesQueue.insert(it, stroke);
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);
+ strokeStrategy->setMutatedJobsInterface(this, id);
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();
KIS_SAFE_ASSERT_RECOVER_RETURN(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();
KIS_SAFE_ASSERT_RECOVER_RETURN(stroke);
stroke->addMutatedJobs(list);
}
void KisStrokesQueue::endStroke(KisStrokeId id)
{
QMutexLocker locker(&m_d->mutex);
KisStrokeSP stroke = id.toStrongRef();
KIS_SAFE_ASSERT_RECOVER_RETURN(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;
const int levelOfDetail = updaterContext.currentLevelOfDetail();
const KisUpdaterContextSnapshotEx snapshot = updaterContext.getContextSnapshotEx();
const bool hasStrokeJobs = !(snapshot == ContextEmpty ||
snapshot == HasMergeJob);
const bool hasMergeJobs = snapshot & HasMergeJob;
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(bool hasMergeJobs,
bool hasStrokeJobs)
{
Q_UNUSED(hasStrokeJobs);
if(!m_d->strokesQueue.head()->isExclusive()) return true;
return hasMergeJobs == 0;
}
bool KisStrokesQueue::checkSequentialProperty(KisUpdaterContextSnapshotEx snapshot,
bool externalJobsPending)
{
KisStrokeSP stroke = m_d->strokesQueue.head();
if (snapshot & HasSequentialJob ||
snapshot & HasBarrierJob) {
return false;
}
KisStrokeJobData::Sequentiality nextSequentiality =
stroke->nextJobSequentiality();
if (nextSequentiality == KisStrokeJobData::UNIQUELY_CONCURRENT &&
snapshot & HasUniquelyConcurrentJob) {
return false;
}
if (nextSequentiality == KisStrokeJobData::SEQUENTIAL &&
(snapshot & HasUniquelyConcurrentJob ||
snapshot & HasConcurrentJob)) {
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_update_job_item.h b/libs/image/kis_update_job_item.h
index 3a6f41b27c..fe4dce18d5 100644
--- a/libs/image/kis_update_job_item.h
+++ b/libs/image/kis_update_job_item.h
@@ -1,271 +1,265 @@
/*
* 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"
#include "kis_updater_context.h"
//#define DEBUG_JOBS_SEQUENCE
class KisUpdateJobItem : public QObject, public QRunnable
{
Q_OBJECT
public:
enum class Type : int {
EMPTY = 0,
WAITING,
MERGE,
STROKE,
SPONTANEOUS
};
public:
KisUpdateJobItem(KisUpdaterContext *updaterContext)
- : m_updaterContext(updaterContext),
- m_atomicType(Type::EMPTY),
- m_runnableJob(0)
+ : m_updaterContext(updaterContext)
{
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 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 (1) {
KIS_SAFE_ASSERT_RECOVER_RETURN(isRunning());
if(m_exclusive) {
m_updaterContext->m_exclusiveJobLock.lockForWrite();
} else {
m_updaterContext->m_exclusiveJobLock.lockForRead();
}
if(m_atomicType == Type::MERGE) {
runMergeJob();
} else {
KIS_ASSERT(m_atomicType == Type::STROKE ||
m_atomicType == Type::SPONTANEOUS);
if (m_runnableJob) {
#ifdef DEBUG_JOBS_SEQUENCE
if (m_atomicType == Type::STROKE) {
qDebug() << "running: stroke" << m_runnableJob->debugName();
} else if (m_atomicType == Type::SPONTANEOUS) {
qDebug() << "running: spont " << m_runnableJob->debugName();
} else {
qDebug() << "running: unkn. " << m_runnableJob->debugName();
}
#endif
m_runnableJob->run();
}
}
setDone();
m_updaterContext->doSomeUsefulWork();
// may flip the current state from Waiting -> Running again
m_updaterContext->jobFinished();
m_updaterContext->m_exclusiveJobLock.unlock();
// try to exit the loop. Please note, that no one 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() {
KIS_SAFE_ASSERT_RECOVER_RETURN(m_atomicType == Type::MERGE);
KIS_SAFE_ASSERT_RECOVER_RETURN(m_walker);
// dbgKrita << "Executing merge job" << m_walker->changeRect()
// << "on thread" << QThread::currentThreadId();
#ifdef DEBUG_JOBS_SEQUENCE
qDebug() << "running: merge " << m_walker->startNode() << m_walker->changeRect();
#endif
m_merger.startMerge(*m_walker);
QRect changeRect = m_walker->changeRect();
m_updaterContext->continueUpdate(changeRect);
}
// 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;
}
// 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;
}
// 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 = spontaneousJob->isExclusive();
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_atomicType = Type::WAITING;
}
inline bool isRunning() const {
return m_atomicType >= Type::MERGE;
}
inline Type type() const {
return m_atomicType;
}
inline const QRect& accessRect() const {
return m_accessRect;
}
inline const QRect& changeRect() const {
return m_changeRect;
}
inline KisStrokeJobData::Sequentiality strokeJobSequentiality() const {
return m_strokeJobSequentiality;
}
private:
/**
* Open walker and stroke job for the testing suite.
* Please, do not use it in production code.
*/
friend class KisTestableUpdaterContext;
friend class KisSimpleUpdateQueueTest;
friend class KisStrokesQueueTest;
friend class KisUpdateSchedulerTest;
friend class KisUpdaterContext;
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() {
setDone();
}
private:
- KisUpdaterContext *m_updaterContext;
-
- bool m_exclusive;
-
- std::atomic<Type> m_atomicType;
-
+ KisUpdaterContext *m_updaterContext {0};
+ bool m_exclusive {false};
+ std::atomic<Type> m_atomicType {Type::EMPTY};
volatile KisStrokeJobData::Sequentiality m_strokeJobSequentiality;
/**
* Runnable jobs part
* The job is owned by the context and deleted after completion
*/
- KisRunnableWithDebugName *m_runnableJob;
+ KisRunnableWithDebugName *m_runnableJob {0};
/**
* 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;
};
#endif /* __KIS_UPDATE_JOB_ITEM_H */
diff --git a/libs/image/krita_utils.cpp b/libs/image/krita_utils.cpp
index 98219152c3..7cd91d3542 100644
--- a/libs/image/krita_utils.cpp
+++ b/libs/image/krita_utils.cpp
@@ -1,518 +1,533 @@
/*
* 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(true);
return QSize(cfg.updatePatchWidth(),
cfg.updatePatchHeight());
}
QVector<QRect> splitRectIntoPatches(const QRect &rc, const QSize &patchSize)
{
using namespace KisAlgebra2D;
QVector<QRect> patches;
const qint32 firstCol = divideFloor(rc.x(), patchSize.width());
const qint32 firstRow = divideFloor(rc.y(), patchSize.height());
// TODO: check if -1 is needed here
const qint32 lastCol = divideFloor(rc.x() + rc.width(), patchSize.width());
const qint32 lastRow = divideFloor(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> splitRectIntoPatchesTight(const QRect &rc, const QSize &patchSize)
+ {
+ QVector<QRect> patches;
+
+ for (qint32 y = rc.y(); y < rc.y() + rc.height(); y += patchSize.height()) {
+ for (qint32 x = rc.x(); x < rc.x() + rc.width(); x += patchSize.width()) {
+ patches << QRect(x, y,
+ qMin(rc.x() + rc.width() - x, patchSize.width()),
+ qMin(rc.y() + rc.height() - y, patchSize.height()));
+ }
+ }
+
+ 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 QLocale().toString(value, '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 y = 0; y < size.height(); ++y) {
for(int x = 0; x < size.width(); ++x) {
const QRgb pixel = image.pixel(x,y);
const int gray = qGray(pixel);
dstImage.setPixel(x, y, 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);
while (dstIt.nextPixel()) {
const quint8 *dstPtr = dstIt.rawDataConst();
func(*dstPtr);
}
}
void filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<quint8(quint8)> func) {
KisSequentialIterator dstIt(dev, rc);
while (dstIt.nextPixel()) {
quint8 *dstPtr = dstIt.rawData();
*dstPtr = func(*dstPtr);
}
}
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);
}
}
void mirrorPoint(Qt::Orientation dir, const QPoint &center, QPointF *pt)
{
if (dir == Qt::Horizontal) {
pt->rx() = -(pt->x() - qreal(center.x())) + center.x();
} else /* if (dir == Qt::Vertical) */ {
pt->ry() = -(pt->y() - qreal(center.y())) + center.y();
}
}
qreal colorDifference(const QColor &c1, const QColor &c2)
{
const qreal dr = c1.redF() - c2.redF();
const qreal dg = c1.greenF() - c2.greenF();
const qreal db = c1.blueF() - c2.blueF();
return std::sqrt(2 * pow2(dr) + 4 * pow2(dg) + 3 * pow2(db));
}
void dragColor(QColor *color, const QColor &baseColor, qreal threshold)
{
while (colorDifference(*color, baseColor) < threshold) {
QColor newColor = *color;
if (newColor.lightnessF() > baseColor.lightnessF()) {
newColor = newColor.lighter(120);
} else {
newColor = newColor.darker(120);
}
if (newColor == *color) {
break;
}
*color = newColor;
}
}
}
diff --git a/libs/image/krita_utils.h b/libs/image/krita_utils.h
index abb6854366..a3282cd562 100644
--- a/libs/image/krita_utils.h
+++ b/libs/image/krita_utils.h
@@ -1,120 +1,121 @@
/*
* 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;
struct 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 splitRectIntoPatchesTight(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);
/**
* \return an approximate difference between \p c1 and \p c2
* in a (nonlinear) range [0, 3]
*
* The colors are compared using the formula:
* difference = sqrt(2 * diff_R^2 + 4 * diff_G^2 + 3 * diff_B^2)
*/
qreal KRITAIMAGE_EXPORT colorDifference(const QColor &c1, const QColor &c2);
/**
* Make the color \p color differ from \p baseColor for at least \p threshold value
*/
void KRITAIMAGE_EXPORT dragColor(QColor *color, const QColor &baseColor, qreal threshold);
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);
void KRITAIMAGE_EXPORT mirrorPoint(Qt::Orientation dir, const QPoint &center, QPointF *pt);
}
#endif /* __KRITA_UTILS_H */
diff --git a/libs/image/layerstyles/kis_ls_utils.cpp b/libs/image/layerstyles/kis_ls_utils.cpp
index 93b606ef05..4422016f11 100644
--- a/libs/image/layerstyles/kis_ls_utils.cpp
+++ b/libs/image/layerstyles/kis_ls_utils.cpp
@@ -1,591 +1,592 @@
/*
* 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_ls_utils.h"
#include <resources/KoAbstractGradient.h>
#include <KoColorSpace.h>
#include <resources/KoPattern.h>
#include "psd.h"
#include "kis_default_bounds.h"
#include "kis_pixel_selection.h"
#include "kis_random_accessor_ng.h"
#include "kis_iterator_ng.h"
#include "kis_convolution_kernel.h"
#include "kis_convolution_painter.h"
#include "kis_gaussian_kernel.h"
#include "kis_fill_painter.h"
#include "kis_gradient_painter.h"
#include "kis_layer_style_filter_environment.h"
#include "kis_selection_filters.h"
#include "kis_multiple_projection.h"
#include "kis_default_bounds_base.h"
#include "kis_cached_paint_device.h"
namespace KisLsUtils
{
QRect growSelectionUniform(KisPixelSelectionSP selection, int growSize, const QRect &applyRect)
{
QRect changeRect = applyRect;
if (growSize > 0) {
KisGrowSelectionFilter filter(growSize, growSize);
changeRect = filter.changeRect(applyRect, selection->defaultBounds());
filter.process(selection, applyRect);
} else if (growSize < 0) {
KisShrinkSelectionFilter filter(qAbs(growSize), qAbs(growSize), false);
changeRect = filter.changeRect(applyRect, selection->defaultBounds());
filter.process(selection, applyRect);
}
return changeRect;
}
void selectionFromAlphaChannel(KisPaintDeviceSP srcDevice,
KisSelectionSP dstSelection,
const QRect &srcRect)
{
const KoColorSpace *cs = srcDevice->colorSpace();
KisPixelSelectionSP selection = dstSelection->pixelSelection();
KisSequentialConstIterator srcIt(srcDevice, srcRect);
KisSequentialIterator dstIt(selection, srcRect);
while (srcIt.nextPixel() && dstIt.nextPixel()) {
quint8 *dstPtr = dstIt.rawData();
const quint8* srcPtr = srcIt.rawDataConst();
*dstPtr = cs->opacityU8(srcPtr);
}
}
void findEdge(KisPixelSelectionSP selection, const QRect &applyRect, const bool edgeHidden)
{
KisSequentialIterator dstIt(selection, applyRect);
if (edgeHidden) {
while(dstIt.nextPixel()) {
quint8 *pixelPtr = dstIt.rawData();
*pixelPtr =
(*pixelPtr < 24) ?
*pixelPtr * 10 : 0xFF;
}
} else {
while(dstIt.nextPixel()) {
quint8 *pixelPtr = dstIt.rawData();
*pixelPtr = 0xFF;
}
}
}
QRect growRectFromRadius(const QRect &rc, int radius)
{
int halfSize = KisGaussianKernel::kernelSizeFromRadius(radius) / 2;
return rc.adjusted(-halfSize, -halfSize, halfSize, halfSize);
}
void applyGaussianWithTransaction(KisPixelSelectionSP selection,
const QRect &applyRect,
qreal radius)
{
KisGaussianKernel::applyGaussian(selection, applyRect,
radius, radius,
- QBitArray(), 0, true);
+ QBitArray(), 0, true,
+ BORDER_IGNORE);
}
namespace Private {
void getGradientTable(const KoAbstractGradient *gradient,
QVector<KoColor> *table,
const KoColorSpace *colorSpace)
{
KIS_ASSERT_RECOVER_RETURN(table->size() == 256);
for (int i = 0; i < 256; i++) {
gradient->colorAt(((*table)[i]), qreal(i) / 255.0);
(*table)[i].convertTo(colorSpace);
}
}
struct LinearGradientIndex
{
int popOneIndex(int selectionAlpha) {
return 255 - selectionAlpha;
}
bool nextPixel() {
return true;
}
};
struct JitterGradientIndex
{
JitterGradientIndex(const QRect &applyRect,
int jitter,
const KisLayerStyleFilterEnvironment *env)
: randomSelection(env->cachedRandomSelection(applyRect)),
noiseIt(randomSelection, applyRect),
m_jitterCoeff(jitter * 255 / 100)
{
}
int popOneIndex(int selectionAlpha) {
int gradientIndex = 255 - selectionAlpha;
gradientIndex += m_jitterCoeff * *noiseIt.rawDataConst() >> 8;
gradientIndex &= 0xFF;
return gradientIndex;
}
bool nextPixel() {
return noiseIt.nextPixel();
}
private:
KisPixelSelectionSP randomSelection;
KisSequentialConstIterator noiseIt;
int m_jitterCoeff;
};
template <class IndexFetcher>
void applyGradientImpl(KisPaintDeviceSP device,
KisPixelSelectionSP selection,
const QRect &applyRect,
const QVector<KoColor> &table,
bool edgeHidden,
IndexFetcher &indexFetcher)
{
KIS_ASSERT_RECOVER_RETURN(
*table.first().colorSpace() == *device->colorSpace());
const KoColorSpace *cs = device->colorSpace();
const int pixelSize = cs->pixelSize();
KisSequentialConstIterator selIt(selection, applyRect);
KisSequentialIterator dstIt(device, applyRect);
if (edgeHidden) {
while (selIt.nextPixel() &&
dstIt.nextPixel() &&
indexFetcher.nextPixel()) {
quint8 selAlpha = *selIt.rawDataConst();
int gradientIndex = indexFetcher.popOneIndex(selAlpha);
const KoColor &color = table[gradientIndex];
quint8 tableAlpha = color.opacityU8();
memcpy(dstIt.rawData(), color.data(), pixelSize);
if (selAlpha < 24 && tableAlpha == 255) {
tableAlpha = int(selAlpha) * 10 * tableAlpha >> 8;
cs->setOpacity(dstIt.rawData(), tableAlpha, 1);
}
}
} else {
while (selIt.nextPixel() &&
dstIt.nextPixel() &&
indexFetcher.nextPixel()) {
int gradientIndex = indexFetcher.popOneIndex(*selIt.rawDataConst());
const KoColor &color = table[gradientIndex];
memcpy(dstIt.rawData(), color.data(), pixelSize);
}
}
}
void applyGradient(KisPaintDeviceSP device,
KisPixelSelectionSP selection,
const QRect &applyRect,
const QVector<KoColor> &table,
bool edgeHidden,
int jitter,
const KisLayerStyleFilterEnvironment *env)
{
if (!jitter) {
LinearGradientIndex fetcher;
applyGradientImpl(device, selection, applyRect, table, edgeHidden, fetcher);
} else {
JitterGradientIndex fetcher(applyRect, jitter, env);
applyGradientImpl(device, selection, applyRect, table, edgeHidden, fetcher);
}
}
}
const int noiseNeedBorder = 8;
void applyNoise(KisPixelSelectionSP selection,
const QRect &applyRect,
int noise,
const psd_layer_effects_context *context,
KisLayerStyleFilterEnvironment *env)
{
Q_UNUSED(context);
const QRect overlayRect = kisGrowRect(applyRect, noiseNeedBorder);
KisPixelSelectionSP randomSelection = env->cachedRandomSelection(overlayRect);
KisCachedSelection::Guard s1(*env->cachedSelection());
KisPixelSelectionSP randomOverlay = s1.selection()->pixelSelection();
KisSequentialConstIterator noiseIt(randomSelection, overlayRect);
KisSequentialConstIterator srcIt(selection, overlayRect);
KisRandomAccessorSP dstIt = randomOverlay->createRandomAccessorNG(overlayRect.x(), overlayRect.y());
while (noiseIt.nextPixel() && srcIt.nextPixel()) {
int itX = noiseIt.x();
int itY = noiseIt.y();
int x = itX + (*noiseIt.rawDataConst() >> 4) - 8;
int y = itY + (*noiseIt.rawDataConst() & 0x0F) - 8;
x = (x + itX) >> 1;
y = (y + itY) >> 1;
dstIt->moveTo(x, y);
quint8 dstAlpha = *dstIt->rawData();
quint8 srcAlpha = *srcIt.rawDataConst();
int value = qMin(255, dstAlpha + srcAlpha);
*dstIt->rawData() = value;
}
noise = noise * 255 / 100;
KisPainter gc(selection);
gc.setOpacity(noise);
gc.setCompositeOp(COMPOSITE_COPY);
gc.bitBlt(applyRect.topLeft(), randomOverlay, applyRect);
}
//const int FULL_PERCENT_RANGE = 100;
void adjustRange(KisPixelSelectionSP selection, const QRect &applyRect, const int range)
{
KIS_ASSERT_RECOVER_RETURN(range >= 1 && range <= 100);
quint8 rangeTable[256];
for(int i = 0; i < 256; i ++) {
quint8 value = i * 100 / range;
rangeTable[i] = qMin(value, quint8(255));
}
KisSequentialIterator dstIt(selection, applyRect);
while (dstIt.nextPixel()) {
quint8 *pixelPtr = dstIt.rawData();
*pixelPtr = rangeTable[*pixelPtr];
}
}
void applyContourCorrection(KisPixelSelectionSP selection,
const QRect &applyRect,
const quint8 *lookup_table,
bool antiAliased,
bool edgeHidden)
{
quint8 contour[PSD_LOOKUP_TABLE_SIZE] = {
0x00, 0x0b, 0x16, 0x21, 0x2c, 0x37, 0x42, 0x4d, 0x58, 0x63, 0x6e, 0x79, 0x84, 0x8f, 0x9a, 0xa5,
0xb0, 0xbb, 0xc6, 0xd1, 0xdc, 0xf2, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
if (edgeHidden) {
if (antiAliased) {
for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) {
contour[i] = contour[i] * lookup_table[i] >> 8;
}
} else {
for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) {
contour[i] = contour[i] * lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)] >> 8;
}
}
} else {
if (antiAliased) {
for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) {
contour[i] = lookup_table[i];
}
} else {
for (int i = 0; i < PSD_LOOKUP_TABLE_SIZE; i++) {
contour[i] = lookup_table[(int)((int)(i / 2.55) * 2.55 + 0.5)];
}
}
}
KisSequentialIterator dstIt(selection, applyRect);
while (dstIt.nextPixel()) {
quint8 *pixelPtr = dstIt.rawData();
*pixelPtr = contour[*pixelPtr];
}
}
void knockOutSelection(KisPixelSelectionSP selection,
KisPixelSelectionSP knockOutSelection,
const QRect &srcRect,
const QRect &dstRect,
const QRect &totalNeedRect,
const bool knockOutInverted)
{
KIS_ASSERT_RECOVER_RETURN(knockOutSelection);
QRect knockOutRect = !knockOutInverted ? srcRect : totalNeedRect;
knockOutRect &= dstRect;
KisPainter gc(selection);
gc.setCompositeOp(COMPOSITE_ERASE);
gc.bitBlt(knockOutRect.topLeft(), knockOutSelection, knockOutRect);
}
void fillPattern(KisPaintDeviceSP fillDevice,
const QRect &applyRect,
KisLayerStyleFilterEnvironment *env,
int scale,
KoPattern *pattern,
int horizontalPhase,
int verticalPhase,
bool alignWithLayer)
{
if (scale != 100) {
warnKrita << "KisLsOverlayFilter::applyOverlay(): Pattern scaling is NOT implemented!";
}
KIS_SAFE_ASSERT_RECOVER_RETURN(pattern);
QSize psize(pattern->width(), pattern->height());
QPoint patternOffset(qreal(psize.width()) * horizontalPhase / 100,
qreal(psize.height()) * verticalPhase / 100);
const QRect boundsRect = alignWithLayer ?
env->layerBounds() : env->defaultBounds();
patternOffset += boundsRect.topLeft();
patternOffset.rx() %= psize.width();
patternOffset.ry() %= psize.height();
QRect fillRect = applyRect | applyRect.translated(patternOffset);
KisFillPainter gc(fillDevice);
gc.fillRect(fillRect.x(), fillRect.y(),
fillRect.width(), fillRect.height(), pattern, -patternOffset);
gc.end();
}
void fillOverlayDevice(KisPaintDeviceSP fillDevice,
const QRect &applyRect,
const psd_layer_effects_overlay_base *config,
KisLayerStyleFilterEnvironment *env)
{
if (config->fillType() == psd_fill_solid_color) {
KoColor color(config->color(), fillDevice->colorSpace());
fillDevice->setDefaultPixel(color);
} else if (config->fillType() == psd_fill_pattern) {
fillPattern(fillDevice, applyRect, env,
config->scale(), config->pattern(),
config->horizontalPhase(),
config->verticalPhase(),
config->alignWithLayer());
} else if (config->fillType() == psd_fill_gradient) {
const QRect boundsRect = config->alignWithLayer() ?
env->layerBounds() : env->defaultBounds();
QPoint center = boundsRect.center();
center += QPoint(boundsRect.width() * config->gradientXOffset() / 100,
boundsRect.height() * config->gradientYOffset() / 100);
int width = (boundsRect.width() * config->scale() + 100) / 200;
int height = (boundsRect.height() * config->scale() + 100) / 200;
/* copy paste from libpsd */
int angle = config->angle();
int corner_angle = (int)(atan((qreal)boundsRect.height() / boundsRect.width()) * 180 / M_PI + 0.5);
int sign_x = 1;
int sign_y = 1;
if(angle < 0) {
angle += 360;
}
if (angle >= 90 && angle < 180) {
angle = 180 - angle;
sign_x = -1;
} else if (angle >= 180 && angle < 270) {
angle = angle - 180;
sign_x = -1;
sign_y = -1;
} else if (angle >= 270 && angle <= 360) {
angle = 360 - angle;
sign_y = -1;
}
int radius_x = 0;
int radius_y = 0;
if (angle <= corner_angle) {
radius_x = width;
radius_y = (int)(radius_x * tan(kisDegreesToRadians(qreal(angle))) + 0.5);
} else {
radius_y = height;
radius_x = (int)(radius_y / tan(kisDegreesToRadians(qreal(angle))) + 0.5);
}
int radius_corner = (int)(std::sqrt((qreal)(radius_x * radius_x + radius_y * radius_y)) + 0.5);
/* end of copy paste from libpsd */
KisGradientPainter gc(fillDevice);
gc.setGradient(config->gradient().data());
QPointF gradStart;
QPointF gradEnd;
KisGradientPainter::enumGradientRepeat repeat =
KisGradientPainter::GradientRepeatNone;
QPoint rectangularOffset(sign_x * radius_x, -sign_y * radius_y);
switch(config->style())
{
case psd_gradient_style_linear:
gc.setGradientShape(KisGradientPainter::GradientShapeLinear);
repeat = KisGradientPainter::GradientRepeatNone;
gradStart = center - rectangularOffset;
gradEnd = center + rectangularOffset;
break;
case psd_gradient_style_radial:
gc.setGradientShape(KisGradientPainter::GradientShapeRadial);
repeat = KisGradientPainter::GradientRepeatNone;
gradStart = center;
gradEnd = center + QPointF(radius_corner, 0);
break;
case psd_gradient_style_angle:
gc.setGradientShape(KisGradientPainter::GradientShapeConical);
repeat = KisGradientPainter::GradientRepeatNone;
gradStart = center;
gradEnd = center + rectangularOffset;
break;
case psd_gradient_style_reflected:
gc.setGradientShape(KisGradientPainter::GradientShapeLinear);
repeat = KisGradientPainter::GradientRepeatAlternate;
gradStart = center - rectangularOffset;
gradEnd = center;
break;
case psd_gradient_style_diamond:
gc.setGradientShape(KisGradientPainter::GradientShapeBiLinear);
repeat = KisGradientPainter::GradientRepeatNone;
gradStart = center - rectangularOffset;
gradEnd = center + rectangularOffset;
break;
default:
qFatal("Gradient Overlay: unknown switch case!");
break;
}
gc.paintGradient(gradStart, gradEnd,
repeat, 0.0,
config->reverse(),
applyRect);
}
}
void applyFinalSelection(const QString &projectionId,
KisSelectionSP baseSelection,
KisPaintDeviceSP srcDevice,
KisMultipleProjection *dst,
const QRect &/*srcRect*/,
const QRect &dstRect,
const psd_layer_effects_context */*context*/,
const psd_layer_effects_shadow_base *config,
const KisLayerStyleFilterEnvironment *env)
{
const KoColor effectColor(config->color(), srcDevice->colorSpace());
const QRect effectRect(dstRect);
const QString compositeOp = config->blendMode();
const quint8 opacityU8 = quint8(qRound(255.0 / 100.0 * config->opacity()));
KisPaintDeviceSP dstDevice = dst->getProjection(projectionId, compositeOp, opacityU8, QBitArray(), srcDevice);
if (config->fillType() == psd_fill_solid_color) {
KisFillPainter gc(dstDevice);
gc.setCompositeOp(COMPOSITE_COPY);
gc.setSelection(baseSelection);
gc.fillSelection(effectRect, effectColor);
gc.end();
} else if (config->fillType() == psd_fill_gradient) {
if (!config->gradient()) {
warnKrita << "KisLsUtils::applyFinalSelection: Gradient object is null! Skipping...";
return;
}
QVector<KoColor> table(256);
Private::getGradientTable(config->gradient().data(), &table, dstDevice->colorSpace());
Private::applyGradient(dstDevice, baseSelection->pixelSelection(),
effectRect, table,
true, config->jitter(), env);
}
//dstDevice->convertToQImage(0, QRect(0,0,300,300)).save("6_device_shadow.png");
}
bool checkEffectEnabled(const psd_layer_effects_shadow_base *config, KisMultipleProjection *dst)
{
bool result = config->effectEnabled();
if (!result) {
dst->freeAllProjections();
}
return result;
}
}
diff --git a/libs/image/lazybrush/patched_boykov_kolmogorov_max_flow.hpp b/libs/image/lazybrush/patched_boykov_kolmogorov_max_flow.hpp
index b2a13e70de..0373186de3 100644
--- a/libs/image/lazybrush/patched_boykov_kolmogorov_max_flow.hpp
+++ b/libs/image/lazybrush/patched_boykov_kolmogorov_max_flow.hpp
@@ -1,882 +1,882 @@
// Copyright (c) 2006, Stephan Diederich
//
// This code may be used under either of the following two licences:
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE. OF SUCH DAMAGE.
//
// Or:
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
-// http://www.boost.org/LICENSE_1_0.txt)
+// https://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_BOYKOV_KOLMOGOROV_MAX_FLOW_HPP
#define BOOST_BOYKOV_KOLMOGOROV_MAX_FLOW_HPP
#include <boost/config.hpp>
#include <boost/assert.hpp>
#include <vector>
#include <list>
#include <utility>
#include <iosfwd>
#include <algorithm> // for std::min and std::max
#include <boost/pending/queue.hpp>
#include <boost/limits.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/none_t.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <boost/graph/named_function_params.hpp>
#include <boost/graph/lookup_edge.hpp>
#include <boost/concept/assert.hpp>
// The algorithm impelemented here is described in:
//
// Boykov, Y., Kolmogorov, V. "An Experimental Comparison of Min-Cut/Max-Flow
// Algorithms for Energy Minimization in Vision", In IEEE Transactions on
// Pattern Analysis and Machine Intelligence, vol. 26, no. 9, pp. 1124-1137,
// Sep 2004.
//
// For further reading, also see:
//
// Kolmogorov, V. "Graph Based Algorithms for Scene Reconstruction from Two or
// More Views". PhD thesis, Cornell University, Sep 2003.
namespace boost {
namespace detail {
template <class Graph,
class EdgeCapacityMap,
class ResidualCapacityEdgeMap,
class ReverseEdgeMap,
class PredecessorMap,
class ColorMap,
class DistanceMap,
class IndexMap>
class bk_max_flow {
typedef typename property_traits<EdgeCapacityMap>::value_type tEdgeVal;
typedef graph_traits<Graph> tGraphTraits;
typedef typename tGraphTraits::vertex_iterator vertex_iterator;
typedef typename tGraphTraits::vertex_descriptor vertex_descriptor;
typedef typename tGraphTraits::edge_descriptor edge_descriptor;
typedef typename tGraphTraits::edge_iterator edge_iterator;
typedef typename tGraphTraits::out_edge_iterator out_edge_iterator;
typedef boost::queue<vertex_descriptor> tQueue; //queue of vertices, used in adoption-stage
typedef typename property_traits<ColorMap>::value_type tColorValue;
typedef color_traits<tColorValue> tColorTraits;
typedef typename property_traits<DistanceMap>::value_type tDistanceVal;
public:
bk_max_flow(Graph& g,
EdgeCapacityMap cap,
ResidualCapacityEdgeMap res,
ReverseEdgeMap rev,
PredecessorMap pre,
ColorMap color,
DistanceMap dist,
IndexMap idx,
vertex_descriptor src,
vertex_descriptor sink):
m_g(g),
m_index_map(idx),
m_cap_map(cap),
m_res_cap_map(res),
m_rev_edge_map(rev),
m_pre_map(pre),
m_tree_map(color),
m_dist_map(dist),
m_source(src),
m_sink(sink),
m_active_nodes(),
m_in_active_list_vec(num_vertices(g), false),
m_in_active_list_map(make_iterator_property_map(m_in_active_list_vec.begin(), m_index_map)),
m_has_parent_vec(num_vertices(g), false),
m_has_parent_map(make_iterator_property_map(m_has_parent_vec.begin(), m_index_map)),
m_time_vec(num_vertices(g), 0),
m_time_map(make_iterator_property_map(m_time_vec.begin(), m_index_map)),
m_flow(0),
m_time(1),
m_last_grow_vertex(graph_traits<Graph>::null_vertex()){
// initialize the color-map with gray-values
vertex_iterator vi, v_end;
for(boost::tie(vi, v_end) = vertices(m_g); vi != v_end; ++vi){
set_tree(*vi, tColorTraits::gray());
}
// Initialize flow to zero which means initializing
// the residual capacity equal to the capacity
edge_iterator ei, e_end;
for(boost::tie(ei, e_end) = edges(m_g); ei != e_end; ++ei) {
put(m_res_cap_map, *ei, get(m_cap_map, *ei));
BOOST_ASSERT(get(m_rev_edge_map, get(m_rev_edge_map, *ei)) == *ei); //check if the reverse edge map is build up properly
}
//init the search trees with the two terminals
set_tree(m_source, tColorTraits::black());
set_tree(m_sink, tColorTraits::white());
put(m_time_map, m_source, 1);
put(m_time_map, m_sink, 1);
}
tEdgeVal max_flow(){
//augment direct paths from SOURCE->SINK and SOURCE->VERTEX->SINK
augment_direct_paths();
//start the main-loop
while(true){
bool path_found;
edge_descriptor connecting_edge;
boost::tie(connecting_edge, path_found) = grow(); //find a path from source to sink
if(!path_found){
//we're finished, no more paths were found
break;
}
++m_time;
augment(connecting_edge); //augment that path
adopt(); //rebuild search tree structure
}
return m_flow;
}
// the complete class is protected, as we want access to members in
// derived test-class (see test/boykov_kolmogorov_max_flow_test.cpp)
protected:
void augment_direct_paths(){
// in a first step, we augment all direct paths from source->NODE->sink
// and additionally paths from source->sink. This improves especially
// graphcuts for segmentation, as most of the nodes have source/sink
// connects but shouldn't have an impact on other maxflow problems
// (this is done in grow() anyway)
out_edge_iterator ei, e_end;
for(boost::tie(ei, e_end) = out_edges(m_source, m_g); ei != e_end; ++ei){
edge_descriptor from_source = *ei;
vertex_descriptor current_node = target(from_source, m_g);
if(current_node == m_sink){
tEdgeVal cap = get(m_res_cap_map, from_source);
put(m_res_cap_map, from_source, 0);
m_flow += cap;
continue;
}
edge_descriptor to_sink;
bool is_there;
boost::tie(to_sink, is_there) = lookup_edge(current_node, m_sink, m_g);
if(is_there){
tEdgeVal cap_from_source = get(m_res_cap_map, from_source);
tEdgeVal cap_to_sink = get(m_res_cap_map, to_sink);
if(cap_from_source > cap_to_sink){
set_tree(current_node, tColorTraits::black());
add_active_node(current_node);
set_edge_to_parent(current_node, from_source);
put(m_dist_map, current_node, 1);
put(m_time_map, current_node, 1);
// add stuff to flow and update residuals. we don't need to
// update reverse_edges, as incoming/outgoing edges to/from
// source/sink don't count for max-flow
put(m_res_cap_map, from_source, get(m_res_cap_map, from_source) - cap_to_sink);
put(m_res_cap_map, to_sink, 0);
m_flow += cap_to_sink;
} else if(cap_to_sink > 0){
set_tree(current_node, tColorTraits::white());
add_active_node(current_node);
set_edge_to_parent(current_node, to_sink);
put(m_dist_map, current_node, 1);
put(m_time_map, current_node, 1);
// add stuff to flow and update residuals. we don't need to update
// reverse_edges, as incoming/outgoing edges to/from source/sink
// don't count for max-flow
put(m_res_cap_map, to_sink, get(m_res_cap_map, to_sink) - cap_from_source);
put(m_res_cap_map, from_source, 0);
m_flow += cap_from_source;
}
} else if(get(m_res_cap_map, from_source)){
// there is no sink connect, so we can't augment this path, but to
// avoid adding m_source to the active nodes, we just activate this
// node and set the appropriate things
set_tree(current_node, tColorTraits::black());
set_edge_to_parent(current_node, from_source);
put(m_dist_map, current_node, 1);
put(m_time_map, current_node, 1);
add_active_node(current_node);
}
}
for(boost::tie(ei, e_end) = out_edges(m_sink, m_g); ei != e_end; ++ei){
edge_descriptor to_sink = get(m_rev_edge_map, *ei);
vertex_descriptor current_node = source(to_sink, m_g);
if(get(m_res_cap_map, to_sink)){
set_tree(current_node, tColorTraits::white());
set_edge_to_parent(current_node, to_sink);
put(m_dist_map, current_node, 1);
put(m_time_map, current_node, 1);
add_active_node(current_node);
}
}
}
/**
* Returns a pair of an edge and a boolean. if the bool is true, the
* edge is a connection of a found path from s->t , read "the link" and
* source(returnVal, m_g) is the end of the path found in the source-tree
* target(returnVal, m_g) is the beginning of the path found in the sink-tree
*/
std::pair<edge_descriptor, bool> grow(){
BOOST_ASSERT(m_orphans.empty());
vertex_descriptor current_node;
while((current_node = get_next_active_node()) != graph_traits<Graph>::null_vertex()){ //if there is one
BOOST_ASSERT(get_tree(current_node) != tColorTraits::gray() &&
(has_parent(current_node) ||
current_node == m_source ||
current_node == m_sink));
if(get_tree(current_node) == tColorTraits::black()){
//source tree growing
out_edge_iterator ei, e_end;
if(current_node != m_last_grow_vertex){
m_last_grow_vertex = current_node;
boost::tie(m_last_grow_edge_it, m_last_grow_edge_end) = out_edges(current_node, m_g);
}
for(; m_last_grow_edge_it != m_last_grow_edge_end; ++m_last_grow_edge_it) {
edge_descriptor out_edge = *m_last_grow_edge_it;
if(get(m_res_cap_map, out_edge) > 0){ //check if we have capacity left on this edge
vertex_descriptor other_node = target(out_edge, m_g);
if(get_tree(other_node) == tColorTraits::gray()){ //it's a free node
set_tree(other_node, tColorTraits::black()); //acquire other node to our search tree
set_edge_to_parent(other_node, out_edge); //set us as parent
put(m_dist_map, other_node, get(m_dist_map, current_node) + 1); //and update the distance-heuristic
put(m_time_map, other_node, get(m_time_map, current_node));
add_active_node(other_node);
} else if(get_tree(other_node) == tColorTraits::black()) {
// we do this to get shorter paths. check if we are nearer to
// the source as its parent is
if(is_closer_to_terminal(current_node, other_node)){
set_edge_to_parent(other_node, out_edge);
put(m_dist_map, other_node, get(m_dist_map, current_node) + 1);
put(m_time_map, other_node, get(m_time_map, current_node));
}
} else{
BOOST_ASSERT(get_tree(other_node)==tColorTraits::white());
//kewl, found a path from one to the other search tree, return
// the connecting edge in src->sink dir
return std::make_pair(out_edge, true);
}
}
} //for all out-edges
} //source-tree-growing
else{
BOOST_ASSERT(get_tree(current_node) == tColorTraits::white());
out_edge_iterator ei, e_end;
if(current_node != m_last_grow_vertex){
m_last_grow_vertex = current_node;
boost::tie(m_last_grow_edge_it, m_last_grow_edge_end) = out_edges(current_node, m_g);
}
for(; m_last_grow_edge_it != m_last_grow_edge_end; ++m_last_grow_edge_it){
edge_descriptor in_edge = get(m_rev_edge_map, *m_last_grow_edge_it);
if(get(m_res_cap_map, in_edge) > 0){ //check if there is capacity left
vertex_descriptor other_node = source(in_edge, m_g);
if(get_tree(other_node) == tColorTraits::gray()){ //it's a free node
set_tree(other_node, tColorTraits::white()); //acquire that node to our search tree
set_edge_to_parent(other_node, in_edge); //set us as parent
add_active_node(other_node); //activate that node
put(m_dist_map, other_node, get(m_dist_map, current_node) + 1); //set its distance
put(m_time_map, other_node, get(m_time_map, current_node));//and time
} else if(get_tree(other_node) == tColorTraits::white()){
if(is_closer_to_terminal(current_node, other_node)){
//we are closer to the sink than its parent is, so we "adopt" him
set_edge_to_parent(other_node, in_edge);
put(m_dist_map, other_node, get(m_dist_map, current_node) + 1);
put(m_time_map, other_node, get(m_time_map, current_node));
}
} else{
BOOST_ASSERT(get_tree(other_node)==tColorTraits::black());
//kewl, found a path from one to the other search tree,
// return the connecting edge in src->sink dir
return std::make_pair(in_edge, true);
}
}
} //for all out-edges
} //sink-tree growing
//all edges of that node are processed, and no more paths were found.
// remove if from the front of the active queue
finish_node(current_node);
} //while active_nodes not empty
//no active nodes anymore and no path found, we're done
return std::make_pair(edge_descriptor(), false);
}
/**
* augments path from s->t and updates residual graph
* source(e, m_g) is the end of the path found in the source-tree
* target(e, m_g) is the beginning of the path found in the sink-tree
* this phase generates orphans on satured edges, if the attached verts are
* from different search-trees orphans are ordered in distance to
* sink/source. first the farest from the source are front_inserted into
* the orphans list, and after that the sink-tree-orphans are
* front_inserted. when going to adoption stage the orphans are popped_front,
* and so we process the nearest verts to the terminals first
*/
void augment(edge_descriptor e) {
BOOST_ASSERT(get_tree(target(e, m_g)) == tColorTraits::white());
BOOST_ASSERT(get_tree(source(e, m_g)) == tColorTraits::black());
BOOST_ASSERT(m_orphans.empty());
const tEdgeVal bottleneck = find_bottleneck(e);
//now we push the found flow through the path
//for each edge we saturate we have to look for the verts that belong to that edge, one of them becomes an orphans
//now process the connecting edge
put(m_res_cap_map, e, get(m_res_cap_map, e) - bottleneck);
BOOST_ASSERT(get(m_res_cap_map, e) >= 0);
put(m_res_cap_map, get(m_rev_edge_map, e), get(m_res_cap_map, get(m_rev_edge_map, e)) + bottleneck);
//now we follow the path back to the source
vertex_descriptor current_node = source(e, m_g);
while(current_node != m_source){
edge_descriptor pred = get_edge_to_parent(current_node);
const tEdgeVal new_pred_cap = get(m_res_cap_map, pred) - bottleneck;
put(m_res_cap_map, pred, new_pred_cap);
BOOST_ASSERT(get(m_res_cap_map, pred) >= 0);
const edge_descriptor pred_rev = get(m_rev_edge_map, pred);
put(m_res_cap_map, pred_rev, get(m_res_cap_map, pred_rev) + bottleneck);
if(new_pred_cap == 0){
set_no_parent(current_node);
m_orphans.push_front(current_node);
}
current_node = source(pred, m_g);
}
//then go forward in the sink-tree
current_node = target(e, m_g);
while(current_node != m_sink){
edge_descriptor pred = get_edge_to_parent(current_node);
const tEdgeVal new_pred_cap = get(m_res_cap_map, pred) - bottleneck;
put(m_res_cap_map, pred, new_pred_cap);
BOOST_ASSERT(get(m_res_cap_map, pred) >= 0);
const edge_descriptor pred_rev = get(m_rev_edge_map, pred);
put(m_res_cap_map, pred_rev, get(m_res_cap_map, pred_rev) + bottleneck);
if(new_pred_cap == 0){
set_no_parent(current_node);
m_orphans.push_front(current_node);
}
current_node = target(pred, m_g);
}
//and add it to the max-flow
m_flow += bottleneck;
}
/**
* returns the bottleneck of a s->t path (end_of_path is last vertex in
* source-tree, begin_of_path is first vertex in sink-tree)
*/
inline tEdgeVal find_bottleneck(edge_descriptor e){
BOOST_USING_STD_MIN();
tEdgeVal minimum_cap = get(m_res_cap_map, e);
vertex_descriptor current_node = source(e, m_g);
//first go back in the source tree
while(current_node != m_source){
edge_descriptor pred = get_edge_to_parent(current_node);
minimum_cap = min BOOST_PREVENT_MACRO_SUBSTITUTION(minimum_cap, get(m_res_cap_map, pred));
current_node = source(pred, m_g);
}
//then go forward in the sink-tree
current_node = target(e, m_g);
while(current_node != m_sink){
edge_descriptor pred = get_edge_to_parent(current_node);
minimum_cap = min BOOST_PREVENT_MACRO_SUBSTITUTION(minimum_cap, get(m_res_cap_map, pred));
current_node = target(pred, m_g);
}
return minimum_cap;
}
/**
* rebuild search trees
* empty the queue of orphans, and find new parents for them or just drop
* them from the search trees
*/
void adopt(){
while(!m_orphans.empty() || !m_child_orphans.empty()){
vertex_descriptor current_node;
if(m_child_orphans.empty()){
//get the next orphan from the main-queue and remove it
current_node = m_orphans.front();
m_orphans.pop_front();
} else{
current_node = m_child_orphans.front();
m_child_orphans.pop();
}
if(get_tree(current_node) == tColorTraits::black()){
//we're in the source-tree
tDistanceVal min_distance = (std::numeric_limits<tDistanceVal>::max)();
edge_descriptor new_parent_edge;
out_edge_iterator ei, e_end;
for(boost::tie(ei, e_end) = out_edges(current_node, m_g); ei != e_end; ++ei){
const edge_descriptor in_edge = get(m_rev_edge_map, *ei);
BOOST_ASSERT(target(in_edge, m_g) == current_node); //we should be the target of this edge
if(get(m_res_cap_map, in_edge) > 0){
vertex_descriptor other_node = source(in_edge, m_g);
if(get_tree(other_node) == tColorTraits::black() && has_source_connect(other_node)){
if(get(m_dist_map, other_node) < min_distance){
min_distance = get(m_dist_map, other_node);
new_parent_edge = in_edge;
}
}
}
}
if(min_distance != (std::numeric_limits<tDistanceVal>::max)()){
set_edge_to_parent(current_node, new_parent_edge);
put(m_dist_map, current_node, min_distance + 1);
put(m_time_map, current_node, m_time);
} else{
put(m_time_map, current_node, 0);
for(boost::tie(ei, e_end) = out_edges(current_node, m_g); ei != e_end; ++ei){
edge_descriptor in_edge = get(m_rev_edge_map, *ei);
vertex_descriptor other_node = source(in_edge, m_g);
if(get_tree(other_node) == tColorTraits::black() && other_node != m_source){
if(get(m_res_cap_map, in_edge) > 0){
add_active_node(other_node);
}
if(has_parent(other_node) && source(get_edge_to_parent(other_node), m_g) == current_node){
//we are the parent of that node
//it has to find a new parent, too
set_no_parent(other_node);
m_child_orphans.push(other_node);
}
}
}
set_tree(current_node, tColorTraits::gray());
} //no parent found
} //source-tree-adoption
else{
//now we should be in the sink-tree, check that...
BOOST_ASSERT(get_tree(current_node) == tColorTraits::white());
out_edge_iterator ei, e_end;
edge_descriptor new_parent_edge;
tDistanceVal min_distance = (std::numeric_limits<tDistanceVal>::max)();
for(boost::tie(ei, e_end) = out_edges(current_node, m_g); ei != e_end; ++ei){
const edge_descriptor out_edge = *ei;
if(get(m_res_cap_map, out_edge) > 0){
const vertex_descriptor other_node = target(out_edge, m_g);
if(get_tree(other_node) == tColorTraits::white() && has_sink_connect(other_node))
if(get(m_dist_map, other_node) < min_distance){
min_distance = get(m_dist_map, other_node);
new_parent_edge = out_edge;
}
}
}
if(min_distance != (std::numeric_limits<tDistanceVal>::max)()){
set_edge_to_parent(current_node, new_parent_edge);
put(m_dist_map, current_node, min_distance + 1);
put(m_time_map, current_node, m_time);
} else{
put(m_time_map, current_node, 0);
for(boost::tie(ei, e_end) = out_edges(current_node, m_g); ei != e_end; ++ei){
const edge_descriptor out_edge = *ei;
const vertex_descriptor other_node = target(out_edge, m_g);
if(get_tree(other_node) == tColorTraits::white() && other_node != m_sink){
if(get(m_res_cap_map, out_edge) > 0){
add_active_node(other_node);
}
if(has_parent(other_node) && target(get_edge_to_parent(other_node), m_g) == current_node){
//we were it's parent, so it has to find a new one, too
set_no_parent(other_node);
m_child_orphans.push(other_node);
}
}
}
set_tree(current_node, tColorTraits::gray());
} //no parent found
} //sink-tree adoption
} //while !orphans.empty()
} //adopt
/**
* return next active vertex if there is one, otherwise a null_vertex
*/
inline vertex_descriptor get_next_active_node(){
while(true){
if(m_active_nodes.empty())
return graph_traits<Graph>::null_vertex();
vertex_descriptor v = m_active_nodes.front();
//if it has no parent, this node can't be active (if its not source or sink)
if(!has_parent(v) && v != m_source && v != m_sink){
m_active_nodes.pop();
put(m_in_active_list_map, v, false);
} else{
BOOST_ASSERT(get_tree(v) == tColorTraits::black() || get_tree(v) == tColorTraits::white());
return v;
}
}
}
/**
* adds v as an active vertex, but only if its not in the list already
*/
inline void add_active_node(vertex_descriptor v){
BOOST_ASSERT(get_tree(v) != tColorTraits::gray());
if(get(m_in_active_list_map, v)){
if (m_last_grow_vertex == v) {
m_last_grow_vertex = graph_traits<Graph>::null_vertex();
}
return;
} else{
put(m_in_active_list_map, v, true);
m_active_nodes.push(v);
}
}
/**
* finish_node removes a node from the front of the active queue (its called in grow phase, if no more paths can be found using this node)
*/
inline void finish_node(vertex_descriptor v){
BOOST_ASSERT(m_active_nodes.front() == v);
m_active_nodes.pop();
put(m_in_active_list_map, v, false);
m_last_grow_vertex = graph_traits<Graph>::null_vertex();
}
/**
* removes a vertex from the queue of active nodes (actually this does nothing,
* but checks if this node has no parent edge, as this is the criteria for
* being no more active)
*/
inline void remove_active_node(vertex_descriptor v){
(void)v; // disable unused parameter warning
BOOST_ASSERT(!has_parent(v));
}
/**
* returns the search tree of v; tColorValue::black() for source tree,
* white() for sink tree, gray() for no tree
*/
inline tColorValue get_tree(vertex_descriptor v) const {
return get(m_tree_map, v);
}
/**
* sets search tree of v; tColorValue::black() for source tree, white()
* for sink tree, gray() for no tree
*/
inline void set_tree(vertex_descriptor v, tColorValue t){
put(m_tree_map, v, t);
}
/**
* returns edge to parent vertex of v;
*/
inline edge_descriptor get_edge_to_parent(vertex_descriptor v) const{
return get(m_pre_map, v);
}
/**
* returns true if the edge stored in m_pre_map[v] is a valid entry
*/
inline bool has_parent(vertex_descriptor v) const{
return get(m_has_parent_map, v);
}
/**
* sets edge to parent vertex of v;
*/
inline void set_edge_to_parent(vertex_descriptor v, edge_descriptor f_edge_to_parent){
BOOST_ASSERT(get(m_res_cap_map, f_edge_to_parent) > 0);
put(m_pre_map, v, f_edge_to_parent);
put(m_has_parent_map, v, true);
}
/**
* removes the edge to parent of v (this is done by invalidating the
* entry an additional map)
*/
inline void set_no_parent(vertex_descriptor v){
put(m_has_parent_map, v, false);
}
/**
* checks if vertex v has a connect to the sink-vertex (@p m_sink)
* @param v the vertex which is checked
* @return true if a path to the sink was found, false if not
*/
inline bool has_sink_connect(vertex_descriptor v){
tDistanceVal current_distance = 0;
vertex_descriptor current_vertex = v;
while(true){
if(get(m_time_map, current_vertex) == m_time){
//we found a node which was already checked this round. use it for distance calculations
current_distance += get(m_dist_map, current_vertex);
break;
}
if(current_vertex == m_sink){
put(m_time_map, m_sink, m_time);
break;
}
if(has_parent(current_vertex)){
//it has a parent, so get it
current_vertex = target(get_edge_to_parent(current_vertex), m_g);
++current_distance;
} else{
//no path found
return false;
}
}
current_vertex=v;
while(get(m_time_map, current_vertex) != m_time){
put(m_dist_map, current_vertex, current_distance);
--current_distance;
put(m_time_map, current_vertex, m_time);
current_vertex = target(get_edge_to_parent(current_vertex), m_g);
}
return true;
}
/**
* checks if vertex v has a connect to the source-vertex (@p m_source)
* @param v the vertex which is checked
* @return true if a path to the source was found, false if not
*/
inline bool has_source_connect(vertex_descriptor v){
tDistanceVal current_distance = 0;
vertex_descriptor current_vertex = v;
while(true){
if(get(m_time_map, current_vertex) == m_time){
//we found a node which was already checked this round. use it for distance calculations
current_distance += get(m_dist_map, current_vertex);
break;
}
if(current_vertex == m_source){
put(m_time_map, m_source, m_time);
break;
}
if(has_parent(current_vertex)){
//it has a parent, so get it
current_vertex = source(get_edge_to_parent(current_vertex), m_g);
++current_distance;
} else{
//no path found
return false;
}
}
current_vertex=v;
while(get(m_time_map, current_vertex) != m_time){
put(m_dist_map, current_vertex, current_distance);
--current_distance;
put(m_time_map, current_vertex, m_time);
current_vertex = source(get_edge_to_parent(current_vertex), m_g);
}
return true;
}
/**
* returns true, if p is closer to a terminal than q
*/
inline bool is_closer_to_terminal(vertex_descriptor p, vertex_descriptor q){
//checks the timestamps first, to build no cycles, and after that the real distance
return (get(m_time_map, q) <= get(m_time_map, p) &&
get(m_dist_map, q) > get(m_dist_map, p)+1);
}
////////
// member vars
////////
Graph& m_g;
IndexMap m_index_map;
EdgeCapacityMap m_cap_map;
ResidualCapacityEdgeMap m_res_cap_map;
ReverseEdgeMap m_rev_edge_map;
PredecessorMap m_pre_map; //stores paths found in the growth stage
ColorMap m_tree_map; //maps each vertex into one of the two search tree or none (gray())
DistanceMap m_dist_map; //stores distance to source/sink nodes
vertex_descriptor m_source;
vertex_descriptor m_sink;
tQueue m_active_nodes;
std::vector<bool> m_in_active_list_vec;
iterator_property_map<std::vector<bool>::iterator, IndexMap> m_in_active_list_map;
std::list<vertex_descriptor> m_orphans;
tQueue m_child_orphans; // we use a second queuqe for child orphans, as they are FIFO processed
std::vector<bool> m_has_parent_vec;
iterator_property_map<std::vector<bool>::iterator, IndexMap> m_has_parent_map;
std::vector<long> m_time_vec; //timestamp of each node, used for sink/source-path calculations
iterator_property_map<std::vector<long>::iterator, IndexMap> m_time_map;
tEdgeVal m_flow;
long m_time;
vertex_descriptor m_last_grow_vertex;
out_edge_iterator m_last_grow_edge_it;
out_edge_iterator m_last_grow_edge_end;
};
} //namespace boost::detail
/**
* non-named-parameter version, given everything
* this is the catch all version
*/
template<class Graph,
class CapacityEdgeMap,
class ResidualCapacityEdgeMap,
class ReverseEdgeMap, class PredecessorMap,
class ColorMap,
class DistanceMap,
class IndexMap>
typename property_traits<CapacityEdgeMap>::value_type
boykov_kolmogorov_max_flow(Graph& g,
CapacityEdgeMap cap,
ResidualCapacityEdgeMap res_cap,
ReverseEdgeMap rev_map,
PredecessorMap pre_map,
ColorMap color,
DistanceMap dist,
IndexMap idx,
typename graph_traits<Graph>::vertex_descriptor src,
typename graph_traits<Graph>::vertex_descriptor sink)
{
typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor;
typedef typename graph_traits<Graph>::edge_descriptor edge_descriptor;
//as this method is the last one before we instantiate the solver, we do the concept checks here
BOOST_CONCEPT_ASSERT(( VertexListGraphConcept<Graph> )); //to have vertices(), num_vertices(),
BOOST_CONCEPT_ASSERT(( EdgeListGraphConcept<Graph> )); //to have edges()
BOOST_CONCEPT_ASSERT(( IncidenceGraphConcept<Graph> )); //to have source(), target() and out_edges()
BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept<CapacityEdgeMap, edge_descriptor> )); //read flow-values from edges
BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<ResidualCapacityEdgeMap, edge_descriptor> )); //write flow-values to residuals
BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept<ReverseEdgeMap, edge_descriptor> )); //read out reverse edges
BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<PredecessorMap, vertex_descriptor> )); //store predecessor there
BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<ColorMap, vertex_descriptor> )); //write corresponding tree
BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<DistanceMap, vertex_descriptor> )); //write distance to source/sink
BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept<IndexMap, vertex_descriptor> )); //get index 0...|V|-1
BOOST_ASSERT(num_vertices(g) >= 2 && src != sink);
detail::bk_max_flow<
Graph, CapacityEdgeMap, ResidualCapacityEdgeMap, ReverseEdgeMap,
PredecessorMap, ColorMap, DistanceMap, IndexMap
> algo(g, cap, res_cap, rev_map, pre_map, color, dist, idx, src, sink);
return algo.max_flow();
}
/**
* non-named-parameter version, given capacity, residucal_capacity,
* reverse_edges, and an index map.
*/
template<class Graph,
class CapacityEdgeMap,
class ResidualCapacityEdgeMap,
class ReverseEdgeMap,
class IndexMap>
typename property_traits<CapacityEdgeMap>::value_type
boykov_kolmogorov_max_flow(Graph& g,
CapacityEdgeMap cap,
ResidualCapacityEdgeMap res_cap,
ReverseEdgeMap rev,
IndexMap idx,
typename graph_traits<Graph>::vertex_descriptor src,
typename graph_traits<Graph>::vertex_descriptor sink)
{
typename graph_traits<Graph>::vertices_size_type n_verts = num_vertices(g);
std::vector<typename graph_traits<Graph>::edge_descriptor> predecessor_vec(n_verts);
std::vector<default_color_type> color_vec(n_verts);
std::vector<typename graph_traits<Graph>::vertices_size_type> distance_vec(n_verts);
return
boykov_kolmogorov_max_flow(
g, cap, res_cap, rev,
make_iterator_property_map(predecessor_vec.begin(), idx),
make_iterator_property_map(color_vec.begin(), idx),
make_iterator_property_map(distance_vec.begin(), idx),
idx, src, sink);
}
/**
* non-named-parameter version, some given: capacity, residual_capacity,
* reverse_edges, color_map and an index map. Use this if you are interested in
* the minimum cut, as the color map provides that info.
*/
template<class Graph,
class CapacityEdgeMap,
class ResidualCapacityEdgeMap,
class ReverseEdgeMap,
class ColorMap,
class IndexMap>
typename property_traits<CapacityEdgeMap>::value_type
boykov_kolmogorov_max_flow(Graph& g,
CapacityEdgeMap cap,
ResidualCapacityEdgeMap res_cap,
ReverseEdgeMap rev,
ColorMap color,
IndexMap idx,
typename graph_traits<Graph>::vertex_descriptor src,
typename graph_traits<Graph>::vertex_descriptor sink)
{
typename graph_traits<Graph>::vertices_size_type n_verts = num_vertices(g);
std::vector<typename graph_traits<Graph>::edge_descriptor> predecessor_vec(n_verts);
std::vector<typename graph_traits<Graph>::vertices_size_type> distance_vec(n_verts);
return
boykov_kolmogorov_max_flow(
g, cap, res_cap, rev,
make_iterator_property_map(predecessor_vec.begin(), idx),
color,
make_iterator_property_map(distance_vec.begin(), idx),
idx, src, sink);
}
/**
* named-parameter version, some given
*/
template<class Graph, class P, class T, class R>
typename property_traits<typename property_map<Graph, edge_capacity_t>::const_type>::value_type
boykov_kolmogorov_max_flow(Graph& g,
typename graph_traits<Graph>::vertex_descriptor src,
typename graph_traits<Graph>::vertex_descriptor sink,
const bgl_named_params<P, T, R>& params)
{
return
boykov_kolmogorov_max_flow(
g,
choose_const_pmap(get_param(params, edge_capacity), g, edge_capacity),
choose_pmap(get_param(params, edge_residual_capacity), g, edge_residual_capacity),
choose_const_pmap(get_param(params, edge_reverse), g, edge_reverse),
choose_pmap(get_param(params, vertex_predecessor), g, vertex_predecessor),
choose_pmap(get_param(params, vertex_color), g, vertex_color),
choose_pmap(get_param(params, vertex_distance), g, vertex_distance),
choose_const_pmap(get_param(params, vertex_index), g, vertex_index),
src, sink);
}
/**
* named-parameter version, none given
*/
template<class Graph>
typename property_traits<typename property_map<Graph, edge_capacity_t>::const_type>::value_type
boykov_kolmogorov_max_flow(Graph& g,
typename graph_traits<Graph>::vertex_descriptor src,
typename graph_traits<Graph>::vertex_descriptor sink)
{
bgl_named_params<int, buffer_param_t> params(0); // bogus empty param
return boykov_kolmogorov_max_flow(g, src, sink, params);
}
} // namespace boost
#endif // BOOST_BOYKOV_KOLMOGOROV_MAX_FLOW_HPP
diff --git a/libs/image/processing/kis_assign_profile_processing_visitor.cpp b/libs/image/processing/kis_assign_profile_processing_visitor.cpp
index ae3d56c807..52eb83d185 100644
--- a/libs/image/processing/kis_assign_profile_processing_visitor.cpp
+++ b/libs/image/processing/kis_assign_profile_processing_visitor.cpp
@@ -1,88 +1,87 @@
/*
* Copyright (c) 2019 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_assign_profile_processing_visitor.h"
#include "kis_external_layer_iface.h"
#include "kis_paint_device.h"
#include "kis_transaction.h"
#include "kis_undo_adapter.h"
#include "kis_transform_mask.h"
#include "lazybrush/kis_colorize_mask.h"
#include <KoColorConversionTransformation.h>
#include "kis_projection_leaf.h"
#include "kis_paint_layer.h"
#include "kis_time_range.h"
#include <QSet>
KisAssignProfileProcessingVisitor::KisAssignProfileProcessingVisitor(const KoColorSpace *srcColorSpace,
const KoColorSpace *dstColorSpace)
: m_srcColorSpace(srcColorSpace)
, m_dstColorSpace(dstColorSpace)
{
}
void KisAssignProfileProcessingVisitor::visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter)
{
undoAdapter->addCommand(layer->setProfile(m_dstColorSpace->profile()));
}
void KisAssignProfileProcessingVisitor::visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter)
{
if (!node->projectionLeaf()->isLayer()) return;
if (*m_dstColorSpace == *node->colorSpace()) return;
QSet<KisPaintDeviceSP> paintDevices;
paintDevices.insert(node->paintDevice());
paintDevices.insert(node->original());
paintDevices.insert(node->projection());
KUndo2Command *parentConversionCommand = new KUndo2Command();
Q_FOREACH (KisPaintDeviceSP dev, paintDevices) {
if (dev->colorSpace()->colorModelId() == m_srcColorSpace->colorModelId()) {
dev->setProfile(m_dstColorSpace->profile(), parentConversionCommand);
}
}
undoAdapter->addCommand(parentConversionCommand);
node->invalidateFrames(KisTimeRange::infinite(0), node->extent());
}
void KisAssignProfileProcessingVisitor::visit(KisTransformMask *mask, KisUndoAdapter *undoAdapter)
{
mask->threadSafeForceStaticImageUpdate();
KisSimpleProcessingVisitor::visit(mask, undoAdapter);
}
void KisAssignProfileProcessingVisitor::visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter)
{
if (mask->colorSpace()->colorModelId() == m_srcColorSpace->colorModelId()) {
KUndo2Command *parentConversionCommand = new KUndo2Command();
mask->setProfile(m_dstColorSpace->profile(), parentConversionCommand);
undoAdapter->addCommand(parentConversionCommand);
mask->invalidateFrames(KisTimeRange::infinite(0), mask->extent());
}
- KisSimpleProcessingVisitor::visit(mask, undoAdapter);
}
diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt
index 0fc7893122..a8091263ab 100644
--- a/libs/image/tests/CMakeLists.txt
+++ b/libs/image/tests/CMakeLists.txt
@@ -1,155 +1,195 @@
# 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_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}
)
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_clone_layer_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_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_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_fixed_point_maths_test.cpp
kis_node_query_path_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
kis_mask_similarity_test.cpp
KisMaskGeneratorTest.cpp
kis_layer_style_filter_environment_test.cpp
kis_asl_parser_test.cpp
KisPerStrokeRandomSourceTest.cpp
KisWatershedWorkerTest.cpp
kis_dom_utils_test.cpp
kis_transform_worker_test.cpp
- kis_perspective_transform_worker_test.cpp
kis_cs_conversion_test.cpp
- kis_processings_test.cpp
kis_projection_leaf_test.cpp
kis_histogram_test.cpp
kis_onion_skin_compositor_test.cpp
- kis_paint_device_test.cpp
kis_queues_progress_updater_test.cpp
kis_image_animation_interface_test.cpp
kis_walkers_test.cpp
- kis_async_merger_test.cpp
kis_cage_transform_worker_test.cpp
kis_random_generator_test.cpp
kis_keyframing_test.cpp
kis_filter_mask_test.cpp
LINK_LIBRARIES kritaimage Qt5::Test
NAME_PREFIX "libs-image-"
)
-krita_add_broken_unit_tests(
- kis_transform_mask_test.cpp
- kis_layer_styles_test.cpp
- kis_update_scheduler_test.cpp
+include(KritaAddBrokenUnitTest)
+
+krita_add_broken_unit_test( kis_transform_mask_test.cpp
+ TEST_NAME kis_transform_mask_test.cpp
+ LINK_LIBRARIES kritaimage Qt5::Test
+ NAME_PREFIX "libs-image-"
+)
+
+krita_add_broken_unit_test( kis_layer_styles_test.cpp
+ TEST_NAME kis_layer_styles_test.cpp
+ LINK_LIBRARIES kritaimage Qt5::Test
+ NAME_PREFIX "libs-image-"
+)
+
+krita_add_broken_unit_test( kis_update_scheduler_test.cpp
+ TEST_NAME kis_update_scheduler_test.cpp
+ LINK_LIBRARIES kritaimage Qt5::Test
+ NAME_PREFIX "libs-image-"
+)
+
+krita_add_broken_unit_test( kis_paint_device_test.cpp
+ TEST_NAME kis_paint_device_test.cpp
+ LINK_LIBRARIES kritaimage Qt5::Test
+ NAME_PREFIX "libs-image-"
+)
+
+krita_add_broken_unit_test( kis_colorize_mask_test.cpp
+ TEST_NAME kis_colorize_mask_test.cpp
+ LINK_LIBRARIES kritaimage Qt5::Test
+ NAME_PREFIX "libs-image-"
+)
+
+krita_add_broken_unit_test( kis_selection_test.cpp
+ TEST_NAME kis_selection_test.cpp
+ LINK_LIBRARIES kritaimage Qt5::Test
+ NAME_PREFIX "libs-image-"
+)
+
+krita_add_broken_unit_test( kis_processings_test.cpp
+ TEST_NAME kis_processings_test.cpp
+ LINK_LIBRARIES kritaimage Qt5::Test
+ NAME_PREFIX "libs-image-"
+)
+
+krita_add_broken_unit_test( kis_perspective_transform_worker_test.cpp
+ TEST_NAME kis_perspective_transform_worker_test.cpp
+ LINK_LIBRARIES kritaimage Qt5::Test
+ NAME_PREFIX "libs-image-"
+)
+krita_add_broken_unit_test( kis_async_merger_test.cpp
+ TEST_NAME kis_async_merger_test.cpp
LINK_LIBRARIES kritaimage Qt5::Test
NAME_PREFIX "libs-image-"
)
diff --git a/libs/image/tests/kis_layer_styles_test.cpp b/libs/image/tests/kis_layer_styles_test.cpp
index c875ceaac8..3fa3159936 100644
--- a/libs/image/tests/kis_layer_styles_test.cpp
+++ b/libs/image/tests/kis_layer_styles_test.cpp
@@ -1,284 +1,284 @@
/*
* 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_layer_styles_test.h"
#include <QTest>
#include "kis_transaction.h"
#include "testutil.h"
#include <KoColor.h>
#include "layerstyles/kis_layer_style_filter.h"
#include "layerstyles/kis_layer_style_filter_environment.h"
#include "layerstyles/kis_ls_drop_shadow_filter.h"
#include "kis_psd_layer_style.h"
#include "layerstyles/kis_multiple_projection.h"
#include "layerstyles/KisLayerStyleKnockoutBlower.h"
struct TestConfig {
TestConfig()
: distance(0),
angle(0),
spread(0),
size(0),
noise(0),
knocks_out(false),
keep_original(false)
{
}
int distance;
int angle;
int spread;
int size;
int noise;
int knocks_out;
int opacity;
bool keep_original;
void writeProperties(KisPSDLayerStyleSP style) const {
style->context()->keep_original = keep_original;
style->dropShadow()->setEffectEnabled(true);
style->dropShadow()->setDistance(distance);
style->dropShadow()->setSpread(spread);
style->dropShadow()->setSize(size);
style->dropShadow()->setNoise(noise);
style->dropShadow()->setKnocksOut(knocks_out);
style->dropShadow()->setOpacity(opacity);
}
QString genTestname(const QString &prefix) const {
return QString("%1_d_%2_an_%3_sz_%4_spr_%5_nz_%6_ko_%7_keep_%8")
.arg(prefix)
.arg(distance)
.arg(angle)
.arg(size)
.arg(spread)
.arg(noise)
.arg(knocks_out)
.arg(keep_original);
}
};
void testDropShadowImpl(const TestConfig &config,
const QVector<QRect> &applyRects,
const QString &testName,
bool useSeparateDevices)
{
Q_UNUSED(useSeparateDevices);
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
QRect srcRect(50, 50, 100, 100);
QRect dstRect(0, 0, 200, 200);
KisPaintDeviceSP dev = new KisPaintDevice(cs);
dev->fill(srcRect, KoColor(Qt::red, cs));
KisMultipleProjection projection;
KisLsDropShadowFilter lsFilter;
KisPSDLayerStyleSP style(new KisPSDLayerStyle());
config.writeProperties(style);
TestUtil::MaskParent parent;
KisLayerStyleFilterEnvironment env(parent.layer.data());
KisLayerStyleKnockoutBlower blower;
Q_FOREACH (const QRect &rc, applyRects) {
lsFilter.processDirectly(dev, &projection, &blower, rc, style, &env);
}
// drop shadow doesn't use global knockout
QVERIFY(blower.isEmpty());
KisPaintDeviceSP dst = new KisPaintDevice(cs);
projection.apply(dst, dstRect, &env);
QImage resultImage =
dst->convertToQImage(0, dstRect);
TestUtil::checkQImage(resultImage,
"layer_styles_test",
"common",
config.genTestname(testName));
}
void KisLayerStylesTest::testLayerStylesFull()
{
TestConfig c;
c.distance = 20;
c.angle = 135;
c.spread = 50;
c.size = 10;
c.noise = 30;
c.knocks_out = false;
c.opacity = 50;
c.keep_original = false;
testDropShadowImpl(c, QVector<QRect>() << QRect(0,0,200,200), "full", false);
}
void KisLayerStylesTest::testLayerStylesPartial()
{
QVector<QRect> rects;
for (int y = 0; y < 200; y += 50) {
for (int x = 0; x < 200; x += 50) {
rects << QRect(x, y, 50, 50);
}
}
TestConfig c;
c.distance = 20;
c.angle = 135;
c.spread = 50;
c.size = 10;
c.noise = 30;
c.knocks_out = false;
c.opacity = 50;
c.keep_original = false;
testDropShadowImpl(c, rects, "partial", true);
}
void KisLayerStylesTest::testLayerStylesPartialVary()
{
QVector<QRect> rects;
for (int y = 0; y < 200; y += 50) {
for (int x = 0; x < 200; x += 50) {
rects << QRect(x, y, 50, 50);
}
}
TestConfig c;
c.distance = 20;
c.angle = 135;
c.spread = 50;
c.size = 10;
c.noise = 30;
c.knocks_out = false;
c.opacity = 50;
c.keep_original = true;
testDropShadowImpl(c, rects, "partial", true);
c.noise = 90;
testDropShadowImpl(c, rects, "partial", true);
c.noise = 0;
testDropShadowImpl(c, rects, "partial", true);
c.noise = 10;
testDropShadowImpl(c, rects, "partial", true);
c.angle = 90;
testDropShadowImpl(c, rects, "partial", true);
c.angle = 45;
testDropShadowImpl(c, rects, "partial", true);
c.knocks_out = true;
testDropShadowImpl(c, rects, "partial", true);
c.spread = 90;
testDropShadowImpl(c, rects, "partial", true);
}
void testDropShadowNeedChangeRects(int distance,
int noise,
int size,
int spread,
const QRect &applyRect,
const QRect &needRect,
const QRect &changeRect)
{
TestConfig c;
c.distance = distance;
c.spread = spread;
c.size = size;
c.noise = noise;
c.angle = 90;
c.knocks_out = false;
c.opacity = 50;
KisLsDropShadowFilter lsFilter;
KisPSDLayerStyleSP style(new KisPSDLayerStyle());
c.writeProperties(style);
TestUtil::MaskParent parent;
KisLayerStyleFilterEnvironment env(parent.layer.data());
QCOMPARE(lsFilter.neededRect(applyRect, style, &env), needRect);
QCOMPARE(lsFilter.changedRect(applyRect, style, &env), changeRect);
}
void KisLayerStylesTest::testLayerStylesRects()
{
QRect applyRect;
QRect needRect;
QRect changeRect;
applyRect = QRect(10,10,10,10);
needRect = applyRect;
changeRect = applyRect;
testDropShadowNeedChangeRects(0, 0, 0, 0, applyRect, needRect, changeRect);
applyRect = QRect(10,10,10,10);
needRect = QRect(10,0,10,20);
changeRect = QRect(10,10,10,20);
testDropShadowNeedChangeRects(10, 0, 0, 0, applyRect, needRect, changeRect);
applyRect = QRect(10,10,10,10);
needRect = QRect(2,2,26,26);
changeRect = QRect(2,2,26,26);
testDropShadowNeedChangeRects(0, 30, 0, 0, applyRect, needRect, changeRect);
applyRect = QRect(10,10,10,10);
needRect = QRect(-2,-2,34,34);
changeRect = QRect(-2,-2,34,34);
testDropShadowNeedChangeRects(0, 0, 10, 0, applyRect, needRect, changeRect);
applyRect = QRect(10,10,10,10);
needRect = QRect(-2,-2,34,34);
changeRect = QRect(-2,-2,34,34);
testDropShadowNeedChangeRects(0, 0, 10, 50, applyRect, needRect, changeRect);
applyRect = QRect(10,10,10,10);
needRect = QRect(-2,-2,34,34);
changeRect = QRect(-2,-2,34,34);
testDropShadowNeedChangeRects(0, 0, 10, 75, applyRect, needRect, changeRect);
}
-QTEST_MAIN(KisLayerStylesTest)
+KISTEST_MAIN(KisLayerStylesTest)
diff --git a/libs/image/tiles3/kis_lockless_stack.h b/libs/image/tiles3/kis_lockless_stack.h
index 6145af836a..2ec0cbc947 100644
--- a/libs/image/tiles3/kis_lockless_stack.h
+++ b/libs/image/tiles3/kis_lockless_stack.h
@@ -1,235 +1,235 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* References:
* * Maged M. Michael, Safe memory reclamation for dynamic
* lock-free objects using atomic reads and writes,
* Proceedings of the twenty-first annual symposium on
* Principles of distributed computing, July 21-24, 2002,
* Monterey, California
*
* * Idea of m_deleteBlockers is taken from Andrey Gulin's blog
- * http://users.livejournal.com/_foreseer/34284.html
+ * https://users.livejournal.com/-foreseer/34284.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public 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_LOCKLESS_STACK_H
#define __KIS_LOCKLESS_STACK_H
#include <QAtomicPointer>
template<class T>
class KisLocklessStack
{
private:
struct Node {
Node *next;
T data;
};
public:
KisLocklessStack() { }
~KisLocklessStack() {
freeList(m_top.fetchAndStoreOrdered(0));
freeList(m_freeNodes.fetchAndStoreOrdered(0));
}
void push(T data) {
Node *newNode = new Node();
newNode->data = data;
Node *top;
do {
top = m_top;
newNode->next = top;
} while (!m_top.testAndSetOrdered(top, newNode));
m_numNodes.ref();
}
bool pop(T &value) {
bool result = false;
m_deleteBlockers.ref();
while(1) {
Node *top = (Node*) m_top;
if(!top) break;
// This is safe as we ref'ed m_deleteBlockers
Node *next = top->next;
if(m_top.testAndSetOrdered(top, next)) {
m_numNodes.deref();
result = true;
value = top->data;
/**
* Test if we are the only delete blocker left
* (it means that we are the only owner of 'top')
* If there is someone else in "delete-blocked section",
* then just add the struct to the list of free nodes.
*/
if (m_deleteBlockers == 1) {
cleanUpNodes();
delete top;
}
else {
releaseNode(top);
}
break;
}
}
m_deleteBlockers.deref();
return result;
}
void clear() {
// a fast-path without write ops
if(!m_top) return;
m_deleteBlockers.ref();
Node *top = m_top.fetchAndStoreOrdered(0);
int removedChunkSize = 0;
Node *tmp = top;
while(tmp) {
removedChunkSize++;
tmp = tmp->next;
}
m_numNodes.fetchAndAddOrdered(-removedChunkSize);
while(top) {
Node *next = top->next;
if (m_deleteBlockers == 1) {
/**
* We are the only owner of top contents.
* So we can delete it freely.
*/
cleanUpNodes();
freeList(top);
next = 0;
}
else {
releaseNode(top);
}
top = next;
}
m_deleteBlockers.deref();
}
void mergeFrom(KisLocklessStack<T> &other) {
Node *otherTop = other.m_top.fetchAndStoreOrdered(0);
if (!otherTop) return;
int removedChunkSize = 1;
Node *last = otherTop;
while(last->next) {
removedChunkSize++;
last = last->next;
}
other.m_numNodes.fetchAndAddOrdered(-removedChunkSize);
Node *top;
do {
top = m_top;
last->next = top;
} while (!m_top.testAndSetOrdered(top, otherTop));
m_numNodes.fetchAndAddOrdered(removedChunkSize);
}
/**
* This is impossible to measure the size of the stack
* in highly concurrent environment. So we return approximate
* value! Do not rely on this value much!
*/
qint32 size() const {
return m_numNodes;
}
bool isEmpty() const {
return !m_numNodes;
}
private:
inline void releaseNode(Node *node) {
Node *top;
do {
top = m_freeNodes;
node->next = top;
} while (!m_freeNodes.testAndSetOrdered(top, node));
}
inline void cleanUpNodes() {
Node *cleanChain = m_freeNodes.fetchAndStoreOrdered(0);
if (!cleanChain) return;
/**
* If we are the only users of the objects is cleanChain,
* then just free it. Otherwise, push them back into the
* recycling list and keep them there till another
* chance comes.
*/
if (m_deleteBlockers == 1) {
freeList(cleanChain);
} else {
Node *last = cleanChain;
while (last->next) last = last->next;
Node *freeTop;
do {
freeTop = m_freeNodes;
last->next = freeTop;
} while (!m_freeNodes.testAndSetOrdered(freeTop, cleanChain));
}
}
inline void freeList(Node *first) {
Node *next;
while (first) {
next = first->next;
delete first;
first = next;
}
}
private:
Q_DISABLE_COPY(KisLocklessStack)
QAtomicPointer<Node> m_top;
QAtomicPointer<Node> m_freeNodes;
QAtomicInt m_deleteBlockers;
QAtomicInt m_numNodes;
};
#endif /* __KIS_LOCKLESS_STACK_H */
diff --git a/libs/libkis/Canvas.cpp b/libs/libkis/Canvas.cpp
index 0bbddf9979..06c0b0b60d 100644
--- a/libs/libkis/Canvas.cpp
+++ b/libs/libkis/Canvas.cpp
@@ -1,142 +1,142 @@
/*
* 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 "Canvas.h"
#include <KoCanvasBase.h>
#include <kis_canvas2.h>
#include <KisView.h>
#include <KoCanvasController.h>
#include <kis_canvas_controller.h>
#include <kis_zoom_manager.h>
#include <QPointer>
#include <View.h>
struct Canvas::Private {
Private() {}
KisCanvas2 *canvas;
};
Canvas::Canvas(KoCanvasBase *canvas, QObject *parent)
: QObject(parent)
, d(new Private)
{
d->canvas = static_cast<KisCanvas2*>(canvas);
}
Canvas::~Canvas()
{
delete d;
}
bool Canvas::operator==(const Canvas &other) const
{
return (d->canvas == other.d->canvas);
}
bool Canvas::operator!=(const Canvas &other) const
{
return !(operator==(other));
}
qreal Canvas::zoomLevel() const
{
if (!d->canvas) return 1.0;
return d->canvas->imageView()->zoomManager()->zoom();
}
void Canvas::setZoomLevel(qreal value)
{
if (!d->canvas) return;
d->canvas->imageView()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, value);
}
void Canvas::resetZoom()
{
if (!d->canvas) return;
d->canvas->imageView()->zoomManager()->zoomTo100();
}
void Canvas::resetRotation()
{
if (!d->canvas) return;
d->canvas->imageView()->canvasController()->resetCanvasRotation();
}
qreal Canvas::rotation() const
{
if (!d->canvas) return 0;
return d->canvas->imageView()->canvasController()->rotation();
}
void Canvas::setRotation(qreal angle)
{
if (!d->canvas) return;
- d->canvas->imageView()->canvasController()->rotateCanvas(angle);
+ d->canvas->imageView()->canvasController()->rotateCanvas(angle - rotation());
}
bool Canvas::mirror() const
{
if (!d->canvas) return false;
return d->canvas->imageView()->canvasIsMirrored();
}
void Canvas::setMirror(bool value)
{
if (!d->canvas) return;
d->canvas->imageView()->canvasController()->mirrorCanvas(value);
}
View *Canvas::view() const
{
if (!d->canvas) return 0;
View *view = new View(d->canvas->imageView());
return view;
}
KisDisplayColorConverter *Canvas::displayColorConverter() const
{
if (!d->canvas) return 0;
return d->canvas->displayColorConverter();
}
bool Canvas::wrapAroundMode() const
{
if (!d->canvas) return false;
return d->canvas->imageView()->canvasController()->wrapAroundMode();
}
void Canvas::setWrapAroundMode(bool enable)
{
if (!d->canvas) return;
d->canvas->imageView()->canvasController()->slotToggleWrapAroundMode(enable);
}
bool Canvas::levelOfDetailMode() const
{
if (!d->canvas) return false;
return d->canvas->imageView()->canvasController()->levelOfDetailMode();
}
void Canvas::setLevelOfDetailMode(bool enable)
{
if (!d->canvas) return;
return d->canvas->imageView()->canvasController()->slotToggleLevelOfDetailMode(enable);
}
diff --git a/libs/libkis/FillLayer.h b/libs/libkis/FillLayer.h
index 07740b2a08..209b682c17 100644
--- a/libs/libkis/FillLayer.h
+++ b/libs/libkis/FillLayer.h
@@ -1,100 +1,100 @@
/*
* 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 Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef LIBKIS_FILLLAYER_H
#define LIBKIS_FILLLAYER_H
#include <QObject>
#include "Node.h"
#include <InfoObject.h>
#include <Selection.h>
#include <kis_types.h>
#include "kritalibkis_export.h"
#include "libkis.h"
/**
* @brief The FillLayer class
* A fill layer is much like a filter layer in that it takes a name
* and filter. It however specializes in filters that fill the whole canvas,
* such as a pattern or full color fill.
*/
class KRITALIBKIS_EXPORT FillLayer : public Node
{
Q_OBJECT
Q_DISABLE_COPY(FillLayer)
public:
/**
* @brief FillLayer Create a new fill layer with the given generator plugin
* @param image the image this fill layer will belong to
* @param name "pattern" or "color"
* @param filterConfig a configuration object appropriate to the given generator plugin
*
* For a "pattern" fill layer, the InfoObject can contain a single "pattern" parameter with
* the name of a pattern as known to the resource system: "pattern" = "Cross01.pat".
*
* For a "color" fill layer, the InfoObject can contain a single "color" parameter with
- * a QColor, a string that QColor can parse (see http://doc.qt.io/qt-5/qcolor.html#setNamedColor)
+ * a QColor, a string that QColor can parse (see https://doc.qt.io/qt-5/qcolor.html#setNamedColor)
* or an XML description of the color, which can be derived from a @see ManagedColor.
*
* @param selection a selection object, can be empty
* @param parent
*/
explicit FillLayer(KisImageSP image, QString name, KisFilterConfigurationSP filterConfig, Selection &selection, QObject *parent = 0);
explicit FillLayer(KisGeneratorLayerSP layer, QObject *parent = 0);
~FillLayer() override;
public Q_SLOTS:
/**
* @brief type Krita has several types of nodes, split in layers and masks. Group
* layers can contain other layers, any layer can contain masks.
*
* @return The type of the node. Valid types are:
* <ul>
* <li>paintlayer
* <li>grouplayer
* <li>filelayer
* <li>filterlayer
* <li>filllayer
* <li>clonelayer
* <li>vectorlayer
* <li>transparencymask
* <li>filtermask
* <li>transformmask
* <li>selectionmask
* <li>colorizemask
* </ul>
*
* If the Node object isn't wrapping a valid Krita layer or mask object, and
* empty string is returned.
*/
virtual QString type() const override;
/**
* @brief setGenerator set the given generator for this fill layer
* @param generatorName "pattern" or "color"
* @param filterConfig a configuration object appropriate to the given generator plugin
* @return true if the generator was correctly created and set on the layer
*/
bool setGenerator(const QString &generatorName, InfoObject *filterConfig);
QString generatorName();
InfoObject *filterConfig();
};
#endif // LIBKIS_FILLLAYER_H
diff --git a/libs/libkis/InfoObject.h b/libs/libkis/InfoObject.h
index ee8c4b9ff7..b3beb91ef0 100644
--- a/libs/libkis/InfoObject.h
+++ b/libs/libkis/InfoObject.h
@@ -1,88 +1,88 @@
/*
* 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.
*/
#ifndef LIBKIS_INFOOBJECT_H
#define LIBKIS_INFOOBJECT_H
#include <QObject>
#include <kis_properties_configuration.h>
#include "kritalibkis_export.h"
#include "libkis.h"
/**
* InfoObject wrap a properties map. These maps can be used to set the
* configuration for filters.
*/
class KRITALIBKIS_EXPORT InfoObject : public QObject
{
Q_OBJECT
public:
InfoObject(KisPropertiesConfigurationSP configuration);
/**
* Create a new, empty InfoObject.
*/
explicit InfoObject(QObject *parent = 0);
~InfoObject() override;
bool operator==(const InfoObject &other) const;
bool operator!=(const InfoObject &other) const;
/**
* Return all properties this InfoObject manages.
*/
QMap<QString, QVariant> properties() const;
/**
* Add all properties in the @p propertyMap to this InfoObject
*/
void setProperties(QMap<QString, QVariant> propertyMap);
public Q_SLOTS:
/**
* set the property identified by @p key to @p value
*
* If you want create a property that represents a color, you can use a QColor
- * or hex string, as defined in http://doc.qt.io/qt-5/qcolor.html#setNamedColor.
+ * or hex string, as defined in https://doc.qt.io/qt-5/qcolor.html#setNamedColor.
*
*/
void setProperty(const QString &key, QVariant value);
/**
* return the value for the property identified by key, or None if there is no such key.
*/
QVariant property(const QString &key);
private:
friend class Filter;
friend class Document;
friend class Node;
/**
* @brief configuration gives access to the internal configuration object. Must
* be used used internally in libkis
* @return the internal configuration object.
*/
KisPropertiesConfigurationSP configuration() const;
struct Private;
Private *d;
};
#endif // LIBKIS_INFOOBJECT_H
diff --git a/libs/libkis/ManagedColor.cpp b/libs/libkis/ManagedColor.cpp
index 75199bdbcd..3d905a1054 100644
--- a/libs/libkis/ManagedColor.cpp
+++ b/libs/libkis/ManagedColor.cpp
@@ -1,174 +1,191 @@
/*
* 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.
*/
#include "ManagedColor.h"
#include <KoColor.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KoColorModelStandardIds.h>
#include <KoChannelInfo.h>
#include <QDomDocument>
#include <QDomElement>
#include <Canvas.h>
#include <kis_display_color_converter.h>
#include <KoColorDisplayRendererInterface.h>
struct ManagedColor::Private {
KoColor color;
};
ManagedColor::ManagedColor(QObject *parent)
: QObject(parent)
, d(new Private())
{
// Default black rgb color
}
ManagedColor::ManagedColor(const QString &colorModel, const QString &colorDepth, const QString &colorProfile, QObject *parent)
: QObject(parent)
, d(new Private())
{
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile);
if (colorSpace) {
d->color = KoColor(colorSpace);
}
}
ManagedColor::ManagedColor(KoColor color, QObject *parent)
: QObject(parent)
, d(new Private())
{
d->color = color;
}
ManagedColor::~ManagedColor()
{
}
bool ManagedColor::operator==(const ManagedColor &other) const
{
return d->color == other.d->color;
}
+
QColor ManagedColor::colorForCanvas(Canvas *canvas) const
{
QColor c = QColor(0,0,0);
if (canvas && canvas->displayColorConverter() && canvas->displayColorConverter()->displayRendererInterface()) {
KoColorDisplayRendererInterface *converter = canvas->displayColorConverter()->displayRendererInterface();
if (converter) {
c = converter->toQColor(d->color);
} else {
c = KoDumbColorDisplayRenderer::instance()->toQColor(d->color);
}
} else {
c = KoDumbColorDisplayRenderer::instance()->toQColor(d->color);
}
return c;
}
+ManagedColor *ManagedColor::fromQColor(const QColor &qcolor, Canvas *canvas)
+{
+ KoColor c;
+ if (canvas && canvas->displayColorConverter() && canvas->displayColorConverter()->displayRendererInterface()) {
+ KoColorDisplayRendererInterface *converter = canvas->displayColorConverter()->displayRendererInterface();
+ if (converter) {
+ c = converter->approximateFromRenderedQColor(qcolor);
+ } else {
+ c = KoDumbColorDisplayRenderer::instance()->approximateFromRenderedQColor(qcolor);
+ }
+ } else {
+ c = KoDumbColorDisplayRenderer::instance()->approximateFromRenderedQColor(qcolor);
+ }
+ return new ManagedColor(c);
+}
+
QString ManagedColor::colorDepth() const
{
return d->color.colorSpace()->colorDepthId().id();
}
QString ManagedColor::colorModel() const
{
return d->color.colorSpace()->colorModelId().id();
}
QString ManagedColor::colorProfile() const
{
return d->color.colorSpace()->profile()->name();
}
bool ManagedColor::setColorProfile(const QString &colorProfile)
{
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile);
if (!profile) return false;
d->color.setProfile(profile);
return true;
}
bool ManagedColor::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile)
{
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile);
if (!colorSpace) return false;
d->color.convertTo(colorSpace);
return true;
}
QVector<float> ManagedColor::components() const
{
QVector<float> values(d->color.colorSpace()->channelCount());
d->color.colorSpace()->normalisedChannelsValue(d->color.data(), values);
return values;
}
QVector<float> ManagedColor::componentsOrdered() const
{
QVector<float> valuesUnsorted = components();
QVector<float> values(d->color.colorSpace()->channelCount());
for (int i=0; i<values.size();i++) {
int location = KoChannelInfo::displayPositionToChannelIndex(i, d->color.colorSpace()->channels());
values[location] = valuesUnsorted[i];
}
return values;
}
void ManagedColor::setComponents(const QVector<float> &values)
{
d->color.colorSpace()->fromNormalisedChannelsValue(d->color.data(), values);
}
QString ManagedColor::toXML() const
{
QDomDocument doc;
QDomElement root = doc.createElement("Color");
root.setAttribute("bitdepth", colorDepth());
doc.appendChild(root);
d->color.toXML(doc, root);
return doc.toString();
}
void ManagedColor::fromXML(const QString &xml)
{
QDomDocument doc;
doc.setContent(xml);
QDomElement e = doc.documentElement();
QDomElement c = e.firstChildElement("Color");
KoColor kc;
if (!c.isNull()) {
QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id());
d->color = KoColor::fromXML(c, colorDepthId);
}
}
QString ManagedColor::toQString()
{
return KoColor::toQString(d->color);
}
KoColor ManagedColor::color() const
{
return d->color;
}
diff --git a/libs/libkis/ManagedColor.h b/libs/libkis/ManagedColor.h
index 5b5837ee17..a8376960f0 100644
--- a/libs/libkis/ManagedColor.h
+++ b/libs/libkis/ManagedColor.h
@@ -1,211 +1,220 @@
/*
* 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 MANAGEDCOLOR_H
#define MANAGEDCOLOR_H
#include <QObject>
#include <QVector>
#include <QScopedPointer>
#include "kritalibkis_export.h"
#include "libkis.h"
class KoColor;
/**
* @brief The ManagedColor class is a class to handle colors that are color managed.
* A managed color is a color of which we know the model(RGB, LAB, CMYK, etc), the bitdepth and
* the specific properties of its colorspace, such as the whitepoint, chromacities, trc, etc, as represented
* by the color profile.
*
* Krita has two color management systems. LCMS and OCIO.
* LCMS is the one handling the ICC profile stuff, and the major one handling that ManagedColor deals with.
* OCIO support is only in the display of the colors. ManagedColor has some support for it in colorForCanvas()
*
* All colors in Krita are color managed. QColors are understood as RGB-type colors in the sRGB space.
*
* We recommend you make a color like this:
*
* @code
* colorYellow = ManagedColor("RGBA", "U8", "")
* QVector<float> yellowComponents = colorYellow.components()
* yellowComponents[0] = 1.0
* yellowComponents[1] = 1.0
* yellowComponents[2] = 0
* yellowComponents[3] = 1.0
*
* colorYellow.setComponents(yellowComponents)
* QColor yellow = colorYellow.colorForCanvas(canvas)
* @endcode
*/
class KRITALIBKIS_EXPORT ManagedColor : public QObject
{
Q_OBJECT
public:
/**
* @brief ManagedColor
* Create a ManagedColor that is black and transparent.
*/
explicit ManagedColor(QObject *parent = 0);
/**
* @brief ManagedColor create a managed color with the given color space properties.
* @see setColorModel() for more details.
*/
ManagedColor(const QString &colorModel, const QString &colorDepth, const QString &colorProfile, QObject *parent = 0);
ManagedColor(KoColor color, QObject *parent = 0);
~ManagedColor() override;
bool operator==(const ManagedColor &other) const;
/**
* @brief colorForCanvas
* @param canvas the canvas whose color management you'd like to use. In Krita, different views have
* separate canvasses, and these can have different OCIO configurations active.
* @return the QColor as it would be displaying on the canvas. This result can be used to draw widgets with
* the correct configuration applied.
*/
QColor colorForCanvas(Canvas *canvas) const;
+
+ /**
+ * @brief fromQColor is the (approximate) reverse of colorForCanvas()
+ * @param qcolor the QColor to convert to a KoColor.
+ * @param canvas the canvas whose color management you'd like to use.
+ * @return the approximated ManagedColor, to use for canvas resources.
+ */
+ static ManagedColor *fromQColor(const QColor &qcolor, Canvas *canvas = 0);
+
/**
* colorDepth A string describing the color depth of the image:
* <ul>
* <li>U8: unsigned 8 bits integer, the most common type</li>
* <li>U16: unsigned 16 bits integer</li>
* <li>F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR</li>
* <li>F32: 32 bits floating point</li>
* </ul>
* @return the color depth.
*/
QString colorDepth() const;
/**
* @brief colorModel retrieve the current color model of this document:
* <ul>
* <li>A: Alpha mask</li>
* <li>RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)</li>
* <li>XYZA: XYZ with alpha channel</li>
* <li>LABA: LAB with alpha channel</li>
* <li>CMYKA: CMYK with alpha channel</li>
* <li>GRAYA: Gray with alpha channel</li>
* <li>YCbCrA: YCbCr with alpha channel</li>
* </ul>
* @return the internal color model string.
*/
QString colorModel() const;
/**
* @return the name of the current color profile
*/
QString colorProfile() const;
/**
* @brief setColorProfile set the color profile of the image to the given profile. The profile has to
* be registered with krita and be compatible with the current color model and depth; the image data
* is <i>not</i> converted.
* @param colorProfile
* @return false if the colorProfile name does not correspond to to a registered profile or if assigning
* the profile failed.
*/
bool setColorProfile(const QString &colorProfile);
/**
* @brief setColorSpace convert the nodes and the image to the given colorspace. The conversion is
* done with Perceptual as intent, High Quality and No LCMS Optimizations as flags and no blackpoint
* compensation.
*
* @param colorModel A string describing the color model of the image:
* <ul>
* <li>A: Alpha mask</li>
* <li>RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)</li>
* <li>XYZA: XYZ with alpha channel</li>
* <li>LABA: LAB with alpha channel</li>
* <li>CMYKA: CMYK with alpha channel</li>
* <li>GRAYA: Gray with alpha channel</li>
* <li>YCbCrA: YCbCr with alpha channel</li>
* </ul>
* @param colorDepth A string describing the color depth of the image:
* <ul>
* <li>U8: unsigned 8 bits integer, the most common type</li>
* <li>U16: unsigned 16 bits integer</li>
* <li>F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR</li>
* <li>F32: 32 bits floating point</li>
* </ul>
* @param colorProfile a valid color profile for this color model and color depth combination.
* @return false the combination of these arguments does not correspond to a colorspace.
*/
bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile);
/**
* @brief components
* @return a QVector containing the channel/components of this color normalized. This includes the alphachannel.
*/
QVector<float> components() const;
/**
* @brief componentsOrdered()
* @return same as Components, except the values are ordered to the display.
*/
QVector<float> componentsOrdered() const;
/**
* @brief setComponents
* Set the channel/components with normalized values. For integer colorspace, this obviously means the limit
* is between 0.0-1.0, but for floating point colorspaces, 2.4 or 103.5 are still meaningful (if bright) values.
* @param values the QVector containing the new channel/component values. These should be normalized.
*/
void setComponents(const QVector<float> &values);
/**
* Serialize this color following Create's swatch color specification available
- * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
+ * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft
*/
QString toXML() const;
/**
* Unserialize a color following Create's swatch color specification available
- * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
+ * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft
*
* @param xml an XML color
*
* @return the unserialized color, or an empty color object if the function failed
* to unserialize the color
*/
void fromXML(const QString &xml);
/**
* @brief toQString create a user-visible string of the channel names and the channel values
* @return a string that can be used to display the values of this color to the user.
*/
QString toQString();
private:
friend class View;
friend class PaletteView;
friend class Swatch;
KoColor color() const;
struct Private;
const QScopedPointer<Private> d;
};
#endif // MANAGEDCOLOR_H
diff --git a/libs/odf/KoPageFormat.cpp b/libs/odf/KoPageFormat.cpp
index ee6be9342b..686e79c34f 100644
--- a/libs/odf/KoPageFormat.cpp
+++ b/libs/odf/KoPageFormat.cpp
@@ -1,181 +1,181 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright 2002, 2003 David Faure <faure@kde.org>
Copyright 2003 Nicolas GOUTTE <goutte@kde.org>
Copyright 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 "KoPageFormat.h"
#include <klocalizedstring.h>
#include <OdfDebug.h>
// paper formats ( mm )
#define PG_A3_WIDTH 297.0
#define PG_A3_HEIGHT 420.0
#define PG_A4_WIDTH 210.0
#define PG_A4_HEIGHT 297.0
#define PG_A5_WIDTH 148.0
#define PG_A5_HEIGHT 210.0
#define PG_B5_WIDTH 182.0
#define PG_B5_HEIGHT 257.0
#define PG_US_LETTER_WIDTH 216.0
#define PG_US_LETTER_HEIGHT 279.0
#define PG_US_LEGAL_WIDTH 216.0
#define PG_US_LEGAL_HEIGHT 356.0
#define PG_US_EXECUTIVE_WIDTH 191.0
#define PG_US_EXECUTIVE_HEIGHT 254.0
struct PageFormatInfo {
KoPageFormat::Format format;
QPrinter::PageSize qprinter;
const char* shortName; // Short name
const char* descriptiveName; // Full name, which will be translated
qreal width; // in mm
qreal height; // in mm
};
// NOTES:
// - the width and height of non-ISO formats are rounded
-// http://en.wikipedia.org/wiki/Paper_size can help
+// https://en.wikipedia.org/wiki/Paper_size can help
// - the comments "should be..." indicates the exact values if the inch sizes would be multiplied by 25.4 mm/inch
const PageFormatInfo pageFormatInfo[] = {
{ KoPageFormat::IsoA3Size, QPrinter::A3, "A3", I18N_NOOP2("Page size", "ISO A3"), 297.0, 420.0 },
{ KoPageFormat::IsoA4Size, QPrinter::A4, "A4", I18N_NOOP2("Page size", "ISO A4"), 210.0, 297.0 },
{ KoPageFormat::IsoA5Size, QPrinter::A5, "A5", I18N_NOOP2("Page size", "ISO A5"), 148.0, 210.0 },
{ KoPageFormat::UsLetterSize, QPrinter::Letter, "Letter", I18N_NOOP2("Page size", "US Letter"), 215.9, 279.4 },
{ KoPageFormat::UsLegalSize, QPrinter::Legal, "Legal", I18N_NOOP2("Page size", "US Legal"), 215.9, 355.6 },
{ KoPageFormat::ScreenSize, QPrinter::A4, "Screen", I18N_NOOP2("Page size", "Screen"), PG_A4_HEIGHT, PG_A4_WIDTH }, // Custom, so fall back to A4
{ KoPageFormat::CustomSize, QPrinter::A4, "Custom", I18N_NOOP2("Page size", "Custom"), PG_A4_WIDTH, PG_A4_HEIGHT }, // Custom, so fall back to A4
{ KoPageFormat::IsoB5Size, QPrinter::B5, "B5", I18N_NOOP2("Page size", "ISO B5"), 182.0, 257.0 },
{ KoPageFormat::UsExecutiveSize, QPrinter::Executive, "Executive", I18N_NOOP2("Page size", "US Executive"), 191.0, 254.0 }, // should be 190.5 mm x 254.0 mm
{ KoPageFormat::IsoA0Size, QPrinter::A0, "A0", I18N_NOOP2("Page size", "ISO A0"), 841.0, 1189.0 },
{ KoPageFormat::IsoA1Size, QPrinter::A1, "A1", I18N_NOOP2("Page size", "ISO A1"), 594.0, 841.0 },
{ KoPageFormat::IsoA2Size, QPrinter::A2, "A2", I18N_NOOP2("Page size", "ISO A2"), 420.0, 594.0 },
{ KoPageFormat::IsoA6Size, QPrinter::A6, "A6", I18N_NOOP2("Page size", "ISO A6"), 105.0, 148.0 },
{ KoPageFormat::IsoA7Size, QPrinter::A7, "A7", I18N_NOOP2("Page size", "ISO A7"), 74.0, 105.0 },
{ KoPageFormat::IsoA8Size, QPrinter::A8, "A8", I18N_NOOP2("Page size", "ISO A8"), 52.0, 74.0 },
{ KoPageFormat::IsoA9Size, QPrinter::A9, "A9", I18N_NOOP2("Page size", "ISO A9"), 37.0, 52.0 },
{ KoPageFormat::IsoB0Size, QPrinter::B0, "B0", I18N_NOOP2("Page size", "ISO B0"), 1030.0, 1456.0 },
{ KoPageFormat::IsoB1Size, QPrinter::B1, "B1", I18N_NOOP2("Page size", "ISO B1"), 728.0, 1030.0 },
{ KoPageFormat::IsoB10Size, QPrinter::B10, "B10", I18N_NOOP2("Page size", "ISO B10"), 32.0, 45.0 },
{ KoPageFormat::IsoB2Size, QPrinter::B2, "B2", I18N_NOOP2("Page size", "ISO B2"), 515.0, 728.0 },
{ KoPageFormat::IsoB3Size, QPrinter::B3, "B3", I18N_NOOP2("Page size", "ISO B3"), 364.0, 515.0 },
{ KoPageFormat::IsoB4Size, QPrinter::B4, "B4", I18N_NOOP2("Page size", "ISO B4"), 257.0, 364.0 },
{ KoPageFormat::IsoB6Size, QPrinter::B6, "B6", I18N_NOOP2("Page size", "ISO B6"), 128.0, 182.0 },
{ KoPageFormat::IsoC5Size, QPrinter::C5E, "C5", I18N_NOOP2("Page size", "ISO C5"), 163.0, 229.0 }, // Some sources tells: 162 mm x 228 mm
{ KoPageFormat::UsComm10Size, QPrinter::Comm10E, "Comm10", I18N_NOOP2("Page size", "US Common 10"), 105.0, 241.0 }, // should be 104.775 mm x 241.3 mm
{ KoPageFormat::IsoDLSize, QPrinter::DLE, "DL", I18N_NOOP2("Page size", "ISO DL"), 110.0, 220.0 },
{ KoPageFormat::UsFolioSize, QPrinter::Folio, "Folio", I18N_NOOP2("Page size", "US Folio"), 210.0, 330.0 }, // should be 209.54 mm x 330.2 mm
{ KoPageFormat::UsLedgerSize, QPrinter::Ledger, "Ledger", I18N_NOOP2("Page size", "US Ledger"), 432.0, 279.0 }, // should be 431.8 mm x 297.4 mm
{ KoPageFormat::UsTabloidSize, QPrinter::Tabloid, "Tabloid", I18N_NOOP2("Page size", "US Tabloid"), 279.0, 432.0 }, // should be 297.4 mm x 431.8 mm
{ KoPageFormat::Invalid, QPrinter::Custom, "", "", -1, -1 }
};
QPrinter::PageSize KoPageFormat::printerPageSize(KoPageFormat::Format format)
{
if (format == ScreenSize) {
warnOdf << "You use the page layout SCREEN. Printing in ISO A4 Landscape.";
return QPrinter::A4;
}
if (format == CustomSize) {
warnOdf << "The used page layout (Custom) is not supported by KQPrinter. Printing in A4.";
return QPrinter::A4;
}
return pageFormatInfo[format].qprinter;
}
qreal KoPageFormat::width(Format format, Orientation orientation)
{
if (orientation == Landscape)
return height(format, Portrait);
return pageFormatInfo[format].width;
}
qreal KoPageFormat::height(Format format, Orientation orientation)
{
if (orientation == Landscape)
return width(format, Portrait);
return pageFormatInfo[format].height;
}
KoPageFormat::Format KoPageFormat::guessFormat(qreal width, qreal height)
{
for (int i = 0; pageFormatInfo[i].format != KoPageFormat::Invalid ;i++) {
// We have some tolerance. 1pt is a third of a mm, this is
// barely noticeable for a page size.
if (qAbs(width - pageFormatInfo[i].width) < 1.0 && qAbs(height - pageFormatInfo[i].height) < 1.0)
return pageFormatInfo[i].format;
}
return CustomSize;
}
QString KoPageFormat::formatString(Format format)
{
return QString::fromLatin1(pageFormatInfo[format].shortName);
}
KoPageFormat::Format KoPageFormat::formatFromString(const QString & string)
{
for (int i = 0; pageFormatInfo[i].format != KoPageFormat::Invalid ;i++) {
if (string == QString::fromLatin1(pageFormatInfo[i].shortName))
return pageFormatInfo[i].format;
}
// We do not know the format name, so we have a custom format
return CustomSize;
}
KoPageFormat::Format KoPageFormat::defaultFormat()
{
int qprinter;
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
qprinter = static_cast<int>(QPageSize::Letter);
}
else {
qprinter = static_cast<int>(QPageSize::A4);
}
for (int i = 0; pageFormatInfo[i].format != KoPageFormat::Invalid ;i++) {
if (pageFormatInfo[i].qprinter == qprinter)
return static_cast<Format>(i);
}
return IsoA4Size;
}
QString KoPageFormat::name(Format format)
{
return i18nc("Page size", pageFormatInfo[format].descriptiveName);
}
QStringList KoPageFormat::localizedPageFormatNames()
{
QStringList lst;
for (int i = 0; pageFormatInfo[i].format != KoPageFormat::Invalid ;i++) {
lst << i18nc("Page size", pageFormatInfo[i].descriptiveName);
}
return lst;
}
QStringList KoPageFormat::pageFormatNames()
{
QStringList lst;
for (int i = 0; pageFormatInfo[i].format != KoPageFormat::Invalid ;i++) {
lst << pageFormatInfo[i].shortName;
}
return lst;
}
diff --git a/libs/pigment/KoCmykColorSpaceTraits.h b/libs/pigment/KoCmykColorSpaceTraits.h
index a81385468f..ec1f9dbb7b 100644
--- a/libs/pigment/KoCmykColorSpaceTraits.h
+++ b/libs/pigment/KoCmykColorSpaceTraits.h
@@ -1,238 +1,238 @@
/*
* Copyright (c) 2006-2007 Cyrille Berger <cberger@cberger.net>
- * Copyright (c) 2016 L. E. Segovia <leo.segovia@siggraph.org>
+ * Copyright (c) 2016 L. E. Segovia <amy@amyspark.me>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KO_CMYK_COLORSPACE_TRAITS_H_
#define _KO_CMYK_COLORSPACE_TRAITS_H_
#include <QVector>
#include "KoColorSpaceConstants.h"
#include "KoColorSpaceMaths.h"
#include "DebugPigment.h"
/**
* Base class for CMYK traits, it provides some convenient functions to
* access CMYK channels through an explicit API.
*/
template<typename _channels_type_>
struct KoCmykTraits : public KoColorSpaceTrait<_channels_type_, 5, 4> {
typedef _channels_type_ channels_type;
typedef KoColorSpaceTrait<_channels_type_, 5, 4> parent;
static const qint32 c_pos = 0;
static const qint32 m_pos = 1;
static const qint32 y_pos = 2;
static const qint32 k_pos = 3;
/**
* An CMYK pixel
*/
struct Pixel {
channels_type cyan;
channels_type magenta;
channels_type yellow;
channels_type black;
channels_type alpha;
};
/// @return the Cyan component
inline static channels_type C(quint8* data) {
channels_type* d = parent::nativeArray(data);
return d[c_pos];
}
/// Set the Cyan component
inline static void setC(quint8* data, channels_type nv) {
channels_type* d = parent::nativeArray(data);
d[c_pos] = nv;
}
/// @return the Magenta component
inline static channels_type M(quint8* data) {
channels_type* d = parent::nativeArray(data);
return d[m_pos];
}
/// Set the Magenta component
inline static void setM(quint8* data, channels_type nv) {
channels_type* d = parent::nativeArray(data);
d[m_pos] = nv;
}
/// @return the Yellow component
inline static channels_type Y(quint8* data) {
channels_type* d = parent::nativeArray(data);
return d[y_pos];
}
/// Set the Yellow component
inline static void setY(quint8* data, channels_type nv) {
channels_type* d = parent::nativeArray(data);
d[y_pos] = nv;
}
/// @return the Key component
inline static channels_type k(quint8* data) {
channels_type* d = parent::nativeArray(data);
return d[k_pos];
}
/// Set the Key component
inline static void setK(quint8* data, channels_type nv) {
channels_type* d = parent::nativeArray(data);
d[k_pos] = nv;
}
};
struct KoCmykU8Traits : public KoCmykTraits<quint8> {
};
struct KoCmykU16Traits : public KoCmykTraits<quint16> {
};
#include <KoConfig.h>
#ifdef HAVE_OPENEXR
#include <half.h>
struct KoCmykF16Traits : public KoCmykTraits<half> {
static constexpr float MAX_CHANNEL_CMYK = 100;
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
return channelValueText(pixel, channelIndex);
}
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() == (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = parent::nativeArray(pixel)[i];
channels[i] = (qreal)c;
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() == (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
float b = 0;
switch(i) {
case c_pos:
case m_pos:
case y_pos:
case k_pos:
b = qBound((float)0,
(float)KoColorSpaceMathsTraits<float>::unitValue * values[i],
(float)MAX_CHANNEL_CMYK);
break;
default:
b = qBound((float)KoColorSpaceMathsTraits<float>::min,
(float)KoColorSpaceMathsTraits<float>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<float>::max);
break;
}
c = (channels_type)b;
parent::nativeArray(pixel)[i] = c;
}
}
};
#endif
struct KoCmykF32Traits : public KoCmykTraits<float> {
static constexpr float MAX_CHANNEL_CMYK = 100;
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
return channelValueText(pixel, channelIndex);
}
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() == (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = parent::nativeArray(pixel)[i];
channels[i] = (qreal)c;
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() == (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
float b = 0;
switch(i) {
case c_pos:
case m_pos:
case y_pos:
case k_pos:
b = qBound((float)0,
(float)KoColorSpaceMathsTraits<float>::unitValue * values[i],
(float)MAX_CHANNEL_CMYK);
break;
default:
b = qBound((float)KoColorSpaceMathsTraits<float>::min,
(float)KoColorSpaceMathsTraits<float>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<float>::max);
break;
}
c = (channels_type)b;
parent::nativeArray(pixel)[i] = c;
}
}
};
struct KoCmykF64Traits : public KoCmykTraits<double> {
static constexpr double MAX_CHANNEL_CMYK = 100;
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
return channelValueText(pixel, channelIndex);
}
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() == (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = parent::nativeArray(pixel)[i];
channels[i] = (qreal)c;
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() == (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
float b = 0;
switch(i) {
case c_pos:
case m_pos:
case y_pos:
case k_pos:
b = qBound((double)0,
(double)KoColorSpaceMathsTraits<double>::unitValue * values[i],
(double)MAX_CHANNEL_CMYK);
break;
default:
b = qBound((double)KoColorSpaceMathsTraits<double>::min,
(double)KoColorSpaceMathsTraits<double>::unitValue * values[i],
(double)KoColorSpaceMathsTraits<double>::max);
break;
}
c = (channels_type)b;
parent::nativeArray(pixel)[i] = c;
}
}
};
#endif
diff --git a/libs/pigment/KoColor.h b/libs/pigment/KoColor.h
index b231e484fc..cc8fd2d8f7 100644
--- a/libs/pigment/KoColor.h
+++ b/libs/pigment/KoColor.h
@@ -1,293 +1,293 @@
/*
* 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 <QtGlobal>
#include "kritapigment_export.h"
#include "KoColorConversionTransformation.h"
#include "KoColorSpaceRegistry.h"
#include "KoColorSpaceTraits.h"
#include <boost/operators.hpp>
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:
/// Create an empty KoColor. It will be valid, but also black and transparent
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 rhs 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;
}
if (m_size != other.m_size) {
return false;
}
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
void convertTo(const KoColorSpace * cs,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags);
void convertTo(const KoColorSpace * cs);
/// Copies this color and converts it to the specified colorspace. If the specified colorspace is the
/// same as the original colorspace, just returns a copy
KoColor convertedTo(const KoColorSpace * cs,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const;
/// Copies this color and converts it to the specified colorspace. If the specified colorspace is the
/// same as the original colorspace, just returns a copy
KoColor convertedTo(const KoColorSpace * cs) const;
/// 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;
}
/**
* Channelwise subtracts \p value from *this and stores the result in *this
*
* Throws a safe assert if the colorspaces of the two colors are different
*/
void subtract(const KoColor &value);
/**
* Channelwise subtracts \p value from a copy of *this and returns the result
*
* Throws a safe assert if the colorspaces of the two colors are different
*/
KoColor subtracted(const KoColor &value) const;
/**
* Channelwise adds \p value to *this and stores the result in *this
*
* Throws a safe assert if the colorspaces of the two colors are different
*/
void add(const KoColor &value);
/**
* Channelwise adds \p value to a copy of *this and returns the result
*
* Throws a safe assert if the colorspaces of the two colors are different
*/
KoColor added(const KoColor &value) const;
/**
* Serialize this color following Create's swatch color specification available
- * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
+ * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft
*
* 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
+ * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft
*
* @param elt the element to unserialize (\<CMYK /\>, \<sRGB /\>, \<RGB /\>)
* @param channelDepthId 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 & channelDepthId);
/**
* Unserialize a color following Create's swatch color specification available
- * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
+ * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft
*
* @param elt the element to unserialize (\<CMYK /\>, \<sRGB /\>, \<RGB /\>)
* @param channelDepthId 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 & channelDepthId, bool* ok);
/**
* @brief toXML creates a string with XML that represents the current color. The XML
* is extended with a "channeldepth" attribute so we can restore the color to the same
* channel depth.
* @return a valid XML document in a string
*/
QString toXML() const;
/**
* @brief fromXML restores a KoColor from a string saved with toXML(). If the
* string does not contain the "channeldepth" attribute, 16 bit integer is assumed.
* @param xml a valid XML document
* @return a new KoColor object
*/
static KoColor fromXML(const QString &xml);
/**
* @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;
};
Q_DECLARE_METATYPE(KoColor)
KRITAPIGMENT_EXPORT QDebug operator<<(QDebug dbg, const KoColor &color);
#endif
diff --git a/libs/pigment/KoColorSpace.h b/libs/pigment/KoColorSpace.h
index 036760e00d..df314198c4 100644
--- a/libs/pigment/KoColorSpace.h
+++ b/libs/pigment/KoColorSpace.h
@@ -1,646 +1,646 @@
/*
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2006-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.
*/
#ifndef KOCOLORSPACE_H
#define KOCOLORSPACE_H
#include <limits.h>
#include <QImage>
#include <QHash>
#include <QVector>
#include <QList>
#include <boost/operators.hpp>
#include "KoColorSpaceConstants.h"
#include "KoColorConversionTransformation.h"
#include "KoColorProofingConversionTransformation.h"
#include "KoCompositeOp.h"
#include <KoID.h>
#include "kritapigment_export.h"
class QDomDocument;
class QDomElement;
class KoChannelInfo;
class KoColorProfile;
class KoColorTransformation;
class QBitArray;
enum Deletability {
OwnedByRegistryDoNotDelete,
OwnedByRegistryRegistryDeletes,
NotOwnedByRegistry
};
enum ColorSpaceIndependence {
FULLY_INDEPENDENT,
TO_LAB16,
TO_RGBA8,
TO_RGBA16
};
class KoMixColorsOp;
class KoConvolutionOp;
/**
* A KoColorSpace is the definition of a certain color space.
*
* A color model and a color space are two related concepts. A color
* model is more general in that it describes the channels involved and
* how they in broad terms combine to describe a color. Examples are
* RGB, HSV, CMYK.
*
* A color space is more specific in that it also describes exactly how
* the channels are combined. So for each color model there can be a
* number of specific color spaces. So RGB is the model and sRGB,
* adobeRGB, etc are colorspaces.
*
* In Pigment KoColorSpace acts as both a color model and a color space.
* You can think of the class definition as the color model, but the
* instance of the class as representing a colorspace.
*
* A third concept is the profile represented by KoColorProfile. It
* represents the info needed to specialize a color model into a color
* space.
*
* KoColorSpace is an abstract class serving as an interface.
*
* Subclasses implement actual color spaces
* Some subclasses implement only some parts and are named Traits
*
*/
class KRITAPIGMENT_EXPORT KoColorSpace : public boost::equality_comparable<KoColorSpace>
{
friend class KoColorSpaceRegistry;
friend class KoColorSpaceFactory;
protected:
/// Only for use by classes that serve as baseclass for real color spaces
KoColorSpace();
public:
/// Should be called by real color spaces
KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp);
virtual bool operator==(const KoColorSpace& rhs) const;
protected:
virtual ~KoColorSpace();
public:
//========== Gamut and other basic info ===================================//
/*
* @returns QPolygonF with 5*channel samples converted to xyY.
* maybe convert to 3d space in future?
*/
QPolygonF gamutXYY() const;
/*
* @returns a polygon with 5 samples per channel converted to xyY, but unlike
* gamutxyY it focuses on the luminance. This then can be used to visualise
* the approximate trc of a given colorspace.
*/
QPolygonF estimatedTRCXYY() const;
QVector <qreal> lumaCoefficients() const;
//========== Channels =====================================================//
/// Return a list describing all the channels this color model has. The order
/// of the channels in the list is the order of channels in the pixel. To find
/// out the preferred display position, use KoChannelInfo::displayPosition.
QList<KoChannelInfo *> channels() const;
/**
* The total number of channels for a single pixel in this color model
*/
virtual quint32 channelCount() const = 0;
/**
* Position of the alpha channel in a pixel
*/
virtual quint32 alphaPos() const = 0;
/**
* The total number of color channels (excludes alpha) for a single
* pixel in this color model.
*/
virtual quint32 colorChannelCount() const = 0;
/**
* returns a QBitArray that contains true for the specified
* channel types:
*
* @param color if true, set all color channels to true
* @param alpha if true, set all alpha channels to true
*
* The order of channels is the colorspace descriptive order,
* not the pixel order.
*/
QBitArray channelFlags(bool color = true, bool alpha = false) const;
/**
* The size in bytes of a single pixel in this color model
*/
virtual quint32 pixelSize() const = 0;
/**
* Return a string with the channel's value suitable for display in the gui.
*/
virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const = 0;
/**
* Return a string with the channel's value with integer
* channels normalised to the floating point range 0 to 1, if
* appropriate.
*/
virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const = 0;
/**
* Return a QVector of floats with channels' values normalized
* to floating point range 0 to 1.
*/
virtual void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) const = 0;
/**
* Write in the pixel the value from the normalized vector.
*/
virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) const = 0;
/**
* Convert the value of the channel at the specified position into
* an 8-bit value. The position is not the number of bytes, but
* the position of the channel as defined in the channel info list.
*/
virtual quint8 scaleToU8(const quint8 * srcPixel, qint32 channelPos) const = 0;
/**
* Set dstPixel to the pixel containing only the given channel of srcPixel. The remaining channels
* should be set to whatever makes sense for 'empty' channels of this color space,
* with the intent being that the pixel should look like it only has the given channel.
*/
virtual void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) const = 0;
//========== Identification ===============================================//
/**
* ID for use in files and internally: unchanging name. As the id must be unique
* it is usually the concatenation of the id of the color model and of the color
* depth, for instance "RGBA8" or "CMYKA16" or "XYZA32f".
*/
QString id() const;
/**
* User visible name which contains the name of the color model and of the color depth.
* For instance "RGBA (8-bits)" or "CMYKA (16-bits)".
*/
QString name() const;
/**
* @return a string that identify the color model (for instance "RGB" or "CMYK" ...)
* @see KoColorModelStandardIds.h
*/
virtual KoID colorModelId() const = 0;
/**
* @return a string that identify the bit depth (for instance "U8" or "F16" ...)
* @see KoColorModelStandardIds.h
*/
virtual KoID colorDepthId() const = 0;
/**
* @return true if the profile given in argument can be used by this color space
*/
virtual bool profileIsCompatible(const KoColorProfile* profile) const = 0;
/**
* If false, images in this colorspace will degrade considerably by
* functions, tools and filters that have the given measure of colorspace
* independence.
*
* @param independence the measure to which this colorspace will suffer
* from the manipulations of the tool or filter asking
* @return false if no degradation will take place, true if degradation will
* take place
*/
virtual bool willDegrade(ColorSpaceIndependence independence) const = 0;
//========== Capabilities =================================================//
/**
* Tests if the colorspace offers the specific composite op.
*/
virtual bool hasCompositeOp(const QString & id) const;
/**
* Returns the list of user-visible composite ops supported by this colorspace.
*/
virtual QList<KoCompositeOp*> compositeOps() const;
/**
* Retrieve a single composite op from the ones this colorspace offers.
* If the requeste composite op does not exist, COMPOSITE_OVER is returned.
*/
const KoCompositeOp * compositeOp(const QString & id) const;
/**
* add a composite op to this colorspace.
*/
virtual void addCompositeOp(const KoCompositeOp * op);
/**
* Returns true if the colorspace supports channel values outside the
* (normalised) range 0 to 1.
*/
virtual bool hasHighDynamicRange() const = 0;
//========== Display profiles =============================================//
/**
* Return the profile of this color space.
*/
virtual const KoColorProfile * profile() const = 0;
//================= Conversion functions ==================================//
/**
* The fromQColor methods take a given color defined as an RGB QColor
* and fills a byte array with the corresponding color in the
* the colorspace managed by this strategy.
*
* @param color the QColor that will be used to fill dst
* @param dst a pointer to a pixel
* @param profile the optional profile that describes the color values of QColor
*/
virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const = 0;
/**
* The toQColor methods take a byte array that is at least pixelSize() long
* and converts the contents to a QColor, using the given profile as a source
* profile and the optional profile as a destination profile.
*
* @param src a pointer to the source pixel
* @param c the QColor that will be filled with the color at src
* @param profile the optional profile that describes the color in c, for instance the monitor profile
*/
virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const = 0;
/**
* Convert the pixels in data to (8-bit BGRA) QImage using the specified profiles.
*
* @param data A pointer to a contiguous memory region containing width * height pixels
* @param width in pixels
* @param height in pixels
* @param dstProfile destination profile
* @param renderingIntent the rendering intent
* @param conversionFlags conversion flags
*/
virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height,
const KoColorProfile * dstProfile,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const;
/**
* Convert the specified data to Lab (D50). All colorspaces are guaranteed to support this
*
* @param src the source data
* @param dst the destination data
* @param nPixels the number of source pixels
*/
virtual void toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const;
/**
* Convert the specified data from Lab (D50). to this colorspace. All colorspaces are
* guaranteed to support this.
*
* @param src the pixels in 16 bit lab format
* @param dst the destination data
* @param nPixels the number of pixels in the array
*/
virtual void fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const;
/**
* Convert the specified data to sRGB 16 bits. All colorspaces are guaranteed to support this
*
* @param src the source data
* @param dst the destination data
* @param nPixels the number of source pixels
*/
virtual void toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const;
/**
* Convert the specified data from sRGB 16 bits. to this colorspace. All colorspaces are
* guaranteed to support this.
*
* @param src the pixels in 16 bit rgb format
* @param dst the destination data
* @param nPixels the number of pixels in the array
*/
virtual void fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const;
/**
* Create a color conversion transformation.
*/
virtual KoColorConversionTransformation* createColorConverter(const KoColorSpace * dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const;
/**
* Convert a byte array of srcLen pixels *src to the specified color space
* and put the converted bytes into the prepared byte array *dst.
*
* Returns false if the conversion failed, true if it succeeded
*
* This function is not thread-safe. If you want to apply multiple conversion
* in different threads at the same time, you need to create one color converter
* per-thread using createColorConverter.
*/
virtual bool convertPixelsTo(const quint8 * src,
quint8 * dst, const KoColorSpace * dstColorSpace,
quint32 numPixels,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const;
virtual KoColorConversionTransformation *createProofingTransform(const KoColorSpace * dstColorSpace,
const KoColorSpace * proofingSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::Intent proofingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags,
quint8 *gamutWarning, double adaptationState) const;
/**
* @brief proofPixelsTo
* @param src source
* @param dst destination
* @param numPixels the amount of pixels.
* @param proofingTransform the intent used for proofing.
* @return
*/
virtual bool proofPixelsTo(const quint8 * src,
quint8 * dst,
quint32 numPixels,
KoColorConversionTransformation *proofingTransform) const;
//============================== Manipulation functions ==========================//
//
// The manipulation functions have default implementations that _convert_ the pixel
// to a QColor and back. Reimplement these methods in your color strategy!
//
/**
* Get the alpha value of the given pixel, downscaled to an 8-bit value.
*/
virtual quint8 opacityU8(const quint8 * pixel) const = 0;
virtual qreal opacityF(const quint8 * pixel) const = 0;
/**
* Set the alpha channel of the given run of pixels to the given value.
*
* pixels -- a pointer to the pixels that will have their alpha set to this value
* alpha -- a downscaled 8-bit value for opacity
* nPixels -- the number of pixels
*
*/
virtual void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0;
virtual void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) const = 0;
/**
* Multiply the alpha channel of the given run of pixels by the given value.
*
* pixels -- a pointer to the pixels that will have their alpha set to this value
* alpha -- a downscaled 8-bit value for opacity
* nPixels -- the number of pixels
*
*/
virtual void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0;
/**
* Applies the specified 8-bit alpha mask to the pixels. We assume that there are just
* as many alpha values as pixels but we do not check this; the alpha values
* are assumed to be 8-bits.
*/
virtual void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0;
/**
* Applies the inverted 8-bit alpha mask to the pixels. We assume that there are just
* as many alpha values as pixels but we do not check this; the alpha values
* are assumed to be 8-bits.
*/
virtual void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0;
/**
* Applies the specified float alpha mask to the pixels. We assume that there are just
* as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0
*/
virtual void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0;
/**
* Applies the inverted specified float alpha mask to the pixels. We assume that there are just
* as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0
*/
virtual void applyInverseNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0;
/**
* Create an adjustment object for adjusting the brightness and contrast
* transferValues is a 256 bins array with values from 0 to 0xFFFF
* This function is thread-safe, but you need to create one KoColorTransformation per thread.
*/
virtual KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const = 0;
/**
* Create an adjustment object for adjusting individual channels
* transferValues is an array of colorChannelCount number of 256 bins array with values from 0 to 0xFFFF
* This function is thread-safe, but you need to create one KoColorTransformation per thread.
*
* The layout of the channels must be the following:
*
* 0..N-2 - color channels of the pixel;
* N-1 - alpha channel of the pixel (if exists)
*/
virtual KoColorTransformation *createPerChannelAdjustment(const quint16 * const* transferValues) const = 0;
/**
* Darken all color channels with the given amount. If compensate is true,
* the compensation factor will be used to limit the darkening.
*
*/
virtual KoColorTransformation *createDarkenAdjustment(qint32 shade, bool compensate, qreal compensation) const = 0;
/**
* Invert color channels of the given pixels
* This function is thread-safe, but you need to create one KoColorTransformation per thread.
*/
virtual KoColorTransformation *createInvertTransformation() const = 0;
/**
* Get the difference between 2 colors, normalized in the range (0,255). Only completely
* opaque and completely transparent are taken into account when computing the difference;
* other transparency levels are not regarded when finding the difference.
*/
virtual quint8 difference(const quint8* src1, const quint8* src2) const = 0;
/**
* Get the difference between 2 colors, normalized in the range (0,255). This function
* takes the Alpha channel of the pixel into account. Alpha channel has the same
* weight as Lightness channel.
*/
virtual quint8 differenceA(const quint8* src1, const quint8* src2) const = 0;
/**
* @return the mix color operation of this colorspace (do not delete it locally, it's deleted by the colorspace).
*/
virtual KoMixColorsOp* mixColorsOp() const;
/**
* @return the convolution operation of this colorspace (do not delete it locally, it's deleted by the colorspace).
*/
virtual KoConvolutionOp* convolutionOp() const;
/**
* Calculate the intensity of the given pixel, scaled down to the range 0-255. XXX: Maybe this should be more flexible
*/
virtual quint8 intensity8(const quint8 * src) const = 0;
/*
*increase luminosity by step
*/
virtual void increaseLuminosity(quint8 * pixel, qreal step) const;
virtual void decreaseLuminosity(quint8 * pixel, qreal step) const;
virtual void increaseSaturation(quint8 * pixel, qreal step) const;
virtual void decreaseSaturation(quint8 * pixel, qreal step) const;
virtual void increaseHue(quint8 * pixel, qreal step) const;
virtual void decreaseHue(quint8 * pixel, qreal step) const;
virtual void increaseRed(quint8 * pixel, qreal step) const;
virtual void increaseGreen(quint8 * pixel, qreal step) const;
virtual void increaseBlue(quint8 * pixel, qreal step) const;
virtual void increaseYellow(quint8 * pixel, qreal step) const;
virtual void toHSY(const QVector<double> &channelValues, qreal *hue, qreal *sat, qreal *luma) const = 0;
virtual QVector <double> fromHSY(qreal *hue, qreal *sat, qreal *luma) const = 0;
virtual void toYUV(const QVector<double> &channelValues, qreal *y, qreal *u, qreal *v) const = 0;
virtual QVector <double> fromYUV(qreal *y, qreal *u, qreal *v) const = 0;
/**
* Compose two arrays of pixels together. If source and target
* are not the same color model, the source pixels will be
* converted to the target model. We're "dst" -- "dst" pixels are always in _this_
* colorspace.
*
* @param srcSpace the colorspace of the source pixels that will be composited onto "us"
* @param params the information needed for blitting e.g. the source and destination pixel data,
* the opacity and flow, ...
* @param op the composition operator to use, e.g. COPY_OVER
* @param renderingIntent the rendering intent
* @param conversionFlags the conversion flags.
*
*/
virtual void bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const;
/**
* Serialize this color following Create's swatch color specification available
- * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
+ * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft
*
* 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 pixel buffer to serialized
* @param colorElt root element for the serialization, it is assumed that this
* element is \<color /\>
* @param doc is the document containing colorElt
*/
virtual void colorToXML(const quint8* pixel, QDomDocument& doc, QDomElement& colorElt) const = 0;
/**
* Unserialize a color following Create's swatch color specification available
- * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
+ * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft
*
* @param pixel buffer where the color will be unserialized
* @param elt the element to unserialize (\<CMYK /\>, \<sRGB /\>, \<RGB /\>)
* @return the unserialize color, or an empty color object if the function failed
* to unserialize the color
*/
virtual void colorFromXML(quint8* pixel, const QDomElement& elt) const = 0;
KoColorTransformation* createColorTransformation(const QString & id, const QHash<QString, QVariant> & parameters) const;
protected:
/**
* Use this function in the constructor of your colorspace to add the information about a channel.
* @param ci a pointer to the information about a channel
*/
virtual void addChannel(KoChannelInfo * ci);
const KoColorConversionTransformation* toLabA16Converter() const;
const KoColorConversionTransformation* fromLabA16Converter() const;
const KoColorConversionTransformation* toRgbA16Converter() const;
const KoColorConversionTransformation* fromRgbA16Converter() const;
/**
* Returns the thread-local conversion cache. If it doesn't exist
* yet, it is created. If it is currently too small, it is resized.
*/
QVector<quint8> * threadLocalConversionCache(quint32 size) const;
/**
* This function defines the behavior of the bitBlt function
* when the composition of pixels in different colorspaces is
* requested, that is in case:
*
* srcCS == any
* dstCS == this
*
* 1) preferCompositionInSourceColorSpace() == false,
*
* the source pixels are first converted to *this color space
* and then composition is performed.
*
* 2) preferCompositionInSourceColorSpace() == true,
*
* the destination pixels are first converted into *srcCS color
* space, then the composition is done, and the result is finally
* converted into *this colorspace.
*
* This is used by alpha8() color space mostly, because it has
* weaker representation of the color, so the composition
* should be done in CS with richer functionality.
*/
virtual bool preferCompositionInSourceColorSpace() const;
struct Private;
Private * const d;
};
inline QDebug operator<<(QDebug dbg, const KoColorSpace *cs)
{
if (cs) {
dbg.nospace() << cs->name() << " (" << cs->colorModelId().id() << "," << cs->colorDepthId().id() << " )";
} else {
dbg.nospace() << "0x0";
}
return dbg.space();
}
#endif // KOCOLORSPACE_H
diff --git a/libs/pigment/KoLabColorSpaceTraits.h b/libs/pigment/KoLabColorSpaceTraits.h
index 925aba8f1c..eba27527d5 100644
--- a/libs/pigment/KoLabColorSpaceTraits.h
+++ b/libs/pigment/KoLabColorSpaceTraits.h
@@ -1,400 +1,400 @@
/*
* Copyright (c) 2006-2007 Cyrille Berger <cberger@cberger.net>
- * Copyright (c) 2016 L. E. Segovia <leo.segovia@siggraph.org>
+ * Copyright (c) 2016 L. E. Segovia <amy@amyspark.me>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KO_LAB_COLORSPACE_TRAITS_H_
#define _KO_LAB_COLORSPACE_TRAITS_H_
/**
* LAB traits, it provides some convenient functions to
* access LAB channels through an explicit API.
*
* Use this class in conjonction with KoColorSpace::toLabA16 and
* KoColorSpace::fromLabA16 data.
*
* Example:
* quint8* p = KoLabU16Traits::allocate(1);
* oneKoColorSpace->toLabA16(somepointertodata, p, 1);
* KoLabU16Traits::setL( p, KoLabU16Traits::L(p) / 10 );
* oneKoColorSpace->fromLabA16(p, somepointertodata, 1);
*/
template<typename _channels_type_>
struct KoLabTraits : public KoColorSpaceTrait<_channels_type_, 4, 3> {
typedef _channels_type_ channels_type;
typedef KoColorSpaceTrait<_channels_type_, 4, 3> parent;
static const qint32 L_pos = 0;
static const qint32 a_pos = 1;
static const qint32 b_pos = 2;
/**
* An Lab pixel
*/
struct Pixel {
channels_type L;
channels_type a;
channels_type b;
channels_type alpha;
};
/// @return the L component
inline static channels_type L(quint8* data) {
channels_type* d = parent::nativeArray(data);
return d[L_pos];
}
/// Set the L component
inline static void setL(quint8* data, channels_type nv) {
channels_type* d = parent::nativeArray(data);
d[L_pos] = nv;
}
/// @return the a component
inline static channels_type a(quint8* data) {
channels_type* d = parent::nativeArray(data);
return d[a_pos];
}
/// Set the a component
inline static void setA(quint8* data, channels_type nv) {
channels_type* d = parent::nativeArray(data);
d[a_pos] = nv;
}
/// @return the b component
inline static channels_type b(quint8* data) {
channels_type* d = parent::nativeArray(data);
return d[b_pos];
}
/// Set the a component
inline static void setB(quint8* data, channels_type nv) {
channels_type* d = parent::nativeArray(data);
d[b_pos] = nv;
}
};
//For quint* values must range from 0 to 1 - see KoColorSpaceMaths<double, quint*>
struct KoLabU8Traits : public KoLabTraits<quint8> {
static const quint32 MAX_CHANNEL_L = 100;
static const quint32 MAX_CHANNEL_AB = 255;
static const quint32 CHANNEL_AB_ZERO_OFFSET = 128;
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = nativeArray(pixel)[i];
switch (i) {
case L_pos:
channels[i] = ((qreal)c) / MAX_CHANNEL_L;
break;
case a_pos:
channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB;
break;
case b_pos:
channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB;
break;
case 3:
channels[i] = ((qreal)c) / UINT16_MAX;
break;
default:
channels[i] = ((qreal)c) / KoColorSpaceMathsTraits<channels_type>::unitValue;
break;
}
}
}
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
if (channelIndex > parent::channels_nb) return QString("Error");
channels_type c = nativeArray(pixel)[channelIndex];
switch (channelIndex) {
case L_pos:
return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L);
case a_pos:
return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB));
case b_pos:
return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB));
case 3:
return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX);
default:
return QString("Error");
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < channels_nb; i++) {
float b = 0;
switch (i) {
case L_pos:
b = qBound((float)0,
(float)MAX_CHANNEL_L * values[i],
(float)MAX_CHANNEL_L);
break;
case a_pos:
case b_pos:
b = qBound((float)0,
(float)MAX_CHANNEL_AB * values[i] + CHANNEL_AB_ZERO_OFFSET,
(float)MAX_CHANNEL_AB);
break;
default:
b = qBound((float)KoColorSpaceMathsTraits<channels_type>::min,
(float)KoColorSpaceMathsTraits<channels_type>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<channels_type>::max);
break;
}
c = (channels_type)b;
nativeArray(pixel)[i] = c;
}
}
};
struct KoLabU16Traits : public KoLabTraits<quint16> {
// https://github.com/mm2/Little-CMS/blob/master/src/cmspcs.c
static const quint32 MAX_CHANNEL_L = 0xFF00;
static const quint32 MAX_CHANNEL_AB = 0xFFFF;
static const quint32 CHANNEL_AB_ZERO_OFFSET = 0x8000;
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = nativeArray(pixel)[i];
switch (i) {
case L_pos:
channels[i] = ((qreal)c) / MAX_CHANNEL_L;
break;
case a_pos:
channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB;
break;
case b_pos:
channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB;
break;
case 3:
channels[i] = ((qreal)c) / UINT16_MAX;
break;
default:
channels[i] = ((qreal)c) / KoColorSpaceMathsTraits<channels_type>::unitValue;
break;
}
}
}
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
if (channelIndex > parent::channels_nb) return QString("Error");
channels_type c = nativeArray(pixel)[channelIndex];
switch (channelIndex) {
case L_pos:
return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L);
case a_pos:
return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB));
case b_pos:
return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB));
case 3:
return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX);
default:
return QString("Error");
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < channels_nb; i++) {
float b = 0;
switch (i) {
case L_pos:
b = qBound((float)0,
(float)MAX_CHANNEL_L * values[i],
(float)MAX_CHANNEL_L);
break;
case a_pos:
case b_pos:
b = qBound((float)0,
(float)MAX_CHANNEL_AB * values[i] + CHANNEL_AB_ZERO_OFFSET,
(float)MAX_CHANNEL_AB);
break;
default:
b = qBound((float)KoColorSpaceMathsTraits<channels_type>::min,
(float)KoColorSpaceMathsTraits<channels_type>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<channels_type>::max);
break;
}
c = (channels_type)b;
nativeArray(pixel)[i] = c;
}
}
};
// Float values are not normalised
// XXX: is it really necessary to bind them to these ranges?
#include <KoConfig.h>
#ifdef HAVE_OPENEXR
#include <half.h>
struct KoLabF16Traits : public KoLabTraits<half> {
static constexpr float MIN_CHANNEL_L = 0;
static constexpr float MAX_CHANNEL_L = 100;
static constexpr float MIN_CHANNEL_AB = -128;
static constexpr float MAX_CHANNEL_AB = +127;
// Lab has some... particulars
// For instance, float et al. are NOT normalised
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
return channelValueText(pixel, channelIndex);
}
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = parent::nativeArray(pixel)[i];
channels[i] = (qreal)c;
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
float b = 0;
switch(i) {
case L_pos:
b = qBound((float)MIN_CHANNEL_L,
(float)KoColorSpaceMathsTraits<half>::unitValue * values[i],
(float)MAX_CHANNEL_L);
break;
case a_pos:
case b_pos:
b = qBound((float)MIN_CHANNEL_AB,
(float)KoColorSpaceMathsTraits<half>::unitValue * values[i],
(float)MAX_CHANNEL_AB);
break;
case 3:
b = qBound((float)KoColorSpaceMathsTraits<half>::min,
(float)KoColorSpaceMathsTraits<half>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<half>::max);
default:
break;
}
c = (channels_type)b;
parent::nativeArray(pixel)[i] = c;
}
}
};
#endif
struct KoLabF32Traits : public KoLabTraits<float> {
static constexpr float MIN_CHANNEL_L = 0;
static constexpr float MAX_CHANNEL_L = 100;
static constexpr float MIN_CHANNEL_AB = -128;
static constexpr float MAX_CHANNEL_AB = +127;
// Lab has some... particulars
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
return channelValueText(pixel, channelIndex);
}
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = parent::nativeArray(pixel)[i];
channels[i] = (qreal)c;
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
float b = 0;
switch(i) {
case L_pos:
b = qBound((float)MIN_CHANNEL_L,
(float)KoColorSpaceMathsTraits<float>::unitValue * values[i],
(float)MAX_CHANNEL_L);
break;
case a_pos:
case b_pos:
b = qBound((float)MIN_CHANNEL_AB,
(float)KoColorSpaceMathsTraits<float>::unitValue * values[i],
(float)MAX_CHANNEL_AB);
break;
case 3:
b = qBound((float)KoColorSpaceMathsTraits<float>::min,
(float)KoColorSpaceMathsTraits<float>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<float>::max);
default:
break;
}
c = (channels_type)b;
parent::nativeArray(pixel)[i] = c;
}
}
};
struct KoLabF64Traits : public KoLabTraits<double> {
static constexpr double MIN_CHANNEL_L = 0;
static constexpr double MAX_CHANNEL_L = 100;
static constexpr double MIN_CHANNEL_AB = -128;
static constexpr double MAX_CHANNEL_AB = +127;
// Lab has some... particulars
inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) {
return channelValueText(pixel, channelIndex);
}
inline static void normalisedChannelsValue(const quint8 *pixel, QVector<float> &channels) {
Q_ASSERT((int)channels.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
c = parent::nativeArray(pixel)[i];
channels[i] = (qreal)c;
}
}
inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector<float> &values) {
Q_ASSERT((int)values.count() >= (int)parent::channels_nb);
channels_type c;
for (uint i = 0; i < parent::channels_nb; i++) {
float b = 0;
switch(i) {
case L_pos:
b = qBound((float)MIN_CHANNEL_L,
(float)KoColorSpaceMathsTraits<double>::unitValue * values[i],
(float)MAX_CHANNEL_L);
break;
case a_pos:
case b_pos:
b = qBound((float)MIN_CHANNEL_AB,
(float)KoColorSpaceMathsTraits<double>::unitValue * values[i],
(float)MAX_CHANNEL_AB);
break;
case 3:
b = qBound((float)KoColorSpaceMathsTraits<double>::min,
(float)KoColorSpaceMathsTraits<double>::unitValue * values[i],
(float)KoColorSpaceMathsTraits<double>::max);
default:
break;
}
c = (channels_type)b;
parent::nativeArray(pixel)[i] = c;
}
}
};
#endif
diff --git a/libs/pigment/KoLut.h b/libs/pigment/KoLut.h
index a92a88d5fd..e9213ad1c7 100644
--- a/libs/pigment/KoLut.h
+++ b/libs/pigment/KoLut.h
@@ -1,33 +1,33 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.bet
*
* 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.
*/
-// The file lut.h comes from http://bitbucket.org/cyrille/lut, keep it as much untouched as possible for easier sync
+// The file lut.h comes from https://bitbucket.org/cyrille/lut/src/default/, keep it as much untouched as possible for easier sync
#ifndef _KO_LUT_H_
#define _KO_LUT_H_
#include <QtGlobal>
#define _USE_QT_TYPES_
namespace Ko {
#include "lut.h"
}
#endif
diff --git a/libs/pigment/compositeops/KoCompositeOpFunctions.h b/libs/pigment/compositeops/KoCompositeOpFunctions.h
index 7357238e0e..80c242c356 100644
--- a/libs/pigment/compositeops/KoCompositeOpFunctions.h
+++ b/libs/pigment/compositeops/KoCompositeOpFunctions.h
@@ -1,952 +1,952 @@
/*
* 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_FUNCTIONS_H_
#define KOCOMPOSITEOP_FUNCTIONS_H_
#include <KoColorSpaceMaths.h>
template<class HSXType, class TReal>
inline void cfReorientedNormalMapCombine(TReal srcR, TReal srcG, TReal srcB, TReal& dstR, TReal& dstG, TReal& dstB)
{
- // see http://blog.selfshadow.com/publications/blending-in-detail/ by Barre-Brisebois and Hill
+ // see https://blog.selfshadow.com/publications/blending-in-detail/ by Barre-Brisebois and Hill
TReal tx = 2*srcR-1;
TReal ty = 2*srcG-1;
TReal tz = 2*srcB;
TReal ux = -2*dstR+1;
TReal uy = -2*dstG+1;
TReal uz = 2*dstB-1;
TReal k = (tx*ux+ty*uy+tz*uz)/tz; // dot(t,u)/t.z
TReal rx = tx*k-ux;
TReal ry = ty*k-uy;
TReal rz = tz*k-uz;
k = 1/sqrt(rx*rx+ry*ry+rz*rz); // normalize result
rx *= k;
ry *= k;
rz *= k;
dstR = rx*0.5+0.5;
dstG = ry*0.5+0.5;
dstB = rz*0.5+0.5;
}
template<class HSXType, class TReal>
inline void cfColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
TReal lum = getLightness<HSXType>(dr, dg, db);
dr = sr;
dg = sg;
db = sb;
setLightness<HSXType>(dr, dg, db, lum);
}
template<class HSXType, class TReal>
inline void cfLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
setLightness<HSXType>(dr, dg, db, getLightness<HSXType>(sr, sg, sb));
}
template<class HSXType, class TReal>
inline void cfIncreaseLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
addLightness<HSXType>(dr, dg, db, getLightness<HSXType>(sr, sg, sb));
}
template<class HSXType, class TReal>
inline void cfDecreaseLightness(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
addLightness<HSXType>(dr, dg, db, getLightness<HSXType>(sr, sg, sb) - TReal(1.0));
}
template<class HSXType, class TReal>
inline void cfSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
TReal sat = getSaturation<HSXType>(sr, sg, sb);
TReal light = getLightness<HSXType>(dr, dg, db);
setSaturation<HSXType>(dr, dg, db, sat);
setLightness<HSXType>(dr, dg, db, light);
}
template<class HSXType, class TReal>
inline void cfIncreaseSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
using namespace Arithmetic;
TReal sat = lerp(getSaturation<HSXType>(dr,dg,db), unitValue<TReal>(), getSaturation<HSXType>(sr,sg,sb));
TReal light = getLightness<HSXType>(dr, dg, db);
setSaturation<HSXType>(dr, dg, db, sat);
setLightness<HSXType>(dr, dg, db, light);
}
template<class HSXType, class TReal>
inline void cfDecreaseSaturation(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
using namespace Arithmetic;
TReal sat = lerp(zeroValue<TReal>(), getSaturation<HSXType>(dr,dg,db), getSaturation<HSXType>(sr,sg,sb));
TReal light = getLightness<HSXType>(dr, dg, db);
setSaturation<HSXType>(dr, dg, db, sat);
setLightness<HSXType>(dr, dg, db, light);
}
template<class HSXType, class TReal>
inline void cfHue(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
TReal sat = getSaturation<HSXType>(dr, dg, db);
TReal lum = getLightness<HSXType>(dr, dg, db);
dr = sr;
dg = sg;
db = sb;
setSaturation<HSXType>(dr, dg, db, sat);
setLightness<HSXType>(dr, dg, db, lum);
}
template<class HSXType, class TReal>
inline void cfTangentNormalmap(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
using namespace Arithmetic;
TReal half=halfValue<TReal>();
dr = sr+(dr-half);
dg = sg+(dg-half);
db = sb+(db-unitValue<TReal>());
}
template<class HSXType, class TReal>
inline void cfDarkerColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
TReal lum = getLightness<HSXType>(dr, dg, db);
TReal lum2 = getLightness<HSXType>(sr, sg, sb);
if (lum<lum2) {
sr = dr;
sg = dg;
sb = db;
}
else {
dr = sr;
dg = sg;
db = sb;
}
}
template<class HSXType, class TReal>
inline void cfLighterColor(TReal sr, TReal sg, TReal sb, TReal& dr, TReal& dg, TReal& db) {
TReal lum = getLightness<HSXType>(dr, dg, db);
TReal lum2 = getLightness<HSXType>(sr, sg, sb);
if (lum>lum2) {
sr = dr;
sg = dg;
sb = db;
}
else {
dr = sr;
dg = sg;
db = sb;
}
}
template<class T>
inline T cfColorBurn(T src, T dst) {
using namespace Arithmetic;
if(dst == unitValue<T>())
return unitValue<T>();
T invDst = inv(dst);
if(src < invDst)
return zeroValue<T>();
return inv(clamp<T>(div(invDst, src)));
}
template<class T>
inline T cfLinearBurn(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
return clamp<T>(composite_type(src) + dst - unitValue<T>());
}
template<class T>
inline T cfColorDodge(T src, T dst) {
using namespace Arithmetic;
//Fixing Color Dodge to avoid ZX Colors on bright area.
if(src == unitValue<T>())
return unitValue<T>();
T invSrc = inv(src);
if(invSrc == zeroValue<T>())
return unitValue<T>();
return Arithmetic::clamp<T>(div(dst, invSrc));
}
template<class T>
inline T cfAddition(T src, T dst) {
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
return Arithmetic::clamp<T>(composite_type(src) + dst);
}
template<class T>
inline T cfSubtract(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
return clamp<T>(composite_type(dst) - src);
}
template<class T>
inline T cfInverseSubtract(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
return clamp<T>(composite_type(dst) - inv(src));
}
template<class T>
inline T cfExclusion(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
composite_type x = mul(src, dst);
return clamp<T>(composite_type(dst) + src - (x + x));
}
template<class T>
inline T cfDivide(T src, T dst) {
using namespace Arithmetic;
//typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
if(src == zeroValue<T>())
return (dst == zeroValue<T>()) ? zeroValue<T>() : unitValue<T>();
return clamp<T>(div(dst, src));
}
template<class T>
inline T cfHardLight(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
composite_type src2 = composite_type(src) + src;
if(src > halfValue<T>()) {
// screen(src*2.0 - 1.0, dst)
src2 -= unitValue<T>();
// src2 is guaranteed to be smaller than unitValue<T>() now
return Arithmetic::unionShapeOpacity(T(src2), dst);
}
// src2 is guaranteed to be smaller than unitValue<T>() due to 'if'
return Arithmetic::mul(T(src2), dst);
}
template<class T>
inline T cfSoftLightSvg(T src, T dst) {
using namespace Arithmetic;
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if(fsrc > 0.5f) {
qreal D = (fdst > 0.25f) ? sqrt(fdst) : ((16.0f*fdst - 12.0)*fdst + 4.0f)*fdst;
return scale<T>(fdst + (2.0f*fsrc - 1.0f) * (D - fdst));
}
return scale<T>(fdst - (1.0f - 2.0f * fsrc) * fdst * (1.0f - fdst));
}
template<class T>
inline T cfSoftLight(T src, T dst) {
using namespace Arithmetic;
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if(fsrc > 0.5f) {
return scale<T>(fdst + (2.0f * fsrc - 1.0f) * (sqrt(fdst) - fdst));
}
return scale<T>(fdst - (1.0f - 2.0f*fsrc) * fdst * (1.0f - fdst));
}
template<class T>
inline T cfVividLight(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
if(src < halfValue<T>()) {
if(src == zeroValue<T>())
return (dst == unitValue<T>()) ? unitValue<T>() : zeroValue<T>();
// min(1,max(0,1-(1-dst) / (2*src)))
composite_type src2 = composite_type(src) + src;
composite_type dsti = inv(dst);
return clamp<T>(unitValue<T>() - (dsti * unitValue<T>() / src2));
}
if(src == unitValue<T>())
return (dst == zeroValue<T>()) ? zeroValue<T>() : unitValue<T>();
// min(1,max(0, dst / (2*(1-src)))
composite_type srci2 = inv(src);
srci2 += srci2;
return clamp<T>(composite_type(dst) * unitValue<T>() / srci2);
}
template<class T>
inline T cfPinLight(T src, T dst) {
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
// TODO: verify that the formula is correct (the first max would be useless here)
// max(0, max(2*src-1, min(dst, 2*src)))
composite_type src2 = composite_type(src) + src;
composite_type a = qMin<composite_type>(dst, src2);
composite_type b = qMax<composite_type>(src2-Arithmetic::unitValue<T>(), a);
return T(b);
}
template<class T>
inline T cfArcTangent(T src, T dst) {
using namespace Arithmetic;
if(dst == zeroValue<T>())
return (src == zeroValue<T>()) ? zeroValue<T>() : unitValue<T>();
return scale<T>(2.0 * atan(scale<qreal>(src) / scale<qreal>(dst)) / Arithmetic::pi);
}
template<class T>
inline T cfAllanon(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
// (dst + src) / 2 [or (dst + src) * 0.5]
return T((composite_type(src) + dst) * halfValue<T>() / unitValue<T>());
}
template<class T>
inline T cfLinearLight(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
// min(1,max(0,(dst + 2*src)-1))
return clamp<T>((composite_type(src) + src + dst) - unitValue<T>());
}
template<class T>
inline T cfParallel(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
// min(max(2 / (1/dst + 1/src), 0), 1)
composite_type unit = unitValue<T>();
composite_type s = (src != zeroValue<T>()) ? div<T>(unit, src) : unit;
composite_type d = (dst != zeroValue<T>()) ? div<T>(unit, dst) : unit;
if (src == zeroValue<T>()) {
return zeroValue<T>();
}
if (dst == zeroValue<T>()) {
return zeroValue<T>();
}
return clamp<T>((unit+unit) * unit / (d+s));
}
template<class T>
inline T cfEquivalence(T src, T dst) {
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
// 1 - abs(dst - src)
composite_type x = composite_type(dst) - src;
return (x < Arithmetic::zeroValue<T>()) ? T(-x) : T(x);
}
template<class T>
inline T cfGrainMerge(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
return clamp<T>(composite_type(dst) + src - halfValue<T>());
}
template<class T>
inline T cfGrainExtract(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
return clamp<T>(composite_type(dst) - src + halfValue<T>());
}
template<class T>
inline T cfHardMix(T src, T dst) {
return (dst > Arithmetic::halfValue<T>()) ? cfColorDodge(src,dst) : cfColorBurn(src,dst);
}
template<class T>
inline T cfHardMixPhotoshop(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
const composite_type sum = composite_type(src) + dst;
return sum > unitValue<T>() ? unitValue<T>() : zeroValue<T>();
}
template<class T>
inline T cfAdditiveSubtractive(T src, T dst) {
using namespace Arithmetic;
// min(1,max(0,abs(sqr(CB)-sqr(CT))))
qreal x = sqrt(scale<qreal>(dst)) - sqrt(scale<qreal>(src));
return scale<T>((x < 0.0) ? -x : x);
}
template<class T>
inline T cfGammaDark(T src, T dst) {
using namespace Arithmetic;
if(src == zeroValue<T>())
return zeroValue<T>();
// power(dst, 1/src)
return scale<T>(pow(scale<qreal>(dst), 1.0/scale<qreal>(src)));
}
template<class T>
inline T cfGammaLight(T src, T dst) {
using namespace Arithmetic;
return scale<T>(pow(scale<qreal>(dst), scale<qreal>(src)));
}
template<class T>
inline T cfGammaIllumination(T src, T dst) {
using namespace Arithmetic;
return inv(cfGammaDark(inv(src),inv(dst)));
}
template<class T>
inline T cfGeometricMean(T src, T dst) {
using namespace Arithmetic;
return scale<T>(sqrt(scale<qreal>(dst) * scale<qreal>(src)));
}
template<class T>
inline T cfOver(T src, T dst) { Q_UNUSED(dst); return src; }
template<class T>
inline T cfOverlay(T src, T dst) { return cfHardLight(dst, src); }
template<class T>
inline T cfMultiply(T src, T dst) { return Arithmetic::mul(src, dst); }
template<class T>
inline T cfHardOverlay(T src, T dst) {
using namespace Arithmetic;
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if (fsrc == 1.0) {
return scale<T>(1.0);}
if(fsrc > 0.5f) {
return scale<T>(cfDivide(inv(2.0 * fsrc - 1.0f), fdst));
}
return scale<T>(mul(2.0 * fsrc, fdst));
}
template<class T>
inline T cfDifference(T src, T dst) { return qMax(src,dst) - qMin(src,dst); }
template<class T>
inline T cfScreen(T src, T dst) { return Arithmetic::unionShapeOpacity(src, dst); }
template<class T>
inline T cfDarkenOnly(T src, T dst) { return qMin(src, dst); }
template<class T>
inline T cfLightenOnly(T src, T dst) { return qMax(src, dst); }
template<class T>
inline T cfGlow(T src, T dst) {
using namespace Arithmetic;
// see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat
if (dst == unitValue<T>()) {
return unitValue<T>();
}
return clamp<T>(div(mul(src, src), inv(dst)));
}
template<class T>
inline T cfReflect(T src, T dst) {
using namespace Arithmetic;
return clamp<T>(cfGlow(dst,src));
}
template<class T>
inline T cfHeat(T src, T dst) {
using namespace Arithmetic;
if(src == unitValue<T>()) {
return unitValue<T>();
}
if(dst == zeroValue<T>()) {
return zeroValue<T>();
}
return inv(clamp<T>(div(mul(inv(src), inv(src)),dst)));
}
template<class T>
inline T cfFreeze(T src, T dst) {
using namespace Arithmetic;
return (cfHeat(dst,src));
}
template<class T>
inline T cfHelow(T src, T dst) {
using namespace Arithmetic;
// see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat
if (cfHardMixPhotoshop(src,dst) == unitValue<T>()) {
return cfHeat(src,dst);
}
if (src == zeroValue<T>()) {
return zeroValue<T>();
}
return (cfGlow(src,dst));
}
template<class T>
inline T cfFrect(T src, T dst) {
using namespace Arithmetic;
if (cfHardMixPhotoshop(src,dst) == unitValue<T>()) {
return cfFreeze(src,dst);
}
if (dst == zeroValue<T>()) {
return zeroValue<T>();
}
return (cfReflect(src,dst));
}
template<class T>
inline T cfGleat(T src, T dst) {
using namespace Arithmetic;
// see http://www.pegtop.net/delphi/articles/blendmodes/quadratic.htm for formulas of Quadratic Blending Modes like Glow, Reflect, Freeze, and Heat
if(dst == unitValue<T>()) {
return unitValue<T>();
}
if(cfHardMixPhotoshop(src,dst) == unitValue<T>()) {
return cfGlow(src,dst);
}
return (cfHeat(src,dst));
}
template<class T>
inline T cfReeze(T src, T dst) {
using namespace Arithmetic;
return (cfGleat(dst,src));
}
template<class T>
inline T cfFhyrd(T src, T dst) {
using namespace Arithmetic;
return (cfAllanon(cfFrect(src,dst),cfHelow(src,dst)));
}
template<class T>
inline T cfInterpolation(T src, T dst) {
using namespace Arithmetic;
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if(dst == zeroValue<T>() && src == zeroValue<T>()) {
return zeroValue<T>();
}
return scale<T>(.5f-.25f*cos(pi*(fsrc))-.25f*cos(pi*(fdst)));
}
template<class T>
inline T cfInterpolationB(T src, T dst) {
using namespace Arithmetic;
return cfInterpolation(cfInterpolation(src,dst),cfInterpolation(src,dst));
}
template<class T>
inline T cfPenumbraB(T src, T dst) {
using namespace Arithmetic;
if (dst == unitValue<T>()) {
return unitValue<T>();
}
if (dst + src < unitValue<T>()) {
return (cfColorDodge(dst,src)/2);
}
if (src == zeroValue<T>()) {
return zeroValue<T>();
}
return inv(clamp<T>(div(inv(dst),src)/2));
}
template<class T>
inline T cfPenumbraD(T src, T dst) {
using namespace Arithmetic;
if (dst == unitValue<T>()) {
return unitValue<T>();
}
return cfArcTangent(src,inv(dst));
}
template<class T>
inline T cfPenumbraC(T src, T dst) {
using namespace Arithmetic;
return cfPenumbraD(dst,src);
}
template<class T>
inline T cfPenumbraA(T src, T dst) {
using namespace Arithmetic;
return (cfPenumbraB(dst,src));
}
template<class T>
inline T cfSoftLightIFSIllusions(T src, T dst) {
using namespace Arithmetic;
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
return scale<T>(pow(fdst,pow(2.0,(mul(2.0,.5f-fsrc)))));
}
template<class T>
inline T cfSoftLightPegtopDelphi(T src, T dst) {
using namespace Arithmetic;
return clamp<T>(cfAddition(mul(dst,cfScreen(src,dst)),mul(mul(src,dst),inv(dst))));
}
template<class T>
inline T cfNegation(T src, T dst) {
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<T>::compositetype composite_type;
composite_type unit = unitValue<T>();
composite_type a = unit - src - dst;
composite_type s = abs(a);
composite_type d = unit - s;
return T(d);
}
template<class T>
inline T cfNor(T src, T dst) {
using namespace Arithmetic;
return and(src,dst);
}
template<class T>
inline T cfNand(T src, T dst) {
using namespace Arithmetic;
return or(src,dst);
}
template<class T>
inline T cfXor(T src, T dst) {
using namespace Arithmetic;
return xor(src,dst);
}
template<class T>
inline T cfXnor(T src, T dst) {
using namespace Arithmetic;
return cfXor(src,inv(dst));
}
template<class T>
inline T cfAnd(T src, T dst) {
using namespace Arithmetic;
return cfNor(inv(src),inv(dst));
}
template<class T>
inline T cfOr(T src, T dst) {
using namespace Arithmetic;
return cfNand(inv(src),inv(dst));
}
template<class T>
inline T cfConverse(T src, T dst) {
using namespace Arithmetic;
return cfOr(inv(src),dst);
}
template<class T>
inline T cfNotConverse(T src, T dst) {
using namespace Arithmetic;
return cfAnd(src,inv(dst));
}
template<class T>
inline T cfImplies(T src, T dst) {
using namespace Arithmetic;
return cfOr(src,inv(dst));
}
template<class T>
inline T cfNotImplies(T src, T dst) {
using namespace Arithmetic;
return cfAnd(inv(src),dst);
}
template<class T>
inline T cfPNormA(T src, T dst) {
using namespace Arithmetic;
//This is also known as P-Norm mode with factor of 2.3333 See IMBLEND image blending mode samples, and please see imblend.m file found on Additional Blending Mode thread at Phabricator. 1/2.3333 is .42875...
return clamp<T>(pow(pow((float)dst, 2.3333333333333333) + pow((float)src, 2.3333333333333333), 0.428571428571434));
}
template<class T>
inline T cfPNormB(T src, T dst) {
using namespace Arithmetic;
//This is also known as P-Norm mode with factor of 2.3333 See IMBLEND image blending mode samples, and please see imblend.m file found on Additional Blending Mode thread at Phabricator. 1/2.3333 is .42875...
return clamp<T>(pow(pow(dst,4)+pow(src,4),0.25));
}
template<class T>
inline T cfSuperLight(T src, T dst) {
using namespace Arithmetic;
//4.0 can be adjusted to taste. 4.0 is picked for being the best in terms of contrast and details. See imblend.m file.
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if (fsrc < .5) {
return scale<T>(inv(pow(pow(inv(fdst),2.875)+pow(inv(2.0*fsrc),2.875),1.0/2.875)));
}
return scale<T>(pow(pow(fdst,2.875)+pow(2.0*fsrc-1.0,2.875),1.0/2.875));
}
template<class T>
inline T cfTintIFSIllusions(T src, T dst) {
using namespace Arithmetic;
//Known as Light Blending mode found in IFS Illusions. Picked this name because it results into a very strong tint, and has better naming convention.
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
return scale<T>(fsrc*inv(fdst)+sqrt(fdst));
}
template<class T>
inline T cfShadeIFSIllusions(T src, T dst) {
using namespace Arithmetic;
//Known as Shadow Blending mode found in IFS Illusions. Picked this name because it is the opposite of Tint (IFS Illusion Blending mode).
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
return scale<T>(inv((inv(fdst)*fsrc)+sqrt(inv(fsrc))));
}
template<class T>
inline T cfFogLightenIFSIllusions(T src, T dst) {
using namespace Arithmetic;
//Known as Bright Blending mode found in IFS Illusions. Picked this name because the shading reminds me of fog when overlaying with a gradient.
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if (fsrc < .5) {
return scale<T>(inv(inv(fsrc)*fsrc)-inv(fdst)*inv(fsrc));
}
return scale<T>(fsrc-inv(fdst)*inv(fsrc)+pow(inv(fsrc),2));
}
template<class T>
inline T cfFogDarkenIFSIllusions(T src, T dst) {
using namespace Arithmetic;
//Known as Dark Blending mode found in IFS Illusions. Picked this name because the shading reminds me of fog when overlaying with a gradient.
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if (fsrc < .5) {
return scale<T>(inv(fsrc)*fsrc+fsrc*fdst);
}
return scale<T>(fsrc*fdst+fsrc-pow(fsrc,2));
}
template<class T>
inline T cfModulo(T src, T dst) {
using namespace Arithmetic;
return mod(dst,src);
}
template<class T>
inline T cfModuloShift(T src, T dst) {
using namespace Arithmetic;
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if (fsrc == 1.0 && fdst == 0.0) {
return scale<T>(0.0);
}
return scale<T>(mod((fdst+fsrc),1.0000000000));
}
template<class T>
inline T cfModuloShiftContinuous(T src, T dst) {
using namespace Arithmetic;
//This blending mode do not behave like difference/equivalent with destination layer inverted if you use group layer on addition while the content of group layer contains several addition-mode layers, it works as expected on float images. So, no need to change this.
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if (fsrc == 1.0 && fdst == 0.0) {
return scale<T>(1.0);
}
return scale<T>((int(ceil(fdst+fsrc)) % 2 != 0) || (fdst == zeroValue<T>()) ? cfModuloShift(fsrc,fdst) : inv(cfModuloShift(fsrc,fdst)));
}
template<class T>
inline T cfDivisiveModulo(T src, T dst) {
using namespace Arithmetic;
//I have to use 1.00000 as unitValue failed to work for those area.
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if (fsrc == zeroValue<T>()) {
return scale<T>(mod(((1.0000000000/epsilon<T>()) * fdst),1.0000000000));
}
return scale<T>(mod(((1.0000000000/fsrc) * fdst),1.0000000000));
}
template<class T>
inline T cfDivisiveModuloContinuous(T src, T dst) {
using namespace Arithmetic;
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if (fdst == zeroValue<T>()) {
return zeroValue<T>();
}
if (fsrc == zeroValue<T>()) {
return cfDivisiveModulo(fsrc,fdst);
}
return scale<T>( int(ceil(fdst/fsrc)) % 2 != 0 ? cfDivisiveModulo(fsrc,fdst) : inv(cfDivisiveModulo(fsrc,fdst)));
}
template<class T>
inline T cfModuloContinuous(T src, T dst) {
using namespace Arithmetic;
return cfMultiply(cfDivisiveModuloContinuous(src,dst),src);
}
template<class T>
inline T cfEasyDodge(T src, T dst) {
using namespace Arithmetic;
// The 13 divided by 15 can be adjusted to taste. See imgblend.m
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
if (fsrc == 1.0) {
return scale<T>(1.0);}
return scale<T>(pow(fdst,mul(inv(fsrc != 1.0 ? fsrc : .999999999999),1.039999999)));
}
template<class T>
inline T cfEasyBurn(T src, T dst) {
using namespace Arithmetic;
// The 13 divided by 15 can be adjusted to taste. See imgblend.m
qreal fsrc = scale<qreal>(src);
qreal fdst = scale<qreal>(dst);
return scale<T>(inv(pow(inv(fsrc != 1.0 ? fsrc : .999999999999),mul(fdst,1.039999999))));
}
template<class T>
inline T cfFlatLight(T src, T dst) {
using namespace Arithmetic;
if (src == zeroValue<T>()) {
return zeroValue<T>();
}
return clamp<T>(cfHardMixPhotoshop(inv(src),dst)==unitValue<T>() ? cfPenumbraB(src,dst) : cfPenumbraA(src,dst));
}
template<class HSXType, class TReal>
inline void cfAdditionSAI(TReal src, TReal sa, TReal& dst, TReal& da)
{
using namespace Arithmetic;
typedef typename KoColorSpaceMathsTraits<TReal>::compositetype composite_type;
Q_UNUSED(da);
composite_type newsrc;
newsrc = mul(src, sa);
dst = clamp<TReal>(newsrc + dst);
}
#endif // KOCOMPOSITEOP_FUNCTIONS_H_
diff --git a/libs/pigment/lut.h b/libs/pigment/lut.h
index 4812876feb..6f0c885c86 100644
--- a/libs/pigment/lut.h
+++ b/libs/pigment/lut.h
@@ -1,334 +1,334 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _LUT_H_
#define _LUT_H_
template<typename _InputT_>
class LutKey;
template<typename _InputT_>
class FullLutKey;
/**
* Provide an implementation for a look-up table for a function. Do not use directly, instead
* use @ref Lut when you are not interested in having the full look-up table or for floating
* point, or use @ref FullLut for 8bits and 16bits when you want to have a full Lut.
*
* @code
* struct MyFunction {
* inline static int compute(int i)
* {
* return 1-i;
* }
* }
* Lut<MyFunction, int, int> myLut;
* @endcode
*/
template<typename _FunctionT_, typename _OutputT_, typename _InputT_, typename _LutKeyT_ >
class BaseLut {
private:
/**
* Initialization of the table.
*/
inline void init()
{
int size = m_key.size();
m_table = new _OutputT_[size];
for(int i = 0; i < size; ++i)
{
m_table[i] = m_function(m_key.keyToInput(i));
}
}
public:
/**
* Create the lut with the specific key.
*/
inline BaseLut(_LutKeyT_ key, _FunctionT_ function = _FunctionT_()) : m_key(key), m_function(function)
{
init();
}
inline ~BaseLut() {
// just leak on exit -- we get into trouble for explicitly
// deleting stuff from static objects, like registries
//delete[] m_table;
}
public:
/**
* @return the function value for parameter @p i
*/
inline _OutputT_ operator()(_InputT_ i) const
{
if(m_key.inrange(i))
{
return m_table[m_key.inputToKey(i)];
}
return m_function(i);
}
private:
_OutputT_* m_table;
_LutKeyT_ m_key;
_FunctionT_ m_function;
};
/**
* This Lut is limited to a range of values.
*/
template<typename _FunctionT_, typename _OutputT_, typename _InputT_>
class Lut : public BaseLut<_FunctionT_, _OutputT_, _InputT_, LutKey<_InputT_> > {
public:
/**
* Create the lut between @p _min and @p _max .
*/
inline Lut(_InputT_ _min, _InputT_ _max, _FunctionT_ function = _FunctionT_()) :
BaseLut<_FunctionT_, _OutputT_, _InputT_, LutKey<_InputT_> >( LutKey<_InputT_>(_min, _max), function)
{
}
inline Lut(LutKey<_InputT_> key, _FunctionT_ function = _FunctionT_()) :
BaseLut<_FunctionT_, _OutputT_, _InputT_, LutKey<_InputT_> >( key, function)
{
}
};
/**
* This Lut has precomputed values for all elements.
*/
template<typename _FunctionT_, typename _OutputT_, typename _InputT_>
class FullLut : public BaseLut<_FunctionT_, _OutputT_, _InputT_, FullLutKey<_InputT_> > {
public:
inline FullLut( _FunctionT_ function = _FunctionT_()) :
BaseLut<_FunctionT_, _OutputT_, _InputT_, FullLutKey<_InputT_> >( FullLutKey<_InputT_>(), function)
{
}
};
#ifdef _USE_QT_TYPES_
typedef quint8 lut_uint8;
typedef quint16 lut_uint16;
typedef quint32 lut_uint32;
#else
#include <stdint.h>
typedef uint8_t lut_uint8;
typedef uint16_t lut_uint16;
typedef uint32_t lut_uint32;
#endif
// integer specialization
#define PARTIAL_LUT_INT_SPECIALIZATION(_INT_TYPE_) \
template<> \
class LutKey<_INT_TYPE_> { \
public: \
LutKey<_INT_TYPE_>(_INT_TYPE_ min, _INT_TYPE_ max) : m_min(min), m_max(max) \
{ \
} \
public: \
inline int inputToKey(_INT_TYPE_ i) const \
{ \
return i - m_min; \
} \
inline _INT_TYPE_ keyToInput(int k) const \
{ \
return k + m_min; \
} \
inline bool inrange(_INT_TYPE_ i) const \
{ \
return i >= m_min && i <= m_max; \
} \
inline _INT_TYPE_ minimum() const \
{ \
return m_min; \
} \
inline _INT_TYPE_ maximum() const \
{ \
return m_max; \
} \
inline int size() const \
{ \
return m_max - m_min + 1; \
} \
private: \
_INT_TYPE_ m_min, m_max; \
};
PARTIAL_LUT_INT_SPECIALIZATION(lut_uint8)
PARTIAL_LUT_INT_SPECIALIZATION(lut_uint16)
PARTIAL_LUT_INT_SPECIALIZATION(lut_uint32)
#define FULL_LUT_INT_SPECIALIZATION(_INT_TYPE_, _MIN_, _MAX_) \
template<> \
class FullLutKey<_INT_TYPE_> { \
public: \
FullLutKey<_INT_TYPE_>() \
{ \
} \
public: \
inline int inputToKey(_INT_TYPE_ i) const \
{ \
return i - _MIN_; \
} \
inline _INT_TYPE_ keyToInput(int k) const \
{ \
return k + _MIN_; \
} \
inline bool inrange(_INT_TYPE_ ) const \
{ \
return true; \
} \
inline _INT_TYPE_ minimum() const \
{ \
return _MIN_; \
} \
inline _INT_TYPE_ maximum() const \
{ \
return _MAX_; \
} \
inline int size() const \
{ \
return _MAX_ - _MIN_ + 1; \
} \
private: \
};
FULL_LUT_INT_SPECIALIZATION(lut_uint8, 0, 255)
FULL_LUT_INT_SPECIALIZATION(lut_uint16, 0, 65535)
// float specialization
/**
* This provide an implementation for a LutKey for floating point input values.
*
* Based on "High-speed Conversion of Floating Point Images to 8-bit" by Bill Spitzaks
- * (http://mysite.verizon.net/spitzak/conversion/)
+ * (https://spitzak.github.io/conversion/sketches_0265.pdf)
*/
template<>
class LutKey<float> {
public:
union IFNumber {
lut_uint32 i;
float f;
};
public:
LutKey<float>(float min, float max, float precision) : m_min(min), m_max(max), m_precision(precision)
{
// Those values where computed using the test_linear and setting the shift and then using
// the standard deviation.
if (precision <= 0.000011809f) {
m_min = 1;
m_max = -1;
}
else if (precision <= 0.0000237291f) m_shift = 8;
else if (precision <= 0.0000475024f) m_shift = 9;
else if (precision <= 0.0000948575f) m_shift = 10;
else if (precision <= 0.00019013f) m_shift = 11;
else if (precision <= 0.000379523f) m_shift = 12;
else if (precision <= 0.000758431f) m_shift = 13;
else if (precision <= 0.00151891f) m_shift = 14;
else if (precision <= 0.00303725f) m_shift = 15;
else m_shift = 16;
if ( 0.0 <= m_min && m_min <= precision)
m_min = precision;
if ( -precision <= m_max && m_max <= 0.0)
m_max = -precision;
IFNumber uf;
if(m_min > 0 && m_max > 0)
{
uf.f = m_min;
m_tMin_p = uf.i >> m_shift;
uf.f = m_max;
m_tMax_p = uf.i >> m_shift;
m_tMin_n = m_tMax_p;
m_tMax_n = m_tMax_p;
} else if( m_max < 0)
{
uf.f = m_min;
m_tMax_n = uf.i >> m_shift;
uf.f = m_max;
m_tMin_n = uf.i >> m_shift;
m_tMin_p = m_tMax_n;
m_tMax_p = m_tMax_n;
} else { // m_min <0 && m_max > 0
uf.f = precision;
m_tMin_p = uf.i >> m_shift;
uf.f = m_max;
m_tMax_p = uf.i >> m_shift;
uf.f = -precision;
m_tMin_n = uf.i >> m_shift;
uf.f = m_min;
m_tMax_n = uf.i >> m_shift;
}
m_diff_p = m_tMax_p - m_tMin_p;
}
public:
inline int inputToKey(float i) const
{
IFNumber uf;
uf.f = i;
int k = (uf.i >> m_shift);
if(k <= m_tMax_p)
{
return k - m_tMin_p;
} else {
return k - m_tMin_n + m_diff_p;
}
}
inline float keyToInput(int k) const
{
IFNumber uf;
if( k <= m_diff_p ) {
uf.i = ((k + m_tMin_p) << m_shift);
} else {
uf.i = ((k + m_tMin_n - m_diff_p ) << m_shift);
}
return uf.f;
}
inline bool inrange(float i) const
{
return i >= m_min && i <= m_max && (i < -m_precision || i > m_precision);
}
inline float minimum() const
{
return m_min;
}
inline float maximum() const
{
return m_max;
}
inline int size() const
{
return m_diff_p + m_tMax_n - m_tMin_p + 1;
}
private:
float m_min, m_max, m_precision;
int m_tMin_p, m_tMax_p, m_tMin_n, m_tMax_n, m_diff_p;
int m_shift;
};
#endif
diff --git a/libs/pigment/resources/KisSwatch.cpp b/libs/pigment/resources/KisSwatch.cpp
index 019638d500..65cd9c14c5 100644
--- a/libs/pigment/resources/KisSwatch.cpp
+++ b/libs/pigment/resources/KisSwatch.cpp
@@ -1,52 +1,52 @@
/*
* This file is part of the KDE project
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
- * Copyright (c) 2016 L. E. Segovia <leo.segovia@siggraph.org>
+ * Copyright (c) 2016 L. E. Segovia <amy@amyspark.me>
*
* 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 "KisSwatch.h"
KisSwatch::KisSwatch(const KoColor &color, const QString &name)
: m_color(color)
, m_name(name)
, m_valid(true)
{ }
void KisSwatch::setName(const QString &name)
{
m_name = name;
m_valid = true;
}
void KisSwatch::setId(const QString &id)
{
m_id = id;
m_valid = true;
}
void KisSwatch::setColor(const KoColor &color)
{
m_color = color;
m_valid = true;
}
void KisSwatch::setSpotColor(bool spotColor)
{
m_spotColor = spotColor;
m_valid = true;
}
diff --git a/libs/pigment/resources/KisSwatch.h b/libs/pigment/resources/KisSwatch.h
index 2507a37b61..25d61c2556 100644
--- a/libs/pigment/resources/KisSwatch.h
+++ b/libs/pigment/resources/KisSwatch.h
@@ -1,63 +1,63 @@
/*
* This file is part of the KDE project
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
- * Copyright (c) 2016 L. E. Segovia <leo.segovia@siggraph.org>
+ * Copyright (c) 2016 L. E. Segovia <amy@amyspark.me>
*
* 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 KISSWATCH_H
#define KISSWATCH_H
#include "kritapigment_export.h"
#include <QString>
#include "KoColor.h"
class KRITAPIGMENT_EXPORT KisSwatch
{
public:
KisSwatch() = default;
KisSwatch(const KoColor &color, const QString &name = QString());
public:
QString name() const { return m_name; }
void setName(const QString &name);
QString id() const { return m_id; }
void setId(const QString &id);
KoColor color() const { return m_color; }
void setColor(const KoColor &color);
bool spotColor() const { return m_spotColor; }
void setSpotColor(bool spotColor);
bool isValid() const { return m_valid; }
public:
bool operator==(const KisSwatch& rhs) const {
return m_color == rhs.m_color && m_name == rhs.m_name;
}
private:
KoColor m_color;
QString m_name;
QString m_id;
bool m_spotColor {false};
bool m_valid {false};
};
#endif // KISSWATCH_H
diff --git a/libs/pigment/resources/KisSwatchGroup.cpp b/libs/pigment/resources/KisSwatchGroup.cpp
index b8382fb0d0..01fbd4dec5 100644
--- a/libs/pigment/resources/KisSwatchGroup.cpp
+++ b/libs/pigment/resources/KisSwatchGroup.cpp
@@ -1,208 +1,208 @@
/* This file is part of the KDE project
Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
- Copyright (c) 2016 L. E. Segovia <leo.segovia@siggraph.org>
+ Copyright (c) 2016 L. E. Segovia <amy@amyspark.me>
Copyright (c) 2018 Michael Zhou <simerixh@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; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "KisSwatchGroup.h"
struct KisSwatchGroup::Private {
typedef QMap<int, KisSwatch> Column;
static int DEFAULT_COLUMN_COUNT;
static int DEFAULT_ROW_COUNT;
QString name {QString()};
QVector<Column> colorMatrix {DEFAULT_COLUMN_COUNT};
int colorCount {0};
int rowCount {DEFAULT_ROW_COUNT};
};
int KisSwatchGroup::Private::DEFAULT_COLUMN_COUNT = 16;
int KisSwatchGroup::Private::DEFAULT_ROW_COUNT = 20;
KisSwatchGroup::KisSwatchGroup()
: d(new Private)
{ }
KisSwatchGroup::~KisSwatchGroup() = default;
KisSwatchGroup::KisSwatchGroup(const KisSwatchGroup &rhs)
: d(new Private(*rhs.d))
{ }
KisSwatchGroup &KisSwatchGroup::operator =(const KisSwatchGroup &rhs)
{
if (&rhs == this) {
return *this;
}
d.reset(new Private(*rhs.d));
return *this;
}
void KisSwatchGroup::setEntry(const KisSwatch &e, int column, int row)
{
Q_ASSERT(column < d->colorMatrix.size() && column >= 0 && row >= 0);
if (row >= d->rowCount) {
setRowCount(row + 1);
}
if (!checkEntry(column, row)) {
d->colorCount++;
}
d->colorMatrix[column][row] = e;
}
bool KisSwatchGroup::checkEntry(int column, int row) const
{
if (row >= d->rowCount) {
return false;
}
if (column >= d->colorMatrix.size()){
return false;
}
if (column < 0) {
return false;
}
if (!d->colorMatrix[column].contains(row)) {
return false;
}
if (!d->colorMatrix[column][row].isValid()) {
return false;
}
return true;
}
bool KisSwatchGroup::removeEntry(int column, int row)
{
if (d->colorCount == 0) {
return false;
}
if (row >= d->rowCount || column >= d->colorMatrix.size() || column < 0) {
return false;
}
// QMap::remove returns 1 if key found else 0
if (d->colorMatrix[column].remove(row)) {
d->colorCount -= 1;
return true;
} else {
return false;
}
}
void KisSwatchGroup::setColumnCount(int columnCount)
{
Q_ASSERT(columnCount >= 0);
if (columnCount < d->colorMatrix.size()) {
int newColorCount = 0;
for (int i = 0; i < columnCount; i++ ) {
newColorCount += d->colorMatrix[i].size();
}
d->colorCount = newColorCount;
}
d->colorMatrix.resize(columnCount);
}
int KisSwatchGroup::columnCount() const {
return d->colorMatrix.size();
}
KisSwatch KisSwatchGroup::getEntry(int column, int row) const
{
Q_ASSERT(checkEntry(column, row));
return d->colorMatrix[column][row];
}
void KisSwatchGroup::addEntry(const KisSwatch &e)
{
if (columnCount() == 0) {
setColumnCount(Private::DEFAULT_COLUMN_COUNT);
}
int y = 0;
int x = 0;
while(checkEntry(x, y))
{
if(++x == d->colorMatrix.size())
{
x = 0;
++y;
}
}
setEntry(e, x, y);
}
void KisSwatchGroup::clear()
{
d->colorMatrix.clear();
}
void KisSwatchGroup::setRowCount(int newRowCount)
{
d->rowCount = newRowCount;
for (Private::Column &c : d->colorMatrix) {
for (int k : c.keys()) {
if (k >= newRowCount) {
c.remove(k);
d->colorCount--;
}
}
}
}
int KisSwatchGroup::rowCount() const
{
return d->rowCount;
}
int KisSwatchGroup::colorCount() const
{
return d->colorCount;
}
QList<KisSwatchGroup::SwatchInfo> KisSwatchGroup::infoList() const
{
QList<SwatchInfo> res;
int column = 0;
for (const Private::Column &c : d->colorMatrix) {
int i = 0;
for (const KisSwatch &s : c.values()) {
SwatchInfo info = {d->name, s, c.keys()[i++], column};
res.append(info);
}
column++;
}
return res;
}
void KisSwatchGroup::setName(const QString &name)
{
d->name = name;
}
QString KisSwatchGroup::name() const
{
return d->name;
}
diff --git a/libs/pigment/resources/KisSwatchGroup.h b/libs/pigment/resources/KisSwatchGroup.h
index e98515edea..7794938449 100644
--- a/libs/pigment/resources/KisSwatchGroup.h
+++ b/libs/pigment/resources/KisSwatchGroup.h
@@ -1,127 +1,127 @@
/* This file is part of the KDE project
Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
- Copyright (c) 2016 L. E. Segovia <leo.segovia@siggraph.org>
+ Copyright (c) 2016 L. E. Segovia <amy@amyspark.me>
Copyright (c) 2018 Michael Zhou <simerixh@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; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef KISSWATCHGROUP_H
#define KISSWATCHGROUP_H
#include "KisSwatch.h"
#include "kritapigment_export.h"
#include <QVector>
#include <QList>
#include <QMap>
#include <QScopedPointer>
/**
* @brief The KisSwatchGroup class stores a matrix of color swatches
* swatches can accessed using (x, y) coordinates.
* x is the column number from left to right and y is the row number from top
* to bottom.
* Both x and y start at 0
* there could be empty entries, so the checkEntry(int, int) method must used
* whenever you want to get an entry from the matrix
*/
class KRITAPIGMENT_EXPORT KisSwatchGroup
{
public /* struct */:
struct SwatchInfo {
QString group;
KisSwatch swatch;
int row;
int column;
};
public:
KisSwatchGroup();
~KisSwatchGroup();
KisSwatchGroup(const KisSwatchGroup &rhs);
KisSwatchGroup &operator =(const KisSwatchGroup &rhs);
public /* methods */:
void setName(const QString &name);
QString name() const;
void setColumnCount(int columnCount);
int columnCount() const;
void setRowCount(int newRowCount);
int rowCount() const;
int colorCount() const;
QList<SwatchInfo> infoList() const;
/**
* @brief checkEntry
* checks if position @p column and @p row has a valid entry
* both @p column and @p row start from 0
* @param column
* @param row
* @return true if there is a valid entry at position (column, row)
*/
bool checkEntry(int column, int row) const;
/**
* @brief setEntry
* sets the entry at position (@p column, @p row) to be @p e
* @param e
* @param column
* @param row
*/
void setEntry(const KisSwatch &e, int column, int row);
/**
* @brief getEntry
* used to get the swatch entry at position (@p column, @p row)
* there is an assertion to make sure that this position isn't empty,
* so checkEntry(int, int) must be used before this method to ensure
* a valid entry can be found
* @param column
* @param row
* @return the swatch entry at position (column, row)
*/
KisSwatch getEntry(int column, int row) const;
/**
* @brief removeEntry
* removes the entry at position (@p column, @p row)
* @param column
* @param row
* @return true if these is an entry at (column, row)
*/
bool removeEntry(int column, int row);
/**
* @brief addEntry
* adds the entry e to the right of the rightmost entry in the last row
* if the rightmost entry in the last row is in the right most column,
* add e to the leftmost column of a new row
*
* when column is set to 0, resize number of columns to default
* @param e
*/
void addEntry(const KisSwatch &e);
void clear();
private /* member variables */:
struct Private;
QScopedPointer<Private> d;
};
#endif // KISSWATCHGROUP_H
diff --git a/libs/pigment/resources/KoColorSet.cpp b/libs/pigment/resources/KoColorSet.cpp
index 722f221884..da4dffde8d 100644
--- a/libs/pigment/resources/KoColorSet.cpp
+++ b/libs/pigment/resources/KoColorSet.cpp
@@ -1,1646 +1,1646 @@
/* This file is part of the KDE project
Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
- Copyright (c) 2016 L. E. Segovia <leo.segovia@siggraph.org>
+ Copyright (c) 2016 L. E. Segovia <amy@amyspark.me>
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/KoColorSet.h>
#include <sys/types.h>
#include <QFile>
#include <QFileInfo>
#include <QBuffer>
#include <QVector>
#include <QTextStream>
#include <QTextCodec>
#include <QHash>
#include <QList>
#include <QByteArray>
#include <QDomDocument>
#include <QDomElement>
#include <QDomNodeList>
#include <QString>
#include <QStringList>
#include <QImage>
#include <QPainter>
#include <QXmlStreamReader>
#include <QXmlStreamAttributes>
#include <QtEndian> // qFromLittleEndian
#include <DebugPigment.h>
#include <klocalizedstring.h>
#include <KoStore.h>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KoColorModelStandardIds.h>
#include "KisSwatch.h"
#include "KoColorSet.h"
#include "KoColorSet_p.h"
namespace {
/**
* readAllLinesSafe() reads all the lines in the byte array
* using the automated UTF8 and CR/LF transformations. That
* might be necessary for opening GPL palettes created on Linux
* in Windows environment.
*/
QStringList readAllLinesSafe(QByteArray *data)
{
QStringList lines;
QBuffer buffer(data);
buffer.open(QBuffer::ReadOnly);
QTextStream stream(&buffer);
QString line;
while (stream.readLineInto(&line)) {
lines << line;
}
return lines;
}
}
const QString KoColorSet::GLOBAL_GROUP_NAME = QString();
const QString KoColorSet::KPL_VERSION_ATTR = "version";
const QString KoColorSet::KPL_GROUP_ROW_COUNT_ATTR = "rows";
const QString KoColorSet::KPL_PALETTE_COLUMN_COUNT_ATTR = "columns";
const QString KoColorSet::KPL_PALETTE_NAME_ATTR = "name";
const QString KoColorSet::KPL_PALETTE_COMMENT_ATTR = "comment";
const QString KoColorSet::KPL_PALETTE_FILENAME_ATTR = "filename";
const QString KoColorSet::KPL_PALETTE_READONLY_ATTR = "readonly";
const QString KoColorSet::KPL_COLOR_MODEL_ID_ATTR = "colorModelId";
const QString KoColorSet::KPL_COLOR_DEPTH_ID_ATTR = "colorDepthId";
const QString KoColorSet::KPL_GROUP_NAME_ATTR = "name";
const QString KoColorSet::KPL_SWATCH_ROW_ATTR = "row";
const QString KoColorSet::KPL_SWATCH_COL_ATTR = "column";
const QString KoColorSet::KPL_SWATCH_NAME_ATTR = "name";
const QString KoColorSet::KPL_SWATCH_ID_ATTR = "id";
const QString KoColorSet::KPL_SWATCH_SPOT_ATTR = "spot";
const QString KoColorSet::KPL_SWATCH_BITDEPTH_ATTR = "bitdepth";
const QString KoColorSet::KPL_PALETTE_PROFILE_TAG = "Profile";
const QString KoColorSet::KPL_SWATCH_POS_TAG = "Position";
const QString KoColorSet::KPL_SWATCH_TAG = "ColorSetEntry";
const QString KoColorSet::KPL_GROUP_TAG = "Group";
const QString KoColorSet::KPL_PALETTE_TAG = "ColorSet";
const int MAXIMUM_ALLOWED_COLUMNS = 4096;
KoColorSet::KoColorSet(const QString& filename)
: KoResource(filename)
, d(new Private(this))
{
if (!filename.isEmpty()) {
QFileInfo f(filename);
setIsEditable(f.isWritable());
}
}
/// Create an copied palette
KoColorSet::KoColorSet(const KoColorSet& rhs)
: QObject(0)
, KoResource(rhs)
, d(new Private(this))
{
d->paletteType = rhs.d->paletteType;
d->data = rhs.d->data;
d->comment = rhs.d->comment;
d->groupNames = rhs.d->groupNames;
d->groups = rhs.d->groups;
d->isGlobal = rhs.d->isGlobal;
d->isEditable = rhs.d->isEditable;
}
KoColorSet::~KoColorSet()
{ }
bool KoColorSet::load()
{
QFile file(filename());
if (file.size() == 0) return false;
if (!file.open(QIODevice::ReadOnly)) {
warnPigment << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&file);
file.close();
if (!QFileInfo(filename()).isWritable()) {
setIsEditable(false);
}
return res;
}
bool KoColorSet::loadFromDevice(QIODevice *dev)
{
if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
d->data = dev->readAll();
Q_ASSERT(d->data.size() != 0);
return d->init();
}
bool KoColorSet::save()
{
if (d->isGlobal) {
// save to resource dir
QFile file(filename());
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
return false;
}
saveToDevice(&file);
file.close();
return true;
} else {
return true; // palette is not global, but still indicate that it's saved
}
}
bool KoColorSet::saveToDevice(QIODevice *dev) const
{
bool res;
switch(d->paletteType) {
case GPL:
res = d->saveGpl(dev);
break;
default:
res = d->saveKpl(dev);
}
if (res) {
KoResource::saveToDevice(dev);
}
return res;
}
QByteArray KoColorSet::toByteArray() const
{
QBuffer s;
s.open(QIODevice::WriteOnly);
if (!saveToDevice(&s)) {
warnPigment << "saving palette failed:" << name();
return QByteArray();
}
s.close();
s.open(QIODevice::ReadOnly);
QByteArray res = s.readAll();
s.close();
return res;
}
bool KoColorSet::fromByteArray(QByteArray &data)
{
QBuffer buf(&data);
buf.open(QIODevice::ReadOnly);
return loadFromDevice(&buf);
}
KoColorSet::PaletteType KoColorSet::paletteType() const
{
return d->paletteType;
}
void KoColorSet::setPaletteType(PaletteType paletteType)
{
d->paletteType = paletteType;
QString suffix;
switch(d->paletteType) {
case GPL:
suffix = ".gpl";
break;
case ACT:
suffix = ".act";
break;
case RIFF_PAL:
case PSP_PAL:
suffix = ".pal";
break;
case ACO:
suffix = ".aco";
break;
case XML:
suffix = ".xml";
break;
case KPL:
suffix = ".kpl";
break;
case SBZ:
suffix = ".sbz";
break;
default:
suffix = defaultFileExtension();
}
QStringList fileName = filename().split(".");
fileName.last() = suffix.replace(".", "");
setFilename(fileName.join("."));
}
quint32 KoColorSet::colorCount() const
{
int colorCount = 0;
for (KisSwatchGroup &g : d->groups.values()) {
colorCount += g.colorCount();
}
return colorCount;
}
void KoColorSet::add(const KisSwatch &c, const QString &groupName)
{
KisSwatchGroup &modifiedGroup = d->groups.contains(groupName)
? d->groups[groupName] : d->global();
modifiedGroup.addEntry(c);
}
void KoColorSet::setEntry(const KisSwatch &e, int x, int y, const QString &groupName)
{
KisSwatchGroup &modifiedGroup = d->groups.contains(groupName)
? d->groups[groupName] : d->global();
modifiedGroup.setEntry(e, x, y);
}
void KoColorSet::clear()
{
d->groups.clear();
d->groupNames.clear();
d->groups[GLOBAL_GROUP_NAME] = KisSwatchGroup();
d->groupNames.append(GLOBAL_GROUP_NAME);
}
KisSwatch KoColorSet::getColorGlobal(quint32 x, quint32 y) const
{
for (const QString &groupName : getGroupNames()) {
if (d->groups.contains(groupName)) {
if ((int)y < d->groups[groupName].rowCount()) {
return d->groups[groupName].getEntry(x, y);
} else {
y -= d->groups[groupName].rowCount();
}
}
}
return KisSwatch();
}
KisSwatch KoColorSet::getColorGroup(quint32 x, quint32 y, QString groupName)
{
KisSwatch e;
const KisSwatchGroup &sourceGroup = groupName == QString()
? d->global() : d->groups[groupName];
if (sourceGroup.checkEntry(x, y)) {
e = sourceGroup.getEntry(x, y);
}
return e;
}
QStringList KoColorSet::getGroupNames() const
{
if (d->groupNames.size() != d->groups.size()) {
warnPigment << "mismatch between groups and the groupnames list.";
return QStringList(d->groups.keys());
}
return d->groupNames;
}
bool KoColorSet::changeGroupName(const QString &oldGroupName, const QString &newGroupName)
{
if (!d->groups.contains(oldGroupName)) {
return false;
}
if (oldGroupName == newGroupName) {
return true;
}
d->groups[newGroupName] = d->groups[oldGroupName];
d->groups.remove(oldGroupName);
d->groups[newGroupName].setName(newGroupName);
//rename the string in the stringlist;
int index = d->groupNames.indexOf(oldGroupName);
d->groupNames.replace(index, newGroupName);
return true;
}
void KoColorSet::setColumnCount(int columns)
{
d->groups[GLOBAL_GROUP_NAME].setColumnCount(columns);
for (KisSwatchGroup &g : d->groups.values()) {
g.setColumnCount(columns);
}
}
int KoColorSet::columnCount() const
{
return d->groups[GLOBAL_GROUP_NAME].columnCount();
}
QString KoColorSet::comment()
{
return d->comment;
}
void KoColorSet::setComment(QString comment)
{
d->comment = comment;
}
bool KoColorSet::addGroup(const QString &groupName)
{
if (d->groups.contains(groupName) || getGroupNames().contains(groupName)) {
return false;
}
d->groupNames.append(groupName);
d->groups[groupName] = KisSwatchGroup();
d->groups[groupName].setName(groupName);
return true;
}
bool KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore)
{
if (!d->groupNames.contains(groupName) || d->groupNames.contains(groupNameInsertBefore)==false) {
return false;
}
if (groupNameInsertBefore != GLOBAL_GROUP_NAME && groupName != GLOBAL_GROUP_NAME) {
d->groupNames.removeAt(d->groupNames.indexOf(groupName));
int index = d->groupNames.indexOf(groupNameInsertBefore);
d->groupNames.insert(index, groupName);
}
return true;
}
bool KoColorSet::removeGroup(const QString &groupName, bool keepColors)
{
if (!d->groups.contains(groupName)) {
return false;
}
if (groupName == GLOBAL_GROUP_NAME) {
return false;
}
if (keepColors) {
// put all colors directly below global
int startingRow = d->groups[GLOBAL_GROUP_NAME].rowCount();
for (const KisSwatchGroup::SwatchInfo &info : d->groups[groupName].infoList()) {
d->groups[GLOBAL_GROUP_NAME].setEntry(info.swatch,
info.column,
info.row + startingRow);
}
}
d->groupNames.removeAt(d->groupNames.indexOf(groupName));
d->groups.remove(groupName);
return true;
}
QString KoColorSet::defaultFileExtension() const
{
return QString(".kpl");
}
int KoColorSet::rowCount() const
{
int res = 0;
for (const QString &name : getGroupNames()) {
res += d->groups[name].rowCount();
}
return res;
}
KisSwatchGroup *KoColorSet::getGroup(const QString &name)
{
if (!d->groups.contains(name)) {
return 0;
}
return &(d->groups[name]);
}
KisSwatchGroup *KoColorSet::getGlobalGroup()
{
return getGroup(GLOBAL_GROUP_NAME);
}
bool KoColorSet::isGlobal() const
{
return d->isGlobal;
}
void KoColorSet::setIsGlobal(bool isGlobal)
{
d->isGlobal = isGlobal;
}
bool KoColorSet::isEditable() const
{
return d->isEditable;
}
void KoColorSet::setIsEditable(bool isEditable)
{
d->isEditable = isEditable;
}
KisSwatchGroup::SwatchInfo KoColorSet::getClosestColorInfo(KoColor compare, bool useGivenColorSpace)
{
KisSwatchGroup::SwatchInfo res;
quint8 highestPercentage = 0;
quint8 testPercentage = 0;
for (const QString &groupName : getGroupNames()) {
KisSwatchGroup *group = getGroup(groupName);
for (const KisSwatchGroup::SwatchInfo &currInfo : group->infoList()) {
KoColor color = currInfo.swatch.color();
if (useGivenColorSpace == true && compare.colorSpace() != color.colorSpace()) {
color.convertTo(compare.colorSpace());
} else if (compare.colorSpace() != color.colorSpace()) {
compare.convertTo(color.colorSpace());
}
testPercentage = (255 - compare.colorSpace()->difference(compare.data(), color.data()));
if (testPercentage > highestPercentage)
{
highestPercentage = testPercentage;
res = currInfo;
}
}
}
return res;
}
/********************************KoColorSet::Private**************************/
KoColorSet::Private::Private(KoColorSet *a_colorSet)
: colorSet(a_colorSet)
{
groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup();
groupNames.append(KoColorSet::GLOBAL_GROUP_NAME);
}
KoColorSet::PaletteType KoColorSet::Private::detectFormat(const QString &fileName, const QByteArray &ba)
{
QFileInfo fi(fileName);
// .pal
if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) {
return KoColorSet::RIFF_PAL;
}
// .gpl
else if (ba.startsWith("GIMP Palette")) {
return KoColorSet::GPL;
}
// .pal
else if (ba.startsWith("JASC-PAL")) {
return KoColorSet::PSP_PAL;
}
else if (fi.suffix().toLower() == "aco") {
return KoColorSet::ACO;
}
else if (fi.suffix().toLower() == "act") {
return KoColorSet::ACT;
}
else if (fi.suffix().toLower() == "xml") {
return KoColorSet::XML;
}
else if (fi.suffix().toLower() == "kpl") {
return KoColorSet::KPL;
}
else if (fi.suffix().toLower() == "sbz") {
return KoColorSet::SBZ;
}
return KoColorSet::UNKNOWN;
}
void KoColorSet::Private::scribusParseColor(KoColorSet *set, QXmlStreamReader *xml)
{
KisSwatch colorEntry;
// It's a color, retrieve it
QXmlStreamAttributes colorProperties = xml->attributes();
QStringRef colorName = colorProperties.value("NAME");
colorEntry.setName(colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString());
// RGB or CMYK?
if (colorProperties.hasAttribute("RGB")) {
dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB");
KoColor currentColor(KoColorSpaceRegistry::instance()->rgb8());
QStringRef colorValue = colorProperties.value("RGB");
if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number
xml->raiseError("Invalid rgb8 color (malformed): " + colorValue);
return;
} else {
bool rgbOk;
quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16);
if (!rgbOk) {
xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue);
return;
}
quint8 r = rgb >> 16 & 0xff;
quint8 g = rgb >> 8 & 0xff;
quint8 b = rgb & 0xff;
dbgPigment << "Color parsed: "<< r << g << b;
currentColor.data()[0] = r;
currentColor.data()[1] = g;
currentColor.data()[2] = b;
currentColor.setOpacity(OPACITY_OPAQUE_U8);
colorEntry.setColor(currentColor);
set->add(colorEntry);
while(xml->readNextStartElement()) {
//ignore - these are all unknown or the /> element tag
xml->skipCurrentElement();
}
return;
}
}
else if (colorProperties.hasAttribute("CMYK")) {
dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK");
KoColor currentColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString()));
QStringRef colorValue = colorProperties.value("CMYK");
if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number
xml->raiseError("Invalid cmyk color (malformed): " % colorValue);
return;
}
else {
bool cmykOk;
quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits
if (!cmykOk) {
xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue);
return;
}
quint8 c = cmyk >> 24 & 0xff;
quint8 m = cmyk >> 16 & 0xff;
quint8 y = cmyk >> 8 & 0xff;
quint8 k = cmyk & 0xff;
dbgPigment << "Color parsed: "<< c << m << y << k;
currentColor.data()[0] = c;
currentColor.data()[1] = m;
currentColor.data()[2] = y;
currentColor.data()[3] = k;
currentColor.setOpacity(OPACITY_OPAQUE_U8);
colorEntry.setColor(currentColor);
set->add(colorEntry);
while(xml->readNextStartElement()) {
//ignore - these are all unknown or the /> element tag
xml->skipCurrentElement();
}
return;
}
}
else {
xml->raiseError("Unknown color space for color " + colorEntry.name());
}
}
bool KoColorSet::Private::loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml)
{
//1. Get name
QXmlStreamAttributes paletteProperties = xml->attributes();
QStringRef paletteName = paletteProperties.value("Name");
dbgPigment << "Processed name of palette:" << paletteName;
set->setName(paletteName.toString());
//2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them
while(xml->readNextStartElement()) {
QStringRef currentElement = xml->name();
if(QStringRef::compare(currentElement, "COLOR", Qt::CaseInsensitive) == 0) {
scribusParseColor(set, xml);
}
else {
xml->skipCurrentElement();
}
}
if(xml->hasError()) {
return false;
}
return true;
}
quint16 KoColorSet::Private::readShort(QIODevice *io) {
quint16 val;
quint64 read = io->read((char*)&val, 2);
if (read != 2) return false;
return qFromBigEndian(val);
}
bool KoColorSet::Private::init()
{
// just in case this is a reload (eg by KoEditColorSetDialog),
groupNames.clear();
groups.clear();
groupNames.append(KoColorSet::GLOBAL_GROUP_NAME);
groups[KoColorSet::GLOBAL_GROUP_NAME] = KisSwatchGroup();
if (colorSet->filename().isNull()) {
warnPigment << "Cannot load palette" << colorSet->name() << "there is no filename set";
return false;
}
if (data.isNull()) {
QFile file(colorSet->filename());
if (file.size() == 0) {
warnPigment << "Cannot load palette" << colorSet->name() << "there is no data available";
return false;
}
file.open(QIODevice::ReadOnly);
data = file.readAll();
file.close();
}
bool res = false;
paletteType = detectFormat(colorSet->filename(), data);
switch(paletteType) {
case GPL:
res = loadGpl();
break;
case ACT:
res = loadAct();
break;
case RIFF_PAL:
res = loadRiff();
break;
case PSP_PAL:
res = loadPsp();
break;
case ACO:
res = loadAco();
break;
case XML:
res = loadXml();
break;
case KPL:
res = loadKpl();
break;
case SBZ:
res = loadSbz();
break;
default:
res = false;
}
colorSet->setValid(res);
QImage img(global().columnCount() * 4, global().rowCount() * 4, QImage::Format_ARGB32);
QPainter gc(&img);
gc.fillRect(img.rect(), Qt::darkGray);
for (const KisSwatchGroup::SwatchInfo &info : global().infoList()) {
QColor c = info.swatch.color().toQColor();
gc.fillRect(info.column * 4, info.row * 4, 4, 4, c);
}
colorSet->setImage(img);
colorSet->setValid(res);
data.clear();
return res;
}
bool KoColorSet::Private::saveGpl(QIODevice *dev) const
{
Q_ASSERT(dev->isOpen());
Q_ASSERT(dev->isWritable());
QTextStream stream(dev);
stream << "GIMP Palette\nName: " << colorSet->name() << "\nColumns: " << colorSet->columnCount() << "\n#\n";
/*
* Qt doesn't provide an interface to get a const reference to a QHash, that is
* the underlying data structure of groups. Therefore, directly use
* groups[KoColorSet::GLOBAL_GROUP_NAME] so that saveGpl can stay const
*/
for (int y = 0; y < groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount(); y++) {
for (int x = 0; x < colorSet->columnCount(); x++) {
if (!groups[KoColorSet::GLOBAL_GROUP_NAME].checkEntry(x, y)) {
continue;
}
const KisSwatch& entry = groups[KoColorSet::GLOBAL_GROUP_NAME].getEntry(x, y);
QColor c = entry.color().toQColor();
stream << c.red() << " " << c.green() << " " << c.blue() << "\t";
if (entry.name().isEmpty())
stream << "Untitled\n";
else
stream << entry.name() << "\n";
}
}
return true;
}
bool KoColorSet::Private::loadGpl()
{
if (data.isEmpty() || data.isNull() || data.length() < 50) {
warnPigment << "Illegal Gimp palette file: " << colorSet->filename();
return false;
}
quint32 index = 0;
QStringList lines = readAllLinesSafe(&data);
if (lines.size() < 3) {
warnPigment << "Not enough lines in palette file: " << colorSet->filename();
return false;
}
QString columnsText;
qint32 r, g, b;
KisSwatch e;
// Read name
if (!lines[0].startsWith("GIMP") || !lines[1].toLower().contains("name")) {
warnPigment << "Illegal Gimp palette file: " << colorSet->filename();
return false;
}
colorSet->setName(i18n(lines[1].split(":")[1].trimmed().toLatin1()));
index = 2;
// Read columns
int columns = 0;
if (lines[index].toLower().contains("columns")) {
columnsText = lines[index].split(":")[1].trimmed();
columns = columnsText.toInt();
if (columns > MAXIMUM_ALLOWED_COLUMNS) {
warnPigment << "Refusing to set unreasonable number of columns (" << columns << ") in GIMP Palette file " << colorSet->filename() << " - using maximum number of allowed columns instead";
global().setColumnCount(MAXIMUM_ALLOWED_COLUMNS);
}
else {
global().setColumnCount(columns);
}
index = 3;
}
for (qint32 i = index; i < lines.size(); i++) {
if (lines[i].startsWith('#')) {
comment += lines[i].mid(1).trimmed() + ' ';
} else if (!lines[i].isEmpty()) {
QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts);
if (a.count() < 3) {
continue;
}
r = qBound(0, a[0].toInt(), 255);
g = qBound(0, a[1].toInt(), 255);
b = qBound(0, a[2].toInt(), 255);
e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
for (int i = 0; i != 3; i++) {
a.pop_front();
}
QString name = a.join(" ");
e.setName(name.isEmpty() || name == "Untitled" ? i18n("Untitled") : name);
global().addEntry(e);
}
}
int rowCount = global().colorCount()/ global().columnCount();
if (global().colorCount() % global().columnCount()>0) {
rowCount ++;
}
global().setRowCount(rowCount);
return true;
}
bool KoColorSet::Private::loadAct()
{
QFileInfo info(colorSet->filename());
colorSet->setName(info.completeBaseName());
KisSwatch e;
for (int i = 0; i < data.size(); i += 3) {
quint8 r = data[i];
quint8 g = data[i+1];
quint8 b = data[i+2];
e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
global().addEntry(e);
}
return true;
}
bool KoColorSet::Private::loadRiff()
{
- // http://worms2d.info/Palette_file
+ // https://worms2d.info/Palette_file
QFileInfo info(colorSet->filename());
colorSet->setName(info.completeBaseName());
KisSwatch e;
RiffHeader header;
memcpy(&header, data.constData(), sizeof(RiffHeader));
header.colorcount = qFromBigEndian(header.colorcount);
for (int i = sizeof(RiffHeader);
(i < (int)(sizeof(RiffHeader) + header.colorcount) && i < data.size());
i += 4) {
quint8 r = data[i];
quint8 g = data[i+1];
quint8 b = data[i+2];
e.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e);
}
return true;
}
bool KoColorSet::Private::loadPsp()
{
QFileInfo info(colorSet->filename());
colorSet->setName(info.completeBaseName());
KisSwatch e;
qint32 r, g, b;
QStringList l = readAllLinesSafe(&data);
if (l.size() < 4) return false;
if (l[0] != "JASC-PAL") return false;
if (l[1] != "0100") return false;
int entries = l[2].toInt();
for (int i = 0; i < entries; ++i) {
QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts);
if (a.count() != 3) {
continue;
}
r = qBound(0, a[0].toInt(), 255);
g = qBound(0, a[1].toInt(), 255);
b = qBound(0, a[2].toInt(), 255);
e.setColor(KoColor(QColor(r, g, b),
KoColorSpaceRegistry::instance()->rgb8()));
QString name = a.join(" ");
e.setName(name.isEmpty() ? i18n("Untitled") : name);
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e);
}
return true;
}
bool KoColorSet::Private::loadKpl()
{
QBuffer buf(&data);
buf.open(QBuffer::ReadOnly);
QScopedPointer<KoStore> store(KoStore::createStore(&buf, KoStore::Read, "krita/x-colorset", KoStore::Zip));
if (!store || store->bad()) { return false; }
if (store->hasFile("profiles.xml")) {
if (!store->open("profiles.xml")) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
QDomDocument doc;
doc.setContent(ba);
QDomElement e = doc.documentElement();
QDomElement c = e.firstChildElement(KPL_PALETTE_PROFILE_TAG);
while (!c.isNull()) {
QString name = c.attribute(KPL_PALETTE_NAME_ATTR);
QString filename = c.attribute(KPL_PALETTE_FILENAME_ATTR);
QString colorModelId = c.attribute(KPL_COLOR_MODEL_ID_ATTR);
QString colorDepthId = c.attribute(KPL_COLOR_DEPTH_ID_ATTR);
if (!KoColorSpaceRegistry::instance()->profileByName(name)) {
store->open(filename);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(colorModelId, colorDepthId, data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
}
}
c = c.nextSiblingElement();
}
}
{
if (!store->open("colorset.xml")) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
int desiredColumnCount;
QDomDocument doc;
doc.setContent(ba);
QDomElement e = doc.documentElement();
colorSet->setName(e.attribute(KPL_PALETTE_NAME_ATTR));
colorSet->setIsEditable(e.attribute(KPL_PALETTE_READONLY_ATTR) != "true");
comment = e.attribute(KPL_PALETTE_COMMENT_ATTR);
desiredColumnCount = e.attribute(KPL_PALETTE_COLUMN_COUNT_ATTR).toInt();
if (desiredColumnCount > MAXIMUM_ALLOWED_COLUMNS) {
warnPigment << "Refusing to set unreasonable number of columns (" << desiredColumnCount << ") in KPL palette file " << colorSet->filename() << " - setting maximum allowed column count instead.";
colorSet->setColumnCount(MAXIMUM_ALLOWED_COLUMNS);
}
else {
colorSet->setColumnCount(desiredColumnCount);
}
loadKplGroup(doc, e, colorSet->getGlobalGroup());
QDomElement g = e.firstChildElement(KPL_GROUP_TAG);
while (!g.isNull()) {
QString groupName = g.attribute(KPL_GROUP_NAME_ATTR);
colorSet->addGroup(groupName);
loadKplGroup(doc, g, colorSet->getGroup(groupName));
g = g.nextSiblingElement(KPL_GROUP_TAG);
}
}
buf.close();
return true;
}
bool KoColorSet::Private::loadAco()
{
QFileInfo info(colorSet->filename());
colorSet->setName(info.completeBaseName());
QBuffer buf(&data);
buf.open(QBuffer::ReadOnly);
quint16 version = readShort(&buf);
quint16 numColors = readShort(&buf);
KisSwatch e;
if (version == 1 && buf.size() > 4+numColors*10) {
buf.seek(4+numColors*10);
version = readShort(&buf);
numColors = readShort(&buf);
}
const quint16 quint16_MAX = 65535;
for (int i = 0; i < numColors && !buf.atEnd(); ++i) {
quint16 colorSpace = readShort(&buf);
quint16 ch1 = readShort(&buf);
quint16 ch2 = readShort(&buf);
quint16 ch3 = readShort(&buf);
quint16 ch4 = readShort(&buf);
bool skip = false;
if (colorSpace == 0) { // RGB
const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile();
KoColor c(KoColorSpaceRegistry::instance()->rgb16(srgb));
reinterpret_cast<quint16*>(c.data())[0] = ch3;
reinterpret_cast<quint16*>(c.data())[1] = ch2;
reinterpret_cast<quint16*>(c.data())[2] = ch1;
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 1) { // HSB
QColor qc;
qc.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0);
KoColor c(qc, KoColorSpaceRegistry::instance()->rgb16());
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 2) { // CMYK
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), QString()));
reinterpret_cast<quint16*>(c.data())[0] = quint16_MAX - ch1;
reinterpret_cast<quint16*>(c.data())[1] = quint16_MAX - ch2;
reinterpret_cast<quint16*>(c.data())[2] = quint16_MAX - ch3;
reinterpret_cast<quint16*>(c.data())[3] = quint16_MAX - ch4;
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 7) { // LAB
KoColor c = KoColor(KoColorSpaceRegistry::instance()->lab16());
reinterpret_cast<quint16*>(c.data())[0] = ch3;
reinterpret_cast<quint16*>(c.data())[1] = ch2;
reinterpret_cast<quint16*>(c.data())[2] = ch1;
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else if (colorSpace == 8) { // GRAY
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), QString()));
reinterpret_cast<quint16*>(c.data())[0] = ch1 * (quint16_MAX / 10000);
c.setOpacity(OPACITY_OPAQUE_U8);
e.setColor(c);
}
else {
warnPigment << "Unsupported colorspace in palette" << colorSet->filename() << "(" << colorSpace << ")";
skip = true;
}
if (version == 2) {
quint16 v2 = readShort(&buf); //this isn't a version, it's a marker and needs to be skipped.
Q_UNUSED(v2);
quint16 size = readShort(&buf) -1; //then comes the length
if (size>0) {
QByteArray ba = buf.read(size*2);
if (ba.size() == size*2) {
QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE");
e.setName(Utf16Codec->toUnicode(ba));
} else {
warnPigment << "Version 2 name block is the wrong size" << colorSet->filename();
}
}
v2 = readShort(&buf); //end marker also needs to be skipped.
Q_UNUSED(v2);
}
if (!skip) {
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(e);
}
}
return true;
}
bool KoColorSet::Private::loadSbz() {
QBuffer buf(&data);
buf.open(QBuffer::ReadOnly);
// &buf is a subclass of QIODevice
QScopedPointer<KoStore> store(KoStore::createStore(&buf, KoStore::Read, "application/x-swatchbook", KoStore::Zip));
if (!store || store->bad()) return false;
if (store->hasFile("swatchbook.xml")) { // Try opening...
if (!store->open("swatchbook.xml")) { return false; }
QByteArray data;
data.resize(store->size());
QByteArray ba = store->read(store->size());
store->close();
dbgPigment << "XML palette: " << colorSet->filename() << ", SwatchBooker format";
QDomDocument doc;
int errorLine, errorColumn;
QString errorMessage;
bool status = doc.setContent(ba, &errorMessage, &errorLine, &errorColumn);
if (!status) {
warnPigment << "Illegal XML palette:" << colorSet->filename();
warnPigment << "Error (line" << errorLine << ", column" << errorColumn << "):" << errorMessage;
return false;
}
QDomElement e = doc.documentElement(); // SwatchBook
// Start reading properties...
QDomElement metadata = e.firstChildElement("metadata");
if (e.isNull()) {
warnPigment << "Palette metadata not found";
return false;
}
QDomElement title = metadata.firstChildElement("dc:title");
QString colorName = title.text();
colorName = colorName.isEmpty() ? i18n("Untitled") : colorName;
colorSet->setName(colorName);
dbgPigment << "Processed name of palette:" << colorSet->name();
// End reading properties
// Now read colors...
QDomElement materials = e.firstChildElement("materials");
if (materials.isNull()) {
warnPigment << "Materials (color definitions) not found";
return false;
}
// This one has lots of "color" elements
QDomElement colorElement = materials.firstChildElement("color");
if (colorElement.isNull()) {
warnPigment << "Color definitions not found (line" << materials.lineNumber() << ", column" << materials.columnNumber() << ")";
return false;
}
// Also read the swatch book...
QDomElement book = e.firstChildElement("book");
if (book.isNull()) {
warnPigment << "Palette book (swatch composition) not found (line" << e.lineNumber() << ", column" << e.columnNumber() << ")";
return false;
}
// Which has lots of "swatch"es (todo: support groups)
QDomElement swatch = book.firstChildElement();
if (swatch.isNull()) {
warnPigment << "Swatches/groups definition not found (line" << book.lineNumber() << ", column" << book.columnNumber() << ")";
return false;
}
// We'll store colors here, and as we process swatches
// we'll add them to the palette
QHash<QString, KisSwatch> materialsBook;
QHash<QString, const KoColorSpace*> fileColorSpaces;
// Color processing
for(; !colorElement.isNull(); colorElement = colorElement.nextSiblingElement("color"))
{
KisSwatch currentEntry;
// Set if color is spot
currentEntry.setSpotColor(colorElement.attribute("usage") == "spot");
// <metadata> inside contains id and name
// one or more <values> define the color
QDomElement currentColorMetadata = colorElement.firstChildElement("metadata");
QDomNodeList currentColorValues = colorElement.elementsByTagName("values");
// Get color name
QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title");
QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier");
// Is there an id? (we need that at the very least for identifying a color)
if (colorId.text().isEmpty()) {
warnPigment << "Unidentified color (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")";
return false;
}
if (materialsBook.contains(colorId.text())) {
warnPigment << "Duplicated color definition (line" << colorId.lineNumber()<< ", column" << colorId.columnNumber() << ")";
return false;
}
// Get a valid color name
currentEntry.setId(colorId.text());
currentEntry.setName(colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text());
// Get a valid color definition
if (currentColorValues.isEmpty()) {
warnPigment << "Color definitions not found (line" << colorElement.lineNumber() << ", column" << colorElement.columnNumber() << ")";
return false;
}
bool firstDefinition = false;
const KoColorProfile *srgb = KoColorSpaceRegistry::instance()->rgb8()->profile();
// Priority: Lab, otherwise the first definition found
for(int j = 0; j < currentColorValues.size(); j++) {
QDomNode colorValue = currentColorValues.at(j);
QDomElement colorValueE = colorValue.toElement();
QString model = colorValueE.attribute("model", QString());
// sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1
// YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5
// Lab: L 0 -> 100 : ab -128 -> 127
// XYZ: 0 -> ~100
if (model == "Lab") {
QStringList lab = colorValueE.text().split(" ");
if (lab.length() != 3) {
warnPigment << "Invalid Lab color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float l = lab.at(0).toFloat(&status);
float a = lab.at(1).toFloat(&status);
float b = lab.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Float32BitsColorDepthID.id(), QString()));
reinterpret_cast<float*>(c.data())[0] = l;
reinterpret_cast<float*>(c.data())[1] = a;
reinterpret_cast<float*>(c.data())[2] = b;
c.setOpacity(OPACITY_OPAQUE_F);
firstDefinition = true;
currentEntry.setColor(c);
break; // Immediately add this one
}
else if (model == "sRGB" && !firstDefinition) {
QStringList rgb = colorValueE.text().split(" ");
if (rgb.length() != 3) {
warnPigment << "Invalid sRGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float r = rgb.at(0).toFloat(&status);
float g = rgb.at(1).toFloat(&status);
float b = rgb.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb));
reinterpret_cast<float*>(c.data())[0] = r;
reinterpret_cast<float*>(c.data())[1] = g;
reinterpret_cast<float*>(c.data())[2] = b;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
else if (model == "XYZ" && !firstDefinition) {
QStringList xyz = colorValueE.text().split(" ");
if (xyz.length() != 3) {
warnPigment << "Invalid XYZ color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float x = xyz.at(0).toFloat(&status);
float y = xyz.at(1).toFloat(&status);
float z = xyz.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
KoColor c(KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Float32BitsColorDepthID.id(), QString()));
reinterpret_cast<float*>(c.data())[0] = x;
reinterpret_cast<float*>(c.data())[1] = y;
reinterpret_cast<float*>(c.data())[2] = z;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
// The following color spaces admit an ICC profile (in SwatchBooker)
else if (model == "CMYK" && !firstDefinition) {
QStringList cmyk = colorValueE.text().split(" ");
if (cmyk.length() != 4) {
warnPigment << "Invalid CMYK color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float c = cmyk.at(0).toFloat(&status);
float m = cmyk.at(1).toFloat(&status);
float y = cmyk.at(2).toFloat(&status);
float k = cmyk.at(3).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
QString space = colorValueE.attribute("space");
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), QString());
if (!space.isEmpty()) {
// Try loading the profile and add it to the registry
if (!fileColorSpaces.contains(space)) {
store->enterDirectory("profiles");
store->open(space);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
fileColorSpaces.insert(space, colorSpace);
}
}
else {
colorSpace = fileColorSpaces.value(space);
}
}
KoColor color(colorSpace);
reinterpret_cast<float*>(color.data())[0] = c;
reinterpret_cast<float*>(color.data())[1] = m;
reinterpret_cast<float*>(color.data())[2] = y;
reinterpret_cast<float*>(color.data())[3] = k;
color.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(color);
firstDefinition = true;
}
else if (model == "GRAY" && !firstDefinition) {
QString gray = colorValueE.text();
float g = gray.toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
QString space = colorValueE.attribute("space");
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Float32BitsColorDepthID.id(), QString());
if (!space.isEmpty()) {
// Try loading the profile and add it to the registry
if (!fileColorSpaces.contains(space)) {
store->enterDirectory("profiles");
store->open(space);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
fileColorSpaces.insert(space, colorSpace);
}
}
else {
colorSpace = fileColorSpaces.value(space);
}
}
KoColor c(colorSpace);
reinterpret_cast<float*>(c.data())[0] = g;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
else if (model == "RGB" && !firstDefinition) {
QStringList rgb = colorValueE.text().split(" ");
if (rgb.length() != 3) {
warnPigment << "Invalid RGB color definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
float r = rgb.at(0).toFloat(&status);
float g = rgb.at(1).toFloat(&status);
float b = rgb.at(2).toFloat(&status);
if (!status) {
warnPigment << "Invalid float definition (line" << colorValueE.lineNumber() << ", column" << colorValueE.columnNumber() << ")";
}
QString space = colorValueE.attribute("space");
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), srgb);
if (!space.isEmpty()) {
// Try loading the profile and add it to the registry
if (!fileColorSpaces.contains(space)) {
store->enterDirectory("profiles");
store->open(space);
QByteArray data;
data.resize(store->size());
data = store->read(store->size());
store->close();
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), data);
if (profile && profile->valid()) {
KoColorSpaceRegistry::instance()->addProfile(profile);
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), profile);
fileColorSpaces.insert(space, colorSpace);
}
}
else {
colorSpace = fileColorSpaces.value(space);
}
}
KoColor c(colorSpace);
reinterpret_cast<float*>(c.data())[0] = r;
reinterpret_cast<float*>(c.data())[1] = g;
reinterpret_cast<float*>(c.data())[2] = b;
c.setOpacity(OPACITY_OPAQUE_F);
currentEntry.setColor(c);
firstDefinition = true;
}
else {
warnPigment << "Color space not implemented:" << model << "(line" << colorValueE.lineNumber() << ", column "<< colorValueE.columnNumber() << ")";
}
}
if (firstDefinition) {
materialsBook.insert(currentEntry.id(), currentEntry);
}
else {
warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber() << ", column "<< colorElement.columnNumber() << ")";
return false;
}
}
// End colors
// Now decide which ones will go into the palette
for(;!swatch.isNull(); swatch = swatch.nextSiblingElement()) {
QString type = swatch.tagName();
if (type.isEmpty() || type.isNull()) {
warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")";
return false;
}
else if (type == "swatch") {
QString id = swatch.attribute("material");
if (id.isEmpty() || id.isNull()) {
warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")";
return false;
}
if (materialsBook.contains(id)) {
groups[KoColorSet::GLOBAL_GROUP_NAME].addEntry(materialsBook.value(id));
}
else {
warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber() << ", column" << swatch.columnNumber() << ")";
return false;
}
}
else if (type == "group") {
QDomElement groupMetadata = swatch.firstChildElement("metadata");
if (groupMetadata.isNull()) {
warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber() << ", column" << groupMetadata.columnNumber() << ")";
return false;
}
QDomElement groupTitle = metadata.firstChildElement("dc:title");
if (groupTitle.isNull()) {
warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber() << ", column" << groupTitle.columnNumber() << ")";
return false;
}
QString currentGroupName = groupTitle.text();
QDomElement groupSwatch = swatch.firstChildElement("swatch");
while(!groupSwatch.isNull()) {
QString id = groupSwatch.attribute("material");
if (id.isEmpty() || id.isNull()) {
warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")";
return false;
}
if (materialsBook.contains(id)) {
groups[currentGroupName].addEntry(materialsBook.value(id));
}
else {
warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber() << ", column" << groupSwatch.columnNumber() << ")";
return false;
}
groupSwatch = groupSwatch.nextSiblingElement("swatch");
}
}
}
// End palette
}
buf.close();
return true;
}
bool KoColorSet::Private::loadXml() {
bool res = false;
QXmlStreamReader *xml = new QXmlStreamReader(data);
if (xml->readNextStartElement()) {
QStringRef paletteId = xml->name();
if (QStringRef::compare(paletteId, "SCRIBUSCOLORS", Qt::CaseInsensitive) == 0) { // Scribus
dbgPigment << "XML palette: " << colorSet->filename() << ", Scribus format";
res = loadScribusXmlPalette(colorSet, xml);
}
else {
// Unknown XML format
xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId);
}
}
// If there is any error (it should be returned through the stream)
if (xml->hasError() || !res) {
warnPigment << "Illegal XML palette:" << colorSet->filename();
warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString();
return false;
}
else {
dbgPigment << "XML palette parsed successfully:" << colorSet->filename();
return true;
}
}
bool KoColorSet::Private::saveKpl(QIODevice *dev) const
{
QScopedPointer<KoStore> store(KoStore::createStore(dev, KoStore::Write, "krita/x-colorset", KoStore::Zip));
if (!store || store->bad()) return false;
QSet<const KoColorSpace *> colorSpaces;
{
QDomDocument doc;
QDomElement root = doc.createElement(KPL_PALETTE_TAG);
root.setAttribute(KPL_VERSION_ATTR, "1.0");
root.setAttribute(KPL_PALETTE_NAME_ATTR, colorSet->name());
root.setAttribute(KPL_PALETTE_COMMENT_ATTR, comment);
root.setAttribute(KPL_PALETTE_READONLY_ATTR,
(colorSet->isEditable() || !colorSet->isGlobal()) ? "false" : "true");
root.setAttribute(KPL_PALETTE_COLUMN_COUNT_ATTR, colorSet->columnCount());
root.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, groups[KoColorSet::GLOBAL_GROUP_NAME].rowCount());
saveKplGroup(doc, root, colorSet->getGroup(KoColorSet::GLOBAL_GROUP_NAME), colorSpaces);
for (const QString &groupName : groupNames) {
if (groupName == KoColorSet::GLOBAL_GROUP_NAME) { continue; }
QDomElement gl = doc.createElement(KPL_GROUP_TAG);
gl.setAttribute(KPL_GROUP_NAME_ATTR, groupName);
root.appendChild(gl);
saveKplGroup(doc, gl, colorSet->getGroup(groupName), colorSpaces);
}
doc.appendChild(root);
if (!store->open("colorset.xml")) { return false; }
QByteArray ba = doc.toByteArray();
if (store->write(ba) != ba.size()) { return false; }
if (!store->close()) { return false; }
}
QDomDocument doc;
QDomElement profileElement = doc.createElement("Profiles");
for (const KoColorSpace *colorSpace : colorSpaces) {
QString fn = QFileInfo(colorSpace->profile()->fileName()).fileName();
if (!store->open(fn)) { return false; }
QByteArray profileRawData = colorSpace->profile()->rawData();
if (!store->write(profileRawData)) { return false; }
if (!store->close()) { return false; }
QDomElement el = doc.createElement(KPL_PALETTE_PROFILE_TAG);
el.setAttribute(KPL_PALETTE_FILENAME_ATTR, fn);
el.setAttribute(KPL_PALETTE_NAME_ATTR, colorSpace->profile()->name());
el.setAttribute(KPL_COLOR_MODEL_ID_ATTR, colorSpace->colorModelId().id());
el.setAttribute(KPL_COLOR_DEPTH_ID_ATTR, colorSpace->colorDepthId().id());
profileElement.appendChild(el);
}
doc.appendChild(profileElement);
if (!store->open("profiles.xml")) { return false; }
QByteArray ba = doc.toByteArray();
if (store->write(ba) != ba.size()) { return false; }
if (!store->close()) { return false; }
return store->finalize();
}
void KoColorSet::Private::saveKplGroup(QDomDocument &doc,
QDomElement &groupEle,
const KisSwatchGroup *group,
QSet<const KoColorSpace *> &colorSetSet) const
{
groupEle.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, QString::number(group->rowCount()));
for (const SwatchInfoType &info : group->infoList()) {
const KoColorProfile *profile = info.swatch.color().colorSpace()->profile();
// Only save non-builtin profiles.=
if (!profile->fileName().isEmpty()) {
colorSetSet.insert(info.swatch.color().colorSpace());
}
QDomElement swatchEle = doc.createElement(KPL_SWATCH_TAG);
swatchEle.setAttribute(KPL_SWATCH_NAME_ATTR, info.swatch.name());
swatchEle.setAttribute(KPL_SWATCH_ID_ATTR, info.swatch.id());
swatchEle.setAttribute(KPL_SWATCH_SPOT_ATTR, info.swatch.spotColor() ? "true" : "false");
swatchEle.setAttribute(KPL_SWATCH_BITDEPTH_ATTR, info.swatch.color().colorSpace()->colorDepthId().id());
info.swatch.color().toXML(doc, swatchEle);
QDomElement positionEle = doc.createElement(KPL_SWATCH_POS_TAG);
positionEle.setAttribute(KPL_SWATCH_ROW_ATTR, info.row);
positionEle.setAttribute(KPL_SWATCH_COL_ATTR, info.column);
swatchEle.appendChild(positionEle);
groupEle.appendChild(swatchEle);
}
}
void KoColorSet::Private::loadKplGroup(const QDomDocument &doc, const QDomElement &parentEle, KisSwatchGroup *group)
{
Q_UNUSED(doc);
if (!parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()) {
group->setRowCount(parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).toInt());
}
group->setColumnCount(colorSet->columnCount());
for (QDomElement swatchEle = parentEle.firstChildElement(KPL_SWATCH_TAG);
!swatchEle.isNull();
swatchEle = swatchEle.nextSiblingElement(KPL_SWATCH_TAG)) {
QString colorDepthId = swatchEle.attribute(KPL_SWATCH_BITDEPTH_ATTR, Integer8BitsColorDepthID.id());
KisSwatch entry;
entry.setColor(KoColor::fromXML(swatchEle.firstChildElement(), colorDepthId));
entry.setName(swatchEle.attribute(KPL_SWATCH_NAME_ATTR));
entry.setId(swatchEle.attribute(KPL_SWATCH_ID_ATTR));
entry.setSpotColor(swatchEle.attribute(KPL_SWATCH_SPOT_ATTR, "false") == "true" ? true : false);
QDomElement positionEle = swatchEle.firstChildElement(KPL_SWATCH_POS_TAG);
if (!positionEle.isNull()) {
int rowNumber = positionEle.attribute(KPL_SWATCH_ROW_ATTR).toInt();
int columnNumber = positionEle.attribute(KPL_SWATCH_COL_ATTR).toInt();
if (columnNumber < 0 ||
columnNumber >= colorSet->columnCount() ||
rowNumber < 0
) {
warnPigment << "Swatch" << entry.name()
<< "of palette" << colorSet->name()
<< "has invalid position.";
continue;
}
group->setEntry(entry, columnNumber, rowNumber);
} else {
group->addEntry(entry);
}
}
if (parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()
&& group->colorCount() > 0
&& group->columnCount() > 0
&& (group->colorCount() / (group->columnCount()) + 1) < 20) {
group->setRowCount((group->colorCount() / group->columnCount()) + 1);
}
}
diff --git a/libs/pigment/resources/KoColorSet.h b/libs/pigment/resources/KoColorSet.h
index 700f07c57d..9364040fe5 100644
--- a/libs/pigment/resources/KoColorSet.h
+++ b/libs/pigment/resources/KoColorSet.h
@@ -1,219 +1,219 @@
/* This file is part of the KDE project
Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
- Copyright (c) 2016 L. E. Segovia <leo.segovia@siggraph.org>
+ Copyright (c) 2016 L. E. Segovia <amy@amyspark.me>
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 KOCOLORSET
#define KOCOLORSET
#include <QObject>
#include <QColor>
#include <QVector>
#include <QScopedPointer>
#include <resources/KoResource.h>
#include "KoColor.h"
#include "KisSwatch.h"
#include "KisSwatchGroup.h"
/**
* Also called palette.
* Open Gimp, Photoshop or RIFF palette files. This is a straight port
* from the Gimp.
*/
class KRITAPIGMENT_EXPORT KoColorSet : public QObject, public KoResource
{
Q_OBJECT
public:
static const QString GLOBAL_GROUP_NAME;
static const QString KPL_VERSION_ATTR;
static const QString KPL_GROUP_ROW_COUNT_ATTR;
static const QString KPL_PALETTE_COLUMN_COUNT_ATTR;
static const QString KPL_PALETTE_NAME_ATTR;
static const QString KPL_PALETTE_COMMENT_ATTR;
static const QString KPL_PALETTE_FILENAME_ATTR;
static const QString KPL_PALETTE_READONLY_ATTR;
static const QString KPL_COLOR_MODEL_ID_ATTR;
static const QString KPL_COLOR_DEPTH_ID_ATTR;
static const QString KPL_GROUP_NAME_ATTR;
static const QString KPL_SWATCH_ROW_ATTR;
static const QString KPL_SWATCH_COL_ATTR;
static const QString KPL_SWATCH_NAME_ATTR;
static const QString KPL_SWATCH_SPOT_ATTR;
static const QString KPL_SWATCH_ID_ATTR;
static const QString KPL_SWATCH_BITDEPTH_ATTR;
static const QString KPL_PALETTE_PROFILE_TAG;
static const QString KPL_SWATCH_POS_TAG;
static const QString KPL_SWATCH_TAG;
static const QString KPL_GROUP_TAG;
static const QString KPL_PALETTE_TAG;
public:
enum PaletteType {
UNKNOWN = 0,
GPL, // GIMP
RIFF_PAL, // RIFF
ACT, // Photoshop binary
PSP_PAL, // PaintShop Pro
ACO, // Photoshop Swatches
XML, // XML palette (Scribus)
KPL, // KoColor-based XML palette
SBZ // SwatchBooker
};
/**
* Load a color set from a file. This can be a Gimp
* palette, a RIFF palette, a Photoshop palette,
* a Krita palette,
* a Scribus palette or a SwatchBooker palette.
*/
explicit KoColorSet(const QString &filename = QString());
// Explicit copy constructor (KoResource copy constructor is private)
KoColorSet(const KoColorSet& rhs);
public /* overridden methods */: // KoResource
~KoColorSet() override;
bool load() override;
bool loadFromDevice(QIODevice *dev) override;
bool save() override;
bool saveToDevice(QIODevice* dev) const override;
QString defaultFileExtension() const override;
public /* methods */:
void setColumnCount(int columns);
int columnCount() const;
void setComment(QString comment);
QString comment();
int rowCount() const;
quint32 colorCount() const;
PaletteType paletteType() const;
void setPaletteType(PaletteType paletteType);
/**
* @brief isGlobal
* A global color set is a set stored in the config directory
* Such a color set would be opened every time Krita is launched.
*
* A non-global color set, on contrary, would be stored in a kra file,
* and would only be opened when that file is opened by Krita.
* @return @c true if the set is global
*/
bool isGlobal() const;
void setIsGlobal(bool);
bool isEditable() const;
void setIsEditable(bool isEditable);
QByteArray toByteArray() const;
bool fromByteArray(QByteArray &data);
/**
* @brief Add a color to the palette.
* @param c the swatch
* @param groupName color to add the group to. If empty, it will be added to the unsorted.
*/
void add(const KisSwatch &, const QString &groupName = GLOBAL_GROUP_NAME);
void setEntry(const KisSwatch &e, int x, int y, const QString &groupName = GLOBAL_GROUP_NAME);
/**
* @brief getColorGlobal
* A function for getting a color based on a global index. Useful for iterating through all color entries.
* @param x the global x index over the whole palette.
* @param y the global y index over the whole palette.
* @return the entry.
*/
KisSwatch getColorGlobal(quint32 x, quint32 y) const;
/**
* @brief getColorGroup
* A function for getting the color from a specific group.
* @param x the x index over the group.
* @param y the y index over the group.
* @param groupName the name of the group, will give unsorted when not defined.
* @return the entry
*/
KisSwatch getColorGroup(quint32 x, quint32 y, QString groupName);
/**
* @brief getGroupNames
* @return returns a list of group names, excluding the unsorted group.
*/
QStringList getGroupNames() const;
/**
* @brief getGroup
* @param name
* @return the group with the name given; global group if no parameter is given
* null pointer if not found.
*/
KisSwatchGroup *getGroup(const QString &name);
KisSwatchGroup *getGlobalGroup();
bool changeGroupName(const QString &oldGroupName, const QString &newGroupName);
/**
* @brief addGroup
* Adds a new group.
* @param groupName the name of the new group. When not specified, this will fail.
* @return whether thegroup was made.
*/
bool addGroup(const QString &groupName);
/**
* @brief moveGroup
* Move a group in the internal stringlist.
* @param groupName the groupname to move.
* @param groupNameInsertBefore the groupname to insert before. Empty means it will be added to the end.
* @return
*/
bool moveGroup(const QString &groupName, const QString &groupNameInsertBefore = GLOBAL_GROUP_NAME);
/**
* @brief removeGroup
* Remove a group from the KoColorSet
* @param groupName the name of the group you want to remove.
* @param keepColors Whether you wish to keep the colorsetentries. These will be added to the unsorted.
* @return whether it could find the group to remove.
*/
bool removeGroup(const QString &groupName, bool keepColors = true);
void clear();
/**
* @brief getIndexClosestColor
* function that matches the color to all colors in the colorset, and returns the index
* of the closest match.
* @param compare the color you wish to compare.
* @param useGivenColorSpace whether to use the color space of the color given
* when the two colors' colorspaces don't match. Else it'll use the entry's colorspace.
* @return returns the int of the closest match.
*/
KisSwatchGroup::SwatchInfo getClosestColorInfo(KoColor compare, bool useGivenColorSpace = true);
private:
class Private;
const QScopedPointer<Private> d;
};
#endif // KOCOLORSET
diff --git a/libs/pigment/resources/KoSegmentGradient.cpp b/libs/pigment/resources/KoSegmentGradient.cpp
index 16fef332cc..9a063b52c3 100644
--- a/libs/pigment/resources/KoSegmentGradient.cpp
+++ b/libs/pigment/resources/KoSegmentGradient.cpp
@@ -1,983 +1,984 @@
/*
Copyright (c) 2000 Matthias Elter <elter@kde.org>
2001 John Califf
2004 Boudewijn Rempt <boud@valdyas.org>
2004 Adrian Page <adrian@pagenet.plus.com>
2004, 2007 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; 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/KoSegmentGradient.h>
#include <cfloat>
#include <cmath>
#include <QImage>
#include <QTextStream>
#include <QFile>
#include <QByteArray>
#include <QDomDocument>
#include <QDomElement>
#include <QBuffer>
#include <kis_dom_utils.h>
#include "KoColorSpaceRegistry.h"
#include "KoColorSpace.h"
#include "KoMixColorsOp.h"
#include <KoColorModelStandardIds.h>
#include <DebugPigment.h>
#include <klocalizedstring.h>
KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::m_instance = 0;
KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::m_instance = 0;
KoGradientSegment::HSVCCWColorInterpolationStrategy *KoGradientSegment::HSVCCWColorInterpolationStrategy::m_instance = 0;
KoGradientSegment::LinearInterpolationStrategy *KoGradientSegment::LinearInterpolationStrategy::m_instance = 0;
KoGradientSegment::CurvedInterpolationStrategy *KoGradientSegment::CurvedInterpolationStrategy::m_instance = 0;
KoGradientSegment::SineInterpolationStrategy *KoGradientSegment::SineInterpolationStrategy::m_instance = 0;
KoGradientSegment::SphereIncreasingInterpolationStrategy *KoGradientSegment::SphereIncreasingInterpolationStrategy::m_instance = 0;
KoGradientSegment::SphereDecreasingInterpolationStrategy *KoGradientSegment::SphereDecreasingInterpolationStrategy::m_instance = 0;
KoSegmentGradient::KoSegmentGradient(const QString& file)
: KoAbstractGradient(file)
{
}
KoSegmentGradient::~KoSegmentGradient()
{
for (int i = 0; i < m_segments.count(); i++) {
delete m_segments[i];
m_segments[i] = 0;
}
}
KoSegmentGradient::KoSegmentGradient(const KoSegmentGradient &rhs)
: KoAbstractGradient(rhs)
{
Q_FOREACH (KoGradientSegment *segment, rhs.m_segments) {
pushSegment(new KoGradientSegment(*segment));
}
}
KoAbstractGradient* KoSegmentGradient::clone() const
{
return new KoSegmentGradient(*this);
}
bool KoSegmentGradient::load()
{
QFile file(filename());
if (!file.open(QIODevice::ReadOnly)) {
warnPigment << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&file);
file.close();
return res;
}
bool KoSegmentGradient::loadFromDevice(QIODevice *dev)
{
QByteArray data = dev->readAll();
QTextStream fileContent(data, QIODevice::ReadOnly);
fileContent.setAutoDetectUnicode(true);
QString header = fileContent.readLine();
if (header != "GIMP Gradient") {
return false;
}
QString nameDefinition = fileContent.readLine();
QString numSegmentsText;
if (nameDefinition.startsWith("Name: ")) {
QString nameText = nameDefinition.right(nameDefinition.length() - 6);
setName(nameText);
numSegmentsText = fileContent.readLine();
} else {
// Older format without name.
numSegmentsText = nameDefinition;
}
dbgPigment << "Loading gradient: " << name();
int numSegments;
bool ok;
numSegments = numSegmentsText.toInt(&ok);
if (!ok || numSegments < 1) {
return false;
}
dbgPigment << "Number of segments = " << numSegments;
const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8();
for (int i = 0; i < numSegments; i++) {
QString segmentText = fileContent.readLine();
QTextStream segmentFields(&segmentText);
QStringList values = segmentText.split(' ');
qreal leftOffset = values[0].toDouble();
qreal middleOffset = values[1].toDouble();
qreal rightOffset = values[2].toDouble();
qreal leftRed = values[3].toDouble();
qreal leftGreen = values[4].toDouble();
qreal leftBlue = values[5].toDouble();
qreal leftAlpha = values[6].toDouble();
qreal rightRed = values[7].toDouble();
qreal rightGreen = values[8].toDouble();
qreal rightBlue = values[9].toDouble();
qreal rightAlpha = values[10].toDouble();
int interpolationType = values[11].toInt();
int colorInterpolationType = values[12].toInt();
quint8 data[4];
data[2] = static_cast<quint8>(leftRed * 255 + 0.5);
data[1] = static_cast<quint8>(leftGreen * 255 + 0.5);
data[0] = static_cast<quint8>(leftBlue * 255 + 0.5);
data[3] = static_cast<quint8>(leftAlpha * OPACITY_OPAQUE_U8 + 0.5);
KoColor leftColor(data, rgbColorSpace);
data[2] = static_cast<quint8>(rightRed * 255 + 0.5);
data[1] = static_cast<quint8>(rightGreen * 255 + 0.5);
data[0] = static_cast<quint8>(rightBlue * 255 + 0.5);
data[3] = static_cast<quint8>(rightAlpha * OPACITY_OPAQUE_U8 + 0.5);
KoColor rightColor(data, rgbColorSpace);
KoGradientSegment *segment = new KoGradientSegment(interpolationType, colorInterpolationType, leftOffset, middleOffset, rightOffset, leftColor, rightColor);
Q_CHECK_PTR(segment);
if (!segment -> isValid()) {
delete segment;
return false;
}
m_segments.push_back(segment);
}
if (!m_segments.isEmpty()) {
updatePreview();
setValid(true);
return true;
} else {
return false;
}
}
bool KoSegmentGradient::save()
{
QFile file(filename());
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
saveToDevice(&file);
file.close();
return true;
}
bool KoSegmentGradient::saveToDevice(QIODevice *dev) const
{
QTextStream fileContent(dev);
fileContent << "GIMP Gradient\n";
fileContent << "Name: " << name() << "\n";
fileContent << m_segments.count() << "\n";
Q_FOREACH (KoGradientSegment* segment, m_segments) {
fileContent << QString::number(segment->startOffset(), 'f') << " " << QString::number(segment->middleOffset(), 'f') << " "
<< QString::number(segment->endOffset(), 'f') << " ";
QColor startColor = segment->startColor().toQColor();
QColor endColor = segment->endColor().toQColor();
fileContent << QString::number(startColor.redF(), 'f') << " " << QString::number(startColor.greenF(), 'f') << " "
<< QString::number(startColor.blueF(), 'f') << " " << QString::number(startColor.alphaF(), 'f') << " ";
fileContent << QString::number(endColor.redF(), 'f') << " " << QString::number(endColor.greenF(), 'f') << " "
<< QString::number(endColor.blueF(), 'f') << " " << QString::number(endColor.alphaF(), 'f') << " ";
fileContent << (int)segment->interpolation() << " " << (int)segment->colorInterpolation() << "\n";
}
KoResource::saveToDevice(dev);
return true;
}
KoGradientSegment *KoSegmentGradient::segmentAt(qreal t) const
{
- Q_ASSERT(t >= 0 || t <= 1);
- Q_ASSERT(!m_segments.empty());
+ if (t < 0.0) return 0;
+ if (t > 1.0) return 0;
+ if (m_segments.isEmpty()) return 0;
for (QList<KoGradientSegment *>::const_iterator it = m_segments.begin(); it != m_segments.end(); ++it) {
if (t > (*it)->startOffset() - DBL_EPSILON && t < (*it)->endOffset() + DBL_EPSILON) {
return *it;
}
}
return 0;
}
void KoSegmentGradient::colorAt(KoColor& dst, qreal t) const
{
const KoGradientSegment *segment = segmentAt(t);
Q_ASSERT(segment != 0);
if (segment) {
segment->colorAt(dst, t);
}
}
QGradient* KoSegmentGradient::toQGradient() const
{
QGradient* gradient = new QLinearGradient();
QColor color;
Q_FOREACH (KoGradientSegment* segment, m_segments) {
segment->startColor().toQColor(&color);
gradient->setColorAt(segment->startOffset() , color);
segment->endColor().toQColor(&color);
gradient->setColorAt(segment->endOffset() , color);
}
return gradient;
}
QString KoSegmentGradient::defaultFileExtension() const
{
return QString(".ggr");
}
void KoSegmentGradient::toXML(QDomDocument &doc, QDomElement &gradientElt) const
{
gradientElt.setAttribute("type", "segment");
Q_FOREACH(KoGradientSegment *segment, this->segments()) {
QDomElement segmentElt = doc.createElement("segment");
QDomElement start = doc.createElement("start");
QDomElement end = doc.createElement("end");
segmentElt.setAttribute("start-offset", KisDomUtils::toString(segment->startOffset()));
const KoColor startColor = segment->startColor();
segmentElt.setAttribute("start-bitdepth", startColor.colorSpace()->colorDepthId().id());
segmentElt.setAttribute("start-alpha", KisDomUtils::toString(startColor.opacityF()));
startColor.toXML(doc, start);
segmentElt.setAttribute("middle-offset", KisDomUtils::toString(segment->middleOffset()));
segmentElt.setAttribute("end-offset", KisDomUtils::toString(segment->endOffset()));
const KoColor endColor = segment->endColor();
segmentElt.setAttribute("end-bitdepth", endColor.colorSpace()->colorDepthId().id());
segmentElt.setAttribute("end-alpha", KisDomUtils::toString(endColor.opacityF()));
endColor.toXML(doc, end);
segmentElt.setAttribute("interpolation", KisDomUtils::toString(segment->interpolation()));
segmentElt.setAttribute("color-interpolation", KisDomUtils::toString(segment->colorInterpolation()));
segmentElt.appendChild(start);
segmentElt.appendChild(end);
gradientElt.appendChild(segmentElt);
}
}
KoSegmentGradient KoSegmentGradient::fromXML(const QDomElement &elt)
{
KoSegmentGradient gradient;
QDomElement segmentElt = elt.firstChildElement("segment");
while (!segmentElt.isNull()) {
int interpolation = KisDomUtils::toInt(segmentElt.attribute("interpolation", "0.0"));
int colorInterpolation = KisDomUtils::toInt(segmentElt.attribute("color-interpolation", "0.0"));
double startOffset = KisDomUtils::toDouble(segmentElt.attribute("start-offset", "0.0"));
qreal middleOffset = KisDomUtils::toDouble(segmentElt.attribute("middle-offset", "0.0"));
qreal endOffset = KisDomUtils::toDouble(segmentElt.attribute("end-offset", "0.0"));
QDomElement start = segmentElt.firstChildElement("start");
QString startBitdepth = segmentElt.attribute("start-bitdepth", Integer8BitsColorDepthID.id());
QColor left = KoColor::fromXML(start.firstChildElement(), startBitdepth).toQColor();
left.setAlphaF(KisDomUtils::toDouble(segmentElt.attribute("start-alpha", "1.0")));
QString endBitdepth = segmentElt.attribute("end-bitdepth", Integer8BitsColorDepthID.id());
QDomElement end = segmentElt.firstChildElement("end");
QColor right = KoColor::fromXML(end.firstChildElement(), endBitdepth).toQColor();
right.setAlphaF(KisDomUtils::toDouble(segmentElt.attribute("end-alpha", "1.0")));
gradient.createSegment(interpolation, colorInterpolation, startOffset, endOffset, middleOffset, left, right);
segmentElt = segmentElt.nextSiblingElement("segment");
}
return gradient;
}
KoGradientSegment::KoGradientSegment(int interpolationType, int colorInterpolationType, qreal startOffset, qreal middleOffset, qreal endOffset, const KoColor& startColor, const KoColor& endColor)
{
m_interpolator = 0;
switch (interpolationType) {
case INTERP_LINEAR:
m_interpolator = LinearInterpolationStrategy::instance();
break;
case INTERP_CURVED:
m_interpolator = CurvedInterpolationStrategy::instance();
break;
case INTERP_SINE:
m_interpolator = SineInterpolationStrategy::instance();
break;
case INTERP_SPHERE_INCREASING:
m_interpolator = SphereIncreasingInterpolationStrategy::instance();
break;
case INTERP_SPHERE_DECREASING:
m_interpolator = SphereDecreasingInterpolationStrategy::instance();
break;
}
m_colorInterpolator = 0;
switch (colorInterpolationType) {
case COLOR_INTERP_RGB:
m_colorInterpolator = RGBColorInterpolationStrategy::instance();
break;
case COLOR_INTERP_HSV_CCW:
m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance();
break;
case COLOR_INTERP_HSV_CW:
m_colorInterpolator = HSVCWColorInterpolationStrategy::instance();
break;
}
if (startOffset < DBL_EPSILON) {
m_startOffset = 0;
} else if (startOffset > 1 - DBL_EPSILON) {
m_startOffset = 1;
} else {
m_startOffset = startOffset;
}
if (middleOffset < m_startOffset + DBL_EPSILON) {
m_middleOffset = m_startOffset;
} else if (middleOffset > 1 - DBL_EPSILON) {
m_middleOffset = 1;
} else {
m_middleOffset = middleOffset;
}
if (endOffset < m_middleOffset + DBL_EPSILON) {
m_endOffset = m_middleOffset;
} else if (endOffset > 1 - DBL_EPSILON) {
m_endOffset = 1;
} else {
m_endOffset = endOffset;
}
m_length = m_endOffset - m_startOffset;
if (m_length < DBL_EPSILON) {
m_middleT = 0.5;
} else {
m_middleT = (m_middleOffset - m_startOffset) / m_length;
}
m_startColor = startColor;
m_endColor = endColor;
}
const KoColor& KoGradientSegment::startColor() const
{
return m_startColor;
}
const KoColor& KoGradientSegment::endColor() const
{
return m_endColor;
}
qreal KoGradientSegment::startOffset() const
{
return m_startOffset;
}
qreal KoGradientSegment::middleOffset() const
{
return m_middleOffset;
}
qreal KoGradientSegment::endOffset() const
{
return m_endOffset;
}
void KoGradientSegment::setStartOffset(qreal t)
{
m_startOffset = t;
m_length = m_endOffset - m_startOffset;
if (m_length < DBL_EPSILON) {
m_middleT = 0.5;
} else {
m_middleT = (m_middleOffset - m_startOffset) / m_length;
}
}
void KoGradientSegment::setMiddleOffset(qreal t)
{
m_middleOffset = t;
if (m_length < DBL_EPSILON) {
m_middleT = 0.5;
} else {
m_middleT = (m_middleOffset - m_startOffset) / m_length;
}
}
void KoGradientSegment::setEndOffset(qreal t)
{
m_endOffset = t;
m_length = m_endOffset - m_startOffset;
if (m_length < DBL_EPSILON) {
m_middleT = 0.5;
} else {
m_middleT = (m_middleOffset - m_startOffset) / m_length;
}
}
int KoGradientSegment::interpolation() const
{
return m_interpolator->type();
}
void KoGradientSegment::setInterpolation(int interpolationType)
{
switch (interpolationType) {
case INTERP_LINEAR:
m_interpolator = LinearInterpolationStrategy::instance();
break;
case INTERP_CURVED:
m_interpolator = CurvedInterpolationStrategy::instance();
break;
case INTERP_SINE:
m_interpolator = SineInterpolationStrategy::instance();
break;
case INTERP_SPHERE_INCREASING:
m_interpolator = SphereIncreasingInterpolationStrategy::instance();
break;
case INTERP_SPHERE_DECREASING:
m_interpolator = SphereDecreasingInterpolationStrategy::instance();
break;
}
}
int KoGradientSegment::colorInterpolation() const
{
return m_colorInterpolator->type();
}
void KoGradientSegment::setColorInterpolation(int colorInterpolationType)
{
switch (colorInterpolationType) {
case COLOR_INTERP_RGB:
m_colorInterpolator = RGBColorInterpolationStrategy::instance();
break;
case COLOR_INTERP_HSV_CCW:
m_colorInterpolator = HSVCCWColorInterpolationStrategy::instance();
break;
case COLOR_INTERP_HSV_CW:
m_colorInterpolator = HSVCWColorInterpolationStrategy::instance();
break;
}
}
void KoGradientSegment::colorAt(KoColor& dst, qreal t) const
{
Q_ASSERT(t > m_startOffset - DBL_EPSILON && t < m_endOffset + DBL_EPSILON);
qreal segmentT;
if (m_length < DBL_EPSILON) {
segmentT = 0.5;
} else {
segmentT = (t - m_startOffset) / m_length;
}
qreal colorT = m_interpolator->valueAt(segmentT, m_middleT);
m_colorInterpolator->colorAt(dst, colorT, m_startColor, m_endColor);
}
bool KoGradientSegment::isValid() const
{
if (m_interpolator == 0 || m_colorInterpolator == 0)
return false;
return true;
}
KoGradientSegment::RGBColorInterpolationStrategy::RGBColorInterpolationStrategy()
: m_colorSpace(KoColorSpaceRegistry::instance()->rgb8())
{
}
KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::instance()
{
if (m_instance == 0) {
m_instance = new RGBColorInterpolationStrategy();
Q_CHECK_PTR(m_instance);
}
return m_instance;
}
void KoGradientSegment::RGBColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& _start, const KoColor& _end) const
{
KoColor buffer(m_colorSpace);
KoColor start(m_colorSpace);
KoColor end(m_colorSpace);
KoColor startDummy, endDummy;
//hack to get a color space with the bitdepth of the gradients(8bit), but with the colour profile of the image//
const KoColorSpace* mixSpace = KoColorSpaceRegistry::instance()->rgb8(dst.colorSpace()->profile());
//convert to the right colorspace for the start and end if we have our mixSpace.
if (mixSpace){
startDummy = KoColor(_start, mixSpace);
endDummy = KoColor(_end, mixSpace);
} else {
startDummy = _start;
endDummy = _end;
}
start.fromKoColor(_start);
end.fromKoColor(_end);
const quint8 *colors[2];
colors[0] = startDummy.data();
colors[1] = endDummy.data();
qint16 colorWeights[2];
colorWeights[0] = static_cast<quint8>((1.0 - t) * 255 + 0.5);
colorWeights[1] = 255 - colorWeights[0];
//check if our mixspace exists, it doesn't at startup.
if (mixSpace){
if (*buffer.colorSpace() != *mixSpace) {
buffer = KoColor(mixSpace);
}
mixSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
}
else {
buffer = KoColor(m_colorSpace);
m_colorSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
}
dst.fromKoColor(buffer);
}
KoGradientSegment::HSVCWColorInterpolationStrategy::HSVCWColorInterpolationStrategy()
: m_colorSpace(KoColorSpaceRegistry::instance()->rgb8())
{
}
KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::instance()
{
if (m_instance == 0) {
m_instance = new HSVCWColorInterpolationStrategy();
Q_CHECK_PTR(m_instance);
}
return m_instance;
}
void KoGradientSegment::HSVCWColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const
{
QColor sc;
QColor ec;
start.toQColor(&sc);
end.toQColor(&ec);
int s = static_cast<int>(sc.saturation() + t * (ec.saturation() - sc.saturation()) + 0.5);
int v = static_cast<int>(sc.value() + t * (ec.value() - sc.value()) + 0.5);
int h;
if (ec.hue() < sc.hue()) {
h = static_cast<int>(ec.hue() + (1 - t) * (sc.hue() - ec.hue()) + 0.5);
} else {
h = static_cast<int>(ec.hue() + (1 - t) * (360 - ec.hue() + sc.hue()) + 0.5);
if (h > 359) {
h -= 360;
}
}
// XXX: added an explicit cast. Is this correct?
quint8 opacity = static_cast<quint8>(sc.alpha() + t * (ec.alpha() - sc.alpha()));
QColor result;
result.setHsv(h, s, v);
result.setAlpha(opacity);
dst.fromQColor(result);
}
KoGradientSegment::HSVCCWColorInterpolationStrategy::HSVCCWColorInterpolationStrategy() :
m_colorSpace(KoColorSpaceRegistry::instance()->rgb8())
{
}
KoGradientSegment::HSVCCWColorInterpolationStrategy *KoGradientSegment::HSVCCWColorInterpolationStrategy::instance()
{
if (m_instance == 0) {
m_instance = new HSVCCWColorInterpolationStrategy();
Q_CHECK_PTR(m_instance);
}
return m_instance;
}
void KoGradientSegment::HSVCCWColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& start, const KoColor& end) const
{
QColor sc;
QColor se;
start.toQColor(&sc);
end.toQColor(&se);
int s = static_cast<int>(sc.saturation() + t * (se.saturation() - sc.saturation()) + 0.5);
int v = static_cast<int>(sc.value() + t * (se.value() - sc.value()) + 0.5);
int h;
if (sc.hue() < se.hue()) {
h = static_cast<int>(sc.hue() + t * (se.hue() - sc.hue()) + 0.5);
} else {
h = static_cast<int>(sc.hue() + t * (360 - sc.hue() + se.hue()) + 0.5);
if (h > 359) {
h -= 360;
}
}
// XXX: Added an explicit static cast
quint8 opacity = static_cast<quint8>(sc.alpha() + t * (se.alpha() - sc.alpha()));
QColor result;
result.setHsv(h, s, v);
result.setAlpha(opacity);
dst.fromQColor(result);
}
KoGradientSegment::LinearInterpolationStrategy *KoGradientSegment::LinearInterpolationStrategy::instance()
{
if (m_instance == 0) {
m_instance = new LinearInterpolationStrategy();
Q_CHECK_PTR(m_instance);
}
return m_instance;
}
qreal KoGradientSegment::LinearInterpolationStrategy::calcValueAt(qreal t, qreal middle)
{
Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON);
Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON);
qreal value = 0;
if (t <= middle) {
if (middle < DBL_EPSILON) {
value = 0;
} else {
value = (t / middle) * 0.5;
}
} else {
if (middle > 1 - DBL_EPSILON) {
value = 1;
} else {
value = ((t - middle) / (1 - middle)) * 0.5 + 0.5;
}
}
return value;
}
qreal KoGradientSegment::LinearInterpolationStrategy::valueAt(qreal t, qreal middle) const
{
return calcValueAt(t, middle);
}
KoGradientSegment::CurvedInterpolationStrategy::CurvedInterpolationStrategy()
{
m_logHalf = log(0.5);
}
KoGradientSegment::CurvedInterpolationStrategy *KoGradientSegment::CurvedInterpolationStrategy::instance()
{
if (m_instance == 0) {
m_instance = new CurvedInterpolationStrategy();
Q_CHECK_PTR(m_instance);
}
return m_instance;
}
qreal KoGradientSegment::CurvedInterpolationStrategy::valueAt(qreal t, qreal middle) const
{
Q_ASSERT(t > -DBL_EPSILON && t < 1 + DBL_EPSILON);
Q_ASSERT(middle > -DBL_EPSILON && middle < 1 + DBL_EPSILON);
qreal value = 0;
if (middle < DBL_EPSILON) {
middle = DBL_EPSILON;
}
value = pow(t, m_logHalf / log(middle));
return value;
}
KoGradientSegment::SineInterpolationStrategy *KoGradientSegment::SineInterpolationStrategy::instance()
{
if (m_instance == 0) {
m_instance = new SineInterpolationStrategy();
Q_CHECK_PTR(m_instance);
}
return m_instance;
}
qreal KoGradientSegment::SineInterpolationStrategy::valueAt(qreal t, qreal middle) const
{
qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle);
qreal value = (sin(-M_PI_2 + M_PI * lt) + 1.0) / 2.0;
return value;
}
KoGradientSegment::SphereIncreasingInterpolationStrategy *KoGradientSegment::SphereIncreasingInterpolationStrategy::instance()
{
if (m_instance == 0) {
m_instance = new SphereIncreasingInterpolationStrategy();
Q_CHECK_PTR(m_instance);
}
return m_instance;
}
qreal KoGradientSegment::SphereIncreasingInterpolationStrategy::valueAt(qreal t, qreal middle) const
{
qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle) - 1;
qreal value = sqrt(1 - lt * lt);
return value;
}
KoGradientSegment::SphereDecreasingInterpolationStrategy *KoGradientSegment::SphereDecreasingInterpolationStrategy::instance()
{
if (m_instance == 0) {
m_instance = new SphereDecreasingInterpolationStrategy();
Q_CHECK_PTR(m_instance);
}
return m_instance;
}
qreal KoGradientSegment::SphereDecreasingInterpolationStrategy::valueAt(qreal t, qreal middle) const
{
qreal lt = LinearInterpolationStrategy::calcValueAt(t, middle);
qreal value = 1 - sqrt(1 - lt * lt);
return value;
}
void KoSegmentGradient::createSegment(int interpolation, int colorInterpolation, double startOffset, double endOffset, double middleOffset, const QColor & left, const QColor & right)
{
pushSegment(new KoGradientSegment(interpolation, colorInterpolation, startOffset, middleOffset, endOffset, KoColor(left, colorSpace()), KoColor(right, colorSpace())));
}
const QList<double> KoSegmentGradient::getHandlePositions() const
{
QList<double> handlePositions;
handlePositions.push_back(m_segments[0]->startOffset());
for (int i = 0; i < m_segments.count(); i++) {
handlePositions.push_back(m_segments[i]->endOffset());
}
return handlePositions;
}
const QList<double> KoSegmentGradient::getMiddleHandlePositions() const
{
QList<double> middleHandlePositions;
for (int i = 0; i < m_segments.count(); i++) {
middleHandlePositions.push_back(m_segments[i]->middleOffset());
}
return middleHandlePositions;
}
void KoSegmentGradient::moveSegmentStartOffset(KoGradientSegment* segment, double t)
{
QList<KoGradientSegment*>::iterator it = std::find(m_segments.begin(), m_segments.end(), segment);
if (it != m_segments.end()) {
if (it == m_segments.begin()) {
segment->setStartOffset(0.0);
return;
}
KoGradientSegment* previousSegment = (*(it - 1));
if (t > segment->startOffset()) {
if (t > segment->middleOffset())
t = segment->middleOffset();
} else {
if (t < previousSegment->middleOffset())
t = previousSegment->middleOffset();
}
previousSegment->setEndOffset(t);
segment->setStartOffset(t);
}
}
void KoSegmentGradient::moveSegmentEndOffset(KoGradientSegment* segment, double t)
{
QList<KoGradientSegment*>::iterator it = std::find(m_segments.begin(), m_segments.end(), segment);
if (it != m_segments.end()) {
if (it + 1 == m_segments.end()) {
segment->setEndOffset(1.0);
return;
}
KoGradientSegment* followingSegment = (*(it + 1));
if (t < segment->endOffset()) {
if (t < segment->middleOffset())
t = segment->middleOffset();
} else {
if (t > followingSegment->middleOffset())
t = followingSegment->middleOffset();
}
followingSegment->setStartOffset(t);
segment->setEndOffset(t);
}
}
void KoSegmentGradient::moveSegmentMiddleOffset(KoGradientSegment* segment, double t)
{
if (segment) {
if (t > segment->endOffset())
segment->setMiddleOffset(segment->endOffset());
else if (t < segment->startOffset())
segment->setMiddleOffset(segment->startOffset());
else
segment->setMiddleOffset(t);
}
}
void KoSegmentGradient::splitSegment(KoGradientSegment* segment)
{
Q_ASSERT(segment != 0);
QList<KoGradientSegment*>::iterator it = std::find(m_segments.begin(), m_segments.end(), segment);
if (it != m_segments.end()) {
KoColor midleoffsetColor(segment->endColor().colorSpace());
segment->colorAt(midleoffsetColor, segment->middleOffset());
KoGradientSegment* newSegment = new KoGradientSegment(
segment->interpolation(), segment->colorInterpolation(),
segment ->startOffset(),
(segment->middleOffset() - segment->startOffset()) / 2 + segment->startOffset(),
segment->middleOffset(),
segment->startColor(),
midleoffsetColor);
m_segments.insert(it, newSegment);
segment->setStartColor(midleoffsetColor);
segment->setStartOffset(segment->middleOffset());
segment->setMiddleOffset((segment->endOffset() - segment->startOffset()) / 2 + segment->startOffset());
}
}
void KoSegmentGradient::duplicateSegment(KoGradientSegment* segment)
{
Q_ASSERT(segment != 0);
QList<KoGradientSegment*>::iterator it = std::find(m_segments.begin(), m_segments.end(), segment);
if (it != m_segments.end()) {
double middlePostionPercentage = (segment->middleOffset() - segment->startOffset()) / segment->length();
double center = segment->startOffset() + segment->length() / 2;
KoGradientSegment* newSegment = new KoGradientSegment(
segment->interpolation(), segment->colorInterpolation(),
segment ->startOffset(),
segment->length() / 2 * middlePostionPercentage + segment->startOffset(),
center, segment->startColor(),
segment->endColor());
m_segments.insert(it, newSegment);
segment->setStartOffset(center);
segment->setMiddleOffset(segment->length() * middlePostionPercentage + segment->startOffset());
}
}
void KoSegmentGradient::mirrorSegment(KoGradientSegment* segment)
{
Q_ASSERT(segment != 0);
KoColor tmpColor = segment->startColor();
segment->setStartColor(segment->endColor());
segment->setEndColor(tmpColor);
segment->setMiddleOffset(segment->endOffset() - (segment->middleOffset() - segment->startOffset()));
if (segment->interpolation() == INTERP_SPHERE_INCREASING)
segment->setInterpolation(INTERP_SPHERE_DECREASING);
else if (segment->interpolation() == INTERP_SPHERE_DECREASING)
segment->setInterpolation(INTERP_SPHERE_INCREASING);
if (segment->colorInterpolation() == COLOR_INTERP_HSV_CW)
segment->setColorInterpolation(COLOR_INTERP_HSV_CCW);
else if (segment->colorInterpolation() == COLOR_INTERP_HSV_CCW)
segment->setColorInterpolation(COLOR_INTERP_HSV_CW);
}
KoGradientSegment* KoSegmentGradient::removeSegment(KoGradientSegment* segment)
{
Q_ASSERT(segment != 0);
if (m_segments.count() < 2)
return 0;
QList<KoGradientSegment*>::iterator it = std::find(m_segments.begin(), m_segments.end(), segment);
if (it != m_segments.end()) {
double middlePostionPercentage;
KoGradientSegment* nextSegment;
if (it == m_segments.begin()) {
nextSegment = (*(it + 1));
middlePostionPercentage = (nextSegment->middleOffset() - nextSegment->startOffset()) / nextSegment->length();
nextSegment->setStartOffset(segment->startOffset());
nextSegment->setMiddleOffset(middlePostionPercentage * nextSegment->length() + nextSegment->startOffset());
} else {
nextSegment = (*(it - 1));
middlePostionPercentage = (nextSegment->middleOffset() - nextSegment->startOffset()) / nextSegment->length();
nextSegment->setEndOffset(segment->endOffset());
nextSegment->setMiddleOffset(middlePostionPercentage * nextSegment->length() + nextSegment->startOffset());
}
delete segment;
m_segments.erase(it);
return nextSegment;
}
return 0;
}
bool KoSegmentGradient::removeSegmentPossible() const
{
if (m_segments.count() < 2)
return false;
return true;
}
const QList<KoGradientSegment *>& KoSegmentGradient::segments() const
{
return m_segments;
}
diff --git a/libs/pigment/tests/CCSGraph.cpp b/libs/pigment/tests/CCSGraph.cpp
index 6a574af35e..404508ff02 100644
--- a/libs/pigment/tests/CCSGraph.cpp
+++ b/libs/pigment/tests/CCSGraph.cpp
@@ -1,119 +1,119 @@
/*
* Copyright (c) 2007-2008 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 <QFile>
#include <QProcess>
#include <QTemporaryFile>
#include <QCoreApplication>
#include <DebugPigment.h>
#include "KoColorSpaceRegistry.h"
#include "KoColorConversionSystem.h"
#include <iostream>
#include <QCommandLineParser>
#include <QCommandLineOption>
struct FriendOfColorSpaceRegistry {
static QString toDot() {
return KoColorSpaceRegistry::instance()->colorConversionSystem()->toDot();
}
static QString bestPathToDot(const QString &srcKey, const QString &dstKey) {
return KoColorSpaceRegistry::instance()->colorConversionSystem()->bestPathToDot(srcKey, dstKey);
}
};
int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
// Initialize the list of options
parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graphs"), i18n("return the list of available graphs")));
parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graph"), i18n("specify the type of graph (see --graphs to get the full list, the default is full)"), QLatin1String("type"), QLatin1String("full")));
parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the source color space"), QLatin1String("key"), QString()));
parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the destination color space"), QLatin1String("key"), QString()));
parser.addOption(QCommandLineOption(QStringList() << QLatin1String("output"), i18n("specify the output (can be ps or dot, the default is ps)"), QLatin1String("type"), QLatin1String("ps")));
parser.addPositionalArgument(QLatin1String("outputfile"), i18n("name of the output file"));
parser.process(app); // PORTING SCRIPT: move this to after any parser.addOption
if (parser.isSet("graphs")) {
// Don't change those lines to use dbgPigment derivatives, they need to be outputted
// to stdout not stderr.
std::cout << "full : show all the connection on the graph" << std::endl;
std::cout << "bestpath : show the best path for a given transformation" << std::endl;
exit(EXIT_SUCCESS);
}
QString graphType = parser.value("graph");
QString outputType = parser.value("output");
if (parser.positionalArguments().count() != 1) {
errorPigment << "No output file name specified";
parser.showHelp();
exit(EXIT_FAILURE);
}
QString outputFileName = parser.positionalArguments()[0];
// Generate the graph
QString dot;
if (graphType == "full") {
dot = FriendOfColorSpaceRegistry::toDot();
} else if (graphType == "bestpath") {
QString srcKey = parser.value("src-key");
QString dstKey = parser.value("dst-key");
if (srcKey.isEmpty() || dstKey.isEmpty()) {
errorPigment << "src-key and dst-key must be specified for the graph bestpath";
exit(EXIT_FAILURE);
} else {
dot = FriendOfColorSpaceRegistry::bestPathToDot(srcKey, dstKey);
}
} else {
errorPigment << "Unknown graph type : " << graphType.toLatin1();
exit(EXIT_FAILURE);
}
if (outputType == "dot") {
QFile file(outputFileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
exit(EXIT_FAILURE);
QTextStream out(&file);
out << dot;
} else if (outputType == "ps" || outputType == "svg") {
QTemporaryFile file;
if (!file.open()) {
exit(EXIT_FAILURE);
}
QTextStream out(&file);
out << dot;
QString cmd = QString("dot -T%1 %2 -o %3").arg(outputType).arg(file.fileName()).arg(outputFileName);
file.close();
if (QProcess::execute(cmd) != 0) {
- errorPigment << "An error has occurred when executing : '" << cmd << "' the most likely cause is that 'dot' command is missing, and that you should install graphviz (from http://www.graphviz.org)";
+ errorPigment << "An error has occurred when executing : '" << cmd << "' the most likely cause is that 'dot' command is missing, and that you should install graphviz (from https://www.graphviz.org)";
}
} else {
errorPigment << "Unknown output type : " << outputType;
exit(EXIT_FAILURE);
}
}
diff --git a/libs/psd/psd.h b/libs/psd/psd.h
index acb2b937e2..ff7efc1d09 100644
--- a/libs/psd/psd.h
+++ b/libs/psd/psd.h
@@ -1,1169 +1,1169 @@
/*
* Copyright (c) 2010 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.
*/
/*
* Constants and defines taken from gimp and psdparse
*/
#ifndef PSD_H
#define PSD_H
#include <QPair>
#include <QString>
#include <QColor>
#include <QVector>
#include <KoColorModelStandardIds.h>
#include <KoCompositeOpRegistry.h>
#include <resources/KoAbstractGradient.h>
#include "kritapsd_export.h"
class KoPattern;
const int MAX_CHANNELS = 56;
typedef qint32 Fixed; /* Represents a fixed point implied decimal */
/**
* Image color/depth modes
*/
enum psd_color_mode {
Bitmap = 0,
Grayscale=1,
Indexed=2,
RGB=3,
CMYK=4,
MultiChannel=7,
DuoTone=8,
Lab=9,
Gray16,
RGB48,
Lab48,
CMYK64,
DeepMultichannel,
Duotone16,
COLORMODE_UNKNOWN = 9000
};
/**
* Color samplers, apparently distict from PSDColormode
*/
namespace psd_color_sampler {
enum PSDColorSamplers {
RGB,
HSB,
CMYK,
PANTONE, // LAB
FOCOLTONE, // CMYK
TRUMATCH, // CMYK
TOYO, // LAB
LAB,
GRAYSCALE,
HKS, // CMYK
DIC, // LAB
TOTAL_INK,
MONITOR_RGB,
DUOTONE,
OPACITY,
ANPA = 3000 // LAB
};
}
// EFFECTS
enum psd_gradient_style {
psd_gradient_style_linear, // 'Lnr '
psd_gradient_style_radial, // 'Rdl '
psd_gradient_style_angle, // 'Angl'
psd_gradient_style_reflected, // 'Rflc'
psd_gradient_style_diamond // 'Dmnd'
};
enum psd_color_stop_type {
psd_color_stop_type_foreground_color, // 'FrgC'
psd_color_stop_type_background_Color, // 'BckC'
psd_color_stop_type_user_stop // 'UsrS'
};
enum psd_technique_type {
psd_technique_softer,
psd_technique_precise,
psd_technique_slope_limit,
};
enum psd_stroke_position {
psd_stroke_outside,
psd_stroke_inside,
psd_stroke_center
};
enum psd_fill_type {
psd_fill_solid_color,
psd_fill_gradient,
psd_fill_pattern,
};
enum psd_glow_source {
psd_glow_center,
psd_glow_edge,
};
enum psd_bevel_style {
psd_bevel_outer_bevel,
psd_bevel_inner_bevel,
psd_bevel_emboss,
psd_bevel_pillow_emboss,
psd_bevel_stroke_emboss,
};
enum psd_direction {
psd_direction_up,
psd_direction_down
};
enum psd_section_type {
psd_other = 0,
psd_open_folder,
psd_closed_folder,
psd_bounding_divider
};
// GRADIENT MAP
// Each color stop
struct psd_gradient_color_stop
{
qint32 location; // Location of color stop
qint32 midpoint; // Midpoint of color stop
QColor actual_color;
psd_color_stop_type color_stop_type;
};
// Each transparency stop
struct psd_gradient_transparency_stop
{
qint32 location; // Location of transparency stop
qint32 midpoint; // Midpoint of transparency stop
qint8 opacity; // Opacity of transparency stop
};
// Gradient settings (Photoshop 6.0)
struct psd_layer_gradient_map
{
bool reverse; // Is gradient reverse
bool dithered; // Is gradient dithered
qint32 name_length;
quint16 *name; // Name of the gradient: Unicode string, padded
qint8 number_color_stops; // Number of color stops to follow
psd_gradient_color_stop * color_stop;
qint8 number_transparency_stops;// Number of transparency stops to follow
psd_gradient_transparency_stop * transparency_stop;
qint8 expansion_count; // Expansion count ( = 2 for Photoshop 6.0)
qint8 interpolation; // Interpolation if length above is non-zero
qint8 length; // Length (= 32 for Photoshop 6.0)
qint8 mode; // Mode for this gradient
qint32 random_number_seed; // Random number seed
qint8 showing_transparency_flag;// Flag for showing transparency
qint8 using_vector_color_flag;// Flag for using vector color
qint32 roughness_factor; // Roughness factor
QColor min_color;
QColor max_color;
QColor lookup_table[256];
};
struct psd_gradient_color {
qint32 smoothness;
qint32 name_length;
quint16 * name; // Name of the gradient: Unicode string, padded
qint8 number_color_stops; // Number of color stops to follow
psd_gradient_color_stop * color_stop;
qint8 number_transparency_stops;// Number of transparency stops to follow
psd_gradient_transparency_stop *transparency_stop;
};
struct psd_pattern {
psd_color_mode color_mode = Bitmap; // The image mode of the file.
quint8 height = 0; // Point: vertical, 2 bytes and horizontal, 2 bytes
quint8 width = 0;
QString name;
QString uuid;
qint32 version = 0;
quint8 top = 0; // Rectangle: top, left, bottom, right
quint8 left = 0;
quint8 bottom = 0;
quint8 right = 0;
qint32 max_channel = 0; // Max channels
qint32 channel_number = 0;
QVector<QRgb> color_table;
};
struct psd_layer_effects_context {
psd_layer_effects_context()
: keep_original(false)
{
}
bool keep_original;
};
#define PSD_LOOKUP_TABLE_SIZE 256
-// dsdw, isdw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_22203
+// dsdw, isdw: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_22203
class KRITAPSD_EXPORT psd_layer_effects_shadow_base {
public:
psd_layer_effects_shadow_base()
: m_invertsSelection(false)
, m_edgeHidden(true)
, m_effectEnabled(false)
, m_blendMode(COMPOSITE_MULT)
, m_color(Qt::black)
, m_nativeColor(Qt::black)
, m_opacity(75)
, m_angle(120)
, m_useGlobalLight(true)
, m_distance(21)
, m_spread(0)
, m_size(21)
, m_antiAliased(0)
, m_noise(0)
, m_knocksOut(false)
, m_fillType(psd_fill_solid_color)
, m_technique(psd_technique_softer)
, m_range(100)
, m_jitter(0)
, m_gradient(0)
{
for(int i = 0; i < PSD_LOOKUP_TABLE_SIZE; ++i) {
m_contourLookupTable[i] = i;
}
}
virtual ~psd_layer_effects_shadow_base() {
}
QPoint calculateOffset(const psd_layer_effects_context *context) const;
void setEffectEnabled(bool value) {
m_effectEnabled = value;
}
bool effectEnabled() const {
return m_effectEnabled;
}
QString blendMode() const {
return m_blendMode;
}
QColor color() const {
return m_color;
}
QColor nativeColor() const {
return m_nativeColor;
}
qint32 opacity() const {
return m_opacity;
}
qint32 angle() const {
return m_angle;
}
bool useGlobalLight() const {
return m_useGlobalLight;
}
qint32 distance() const {
return m_distance;
}
qint32 spread() const {
return m_spread;
}
qint32 size() const {
return m_size;
}
const quint8* contourLookupTable() const {
return m_contourLookupTable;
}
bool antiAliased() const {
return m_antiAliased;
}
qint32 noise() const {
return m_noise;
}
bool knocksOut() const {
return m_knocksOut;
}
bool invertsSelection() const {
return m_invertsSelection;
}
bool edgeHidden() const {
return m_edgeHidden;
}
psd_fill_type fillType() const {
return m_fillType;
}
psd_technique_type technique() const {
return m_technique;
}
qint32 range() const {
return m_range;
}
qint32 jitter() const {
return m_jitter;
}
KoAbstractGradientSP gradient() const {
return m_gradient;
}
public:
void setBlendMode(QString value) {
m_blendMode = value;
}
void setColor(QColor value) {
m_color = value;
}
void setNativeColor(QColor value) {
m_nativeColor = value;
}
void setOpacity(qint32 value) {
m_opacity = value;
}
void setAngle(qint32 value) {
m_angle = value;
}
void setUseGlobalLight(bool value) {
m_useGlobalLight = value;
}
void setDistance(qint32 value) {
m_distance = value;
}
void setSpread(qint32 value) {
m_spread = value;
}
void setSize(qint32 value) {
m_size = value;
}
void setContourLookupTable(const quint8* value) {
memcpy(m_contourLookupTable, value, PSD_LOOKUP_TABLE_SIZE * sizeof(quint8));
}
void setAntiAliased(bool value) {
m_antiAliased = value;
}
void setNoise(qint32 value) {
m_noise = value;
}
void setKnocksOut(bool value) {
m_knocksOut = value;
}
void setInvertsSelection(bool value) {
m_invertsSelection = value;
}
void setEdgeHidden(bool value) {
m_edgeHidden = value;
}
void setFillType(psd_fill_type value) {
m_fillType = value;
}
void setTechnique(psd_technique_type value) {
m_technique = value;
}
void setRange(qint32 value) {
m_range = value;
}
void setJitter(qint32 value) {
m_jitter = value;
}
void setGradient(KoAbstractGradientSP value) {
m_gradient = value;
}
virtual void scaleLinearSizes(qreal scale) {
m_distance *= scale;
m_size *= scale;
}
private:
// internal
bool m_invertsSelection;
bool m_edgeHidden;
private:
bool m_effectEnabled; // Effect enabled
QString m_blendMode; // already in Krita format!
QColor m_color;
QColor m_nativeColor;
qint32 m_opacity; // Opacity as a percent (0...100)
qint32 m_angle; // Angle in degrees
bool m_useGlobalLight; // Use this angle in all of the layer effects
qint32 m_distance; // Distance in pixels
qint32 m_spread; // Intensity as a percent
qint32 m_size; // Blur value in pixels
quint8 m_contourLookupTable[PSD_LOOKUP_TABLE_SIZE];
bool m_antiAliased;
qint32 m_noise;
bool m_knocksOut;
// for Outer/Inner Glow
psd_fill_type m_fillType;
psd_technique_type m_technique;
qint32 m_range;
qint32 m_jitter;
KoAbstractGradientSP m_gradient;
};
class KRITAPSD_EXPORT psd_layer_effects_shadow_common : public psd_layer_effects_shadow_base
{
public:
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
// using psd_layer_effects_shadow_base::setBlendMode;
// using psd_layer_effects_shadow_base::setColor;
// using psd_layer_effects_shadow_base::setOpacity;
// using psd_layer_effects_shadow_base::setAngle;
// using psd_layer_effects_shadow_base::setUseGlobalLight;
// using psd_layer_effects_shadow_base::setDistance;
// using psd_layer_effects_shadow_base::setSpread;
// using psd_layer_effects_shadow_base::setSize;
// using psd_layer_effects_shadow_base::setContourLookupTable;
// using psd_layer_effects_shadow_base::setAntiAliased;
// using psd_layer_effects_shadow_base::setNoise;
};
class KRITAPSD_EXPORT psd_layer_effects_drop_shadow : public psd_layer_effects_shadow_common
{
public:
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
//using psd_layer_effects_shadow_base::setKnocksOut;
};
-// isdw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_22203
+// isdw: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_22203
class KRITAPSD_EXPORT psd_layer_effects_inner_shadow : public psd_layer_effects_shadow_common
{
public:
psd_layer_effects_inner_shadow() {
setKnocksOut(false);
setInvertsSelection(true);
setEdgeHidden(false);
}
};
class KRITAPSD_EXPORT psd_layer_effects_glow_common : public psd_layer_effects_shadow_base
{
public:
psd_layer_effects_glow_common() {
setKnocksOut(true);
setDistance(0);
setBlendMode(COMPOSITE_LINEAR_DODGE);
setColor(Qt::white);
}
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
// using psd_layer_effects_shadow_base::setBlendMode;
// using psd_layer_effects_shadow_base::setColor;
// using psd_layer_effects_shadow_base::setOpacity;
// using psd_layer_effects_shadow_base::setSpread;
// using psd_layer_effects_shadow_base::setSize;
// using psd_layer_effects_shadow_base::setContourLookupTable;
// using psd_layer_effects_shadow_base::setAntiAliased;
// using psd_layer_effects_shadow_base::setNoise;
// using psd_layer_effects_shadow_base::setFillType;
// using psd_layer_effects_shadow_base::setTechnique;
// using psd_layer_effects_shadow_base::setRange;
// using psd_layer_effects_shadow_base::setJitter;
// using psd_layer_effects_shadow_base::setGradient;
};
-// oglw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_25738
+// oglw: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_25738
class KRITAPSD_EXPORT psd_layer_effects_outer_glow : public psd_layer_effects_glow_common
{
};
-// iglw: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_27692
+// iglw: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_27692
class KRITAPSD_EXPORT psd_layer_effects_inner_glow : public psd_layer_effects_glow_common
{
public:
psd_layer_effects_inner_glow()
: m_source(psd_glow_edge) {
setInvertsSelection(true);
setEdgeHidden(false);
setKnocksOut(false);
}
psd_glow_source source() const {
return m_source;
}
void setSource(psd_glow_source value) {
m_source = value;
}
private:
psd_glow_source m_source;
};
struct psd_layer_effects_satin : public psd_layer_effects_shadow_base
{
psd_layer_effects_satin() {
setInvert(false);
setUseGlobalLight(false);
setDistance(8);
setSize(7);
setSpread(0);
setKnocksOut(true);
setEdgeHidden(false);
setBlendMode(COMPOSITE_LINEAR_BURN);
}
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
// using psd_layer_effects_shadow_base::setBlendMode;
// using psd_layer_effects_shadow_base::setColor;
// using psd_layer_effects_shadow_base::setOpacity;
// // NOTE: no global light setting explicitly!
// using psd_layer_effects_shadow_base::setAngle;
// using psd_layer_effects_shadow_base::setDistance;
// using psd_layer_effects_shadow_base::setSize;
// using psd_layer_effects_shadow_base::setContourLookupTable;
// using psd_layer_effects_shadow_base::setAntiAliased;
bool invert() const {
return m_invert;
}
void setInvert(bool value) {
m_invert = value;
}
private:
bool m_invert;
};
struct psd_pattern_info {
qint32 name_length;
quint16 * name;
quint8 identifier[256];
};
-// bevl: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_31889
+// bevl: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_31889
struct psd_layer_effects_bevel_emboss : public psd_layer_effects_shadow_base
{
psd_layer_effects_bevel_emboss()
: m_style(psd_bevel_inner_bevel),
m_technique(psd_technique_softer),
m_depth(100),
m_direction(psd_direction_up),
m_soften(0),
m_altitude(30),
m_glossAntiAliased(false),
m_highlightBlendMode(COMPOSITE_SCREEN),
m_highlightColor(Qt::white),
m_highlightOpacity(75),
m_shadowBlendMode(COMPOSITE_MULT),
m_shadowColor(Qt::black),
m_shadowOpacity(75),
m_contourEnabled(false),
m_contourRange(100),
m_textureEnabled(false),
m_texturePattern(0),
m_textureScale(100),
m_textureDepth(100),
m_textureInvert(false),
m_textureAlignWithLayer(true),
m_textureHorizontalPhase(0),
m_textureVerticalPhase(0)
{
for(int i = 0; i < PSD_LOOKUP_TABLE_SIZE; ++i) {
m_glossContourLookupTable[i] = i;
}
}
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
// using psd_layer_effects_shadow_base::setSize;
// using psd_layer_effects_shadow_base::setAngle;
// using psd_layer_effects_shadow_base::setUseGlobalLight;
// using psd_layer_effects_shadow_base::setContourLookupTable;
// using psd_layer_effects_shadow_base::setAntiAliased;
psd_bevel_style style() const {
return m_style;
}
void setStyle(psd_bevel_style value) {
m_style = value;
}
psd_technique_type technique() const {
return m_technique;
}
void setTechnique(psd_technique_type value) {
m_technique = value;
}
int depth() const {
return m_depth;
}
void setDepth(int value) {
m_depth = value;
}
psd_direction direction() const {
return m_direction;
}
void setDirection(psd_direction value) {
m_direction = value;
}
int soften() const {
return m_soften;
}
void setSoften(int value) {
m_soften = value;
}
int altitude() const {
return m_altitude;
}
void setAltitude(int value) {
m_altitude = value;
}
const quint8* glossContourLookupTable() const {
return m_glossContourLookupTable;
}
void setGlossContourLookupTable(const quint8 *value) {
memcpy(m_glossContourLookupTable, value, PSD_LOOKUP_TABLE_SIZE * sizeof(quint8));
}
bool glossAntiAliased() const {
return m_glossAntiAliased;
}
void setGlossAntiAliased(bool value) {
m_glossAntiAliased = value;
}
QString highlightBlendMode() const {
return m_highlightBlendMode;
}
void setHighlightBlendMode(QString value) {
m_highlightBlendMode = value;
}
QColor highlightColor() const {
return m_highlightColor;
}
void setHighlightColor(QColor value) {
m_highlightColor = value;
}
qint32 highlightOpacity() const {
return m_highlightOpacity;
}
void setHighlightOpacity(qint32 value) {
m_highlightOpacity = value;
}
QString shadowBlendMode() const {
return m_shadowBlendMode;
}
void setShadowBlendMode(QString value) {
m_shadowBlendMode = value;
}
QColor shadowColor() const {
return m_shadowColor;
}
void setShadowColor(QColor value) {
m_shadowColor = value;
}
qint32 shadowOpacity() const {
return m_shadowOpacity;
}
void setShadowOpacity(qint32 value) {
m_shadowOpacity = value;
}
bool contourEnabled() const {
return m_contourEnabled;
}
void setContourEnabled(bool value) {
m_contourEnabled = value;
}
int contourRange() const {
return m_contourRange;
}
void setContourRange(int value) {
m_contourRange = value;
}
bool textureEnabled() const {
return m_textureEnabled;
}
void setTextureEnabled(bool value) {
m_textureEnabled = value;
}
KoPattern* texturePattern() const {
return m_texturePattern;
}
void setTexturePattern(KoPattern *value) {
m_texturePattern = value;
}
int textureScale() const {
return m_textureScale;
}
void setTextureScale(int value) {
m_textureScale = value;
}
int textureDepth() const {
return m_textureDepth;
}
void setTextureDepth(int value) {
m_textureDepth = value;
}
bool textureInvert() const {
return m_textureInvert;
}
void setTextureInvert(bool value) {
m_textureInvert = value;
}
bool textureAlignWithLayer() const {
return m_textureAlignWithLayer;
}
void setTextureAlignWithLayer(bool value) {
m_textureAlignWithLayer = value;
}
void setTexturePhase(const QPointF &phase) {
m_textureHorizontalPhase = phase.x();
m_textureVerticalPhase = phase.y();
}
QPointF texturePhase() const {
return QPointF(m_textureHorizontalPhase, m_textureVerticalPhase);
}
int textureHorizontalPhase() const {
return m_textureHorizontalPhase;
}
void setTextureHorizontalPhase(int value) {
m_textureHorizontalPhase = value;
}
int textureVerticalPhase() const {
return m_textureVerticalPhase;
}
void setTextureVerticalPhase(int value) {
m_textureVerticalPhase = value;
}
void scaleLinearSizes(qreal scale) override {
psd_layer_effects_shadow_base::scaleLinearSizes(scale);
m_soften *= scale;
m_textureScale *= scale;
}
private:
psd_bevel_style m_style;
psd_technique_type m_technique;
int m_depth;
psd_direction m_direction; // Up or down
int m_soften; // Blur value in pixels.
int m_altitude;
quint8 m_glossContourLookupTable[256];
bool m_glossAntiAliased;
QString m_highlightBlendMode; // already in Krita format
QColor m_highlightColor;
qint32 m_highlightOpacity; // Highlight opacity as a percent
QString m_shadowBlendMode; // already in Krita format
QColor m_shadowColor;
qint32 m_shadowOpacity; // Shadow opacity as a percent
bool m_contourEnabled;
int m_contourRange;
bool m_textureEnabled;
KoPattern *m_texturePattern;
int m_textureScale;
int m_textureDepth;
bool m_textureInvert;
bool m_textureAlignWithLayer;
int m_textureHorizontalPhase; // 0..100%
int m_textureVerticalPhase; // 0..100%
};
struct psd_layer_effects_overlay_base : public psd_layer_effects_shadow_base
{
psd_layer_effects_overlay_base()
: m_scale(100),
m_alignWithLayer(true),
m_reverse(false),
m_style(psd_gradient_style_linear),
m_gradientXOffset(0),
m_gradientYOffset(0),
m_pattern(0),
m_horizontalPhase(0),
m_verticalPhase(0)
{
setUseGlobalLight(false);
}
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
// using psd_layer_effects_shadow_base::setBlendMode;
// using psd_layer_effects_shadow_base::setOpacity;
int scale() const {
return m_scale;
}
bool alignWithLayer() const {
return m_alignWithLayer;
}
bool reverse() const {
return m_reverse;
}
psd_gradient_style style() const {
return m_style;
}
int gradientXOffset() const {
return m_gradientXOffset;
}
int gradientYOffset() const {
return m_gradientYOffset;
}
KoPattern* pattern() const {
return m_pattern;
}
int horizontalPhase() const {
return m_horizontalPhase;
}
int verticalPhase() const {
return m_verticalPhase;
}
// refactor that
public:
void setScale(int value) {
m_scale = value;
}
void setAlignWithLayer(bool value) {
m_alignWithLayer = value;
}
void setReverse(bool value) {
m_reverse = value;
}
void setStyle(psd_gradient_style value) {
m_style = value;
}
void setGradientOffset(const QPointF &pt) {
m_gradientXOffset = qRound(pt.x());
m_gradientYOffset = qRound(pt.y());
}
QPointF gradientOffset() const {
return QPointF(m_gradientXOffset, m_gradientYOffset);
}
void setPattern(KoPattern *value) {
m_pattern = value;
}
void setPatternPhase(const QPointF &phase) {
m_horizontalPhase = phase.x();
m_verticalPhase = phase.y();
}
QPointF patternPhase() const {
return QPointF(m_horizontalPhase, m_verticalPhase);
}
void scaleLinearSizes(qreal scale) override {
psd_layer_effects_shadow_base::scaleLinearSizes(scale);
m_scale *= scale;
}
private:
// Gradient+Pattern
int m_scale;
bool m_alignWithLayer;
// Gradient
bool m_reverse;
psd_gradient_style m_style;
int m_gradientXOffset; // 0..100%
int m_gradientYOffset; // 0..100%
// Pattern
KoPattern *m_pattern;
int m_horizontalPhase; // 0..100%
int m_verticalPhase; // 0..100%
protected:
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
// must be called in the derived classes' c-tor
// using psd_layer_effects_shadow_base::setFillType;
};
-// sofi: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_70055
+// sofi: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577409_70055
struct psd_layer_effects_color_overlay : public psd_layer_effects_overlay_base
{
psd_layer_effects_color_overlay() {
setFillType(psd_fill_solid_color);
setColor(Qt::white);
}
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
// using psd_layer_effects_shadow_base::setColor;
};
struct psd_layer_effects_gradient_overlay : public psd_layer_effects_overlay_base
{
psd_layer_effects_gradient_overlay()
{
setFillType(psd_fill_gradient);
setAngle(90);
setReverse(false);
setScale(100);
setAlignWithLayer(true);
setStyle(psd_gradient_style_linear);
}
public:
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
// using psd_layer_effects_shadow_base::setGradient;
// using psd_layer_effects_shadow_base::setAngle;
// using psd_layer_effects_overlay_base::setReverse;
// using psd_layer_effects_overlay_base::setScale;
// using psd_layer_effects_overlay_base::setAlignWithLayer;
// using psd_layer_effects_overlay_base::setStyle;
// using psd_layer_effects_overlay_base::setGradientOffset;
// using psd_layer_effects_overlay_base::gradientOffset;
};
struct psd_layer_effects_pattern_overlay : public psd_layer_effects_overlay_base
{
psd_layer_effects_pattern_overlay()
{
setFillType(psd_fill_pattern);
setScale(100);
setAlignWithLayer(true);
}
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
// using psd_layer_effects_overlay_base::setScale;
// using psd_layer_effects_overlay_base::setAlignWithLayer;
// using psd_layer_effects_overlay_base::setPattern;
// using psd_layer_effects_overlay_base::setPatternPhase;
// using psd_layer_effects_overlay_base::patternPhase;
private:
// These are unused
/*int m_scale;
bool m_alignWithLayer;
KoPattern *m_pattern;
int m_horizontalPhase;
int m_verticalPhase;*/
};
struct psd_layer_effects_stroke : public psd_layer_effects_overlay_base
{
psd_layer_effects_stroke()
: m_position(psd_stroke_outside)
{
setFillType(psd_fill_solid_color);
setColor(Qt::black);
setAngle(90);
setReverse(false);
setScale(100);
setAlignWithLayer(true);
setStyle(psd_gradient_style_linear);
setScale(100);
setAlignWithLayer(true);
}
/// FIXME: 'using' is not supported by MSVC, so please refactor in
/// some other way to ensure that the setters are not used
/// in the classes we don't want
// using psd_layer_effects_shadow_base::setFillType;
// using psd_layer_effects_shadow_base::setSize;
// using psd_layer_effects_shadow_base::setColor;
// using psd_layer_effects_shadow_base::setGradient;
// using psd_layer_effects_shadow_base::setAngle;
// using psd_layer_effects_overlay_base::setReverse;
// using psd_layer_effects_overlay_base::setScale;
// using psd_layer_effects_overlay_base::setAlignWithLayer;
// using psd_layer_effects_overlay_base::setStyle;
// using psd_layer_effects_overlay_base::setGradientOffset;
// using psd_layer_effects_overlay_base::gradientOffset;
// using psd_layer_effects_overlay_base::setPattern;
// using psd_layer_effects_overlay_base::setPatternPhase;
// using psd_layer_effects_overlay_base::patternPhase;
psd_stroke_position position() const {
return m_position;
}
void setPosition(psd_stroke_position value) {
m_position = value;
}
private:
psd_stroke_position m_position;
};
/**
* Convert PsdColorMode to pigment colormodelid and colordepthid.
* @see KoColorModelStandardIds
*
* @return a QPair containing ColorModelId and ColorDepthID
*/
QPair<QString, QString> KRITAPSD_EXPORT psd_colormode_to_colormodelid(psd_color_mode colormode, quint16 channelDepth);
/**
* Convert the Photoshop blend mode strings to Pigment compositeop id's
*/
QString KRITAPSD_EXPORT psd_blendmode_to_composite_op(const QString& blendmode);
QString KRITAPSD_EXPORT composite_op_to_psd_blendmode(const QString& compositeOp);
#endif // PSD_H
diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp
index 209a89d592..e2d9bd5412 100644
--- a/libs/ui/KisApplication.cpp
+++ b/libs/ui/KisApplication.cpp
@@ -1,874 +1,888 @@
/*
* 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_MACOS
#include "osx.h"
#endif
#include <QStandardPaths>
#include <QDesktopWidget>
#include <QDir>
#include <QFile>
#include <QLocale>
#include <QMessageBox>
#include <QProcessEnvironment>
#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 "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 <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 <KisResourceServerProvider.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 <KisUsageLogger.h>
#include <KritaVersionWrapper.h>
#include <dialogs/KisSessionManagerDialog.h>
#include "widgets/KisScreenColorPicker.h"
#include "KisDlgInternalColorSelector.h"
#include <dialogs/KisAsyncAnimationFramesSaveDialog.h>
#include <kis_image_animation_interface.h>
namespace {
const QTime appStartTime(QTime::currentTime());
}
class KisApplication::Private
{
public:
Private() {}
QPointer<KisSplashScreen> splashScreen;
KisAutoSaveRecoveryDialog *autosaveDialog {0};
QPointer<KisMainWindow> mainWindow; // The first mainwindow we create on startup
bool batchRun {false};
+ QVector<QByteArray> earlyRemoteArguments;
};
class KisApplication::ResetStarting
{
public:
ResetStarting(KisSplashScreen *splash, int fileCount)
: m_splash(splash)
, m_fileCount(fileCount)
{
}
~ResetStarting() {
if (m_splash) {
m_splash->hide();
}
}
QPointer<KisSplashScreen> m_splash;
int m_fileCount;
};
KisApplication::KisApplication(const QString &key, int &argc, char **argv)
: QtSingleApplication(key, argc, argv)
, d(new Private)
{
#ifdef Q_OS_MACOS
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("krita"));
if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) {
- QStringList styles = QStringList() /*<< "breeze"*/ << "fusion" << "plastique";
+ 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();
}
}
#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()
{
// qDebug() << "addResourceTypes();";
// All Krita's resource types
KoResourcePaths::addResourceType("markers", "data", "/styles/");
KoResourcePaths::addResourceType("kis_pics", "data", "/pics/");
KoResourcePaths::addResourceType("kis_images", "data", "/images/");
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("kis_windowlayouts", "data", "/windowlayouts/");
KoResourcePaths::addResourceType("kis_sessions", "data", "/sessions/");
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("kis_actions", "data", "/pykrita");
KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc");
KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/");
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");
KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons");
KoResourcePaths::addResourceType("ko_gamutmasks", "data", "/gamutmasks/", true);
// // 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) + "/brushes/");
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/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/");
}
void KisApplication::loadResources()
{
// qDebug() << "loadResources();";
setSplashScreenLoadingText(i18n("Loading Resources..."));
processEvents();
KoResourceServerProvider::instance();
setSplashScreenLoadingText(i18n("Loading Brush Presets..."));
processEvents();
KisResourceServerProvider::instance();
setSplashScreenLoadingText(i18n("Loading Brushes..."));
processEvents();
KisBrushServer::instance()->brushServer();
setSplashScreenLoadingText(i18n("Loading Bundles..."));
processEvents();
KisResourceBundleServerProvider::instance();
}
void KisApplication::loadResourceTags()
{
// qDebug() << "loadResourceTags()";
KoResourceServerProvider::instance()->patternServer()->loadTags();
KoResourceServerProvider::instance()->gradientServer()->loadTags();
KoResourceServerProvider::instance()->paletteServer()->loadTags();
KoResourceServerProvider::instance()->svgSymbolCollectionServer()->loadTags();
KisBrushServer::instance()->brushServer()->loadTags();
KisResourceServerProvider::instance()->workspaceServer()->loadTags();
KisResourceServerProvider::instance()->layerStyleCollectionServer()->loadTags();
KisResourceBundleServerProvider::instance()->resourceBundleServer()->loadTags();
KisResourceServerProvider::instance()->paintOpPresetServer()->loadTags();
KisResourceServerProvider::instance()->paintOpPresetServer()->clearOldSystemTags();
}
void KisApplication::loadPlugins()
{
// qDebug() << "loadPlugins();";
KoShapeRegistry* r = KoShapeRegistry::instance();
r->add(new KisShapeSelectionFactory());
KisActionRegistry::instance();
KisFilterRegistry::instance();
KisGeneratorRegistry::instance();
KisPaintOpRegistry::instance();
KoColorSpaceRegistry::instance();
}
void KisApplication::loadGuiPlugins()
{
// qDebug() << "loadGuiPlugins();";
// Load the krita-specific tools
setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool..."));
processEvents();
// qDebug() << "loading tools";
KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"),
QString::fromLatin1("[X-Krita-Version] == 28"));
// Load dockers
setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock..."));
processEvents();
// qDebug() << "loading dockers";
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();
// qDebug() << "loading exiv2";
KisExiv2::initialize();
}
bool KisApplication::start(const KisApplicationArguments &args)
{
KisConfig cfg(false);
#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" && opengl != "TRY_OPENGL") {
cfg.setCanvasState("OPENGL_FAILED");
}
setSplashScreenLoadingText(i18n("Initializing Globals"));
processEvents();
initializeGlobals(args);
const bool doNewImage = args.doNewImage();
const bool doTemplate = args.doTemplate();
const bool exportAs = args.exportAs();
const bool exportSequence = args.exportSequence();
const QString exportFileName = args.exportFileName();
d->batchRun = (exportAs || exportSequence || !exportFileName.isEmpty());
const bool needsMainWindow = (!exportAs && !exportSequence);
// only show the mainWindow when no command-line mode option is passed
bool showmainWindow = (!exportAs && !exportSequence); // would be !batchRun;
const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH");
if (showSplashScreen && d->splashScreen) {
d->splashScreen->show();
d->splashScreen->repaint();
processEvents();
}
KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator());
KConfigGroup group(KSharedConfig::openConfig(), "theme");
Digikam::ThemeManager themeManager;
themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark"));
ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done
Q_UNUSED(resetStarting);
// Make sure we can save resources and tags
setSplashScreenLoadingText(i18n("Adding resource types"));
processEvents();
addResourceTypes();
// Load the plugins
loadPlugins();
// Load all resources
loadResources();
// Load all the tags
loadResourceTags();
// Load the gui plugins
loadGuiPlugins();
KisPart *kisPart = KisPart::instance();
if (needsMainWindow) {
// show a mainWindow asap, if we want that
setSplashScreenLoadingText(i18n("Loading Main Window..."));
processEvents();
bool sessionNeeded = true;
auto sessionMode = cfg.sessionOnStartup();
if (!args.session().isEmpty()) {
sessionNeeded = !kisPart->restoreSession(args.session());
} else if (sessionMode == KisConfig::SOS_ShowSessionManager) {
showmainWindow = false;
sessionNeeded = false;
kisPart->showSessionManager();
} else if (sessionMode == KisConfig::SOS_PreviousSession) {
KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session");
const QString &sessionName = sessionCfg.readEntry("previousSession");
sessionNeeded = !kisPart->restoreSession(sessionName);
}
if (sessionNeeded) {
kisPart->startBlankSession();
}
if (!args.windowLayout().isEmpty()) {
KoResourceServer<KisWindowLayoutResource> * rserver = KisResourceServerProvider::instance()->windowLayoutServer();
KisWindowLayoutResource* windowLayout = rserver->resourceByName(args.windowLayout());
if (windowLayout) {
windowLayout->applyLayout();
}
}
if (showmainWindow) {
d->mainWindow = kisPart->currentMainwindow();
if (!args.workspace().isEmpty()) {
KoResourceServer<KisWorkspaceResource> * rserver = KisResourceServerProvider::instance()->workspaceServer();
KisWorkspaceResource* workspace = rserver->resourceByName(args.workspace());
if (workspace) {
d->mainWindow->restoreWorkspace(workspace);
}
}
if (args.canvasOnly()) {
d->mainWindow->viewManager()->switchCanvasOnly(true);
}
if (args.fullScreen()) {
d->mainWindow->showFullScreen();
}
} else {
d->mainWindow = kisPart->createMainWindow();
}
}
short int numberOfOpenDocuments = 0; // number of documents open
// Check for autosave files that can be restored, if we're not running a batchrun (test)
if (!d->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->addDocument(doc);
d->mainWindow->addViewAndNotifyLoadingCompleted(doc);
}
}
// Get the command line arguments which we have to parse
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) {
// called in mix with batch options? ignore and silently skip
if (d->batchRun) {
continue;
}
if (createNewDocFromTemplate(fileName, d->mainWindow)) {
++numberOfOpenDocuments;
}
// now try to load
}
else {
if (exportAs) {
QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false);
if (outputMimetype == "application/octetstream") {
dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl;
return false;
}
KisDocument *doc = kisPart->createDocument();
doc->setFileBatchMode(d->batchRun);
bool result = doc->openUrl(QUrl::fromLocalFile(fileName));
if (!result) {
errKrita << "Could not load " << fileName << ":" << doc->errorMessage();
QTimer::singleShot(0, this, SLOT(quit()));
return false;
}
if (exportFileName.isEmpty()) {
errKrita << "Export destination is not specified for" << fileName << "Please specify export destination with --export-filename option";
QTimer::singleShot(0, this, SLOT(quit()));
return false;
}
qApp->processEvents(); // For vector layers to be updated
doc->setFileBatchMode(true);
if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) {
errKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage();
}
QTimer::singleShot(0, this, SLOT(quit()));
return true;
}
else if (exportSequence) {
KisDocument *doc = kisPart->createDocument();
doc->setFileBatchMode(d->batchRun);
doc->openUrl(QUrl::fromLocalFile(fileName));
qApp->processEvents(); // For vector layers to be updated
if (!doc->image()->animationInterface()->hasAnimation()) {
errKrita << "This file has no animation." << endl;
QTimer::singleShot(0, this, SLOT(quit()));
return false;
}
doc->setFileBatchMode(true);
int sequenceStart = 0;
KisAsyncAnimationFramesSaveDialog exporter(doc->image(),
doc->image()->animationInterface()->fullClipRange(),
exportFileName,
sequenceStart,
0);
exporter.setBatchMode(d->batchRun);
KisAsyncAnimationFramesSaveDialog::Result result =
exporter.regenerateRange(0);
if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) {
errKrita << i18n("Failed to render animation frames!") << endl;
}
QTimer::singleShot(0, this, SLOT(quit()));
return true;
}
else if (d->mainWindow) {
if (fileName.endsWith(".bundle")) {
d->mainWindow->installBundle(fileName);
}
else {
KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) {
// Normal case, success
numberOfOpenDocuments++;
}
}
}
}
}
}
// 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(true);
d->splashScreen->displayRecentFiles(true);
}
+ Q_FOREACH(const QByteArray &message, d->earlyRemoteArguments) {
+ executeRemoteArguments(message, d->mainWindow);
+ }
+
+ KisUsageLogger::writeSysInfo(KisUsageLogger::screenInformation());
+
// not calling this before since the program will quit there.
return true;
}
KisApplication::~KisApplication()
{
}
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->setLoadingText(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)
+void KisApplication::executeRemoteArguments(QByteArray message, KisMainWindow *mainWindow)
{
- 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);
+ createNewDocFromTemplate(filename, mainWindow);
}
else if (QFile(filename).exists()) {
KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
- mw->openDocument(QUrl::fromLocalFile(filename), flags);
+ mainWindow->openDocument(QUrl::fromLocalFile(filename), flags);
}
}
}
}
+
+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 && KisPart::instance()->mainWindows().size() > 0) {
+ mw = KisPart::instance()->mainWindows().first();
+ }
+
+ if (!mw) {
+ d->earlyRemoteArguments << message;
+ return;
+ }
+ executeRemoteArguments(message, mw);
+}
+
void KisApplication::fileOpenRequested(const QString &url)
{
KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first();
if (mainWindow) {
KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
mainWindow->openDocument(QUrl::fromLocalFile(url), flags);
}
}
void KisApplication::checkAutosaveFiles()
{
if (d->batchRun) return;
#ifdef Q_OS_WIN
QDir dir = QDir::temp();
#else
QDir dir = QDir::home();
#endif
// 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!
// Hidden autosave files
QStringList filters = QStringList() << QString(".krita-*-*-autosave.kra");
// all autosave files for our application
QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden);
// Visible autosave files
filters = QStringList() << QString("krita-*-*-autosave.kra");
autosaveFiles += dir.entryList(filters, QDir::Files);
// Allow the user to make their selection
if (autosaveFiles.size() > 0) {
if (d->splashScreen) {
// hide the splashscreen to see the dialog
d->splashScreen->hide();
}
d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow());
QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec();
if (result == QDialog::Accepted) {
QStringList filesToRecover = d->autosaveDialog->recoverableFiles();
Q_FOREACH (const QString &autosaveFile, autosaveFiles) {
if (!filesToRecover.contains(autosaveFile)) {
KisUsageLogger::log(QString("Removing autosave file %1").arg(dir.absolutePath() + "/" + 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 (d->mainWindow) {
Q_FOREACH (const QUrl &url, autosaveUrls) {
KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile);
}
}
}
// cleanup
delete d->autosaveDialog;
d->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);
if (templateURL.scheme().isEmpty()) {
templateURL.setScheme("file");
}
KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) {
dbgUI << "Template loaded...";
return true;
}
else {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("Template %1 failed to load.", templateURL.toDisplayString()));
}
}
return false;
}
void KisApplication::clearConfig()
{
KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread());
KSharedConfigPtr config = KSharedConfig::openConfig();
// find user settings file
bool createDir = false;
QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir);
QFile configFile(kritarcPath);
if (configFile.exists()) {
// clear file
if (configFile.open(QFile::WriteOnly)) {
configFile.close();
}
else {
QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("Failed to clear %1\n\n"
"Please make sure no other program is using the file and try again.",
kritarcPath),
QMessageBox::Ok, QMessageBox::Ok);
}
}
// reload from disk; with the user file settings cleared,
// this should load any default configuration files shipping with the program
config->reparseConfiguration();
config->sync();
}
void KisApplication::askClearConfig()
{
Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers();
bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier);
if (askClearConfig) {
bool ok = QMessageBox::question(0,
i18nc("@title:window", "Krita"),
i18n("Do you want to clear the settings file?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes;
if (ok) {
clearConfig();
}
}
}
diff --git a/libs/ui/KisApplication.h b/libs/ui/KisApplication.h
index 54fed0058c..0b6a7c4dd0 100644
--- a/libs/ui/KisApplication.h
+++ b/libs/ui/KisApplication.h
@@ -1,124 +1,125 @@
/*
* 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.
*/
#ifndef KIS_APPLICATION_H
#define KIS_APPLICATION_H
#include <QPointer>
#include <QScopedPointer>
#include <qtsingleapplication/qtsingleapplication.h>
#include "kritaui_export.h"
class KisMainWindow;
class KisApplicationPrivate;
class QWidget;
class KisApplicationArguments;
class KisAutoSaveRecoveryDialog;
#include <KisImportExportManager.h>
/**
* @brief Base class for the %Krita app
*
* This class handles arguments given on the command line and
* shows a generic about dialog for the Krita app.
*
* In addition it adds the standard directories where Krita
* can find its images etc.
*
* If the last mainwindow becomes closed, KisApplication automatically
* calls QApplication::quit.
*/
class KRITAUI_EXPORT KisApplication : public QtSingleApplication
{
Q_OBJECT
public:
/**
* Creates an application object, adds some standard directories and
* initializes kimgio.
*/
explicit KisApplication(const QString &key, int &argc, char **argv);
/**
* Destructor.
*/
~KisApplication() override;
/**
* Call this to start the application.
*
* Parses command line arguments and creates the initial main windowss and docs
* from them (or an empty doc if no cmd-line argument is specified ).
*
* You must call this method directly before calling QApplication::exec.
*
* It is valid behaviour not to call this method at all. In this case you
* have to process your command line parameters by yourself.
*/
virtual bool start(const KisApplicationArguments &args);
/**
* Checks if user is holding ctrl+alt+shift keys and asks if the settings file should be cleared.
*
* Typically called during startup before reading the config.
*/
void askClearConfig();
/**
* Tell KisApplication to show this splashscreen when you call start();
* when start returns, the splashscreen is hidden. Use KSplashScreen
* to have the splash show correctly on Xinerama displays.
*/
void setSplashScreen(QWidget *splash);
void setSplashScreenLoadingText(QString);
void hideSplashScreen();
/// Overridden to handle exceptions from event handlers.
bool notify(QObject *receiver, QEvent *event) override;
void addResourceTypes();
void loadResources();
void loadResourceTags();
void loadPlugins();
void loadGuiPlugins();
void initializeGlobals(const KisApplicationArguments &args);
public Q_SLOTS:
+ void executeRemoteArguments(QByteArray message, KisMainWindow *mainWindow);
void remoteArguments(QByteArray message, QObject*socket);
void fileOpenRequested(const QString & url);
private:
/// @return the number of autosavefiles opened
void checkAutosaveFiles();
bool createNewDocFromTemplate(const QString &fileName, KisMainWindow *m_mainWindow);
void clearConfig();
private:
class Private;
QScopedPointer<Private> d;
class ResetStarting;
friend class ResetStarting;
};
#endif
diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp
index 69c0fcdd8c..2e5414446c 100644
--- a/libs/ui/KisImportExportManager.cpp
+++ b/libs/ui/KisImportExportManager.cpp
@@ -1,726 +1,730 @@
/*
* 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 "KisImportExportManager.h"
#include <QDir>
#include <QFile>
#include <QLabel>
#include <QVBoxLayout>
#include <QList>
#include <QApplication>
#include <QByteArray>
#include <QPluginLoader>
#include <QFileInfo>
#include <QMessageBox>
#include <QJsonObject>
#include <QTextBrowser>
#include <QCheckBox>
#include <QSaveFile>
#include <QGroupBox>
#include <QFuture>
#include <QtConcurrent>
#include <klocalizedstring.h>
#include <ksqueezedtextlabel.h>
#include <kpluginfactory.h>
#include <KisUsageLogger.h>
#include <KoFileDialog.h>
#include <kis_icon_utils.h>
#include <KoDialog.h>
#include <KoProgressUpdater.h>
#include <KoJsonTrader.h>
#include <KisMimeDatabase.h>
#include <kis_config_widget.h>
#include <kis_debug.h>
#include <KisPreExportChecker.h>
#include <KisPart.h>
#include "kis_config.h"
#include "KisImportExportFilter.h"
#include "KisDocument.h"
#include <kis_image.h>
#include <kis_paint_layer.h>
#include "kis_painter.h"
#include "kis_guides_config.h"
#include "kis_grid_config.h"
#include "kis_popup_button.h"
#include <kis_iterator_ng.h>
#include "kis_async_action_feedback.h"
#include "KisReferenceImagesLayer.h"
// static cache for import and export mimetypes
QStringList KisImportExportManager::m_importMimeTypes;
QStringList KisImportExportManager::m_exportMimeTypes;
class Q_DECL_HIDDEN KisImportExportManager::Private
{
public:
KoUpdaterPtr updater;
QString cachedExportFilterMimeType;
QSharedPointer<KisImportExportFilter> cachedExportFilter;
};
struct KisImportExportManager::ConversionResult {
ConversionResult()
{
}
ConversionResult(const QFuture<KisImportExportErrorCode> &futureStatus)
: m_isAsync(true),
m_futureStatus(futureStatus)
{
}
ConversionResult(KisImportExportErrorCode status)
: m_isAsync(false),
m_status(status)
{
}
bool isAsync() const {
return m_isAsync;
}
QFuture<KisImportExportErrorCode> futureStatus() const {
// if the result is not async, then it means some failure happened,
// just return a cancelled future
KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || !m_status.isOk());
return m_futureStatus;
}
KisImportExportErrorCode status() const {
return m_status;
}
void setStatus(KisImportExportErrorCode value) {
m_status = value;
}
private:
bool m_isAsync = false;
QFuture<KisImportExportErrorCode> m_futureStatus;
KisImportExportErrorCode m_status = ImportExportCodes::InternalError;
};
KisImportExportManager::KisImportExportManager(KisDocument* document)
: m_document(document)
, d(new Private)
{
}
KisImportExportManager::~KisImportExportManager()
{
delete d;
}
KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
{
ConversionResult result = convert(Import, location, location, mimeType, false, 0, false);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError);
return result.status();
}
KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError);
return result.status();
}
QFuture<KisImportExportErrorCode> KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType,
KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() ||
!result.status().isOk(), QFuture<KisImportExportErrorCode>());
status = result.status();
return result.futureStatus();
}
// The static method to figure out to which parts of the
// graph this mimetype has a connection to.
QStringList KisImportExportManager::supportedMimeTypes(Direction direction)
{
// Find the right mimetype by the extension
QSet<QString> mimeTypes;
// mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster";
if (direction == KisImportExportManager::Import) {
if (m_importMimeTypes.isEmpty()) {
QList<QPluginLoader *>list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_importMimeTypes = mimeTypes.toList();
}
return m_importMimeTypes;
}
else if (direction == KisImportExportManager::Export) {
if (m_exportMimeTypes.isEmpty()) {
QList<QPluginLoader *>list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_exportMimeTypes = mimeTypes.toList();
}
return m_exportMimeTypes;
}
return QStringList();
}
KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction)
{
int weight = -1;
KisImportExportFilter *filter = 0;
QList<QPluginLoader *>list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import";
if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) {
KLibFactory *factory = qobject_cast<KLibFactory *>(loader->instance());
if (!factory) {
warnUI << loader->errorString();
continue;
}
QObject* obj = factory->create<KisImportExportFilter>(0);
if (!obj || !obj->inherits("KisImportExportFilter")) {
delete obj;
continue;
}
KisImportExportFilter *f = qobject_cast<KisImportExportFilter*>(obj);
if (!f) {
delete obj;
continue;
}
int w = json.value("X-KDE-Weight").toInt();
if (w > weight) {
delete filter;
filter = f;
f->setObjectName(loader->fileName());
weight = w;
}
}
}
qDeleteAll(list);
if (filter) {
filter->setMimeType(mimetype);
}
return filter;
}
bool KisImportExportManager::batchMode(void) const
{
return m_document->fileBatchMode();
}
void KisImportExportManager::setUpdater(KoUpdaterPtr updater)
{
d->updater = updater;
}
QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
{
KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio");
if (!defaultDir.isEmpty()) {
dialog.setDefaultDir(defaultDir);
}
QStringList mimeTypes;
mimeTypes << "audio/mpeg";
mimeTypes << "audio/ogg";
mimeTypes << "audio/vorbis";
mimeTypes << "audio/vnd.wave";
mimeTypes << "audio/flac";
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@title:window", "Open Audio"));
return dialog.filename();
}
KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync)
{
// export configuration is supported for export only
KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration));
QString typeName = mimeType;
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true);
}
QSharedPointer<KisImportExportFilter> filter;
/**
* Fetching a filter from the registry is a really expensive operation,
* because it blocks all the threads. Cache the filter if possible.
*/
if (direction == KisImportExportManager::Export &&
d->cachedExportFilter &&
d->cachedExportFilterMimeType == typeName) {
filter = d->cachedExportFilter;
} else {
filter = toQShared(filterForMimeType(typeName, direction));
if (direction == Export) {
d->cachedExportFilter = filter;
d->cachedExportFilterMimeType = typeName;
}
}
if (!filter) {
return KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect);
}
filter->setFilename(location);
filter->setRealFilename(realLocation);
filter->setBatchMode(batchMode());
filter->setMimeType(typeName);
if (!d->updater.isNull()) {
// WARNING: The updater is not guaranteed to be persistent! If you ever want
// to add progress reporting to "Save also as .kra", make sure you create
// a separate KoProgressUpdater for that!
// WARNING2: the failsafe completion of the updater happens in the destructor
// the filter.
filter->setUpdater(d->updater);
}
QByteArray from, to;
if (direction == Export) {
from = m_document->nativeFormatMimeType();
to = typeName.toLatin1();
}
else {
from = typeName.toLatin1();
to = m_document->nativeFormatMimeType();
}
KIS_ASSERT_RECOVER_RETURN_VALUE(
direction == Import || direction == Export,
KisImportExportErrorCode(ImportExportCodes::InternalError)); // "bad conversion graph"
ConversionResult result = KisImportExportErrorCode(ImportExportCodes::OK);
if (direction == Import) {
KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5")
.arg(QString::fromLatin1(from))
.arg(QString::fromLatin1(to))
.arg(location)
.arg(realLocation)
.arg(batchMode()));
// async importing is not yet supported!
KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync);
// FIXME: Dmitry says "this progress reporting code never worked. Initial idea was to implement it his way, but I stopped and didn't finish it"
if (0 && !batchMode()) {
KisAsyncActionFeedback f(i18n("Opening document..."), 0);
result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter));
} else {
result = doImport(location, filter);
}
if (result.status().isOk()) {
- KisImageSP image = m_document->image();
- KisUsageLogger::log(QString("Loaded image from %1. Size: %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8")
- .arg(QString::fromLatin1(from))
- .arg(image->width())
- .arg(image->height())
- .arg(image->xRes())
- .arg(image->colorSpace()->colorModelId().name())
- .arg(image->colorSpace()->colorDepthId().name())
- .arg(image->colorSpace()->profile()->name())
- .arg(image->nlayers()));
-
+ KisImageSP image = m_document->image().toStrongRef();
+ if (image) {
+ KisUsageLogger::log(QString("Loaded image from %1. Size: %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8")
+ .arg(QString::fromLatin1(from))
+ .arg(image->width())
+ .arg(image->height())
+ .arg(image->xRes())
+ .arg(image->colorSpace()->colorModelId().name())
+ .arg(image->colorSpace()->colorDepthId().name())
+ .arg(image->colorSpace()->profile()->name())
+ .arg(image->nlayers()));
+ }
+ else {
+ qWarning() << "The filter returned OK, but there is no image";
+ }
}
else {
KisUsageLogger::log(QString("Failed to load image from %1").arg(QString::fromLatin1(from)));
}
}
else /* if (direction == Export) */ {
if (!exportConfiguration) {
exportConfiguration = filter->lastSavedConfiguration(from, to);
}
if (exportConfiguration) {
fillStaticExportConfigurationProperties(exportConfiguration);
}
bool alsoAsKra = false;
bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration,
from, to,
batchMode(), showWarnings,
&alsoAsKra);
if (!batchMode() && !askUser) {
return KisImportExportErrorCode(ImportExportCodes::Cancelled);
}
KisUsageLogger::log(QString("Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6")
.arg(QString::fromLatin1(from))
.arg(QString::fromLatin1(to))
.arg(location)
.arg(realLocation)
.arg(batchMode())
.arg(exportConfiguration ? exportConfiguration->toXML() : "none"));
if (isAsync) {
result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
// we should explicitly report that the exporting has been initiated
result.setStatus(ImportExportCodes::OK);
} else if (!batchMode()) {
KisAsyncActionFeedback f(i18n("Saving document..."), 0);
result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
} else {
result = doExport(location, filter, exportConfiguration, alsoAsKra);
}
if (exportConfiguration && !batchMode()) {
KisConfig(false).setExportConfiguration(typeName, exportConfiguration);
}
}
return result;
}
void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image)
{
KisPaintDeviceSP dev = image->projection();
const KoColorSpace* cs = dev->colorSpace();
const bool isThereAlpha =
KisPainter::checkDeviceHasTransparency(image->projection());
exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha);
exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id());
exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id());
const bool sRGB =
(cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) &&
!cs->profile()->name().contains(QLatin1String("g10")));
exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB);
}
void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration)
{
return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image());
}
bool KisImportExportManager::askUserAboutExportConfiguration(
QSharedPointer<KisImportExportFilter> filter,
KisPropertiesConfigurationSP exportConfiguration,
const QByteArray &from,
const QByteArray &to,
const bool batchMode,
const bool showWarnings,
bool *alsoAsKra)
{
// prevents the animation renderer from running this code
const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to);
QStringList warnings;
QStringList errors;
{
KisPreExportChecker checker;
checker.check(m_document->image(), filter->exportChecks());
warnings = checker.warnings();
errors = checker.errors();
}
KisConfigWidget *wdg = 0;
if (QThread::currentThread() == qApp->thread()) {
wdg = filter->createConfigurationWidget(0, from, to);
KisMainWindow *kisMain = KisPart::instance()->currentMainwindow();
if (wdg && kisMain) {
KisViewManager *manager = kisMain->viewManager();
wdg->setView(manager);
}
}
// Extra checks that cannot be done by the checker, because the checker only has access to the image.
if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains <b>assistants</b>. The assistants will not be saved."));
}
if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains <b>reference images</b>. The reference images will not be saved."));
}
if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains <b>guides</b>. The guides will not be saved."));
}
if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains a <b>custom grid configuration</b>. The configuration will not be saved."));
}
if (!batchMode && !errors.isEmpty()) {
QString error = "<html><body><p><b>"
+ i18n("Error: cannot save this image as a %1.", mimeUserDescription)
+ "</b> " + i18n("Reasons:") + "</p>"
+ "<p/><ul>";
Q_FOREACH(const QString &w, errors) {
error += "\n<li>" + w + "</li>";
}
error += "</ul>";
QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error);
return false;
}
if (!batchMode && (wdg || !warnings.isEmpty())) {
KoDialog dlg;
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
dlg.setWindowTitle(mimeUserDescription);
QWidget *page = new QWidget(&dlg);
QVBoxLayout *layout = new QVBoxLayout(page);
if (showWarnings && !warnings.isEmpty()) {
QHBoxLayout *hLayout = new QHBoxLayout();
QLabel *labelWarning = new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
hLayout->addWidget(labelWarning);
KisPopupButton *bn = new KisPopupButton(0);
bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription));
hLayout->addWidget(bn);
layout->addLayout(hLayout);
QTextBrowser *browser = new QTextBrowser();
browser->setMinimumWidth(bn->width());
bn->setPopupWidget(browser);
QString warning = "<html><body><p><b>"
+ i18n("You will lose information when saving this image as a %1.", mimeUserDescription);
if (warnings.size() == 1) {
warning += "</b> " + i18n("Reason:") + "</p>";
}
else {
warning += "</b> " + i18n("Reasons:") + "</p>";
}
warning += "<p/><ul>";
Q_FOREACH(const QString &w, warnings) {
warning += "\n<li>" + w + "</li>";
}
warning += "</ul>";
browser->setHtml(warning);
}
if (wdg) {
QGroupBox *box = new QGroupBox(i18n("Options"));
QVBoxLayout *boxLayout = new QVBoxLayout(box);
wdg->setConfiguration(exportConfiguration);
boxLayout->addWidget(wdg);
layout->addWidget(box);
}
QCheckBox *chkAlsoAsKra = 0;
if (showWarnings && !warnings.isEmpty()) {
chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file."));
chkAlsoAsKra->setChecked(KisConfig(true).readEntry<bool>("AlsoSaveAsKra", false));
layout->addWidget(chkAlsoAsKra);
}
dlg.setMainWidget(page);
dlg.resize(dlg.minimumSize());
if (showWarnings || wdg) {
if (!dlg.exec()) {
return false;
}
}
*alsoAsKra = false;
if (chkAlsoAsKra) {
KisConfig(false).writeEntry<bool>("AlsoSaveAsKra", chkAlsoAsKra->isChecked());
*alsoAsKra = chkAlsoAsKra->isChecked();
}
if (wdg) {
*exportConfiguration = *wdg->configuration();
}
}
return true;
}
KisImportExportErrorCode KisImportExportManager::doImport(const QString &location, QSharedPointer<KisImportExportFilter> filter)
{
QFile file(location);
if (!file.exists()) {
return ImportExportCodes::FileNotExist;
}
if (filter->supportsIO() && !file.open(QFile::ReadOnly)) {
return KisImportExportErrorCode(KisImportExportErrorCannotRead(file.error()));
}
KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP());
if (file.isOpen()) {
file.close();
}
return status;
}
KisImportExportErrorCode KisImportExportManager::doExport(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra)
{
KisImportExportErrorCode status =
doExportImpl(location, filter, exportConfiguration);
if (alsoAsKra && status.isOk()) {
QString kraLocation = location + ".kra";
QByteArray mime = m_document->nativeFormatMimeType();
QSharedPointer<KisImportExportFilter> filter(
filterForMimeType(QString::fromLatin1(mime), Export));
KIS_SAFE_ASSERT_RECOVER_NOOP(filter);
if (filter) {
filter->setFilename(kraLocation);
KisPropertiesConfigurationSP kraExportConfiguration =
filter->lastSavedConfiguration(mime, mime);
status = doExportImpl(kraLocation, filter, kraExportConfiguration);
} else {
status = ImportExportCodes::FileFormatIncorrect;
}
}
return status;
}
// Temporary workaround until QTBUG-57299 is fixed.
// 02-10-2019 update: the bug is closed, but we've still seen this issue.
// and without using QSaveFile the issue can still occur
// when QFile::copy fails because Dropbox/Google/OneDrive
// locks the target file.
#ifndef Q_OS_WIN
#define USE_QSAVEFILE
#endif
KisImportExportErrorCode KisImportExportManager::doExportImpl(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration)
{
#ifdef USE_QSAVEFILE
QSaveFile file(location);
file.setDirectWriteFallback(true);
if (filter->supportsIO() && !file.open(QFile::WriteOnly)) {
#else
QFileInfo fi(location);
QTemporaryFile file(QDir::tempPath() + "/.XXXXXX.kra");
if (filter->supportsIO() && !file.open()) {
#endif
KisImportExportErrorCannotWrite result(file.error());
#ifdef USE_QSAVEFILE
file.cancelWriting();
#endif
return result;
}
KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration);
if (filter->supportsIO()) {
if (!status.isOk()) {
#ifdef USE_QSAVEFILE
file.cancelWriting();
#endif
} else {
#ifdef USE_QSAVEFILE
if (!file.commit()) {
qWarning() << "Could not commit QSaveFile";
status = KisImportExportErrorCannotWrite(file.error());
}
#else
file.flush();
file.close();
QFile target(location);
if (target.exists()) {
// There should already be a .kra~ backup
target.remove();
}
if (!file.copy(location)) {
file.setAutoRemove(false);
return KisImportExportErrorCannotWrite(file.error());
}
#endif
}
}
// Do some minimal verification
QString verificationResult = filter->verify(location);
if (!verificationResult.isEmpty()) {
status = KisImportExportErrorCode(ImportExportCodes::ErrorWhileWriting);
m_document->setErrorMessage(verificationResult);
}
return status;
}
#include <KisMimeDatabase.h>
diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp
index d4f1425b8a..fbd505259b 100644
--- a/libs/ui/KisMainWindow.cpp
+++ b/libs/ui/KisMainWindow.cpp
@@ -1,2766 +1,2777 @@
/* 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 <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 <KisSignalMapper.h>
#include <QTabBar>
#include <QMoveEvent>
#include <QUrl>
#include <QMessageBox>
#include <QStatusBar>
#include <QMenu>
#include <QMenuBar>
#include <KisMimeDatabase.h>
#include <QMimeData>
#include <QStackedWidget>
#include <QProxyStyle>
#include <QScreen>
#include <QAction>
#include <QWindow>
#include <QScrollArea>
#include <kactioncollection.h>
#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>
#include "kis_selection_manager.h"
#include "kis_icon_utils.h"
#include <krecentfilesaction.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 <kformat.h>
#include <KoResourcePaths.h>
#include <KoToolFactoryBase.h>
#include <KoToolRegistry.h>
#include "KoDockFactoryBase.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 "KoToolBoxDocker_p.h"
#include <KoToolBoxFactory.h>
#include <KoDockRegistry.h>
#include <KoPluginLoader.h>
#include <KoColorSpaceEngine.h>
#include <KoUpdater.h>
#include <KoResourceModel.h>
#ifdef Q_OS_ANDROID
#include <KisAndroidFileManager.h>
#endif
+#include <KisUsageLogger.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_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 "kis_group_layer.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 "KisResourceServerProvider.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 <KisImageConfigNotifier.h>
#include "KisWindowLayoutManager.h"
#include <KisUndoActionsUpdateManager.h>
#include "KisWelcomePageWidget.h"
#include <KritaVersionWrapper.h>
#include <kritaversion.h>
#include "KisCanvasWindow.h"
#include "kis_action.h"
#include <mutex>
class ToolDockerFactory : public KoDockFactoryBase
{
public:
ToolDockerFactory() : KoDockFactoryBase() { }
QString id() const override {
return "sharedtooldocker";
}
QDockWidget* createDockWidget() override {
KoToolDocker* dockWidget = new KoToolDocker();
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
class Q_DECL_HIDDEN KisMainWindow::Private
{
public:
Private(KisMainWindow *parent, QUuid id)
: q(parent)
, id(id)
, 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))
, welcomePage(new KisWelcomePageWidget(parent))
, widgetStack(new QStackedWidget(parent))
, mdiArea(new QMdiArea(parent))
, windowMapper(new KisSignalMapper(parent))
, documentMapper(new KisSignalMapper(parent))
#ifdef Q_OS_ANDROID
, fileManager(new KisAndroidFileManager(parent))
#endif
{
if (id.isNull()) this->id = QUuid::createUuid();
welcomeScroller = new QScrollArea();
welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
welcomeScroller->setWidget(welcomePage);
welcomeScroller->setWidgetResizable(true);
widgetStack->addWidget(welcomeScroller);
widgetStack->addWidget(mdiArea);
mdiArea->setTabsMovable(true);
mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder);
}
~Private() {
qDeleteAll(toolbarList);
}
KisMainWindow *q {0};
QUuid id;
KisViewManager *viewManager {0};
QPointer<KisView> activeView;
QList<QAction *> toolbarList;
bool firstTime {true};
bool windowSizeDirty {false};
bool readOnly {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 *toggleDetachCanvas {0};
KisAction *fullScreenMode {0};
KisAction *showSessionManager {0};
KisAction *expandingSpacers[2];
KActionMenu *dockWidgetMenu;
KActionMenu *windowMenu;
KActionMenu *documentMenu;
KActionMenu *workspaceMenu;
KHelpMenu *helpMenu {0};
KRecentFilesAction *recentFiles {0};
KoResourceModel *workspacemodel {0};
QScopedPointer<KisUndoActionsUpdateManager> undoActionsUpdateManager;
QString lastExportLocation;
QMap<QString, QDockWidget *> dockWidgetsMap;
QByteArray dockerStateBeforeHiding;
KoToolDocker *toolOptionsDocker {0};
QCloseEvent *deferredClosingEvent {0};
Digikam::ThemeManager *themeManager {0};
QScrollArea *welcomeScroller {0};
KisWelcomePageWidget *welcomePage {0};
QStackedWidget *widgetStack {0};
QMdiArea *mdiArea;
QMdiSubWindow *activeSubWindow {0};
KisSignalMapper *windowMapper;
KisSignalMapper *documentMapper;
KisCanvasWindow *canvasWindow {0};
QByteArray lastExportedFormat;
QScopedPointer<KisSignalCompressorWithParam<int> > tabSwitchCompressor;
QMutex savingEntryMutex;
KConfigGroup windowStateConfig;
QUuid workspaceBorrowedBy;
KisSignalAutoConnectionsStore screenConnectionsStore;
#ifdef Q_OS_ANDROID
KisAndroidFileManager *fileManager;
#endif
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(QUuid uuid)
: KXmlGuiWindow()
, d(new Private(this, uuid))
{
auto rserver = KisResourceServerProvider::instance()->workspaceServer();
QSharedPointer<KoAbstractResourceServerAdapter> adapter(new KoResourceServerAdapter<KisWorkspaceResource>(rserver));
d->workspacemodel = new KoResourceModel(adapter, this);
connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); });
d->viewManager = new KisViewManager(this, actionCollection());
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this);
d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow");
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_MACOS
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, false);
// Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created.
KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true);
KoToolBoxFactory toolBoxFactory;
QDockWidget *toolbox = createDockWidget(&toolBoxFactory);
toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
KisConfig cfg(true);
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->setViewManager(d->viewManager);
}
}
// Load all the actions from the tool plugins
Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) {
toolFactory->createActions(actionCollection());
}
d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setTabPosition(QTabWidget::North);
d->mdiArea->setTabsClosable(true);
// Tab close button override
// Windows just has a black X, and Ubuntu has a dark x that is hard to read
// just switch this icon out for all OSs so it is easier to see
d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }");
setCentralWidget(d->widgetStack);
d->widgetStack->setCurrentIndex(0);
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*)));
d->canvasWindow = new KisCanvasWindow(this);
actionCollection()->addAssociatedWidget(d->canvasWindow);
createActions();
// the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist
d->welcomePage->setMainWindow(this);
setAutoSaveSettings(d->windowStateConfig, 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);
connect(guiFactory(), SIGNAL(makingChanges(bool)), SLOT(slotXmlGuiMakingChanges(bool)));
// Create and plug toolbar list for Settings menu
QList<QAction *> toolbarList;
Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) {
KToolBar * toolBar = ::qobject_cast<KToolBar *>(it);
toolBar->setMovable(KisConfig(true).readEntry<bool>("LockAllDockerPanels", false));
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!";
}
}
KToolBar::setToolBarsLocked(KisConfig(true).readEntry<bool>("LockAllDockerPanels", false));
plugActionList("toolbarlist", toolbarList);
d->toolbarList = toolbarList;
applyToolBarLayout();
d->viewManager->updateGUI();
d->viewManager->updateIcons();
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));
}
if (cfg.readEntry("CanvasOnlyActive", false)) {
QString currentWorkspace = cfg.readEntry<QString>("CurrentWorkspace", "Default");
KoResourceServer<KisWorkspaceResource> * rserver = KisResourceServerProvider::instance()->workspaceServer();
KisWorkspaceResource* workspace = rserver->resourceByName(currentWorkspace);
if (workspace) {
restoreWorkspace(workspace);
}
cfg.writeEntry("CanvasOnlyActive", false);
menuBar()->setVisible(true);
}
this->winId(); // Ensures the native window has been created.
QWindow *window = this->windowHandle();
connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *)));
}
KisMainWindow::~KisMainWindow()
{
// Q_FOREACH (QAction *ac, actionCollection()->actions()) {
// QAction *action = qobject_cast<QAction*>(ac);
// if (action) {
// qDebug() << "<Action"
// << "\n\tname=" << action->objectName()
// << "\n\ticon=" << action->icon().name()
// << "\n\ttext=" << action->text().replace("&", "&amp;")
// << "\n\twhatsThis=" << action->whatsThis()
// << "\n\ttoolTip=" << action->toolTip().replace("<html>", "").replace("</html>", "")
// << "\n\ticonText=" << action->iconText().replace("&", "&amp;")
// << "\n\tshortcut=" << action->shortcut().toString()
// << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false"))
// << "\n\tstatusTip=" << action->statusTip()
// << "\n/>\n" ;
// }
// else {
// dbgKrita << "Got a non-qaction:" << ac->objectName();
// }
// }
// The doc and view might still exist (this is the case when closing the window)
KisPart::instance()->removeMainWindow(this);
delete d->viewManager;
delete d;
}
QUuid KisMainWindow::id() const {
return d->id;
}
void KisMainWindow::addView(KisView *view, QMdiSubWindow *subWindow)
{
if (d->activeView == view && !subWindow) return;
if (d->activeView) {
d->activeView->disconnect(this);
}
// register the newly created view in the input manager
viewManager()->inputManager()->addTrackedCanvas(view->canvasBase());
showView(view, subWindow);
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)
{
/**
* If we are the last view of the window, Qt will not activate another tab
* before destroying tab/window. In this case we should clear all the dangling
* pointers manually by setting the current view to null
*/
viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase());
if (view->canvasBase() == viewManager()->canvasBase()) {
viewManager()->setCurrentView(0);
}
}
void KisMainWindow::showView(KisView *imageView, QMdiSubWindow *subwin)
{
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();
if (!subwin) {
subwin = d->mdiArea->addSubWindow(imageView);
} else {
subwin->setWidget(imageView);
}
imageView->setSubWindow(subwin);
subwin->setAttribute(Qt::WA_DeleteOnClose, true);
connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu()));
KisConfig cfg(true);
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());
+ if (d->mdiArea->subWindowList().size() == 1) {
+ imageView->showMaximized();
+ }
+ else {
+ imageView->show();
+ }
+
/**
* 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()
{
QScopedPointer<KisDlgPreferences> dlgPreferences(new KisDlgPreferences(this));
if (!dlgPreferences->editPreferences()) {
KisConfigNotifier::instance()->notifyConfigChanged();
KisConfigNotifier::instance()->notifyPixelGridModeChanged();
KisImageConfigNotifier::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();
}
}
updateWindowMenu();
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);
}
if (d->mdiArea) {
d->mdiArea->setPalette(qApp->palette());
for (int i=0; i<d->mdiArea->subWindowList().size(); i++) {
QMdiSubWindow *window = d->mdiArea->subWindowList().at(i);
if (window) {
window->setPalette(qApp->palette());
KisView *view = qobject_cast<KisView*>(window->widget());
if (view) {
view->slotThemeChanged(qApp->palette());
}
}
}
}
emit themeChanged();
}
bool KisMainWindow::canvasDetached() const
{
return centralWidget() != d->widgetStack;
}
void KisMainWindow::setCanvasDetached(bool detach)
{
if (detach == canvasDetached()) return;
QWidget *outgoingWidget = centralWidget() ? takeCentralWidget() : nullptr;
QWidget *incomingWidget = d->canvasWindow->swapMainWidget(outgoingWidget);
if (incomingWidget) {
setCentralWidget(incomingWidget);
}
if (detach) {
d->canvasWindow->show();
} else {
d->canvasWindow->hide();
}
}
void KisMainWindow::slotFileSelected(QString path)
{
QString url = path;
if (!url.isEmpty()) {
bool res = openDocument(QUrl::fromLocalFile(url), Import);
if (!res) {
warnKrita << "Loading" << url << "failed";
}
}
}
void KisMainWindow::slotEmptyFilePath()
{
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The chosen file's location could not be found. Does it exist?"));
}
QWidget * KisMainWindow::canvasWindow() const
{
return d->canvasWindow;
}
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)
{
// 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
}
}
const QStringList templateDirs = KoResourcePaths::findDirs("templates");
for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) {
if (path.contains(*it)) {
ok = false; // it's in the templates directory.
break;
}
}
}
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 (KisMainWindow *mw, KisPart::instance()->mainWindows()) {
if (mw != this) {
mw->reloadRecentFileList();
}
}
}
QList<QUrl> KisMainWindow::recentFilesUrls()
{
return d->recentFiles->urls();
}
void KisMainWindow::clearRecentFiles()
{
d->recentFiles->clear();
d->welcomePage->populateRecentDocuments();
}
void KisMainWindow::removeRecentUrl(const QUrl &url)
{
d->recentFiles->removeUrl(url);
KSharedConfigPtr config = KSharedConfig::openConfig();
d->recentFiles->saveEntries(config->group("RecentFiles"));
config->sync();
}
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() && d->activeView->image()){
KisDocument *doc = d->activeView->document();
QString caption(doc->caption());
if (d->readOnly) {
caption += " [" + i18n("Write Protected") + "] ";
}
if (doc->isRecovered()) {
caption += " [" + i18n("Recovered") + "] ";
}
// 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( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")");
}
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 modified)
{
QString versionString = KritaVersionWrapper::versionString(true);
QString title = caption;
if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works
title.append(QStringLiteral(" [*]"));
}
if (d->mdiArea->activeSubWindow()) {
#if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC)
d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title));
#else
d->mdiArea->activeSubWindow()->setWindowTitle(title);
#endif
d->mdiArea->activeSubWindow()->setWindowModified(modified);
}
else {
#if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC)
setWindowTitle(QString("%1: %2").arg(versionString).arg(title));
#else
setWindowTitle(title);
#endif
}
setWindowModified(modified);
}
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(QString)), this, SLOT(slotLoadCanceled(QString)));
KisDocument::OpenFlags openFlags = KisDocument::None;
+ // XXX: Why this duplication of of OpenFlags...
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);
}
+
+ if (flags & RecoveryFile &&
+ ( url.toLocalFile().startsWith(QDir::tempPath())
+ || url.toLocalFile().startsWith(QDir::homePath()))
+ ) {
+ newdoc->setUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + "/" + QFileInfo(url.toLocalFile()).fileName()));
+ newdoc->save(false, 0);
+ }
+
return true;
}
void KisMainWindow::showDocument(KisDocument *document) {
Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) {
KisView *view = qobject_cast<KisView*>(subwindow->widget());
KIS_SAFE_ASSERT_RECOVER_NOOP(view);
if (view) {
if (view->document() == document) {
setActiveSubWindow(subwindow);
return;
}
}
}
addViewAndNotifyLoadingCompleted(document);
}
KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document,
QMdiSubWindow *subWindow)
{
showWelcomeScreen(false); // see workaround in function header
KisView *view = KisPart::instance()->createView(document, d->viewManager, this);
addView(view, subWindow);
emit guiLoadingFinished();
return view;
}
QStringList KisMainWindow::showOpenFileDialog(bool isImporting)
{
KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument");
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(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(QString)), this, SLOT(slotLoadCanceled(QString)));
emit loadCompleted();
}
}
void KisMainWindow::slotLoadCanceled(const QString & errMsg)
{
- dbgUI << "KisMainWindow::slotLoadCanceled";
+ KisUsageLogger::log(QString("Loading canceled. Error:").arg(errMsg));
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(QString)), this, SLOT(slotLoadCanceled(QString)));
}
void KisMainWindow::slotSaveCanceled(const QString &errMsg)
{
- dbgUI << "KisMainWindow::slotSaveCanceled";
- if (!errMsg.isEmpty()) // empty when canceled by user
+ KisUsageLogger::log(QString("Saving canceled. Error:").arg(errMsg));
+ if (!errMsg.isEmpty()) { // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
+ }
slotSaveCompleted();
}
void KisMainWindow::slotSaveCompleted()
{
- dbgUI << "KisMainWindow::slotSaveCompleted";
+ KisUsageLogger::log(QString("Saving Completed"));
KisDocument* doc = qobject_cast<KisDocument*>(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(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::installBundle(const QString &fileName) const
{
QFileInfo from(fileName);
QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName());
if (to.exists()) {
QFile::remove(to.canonicalFilePath());
}
return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName());
}
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;
}
if (document->url().isEmpty()) {
saveas = true;
}
connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString)));
QByteArray nativeFormat = document->nativeFormatMimeType();
QByteArray oldMimeFormat = document->mimeType();
QUrl suggestedURL = document->url();
QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
mimeFilter = KisImportExportManager::supportedMimeTypes(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()).completeBaseName();
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() && !d->lastExportLocation.contains(QDir::tempPath())) {
// 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()).completeBaseName();
// 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 = 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.completeBaseName());
}
QByteArray outputFormat = nativeFormat;
QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false);
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()).completeBaseName();
justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path)
&& (QFileInfo(newURL.toLocalFile()).completeBaseName() == 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!";
}
}
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!";
}
}
updateReloadFileAction(document);
updateCaption();
return ret;
}
void KisMainWindow::undo()
{
if (activeView()) {
activeView()->document()->undoStack()->undo();
}
}
void KisMainWindow::redo()
{
if (activeView()) {
activeView()->document()->undoStack()->redo();
}
}
void KisMainWindow::closeEvent(QCloseEvent *e)
{
if (hackIsSaving()) {
e->setAccepted(false);
return;
}
if (!KisPart::instance()->closingSession()) {
QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only");
if ((action) && (action->isChecked())) {
action->setChecked(false);
}
// Save session when last window is closed
if (KisPart::instance()->mainwindowCount() == 1) {
bool closeAllowed = KisPart::instance()->closeSession();
if (!closeAllowed) {
e->setAccepted(false);
return;
}
}
}
d->mdiArea->closeAllSubWindows();
QList<QMdiSubWindow*> childrenList = d->mdiArea->subWindowList();
if (childrenList.isEmpty()) {
d->deferredClosingEvent = e;
saveWindowState(true);
d->canvasWindow->close();
} else {
e->setAccepted(false);
}
}
void KisMainWindow::saveWindowSettings()
{
KSharedConfigPtr config = KSharedConfig::openConfig();
if (d->windowSizeDirty ) {
dbgUI << "KisMainWindow::saveWindowSettings";
KConfigGroup group = d->windowStateConfig;
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 = d->windowStateConfig;
saveMainWindowSettings(group);
// Save collapsible 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();
if (d->undoActionsUpdateManager) {
d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0);
}
d->viewManager->setCurrentView(view);
KisWindowLayoutManager::instance()->activeDocumentChanged(view->document());
}
void KisMainWindow::dragMove(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::dragLeave()
{
if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
void KisMainWindow::switchTab(int index)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar) return;
tabBar->setCurrentIndex(index);
}
void KisMainWindow::showWelcomeScreen(bool show)
{
d->widgetStack->setCurrentIndex(!show);
}
void KisMainWindow::slotFileNew()
{
const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import);
KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/"));
startupWidget->setWindowModality(Qt::WindowModal);
startupWidget->setWindowTitle(i18n("Create new document"));
KisConfig cfg(true);
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";
item.title = i18n("Custom Document");
startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", 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, "Create from ClipBoard", item.icon);
// calls deleteLater
connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*)));
// calls deleteLater
connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl)));
startupWidget->exec();
// Cancel calls deleteLater...
}
void KisMainWindow::slotImportFile()
{
dbgUI << "slotImportFile()";
slotFileOpen(true);
}
void KisMainWindow::slotFileOpen(bool isImporting)
{
#ifndef Q_OS_ANDROID
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";
}
}
}
#else
Q_UNUSED(isImporting)
d->fileManager->openImportFile();
connect(d->fileManager, SIGNAL(sigFileSelected(QString)), this, SLOT(slotFileSelected(QString)));
connect(d->fileManager, SIGNAL(sigEmptyFilePath()), this, SLOT(slotEmptyFilePath()));
#endif
}
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();
}
}
void KisMainWindow::slotShowSessionManager() {
KisPart::instance()->showSessionManager();
}
KoCanvasResourceProvider *KisMainWindow::resourceManager() const
{
return d->viewManager->canvasResourceProvider()->resourceManager();
}
int KisMainWindow::viewCount() const
{
return d->mdiArea->subWindowList().size();
}
const KConfigGroup &KisMainWindow::windowStateConfig() const
{
return d->windowStateConfig;
}
void KisMainWindow::saveWindowState(bool restoreNormalState)
{
if (restoreNormalState) {
QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only");
if (showCanvasOnly && showCanvasOnly->isChecked()) {
showCanvasOnly->setChecked(false);
}
d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64());
d->windowStateConfig.writeEntry("State", saveState().toBase64());
if (!d->dockerStateBeforeHiding.isEmpty()) {
restoreState(d->dockerStateBeforeHiding);
}
statusBar()->setVisible(true);
menuBar()->setVisible(true);
saveWindowSettings();
} else {
saveMainWindowSettings(d->windowStateConfig);
}
}
bool KisMainWindow::restoreWorkspaceState(const QByteArray &state)
{
QByteArray oldState = saveState();
// needed because otherwise the layout isn't correctly restored in some situations
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
dock->toggleViewAction()->setEnabled(true);
dock->hide();
}
bool success = KXmlGuiWindow::restoreState(state);
if (!success) {
KXmlGuiWindow::restoreState(oldState);
return false;
}
return success;
}
bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace)
{
bool success = restoreWorkspaceState(workspace->dockerState());
if (activeKisView()) {
activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace);
}
return success;
}
QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other)
{
QByteArray currentWorkspace = saveState();
if (!d->workspaceBorrowedBy.isNull()) {
if (other->id() == d->workspaceBorrowedBy) {
// We're swapping our original workspace back
d->workspaceBorrowedBy = QUuid();
return currentWorkspace;
} else {
// Get our original workspace back before swapping with a third window
KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy);
if (borrower) {
QByteArray originalLayout = borrower->borrowWorkspace(this);
borrower->restoreWorkspaceState(currentWorkspace);
d->workspaceBorrowedBy = other->id();
return originalLayout;
}
}
}
d->workspaceBorrowedBy = other->id();
return currentWorkspace;
}
void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b)
{
QByteArray workspaceA = a->borrowWorkspace(b);
QByteArray workspaceB = b->borrowWorkspace(a);
a->restoreWorkspaceState(workspaceB);
b->restoreWorkspaceState(workspaceA);
}
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()
{
// Do not close while KisMainWindow has the savingEntryMutex locked, bug409395.
// After the background saving job is initiated, KisDocument blocks closing
// while it saves itself.
if (hackIsSaving()) {
return;
}
KisPart::instance()->closeSession();
}
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 = 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);
KisImportExportErrorCode status = importer.import(files, firstFrame, step);
if (!status.isOk() && !status.isInternalError()) {
QString msg = status.errorMessage();
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()
{
saveWindowState();
KEditToolBar edit(factory(), this);
connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig()));
(void) edit.exec();
applyToolBarLayout();
}
void KisMainWindow::slotNewToolbarConfig()
{
applyMainWindowSettings(d->windowStateConfig);
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()) {
saveWindowState();
}
} else
warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!";
}
void KisMainWindow::viewFullscreen(bool fullScreen)
{
KisConfig cfg(false);
cfg.setFullscreenMode(fullScreen);
if (fullScreen) {
setWindowState(windowState() | Qt::WindowFullScreen); // set
} else {
setWindowState(windowState() & ~Qt::WindowFullScreen); // reset
}
d->fullScreenMode->setChecked(isFullScreen());
}
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;
bool lockAllDockers = KisConfig(true).readEntry<bool>("LockAllDockerPanels", false);
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;
}
dockWidget->setFont(KoDockRegistry::dockFont());
dockWidget->setObjectName(factory->id());
dockWidget->setParent(this);
if (lockAllDockers) {
if (dockWidget->titleBarWidget()) {
dockWidget->titleBarWidget()->setVisible(false);
}
dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures);
}
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 = d->windowStateConfig.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();
}
d->dockWidgetsMap.insert(factory->id(), dockWidget);
}
else {
dockWidget = d->dockWidgetsMap[factory->id()];
}
#ifdef Q_OS_MACOS
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);
}
}
/**
* Qt has a weirdness, it has hardcoded shortcuts added to an action
* in the window menu. We need to reset the shortcuts for that menu
* to nothing, otherwise the shortcuts cannot be made configurable.
*
* See: https://bugs.kde.org/show_bug.cgi?id=352205
* https://bugs.kde.org/show_bug.cgi?id=375524
* https://bugs.kde.org/show_bug.cgi?id=398729
*/
QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow();
if (subWindow) {
QMenu *menu = subWindow->systemMenu();
if (menu && menu->actions().size() == 8) {
Q_FOREACH (QAction *action, menu->actions()) {
action->setShortcut(QKeySequence());
}
menu->actions().last()->deleteLater();
}
}
updateCaption();
d->actionManager()->updateGUI();
}
void KisMainWindow::windowFocused()
{
/**
* Notify selection manager so that it could update selection mask overlay
*/
if (viewManager() && viewManager()->selectionManager()) {
viewManager()->selectionManager()->selectionChanged();
}
KisPart *kisPart = KisPart::instance();
KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance();
if (!layoutManager->primaryWorkspaceFollowsFocus()) return;
QUuid primary = layoutManager->primaryWindowId();
if (primary.isNull()) return;
if (d->id == primary) {
if (!d->workspaceBorrowedBy.isNull()) {
KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy);
if (!borrower) return;
swapWorkspaces(this, borrower);
}
} else {
if (d->workspaceBorrowedBy == primary) return;
KisMainWindow *primaryWindow = kisPart->windowById(primary);
if (!primaryWindow) return;
swapWorkspaces(this, primaryWindow);
}
}
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();
QFontMetrics fontMetrics = docMenu->fontMetrics();
int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f);
Q_FOREACH (QPointer<KisDocument> doc, KisPart::instance()->documents()) {
if (doc) {
QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth);
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()->resources();
auto m_this = this;
for (auto &w : workspaces) {
auto action = workspaceMenu->addAction(w->name());
connect(action, &QAction::triggered, this, [=]() {
m_this->restoreWorkspace(w);
});
}
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->canvasResourceProvider()->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, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth));
}
else {
text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth));
}
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));
}
}
bool showMdiArea = windows.count( ) > 0;
if (!showMdiArea) {
showWelcomeScreen(true); // see workaround in function in header
// keep the recent file list updated when going back to welcome screen
reloadRecentFileList();
d->welcomePage->populateRecentDocuments();
}
// enable/disable the toolbox docker if there are no documents open
Q_FOREACH (QObject* widget, children()) {
if (widget->inherits("QDockWidget")) {
QDockWidget* dw = static_cast<QDockWidget*>(widget);
if ( dw->objectName() == "ToolBox") {
dw->setEnabled(showMdiArea);
}
}
}
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(true);
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 continue 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();
QString s = cfg.getMDIBackgroundColor();
KoColor c = KoColor::fromXML(s);
QBrush brush(c.toQColor());
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, QMdiSubWindow *subWindow)
{
KisDocument *doc = qobject_cast<KisDocument*>(document);
KisView *view = addViewAndNotifyLoadingCompleted(doc, subWindow);
d->actionManager()->updateGUI();
return view;
}
void KisMainWindow::newWindow()
{
KisMainWindow *mainWindow = KisPart::instance()->createMainWindow();
mainWindow->initializeGeometry();
mainWindow->show();
}
void KisMainWindow::closeCurrentWindow()
{
if (d->mdiArea->currentSubWindow()) {
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_MACOS
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.completeBaseName();
}
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();
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()));
d->fullScreenMode = 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->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo));
d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0);
// 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()));
connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors()));
d->toggleDockers = actionManager->createAction("view_toggledockers");
KisConfig(true).showDockers(true);
d->toggleDockers->setChecked(true);
connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool)));
d->toggleDetachCanvas = actionManager->createAction("view_detached_canvas");
d->toggleDetachCanvas->setChecked(false);
connect(d->toggleDetachCanvas, SIGNAL(toggled(bool)), SLOT(setCanvasDetached(bool)));
setCanvasDetached(false);
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->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow()));
d->showSessionManager = actionManager->createAction("file_sessions");
connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager()));
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 = d->windowStateConfig;
QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray()));
if (!restoreGeometry(geom)) {
const int scnum = QApplication::desktop()->screenNumber(parentWidget());
QRect desk = QGuiApplication::screens().at(scnum)->availableVirtualGeometry();
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 compensate 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);
}
d->fullScreenMode->setChecked(isFullScreen());
}
void KisMainWindow::showManual()
{
QDesktopServices::openUrl(QUrl("https://docs.krita.org"));
}
void KisMainWindow::windowScreenChanged(QScreen *screen)
{
emit screenChanged();
d->screenConnectionsStore.clear();
d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)),
this, SIGNAL(screenChanged()));
}
void KisMainWindow::slotXmlGuiMakingChanges(bool finished)
{
if (finished) {
subWindowActivated();
}
}
#include <moc_KisMainWindow.cpp>
diff --git a/libs/ui/KisReferenceImage.cpp b/libs/ui/KisReferenceImage.cpp
index 1ec55df539..b1c0a774bc 100644
--- a/libs/ui/KisReferenceImage.cpp
+++ b/libs/ui/KisReferenceImage.cpp
@@ -1,361 +1,370 @@
/*
* 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.
*/
#include "KisReferenceImage.h"
#include <QImage>
#include <QMessageBox>
#include <QPainter>
#include <QApplication>
#include <QClipboard>
#include <QSharedData>
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
+#include <QColorSpace>
+#endif
#include <kundo2command.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoTosContainer_p.h>
#include <krita_utils.h>
#include <kis_coordinates_converter.h>
#include <kis_dom_utils.h>
#include <SvgUtil.h>
#include <libs/flake/svg/parsers/SvgTransformParser.h>
#include <libs/brush/kis_qimage_pyramid.h>
#include <utils/KisClipboardUtil.h>
struct KisReferenceImage::Private : public QSharedData
{
// Filename within .kra (for embedding)
QString internalFilename;
// File on disk (for linking)
QString externalFilename;
QImage image;
QImage cachedImage;
KisQImagePyramid mipmap;
qreal saturation{1.0};
int id{-1};
bool embed{true};
bool loadFromFile() {
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!externalFilename.isEmpty(), false);
- return image.load(externalFilename);
+ bool r = image.load(externalFilename);
+ // See https://bugs.kde.org/show_bug.cgi?id=416515 -- a jpeg image
+ // loaded into a qimage cannot be saved to png unless we explicitly
+ // convert the colorspace of the QImage
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
+ image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
+#endif
+ return r;
}
bool loadFromClipboard() {
image = KisClipboardUtil::getImageFromClipboard();
return !image.isNull();
}
void updateCache() {
if (saturation < 1.0) {
cachedImage = KritaUtils::convertQImageToGrayA(image);
if (saturation > 0.0) {
QPainter gc2(&cachedImage);
gc2.setOpacity(saturation);
gc2.drawImage(QPoint(), image);
}
} else {
cachedImage = image;
}
mipmap = KisQImagePyramid(cachedImage);
}
};
KisReferenceImage::SetSaturationCommand::SetSaturationCommand(const QList<KoShape *> &shapes, qreal newSaturation, KUndo2Command *parent)
: KUndo2Command(kundo2_i18n("Set saturation"), parent)
, newSaturation(newSaturation)
{
images.reserve(shapes.count());
Q_FOREACH(auto *shape, shapes) {
auto *reference = dynamic_cast<KisReferenceImage*>(shape);
KIS_SAFE_ASSERT_RECOVER_BREAK(reference);
images.append(reference);
}
Q_FOREACH(auto *image, images) {
oldSaturations.append(image->saturation());
}
}
void KisReferenceImage::SetSaturationCommand::undo()
{
auto saturationIterator = oldSaturations.begin();
Q_FOREACH(auto *image, images) {
image->setSaturation(*saturationIterator);
image->update();
saturationIterator++;
}
}
void KisReferenceImage::SetSaturationCommand::redo()
{
Q_FOREACH(auto *image, images) {
image->setSaturation(newSaturation);
image->update();
}
}
KisReferenceImage::KisReferenceImage()
: d(new Private())
{
setKeepAspectRatio(true);
}
KisReferenceImage::KisReferenceImage(const KisReferenceImage &rhs)
: KoTosContainer(rhs)
, d(rhs.d)
{}
KisReferenceImage::~KisReferenceImage()
{}
KisReferenceImage * KisReferenceImage::fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent)
{
KisReferenceImage *reference = new KisReferenceImage();
reference->d->externalFilename = filename;
bool ok = reference->d->loadFromFile();
if (ok) {
QRect r = QRect(QPoint(), reference->d->image.size());
QSizeF shapeSize = converter.imageToDocument(r).size();
reference->setSize(shapeSize);
} else {
delete reference;
if (parent) {
QMessageBox::critical(parent, i18nc("@title:window", "Krita"), i18n("Could not load %1.", filename));
}
return nullptr;
}
return reference;
}
KisReferenceImage *KisReferenceImage::fromClipboard(const KisCoordinatesConverter &converter)
{
KisReferenceImage *reference = new KisReferenceImage();
bool ok = reference->d->loadFromClipboard();
if (ok) {
QRect r = QRect(QPoint(), reference->d->image.size());
QSizeF size = converter.imageToDocument(r).size();
reference->setSize(size);
} else {
delete reference;
reference = nullptr;
}
return reference;
}
-void KisReferenceImage::paint(QPainter &gc, const KoViewConverter &converter, KoShapePaintingContext &/*paintcontext*/)
+void KisReferenceImage::paint(QPainter &gc, KoShapePaintingContext &/*paintcontext*/) const
{
if (!parent()) return;
gc.save();
- applyConversion(gc, converter);
-
QSizeF shapeSize = size();
QTransform transform = QTransform::fromScale(shapeSize.width() / d->image.width(), shapeSize.height() / d->image.height());
if (d->cachedImage.isNull()) {
- d->updateCache();
+ // detach the data
+ const_cast<KisReferenceImage*>(this)->d->updateCache();
}
qreal scale;
- QImage prescaled = d->mipmap.getClosest(gc.transform() * transform, &scale);
+ QImage prescaled = d->mipmap.getClosest(transform * gc.transform(), &scale);
transform.scale(1.0 / scale, 1.0 / scale);
gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
gc.setClipRect(QRectF(QPointF(), shapeSize), Qt::IntersectClip);
gc.setTransform(transform, true);
gc.drawImage(QPoint(), prescaled);
gc.restore();
}
void KisReferenceImage::setSaturation(qreal saturation)
{
d->saturation = saturation;
d->cachedImage = QImage();
}
qreal KisReferenceImage::saturation() const
{
return d->saturation;
}
void KisReferenceImage::setEmbed(bool embed)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(embed || !d->externalFilename.isEmpty());
d->embed = embed;
}
bool KisReferenceImage::embed()
{
return d->embed;
}
bool KisReferenceImage::hasLocalFile()
{
return !d->externalFilename.isEmpty();
}
QString KisReferenceImage::filename() const
{
return d->externalFilename;
}
QString KisReferenceImage::internalFile() const
{
return d->internalFilename;
}
void KisReferenceImage::setFilename(const QString &filename)
{
d->externalFilename = filename;
d->embed = false;
}
QColor KisReferenceImage::getPixel(QPointF position)
{
if (transparency() == 1.0) return Qt::transparent;
const QSizeF shapeSize = size();
const QTransform scale = QTransform::fromScale(d->image.width() / shapeSize.width(), d->image.height() / shapeSize.height());
- const QTransform transform = absoluteTransformation(nullptr).inverted() * scale;
+ const QTransform transform = absoluteTransformation().inverted() * scale;
const QPointF localPosition = position * transform;
if (d->cachedImage.isNull()) {
d->updateCache();
}
return d->cachedImage.pixelColor(localPosition.toPoint());
}
void KisReferenceImage::saveXml(QDomDocument &document, QDomElement &parentElement, int id)
{
d->id = id;
QDomElement element = document.createElement("referenceimage");
if (d->embed) {
d->internalFilename = QString("reference_images/%1.png").arg(id);
}
const QString src = d->embed ? d->internalFilename : (QString("file://") + d->externalFilename);
element.setAttribute("src", src);
const QSizeF &shapeSize = size();
element.setAttribute("width", KisDomUtils::toString(shapeSize.width()));
element.setAttribute("height", KisDomUtils::toString(shapeSize.height()));
element.setAttribute("keepAspectRatio", keepAspectRatio() ? "true" : "false");
element.setAttribute("transform", SvgUtil::transformToString(transform()));
element.setAttribute("opacity", KisDomUtils::toString(1.0 - transparency()));
element.setAttribute("saturation", KisDomUtils::toString(d->saturation));
parentElement.appendChild(element);
}
KisReferenceImage * KisReferenceImage::fromXml(const QDomElement &elem)
{
auto *reference = new KisReferenceImage();
const QString &src = elem.attribute("src");
if (src.startsWith("file://")) {
reference->d->externalFilename = src.mid(7);
reference->d->embed = false;
} else {
reference->d->internalFilename = src;
reference->d->embed = true;
}
qreal width = KisDomUtils::toDouble(elem.attribute("width", "100"));
qreal height = KisDomUtils::toDouble(elem.attribute("height", "100"));
reference->setSize(QSizeF(width, height));
reference->setKeepAspectRatio(elem.attribute("keepAspectRatio", "true").toLower() == "true");
auto transform = SvgTransformParser(elem.attribute("transform")).transform();
reference->setTransformation(transform);
qreal opacity = KisDomUtils::toDouble(elem.attribute("opacity", "1"));
reference->setTransparency(1.0 - opacity);
qreal saturation = KisDomUtils::toDouble(elem.attribute("saturation", "1"));
reference->setSaturation(saturation);
return reference;
}
bool KisReferenceImage::saveImage(KoStore *store) const
{
if (!d->embed) return true;
if (!store->open(d->internalFilename)) {
return false;
}
bool saved = false;
KoStoreDevice storeDev(store);
if (storeDev.open(QIODevice::WriteOnly)) {
saved = d->image.save(&storeDev, "PNG");
}
return store->close() && saved;
}
bool KisReferenceImage::loadImage(KoStore *store)
{
if (!d->embed) {
return d->loadFromFile();
}
if (!store->open(d->internalFilename)) {
return false;
}
KoStoreDevice storeDev(store);
if (!storeDev.open(QIODevice::ReadOnly)) {
return false;
}
if (!d->image.load(&storeDev, "PNG")) {
return false;
}
return store->close();
}
KoShape *KisReferenceImage::cloneShape() const
{
return new KisReferenceImage(*this);
}
diff --git a/libs/ui/KisReferenceImage.h b/libs/ui/KisReferenceImage.h
index c05ed517dd..fdc690d34d 100644
--- a/libs/ui/KisReferenceImage.h
+++ b/libs/ui/KisReferenceImage.h
@@ -1,97 +1,97 @@
/*
* 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 KISREFERENCEIMAGE_H
#define KISREFERENCEIMAGE_H
#include <QSharedDataPointer>
#include <kundo2command.h>
#include <kritaui_export.h>
#include <KoTosContainer.h>
#include <KoColor.h>
class QImage;
class QPointF;
class QPainter;
class QRectF;
class KoStore;
class KisCoordinatesConverter;
class KisCanvas2;
/**
* @brief The KisReferenceImage class represents a single reference image
*/
class KRITAUI_EXPORT KisReferenceImage : public KoTosContainer
{
public:
struct KRITAUI_EXPORT SetSaturationCommand : public KUndo2Command {
QVector<KisReferenceImage*> images;
QVector<qreal> oldSaturations;
qreal newSaturation;
explicit SetSaturationCommand(const QList<KoShape *> &images, qreal newSaturation, KUndo2Command *parent = 0);
void undo() override;
void redo() override;
};
KisReferenceImage();
KisReferenceImage(const KisReferenceImage &rhs);
~KisReferenceImage();
KoShape *cloneShape() const override;
/**
* Load a reference image from specified file.
* If parent is provided and the image cannot be loaded, a warning message will be displayed to user.
* @return reference image or null if one could not be loaded
*/
static KisReferenceImage * fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent /*= nullptr*/);
static KisReferenceImage * fromClipboard(const KisCoordinatesConverter &converter);
void setSaturation(qreal saturation);
qreal saturation() const;
void setEmbed(bool embed);
bool embed();
bool hasLocalFile();
void setFilename(const QString &filename);
QString filename() const;
QString internalFile() const;
- void paint(QPainter &gc, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
+ void paint(QPainter &gc, KoShapePaintingContext &paintcontext) const override;
bool loadOdf(const KoXmlElement &/*element*/, KoShapeLoadingContext &/*context*/) override { return false; }
void saveOdf(KoShapeSavingContext &/*context*/) const override {}
QColor getPixel(QPointF position);
void saveXml(QDomDocument &document, QDomElement &parentElement, int id);
bool saveImage(KoStore *store) const;
static KisReferenceImage * fromXml(const QDomElement &elem);
bool loadImage(KoStore *store);
private:
struct Private;
QSharedDataPointer<Private> d;
};
#endif // KISREFERENCEIMAGE_H
diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp
index 4b547ef795..22ca42464b 100644
--- a/libs/ui/KisViewManager.cpp
+++ b/libs/ui/KisViewManager.cpp
@@ -1,1455 +1,1455 @@
/*
* 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>
+ * 2017 L. E. Segovia <amy@amyspark.me>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* 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 <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 <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 <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 "KisDecorationsManager.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 "KisResourceServerProvider.h"
#include "kis_selection.h"
#include "kis_selection_mask.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_icon_utils.h"
#include "kis_guides_manager.h"
#include "kis_derived_resources.h"
#include "dialogs/kis_delayed_save_dialog.h"
#include <KisMainWindow.h>
#include "kis_signals_blocker.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)
, actionAuthor(0)
, showPixelGrid(0)
{
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;
KisAction *toggleFgBg;
KisAction *resetFgBg;
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;
KisDecorationsManager paintingAssistantsManager;
BlockingUserInputEventFilter blockingEventFilter;
KisActionManager actionManager;
QMainWindow* mainWindow;
QPointer<KisFloatingMessage> savedFloatingMessage;
bool showFloatingMessage;
QPointer<KisView> currentImageView;
KisCanvasResourceProvider canvasResourceProvider;
KoCanvasResourceProvider canvasResourceManager;
KisSignalCompressor guiUpdateCompressor;
KActionCollection *actionCollection;
KisMirrorManager mirrorManager;
KisInputManager inputManager;
KisSignalAutoConnectionsStore viewConnections;
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)),
canvasResourceProvider(), SLOT(slotNodeActivated(KisNodeSP)));
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()));
connect(KisConfigNotifier::instance(), SIGNAL(pixelGridModeChanged()), SLOT(slotUpdatePixelGridAction()));
KisInputProfileManager::instance()->loadProfiles();
KisConfig cfg(true);
d->showFloatingMessage = cfg.showCanvasMessages();
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));
}
KisViewManager::~KisViewManager()
{
KisConfig cfg(false);
if (canvasResourceProvider() && canvasResourceProvider()->currentPreset()) {
cfg.writeKoColor("LastForeGroundColor",canvasResourceProvider()->fgColor());
cfg.writeKoColor("LastBackGroundColor",canvasResourceProvider()->bgColor());
}
cfg.writeEntry("baseLength", KoResourceItemChooserSync::instance()->baseLength());
cfg.writeEntry("CanvasOnlyActive", false); // We never restart in CavnasOnlyMode
delete d;
}
void KisViewManager::initializeResourceManager(KoCanvasResourceProvider *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 KisLodSizeThresholdResourceConverter));
resourceManager->addDerivedResourceConverter(toQShared(new KisLodSizeThresholdSupportedResourceConverter));
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();
}
KisConfig cfg(false);
if (canvasResourceProvider() && canvasResourceProvider()->currentPreset()) {
cfg.writeEntry("LastPreset", canvasResourceProvider()->currentPreset()->name());
}
}
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 = imageView->document();
if (KisConfig(true).readEntry<bool>("EnablePositionLabel", false)) {
connect(d->currentImageView->canvasController()->proxyObject,
SIGNAL(documentMousePositionChanged(QPointF)),
&d->statusBar,
SLOT(documentMousePositionChanged(QPointF)));
}
// Restore the last used brush preset, color and background color.
if (first) {
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
QString defaultPresetName = "basic_tip_default";
bool foundTip = false;
for (int i=0; i<rserver->resourceCount(); i++) {
KisPaintOpPresetSP resource = rserver->resources().at(i);
if (resource->name().toLower().contains("basic_tip_default")) {
defaultPresetName = resource->name();
foundTip = true;
} else if (foundTip == false && (resource->name().toLower().contains("default") ||
resource->filename().toLower().contains("default"))) {
defaultPresetName = resource->name();
foundTip = true;
}
}
KisConfig cfg(true);
QString lastPreset = cfg.readEntry("LastPreset", defaultPresetName);
KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset);
if (!preset) {
preset = rserver->resourceByName(defaultPresetName);
}
if (!preset && !rserver->resources().isEmpty()) {
preset = rserver->resources().first();
}
if (preset) {
paintOpBox()->restoreResource(preset.data());
canvasResourceProvider()->setCurrentCompositeOp(preset->settings()->paintOpCompositeOp());
}
}
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(QPointF,QPointF)),
canvasResourceProvider(), SLOT(slotImageSizeChanged()));
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigResolutionChanged(double,double)),
canvasResourceProvider(), SLOT(slotOnScreenResolutionChanged()));
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigNodeChanged(KisNodeSP)),
this, SLOT(updateGUI()));
d->viewConnections.addUniqueConnection(
d->currentImageView->zoomManager()->zoomController(),
SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)),
canvasResourceProvider(), SLOT(slotOnScreenResolutionChanged()));
}
d->actionManager.updateGUI();
canvasResourceProvider()->slotImageSizeChanged();
canvasResourceProvider()->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::canvasResourceProvider()
{
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;
}
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) {
KisSelectionMaskSP mask = layer->selectionMask();
if (mask) {
return mask->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(true);
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()));
actionManager()->createAction("ruler_pixel_multiple2");
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(QString)), this, SLOT(changeAuthorProfile(QString)));
actionCollection()->addAction("settings_active_author", d->actionAuthor);
slotUpdateAuthorProfileActions();
d->showPixelGrid = actionManager()->createAction("view_pixel_grid");
slotUpdatePixelGridAction();
d->toggleFgBg = actionManager()->createAction("toggle_fg_bg");
connect(d->toggleFgBg, SIGNAL(triggered(bool)), this, SLOT(slotToggleFgBg()));
d->resetFgBg = actionManager()->createAction("reset_fg_bg");
connect(d->resetFgBg, SIGNAL(triggered(bool)), this, SLOT(slotResetFgBg()));
}
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());
}
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;
}
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;
if (document()->url().isEmpty()) {
KisMainWindow *mw = qobject_cast<KisMainWindow*>(d->mainWindow);
mw->saveDocument(document(), true, false);
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;
if (document()->url().isEmpty()) {
KisMainWindow *mw = qobject_cast<KisMainWindow*>(d->mainWindow);
mw->saveDocument(document(), true, false);
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(false);
cfg.setShowStatusBar(toggled);
}
}
void KisViewManager::switchCanvasOnly(bool toggled)
{
KisConfig cfg(false);
KisMainWindow *main = mainWindow();
if(!main) {
dbgUI << "Unable to switch to canvas-only mode, main window not found";
return;
}
cfg.writeEntry("CanvasOnlyActive", toggled);
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) {
QObjectList objects;
objects.append(dock);
while (!objects.isEmpty()) {
QObject* object = objects.takeFirst();
objects.append(object->children());
KisIconUtils::updateIconCommon(object);
}
}
}
}
void KisViewManager::initializeStatusBarVisibility()
{
KisConfig cfg(true);
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->showFloatingMessage(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(true);
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(false);
cfg.setShowRulers(value);
}
void KisViewManager::slotSaveRulersTrackMouseState(bool value)
{
KisConfig cfg(false);
cfg.setRulersTrackMouse(value);
}
void KisViewManager::setShowFloatingMessage(bool show)
{
d->showFloatingMessage = show;
}
void KisViewManager::changeAuthorProfile(const QString &profileName)
{
KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author");
if (profileName.isEmpty() || profileName == i18nc("choice for author profile", "Anonymous")) {
appAuthorGroup.writeEntry("active-profile", "");
} 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(i18nc("choice for author profile", "Anonymous"));
KConfigGroup authorGroup(KSharedConfig::openConfig(), "Author");
QStringList profiles = authorGroup.readEntry("profile-names", QStringList());
QString authorInfo = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/authorinfo/";
QStringList filters = QStringList() << "*.authorinfo";
QDir dir(authorInfo);
Q_FOREACH(QString entry, dir.entryList(filters)) {
int ln = QString(".authorinfo").size();
entry.chop(ln);
if (!profiles.contains(entry)) {
profiles.append(entry);
}
}
Q_FOREACH (const QString &profile , profiles) {
d->actionAuthor->addAction(profile);
}
KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author");
QString profileName = appAuthorGroup.readEntry("active-profile", "");
if (profileName == "anonymous" || profileName.isEmpty()) {
d->actionAuthor->setCurrentItem(0);
} else if (profiles.contains(profileName)) {
d->actionAuthor->setCurrentAction(profileName);
}
}
void KisViewManager::slotUpdatePixelGridAction()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(d->showPixelGrid);
KisSignalsBlocker b(d->showPixelGrid);
KisConfig cfg(true);
d->showPixelGrid->setChecked(cfg.pixelGridEnabled() && cfg.useOpenGL());
}
void KisViewManager::slotActivateTransformTool()
{
if(KoToolManager::instance()->activeToolId() == "KisToolTransform") {
KoToolBase* tool = KoToolManager::instance()->toolById(canvasBase(), "KisToolTransform");
QSet<KoShape*> dummy;
// Start a new stroke
tool->deactivate();
tool->activate(KoToolBase::DefaultActivation, dummy);
}
KoToolManager::instance()->switchToolRequested("KisToolTransform");
}
void KisViewManager::slotToggleFgBg()
{
KoColor newFg = d->canvasResourceManager.backgroundColor();
KoColor newBg = d->canvasResourceManager.foregroundColor();
/**
* NOTE: Some of color selectors do not differentiate foreground
* and background colors, so if one wants them to end up
* being set up to foreground color, it should be set the
* last.
*/
d->canvasResourceManager.setBackgroundColor(newBg);
d->canvasResourceManager.setForegroundColor(newFg);
}
void KisViewManager::slotResetFgBg()
{
// see a comment in slotToggleFgBg()
d->canvasResourceManager.setBackgroundColor(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8()));
d->canvasResourceManager.setForegroundColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()));
}
void KisViewManager::slotResetRotation()
{
KisCanvasController *canvasController = d->currentImageView->canvasController();
canvasController->resetCanvasRotation();
}
void KisViewManager::slotToggleFullscreen()
{
KisConfig cfg(false);
KisMainWindow *main = mainWindow();
main->viewFullscreen(!main->isFullScreen());
cfg.fullscreenMode(main->isFullScreen());
}
diff --git a/libs/ui/KisWindowLayoutResource.cpp b/libs/ui/KisWindowLayoutResource.cpp
index 2cc94b57d4..bcb6d4b4fc 100644
--- a/libs/ui/KisWindowLayoutResource.cpp
+++ b/libs/ui/KisWindowLayoutResource.cpp
@@ -1,450 +1,450 @@
/*
* Copyright (c) 2018 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 "KisWindowLayoutResource.h"
#include "KisWindowLayoutManager.h"
#include <QVector>
#include <QList>
#include <QFile>
#include <QDomDocument>
#include <QApplication>
#include <QEventLoop>
#include <QWindow>
#include <QScreen>
#include <KisPart.h>
#include <KisDocument.h>
#include <kis_dom_utils.h>
static const int WINDOW_LAYOUT_VERSION = 1;
struct KisWindowLayoutResource::Private
{
struct WindowGeometry{
int screen = -1;
Qt::WindowStates stateFlags = Qt::WindowNoState;
QByteArray data;
static WindowGeometry fromWindow(const QWidget *window, QList<QScreen*> screens)
{
WindowGeometry geometry;
QWindow *windowHandle = window->windowHandle();
geometry.data = window->saveGeometry();
geometry.stateFlags = windowHandle->windowState();
int index = screens.indexOf(windowHandle->screen());
if (index >= 0) {
geometry.screen = index;
}
return geometry;
}
void forceOntoCorrectScreen(QWidget *window, QList<QScreen*> screens)
{
QWindow *windowHandle = window->windowHandle();
if (screens.indexOf(windowHandle->screen()) != screen) {
QScreen *qScreen = screens[screen];
windowHandle->setScreen(qScreen);
windowHandle->setPosition(qScreen->availableGeometry().topLeft());
}
if (stateFlags) {
window->setWindowState(stateFlags);
}
}
void save(QDomDocument &doc, QDomElement &elem) const
{
if (screen >= 0) {
elem.setAttribute("screen", screen);
}
if (stateFlags & Qt::WindowMaximized) {
elem.setAttribute("maximized", "1");
}
QDomElement geometry = doc.createElement("geometry");
geometry.appendChild(doc.createCDATASection(data.toBase64()));
elem.appendChild(geometry);
}
static WindowGeometry load(const QDomElement &element)
{
WindowGeometry geometry;
geometry.screen = element.attribute("screen", "-1").toInt();
if (element.attribute("maximized", "0") != "0") {
geometry.stateFlags |= Qt::WindowMaximized;
}
QDomElement dataElement = element.firstChildElement("geometry");
geometry.data = QByteArray::fromBase64(dataElement.text().toLatin1());
return geometry;
}
};
struct Window {
QUuid windowId;
QByteArray windowState;
WindowGeometry geometry;
bool canvasDetached = false;
WindowGeometry canvasWindowGeometry;
};
QVector<Window> windows;
- bool showImageInAllWindows;
- bool primaryWorkspaceFollowsFocus;
+ bool showImageInAllWindows {false};
+ bool primaryWorkspaceFollowsFocus {false};
QUuid primaryWindow;
Private() = default;
explicit Private(QVector<Window> windows)
: windows(std::move(windows))
{}
void openNecessaryWindows(QList<QPointer<KisMainWindow>> &currentWindows) {
auto *kisPart = KisPart::instance();
Q_FOREACH(const Window &window, windows) {
QPointer<KisMainWindow> mainWindow = kisPart->windowById(window.windowId);
if (mainWindow.isNull()) {
mainWindow = kisPart->createMainWindow(window.windowId);
currentWindows.append(mainWindow);
mainWindow->show();
}
}
}
void closeUnneededWindows(QList<QPointer<KisMainWindow>> &currentWindows) {
QVector<QPointer<KisMainWindow>> windowsToClose;
Q_FOREACH(KisMainWindow *mainWindow, currentWindows) {
bool keep = false;
Q_FOREACH(const Window &window, windows) {
if (window.windowId == mainWindow->id()) {
keep = true;
break;
}
}
if (!keep) {
windowsToClose.append(mainWindow);
// Set the window hidden to prevent "show image in all windows" feature from opening new views on it
// while we migrate views onto the remaining windows
mainWindow->hide();
}
}
migrateViewsFromClosingWindows(windowsToClose);
Q_FOREACH(QPointer<KisMainWindow> mainWindow, windowsToClose) {
mainWindow->close();
}
}
void migrateViewsFromClosingWindows(QVector<QPointer<KisMainWindow>> &closingWindows) const
{
auto *kisPart = KisPart::instance();
KisMainWindow *migrationTarget = nullptr;
Q_FOREACH(KisMainWindow *mainWindow, kisPart->mainWindows()) {
if (!closingWindows.contains(mainWindow)) {
migrationTarget = mainWindow;
break;
}
}
if (!migrationTarget) {
qWarning() << "Problem: window layout with no windows would leave user with zero main windows.";
migrationTarget = closingWindows.takeLast();
migrationTarget->show();
}
QVector<KisDocument*> visibleDocuments;
Q_FOREACH(KisView *view, kisPart->views()) {
KisMainWindow *window = view->mainWindow();
if (!closingWindows.contains(window)) {
visibleDocuments.append(view->document());
}
}
Q_FOREACH(KisDocument *document, kisPart->documents()) {
if (!visibleDocuments.contains(document)) {
visibleDocuments.append(document);
migrationTarget->newView(document);
}
}
}
QList<QScreen*> getScreensInConsistentOrder() {
QList<QScreen*> screens = QGuiApplication::screens();
std::sort(screens.begin(), screens.end(), [](const QScreen *a, const QScreen *b) {
QRect aRect = a->geometry();
QRect bRect = b->geometry();
if (aRect.y() == bRect.y()) return aRect.x() < bRect.x();
return (aRect.y() < aRect.y());
});
return screens;
}
};
KisWindowLayoutResource::KisWindowLayoutResource(const QString &filename)
: KoResource(filename)
, d(new Private)
{}
KisWindowLayoutResource::~KisWindowLayoutResource()
{}
KisWindowLayoutResource * KisWindowLayoutResource::fromCurrentWindows(
const QString &filename, const QList<QPointer<KisMainWindow>> &mainWindows, bool showImageInAllWindows,
bool primaryWorkspaceFollowsFocus, KisMainWindow *primaryWindow
)
{
auto resource = new KisWindowLayoutResource(filename);
resource->setWindows(mainWindows);
resource->d->showImageInAllWindows = showImageInAllWindows;
resource->d->primaryWorkspaceFollowsFocus = primaryWorkspaceFollowsFocus;
resource->d->primaryWindow = primaryWindow->id();
return resource;
}
void KisWindowLayoutResource::applyLayout()
{
auto *kisPart = KisPart::instance();
auto *layoutManager= KisWindowLayoutManager::instance();
layoutManager->setLastUsedLayout(this);
QList<QPointer<KisMainWindow>> currentWindows = kisPart->mainWindows();
if (d->windows.isEmpty()) {
// No windows defined (e.g. fresh new session). Leave things as they are, but make sure there's at least one visible main window
if (kisPart->mainwindowCount() == 0) {
kisPart->createMainWindow();
} else {
kisPart->mainWindows().first()->show();
}
} else {
d->openNecessaryWindows(currentWindows);
d->closeUnneededWindows(currentWindows);
}
// Wait for the windows to finish opening / closing before applying saved geometry.
// If we don't, the geometry may get reset after we apply it.
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
Q_FOREACH(const auto &window, d->windows) {
QPointer<KisMainWindow> mainWindow = kisPart->windowById(window.windowId);
KIS_SAFE_ASSERT_RECOVER_BREAK(mainWindow);
mainWindow->restoreGeometry(window.geometry.data);
mainWindow->restoreWorkspaceState(window.windowState);
mainWindow->setCanvasDetached(window.canvasDetached);
if (window.canvasDetached) {
QWidget *canvasWindow = mainWindow->canvasWindow();
canvasWindow->restoreGeometry(window.canvasWindowGeometry.data);
}
}
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
QList<QScreen*> screens = d->getScreensInConsistentOrder();
Q_FOREACH(const auto &window, d->windows) {
Private::WindowGeometry geometry = window.geometry;
QPointer<KisMainWindow> mainWindow = kisPart->windowById(window.windowId);
KIS_SAFE_ASSERT_RECOVER_BREAK(mainWindow);
if (geometry.screen >= 0 && geometry.screen < screens.size()) {
geometry.forceOntoCorrectScreen(mainWindow, screens);
}
if (window.canvasDetached) {
Private::WindowGeometry canvasWindowGeometry = window.canvasWindowGeometry;
if (canvasWindowGeometry.screen >= 0 && canvasWindowGeometry.screen < screens.size()) {
canvasWindowGeometry.forceOntoCorrectScreen(mainWindow->canvasWindow(), screens);
}
}
}
layoutManager->setShowImageInAllWindowsEnabled(d->showImageInAllWindows);
layoutManager->setPrimaryWorkspaceFollowsFocus(d->primaryWorkspaceFollowsFocus, d->primaryWindow);
}
bool KisWindowLayoutResource::save()
{
if (filename().isEmpty())
return false;
QFile file(filename());
file.open(QIODevice::WriteOnly);
bool res = saveToDevice(&file);
file.close();
return res;
}
bool KisWindowLayoutResource::load()
{
if (filename().isEmpty())
return false;
QFile file(filename());
if (file.size() == 0) return false;
if (!file.open(QIODevice::ReadOnly)) {
warnKrita << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&file);
file.close();
return res;
}
bool KisWindowLayoutResource::saveToDevice(QIODevice *dev) const
{
QDomDocument doc;
QDomElement root = doc.createElement("WindowLayout");
root.setAttribute("name", name());
root.setAttribute("version", WINDOW_LAYOUT_VERSION);
saveXml(doc, root);
doc.appendChild(root);
QTextStream textStream(dev);
textStream.setCodec("UTF-8");
doc.save(textStream, 4);
KoResource::saveToDevice(dev);
return true;
}
bool KisWindowLayoutResource::loadFromDevice(QIODevice *dev)
{
QDomDocument doc;
if (!doc.setContent(dev)) {
return false;
}
QDomElement element = doc.documentElement();
setName(element.attribute("name"));
d->windows.clear();
loadXml(element);
setValid(true);
return true;
}
void KisWindowLayoutResource::saveXml(QDomDocument &doc, QDomElement &root) const
{
root.setAttribute("showImageInAllWindows", (int)d->showImageInAllWindows);
root.setAttribute("primaryWorkspaceFollowsFocus", (int)d->primaryWorkspaceFollowsFocus);
root.setAttribute("primaryWindow", d->primaryWindow.toString());
Q_FOREACH(const auto &window, d->windows) {
QDomElement elem = doc.createElement("window");
elem.setAttribute("id", window.windowId.toString());
window.geometry.save(doc, elem);
if (window.canvasDetached) {
QDomElement canvasWindowElement = doc.createElement("canvasWindow");
window.canvasWindowGeometry.save(doc, canvasWindowElement);
elem.appendChild(canvasWindowElement);
}
QDomElement state = doc.createElement("windowState");
state.appendChild(doc.createCDATASection(window.windowState.toBase64()));
elem.appendChild(state);
root.appendChild(elem);
}
}
void KisWindowLayoutResource::loadXml(const QDomElement &element) const
{
d->showImageInAllWindows = KisDomUtils::toInt(element.attribute("showImageInAllWindows", "0"));
d->primaryWorkspaceFollowsFocus = KisDomUtils::toInt(element.attribute("primaryWorkspaceFollowsFocus", "0"));
d->primaryWindow = element.attribute("primaryWindow");
for (auto windowElement = element.firstChildElement("window");
!windowElement.isNull();
windowElement = windowElement.nextSiblingElement("window")) {
Private::Window window;
window.windowId = QUuid(windowElement.attribute("id", QUuid().toString()));
if (window.windowId.isNull()) {
window.windowId = QUuid::createUuid();
}
window.geometry = Private::WindowGeometry::load(windowElement);
QDomElement canvasWindowElement = windowElement.firstChildElement("canvasWindow");
if (!canvasWindowElement.isNull()) {
window.canvasDetached = true;
window.canvasWindowGeometry = Private::WindowGeometry::load(canvasWindowElement);
}
QDomElement state = windowElement.firstChildElement("windowState");
window.windowState = QByteArray::fromBase64(state.text().toLatin1());
d->windows.append(window);
}
}
QString KisWindowLayoutResource::defaultFileExtension() const
{
return QString(".kwl");
}
void KisWindowLayoutResource::setWindows(const QList<QPointer<KisMainWindow>> &mainWindows)
{
d->windows.clear();
QList<QScreen*> screens = d->getScreensInConsistentOrder();
Q_FOREACH(auto window, mainWindows) {
if (!window->isVisible()) continue;
Private::Window state;
state.windowId = window->id();
state.windowState = window->saveState();
state.geometry = Private::WindowGeometry::fromWindow(window, screens);
state.canvasDetached = window->canvasDetached();
if (state.canvasDetached) {
state.canvasWindowGeometry = Private::WindowGeometry::fromWindow(window->canvasWindow(), screens);
}
d->windows.append(state);
}
}
diff --git a/libs/ui/canvas/kis_canvas_widget_base.cpp b/libs/ui/canvas/kis_canvas_widget_base.cpp
index ebf1e5c057..4a86defdc3 100644
--- a/libs/ui/canvas/kis_canvas_widget_base.cpp
+++ b/libs/ui/canvas/kis_canvas_widget_base.cpp
@@ -1,275 +1,276 @@
/*
* 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"
#include "kis_update_info.h"
+#include "KisQPainterStateSaver.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
+ bool ignorenextMouseEventExceptRightMiddleClick; // HACK work around Qt bug not sending tablet right/dblclick https://bugreports.qt.io/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. Otherwise 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 re-enable 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
+ // https://lists.qt-project.org/pipermail/qt-interest-old/2009-December/017078.html
// gc.setRenderHint(QPainter::HighQualityAntialiasing);
gc.setRenderHint(QPainter::SmoothPixmapTransform);
- gc.save();
- gc.setClipRect(updateWidgetRect);
+ {
+ KisQPainterStateSaver paintShapesState(&gc);
- QTransform transform = m_d->coordinatesConverter->flakeToWidgetTransform();
- gc.setTransform(transform);
+ gc.setClipRect(updateWidgetRect);
+ gc.setTransform(m_d->coordinatesConverter->documentToWidgetTransform());
- // 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();
+ // Paint the shapes (other than the layers)
+ m_d->canvas->globalShapeManager()->paint(gc, false);
- // 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);
+ // draw green selection outlines around text shapes that are edited, so the user sees where they end
+ gc.setPen( Qt::green );
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.setTransform(shape->absoluteTransformation(), true);
+ gc.drawRect(QRectF(QPointF(), shape->size()));
gc.restore();
}
}
- gc.restore();
- }
- 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.setTransform(m_d->coordinatesConverter->documentToWidgetTransform());
+ 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);
+ KoShapeManager::renderSingleShape(shape, gc, paintContext);
+ }
+ }
+ gc.restore();
+ }
+ }
// ask the decorations to paint themselves
+ // decorations are painted in "widget" coordinate system
Q_FOREACH (KisCanvasDecorationSP deco, m_d->decorations) {
deco->paint(gc, m_d->coordinatesConverter->widgetToDocument(updateWidgetRect), m_d->coordinatesConverter,m_d->canvas);
}
- gc.setTransform(transform);
- // - 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);
+ {
+ KisQPainterStateSaver paintDecorationsState(&gc);
+ gc.setTransform(m_d->coordinatesConverter->flakeToWidgetTransform());
+
+ // - 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();
}
void KisCanvasWidgetBase::addDecoration(KisCanvasDecorationSP deco)
{
m_d->decorations.push_back(deco);
std::stable_sort(m_d->decorations.begin(), m_d->decorations.end(), KisCanvasDecoration::comparePriority);
}
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;
std::stable_sort(m_d->decorations.begin(), m_d->decorations.end(), KisCanvasDecoration::comparePriority);
}
QList<KisCanvasDecorationSP > KisCanvasWidgetBase::decorations() const
{
return m_d->decorations;
}
void KisCanvasWidgetBase::setWrapAroundViewingMode(bool value)
{
Q_UNUSED(value);
}
QImage KisCanvasWidgetBase::createCheckersImage(qint32 checkSize)
{
KisConfig cfg(true);
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(true);
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;
}
QVector<QRect> KisCanvasWidgetBase::updateCanvasProjection(const QVector<KisUpdateInfoSP> &infoObjects)
{
QVector<QRect> dirtyViewRects;
Q_FOREACH (KisUpdateInfoSP info, infoObjects) {
dirtyViewRects << this->updateCanvasProjection(info);
}
return dirtyViewRects;
}
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/dialogs/kis_about_application.cpp b/libs/ui/dialogs/kis_about_application.cpp
index 5b518c1dd8..5b41403b34 100644
--- a/libs/ui/dialogs/kis_about_application.cpp
+++ b/libs/ui/dialogs/kis_about_application.cpp
@@ -1,214 +1,212 @@
/*
* 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_about_application.h"
#include <kis_debug.h>
#include <QStandardPaths>
#include <QTabWidget>
#include <QLabel>
#include <QTextEdit>
#include <QTextBrowser>
#include <QString>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QDate>
#include <QApplication>
#include <QFile>
#include <QDesktopServices>
#include <klocalizedstring.h>
#include "../../krita/data/splash/splash_screen.xpm"
#include "../../krita/data/splash/splash_holidays.xpm"
#include "../../krita/data/splash/splash_screen_x2.xpm"
#include "../../krita/data/splash/splash_holidays_x2.xpm"
#include "kis_splash_screen.h"
KisAboutApplication::KisAboutApplication(QWidget *parent)
: QDialog(parent)
{
setWindowTitle(i18n("About Krita"));
QVBoxLayout *vlayout = new QVBoxLayout(this);
vlayout->setMargin(0);
QTabWidget *wdgTab = new QTabWidget;
vlayout->addWidget(wdgTab);
KisSplashScreen *splash = 0;
QDate currentDate = QDate::currentDate();
if (currentDate > QDate(currentDate.year(), 12, 4) ||
currentDate < QDate(currentDate.year(), 1, 9)) {
splash = new KisSplashScreen(qApp->applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm));
}
else {
splash = new KisSplashScreen(qApp->applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm));
}
splash->setWindowFlags(Qt::Widget);
splash->displayLinks(true);
splash->setFixedSize(splash->sizeHint());
wdgTab->addTab(splash, i18n("About"));
setMinimumSize(wdgTab->sizeHint());
QTextEdit *lblAuthors = new QTextEdit();
lblAuthors->setReadOnly(true);
QString authors = i18n("<html>"
"<head/>"
"<body>"
"<h1 align=\"center\">Created By</h1></p>"
"<p>");
QFile fileDevelopers(":/developers.txt");
Q_ASSERT(fileDevelopers.exists());
fileDevelopers.open(QIODevice::ReadOnly);
Q_FOREACH (const QByteArray &author, fileDevelopers.readAll().split('\n')) {
authors.append(QString::fromUtf8(author));
authors.append(", ");
}
authors.chop(2);
authors.append(".</p></body></html>");
lblAuthors->setText(authors);
wdgTab->addTab(lblAuthors, i18nc("Heading for the list of Krita authors/developers", "Authors"));
QTextEdit *lblKickstarter = new QTextEdit();
lblKickstarter->setReadOnly(true);
QString backers = i18n("<html>"
"<head/>"
"<body>"
"<h1 align=\"center\">Backed By</h1>"
"<p>");
QFile fileBackers(":/backers.txt");
Q_ASSERT(fileBackers.exists());
fileBackers.open(QIODevice::ReadOnly | QIODevice::Text);
QTextStream backersText(&fileBackers);
backersText.setCodec("UTF-8");
backers.append(backersText.readAll().replace("\n", ", "));
backers.chop(2);
backers.append(i18n(".</p><p><i>Thanks! You were all <b>awesome</b>!</i></p></body></html>"));
lblKickstarter->setText(backers);
wdgTab->addTab(lblKickstarter, i18n("Backers"));
QTextEdit *lblCredits = new QTextEdit();
lblCredits->setReadOnly(true);
QString credits = i18n("<html>"
"<head/>"
"<body>"
"<h1 align=\"center\">Thanks To</h1>"
"<p>");
QFile fileCredits(":/credits.txt");
Q_ASSERT(fileCredits.exists());
fileCredits.open(QIODevice::ReadOnly);
Q_FOREACH (const QString &credit, QString::fromUtf8(fileCredits.readAll()).split('\n', QString::SkipEmptyParts)) {
if (credit.contains(":")) {
QList<QString> creditSplit = credit.split(':');
credits.append(creditSplit.at(0));
credits.append(" (<i>" + creditSplit.at(1) + "</i>)");
credits.append(", ");
}
}
credits.chop(2);
credits.append(i18n(".</p><p><i>For supporting Krita development with advice, icons, brush sets and more.</i></p></body></html>"));
lblCredits->setText(credits);
wdgTab->addTab(lblCredits, i18n("Also Thanks To"));
QTextEdit *lblLicense = new QTextEdit();
lblLicense->setReadOnly(true);
QString license = i18n("<html>"
"<head/>"
"<body>"
"<h1 align=\"center\"><b>Your Rights</b></h1>"
"<p>Krita is released under the GNU General Public License (version 3 or any later version).</p>"
"<p>This license grants people a number of freedoms:</p>"
"<ul>"
"<li>You are free to use Krita, for any purpose</li>"
"<li>You are free to distribute Krita</li>"
"<li>You can study how Krita works and change it</li>"
"<li>You can distribute changed versions of Krita</li>"
"</ul>"
"<p>The Krita Foundation and its projects on krita.org are <b>committed</b> to preserving Krita as free software.</p>"
"<h1 align=\"center\">Your artwork</h1>"
"<p>What you create with Krita is your sole property. All your artwork is free for you to use as you like.</p>"
"<p>That means that Krita can be used commercially, for any purpose. There are no restrictions whatsoever.</p>"
"<p>Krita’s GNU GPL license guarantees you this freedom. Nobody is ever permitted to take it away, in contrast "
"to trial or educational versions of commercial software that will forbid your work in commercial situations.</p>"
"<br/><hr/><pre>");
QFile licenseFile(":/LICENSE");
Q_ASSERT(licenseFile.exists());
licenseFile.open(QIODevice::ReadOnly);
QByteArray ba = licenseFile.readAll();
license.append(QString::fromUtf8(ba));
license.append("</pre></body></html>");
lblLicense->setText(license);
wdgTab->addTab(lblLicense, i18n("License"));
QTextBrowser *lblThirdParty = new QTextBrowser();
lblThirdParty->setOpenExternalLinks(true);
QFile thirdPartyFile(":/libraries.txt");
if (thirdPartyFile.open(QIODevice::ReadOnly)) {
ba = thirdPartyFile.readAll();
QString thirdPartyHtml = i18n("<html>"
"<head/>"
"<body>"
"<h1 align=\"center\"><b>Third-party Libraries used by Krita</b></h1>"
"<p>Krita is built on the following free software libraries:</p><p><ul>");
Q_FOREACH(const QString &lib, QString::fromUtf8(ba).split('\n')) {
if (!lib.startsWith("#")) {
QStringList parts = lib.split(',');
if (parts.size() >= 3) {
thirdPartyHtml.append(QString("<li><a href=\"%2\">%1</a>: %3</li>").arg(parts[0], parts[1], parts[2]));
}
}
}
thirdPartyHtml.append("<ul></p></body></html>");
lblThirdParty->setText(thirdPartyHtml);
}
wdgTab->addTab(lblThirdParty, i18n("Third-party libraries"));
-
QPushButton *bnClose = new QPushButton(i18n("Close"));
+ bnClose->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
connect(bnClose, SIGNAL(clicked()), SLOT(close()));
QHBoxLayout *hlayout = new QHBoxLayout;
hlayout->setMargin(10);
hlayout->addStretch(10);
hlayout->addWidget(bnClose);
-
-
vlayout->addLayout(hlayout);
}
diff --git a/libs/ui/dialogs/kis_dlg_filter.cpp b/libs/ui/dialogs/kis_dlg_filter.cpp
index c8294b07f8..f01e7b93ed 100644
--- a/libs/ui/dialogs/kis_dlg_filter.cpp
+++ b/libs/ui/dialogs/kis_dlg_filter.cpp
@@ -1,250 +1,251 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2008 Boudewijn Rempt <boud@valdysa.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_filter.h"
#include <KoResourcePaths.h>
#include <QPushButton>
#include <filter/kis_filter.h>
#include <filter/kis_filter_configuration.h>
#include <kis_filter_mask.h>
#include <kis_node.h>
#include <kis_layer.h>
#include <kis_paint_layer.h>
#include <KisViewManager.h>
#include <kis_config.h>
#include "kis_selection.h"
#include "kis_node_commands_adapter.h"
#include "kis_filter_manager.h"
#include "ui_wdgfilterdialog.h"
#include "kis_canvas2.h"
struct KisDlgFilter::Private {
Private(KisFilterManager *_filterManager, KisViewManager *_view)
: currentFilter(0)
, resizeCount(0)
, view(_view)
, filterManager(_filterManager)
, blockModifyingActionsGuard(new KisInputActionGroupsMaskGuard(view->canvasBase(), ViewTransformActionGroup))
{
}
KisFilterSP currentFilter;
Ui_FilterDialog uiFilterDialog;
KisNodeSP node;
int resizeCount;
KisViewManager *view;
KisFilterManager *filterManager;
// a special guard object that blocks all the painting input actions while the
// dialog is open
QScopedPointer<KisInputActionGroupsMaskGuard> blockModifyingActionsGuard;
};
KisDlgFilter::KisDlgFilter(KisViewManager *view, KisNodeSP node, KisFilterManager *filterManager, QWidget *parent) :
QDialog(parent),
d(new Private(filterManager, view))
{
setModal(false);
d->uiFilterDialog.setupUi(this);
d->node = node;
d->uiFilterDialog.filterSelection->setView(view);
d->uiFilterDialog.filterSelection->showFilterGallery(KisConfig(true).showFilterGallery());
d->uiFilterDialog.pushButtonCreateMaskEffect->show();
connect(d->uiFilterDialog.pushButtonCreateMaskEffect, SIGNAL(pressed()), SLOT(createMask()));
connect(d->uiFilterDialog.pushButtonCreateMaskEffect,SIGNAL(pressed()),SLOT(close()));
d->uiFilterDialog.filterGalleryToggle->setChecked(d->uiFilterDialog.filterSelection->isFilterGalleryVisible());
d->uiFilterDialog.filterGalleryToggle->setIcon(QPixmap(":/pics/sidebaricon.png"));
d->uiFilterDialog.filterGalleryToggle->setMaximumWidth(d->uiFilterDialog.filterGalleryToggle->height());
connect(d->uiFilterDialog.filterSelection, SIGNAL(sigFilterGalleryToggled(bool)), d->uiFilterDialog.filterGalleryToggle, SLOT(setChecked(bool)));
connect(d->uiFilterDialog.filterGalleryToggle, SIGNAL(toggled(bool)), d->uiFilterDialog.filterSelection, SLOT(showFilterGallery(bool)));
connect(d->uiFilterDialog.filterSelection, SIGNAL(sigSizeChanged()), this, SLOT(slotFilterWidgetSizeChanged()));
if (node->inherits("KisMask")) {
d->uiFilterDialog.pushButtonCreateMaskEffect->setVisible(false);
}
d->uiFilterDialog.filterSelection->setPaintDevice(true, d->node->original());
connect(d->uiFilterDialog.buttonBox, SIGNAL(accepted()), SLOT(accept()));
connect(d->uiFilterDialog.buttonBox, SIGNAL(rejected()), SLOT(reject()));
connect(d->uiFilterDialog.checkBoxPreview, SIGNAL(toggled(bool)), SLOT(enablePreviewToggled(bool)));
connect(d->uiFilterDialog.filterSelection, SIGNAL(configurationChanged()), SLOT(filterSelectionChanged()));
connect(this, SIGNAL(accepted()), SLOT(slotOnAccept()));
+ connect(this, SIGNAL(accepted()), d->uiFilterDialog.filterSelection, SLOT(slotBookMarkCurrentFilter()));
connect(this, SIGNAL(rejected()), SLOT(slotOnReject()));
KConfigGroup group( KSharedConfig::openConfig(), "filterdialog");
d->uiFilterDialog.checkBoxPreview->setChecked(group.readEntry("showPreview", true));
restoreGeometry(KisConfig(true).readEntry("filterdialog/geometry", QByteArray()));
}
KisDlgFilter::~KisDlgFilter()
{
KisConfig(false).writeEntry("filterdialog/geometry", saveGeometry());
delete d;
}
void KisDlgFilter::setFilter(KisFilterSP f)
{
Q_ASSERT(f);
setDialogTitle(f);
d->uiFilterDialog.filterSelection->setFilter(f);
d->uiFilterDialog.pushButtonCreateMaskEffect->setEnabled(f->supportsAdjustmentLayers());
d->currentFilter = f;
updatePreview();
}
void KisDlgFilter::setDialogTitle(KisFilterSP filter)
{
setWindowTitle(filter.isNull() ? i18nc("@title:window", "Filter") : i18nc("@title:window", "Filter: %1", filter->name()));
}
void KisDlgFilter::startApplyingFilter(KisFilterConfigurationSP config)
{
if (!d->uiFilterDialog.filterSelection->configuration()) return;
if (d->node->inherits("KisPaintLayer")) {
config->setChannelFlags(qobject_cast<KisPaintLayer*>(d->node.data())->channelLockFlags());
}
d->filterManager->apply(config);
}
void KisDlgFilter::updatePreview()
{
KisFilterConfigurationSP config = d->uiFilterDialog.filterSelection->configuration();
if (!config) return;
bool maskCreationAllowed = !d->currentFilter || d->currentFilter->configurationAllowedForMask(config);
d->uiFilterDialog.pushButtonCreateMaskEffect->setEnabled(maskCreationAllowed);
if (d->uiFilterDialog.checkBoxPreview->isChecked()) {
KisFilterConfigurationSP config(d->uiFilterDialog.filterSelection->configuration());
startApplyingFilter(config);
}
d->uiFilterDialog.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
void KisDlgFilter::adjustSize()
{
QWidget::adjustSize();
}
void KisDlgFilter::slotFilterWidgetSizeChanged()
{
QMetaObject::invokeMethod(this, "adjustSize", Qt::QueuedConnection);
}
void KisDlgFilter::slotOnAccept()
{
if (!d->filterManager->isStrokeRunning()) {
KisFilterConfigurationSP config(d->uiFilterDialog.filterSelection->configuration());
startApplyingFilter(config);
}
d->filterManager->finish();
d->uiFilterDialog.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
KisConfig(false).setShowFilterGallery(d->uiFilterDialog.filterSelection->isFilterGalleryVisible());
}
void KisDlgFilter::slotOnReject()
{
if (d->filterManager->isStrokeRunning()) {
d->filterManager->cancel();
}
KisConfig(false).setShowFilterGallery(d->uiFilterDialog.filterSelection->isFilterGalleryVisible());
}
void KisDlgFilter::createMask()
{
if (d->node->inherits("KisMask")) return;
if (d->filterManager->isStrokeRunning()) {
d->filterManager->cancel();
}
KisLayer *layer = qobject_cast<KisLayer*>(d->node.data());
KisFilterMaskSP mask = new KisFilterMask();
mask->setName(d->currentFilter->name());
mask->initSelection(d->view->selection(), layer);
mask->setFilter(d->uiFilterDialog.filterSelection->configuration());
Q_ASSERT(layer->allowAsChild(mask));
KisNodeCommandsAdapter adapter(d->view);
adapter.addNode(mask, layer, layer->lastChild());
}
void KisDlgFilter::enablePreviewToggled(bool state)
{
if (state) {
updatePreview();
} else if (d->filterManager->isStrokeRunning()) {
d->filterManager->cancel();
}
KConfigGroup group( KSharedConfig::openConfig(), "filterdialog");
group.writeEntry("showPreview", d->uiFilterDialog.checkBoxPreview->isChecked());
group.config()->sync();
}
void KisDlgFilter::filterSelectionChanged()
{
KisFilterSP filter = d->uiFilterDialog.filterSelection->currentFilter();
setDialogTitle(filter);
d->currentFilter = filter;
d->uiFilterDialog.pushButtonCreateMaskEffect->setEnabled(filter.isNull() ? false : filter->supportsAdjustmentLayers());
updatePreview();
}
void KisDlgFilter::resizeEvent(QResizeEvent* event)
{
QDialog::resizeEvent(event);
// // Workaround, after the initialisation don't center the dialog anymore
// if(d->resizeCount < 2) {
// QWidget* canvas = d->view->canvas();
// QRect rect(canvas->mapToGlobal(canvas->geometry().topLeft()), size());
// int deltaX = (canvas->geometry().width() - geometry().width())/2;
// int deltaY = (canvas->geometry().height() - geometry().height())/2;
// rect.translate(deltaX, deltaY);
// setGeometry(rect);
// d->resizeCount++;
// }
}
diff --git a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp
index 9a830700ab..f1b547f0cc 100644
--- a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp
+++ b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp
@@ -1,395 +1,383 @@
/*
* Copyright (c) 2016 Kapustin Alexey <akapust1n@yandex.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* 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_stroke_selection_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 "widgets/kis_cmb_idlist.h"
#include <KisSqueezedComboBox.h>
#include "kis_layer_utils.h"
#include <kis_ls_utils.h>
#include "kis_canvas_resource_provider.h"
#include "KoUnit.h"
#include "kis_display_color_converter.h"
KisDlgStrokeSelection::KisDlgStrokeSelection(KisImageWSP image, KisViewManager *view, bool isVectorLayer)
: KoDialog(view->mainWindow())
{
m_resourceManager = view->mainWindow()->resourceManager();
+ KisPropertiesConfigurationSP cfg = KisConfig(true).exportConfiguration("StrokeSelection");
converter = view->canvasBase()->displayColorConverter();
setButtons(Ok | Cancel);
setDefaultButton(Ok);
setCaption(i18nc("@title:window", "Stroke Selection Properties"));
m_page = new WdgStrokeSelection(this);
+ m_page->m_isVectorLayer = isVectorLayer;
+ m_page->m_cfg = cfg;
m_image = image;
setMainWidget(m_page);
- resize(m_page->sizeHint());
-
- KisPropertiesConfigurationSP cfg = KisConfig(true).exportConfiguration("StrokeSelection");
- auto &m_options = m_page->m_options;
+ StrokeSelectionOptions &m_options = m_page->m_options;
m_options.color = cfg->getColor("color");
m_options.lineColorSource = cfg->getInt("lineColorSource");
m_page->lineColorBox->setCurrentIndex(m_options.lineColorSource);
m_page->colorSelector->setColor(getSelectedColor().toQColor());
m_options.brushSelected = cfg->getBool("useBrush", 0);
m_page->typeBox->setCurrentIndex(m_options.brushSelected? 0 : 1);
m_options._colorFillSource = cfg->getInt("colorFillSource", 0);
m_page->fillBox->setCurrentIndex(m_options._colorFillSource);
m_options.customColor = cfg->getColor("customColor");
+
if (m_options._colorFillSource == static_cast<int>(colorFillSource::CustomColor)) {
m_page->colorFillSelector->setColor(m_options.customColor.toQColor());
}
else {
m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor());
}
- m_options.fillColor = cfg->getColor("fillColor");
- if (m_options._colorFillSource == static_cast<int>(colorFillSource::None)) {
- m_page->colorFillSelector->setDisabled(true);
- }
- else {
- m_page->colorFillSelector->setDisabled(false); }
-
m_options.lineSize = cfg->getInt("lineSize", 1);
m_page->lineSize->setValue(m_options.lineSize);
- if (m_options.brushSelected) {
- m_page->lineSize->setDisabled(true);
- m_page->fillBox->setDisabled(true);
- m_page->colorFillSelector->setDisabled(true);
- m_page->sizeBox->setDisabled(true);
- }
m_options.lineDimension = cfg->getInt("lineDimension", 0);
m_page->sizeBox->setCurrentIndex(m_options.lineDimension);
connect(m_page, SIGNAL(colorSelectorChanged()), SLOT(setColorButton()));
connect(m_page, SIGNAL(colorFillSelectorChanged()), SLOT(setColorFillButton()));
connect(m_page->colorFillSelector, SIGNAL(changed(QColor)), SLOT(colorFillChanged(QColor)));
connect(m_page->colorSelector, SIGNAL(changed(QColor)), SLOT(colorChanged(QColor)));
- if (isVectorLayer) {
- lockVectorLayerFunctions();
- }
+ m_page->enableControls();
+
}
KisDlgStrokeSelection::~KisDlgStrokeSelection()
{
- auto &m_options = m_page->m_options;
+ StrokeSelectionOptions &m_options = m_page->m_options;
m_options.lineSize = m_page->lineSize->value();
m_options.lineDimension = m_page->sizeBox->currentIndex();
m_options.lineColorSource = m_page->lineColorBox->currentIndex();
KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration());
cfg->setProperty("lineSize", m_options.lineSize);
cfg->setProperty("colorFillSource", m_options._colorFillSource);
cfg->setProperty("useBrush", m_options.brushSelected);
cfg->setProperty("lineDimension", m_options.lineDimension);
cfg->setProperty("lineColorSource", m_options.lineColorSource);
QVariant colorVariant("KoColor");
colorVariant.setValue(m_options.customColor);
cfg->setProperty("customColor", colorVariant);
QVariant _colorVariant("KoColor");
_colorVariant.setValue(m_options.color);
cfg->setProperty("color", _colorVariant);
QVariant _cVariant("KoColor");
_cVariant.setValue(m_options.fillColor);
cfg->setProperty("fillColor", _cVariant);
KisConfig(false).setExportConfiguration("StrokeSelection", cfg);
delete m_page;
}
KoColor KisDlgStrokeSelection::getSelectedColor() const
{
KoColor color;
QString currentSource = m_page->lineColorBox->currentText();
if (currentSource == "Foreground color") {
color = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value<KoColor>();
}
else if (currentSource == "Background color") {
- color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value<KoColor>();
- }
- else {
- color = m_page->m_options.color;
- }
+ color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value<KoColor>();
+ }
+ else {
+ color = m_page->m_options.color;
+ }
return color;
}
KoColor KisDlgStrokeSelection::getFillSelectedColor() const
{
KoColor color;
colorFillSource currentSource = static_cast<colorFillSource>(m_page->fillBox->currentIndex());
if (currentSource == colorFillSource::FGColor) {
color = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value<KoColor>();
}
else if (currentSource == colorFillSource::BGColor) {
- color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value<KoColor>();
- }
- else if (currentSource == colorFillSource::PaintColor) {
- color = converter->approximateFromRenderedQColor(m_page->colorSelector->color());
- }
- else {
- color = m_page->m_options.customColor;
- }
+ color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value<KoColor>();
+ }
+ else if (currentSource == colorFillSource::PaintColor) {
+ color = converter->approximateFromRenderedQColor(m_page->colorSelector->color());
+ }
+ else {
+ color = m_page->m_options.customColor;
+ }
return color;
}
bool KisDlgStrokeSelection::isBrushSelected() const
{
- int index = m_page->typeBox->currentIndex();
- drawType type = static_cast<drawType>(index);
-
- if (type == drawType::brushDraw){
- return true;
- }
- else {
- return false;
- }
+ if (static_cast<drawType>(m_page->typeBox->currentIndex()) == drawType::brushDraw){
+ return true;
+ }
+ else {
+ return false;
+ }
}
StrokeSelectionOptions KisDlgStrokeSelection::getParams() const
- {
- StrokeSelectionOptions params;
-
- params.lineSize = getLineSize();
- params.color = getSelectedColor();
- params.brushSelected = isBrushSelected();
- params.fillColor = getFillSelectedColor();
- params._colorFillSource = m_page->m_options._colorFillSource;
- return params;
+{
+ StrokeSelectionOptions params;
-}
+ params.lineSize = getLineSize();
+ params.color = getSelectedColor();
+ params.brushSelected = isBrushSelected();
+ params.fillColor = getFillSelectedColor();
+ params._colorFillSource = m_page->m_options._colorFillSource;
+ return params;
-void KisDlgStrokeSelection::lockVectorLayerFunctions()
-{
- m_page->colorFillSelector->setEnabled(false);
- m_page->lineSize->setEnabled(false);
- m_page->sizeBox->setEnabled(false);
- m_page->fillBox->setEnabled(false);
- m_page->typeBox->setEnabled(false);
}
-void KisDlgStrokeSelection::unlockVectorLayerFunctions()
-{
- m_page->colorFillSelector->setEnabled(true);
- m_page->lineSize->setEnabled(true);
- m_page->sizeBox->setEnabled(true);
- m_page->fillBox->setEnabled(true);
- m_page->typeBox->setEnabled(true);
-}
void KisDlgStrokeSelection::setColorFillButton()
{
m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor());
}
void KisDlgStrokeSelection::setColorButton()
{
m_page->colorSelector->setColor(getSelectedColor().toQColor());
}
int KisDlgStrokeSelection::getLineSize() const
{
int value = m_page->lineSize->value();
if (m_page->sizeBox->currentText() == i18n("px")) {
return value;
}
else if (m_page->sizeBox->currentText() == i18n("mm")) {
- int pixels = static_cast<int>(KoUnit::convertFromUnitToUnit(value,KoUnit(KoUnit::Millimeter), KoUnit(KoUnit::Pixel)));
- return pixels;
+ int pixels = static_cast<int>(KoUnit::convertFromUnitToUnit(value,KoUnit(KoUnit::Millimeter), KoUnit(KoUnit::Pixel)));
+ return pixels;
}
- else {
- int pixels = static_cast<int>(KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel)));
- return pixels;
+ else {
+ int pixels = static_cast<int>(KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel)));
+ return pixels;
}
}
linePosition KisDlgStrokeSelection::getLinePosition() const
{/* TODO
int index = m_page->linePosition->currentIndex();
switch(index)
{
case(0):
return linePosition::OUTSIDE;
case(1):
return linePosition::INSIDE;
case(2):
return linePosition::CENTER;
default:
return linePosition::CENTER;
}*/
return linePosition::CENTER;
}
void KisDlgStrokeSelection::colorChanged(const QColor &newColor)
{
if (m_page->fillBox->currentText() == "Paint color") {
m_page->colorFillSelector->setColor(newColor);
}
QColor BGColor = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value<KoColor>().toQColor();
QColor FGColor = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value<KoColor>().toQColor();
KoColor tempColor= converter->approximateFromRenderedQColor(newColor);
- if (!(newColor == BGColor) && !(newColor == FGColor)) {
+ if (!(newColor == BGColor) && !(newColor == FGColor)) {
m_page->m_options.color = tempColor;
m_page->lineColorBox->setCurrentIndex(2); //custom color
- }
+ }
}
void KisDlgStrokeSelection::colorFillChanged(const QColor &newColor)
{
QColor PaintColor = m_page->colorSelector->color();
QColor BGcolor = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value<KoColor>().toQColor();
QColor FGColor = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value<KoColor>().toQColor();
KoColor tempColor= converter->approximateFromRenderedQColor(newColor);
if (!(newColor == FGColor) && !(newColor == BGcolor) && !(newColor == PaintColor)) {
m_page->m_options.customColor = tempColor;
m_page->fillBox->setCurrentIndex(static_cast<int>(colorFillSource::CustomColor));
}
m_page->m_options.fillColor = tempColor;
}
WdgStrokeSelection::WdgStrokeSelection(QWidget *parent) : QWidget(parent)
{
setupUi(this);
}
+void WdgStrokeSelection::enableControls()
+{
+ m_options.fillColor = m_cfg->getColor("fillColor");
+ if (m_options._colorFillSource == static_cast<int>(colorFillSource::None)) {
+ colorFillSelector->setEnabled(false);
+ }
+ else {
+ colorFillSelector->setEnabled(true);
+ }
+
+ if (m_isVectorLayer) {
+ typeBox->setCurrentIndex(1);
+ typeBox->setEnabled(false);
+ }
+ else {
+ typeBox->setEnabled(true);
+ }
+
+ on_typeBox_currentIndexChanged(typeBox->currentIndex());
+}
+
void WdgStrokeSelection::on_fillBox_currentIndexChanged(int index)
{
if (index == static_cast<int>(colorFillSource::None)) {
colorFillSelector->setDisabled(true);
}
else {
colorFillSelector->setDisabled(false);
emit colorFillSelectorChanged();
}
m_options._colorFillSource = index;
}
-void WdgStrokeSelection::on_typeBox_currentIndexChanged(const QString &arg1)
+void WdgStrokeSelection::on_typeBox_currentIndexChanged(int arg1)
{
- if (arg1 == "Current Brush") {
- m_options.brushSelected = true;
- lineSize->setDisabled(true);
- fillBox->setDisabled(true);
- colorFillSelector->setDisabled(true);
- sizeBox->setDisabled(true);
- }
- else {
- m_options.brushSelected = false;
- lineSize->setDisabled(false);
- fillBox->setDisabled(false);
- colorFillSelector->setDisabled(false);
- sizeBox->setDisabled(false);
- }
+ if (arg1 == 0) {
+ m_options.brushSelected = true;
+ lineSize->setEnabled(false);
+ fillBox->setEnabled(false);
+ colorFillSelector->setEnabled(false);
+ sizeBox->setEnabled(false);
+ }
+ else {
+ m_options.brushSelected = false;
+ lineSize->setEnabled(true);
+ fillBox->setEnabled(true);
+ colorFillSelector->setEnabled(true);
+ sizeBox->setEnabled(true);
+ }
}
-void WdgStrokeSelection::on_lineColorBox_currentIndexChanged(const QString &/*arg1*/)
+void WdgStrokeSelection::on_lineColorBox_currentIndexChanged(int/*arg1*/)
{
emit colorSelectorChanged();
}
StrokeSelectionOptions ::StrokeSelectionOptions():
lineSize(1),
brushSelected(false),
_colorFillSource(0),
lineColorSource(0),
lineDimension(0)
{
color.fromQColor(Qt::black);
fillColor.fromQColor(Qt::black);
customColor.fromQColor(Qt::black);
}
KisToolShapeUtils::FillStyle StrokeSelectionOptions::fillStyle() const
{
using namespace KisToolShapeUtils;
colorFillSource tempColor = static_cast<colorFillSource>(_colorFillSource);
FillStyle style = FillStyleNone;
switch (tempColor) {
case colorFillSource::PaintColor:
style = FillStyleForegroundColor;
break;
case colorFillSource::BGColor:
style = FillStyleBackgroundColor;
break;
case colorFillSource::CustomColor:
style = FillStyleBackgroundColor;
break;
case colorFillSource::None:
style = FillStyleNone;
break;
case colorFillSource::FGColor:
style = FillStyleBackgroundColor;
break;
}
return style;
}
diff --git a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h
index b2f8d6ae02..a866957a8d 100644
--- a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h
+++ b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h
@@ -1,113 +1,117 @@
/*
* Copyright (c) 2016 Alexey Kapustin <akapust1n@yandex.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public 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_STROKE_SELECTION_PROPERTIES_H_
#define KIS_DLG_STROKE_SELECTION_PROPERTIES_H_
#include <KoDialog.h>
#include "KisProofingConfiguration.h"
#include <kis_types.h>
#include "KisViewManager.h"
#include "KoStrokeConfigWidget.h"
#include "ui_wdgstrokeselectionproperties.h"
#include <kis_canvas2.h>
#include <KisToolShapeUtils.h>
class KoColorSpace;
class KoColorPopupAction;
enum class linePosition
{
OUTSIDE, INSIDE, CENTER
};
enum class drawType{
brushDraw, lineDraw
};
enum class colorFillSource {
None, PaintColor, BGColor, CustomColor, FGColor
};
struct StrokeSelectionOptions {
StrokeSelectionOptions ();
int lineSize;
bool brushSelected;
int _colorFillSource;
int lineColorSource;
int lineDimension;
KoColor color;
KoColor fillColor;
KoColor customColor;
KisToolShapeUtils::FillStyle fillStyle() const;
void lock();
};
class WdgStrokeSelection : public QWidget, public Ui::WdgStrokeSelection
{
Q_OBJECT
public:
WdgStrokeSelection(QWidget *parent) ;
StrokeSelectionOptions m_options;
+ bool m_isVectorLayer;
+ KisPropertiesConfigurationSP m_cfg;
+
+ void enableControls();
+
Q_SIGNALS:
void colorFillSelectorChanged();
void colorSelectorChanged();
private Q_SLOTS:
void on_fillBox_currentIndexChanged(int index);
- void on_typeBox_currentIndexChanged(const QString &arg1);
- void on_lineColorBox_currentIndexChanged(const QString &arg1);
+ void on_typeBox_currentIndexChanged(int index);
+ void on_lineColorBox_currentIndexChanged(int index);
};
class KisDlgStrokeSelection : public KoDialog
{
Q_OBJECT
-
public:
KisDlgStrokeSelection(KisImageWSP image, KisViewManager *view, bool isVectorLayer);
~KisDlgStrokeSelection() override;
+
int getLineSize() const;
linePosition getLinePosition() const;
KoColor getSelectedColor() const;
bool isBrushSelected() const;
KoColor getFillSelectedColor() const;
StrokeSelectionOptions getParams() const;
- void lockVectorLayerFunctions();
- void unlockVectorLayerFunctions();
private:
- WdgStrokeSelection * m_page;
+ WdgStrokeSelection *m_page {0};
KisImageWSP m_image;
- KoCanvasResourceProvider *m_resourceManager;
- KisDisplayColorConverter *converter;
+ KoCanvasResourceProvider *m_resourceManager {0};
+ KisDisplayColorConverter *converter {0};
+ bool m_isVectorLayer {false};
private Q_SLOTS:
void setColorFillButton();
void setColorButton();
void colorChanged(const QColor &newColor);
void colorFillChanged(const QColor &newColor);
};
#endif // KIS_DLG_STROKE_SEL_PROPERTIES_H_
diff --git a/libs/ui/flake/KisReferenceImagesLayer.cpp b/libs/ui/flake/KisReferenceImagesLayer.cpp
index e029ce6149..cbd094995e 100644
--- a/libs/ui/flake/KisReferenceImagesLayer.cpp
+++ b/libs/ui/flake/KisReferenceImagesLayer.cpp
@@ -1,234 +1,235 @@
/*
* Copyright (C) 2017 Jouni Pentikäinen <joupent@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 <KoShapeCreateCommand.h>
#include <KoShapeDeleteCommand.h>
#include <kis_node_visitor.h>
#include <kis_processing_visitor.h>
#include <kis_shape_layer_canvas.h>
#include "KisReferenceImagesLayer.h"
#include "KisReferenceImage.h"
#include "KisDocument.h"
struct AddReferenceImagesCommand : KoShapeCreateCommand
{
AddReferenceImagesCommand(KisDocument *document, KisSharedPtr<KisReferenceImagesLayer> layer, const QList<KoShape*> referenceImages)
: KoShapeCreateCommand(layer->shapeController(), referenceImages, layer.data(), nullptr, kundo2_i18n("Add reference image"))
, m_document(document)
, m_layer(layer)
{}
void redo() override {
auto layer = m_document->referenceImagesLayer();
KIS_SAFE_ASSERT_RECOVER_NOOP(!layer || layer == m_layer);
if (!layer) {
m_document->setReferenceImagesLayer(m_layer, true);
}
KoShapeCreateCommand::redo();
}
void undo() override {
KoShapeCreateCommand::undo();
if (m_layer->shapeCount() == 0) {
m_document->setReferenceImagesLayer(nullptr, true);
}
}
private:
KisDocument *m_document;
KisSharedPtr<KisReferenceImagesLayer> m_layer;
};
struct RemoveReferenceImagesCommand : KoShapeDeleteCommand
{
RemoveReferenceImagesCommand(KisDocument *document, KisSharedPtr<KisReferenceImagesLayer> layer, QList<KoShape *> referenceImages)
: KoShapeDeleteCommand(layer->shapeController(), referenceImages)
, m_document(document)
, m_layer(layer)
{}
void redo() override {
KoShapeDeleteCommand::redo();
if (m_layer->shapeCount() == 0) {
m_document->setReferenceImagesLayer(nullptr, true);
}
}
void undo() override {
auto layer = m_document->referenceImagesLayer();
KIS_SAFE_ASSERT_RECOVER_NOOP(!layer || layer == m_layer);
if (!layer) {
m_document->setReferenceImagesLayer(m_layer, true);
}
KoShapeDeleteCommand::undo();
}
private:
KisDocument *m_document;
KisSharedPtr<KisReferenceImagesLayer> m_layer;
};
class ReferenceImagesCanvas : public KisShapeLayerCanvasBase
{
public:
ReferenceImagesCanvas(KisReferenceImagesLayer *parent, KisImageWSP image)
: KisShapeLayerCanvasBase(parent, image)
, m_layer(parent)
{}
void updateCanvas(const QRectF &rect) override
{
if (!m_layer->image() || m_isDestroying) {
return;
}
QRectF r = m_viewConverter->documentToView(rect);
m_layer->signalUpdate(r);
}
void forceRepaint() override
{
m_layer->signalUpdate(m_layer->boundingImageRect());
}
bool hasPendingUpdates() const override
{
return false;
}
void rerenderAfterBeingInvisible() override {}
void resetCache() override {}
void setImage(KisImageWSP image) override
{
m_viewConverter->setImage(image);
}
private:
KisReferenceImagesLayer *m_layer;
};
KisReferenceImagesLayer::KisReferenceImagesLayer(KoShapeControllerBase* shapeController, KisImageWSP image)
: KisShapeLayer(shapeController, image, i18n("Reference images"), OPACITY_OPAQUE_U8, new ReferenceImagesCanvas(this, image))
{}
KisReferenceImagesLayer::KisReferenceImagesLayer(const KisReferenceImagesLayer &rhs)
: KisShapeLayer(rhs, rhs.shapeController(), new ReferenceImagesCanvas(this, rhs.image()))
{}
KUndo2Command * KisReferenceImagesLayer::addReferenceImages(KisDocument *document, const QList<KoShape*> referenceImages)
{
KisSharedPtr<KisReferenceImagesLayer> layer = document->referenceImagesLayer();
if (!layer) {
layer = new KisReferenceImagesLayer(document->shapeController(), document->image());
}
return new AddReferenceImagesCommand(document, layer, referenceImages);
}
KUndo2Command * KisReferenceImagesLayer::removeReferenceImages(KisDocument *document, QList<KoShape*> referenceImages)
{
return new RemoveReferenceImagesCommand(document, this, referenceImages);
}
QVector<KisReferenceImage*> KisReferenceImagesLayer::referenceImages() const
{
QVector<KisReferenceImage*> references;
Q_FOREACH(auto shape, shapes()) {
KisReferenceImage *referenceImage = dynamic_cast<KisReferenceImage*>(shape);
if (referenceImage) {
references.append(referenceImage);
}
}
return references;
}
void KisReferenceImagesLayer::paintReferences(QPainter &painter) {
- shapeManager()->paint(painter, *converter(), false);
+ painter.setTransform(converter()->documentToView(), true);
+ shapeManager()->paint(painter, false);
}
bool KisReferenceImagesLayer::allowAsChild(KisNodeSP) const
{
return false;
}
bool KisReferenceImagesLayer::accept(KisNodeVisitor &visitor)
{
return visitor.visit(this);
}
void KisReferenceImagesLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
visitor.visit(this, undoAdapter);
}
bool KisReferenceImagesLayer::isFakeNode() const
{
return true;
}
KUndo2Command *KisReferenceImagesLayer::setProfile(const KoColorProfile *profile)
{
// references should not be converted with the image
Q_UNUSED(profile);
return 0;
}
KUndo2Command *KisReferenceImagesLayer::convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
// references should not be converted with the image
Q_UNUSED(dstColorSpace);
Q_UNUSED(renderingIntent);
Q_UNUSED(conversionFlags);
return 0;
}
void KisReferenceImagesLayer::signalUpdate(const QRectF &rect)
{
emit sigUpdateCanvas(rect);
}
QRectF KisReferenceImagesLayer::boundingImageRect() const
{
return converter()->documentToView(boundingRect());
}
QColor KisReferenceImagesLayer::getPixel(QPointF position) const
{
const QPointF docPoint = converter()->viewToDocument(position);
KoShape *shape = shapeManager()->shapeAt(docPoint);
if (shape) {
auto *reference = dynamic_cast<KisReferenceImage*>(shape);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, QColor());
return reference->getPixel(docPoint);
}
return QColor();
}
diff --git a/libs/ui/flake/kis_node_shape.cpp b/libs/ui/flake/kis_node_shape.cpp
index 12ad1efecf..f7c599384a 100644
--- a/libs/ui/flake/kis_node_shape.cpp
+++ b/libs/ui/flake/kis_node_shape.cpp
@@ -1,152 +1,152 @@
/*
* 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_node_shape.h"
#include <KoCanvasBase.h>
#include <KoCanvasController.h>
#include <KoSelection.h>
#include <KoToolManager.h>
#include <kis_types.h>
#include <kis_layer.h>
#include <kis_node.h>
#include <KoSelectedShapesProxy.h>
#include "kis_shape_layer.h"
struct KisNodeShape::Private
{
public:
KisNodeSP node;
};
KisNodeShape::KisNodeShape(KisNodeSP node)
: KoShapeLayer()
, m_d(new Private())
{
m_d->node = node;
setShapeId(KIS_NODE_SHAPE_ID);
setSelectable(false);
connect(node, SIGNAL(sigNodeChangedInternal()), SLOT(editabilityChanged()));
editabilityChanged(); // Correctly set the lock at loading
}
KisNodeShape::~KisNodeShape()
{
if (KoToolManager::instance()) {
KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
// If we're the active layer, we should tell the active selection we're dead meat.
if (canvasController && canvasController->canvas()) {
KoSelection *activeSelection = canvasController->canvas()->selectedShapesProxy()->selection();
KoShapeLayer *activeLayer = activeSelection->activeLayer();
if (activeLayer == this){
activeSelection->setActiveLayer(0);
}
}
}
delete m_d;
}
KisNodeSP KisNodeShape::node()
{
return m_d->node;
}
bool KisNodeShape::checkIfDescendant(KoShapeLayer *activeLayer)
{
bool found(false);
KoShapeLayer *layer = activeLayer;
while(layer && !(found = layer == this)) {
layer = dynamic_cast<KoShapeLayer*>(layer->parent());
}
return found;
}
void KisNodeShape::editabilityChanged()
{
if (m_d->node->inherits("KisShapeLayer")) {
setGeometryProtected(!m_d->node->isEditable());
} else {
setGeometryProtected(false);
}
Q_FOREACH (KoShape *shape, this->shapes()) {
KisNodeShape *node = dynamic_cast<KisNodeShape*>(shape);
KIS_SAFE_ASSERT_RECOVER(node) { continue; }
if (node) {
node->editabilityChanged();
}
}
/**
* Editability of a child depends on the editablity
* of its parent. So when we change one's editability,
* we need to search for active children and reactivate them
*/
KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
if(canvasController && canvasController->canvas()) {
KoSelection *activeSelection = canvasController->canvas()->selectedShapesProxy()->selection();
KoShapeLayer *activeLayer = activeSelection->activeLayer();
KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(m_d->node.data());
if(activeLayer && (checkIfDescendant(activeLayer) || (shapeLayer && shapeLayer == activeLayer))) {
activeSelection->setActiveLayer(activeLayer);
}
}
}
QSizeF KisNodeShape::size() const
{
return boundingRect().size();
}
QRectF KisNodeShape::boundingRect() const
{
return QRectF();
}
void KisNodeShape::setPosition(const QPointF &)
{
}
-void KisNodeShape::paint(QPainter &, const KoViewConverter &, KoShapePaintingContext &)
+void KisNodeShape::paint(QPainter &, KoShapePaintingContext &) const
{
}
void KisNodeShape::saveOdf(KoShapeSavingContext &) const
{
}
bool KisNodeShape::loadOdf(const KoXmlElement &, KoShapeLoadingContext &)
{
return false;
}
diff --git a/libs/ui/flake/kis_node_shape.h b/libs/ui/flake/kis_node_shape.h
index 686a271f5c..dbe4b3e85d 100644
--- a/libs/ui/flake/kis_node_shape.h
+++ b/libs/ui/flake/kis_node_shape.h
@@ -1,66 +1,64 @@
/*
* 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_NODE_SHAPE_H_
#define KIS_NODE_SHAPE_H_
#include <QObject>
#include <KoShapeLayer.h>
#include <kritaui_export.h>
#include <kis_types.h>
-class KoViewConverter;
-
#define KIS_NODE_SHAPE_ID "KisNodeShape"
/**
* A KisNodeShape is a flake wrapper around Krita nodes. It is used
* for dealing with currently active node for tools.
*/
class KRITAUI_EXPORT KisNodeShape : public QObject, public KoShapeLayer
{
Q_OBJECT
public:
KisNodeShape(KisNodeSP node);
~KisNodeShape() override;
KisNodeSP node();
// Empty implementations as the node is not painted anywhere
QSizeF size() const override;
QRectF boundingRect() const override;
void setPosition(const QPointF &) override;
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
+ void paint(QPainter &painter, KoShapePaintingContext &paintcontext) const override;
void saveOdf(KoShapeSavingContext & context) const override;
bool loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) override;
private Q_SLOTS:
void editabilityChanged();
private:
bool checkIfDescendant(KoShapeLayer *activeLayer);
private:
struct Private;
Private * const m_d;
};
#endif
diff --git a/libs/ui/flake/kis_shape_layer.cc b/libs/ui/flake/kis_shape_layer.cc
index 4757138e8d..82081860c9 100644
--- a/libs/ui/flake/kis_shape_layer.cc
+++ b/libs/ui/flake/kis_shape_layer.cc
@@ -1,754 +1,754 @@
/*
* Copyright (c) 2006-2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Thomas Zander <zander@kde.org>
* Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Jan Hambrecht <jaham@gmx.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_shape_layer.h"
#include <QPainter>
#include <QPainterPath>
#include <QRect>
#include <QDomElement>
#include <QDomDocument>
#include <QString>
#include <QList>
#include <QMap>
#include <kis_debug.h>
#include <kundo2command.h>
#include <commands_new/kis_node_move_command2.h>
#include <QMimeData>
#include <kis_icon.h>
#include <KoColorSpace.h>
#include <KoCompositeOp.h>
#include <KisDocument.h>
#include <KoUnit.h>
#include <KoOdf.h>
#include <KoOdfReadStore.h>
#include <KoOdfStylesReader.h>
#include <KoOdfLoadingContext.h>
#include <KoPageLayout.h>
#include <KoShapeContainer.h>
#include <KoShapeLayer.h>
#include <KoShapeGroup.h>
#include <KoShapeLoadingContext.h>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxy.h>
#include <KoShapeRegistry.h>
#include <KoShapeSavingContext.h>
#include <KoStore.h>
#include <KoShapeControllerBase.h>
#include <KoStoreDevice.h>
#include <KoViewConverter.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoSelection.h>
#include <KoShapeMoveCommand.h>
#include <KoShapeTransformCommand.h>
#include <KoShapeShadow.h>
#include <KoShapeShadowCommand.h>
#include "SvgWriter.h"
#include "SvgParser.h"
#include <kis_types.h>
#include <kis_image.h>
#include "kis_default_bounds.h"
#include <kis_paint_device.h>
#include "kis_shape_layer_canvas.h"
#include "kis_image_view_converter.h"
#include <kis_painter.h>
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_effect_mask.h"
#include "commands/KoShapeReorderCommand.h"
#include "kis_do_something_command.h"
#include <SimpleShapeContainerModel.h>
class ShapeLayerContainerModel : public SimpleShapeContainerModel
{
public:
ShapeLayerContainerModel(KisShapeLayer *parent)
: q(parent)
{}
void add(KoShape *child) override {
SimpleShapeContainerModel::add(child);
/**
* The shape is always added with the absolute transformation set appropriately.
* Here we should just squeeze it into the layer's transformation.
*/
KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child));
if (inheritsTransform(child)) {
- QTransform parentTransform = q->absoluteTransformation(0);
+ QTransform parentTransform = q->absoluteTransformation();
child->applyAbsoluteTransformation(parentTransform.inverted());
}
}
void remove(KoShape *child) override {
KIS_SAFE_ASSERT_RECOVER_NOOP(inheritsTransform(child));
if (inheritsTransform(child)) {
- QTransform parentTransform = q->absoluteTransformation(0);
+ QTransform parentTransform = q->absoluteTransformation();
child->applyAbsoluteTransformation(parentTransform);
}
SimpleShapeContainerModel::remove(child);
}
void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override {
q->shapeManager()->addShape(shape);
SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree);
}
void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override {
q->shapeManager()->remove(shape);
SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree);
}
private:
KisShapeLayer *q;
};
struct KisShapeLayer::Private
{
public:
Private()
: canvas(0)
, controller(0)
, x(0)
, y(0)
{}
KisPaintDeviceSP paintDevice;
KisShapeLayerCanvasBase * canvas;
KoShapeControllerBase* controller;
int x;
int y;
};
KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller,
KisImageWSP image,
const QString &name,
quint8 opacity)
: KisExternalLayer(image, name, opacity),
KoShapeLayer(new ShapeLayerContainerModel(this)),
m_d(new Private())
{
initShapeLayer(controller);
}
KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs)
: KisShapeLayer(rhs, rhs.m_d->controller)
{
}
KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeControllerBase* controller, KisShapeLayerCanvasBase *canvas)
: KisExternalLayer(_rhs)
, KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel
, m_d(new Private())
{
// copy the projection to avoid extra round of updates!
initShapeLayer(controller, _rhs.m_d->paintDevice, canvas);
/**
* The transformaitons of the added shapes are automatically merged into the transformation
* of the layer, so we should apply this extra transform separately
*/
- const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted();
+ const QTransform thisInvertedTransform = this->absoluteTransformation().inverted();
m_d->canvas->shapeManager()->setUpdatesBlocked(true);
Q_FOREACH (KoShape *shape, _rhs.shapes()) {
KoShape *clonedShape = shape->cloneShape();
KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; }
- clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform);
+ clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform);
addShape(clonedShape);
}
m_d->canvas->shapeManager()->setUpdatesBlocked(false);
}
KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes)
: KisExternalLayer(_rhs)
, KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel
, m_d(new Private())
{
// Make sure our new layer is visible otherwise the shapes cannot be painted.
setVisible(true);
initShapeLayer(_rhs.m_d->controller);
/**
* With current implementation this matrix will always be an identity, because
* we do not copy the transformation from any of the source layers. But we should
* handle this anyway, to not be caught by this in the future.
*/
- const QTransform thisInvertedTransform = this->absoluteTransformation(0).inverted();
+ const QTransform thisInvertedTransform = this->absoluteTransformation().inverted();
QList<KoShape *> shapesAbove;
QList<KoShape *> shapesBelow;
// copy in _rhs's shapes
Q_FOREACH (KoShape *shape, _rhs.shapes()) {
KoShape *clonedShape = shape->cloneShape();
KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; }
- clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform);
+ clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform);
shapesBelow.append(clonedShape);
}
// copy in _addShapes's shapes
Q_FOREACH (KoShape *shape, _addShapes.shapes()) {
KoShape *clonedShape = shape->cloneShape();
KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; }
- clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform);
+ clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform);
shapesAbove.append(clonedShape);
}
QList<KoShapeReorderCommand::IndexedShape> shapes =
KoShapeReorderCommand::mergeDownShapes(shapesBelow, shapesAbove);
KoShapeReorderCommand cmd(shapes);
cmd.redo();
Q_FOREACH (KoShape *shape, shapesBelow + shapesAbove) {
addShape(shape);
}
}
KisShapeLayer::KisShapeLayer(KoShapeControllerBase* controller,
KisImageWSP image,
const QString &name,
quint8 opacity,
KisShapeLayerCanvasBase *canvas)
: KisExternalLayer(image, name, opacity)
, KoShapeLayer(new ShapeLayerContainerModel(this))
, m_d(new Private())
{
initShapeLayer(controller, nullptr, canvas);
}
KisShapeLayer::~KisShapeLayer()
{
/**
* Small hack alert: we should avoid updates on shape deletion
*/
m_d->canvas->prepareForDestroying();
Q_FOREACH (KoShape *shape, shapes()) {
shape->setParent(0);
delete shape;
}
delete m_d->canvas;
delete m_d;
}
void KisShapeLayer::initShapeLayer(KoShapeControllerBase* controller, KisPaintDeviceSP copyFromProjection, KisShapeLayerCanvasBase *canvas)
{
setSupportsLodMoves(false);
setShapeId(KIS_SHAPE_LAYER_ID);
KIS_ASSERT_RECOVER_NOOP(this->image());
if (!copyFromProjection) {
m_d->paintDevice = new KisPaintDevice(image()->colorSpace());
m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image()));
m_d->paintDevice->setParentNode(this);
} else {
m_d->paintDevice = new KisPaintDevice(*copyFromProjection);
}
if (!canvas) {
auto *slCanvas = new KisShapeLayerCanvas(this, image());
slCanvas->setProjection(m_d->paintDevice);
canvas = slCanvas;
}
m_d->canvas = canvas;
m_d->canvas->moveToThread(this->thread());
m_d->controller = controller;
m_d->canvas->shapeManager()->selection()->disconnect(this);
connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)),
this, SIGNAL(currentLayerChanged(const KoShapeLayer*)));
connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF)));
}
bool KisShapeLayer::allowAsChild(KisNodeSP node) const
{
return node->inherits("KisMask");
}
void KisShapeLayer::setImage(KisImageWSP _image)
{
KisLayer::setImage(_image);
m_d->canvas->setImage(_image);
m_d->paintDevice->convertTo(_image->colorSpace());
m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image));
}
KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer)
{
KisShapeLayer *prevShape = dynamic_cast<KisShapeLayer*>(prevLayer.data());
if (prevShape)
return new KisShapeLayer(*prevShape, *this);
else
return KisExternalLayer::createMergedLayerTemplate(prevLayer);
}
void KisShapeLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer)
{
if (!dynamic_cast<KisShapeLayer*>(dstLayer.data())) {
KisLayer::fillMergedLayerTemplate(dstLayer, prevLayer);
}
}
void KisShapeLayer::setParent(KoShapeContainer *parent)
{
Q_UNUSED(parent)
KIS_ASSERT_RECOVER_RETURN(0);
}
QIcon KisShapeLayer::icon() const
{
return KisIconUtils::loadIcon("vectorLayer");
}
KisPaintDeviceSP KisShapeLayer::original() const
{
return m_d->paintDevice;
}
KisPaintDeviceSP KisShapeLayer::paintDevice() const
{
return 0;
}
qint32 KisShapeLayer::x() const
{
return m_d->x;
}
qint32 KisShapeLayer::y() const
{
return m_d->y;
}
void KisShapeLayer::setX(qint32 x)
{
qint32 delta = x - this->x();
QPointF diff = QPointF(m_d->canvas->viewConverter()->viewToDocumentX(delta), 0);
emit sigMoveShapes(diff);
// Save new value to satisfy LSP
m_d->x = x;
}
void KisShapeLayer::setY(qint32 y)
{
qint32 delta = y - this->y();
QPointF diff = QPointF(0, m_d->canvas->viewConverter()->viewToDocumentY(delta));
emit sigMoveShapes(diff);
// Save new value to satisfy LSP
m_d->y = y;
}
namespace {
void filterTransformableShapes(QList<KoShape*> &shapes)
{
auto it = shapes.begin();
while (it != shapes.end()) {
if (shapes.size() == 1) break;
if ((*it)->inheritsTransformFromAny(shapes)) {
it = shapes.erase(it);
} else {
++it;
}
}
}
}
QList<KoShape *> KisShapeLayer::shapesToBeTransformed()
{
QList<KoShape*> shapes = shapeManager()->shapes();
// We expect that **all** the shapes inherit the transform from its parent
// SANITY_CHECK: we expect all the shapes inside the
// shape layer to inherit transform!
Q_FOREACH (KoShape *shape, shapes) {
if (shape->parent()) {
KIS_SAFE_ASSERT_RECOVER(shape->parent()->inheritsTransform(shape)) {
break;
}
}
}
shapes << this;
filterTransformableShapes(shapes);
return shapes;
}
void KisShapeLayer::slotMoveShapes(const QPointF &diff)
{
QList<KoShape*> shapes = shapesToBeTransformed();
if (shapes.isEmpty()) return;
KoShapeMoveCommand cmd(shapes, diff);
cmd.redo();
}
bool KisShapeLayer::accept(KisNodeVisitor& visitor)
{
return visitor.visit(this);
}
void KisShapeLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
KoShapeManager* KisShapeLayer::shapeManager() const
{
return m_d->canvas->shapeManager();
}
KoViewConverter* KisShapeLayer::converter() const
{
return m_d->canvas->viewConverter();
}
bool KisShapeLayer::visible(bool recursive) const
{
return KisExternalLayer::visible(recursive);
}
void KisShapeLayer::setVisible(bool visible, bool isLoading)
{
const bool oldVisible = this->visible(false);
KoShapeLayer::setVisible(visible);
KisExternalLayer::setVisible(visible, isLoading);
if (visible && !oldVisible &&
m_d->canvas->hasChangedWhileBeingInvisible()) {
m_d->canvas->rerenderAfterBeingInvisible();
}
}
void KisShapeLayer::setUserLocked(bool value)
{
KoShapeLayer::setGeometryProtected(value);
KisExternalLayer::setUserLocked(value);
}
bool KisShapeLayer::isShapeEditable(bool recursive) const
{
return KoShapeLayer::isShapeEditable(recursive) && isEditable(true);
}
// we do not override KoShape::setGeometryProtected() as we consider
// the user not being able to access the layer shape from Krita UI!
void KisShapeLayer::forceUpdateTimedNode()
{
m_d->canvas->forceRepaint();
}
bool KisShapeLayer::hasPendingTimedUpdates() const
{
return m_d->canvas->hasPendingUpdates();
}
void KisShapeLayer::forceUpdateHiddenAreaOnOriginal()
{
m_d->canvas->forceRepaintWithHiddenAreas();
}
bool KisShapeLayer::saveShapesToStore(KoStore *store, QList<KoShape *> shapes, const QSizeF &sizeInPt)
{
if (!store->open("content.svg")) {
return false;
}
KoStoreDevice storeDev(store);
storeDev.open(QIODevice::WriteOnly);
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
SvgWriter writer(shapes);
writer.save(storeDev, sizeInPt);
if (!store->close()) {
return false;
}
return true;
}
QList<KoShape *> KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize)
{
QString errorMsg;
int errorLine = 0;
int errorColumn;
KoXmlDocument doc = SvgParser::createDocumentFromSvg(device, &errorMsg, &errorLine, &errorColumn);
if (doc.isNull()) {
errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3"
, errorLine , errorColumn , errorMsg);
}
SvgParser parser(resourceManager);
parser.setXmlBaseDir(baseXmlDir);
parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */);
return parser.parseSvg(doc.documentElement(), fragmentSize);
}
bool KisShapeLayer::saveLayer(KoStore * store) const
{
// FIXME: we handle xRes() only!
const QSizeF sizeInPx = image()->bounds().size();
const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes());
return saveShapesToStore(store, this->shapes(), sizeInPt);
}
bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir)
{
QSizeF fragmentSize; // unused!
KisImageSP image = this->image();
// FIXME: we handle xRes() only!
KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes()));
const qreal resolutionPPI = 72.0 * image->xRes();
QList<KoShape*> shapes =
createShapesFromSvg(device, baseXmlDir,
image->bounds(), resolutionPPI,
m_d->controller->resourceManager(),
&fragmentSize);
Q_FOREACH (KoShape *shape, shapes) {
addShape(shape);
}
return true;
}
bool KisShapeLayer::loadLayer(KoStore* store)
{
if (!store) {
warnKrita << i18n("No store backend");
return false;
}
if (store->open("content.svg")) {
KoStoreDevice storeDev(store);
storeDev.open(QIODevice::ReadOnly);
loadSvg(&storeDev, "");
store->close();
return true;
}
KoOdfReadStore odfStore(store);
QString errorMessage;
odfStore.loadAndParse(errorMessage);
if (!errorMessage.isEmpty()) {
warnKrita << errorMessage;
return false;
}
KoXmlElement contents = odfStore.contentDoc().documentElement();
// dbgKrita <<"Start loading OASIS document..." << contents.text();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement();
KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body"));
if (body.isNull()) {
//setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) );
return false;
}
body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing");
if (body.isNull()) {
//setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) );
return false;
}
KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page"));
if (page.isNull()) {
//setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) );
return false;
}
KoXmlElement * master = 0;
if (odfStore.styles().masterPages().contains("Standard"))
master = odfStore.styles().masterPages().value("Standard");
else if (odfStore.styles().masterPages().contains("Default"))
master = odfStore.styles().masterPages().value("Default");
else if (! odfStore.styles().masterPages().empty())
master = odfStore.styles().masterPages().begin().value();
if (master) {
const KoXmlElement *style = odfStore.styles().findStyle(
master->attributeNS(KoXmlNS::style, "page-layout-name", QString()));
KoPageLayout pageLayout;
pageLayout.loadOdf(*style);
setSize(QSizeF(pageLayout.width, pageLayout.height));
}
// We work fine without a master page
KoOdfLoadingContext context(odfStore.styles(), odfStore.store());
context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml");
KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager());
KoXmlElement layerElement;
forEachElement(layerElement, context.stylesReader().layerSet()) {
// FIXME: investigate what is this
// KoShapeLayer * l = new KoShapeLayer();
if (!loadOdf(layerElement, shapeContext)) {
dbgKrita << "Could not load vector layer!";
return false;
}
}
KoXmlElement child;
forEachElement(child, page) {
KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext);
if (shape) {
addShape(shape);
}
}
return true;
}
void KisShapeLayer::resetCache()
{
m_d->canvas->resetCache();
}
KUndo2Command* KisShapeLayer::crop(const QRect & rect)
{
QPoint oldPos(x(), y());
QPoint newPos = oldPos - rect.topLeft();
return new KisNodeMoveCommand2(this, oldPos, newPos);
}
KUndo2Command* KisShapeLayer::transform(const QTransform &transform) {
QList<KoShape*> shapes = shapesToBeTransformed();
if (shapes.isEmpty()) return 0;
KisImageViewConverter *converter = dynamic_cast<KisImageViewConverter*>(this->converter());
QTransform realTransform = converter->documentToView() *
transform * converter->viewToDocument();
QList<QTransform> oldTransformations;
QList<QTransform> newTransformations;
QList<KoShapeShadow*> newShadows;
const qreal transformBaseScale = KoUnit::approxTransformScale(transform);
Q_FOREACH (const KoShape* shape, shapes) {
QTransform oldTransform = shape->transformation();
oldTransformations.append(oldTransform);
- QTransform globalTransform = shape->absoluteTransformation(0);
+ QTransform globalTransform = shape->absoluteTransformation();
QTransform localTransform = globalTransform * realTransform * globalTransform.inverted();
newTransformations.append(localTransform * oldTransform);
KoShapeShadow *shadow = 0;
if (shape->shadow()) {
shadow = new KoShapeShadow(*shape->shadow());
shadow->setOffset(transformBaseScale * shadow->offset());
shadow->setBlur(transformBaseScale * shadow->blur());
}
newShadows.append(shadow);
}
KUndo2Command *parentCommand = new KUndo2Command();
new KoShapeTransformCommand(shapes,
oldTransformations,
newTransformations,
parentCommand);
new KoShapeShadowCommand(shapes,
newShadows,
parentCommand);
return parentCommand;
}
KUndo2Command *KisShapeLayer::setProfile(const KoColorProfile *profile)
{
using namespace KisDoSomethingCommandOps;
KUndo2Command *cmd = new KUndo2Command();
new KisDoSomethingCommand<ResetOp, KisShapeLayer*>(this, false, cmd);
m_d->paintDevice->setProfile(profile, cmd);
new KisDoSomethingCommand<ResetOp, KisShapeLayer*>(this, true, cmd);
return cmd;
}
KUndo2Command *KisShapeLayer::convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
using namespace KisDoSomethingCommandOps;
KUndo2Command *cmd = new KUndo2Command();
new KisDoSomethingCommand<ResetOp, KisShapeLayer*>(this, false, cmd);
m_d->paintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags, cmd);
new KisDoSomethingCommand<ResetOp, KisShapeLayer*>(this, true, cmd);
return cmd;
}
KoShapeControllerBase *KisShapeLayer::shapeController() const
{
return m_d->controller;
}
diff --git a/libs/ui/flake/kis_shape_layer_canvas.cpp b/libs/ui/flake/kis_shape_layer_canvas.cpp
index 3676366f82..cc9b2f8448 100644
--- a/libs/ui/flake/kis_shape_layer_canvas.cpp
+++ b/libs/ui/flake/kis_shape_layer_canvas.cpp
@@ -1,426 +1,438 @@
/*
* 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_shape_layer_canvas.h"
#include <QPainter>
#include <QMutexLocker>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxySimple.h>
#include <KoViewConverter.h>
#include <KoColorSpace.h>
#include <kis_paint_device.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_painter.h>
#include <flake/kis_shape_layer.h>
#include <KoCompositeOpRegistry.h>
#include <KoSelection.h>
#include <KoUnit.h>
#include "kis_image_view_converter.h"
#include <kis_debug.h>
#include <QThread>
#include <QApplication>
#include <kis_spontaneous_job.h>
#include "kis_global.h"
-
-//#define DEBUG_REPAINT
+#include "krita_utils.h"
KisShapeLayerCanvasBase::KisShapeLayerCanvasBase(KisShapeLayer *parent, KisImageWSP image)
: KoCanvasBase(0)
, m_viewConverter(new KisImageViewConverter(image))
, m_shapeManager(new KoShapeManager(this))
, m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
{
m_shapeManager->selection()->setActiveLayer(parent);
}
KoShapeManager *KisShapeLayerCanvasBase::shapeManager() const
{
return m_shapeManager.data();
}
KoSelectedShapesProxy *KisShapeLayerCanvasBase::selectedShapesProxy() const
{
return m_selectedShapesProxy.data();
}
KoViewConverter* KisShapeLayerCanvasBase::viewConverter() const
{
return m_viewConverter.data();
}
void KisShapeLayerCanvasBase::gridSize(QPointF *offset, QSizeF *spacing) const
{
KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
Q_UNUSED(offset);
Q_UNUSED(spacing);
}
bool KisShapeLayerCanvasBase::snapToGrid() const
{
KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
return false;
}
void KisShapeLayerCanvasBase::addCommand(KUndo2Command *)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
}
KoToolProxy * KisShapeLayerCanvasBase::toolProxy() const
{
// KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
return 0;
}
QWidget* KisShapeLayerCanvasBase::canvasWidget()
{
return 0;
}
const QWidget* KisShapeLayerCanvasBase::canvasWidget() const
{
return 0;
}
KoUnit KisShapeLayerCanvasBase::unit() const
{
KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
return KoUnit(KoUnit::Point);
}
void KisShapeLayerCanvasBase::prepareForDestroying()
{
m_isDestroying = true;
}
bool KisShapeLayerCanvasBase::hasChangedWhileBeingInvisible()
{
return m_hasChangedWhileBeingInvisible;
}
KisShapeLayerCanvas::KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP image)
: KisShapeLayerCanvasBase(parent, image)
, m_projection(0)
, m_parentLayer(parent)
- , m_canvasUpdateCompressor(100, KisSignalCompressor::FIRST_INACTIVE)
, m_asyncUpdateSignalCompressor(100, KisSignalCompressor::FIRST_INACTIVE)
{
/**
* The layour should also add itself to its own shape manager, so that the canvas
* would track its changes/transformations
*/
m_shapeManager->addShape(parent, KoShapeManager::AddWithoutRepaint);
m_shapeManager->selection()->setActiveLayer(parent);
connect(&m_asyncUpdateSignalCompressor, SIGNAL(timeout()), SLOT(slotStartAsyncRepaint()));
- connect(this, SIGNAL(forwardRepaint()), &m_canvasUpdateCompressor, SLOT(start()));
- connect(&m_canvasUpdateCompressor, SIGNAL(timeout()), this, SLOT(slotStartDirectSyncRepaint()));
setImage(image);
}
KisShapeLayerCanvas::~KisShapeLayerCanvas()
{
m_shapeManager->remove(m_parentLayer);
}
void KisShapeLayerCanvas::setImage(KisImageWSP image)
{
if (m_image) {
disconnect(m_image, 0, this, 0);
}
m_viewConverter->setImage(image);
m_image = image;
if (image) {
connect(m_image, SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged()));
m_cachedImageRect = m_image->bounds();
}
-
- updateUpdateCompressorDelay();
}
-
-#ifdef DEBUG_REPAINT
-# include <stdlib.h>
-#endif
-
-
class KisRepaintShapeLayerLayerJob : public KisSpontaneousJob
{
public:
KisRepaintShapeLayerLayerJob(KisShapeLayerSP layer, KisShapeLayerCanvas *canvas)
: m_layer(layer),
m_canvas(canvas)
{
}
bool overrides(const KisSpontaneousJob *_otherJob) override {
const KisRepaintShapeLayerLayerJob *otherJob =
dynamic_cast<const KisRepaintShapeLayerLayerJob*>(_otherJob);
return otherJob && otherJob->m_canvas == m_canvas;
}
void run() override {
m_canvas->repaint();
}
int levelOfDetail() const override {
return 0;
}
QString debugName() const override {
QString result;
QDebug dbg(&result);
dbg << "KisRepaintShapeLayerLayerJob" << m_layer;
return result;
}
private:
// we store a pointer to the layer just
// to keep the lifetime of the canvas!
KisShapeLayerSP m_layer;
KisShapeLayerCanvas *m_canvas;
};
void KisShapeLayerCanvas::updateCanvas(const QVector<QRectF> &region)
{
if (!m_parentLayer->image() || m_isDestroying) {
return;
}
{
QMutexLocker locker(&m_dirtyRegionMutex);
Q_FOREACH (const QRectF &rc, region) {
// grow for antialiasing
const QRect imageRect = kisGrowRect(m_viewConverter->documentToView(rc).toAlignedRect(), 2);
m_dirtyRegion += imageRect;
}
}
+ m_asyncUpdateSignalCompressor.start();
+ m_hasUpdateInCompressor = true;
+}
+
+
+void KisShapeLayerCanvas::updateCanvas(const QRectF& rc)
+{
+ updateCanvas(QVector<QRectF>({rc}));
+}
+
+void KisShapeLayerCanvas::slotStartAsyncRepaint()
+{
+ QRect repaintRect;
+ bool forceUpdateHiddenAreasOnly = false;
+ const qint32 MASK_IMAGE_WIDTH = 256;
+ const qint32 MASK_IMAGE_HEIGHT = 256;
+ {
+ QMutexLocker locker(&m_dirtyRegionMutex);
+
+ repaintRect = m_dirtyRegion.boundingRect();
+ forceUpdateHiddenAreasOnly = m_forceUpdateHiddenAreasOnly;
+
+ /// Since we are going to override the previous jobs, we should fetch
+ /// all the area covered by it. Otherwise we'll get dirty leftovers of
+ /// the layer on the projection
+ Q_FOREACH (const KoShapeManager::PaintJob &job, m_paintJobs) {
+ repaintRect |= m_viewConverter->documentToView().mapRect(job.docUpdateRect).toAlignedRect();
+ }
+ m_paintJobs.clear();
+
+ m_dirtyRegion = QRegion();
+ m_forceUpdateHiddenAreasOnly = false;
+ }
+
+ if (!forceUpdateHiddenAreasOnly) {
+ if (repaintRect.isEmpty()) {
+ return;
+ }
+
+ // Crop the update rect by the image bounds. We keep the cache consistent
+ // by tracking the size of the image in slotImageSizeChanged()
+ repaintRect = repaintRect.intersected(m_parentLayer->image()->bounds());
+ } else {
+ const QRectF shapesBounds = KoShape::boundingRect(m_shapeManager->shapes());
+ repaintRect = kisGrowRect(m_viewConverter->documentToView(shapesBounds).toAlignedRect(), 2);
+ }
+
/**
- * HACK ALERT!
+ * Vector shapes are not thread-safe against concurrent read-writes, so we
+ * need to utilize rather complicated policy on accessing them:
*
- * The shapes may be accessed from both, GUI and worker threads! And we have no real
- * guard against this until the vector tools will be ported to the strokes framework.
+ * 1) All shape writes happen in GUI thread (right in the tools)
+ * 2) No concurrent reads from the shapes may happen in other threads
+ * while the user is modifying them.
*
- * Here we just avoid the most obvious conflict of threads:
+ * That is why our shape rendering code is split into two parts:
*
- * 1) If the layer is modified by a non-gui (worker) thread, use a spontaneous jobs
- * to rerender the canvas. The job will be executed (almost) exclusively and it is
- * the responsibility of the worker thread to add a barrier to wait until this job is
- * completed, and not try to access the shapes concurrently.
+ * 1) First we just fetch a shallow copy of the shapes of the layer (it
+ * takes about 1ms for complicated vecotor layers) and pack them into
+ * KoShapeManager::PaintJobsList jobs. It happens here, in
+ * slotStartAsyncRepaint(), which runs in the GUI thread. It guarantees
+ * that noone is accessing the shapes during the copy operation.
*
- * 2) If the layer is modified by a gui thread, it means that we are being accessed by
- * a legacy vector tool. It this case just emit a queued signal to make sure the updates
- * are compressed a little bit (TODO: add a compressor?)
+ * 2) The rendering itself happens in the worker thread in repaint(). But
+ * repaint() doesn't access original shapes anymore. It accesses only they
+ * shallow copies, which means that there is no concurrent
+ * access to anything (*).
+ *
+ * (*) "no concurrent access to anything" is a rather fragile term :) There
+ * will still be concurrent access to it, on detaching... But(!), when detaching,
+ * the original data is kept unchanged, so "it should be safe enough"(c). Especially
+ * if we guarantee that rendering thread may not cause a detach (?), and the detach
+ * can happen only from a single GUI thread.
*/
- if (qApp->thread() == QThread::currentThread()) {
- emit forwardRepaint();
- m_hasDirectSyncRepaintInitiated = true;
- } else {
- m_asyncUpdateSignalCompressor.start();
- m_hasUpdateInCompressor = true;
+ const QVector<QRect> updateRects =
+ KritaUtils::splitRectIntoPatchesTight(repaintRect,
+ QSize(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT));
+
+ KoShapeManager::PaintJobsList jobs;
+ Q_FOREACH (const QRect &viewUpdateRect, updateRects) {
+ jobs << KoShapeManager::PaintJob(m_viewConverter->viewToDocument().mapRect(QRectF(viewUpdateRect)),
+ viewUpdateRect);
}
-}
+ m_shapeManager->preparePaintJobs(jobs, m_parentLayer);
-void KisShapeLayerCanvas::updateCanvas(const QRectF& rc)
-{
- updateCanvas(QVector<QRectF>({rc}));
-}
+ {
+ QMutexLocker locker(&m_dirtyRegionMutex);
+ m_paintJobs = jobs;
+ }
-void KisShapeLayerCanvas::slotStartAsyncRepaint()
-{
m_hasUpdateInCompressor = false;
m_image->addSpontaneousJob(new KisRepaintShapeLayerLayerJob(m_parentLayer, this));
}
-void KisShapeLayerCanvas::slotStartDirectSyncRepaint()
-{
- m_hasDirectSyncRepaintInitiated = false;
- repaint();
-}
-
void KisShapeLayerCanvas::slotImageSizeChanged()
{
QRegion dirtyCacheRegion;
dirtyCacheRegion += m_image->bounds();
dirtyCacheRegion += m_cachedImageRect;
dirtyCacheRegion -= m_image->bounds() & m_cachedImageRect;
QVector<QRectF> dirtyRects;
Q_FOREACH (const QRect &rc, dirtyCacheRegion.rects()) {
dirtyRects.append(m_viewConverter->viewToDocument(rc));
}
updateCanvas(dirtyRects);
m_cachedImageRect = m_image->bounds();
- updateUpdateCompressorDelay();
}
void KisShapeLayerCanvas::repaint()
{
- QRect repaintRect;
- bool forceUpdateHiddenAreasOnly = false;
+
+ KoShapeManager::PaintJobsList paintJobs;
{
QMutexLocker locker(&m_dirtyRegionMutex);
- repaintRect = m_dirtyRegion.boundingRect();
- forceUpdateHiddenAreasOnly = m_forceUpdateHiddenAreasOnly;
-
- m_dirtyRegion = QRegion();
- m_forceUpdateHiddenAreasOnly = false;
- }
-
- if (!forceUpdateHiddenAreasOnly) {
- if (repaintRect.isEmpty()) {
- return;
- }
-
- // Crop the update rect by the image bounds. We keep the cache consistent
- // by tracking the size of the image in slotImageSizeChanged()
- repaintRect = repaintRect.intersected(m_parentLayer->image()->bounds());
- } else {
- const QRectF shapesBounds = KoShape::boundingRect(m_shapeManager->shapes());
- repaintRect = kisGrowRect(m_viewConverter->documentToView(shapesBounds).toAlignedRect(), 2);
+ std::swap(paintJobs, m_paintJobs);
}
- const QRect r = repaintRect;
const qint32 MASK_IMAGE_WIDTH = 256;
const qint32 MASK_IMAGE_HEIGHT = 256;
QImage image(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32);
QPainter tempPainter(&image);
tempPainter.setRenderHint(QPainter::Antialiasing);
tempPainter.setRenderHint(QPainter::TextAntialiasing);
quint8 * dstData = new quint8[MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT * m_projection->pixelSize()];
- for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) {
- for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) {
+ QRect repaintRect;
- image.fill(0);
- tempPainter.translate(-x, -y);
- tempPainter.setClipRect(QRect(x,y,MASK_IMAGE_WIDTH,MASK_IMAGE_HEIGHT));
+ Q_FOREACH (const KoShapeManager::PaintJob &job, paintJobs) {
+ image.fill(0);
- #ifdef DEBUG_REPAINT
- QColor color = QColor(random() % 255, random() % 255, random() % 255);
- maskPainter.fillRect(srcRect, color);
- #endif
+ tempPainter.setTransform(QTransform());
+ tempPainter.setClipRect(QRect(0,0,MASK_IMAGE_WIDTH,MASK_IMAGE_HEIGHT));
+ tempPainter.setTransform(m_viewConverter->documentToView() *
+ QTransform::fromTranslate(-job.viewUpdateRect.x(), -job.viewUpdateRect.y()));
- m_shapeManager->paint(tempPainter, *m_viewConverter, false);
+ m_shapeManager->paintJob(tempPainter, job, false);
- tempPainter.translate(x, y);
+ KoColorSpaceRegistry::instance()->rgb8()
+ ->convertPixelsTo(image.constBits(), dstData, m_projection->colorSpace(),
+ MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT,
+ KoColorConversionTransformation::internalRenderingIntent(),
+ KoColorConversionTransformation::internalConversionFlags());
- KoColorSpaceRegistry::instance()->rgb8()
- ->convertPixelsTo(image.constBits(), dstData, m_projection->colorSpace(),
- MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT,
- KoColorConversionTransformation::internalRenderingIntent(),
- KoColorConversionTransformation::internalConversionFlags());
+ // TODO: use job.viewUpdateRect instead of MASK_IMAGE_WIDTH/HEIGHT
+ m_projection->writeBytes(dstData,
+ job.viewUpdateRect.x(),
+ job.viewUpdateRect.y(),
+ MASK_IMAGE_WIDTH,
+ MASK_IMAGE_HEIGHT);
- m_projection->writeBytes(dstData, x, y, MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT);
- }
+ repaintRect |= job.viewUpdateRect;
}
+
delete[] dstData;
m_projection->purgeDefaultPixels();
m_parentLayer->setDirty(repaintRect);
m_hasChangedWhileBeingInvisible |= !m_parentLayer->visible(true);
}
void KisShapeLayerCanvas::forceRepaint()
{
/**
* WARNING! Although forceRepaint() may be called from different threads, it is
* not entirely safe. If the user plays with shapes at the same time (vector tools are
* not ported to strokes yet), the shapes my be accessed from two different places at
* the same time, which will cause a crash.
*
* The only real solution to this is to port vector tools to strokes framework.
*/
if (hasPendingUpdates()) {
m_asyncUpdateSignalCompressor.stop();
slotStartAsyncRepaint();
}
}
bool KisShapeLayerCanvas::hasPendingUpdates() const
{
- return m_hasUpdateInCompressor || m_hasDirectSyncRepaintInitiated;
+ return m_hasUpdateInCompressor;
}
void KisShapeLayerCanvas::forceRepaintWithHiddenAreas()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_parentLayer->image());
KIS_SAFE_ASSERT_RECOVER_RETURN(!m_isDestroying);
{
QMutexLocker locker(&m_dirtyRegionMutex);
m_forceUpdateHiddenAreasOnly = true;
}
m_asyncUpdateSignalCompressor.stop();
slotStartAsyncRepaint();
}
void KisShapeLayerCanvas::resetCache()
{
m_projection->clear();
QList<KoShape*> shapes = m_shapeManager->shapes();
Q_FOREACH (const KoShape* shape, shapes) {
shape->update();
}
}
void KisShapeLayerCanvas::rerenderAfterBeingInvisible()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_parentLayer->visible(true));
m_hasChangedWhileBeingInvisible = false;
resetCache();
}
-
-void KisShapeLayerCanvas::updateUpdateCompressorDelay()
-{
- if (m_cachedImageRect.width() * m_cachedImageRect.height() < 2480 * 3508) { // A4 300 DPI
- m_canvasUpdateCompressor.setDelay(25);
- } else if (m_cachedImageRect.width() * m_cachedImageRect.height() < 4961 * 7061) { // A4 600 DPI
- m_canvasUpdateCompressor.setDelay(100);
- } else { // Really big
- m_canvasUpdateCompressor.setDelay(500);
- }
-}
diff --git a/libs/ui/flake/kis_shape_layer_canvas.h b/libs/ui/flake/kis_shape_layer_canvas.h
index 9799b81b61..8c6c163bc0 100644
--- a/libs/ui/flake/kis_shape_layer_canvas.h
+++ b/libs/ui/flake/kis_shape_layer_canvas.h
@@ -1,141 +1,136 @@
/*
* 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_SHAPE_LAYER_CANVAS_H
#define KIS_SHAPE_LAYER_CANVAS_H
#include <QMutex>
#include <QRegion>
#include <KoCanvasBase.h>
#include <kis_types.h>
#include "kis_thread_safe_signal_compressor.h"
#include <KoSelectedShapesProxy.h>
#include <KoShapeManager.h>
#include <kis_image_view_converter.h>
class KoShapeManager;
class KoToolProxy;
class KoViewConverter;
class KUndo2Command;
class QWidget;
class KoUnit;
class KisImageViewConverter;
class KisShapeLayerCanvasBase : public KoCanvasBase
{
public:
KisShapeLayerCanvasBase(KisShapeLayer *parent, KisImageWSP image);
virtual void setImage(KisImageWSP image) = 0;
void prepareForDestroying();
virtual void forceRepaint() = 0;
virtual bool hasPendingUpdates() const = 0;
virtual void forceRepaintWithHiddenAreas() { forceRepaint(); }
bool hasChangedWhileBeingInvisible();
virtual void rerenderAfterBeingInvisible() = 0;
virtual void resetCache() = 0;
KoShapeManager *shapeManager() const override;
KoViewConverter *viewConverter() const override;
void gridSize(QPointF *offset, QSizeF *spacing) const override;
bool snapToGrid() const override;
void addCommand(KUndo2Command *command) override;
KoSelectedShapesProxy *selectedShapesProxy() const override;
KoToolProxy * toolProxy() const override;
QWidget* canvasWidget() override;
const QWidget* canvasWidget() const override;
KoUnit unit() const override;
void updateInputMethodInfo() override {}
void setCursor(const QCursor &) override {}
protected:
QScopedPointer<KisImageViewConverter> m_viewConverter;
QScopedPointer<KoShapeManager> m_shapeManager;
QScopedPointer<KoSelectedShapesProxy> m_selectedShapesProxy;
bool m_hasChangedWhileBeingInvisible {false};
bool m_isDestroying {false};
};
/**
* KisShapeLayerCanvas is a special canvas implementation that Krita
* uses for non-krita shapes to request updates on.
*
* Do NOT give this canvas to tools or to the KoCanvasController, it's
* not made for that.
*/
class KisShapeLayerCanvas : public KisShapeLayerCanvasBase
{
Q_OBJECT
public:
KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP image);
~KisShapeLayerCanvas() override;
/// This canvas won't render onto a widget, but a projection
void setProjection(KisPaintDeviceSP projection) {
m_projection = projection;
}
void setImage(KisImageWSP image) override;
void updateCanvas(const QRectF& rc) override;
void updateCanvas(const QVector<QRectF> &region);
void forceRepaint() override;
bool hasPendingUpdates() const override;
void forceRepaintWithHiddenAreas() override;
void resetCache() override;
void rerenderAfterBeingInvisible() override;
private Q_SLOTS:
friend class KisRepaintShapeLayerLayerJob;
void repaint();
void slotStartAsyncRepaint();
- void slotStartDirectSyncRepaint();
void slotImageSizeChanged();
Q_SIGNALS:
void forwardRepaint();
-private:
- void updateUpdateCompressorDelay();
-
private:
KisPaintDeviceSP m_projection;
KisShapeLayer *m_parentLayer {0};
- KisThreadSafeSignalCompressor m_canvasUpdateCompressor;
KisThreadSafeSignalCompressor m_asyncUpdateSignalCompressor;
volatile bool m_hasUpdateInCompressor = false;
- volatile bool m_hasDirectSyncRepaintInitiated = false;
bool m_forceUpdateHiddenAreasOnly = false;
QRegion m_dirtyRegion;
QMutex m_dirtyRegionMutex;
+ KoShapeManager::PaintJobsList m_paintJobs;
QRect m_cachedImageRect;
KisImageWSP m_image;
};
#endif
diff --git a/libs/ui/flake/kis_shape_selection.cpp b/libs/ui/flake/kis_shape_selection.cpp
index 2654a92bb9..0764e04294 100644
--- a/libs/ui/flake/kis_shape_selection.cpp
+++ b/libs/ui/flake/kis_shape_selection.cpp
@@ -1,416 +1,415 @@
/*
* Copyright (c) 2010 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2011 Jan Hambrecht <jaham@gmx.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_shape_selection.h"
#include <QPainter>
#include <QTimer>
#include <kundo2command.h>
#include <QMimeData>
#include <QApplication>
#include <QThread>
#include <KoShapeStroke.h>
#include <KoPathShape.h>
#include <KoShapeGroup.h>
#include <KoCompositeOp.h>
#include <KoShapeManager.h>
#include <KisDocument.h>
#include <KoEmbeddedDocumentSaver.h>
#include <KoGenStyles.h>
#include <KoOdfLoadingContext.h>
#include <KoOdfReadStore.h>
#include <KoOdfStylesReader.h>
#include <KoOdfWriteStore.h>
#include <KoXmlNS.h>
#include <KoShapeRegistry.h>
#include <KoShapeLoadingContext.h>
#include <KoXmlWriter.h>
#include <KoStore.h>
#include <KoShapeController.h>
#include <KoShapeSavingContext.h>
#include <KoStoreDevice.h>
#include <KoShapeTransformCommand.h>
#include <KoElementReference.h>
#include <kis_painter.h>
#include <kis_paint_device.h>
#include <kis_image.h>
#include <kis_iterator_ng.h>
#include <kis_selection.h>
#include "kis_shape_selection_model.h"
#include "kis_shape_selection_canvas.h"
#include "kis_take_all_shapes_command.h"
#include "kis_image_view_converter.h"
#include "kis_shape_layer.h"
#include <kis_debug.h>
KisShapeSelection::KisShapeSelection(KoShapeControllerBase *shapeControllerBase, KisImageWSP image, KisSelectionWSP selection)
: KoShapeLayer(m_model = new KisShapeSelectionModel(image, selection, this))
, m_image(image)
, m_shapeControllerBase(shapeControllerBase)
{
Q_ASSERT(m_image);
setShapeId("KisShapeSelection");
setSelectable(false);
m_converter = new KisImageViewConverter(image);
m_canvas = new KisShapeSelectionCanvas(shapeControllerBase);
m_canvas->shapeManager()->addShape(this);
m_model->setObjectName("KisShapeSelectionModel");
m_model->moveToThread(image->thread());
m_canvas->setObjectName("KisShapeSelectionCanvas");
m_canvas->moveToThread(image->thread());
connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF)));
}
KisShapeSelection::~KisShapeSelection()
{
m_model->setShapeSelection(0);
delete m_canvas;
delete m_converter;
}
KisShapeSelection::KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection)
: KoShapeLayer(m_model = new KisShapeSelectionModel(rhs.m_image, selection, this))
{
m_image = rhs.m_image;
m_shapeControllerBase = rhs.m_shapeControllerBase;
m_converter = new KisImageViewConverter(m_image);
m_canvas = new KisShapeSelectionCanvas(m_shapeControllerBase);
// TODO: refactor shape selection to pass signals
// via KoShapeManager, not via the model
m_canvas->shapeManager()->setUpdatesBlocked(true);
m_model->setUpdatesEnabled(false);
m_canvas->shapeManager()->addShape(this);
Q_FOREACH (KoShape *shape, rhs.shapes()) {
KoShape *clonedShape = shape->cloneShape();
KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; }
this->addShape(clonedShape);
}
m_canvas->shapeManager()->setUpdatesBlocked(false);
m_model->setUpdatesEnabled(true);
}
KisSelectionComponent* KisShapeSelection::clone(KisSelection* selection)
{
/**
* TODO: make cloning of vector selections safe! Right now it crashes
* on Windows because of manipulations with timers from non-gui thread.
*/
KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == qApp->thread());
return new KisShapeSelection(*this, selection);
}
bool KisShapeSelection::saveSelection(KoStore * store) const
{
const QSizeF sizeInPx = m_image->bounds().size();
const QSizeF sizeInPt(sizeInPx.width() / m_image->xRes(), sizeInPx.height() / m_image->yRes());
return KisShapeLayer::saveShapesToStore(store, this->shapes(), sizeInPt);
}
bool KisShapeSelection::loadSelection(KoStore* store)
{
QSizeF fragmentSize; // unused!
// FIXME: we handle xRes() only!
KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(m_image->xRes(), m_image->yRes()));
const qreal resolutionPPI = 72.0 * m_image->xRes();
QList<KoShape*> shapes;
if (store->open("content.svg")) {
KoStoreDevice storeDev(store);
storeDev.open(QIODevice::ReadOnly);
shapes = KisShapeLayer::createShapesFromSvg(&storeDev,
"", m_image->bounds(),
resolutionPPI, m_canvas->shapeController()->resourceManager(),
&fragmentSize);
store->close();
Q_FOREACH (KoShape *shape, shapes) {
addShape(shape);
}
return true;
}
KoOdfReadStore odfStore(store);
QString errorMessage;
odfStore.loadAndParse(errorMessage);
if (!errorMessage.isEmpty()) {
dbgKrita << errorMessage;
return false;
}
KoXmlElement contents = odfStore.contentDoc().documentElement();
// dbgKrita <<"Start loading OASIS document..." << contents.text();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement();
KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body"));
if (body.isNull()) {
dbgKrita << "No office:body found!";
//setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) );
return false;
}
body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing");
if (body.isNull()) {
dbgKrita << "No office:drawing found!";
//setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) );
return false;
}
KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page"));
if (page.isNull()) {
dbgKrita << "No office:drawing found!";
//setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) );
return false;
}
KoXmlElement * master = 0;
if (odfStore.styles().masterPages().contains("Standard"))
master = odfStore.styles().masterPages().value("Standard");
else if (odfStore.styles().masterPages().contains("Default"))
master = odfStore.styles().masterPages().value("Default");
else if (! odfStore.styles().masterPages().empty())
master = odfStore.styles().masterPages().begin().value();
if (master) {
const KoXmlElement *style = odfStore.styles().findStyle(
master->attributeNS(KoXmlNS::style, "page-layout-name", QString()));
KoPageLayout pageLayout;
pageLayout.loadOdf(*style);
setSize(QSizeF(pageLayout.width, pageLayout.height));
} else {
dbgKrita << "No master page found!";
return false;
}
KoOdfLoadingContext context(odfStore.styles(), odfStore.store());
KoShapeLoadingContext shapeContext(context, 0);
KoXmlElement layerElement;
forEachElement(layerElement, context.stylesReader().layerSet()) {
if (!loadOdf(layerElement, shapeContext)) {
dbgKrita << "Could not load vector layer!";
return false;
}
}
KoXmlElement child;
forEachElement(child, page) {
KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext);
if (shape) {
addShape(shape);
}
}
return true;
}
void KisShapeSelection::setUpdatesEnabled(bool enabled)
{
m_model->setUpdatesEnabled(enabled);
}
bool KisShapeSelection::updatesEnabled() const
{
return m_model->updatesEnabled();
}
KUndo2Command* KisShapeSelection::resetToEmpty()
{
return new KisTakeAllShapesCommand(this, true, false);
}
bool KisShapeSelection::isEmpty() const
{
return !m_model->count();
}
QPainterPath KisShapeSelection::outlineCache() const
{
return m_outline;
}
bool KisShapeSelection::outlineCacheValid() const
{
return true;
}
void KisShapeSelection::recalculateOutlineCache()
{
QList<KoShape*> shapesList = shapes();
QPainterPath outline;
Q_FOREACH (KoShape * shape, shapesList) {
- QTransform shapeMatrix = shape->absoluteTransformation(0);
+ QTransform shapeMatrix = shape->absoluteTransformation();
outline = outline.united(shapeMatrix.map(shape->outline()));
}
QTransform resolutionMatrix;
resolutionMatrix.scale(m_image->xRes(), m_image->yRes());
m_outline = resolutionMatrix.map(outline);
}
-void KisShapeSelection::paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &)
+void KisShapeSelection::paintComponent(QPainter& painter, KoShapePaintingContext &) const
{
Q_UNUSED(painter);
- Q_UNUSED(converter);
}
void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection)
{
Q_ASSERT(projection);
Q_ASSERT(m_image);
QRectF boundingRect = outlineCache().boundingRect();
renderSelection(projection, boundingRect.toAlignedRect());
}
void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& r)
{
Q_ASSERT(projection);
renderSelection(projection, r);
}
void KisShapeSelection::renderSelection(KisPaintDeviceSP projection, const QRect& requestedRect)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(projection);
KIS_SAFE_ASSERT_RECOVER_RETURN(m_image);
const qint32 MASK_IMAGE_WIDTH = 256;
const qint32 MASK_IMAGE_HEIGHT = 256;
const QPainterPath selectionOutline = outlineCache();
if (*projection->defaultPixel().data() == OPACITY_TRANSPARENT_U8) {
projection->clear(requestedRect);
} else {
KoColor transparentColor(Qt::transparent, projection->colorSpace());
projection->fill(requestedRect, transparentColor);
}
const QRect r = requestedRect & selectionOutline.boundingRect().toAlignedRect();
QImage polygonMaskImage(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32);
QPainter maskPainter(&polygonMaskImage);
maskPainter.setRenderHint(QPainter::Antialiasing, true);
// Break the mask up into chunks so we don't have to allocate a potentially very large QImage.
for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) {
for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) {
maskPainter.fillRect(polygonMaskImage.rect(), Qt::black);
maskPainter.translate(-x, -y);
maskPainter.fillPath(selectionOutline, Qt::white);
maskPainter.translate(x, y);
qint32 rectWidth = qMin(r.x() + r.width() - x, MASK_IMAGE_WIDTH);
qint32 rectHeight = qMin(r.y() + r.height() - y, MASK_IMAGE_HEIGHT);
KisSequentialIterator it(projection, QRect(x, y, rectWidth, rectHeight));
while (it.nextPixel()) {
(*it.rawData()) = qRed(polygonMaskImage.pixel(it.x() - x, it.y() - y));
}
}
}
}
KoShapeManager* KisShapeSelection::shapeManager() const
{
return m_canvas->shapeManager();
}
KisShapeSelectionFactory::KisShapeSelectionFactory()
: KoShapeFactoryBase("KisShapeSelection", "selection shape container")
{
setHidden(true);
}
void KisShapeSelection::moveX(qint32 x)
{
const QPointF diff(x / m_image->xRes(), 0);
emit sigMoveShapes(diff);
}
void KisShapeSelection::moveY(qint32 y)
{
const QPointF diff(0, y / m_image->yRes());
emit sigMoveShapes(diff);
}
void KisShapeSelection::slotMoveShapes(const QPointF &diff)
{
Q_FOREACH (KoShape* shape, shapeManager()->shapes()) {
if (shape != this) {
QPointF pos = shape->position();
shape->setPosition(pos + diff);
}
}
}
// TODO same code as in vector layer, refactor!
KUndo2Command* KisShapeSelection::transform(const QTransform &transform) {
QList<KoShape*> shapes = m_canvas->shapeManager()->shapes();
if(shapes.isEmpty()) return 0;
QTransform realTransform = m_converter->documentToView() *
transform * m_converter->viewToDocument();
QList<QTransform> oldTransformations;
QList<QTransform> newTransformations;
// this code won't work if there are shapes, that inherit the transformation from the parent container.
// the chart and tree shapes are examples for that, but they aren't used in krita and there are no other shapes like that.
Q_FOREACH (const KoShape* shape, shapes) {
QTransform oldTransform = shape->transformation();
oldTransformations.append(oldTransform);
if (dynamic_cast<const KoShapeGroup*>(shape)) {
newTransformations.append(oldTransform);
} else {
- QTransform globalTransform = shape->absoluteTransformation(0);
+ QTransform globalTransform = shape->absoluteTransformation();
QTransform localTransform = globalTransform * realTransform * globalTransform.inverted();
newTransformations.append(localTransform*oldTransform);
}
}
return new KoShapeTransformCommand(shapes, oldTransformations, newTransformations);
}
diff --git a/libs/ui/flake/kis_shape_selection.h b/libs/ui/flake/kis_shape_selection.h
index 3b2e8530e1..ad795b36b1 100644
--- a/libs/ui/flake/kis_shape_selection.h
+++ b/libs/ui/flake/kis_shape_selection.h
@@ -1,140 +1,140 @@
/*
* 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_SHAPE_SELECTION_H
#define KIS_SHAPE_SELECTION_H
#include <KoShapeLayer.h>
#include <KoShapeFactoryBase.h>
#include <KoShapeUserData.h>
#include <KoShapeLoadingContext.h>
#include <kis_selection_component.h>
#include <kis_types.h>
#include <kritaui_export.h>
class KoStore;
class KoShapeManager;
class KisShapeSelectionCanvas;
class KisShapeSelectionModel;
class KisImageViewConverter;
class KUndo2Command;
/**
* The marker class.
* It is added to the shape's user data to show this shape
* is a part of a shape selection
*/
class KisShapeSelectionMarker : public KoShapeUserData
{
KoShapeUserData* clone() const override {
return new KisShapeSelectionMarker(*this);
}
};
class KRITAUI_EXPORT KisShapeSelection : public QObject, public KoShapeLayer, public KisSelectionComponent
{
Q_OBJECT
KisShapeSelection(const KisShapeSelection& rhs);
public:
KisShapeSelection(KoShapeControllerBase *shapeControllerBase, KisImageWSP image, KisSelectionWSP selection);
~KisShapeSelection() override;
KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection);
KisSelectionComponent* clone(KisSelection* selection) override;
bool saveSelection(KoStore * store) const;
bool loadSelection(KoStore * store);
/**
* Renders the shapes to a selection. This method should only be called
* by KisSelection to update it's projection.
*
* @param projection the target selection
*/
void renderToProjection(KisPaintDeviceSP projection) override;
void renderToProjection(KisPaintDeviceSP projection, const QRect& r) override;
KUndo2Command* resetToEmpty() override;
bool isEmpty() const override;
QPainterPath outlineCache() const override;
bool outlineCacheValid() const override;
void recalculateOutlineCache() override;
KoShapeManager *shapeManager() const;
void moveX(qint32 x) override;
void moveY(qint32 y) override;
KUndo2Command* transform(const QTransform &transform) override;
Q_SIGNALS:
void sigMoveShapes(const QPointF &diff);
private Q_SLOTS:
void slotMoveShapes(const QPointF &diff);
protected:
- void paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &paintcontext) override;
+ void paintComponent(QPainter& painter, KoShapePaintingContext &paintcontext) const override;
private:
friend class KisTakeAllShapesCommand;
void setUpdatesEnabled(bool enabled);
bool updatesEnabled() const;
private:
void renderSelection(KisPaintDeviceSP projection, const QRect& requestedRect);
KisImageWSP m_image;
QPainterPath m_outline;
KisImageViewConverter *m_converter;
KisShapeSelectionCanvas *m_canvas;
KisShapeSelectionModel *m_model;
KoShapeControllerBase *m_shapeControllerBase;
friend class KisShapeSelectionModel;
};
class KRITAUI_EXPORT KisShapeSelectionFactory : public KoShapeFactoryBase
{
public:
KisShapeSelectionFactory();
~KisShapeSelectionFactory() override {}
KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const override {
Q_UNUSED(documentResources);
return 0;
}
bool supports(const KoXmlElement & e, KoShapeLoadingContext &context) const override {
Q_UNUSED(e);
Q_UNUSED(context);
return false;
}
};
#endif
diff --git a/libs/ui/forms/wdgstrokeselectionproperties.ui b/libs/ui/forms/wdgstrokeselectionproperties.ui
index 1f258c5e65..6641d1ecd1 100644
--- a/libs/ui/forms/wdgstrokeselectionproperties.ui
+++ b/libs/ui/forms/wdgstrokeselectionproperties.ui
@@ -1,210 +1,248 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgStrokeSelection</class>
<widget class="QWidget" name="WdgStrokeSelection">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>304</width>
+ <width>334</width>
<height>208</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>New Image</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0" rowspan="2">
+ <item row="0" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Stroke</string>
</attribute>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="1">
- <widget class="QComboBox" name="typeBox">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <item>
- <property name="text">
- <string>Current Brush</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Line selection</string>
- </property>
- </item>
- </widget>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="typeBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>175</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <item>
+ <property name="text">
+ <string>Current Brush</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Line selection</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Line:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="lineColorBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>175</width>
+ <height>0</height>
+ </size>
+ </property>
+ <item>
+ <property name="text">
+ <string>Foreground color</string>
+ </property>
</item>
- <item row="5" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Fill:</string>
- </property>
- </widget>
+ <item>
+ <property name="text">
+ <string>Background color</string>
+ </property>
</item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Type:</string>
- </property>
- </widget>
+ <item>
+ <property name="text">
+ <string>Custom color</string>
+ </property>
</item>
- <item row="4" column="1">
- <widget class="QSpinBox" name="lineSize">
- <property name="suffix">
- <string/>
- </property>
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>1000000</number>
- </property>
- <property name="value">
- <number>1</number>
- </property>
- </widget>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="KColorButton" name="colorSelector">
+ <property name="text">
+ <string>Color</string>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ <property name="default">
+ <bool>false</bool>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="lblSize">
+ <property name="text">
+ <string>Width:</string>
+ </property>
+ <property name="buddy">
+ <cstring>lineSize</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="lineSize">
+ <property name="minimumSize">
+ <size>
+ <width>175</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="suffix">
+ <string/>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>1000000</number>
+ </property>
+ <property name="value">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QComboBox" name="sizeBox">
+ <item>
+ <property name="text">
+ <string>px</string>
+ </property>
</item>
- <item row="4" column="0">
- <widget class="QLabel" name="lblSize">
- <property name="text">
- <string>Width:</string>
- </property>
- <property name="buddy">
- <cstring>lineSize</cstring>
- </property>
- </widget>
+ <item>
+ <property name="text">
+ <string>mm</string>
+ </property>
</item>
- <item row="4" column="2">
- <widget class="QComboBox" name="sizeBox">
- <item>
- <property name="text">
- <string>px</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>mm</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>inch</string>
- </property>
- </item>
- </widget>
+ <item>
+ <property name="text">
+ <string>inch</string>
+ </property>
</item>
- <item row="5" column="1">
- <widget class="QComboBox" name="fillBox">
- <item>
- <property name="text">
- <string>None</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Paint color</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Background color</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Custom color</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Foreground color</string>
- </property>
- </item>
- </widget>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Fill:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="fillBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>175</width>
+ <height>0</height>
+ </size>
+ </property>
+ <item>
+ <property name="text">
+ <string>None</string>
+ </property>
</item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Line:</string>
- </property>
- </widget>
+ <item>
+ <property name="text">
+ <string>Paint color</string>
+ </property>
</item>
- <item row="3" column="2">
- <widget class="KColorButton" name="colorSelector">
- <property name="text">
- <string>Color</string>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <property name="autoDefault">
- <bool>false</bool>
- </property>
- <property name="default">
- <bool>false</bool>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- </widget>
+ <item>
+ <property name="text">
+ <string>Background color</string>
+ </property>
</item>
- <item row="3" column="1">
- <widget class="QComboBox" name="lineColorBox">
- <item>
- <property name="text">
- <string>Foreground color</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Background color</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Custom color</string>
- </property>
- </item>
- </widget>
+ <item>
+ <property name="text">
+ <string>Custom color</string>
+ </property>
</item>
- <item row="5" column="2">
- <widget class="KColorButton" name="colorFillSelector">
- <property name="text">
- <string>Color</string>
- </property>
- </widget>
+ <item>
+ <property name="text">
+ <string>Foreground color</string>
+ </property>
</item>
- </layout>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="KColorButton" name="colorFillSelector">
+ <property name="text">
+ <string>Color</string>
+ </property>
+ </widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KColorButton</class>
<extends>QPushButton</extends>
<header>kcolorbutton.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/libs/ui/input/wintab/kis_tablet_support_win_p.h b/libs/ui/input/wintab/kis_tablet_support_win_p.h
index 64a8da3ea1..303948b3ac 100644
--- a/libs/ui/input/wintab/kis_tablet_support_win_p.h
+++ b/libs/ui/input/wintab/kis_tablet_support_win_p.h
@@ -1,146 +1,146 @@
/*
* Copyright (C) 2015 The Qt Company Ltd.
- * Contact: http://www.qt.io/licensing/
+ * Contact: https://www.qt.io/licensing/
* Copyright (C) 2015 Michael Abrahms <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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TABLET_SUPPORT_WIN_P_H
#define KIS_TABLET_SUPPORT_WIN_P_H
#include <QVector>
#include <QPointF>
#include <QMap>
#include <QRect>
#include "wintab.h"
QT_BEGIN_NAMESPACE
class QDebug;
class QWindow;
class QRect;
class QWidget;
struct QWindowsWinTab32DLL
{
QWindowsWinTab32DLL() : wTOpen(0), wTClose(0), wTInfo(0), wTEnable(0), wTOverlap(0), wTPacketsGet(0), wTGet(0),
wTQueueSizeGet(0), wTQueueSizeSet(0) {}
bool init();
typedef HCTX (API *PtrWTOpen)(HWND, LPLOGCONTEXT, BOOL);
typedef BOOL (API *PtrWTClose)(HCTX);
typedef UINT (API *PtrWTInfo)(UINT, UINT, LPVOID);
typedef BOOL (API *PtrWTEnable)(HCTX, BOOL);
typedef BOOL (API *PtrWTOverlap)(HCTX, BOOL);
typedef int (API *PtrWTPacketsGet)(HCTX, int, LPVOID);
typedef BOOL (API *PtrWTGet)(HCTX, LPLOGCONTEXT);
typedef int (API *PtrWTQueueSizeGet)(HCTX);
typedef BOOL (API *PtrWTQueueSizeSet)(HCTX, int);
PtrWTOpen wTOpen;
PtrWTClose wTClose;
PtrWTInfo wTInfo;
PtrWTEnable wTEnable;
PtrWTOverlap wTOverlap;
PtrWTPacketsGet wTPacketsGet;
PtrWTGet wTGet;
PtrWTQueueSizeGet wTQueueSizeGet;
PtrWTQueueSizeSet wTQueueSizeSet;
};
struct QWindowsTabletDeviceData
{
QWindowsTabletDeviceData() : minPressure(0), maxPressure(0), minTanPressure(0),
maxTanPressure(0), minX(0), maxX(0), minY(0), maxY(0), minZ(0), maxZ(0),
uniqueId(0), currentDevice(0), currentPointerType(0) {}
QPointF scaleCoordinates(int coordX, int coordY,const QRect &targetArea) const;
qreal scalePressure(qreal p) const { return p / qreal(maxPressure - minPressure); }
qreal scaleTangentialPressure(qreal p) const { return p / qreal(maxTanPressure - minTanPressure); }
int minPressure;
int maxPressure;
int minTanPressure;
int maxTanPressure;
int minX, maxX, minY, maxY, minZ, maxZ;
qint64 uniqueId;
int currentDevice;
int currentPointerType;
QRect virtualDesktopArea;
// Added by Krita
QMap<quint8, quint8> buttonsMap;
};
QDebug operator<<(QDebug d, const QWindowsTabletDeviceData &t);
class QWindowsTabletSupport
{
Q_DISABLE_COPY(QWindowsTabletSupport)
explicit QWindowsTabletSupport(HWND window, HCTX context);
public:
~QWindowsTabletSupport();
static QWindowsTabletSupport *create();
void notifyActivate();
QString description() const;
bool translateTabletProximityEvent(WPARAM wParam, LPARAM lParam);
bool translateTabletPacketEvent();
int absoluteRange() const { return m_absoluteRange; }
void setAbsoluteRange(int a) { m_absoluteRange = a; }
void tabletUpdateCursor(const int pkCursor);
static QWindowsWinTab32DLL m_winTab32DLL;
private:
unsigned options() const;
QWindowsTabletDeviceData tabletInit(const quint64 uniqueId, const UINT cursorType) const;
const HWND m_window;
const HCTX m_context;
int m_absoluteRange;
bool m_tiltSupport;
QVector<QWindowsTabletDeviceData> m_devices;
int m_currentDevice;
QWidget *targetWidget{0};
/**
* This is an inelegant solution to record pen / eraser switches.
* On the Surface Pro 3 we are only notified of cursor changes at the last minute.
* The recommended way to handle switches is WT_CSRCHANGE, but that doesn't work
* unless we save packet ID information, and we cannot change the structure of the
* PACKETDATA due to Qt restrictions.
*
* Furthermore, WT_CSRCHANGE only ever appears *after* we receive the packet.
*/
UINT currentPkCursor{0};
bool isSurfacePro3{false}; //< Only enable this on SP3 or other devices with the same issue.
};
QT_END_NAMESPACE
#endif // KIS_TABLET_SUPPORT_WIN_P_H
diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc
index 4c7711b248..dc2d10931a 100644
--- a/libs/ui/kis_config.cc
+++ b/libs/ui/kis_config.cc
@@ -1,2209 +1,2215 @@
/*
* 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 <QtGlobal>
#include <QApplication>
#include <QDesktopWidget>
#include <QMutex>
#include <QFont>
#include <QThread>
#include <QStringList>
#include <QSettings>
#include <QStandardPaths>
#include <QDebug>
#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>
#include <KisOcioConfiguration.h>
#include <KisUsageLogger.h>
#include <kis_image_config.h>
#ifdef Q_OS_WIN
#include "config_use_qt_tablet_windows.h"
#endif
KisConfig::KisConfig(bool readOnly)
: m_cfg( KSharedConfig::openConfig()->group(""))
, m_readOnly(readOnly)
{
if (!readOnly) {
KIS_SAFE_ASSERT_RECOVER_RETURN(qApp && qApp->thread() == QThread::currentThread());
}
}
KisConfig::~KisConfig()
{
if (m_readOnly) return;
if (qApp && qApp->thread() != QThread::currentThread()) {
dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Called from:" << kisBacktrace();
return;
}
m_cfg.sync();
}
void KisConfig::logImportantSettings() const
{
- KisUsageLogger::write("Current Settings\n");
- KisUsageLogger::write(QString("\tCurrent Swap Location: %1").arg(KisImageConfig(true).swapDir()));
- KisUsageLogger::write(QString("\tUndo Enabled: %1").arg(undoEnabled()));
- KisUsageLogger::write(QString("\tUndo Stack Limit: %1").arg(undoStackLimit()));
- KisUsageLogger::write(QString("\tUse OpenGL: %1").arg(useOpenGL()));
- KisUsageLogger::write(QString("\tUse OpenGL Texture Buffer: %1").arg(useOpenGLTextureBuffer()));
- KisUsageLogger::write(QString("\tUse AMD Vectorization Workaround: %1").arg(enableAmdVectorizationWorkaround()));
- KisUsageLogger::write(QString("\tCanvas State: %1").arg(canvasState()));
- KisUsageLogger::write(QString("\tAutosave Interval: %1").arg(autoSaveInterval()));
- KisUsageLogger::write(QString("\tUse Backup Files: %1").arg(backupFile()));
- KisUsageLogger::write(QString("\tNumber of Backups Kept: %1").arg(m_cfg.readEntry("numberofbackupfiles", 1)));
- KisUsageLogger::write(QString("\tBackup File Suffix: %1").arg(m_cfg.readEntry("backupfilesuffix", "~")));
+ KisUsageLogger::writeSysInfo("Current Settings\n");
+ KisUsageLogger::writeSysInfo(QString(" Current Swap Location: %1").arg(KisImageConfig(true).swapDir()));
+ KisUsageLogger::writeSysInfo(QString(" Undo Enabled: %1").arg(undoEnabled()));
+ KisUsageLogger::writeSysInfo(QString(" Undo Stack Limit: %1").arg(undoStackLimit()));
+ KisUsageLogger::writeSysInfo(QString(" Use OpenGL: %1").arg(useOpenGL()));
+ KisUsageLogger::writeSysInfo(QString(" Use OpenGL Texture Buffer: %1").arg(useOpenGLTextureBuffer()));
+ KisUsageLogger::writeSysInfo(QString(" Use AMD Vectorization Workaround: %1").arg(enableAmdVectorizationWorkaround()));
+ KisUsageLogger::writeSysInfo(QString(" Canvas State: %1").arg(canvasState()));
+ KisUsageLogger::writeSysInfo(QString(" Autosave Interval: %1").arg(autoSaveInterval()));
+ KisUsageLogger::writeSysInfo(QString(" Use Backup Files: %1").arg(backupFile()));
+ KisUsageLogger::writeSysInfo(QString(" Number of Backups Kept: %1").arg(m_cfg.readEntry("numberofbackupfiles", 1)));
+ KisUsageLogger::writeSysInfo(QString(" Backup File Suffix: %1").arg(m_cfg.readEntry("backupfilesuffix", "~")));
QString backupDir;
switch(m_cfg.readEntry("backupfilelocation", 0)) {
case 1:
backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
break;
case 2:
backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
break;
default:
// Do nothing: the empty string is user file location
backupDir = "Same Folder as the File";
}
- KisUsageLogger::write(QString("\tBackup Location: %1").arg(backupDir));
+ KisUsageLogger::writeSysInfo(QString(" Backup Location: %1").arg(backupDir));
- KisUsageLogger::write(QString("\tUse Win8 Pointer Input: %1").arg(useWin8PointerInput()));
- KisUsageLogger::write(QString("\tUse RightMiddleTabletButton Workaround: %1").arg(useRightMiddleTabletButtonWorkaround()));
- KisUsageLogger::write(QString("\tLevels of Detail Enabled: %1").arg(levelOfDetailEnabled()));
- KisUsageLogger::write(QString("\tUse Zip64: %1").arg(useZip64()));
+ KisUsageLogger::writeSysInfo(QString(" Use Win8 Pointer Input: %1").arg(useWin8PointerInput()));
+ KisUsageLogger::writeSysInfo(QString(" Use RightMiddleTabletButton Workaround: %1").arg(useRightMiddleTabletButtonWorkaround()));
+ KisUsageLogger::writeSysInfo(QString(" Levels of Detail Enabled: %1").arg(levelOfDetailEnabled()));
+ KisUsageLogger::writeSysInfo(QString(" Use Zip64: %1").arg(useZip64()));
- KisUsageLogger::write("\n");
+ KisUsageLogger::writeSysInfo("\n");
}
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::disableTouchRotation(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("disableTouchRotation", false));
}
void KisConfig::setDisableTouchRotation(bool value) const
{
m_cfg.writeEntry("disableTouchRotation", 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);
}
int KisConfig::preferredVectorImportResolutionPPI(bool defaultValue) const
{
return defaultValue ? 100.0 : m_cfg.readEntry("preferredVectorImportResolution", 100.0);
}
void KisConfig::setPreferredVectorImportResolutionPPI(int value) const
{
m_cfg.writeEntry("preferredVectorImportResolution", value);
}
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", true));
}
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();
}
QString KisConfig::getMDIBackgroundColor(bool defaultValue) const
{
QColor col(77, 77, 77);
KoColor kol(KoColorSpaceRegistry::instance()->rgb8());
kol.fromQColor(col);
QString xml = kol.toXML();
return (defaultValue ? xml : m_cfg.readEntry("mdiBackgroundColorXML", xml));
}
void KisConfig::setMDIBackgroundColor(const QString &v) const
{
m_cfg.writeEntry("mdiBackgroundColorXML", 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(true);
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();
const KoColorProfile * profile = 0;
if (bytes.length() > 0) {
profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes);
//dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name();
}
return profile;
}
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::forcePaletteColors(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("colorsettings/forcepalettecolors", false));
}
void KisConfig::setForcePaletteColors(bool forcePaletteColors)
{
m_cfg.writeEntry("colorsettings/forcepalettecolors", forcePaletteColors);
}
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;
}
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
return kritarc.value("OpenGLRenderer", "auto").toString() != "none";
}
void KisConfig::disableOpenGL() const
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("OpenGLRenderer", "none");
}
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 = 24.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()));
config->setToPixel(m_cfg.readEntry("globalSnapToPixel", defaultConfig.toPixel()));
}
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());
m_cfg.writeEntry("globalSnapToPixel", config.toPixel());
}
qint32 KisConfig::checkSize(bool defaultValue) const
{
qint32 size = (defaultValue ? 32 : m_cfg.readEntry("checksize", 32));
if (size == 0) size = 32;
return size;
}
void KisConfig::setCheckSize(qint32 checksize) const
{
if (checksize == 0) {
checksize = 32;
}
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);
}
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::forceAlwaysFullSizedOutline(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("forceAlwaysFullSizedOutline", false));
}
void KisConfig::setForceAlwaysFullSizedOutline(bool value) const
{
m_cfg.writeEntry("forceAlwaysFullSizedOutline", value);
}
KisConfig::SessionOnStartup KisConfig::sessionOnStartup(bool defaultValue) const
{
int value = defaultValue ? SOS_BlankSession : m_cfg.readEntry("sessionOnStartup", (int)SOS_BlankSession);
return (KisConfig::SessionOnStartup)value;
}
void KisConfig::setSessionOnStartup(SessionOnStartup value)
{
m_cfg.writeEntry("sessionOnStartup", (int)value);
}
bool KisConfig::saveSessionOnQuit(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("saveSessionOnQuit", false);
}
void KisConfig::setSaveSessionOnQuit(bool value)
{
m_cfg.writeEntry("saveSessionOnQuit", value);
}
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
#ifdef USE_QT_TABLET_WINDOWS
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
return useWin8PointerInputNoApp(&kritarc, defaultValue);
#else
return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false));
#endif
#else
Q_UNUSED(defaultValue);
return false;
#endif
}
void KisConfig::setUseWin8PointerInput(bool value)
{
#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) {
#ifdef USE_QT_TABLET_WINDOWS
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
setUseWin8PointerInputNoApp(&kritarc, value);
#else
m_cfg.writeEntry("useWin8PointerInput", value);
#endif
}
#else
Q_UNUSED(value)
#endif
}
bool KisConfig::useWin8PointerInputNoApp(QSettings *settings, bool defaultValue)
{
return defaultValue ? false : settings->value("useWin8PointerInput", false).toBool();
}
void KisConfig::setUseWin8PointerInputNoApp(QSettings *settings, bool value)
{
settings->setValue("useWin8PointerInput", value);
}
bool KisConfig::useRightMiddleTabletButtonWorkaround(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useRightMiddleTabletButtonWorkaround", false));
}
void KisConfig::setUseRightMiddleTabletButtonWorkaround(bool value)
{
m_cfg.writeEntry("useRightMiddleTabletButtonWorkaround", value);
}
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 ? 60 : m_cfg.readEntry("presetIconSize", 60));
}
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::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",
QString("normal,erase,multiply,burn,darken,add,dodge,screen,overlay,soft_light_svg,luminize,lighten,saturation,color,divide").split(',')));
}
void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const
{
m_cfg.writeEntry("favoriteCompositeOps", compositeOps);
}
QString KisConfig::exportConfigurationXML(const QString &filterId, bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString()));
}
KisPropertiesConfigurationSP KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
const QString xmlData = exportConfigurationXML(filterId, defaultValue);
cfg->fromXML(xmlData);
return cfg;
}
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);
}
KisOcioConfiguration KisConfig::ocioConfiguration(bool defaultValue) const
{
KisOcioConfiguration cfg;
if (!defaultValue) {
cfg.mode = (KisOcioConfiguration::Mode)m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", 0);
cfg.configurationPath = m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString());
cfg.lutPath = m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString());
cfg.inputColorSpace = m_cfg.readEntry("Krita/Ocio/InputColorSpace", QString());
cfg.displayDevice = m_cfg.readEntry("Krita/Ocio/DisplayDevice", QString());
cfg.displayView = m_cfg.readEntry("Krita/Ocio/DisplayView", QString());
cfg.look = m_cfg.readEntry("Krita/Ocio/DisplayLook", QString());
}
return cfg;
}
void KisConfig::setOcioConfiguration(const KisOcioConfiguration &cfg)
{
m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) cfg.mode);
m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", cfg.configurationPath);
m_cfg.writeEntry("Krita/Ocio/OcioLutPath", cfg.lutPath);
m_cfg.writeEntry("Krita/Ocio/InputColorSpace", cfg.inputColorSpace);
m_cfg.writeEntry("Krita/Ocio/DisplayDevice", cfg.displayDevice);
m_cfg.writeEntry("Krita/Ocio/DisplayView", cfg.displayView);
m_cfg.writeEntry("Krita/Ocio/DisplayLook", cfg.look);
}
KisConfig::OcioColorManagementMode
KisConfig::ocioColorManagementMode(bool defaultValue) const
{
// FIXME: this option duplicates ocioConfiguration(), please deprecate it
return (OcioColorManagementMode)(defaultValue ? INTERNAL
: m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL));
}
void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const
{
// FIXME: this option duplicates ocioConfiguration(), please deprecate it
m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode);
}
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", "Default"));
}
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);
}
int KisConfig::layerThumbnailSize(bool defaultValue) const
{
return (defaultValue ? 20 : m_cfg.readEntry("layerThumbnailSize", 20));
}
void KisConfig::setLayerThumbnailSize(int size)
{
m_cfg.writeEntry("layerThumbnailSize", size);
}
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(const QColor &value)
{
m_cfg.writeEntry("BackgroundColorForNewImage", value);
}
KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const
{
return (KisConfig::BackgroundStyle)(defaultValue ? RASTER_LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)RASTER_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::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);
}
bool KisConfig::kineticScrollingEnabled(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("KineticScrollingEnabled", true));
}
void KisConfig::setKineticScrollingEnabled(bool value)
{
m_cfg.writeEntry("KineticScrollingEnabled", value);
}
int KisConfig::kineticScrollingGesture(bool defaultValue) const
{
- return (defaultValue ? 2 : m_cfg.readEntry("KineticScrollingGesture", 2));
+#ifdef Q_OS_ANDROID
+ int defaultGesture = 0; // TouchGesture
+#else
+ int defaultGesture = 2; // MiddleMouseButtonGesture
+#endif
+
+ return (defaultValue ? defaultGesture : m_cfg.readEntry("KineticScrollingGesture", defaultGesture));
}
void KisConfig::setKineticScrollingGesture(int gesture)
{
m_cfg.writeEntry("KineticScrollingGesture", gesture);
}
int KisConfig::kineticScrollingSensitivity(bool defaultValue) const
{
return (defaultValue ? 75 : m_cfg.readEntry("KineticScrollingSensitivity", 75));
}
void KisConfig::setKineticScrollingSensitivity(int sensitivity)
{
m_cfg.writeEntry("KineticScrollingSensitivity", sensitivity);
}
bool KisConfig::kineticScrollingHiddenScrollbars(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("KineticScrollingHideScrollbar", false));
}
void KisConfig::setKineticScrollingHideScrollbars(bool scrollbar)
{
m_cfg.writeEntry("KineticScrollingHideScrollbar", scrollbar);
}
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::setDisableAVXOptimizations(bool value)
{
m_cfg.writeEntry("disableAVXOptimizations", value);
}
bool KisConfig::disableAVXOptimizations(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("disableAVXOptimizations", 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);
}
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 <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);
}
QColor KisConfig::defaultAssistantsColor(bool defaultValue) const
{
static const QColor defaultColor = QColor(176, 176, 176, 255);
return defaultValue ? defaultColor : m_cfg.readEntry("defaultAssistantsColor", defaultColor);
}
void KisConfig::setDefaultAssistantsColor(const QColor &color) const
{
m_cfg.writeEntry("defaultAssistantsColor", color);
}
bool KisConfig::autoSmoothBezierCurves(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("autoSmoothBezierCurves", false);
}
void KisConfig::setAutoSmoothBezierCurves(bool value)
{
m_cfg.writeEntry("autoSmoothBezierCurves", value);
}
bool KisConfig::activateTransformToolAfterPaste(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("activateTransformToolAfterPaste", false);
}
void KisConfig::setActivateTransformToolAfterPaste(bool value)
{
m_cfg.writeEntry("activateTransformToolAfterPaste", value);
}
KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(bool defaultValue) const
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
return rootSurfaceFormat(&kritarc, defaultValue);
}
void KisConfig::setRootSurfaceFormat(KisConfig::RootSurfaceFormat value)
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
setRootSurfaceFormat(&kritarc, value);
}
KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(QSettings *displayrc, bool defaultValue)
{
QString textValue = "bt709-g22";
if (!defaultValue) {
textValue = displayrc->value("rootSurfaceFormat", textValue).toString();
}
return textValue == "bt709-g10" ? BT709_G10 :
textValue == "bt2020-pq" ? BT2020_PQ :
BT709_G22;
}
void KisConfig::setRootSurfaceFormat(QSettings *displayrc, KisConfig::RootSurfaceFormat value)
{
const QString textValue =
value == BT709_G10 ? "bt709-g10" :
value == BT2020_PQ ? "bt2020-pq" :
"bt709-g22";
displayrc->setValue("rootSurfaceFormat", textValue);
}
bool KisConfig::useZip64(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("UseZip64", false);
}
void KisConfig::setUseZip64(bool value)
{
m_cfg.writeEntry("UseZip64", value);
}
bool KisConfig::convertLayerColorSpaceInProperties(bool defaultValue) const
{
return defaultValue ? true : m_cfg.readEntry("convertLayerColorSpaceInProperties", true);
}
void KisConfig::setConvertLayerColorSpaceInProperties(bool value)
{
m_cfg.writeEntry("convertLayerColorSpaceInProperties", 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;
KoColor color = _color;
if (!m_cfg.readEntry(name).isNull()) {
doc.setContent(m_cfg.readEntry(name));
QDomElement e = doc.documentElement().firstChild().toElement();
color = 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();
color = KoColor::fromXML(e, Integer16BitsColorDepthID.id());
}
return color;
}
diff --git a/libs/ui/kis_import_catcher.cc b/libs/ui/kis_import_catcher.cc
index a7e0ebd2a0..fd26c13485 100644
--- a/libs/ui/kis_import_catcher.cc
+++ b/libs/ui/kis_import_catcher.cc
@@ -1,149 +1,149 @@
/*
* 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_import_catcher.h"
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <QUrl>
#include <KisImportExportManager.h>
#include "kis_node_manager.h"
#include "kis_count_visitor.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "kis_image.h"
#include "kis_layer.h"
#include "kis_painter.h"
#include "kis_selection.h"
#include "kis_node_commands_adapter.h"
#include "kis_group_layer.h"
#include "kis_progress_widget.h"
#include "kis_config.h"
#include "KisPart.h"
#include "kis_shape_layer.h"
struct KisImportCatcher::Private
{
public:
KisDocument* doc;
KisViewManager* view;
QUrl url;
QString layerType;
QString prettyLayerName() const;
void importAsPaintLayer(KisPaintDeviceSP device);
void importAsTransparencyMask(KisPaintDeviceSP device);
};
QString KisImportCatcher::Private::prettyLayerName() const
{
QString name = url.fileName();
return !name.isEmpty() ? name : url.toDisplayString();
}
void KisImportCatcher::Private::importAsPaintLayer(KisPaintDeviceSP device)
{
KisLayerSP newLayer = new KisPaintLayer(view->image(),
prettyLayerName(),
OPACITY_OPAQUE_U8,
device);
KisNodeSP parent = 0;
KisLayerSP currentActiveLayer = view->activeLayer();
if (currentActiveLayer) {
parent = currentActiveLayer->parent();
}
if (parent.isNull()) {
parent = view->image()->rootLayer();
}
KisNodeCommandsAdapter adapter(view);
adapter.addNode(newLayer, parent, currentActiveLayer);
}
KisImportCatcher::KisImportCatcher(const QUrl &url, KisViewManager *view, const QString &layerType)
: m_d(new Private)
{
m_d->doc = KisPart::instance()->createDocument();
m_d->view = view;
m_d->url = url;
m_d->layerType = layerType;
connect(m_d->doc, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished()));
bool result = m_d->doc->openUrl(url, KisDocument::DontAddToRecent);
if (!result) {
deleteMyself();
}
}
void KisImportCatcher::slotLoadingFinished()
{
KisImageWSP importedImage = m_d->doc->image();
importedImage->waitForDone();
if (importedImage && importedImage->bounds().isValid()) {
if (m_d->layerType == "KisPaintLayer") {
KisPaintDeviceSP dev = importedImage->projection();
adaptClipToImageColorSpace(dev, m_d->view->image());
m_d->importAsPaintLayer(dev);
}
else if (m_d->layerType == "KisShapeLayer") {
KisShapeLayerSP shapeLayer = dynamic_cast<KisShapeLayer*>(m_d->view->nodeManager()->createNode(m_d->layerType, false, importedImage->projection()).data());
KisShapeLayerSP imported = dynamic_cast<KisShapeLayer*>(importedImage->rootLayer()->firstChild().data());
- const QTransform thisInvertedTransform = shapeLayer->absoluteTransformation(0).inverted();
+ const QTransform thisInvertedTransform = shapeLayer->absoluteTransformation().inverted();
Q_FOREACH (KoShape *shape, imported->shapes()) {
KoShape *clonedShape = shape->cloneShape();
- clonedShape->setTransformation(shape->absoluteTransformation(0) * thisInvertedTransform);
+ clonedShape->setTransformation(shape->absoluteTransformation() * thisInvertedTransform);
shapeLayer->addShape(clonedShape);
}
}
else {
m_d->view->nodeManager()->createNode(m_d->layerType, false, importedImage->projection());
}
}
deleteMyself();
}
void KisImportCatcher::deleteMyself()
{
m_d->doc->deleteLater();
deleteLater();
}
KisImportCatcher::~KisImportCatcher()
{
delete m_d;
}
void KisImportCatcher::adaptClipToImageColorSpace(KisPaintDeviceSP dev, KisImageSP image)
{
KisConfig cfg(true);
if (cfg.convertToImageColorspaceOnImport() && *dev->colorSpace() != *image->colorSpace()) {
/// XXX: do we need intent here?
dev->convertTo(image->colorSpace());
}
}
diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc
index 4506ced736..da4015598a 100644
--- a/libs/ui/kis_layer_manager.cc
+++ b/libs/ui/kis_layer_manager.cc
@@ -1,1002 +1,995 @@
/*
* 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 <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 <kis_meta_data_store.h>
#include <kis_meta_data_merge_strategy_registry.h>
#include <kis_psd_layer_style.h>
#include <KisMimeDatabase.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 "dialogs/KisDlgChangeCloneSource.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_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 "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 "kis_processing_applicator.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) {
layersUpdated();
if (layer) {
m_view->canvasResourceProvider()->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();
if (!layer) return;
QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
const bool multipleLayersSelected = selectedNodes.size() > 1;
KisAdjustmentLayerSP adjustmentLayer = KisAdjustmentLayerSP(dynamic_cast<KisAdjustmentLayer*>(layer.data()));
KisGeneratorLayerSP generatorLayer = KisGeneratorLayerSP(dynamic_cast<KisGeneratorLayer*>(layer.data()));
KisFileLayerSP fileLayer = KisFileLayerSP(dynamic_cast<KisFileLayer*>(layer.data()));
if (adjustmentLayer && !multipleLayersSelected) {
KisPaintDeviceSP dev = adjustmentLayer->projection();
KisDlgAdjLayerProps dlg(adjustmentLayer, adjustmentLayer.data(), dev, m_view, adjustmentLayer->filter().data(), adjustmentLayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops");
dlg.resize(dlg.minimumSizeHint());
KisFilterConfigurationSP configBefore(adjustmentLayer->filter());
KIS_ASSERT_RECOVER_RETURN(configBefore);
QString xmlBefore = configBefore->toXML();
if (dlg.exec() == QDialog::Accepted) {
adjustmentLayer->setName(dlg.layerName());
KisFilterConfigurationSP configAfter(dlg.filterConfiguration());
Q_ASSERT(configAfter);
QString xmlAfter = configAfter->toXML();
if(xmlBefore != xmlAfter) {
KisChangeFilterCmd *cmd
= new KisChangeFilterCmd(adjustmentLayer,
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) {
adjustmentLayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data()));
adjustmentLayer->setDirty();
}
}
}
else if (generatorLayer && !multipleLayersSelected) {
KisFilterConfigurationSP configBefore(generatorLayer->filter());
Q_ASSERT(configBefore);
QString xmlBefore = configBefore->toXML();
KisDlgGeneratorLayer *dlg = new KisDlgGeneratorLayer(generatorLayer->name(), m_view, m_view->mainWindow(), generatorLayer, configBefore);
dlg->setCaption(i18n("Fill Layer Properties"));
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setConfiguration(configBefore.data());
dlg->resize(dlg->minimumSizeHint());
Qt::WindowFlags flags = dlg->windowFlags();
dlg->setWindowFlags(flags | Qt::Tool | Qt::Dialog);
dlg->show();
}
else if (fileLayer && !multipleLayersSelected){
QString basePath = QFileInfo(m_view->document()->url().toLocalFile()).absolutePath();
QString fileNameOld = fileLayer->fileName();
KisFileLayer::ScalingMethod scalingMethodOld = fileLayer->scalingMethod();
KisDlgFileLayer dlg(basePath, fileLayer->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;
}
fileLayer->setName(dlg.layerName());
if (fileNameOld!= fileNameNew || scalingMethodOld != scalingMethodNew) {
KisChangeFileLayerCmd *cmd
= new KisChangeFileLayerCmd(fileLayer,
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::Tool | Qt::Dialog);
dialog->show();
}
}
void KisLayerManager::changeCloneSource()
{
QList<KisNodeSP> selectedNodes = m_view->nodeManager()->selectedNodes();
if (selectedNodes.isEmpty()) {
return;
}
QList<KisCloneLayerSP> cloneLayers;
KisNodeSP node;
Q_FOREACH (node, selectedNodes) {
KisCloneLayerSP cloneLayer(qobject_cast<KisCloneLayer *>(node.data()));
if (cloneLayer) {
cloneLayers << cloneLayer;
}
}
if (cloneLayers.isEmpty()) {
return;
}
KisDlgChangeCloneSource *dialog = new KisDlgChangeCloneSource(cloneLayers, m_view);
dialog->setCaption(i18n("Change Clone Layer"));
dialog->resize(dialog->minimumSizeHint());
dialog->setAttribute(Qt::WA_DeleteOnClose);
Qt::WindowFlags flags = dialog->windowFlags();
dialog->setWindowFlags(flags | Qt::Tool | 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->prevSibling();
while (parent && !parent->allowAsChild(layer)) {
above = above ? above->parent() : source->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->removeNode(source);
m_commandsAdapter->addNode(layer, parent, above);
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::supportedMimeTypes(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()).completeBaseName();
location.setFile(location.dir(), location.completeBaseName() + "_" + source->name() + ".png");
urlRequester->setFileName(location.absoluteFilePath());
}
else {
const QFileInfo location = QFileInfo(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
const QString proposedFileName = QDir(location.absoluteFilePath()).absoluteFilePath(source->name() + ".png");
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();
if (bounds.isEmpty()) {
bounds = image->bounds();
}
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) {
qWarning() << "Converting layer to file layer. path:"<< path << "gave errors" << 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();
if (parent->inherits("KisGroupLayer") && parent->collapsed()) {
above = parent;
parent = parent->parent();
return;
}
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, KisNodeSP layer, bool updateImage, KisProcessingApplicator *applicator)
{
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->addNodeAsync(layer, parent, above, updateImage, updateImage, applicator);
}
KisLayerSP KisLayerManager::addPaintLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace());
addLayerCommon(activeNode, layer, false, 0);
return layer;
}
KisNodeSP KisLayerManager::addGroupLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisGroupLayerSP group = new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, group, false, 0);
return group;
}
KisNodeSP KisLayerManager::addCloneLayer(KisNodeList nodes)
{
KisImageWSP image = m_view->image();
KisNodeList filteredNodes = KisLayerUtils::sortAndFilterMergableInternalNodes(nodes, false);
if (filteredNodes.isEmpty()) return KisNodeSP();
KisNodeSP newAbove = filteredNodes.last();
KisNodeSP node, lastClonedNode;
Q_FOREACH (node, filteredNodes) {
lastClonedNode = new KisCloneLayer(qobject_cast<KisLayer*>(node.data()), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
addLayerCommon(newAbove, lastClonedNode, true, 0 );
}
return lastClonedNode;
}
KisNodeSP KisLayerManager::addShapeLayer(KisNodeSP activeNode)
{
if (!m_view) return 0;
if (!m_view->document()) return 0;
KisImageWSP image = m_view->image();
KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, layer, false, 0);
return layer;
}
KisNodeSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
KisSelectionSP selection = m_view->selection();
KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Add Layer"));
KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection, &applicator);
KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original());
KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view, qApp->activeWindow());
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!
applicator.cancel();
} else {
adjl->setName(dlg.layerName());
applicator.end();
}
return adjl;
}
KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name,
KisFilterConfigurationSP filter,
KisSelectionSP selection,
KisProcessingApplicator *applicator)
{
KisImageWSP image = m_view->image();
KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection);
addLayerCommon(activeNode, layer, true, applicator);
return layer;
}
KisNodeSP KisLayerManager::addGeneratorLayer(KisNodeSP activeNode)
{
KisImageWSP image = m_view->image();
QColor currentForeground = m_view->canvasResourceProvider()->fgColor().toQColor();
KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow(), 0, 0);
KisFilterConfigurationSP defaultConfig = dlg.configuration();
defaultConfig->setProperty("color", currentForeground);
dlg.setConfiguration(defaultConfig);
dlg.resize(dlg.minimumSizeHint());
if (dlg.exec() == QDialog::Accepted) {
KisSelectionSP selection = m_view->selection();
KisFilterConfigurationSP generator = dlg.configuration();
QString name = dlg.layerName();
KisNodeSP node = new KisGeneratorLayer(image, name, generator, selection);
addLayerCommon(activeNode, node, true, 0);
return node;
}
return 0;
}
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(m_view->activeNode());
}
}
}
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::supportedMimeTypes(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(), false);
if (mimeType.isEmpty()) {
mimeType = "image/png";
}
QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first();
QString basename = f.completeBaseName();
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);
}
KisNodeSP 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 0;
}
KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution();
KisNodeSP node = new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8);
addLayerCommon(activeNode, node, true, 0);
return node;
}
return 0;
}
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->canvasResourceProvider());
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 0e93d4eab8..0a5dce153f 100644
--- a/libs/ui/kis_layer_manager.h
+++ b/libs/ui/kis_layer_manager.h
@@ -1,134 +1,134 @@
/*
* 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;
class KisProcessingApplicator;
/**
* 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:
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 addPaintLayer(KisNodeSP activeNode);
KisNodeSP addGroupLayer(KisNodeSP activeNode);
KisNodeSP addCloneLayer(KisNodeList nodes);
KisNodeSP addShapeLayer(KisNodeSP activeNode);
KisNodeSP addAdjustmentLayer(KisNodeSP activeNode);
KisAdjustmentLayerSP addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection, KisProcessingApplicator *applicator);
KisNodeSP addGeneratorLayer(KisNodeSP activeNode);
KisNodeSP addFileLayer(KisNodeSP activeNode);
void layerStyle();
void changeCloneSource();
private:
void adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above);
void addLayerCommon(KisNodeSP activeNode, KisNodeSP layer, bool updateImage = true, KisProcessingApplicator *applicator = 0);
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;
+ QPointer<KisView>m_imageView {0};
+
+ KisAction *m_imageFlatten {0};
+ KisAction *m_imageMergeLayer {0};
+ KisAction *m_groupLayersSave {0};
+ KisAction *m_convertGroupAnimated {0};
+ KisAction *m_imageResizeToLayer {0};
+ KisAction *m_flattenLayer {0};
+ KisAction *m_rasterizeLayer {0};
KisNodeCommandsAdapter* m_commandsAdapter;
- KisAction *m_layerStyle;
+ KisAction *m_layerStyle {0};
};
#endif
diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc
index 86e63f5bf2..2f3e2831a0 100644
--- a/libs/ui/kis_selection_manager.cc
+++ b/libs/ui/kis_selection_manager.cc
@@ -1,760 +1,742 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
*
* The outline algorithm uses the limn algorithm of fontutils by
* Karl Berry <karl@cs.umb.edu> and Kathryn Hargreaves <letters@cs.umb.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* 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_selection_manager.h"
#include <QApplication>
#include <QClipboard>
#include <QColor>
#include <QTimer>
#include <QMimeData>
#include <QAction>
#include <ktoggleaction.h>
#include <klocalizedstring.h>
#include <kstandardaction.h>
#include <kactioncollection.h>
#include "KoCanvasController.h"
#include "KoChannelInfo.h"
#include "KoIntegerMaths.h"
#include <KisDocument.h>
#include <KisMainWindow.h>
-#include <KoViewConverter.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxy.h>
#include <KoShapeStroke.h>
#include <KoColorSpace.h>
#include <KoCompositeOp.h>
#include <KoToolProxy.h>
#include <KoSvgPaste.h>
#include <kis_icon.h>
#include "kis_adjustment_layer.h"
#include "kis_node_manager.h"
#include "canvas/kis_canvas2.h"
#include "kis_config.h"
#include "kis_convolution_painter.h"
#include "kis_convolution_kernel.h"
#include "kis_debug.h"
#include "kis_fill_painter.h"
#include "kis_group_layer.h"
#include "kis_layer.h"
#include "kis_statusbar.h"
#include "kis_paint_device.h"
#include "kis_paint_layer.h"
#include "kis_painter.h"
#include "kis_transaction.h"
#include "kis_selection.h"
#include "kis_types.h"
#include "kis_canvas_resource_provider.h"
#include "kis_undo_adapter.h"
#include "kis_pixel_selection.h"
#include "flake/kis_shape_selection.h"
#include "commands/kis_selection_commands.h"
#include "kis_selection_mask.h"
#include "flake/kis_shape_layer.h"
#include "kis_selection_decoration.h"
#include "canvas/kis_canvas_decoration.h"
#include "kis_node_commands_adapter.h"
#include "kis_iterator_ng.h"
#include "kis_clipboard.h"
#include "KisViewManager.h"
#include "kis_selection_filters.h"
#include "kis_figure_painting_tool_helper.h"
#include "KisView.h"
#include "dialogs/kis_dlg_stroke_selection_properties.h"
#include "actions/kis_selection_action_factories.h"
#include "actions/KisPasteActionFactories.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "operations/kis_operation_configuration.h"
//new
#include "kis_node_query_path.h"
#include "kis_tool_shape.h"
KisSelectionManager::KisSelectionManager(KisViewManager * view)
- : m_view(view),
- m_doc(0),
- m_imageView(0),
- m_adapter(new KisNodeCommandsAdapter(view)),
- m_copy(0),
- m_copyMerged(0),
- m_cut(0),
- m_paste(0),
- m_pasteNew(0),
- m_cutToNewLayer(0),
- m_selectAll(0),
- m_deselect(0),
- m_clear(0),
- m_reselect(0),
- m_invert(0),
- m_copyToNewLayer(0),
- m_fillForegroundColor(0),
- m_fillBackgroundColor(0),
- m_fillPattern(0),
- m_imageResizeToSelection(0),
- m_selectionDecoration(0)
+ : m_view(view)
+ , m_adapter(new KisNodeCommandsAdapter(view))
{
m_clipboard = KisClipboard::instance();
}
KisSelectionManager::~KisSelectionManager()
{
}
void KisSelectionManager::setup(KisActionManager* actionManager)
{
m_cut = actionManager->createStandardAction(KStandardAction::Cut, this, SLOT(cut()));
m_copy = actionManager->createStandardAction(KStandardAction::Copy, this, SLOT(copy()));
m_paste = actionManager->createStandardAction(KStandardAction::Paste, this, SLOT(paste()));
KisAction *action = actionManager->createAction("copy_sharp");
connect(action, SIGNAL(triggered()), this, SLOT(copySharp()));
action = actionManager->createAction("cut_sharp");
connect(action, SIGNAL(triggered()), this, SLOT(cutSharp()));
m_pasteNew = actionManager->createAction("paste_new");
connect(m_pasteNew, SIGNAL(triggered()), this, SLOT(pasteNew()));
m_pasteAt = actionManager->createAction("paste_at");
connect(m_pasteAt, SIGNAL(triggered()), this, SLOT(pasteAt()));
m_pasteAsReference = actionManager->createAction("paste_as_reference");
connect(m_pasteAsReference, SIGNAL(triggered()), this, SLOT(pasteAsReference()));
m_copyMerged = actionManager->createAction("copy_merged");
connect(m_copyMerged, SIGNAL(triggered()), this, SLOT(copyMerged()));
m_selectAll = actionManager->createAction("select_all");
connect(m_selectAll, SIGNAL(triggered()), this, SLOT(selectAll()));
m_deselect = actionManager->createAction("deselect");
connect(m_deselect, SIGNAL(triggered()), this, SLOT(deselect()));
m_clear = actionManager->createAction("clear");
connect(m_clear, SIGNAL(triggered()), SLOT(clear()));
m_reselect = actionManager->createAction("reselect");
connect(m_reselect, SIGNAL(triggered()), this, SLOT(reselect()));
m_invert = actionManager->createAction("invert_selection");
m_invert->setOperationID("invertselection");
actionManager->registerOperation(new KisInvertSelectionOperation);
m_copyToNewLayer = actionManager->createAction("copy_selection_to_new_layer");
connect(m_copyToNewLayer, SIGNAL(triggered()), this, SLOT(copySelectionToNewLayer()));
m_cutToNewLayer = actionManager->createAction("cut_selection_to_new_layer");
connect(m_cutToNewLayer, SIGNAL(triggered()), this, SLOT(cutToNewLayer()));
m_fillForegroundColor = actionManager->createAction("fill_selection_foreground_color");
connect(m_fillForegroundColor, SIGNAL(triggered()), this, SLOT(fillForegroundColor()));
m_fillBackgroundColor = actionManager->createAction("fill_selection_background_color");
connect(m_fillBackgroundColor, SIGNAL(triggered()), this, SLOT(fillBackgroundColor()));
m_fillPattern = actionManager->createAction("fill_selection_pattern");
connect(m_fillPattern, SIGNAL(triggered()), this, SLOT(fillPattern()));
m_fillForegroundColorOpacity = actionManager->createAction("fill_selection_foreground_color_opacity");
connect(m_fillForegroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillForegroundColorOpacity()));
m_fillBackgroundColorOpacity = actionManager->createAction("fill_selection_background_color_opacity");
connect(m_fillBackgroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillBackgroundColorOpacity()));
m_fillPatternOpacity = actionManager->createAction("fill_selection_pattern_opacity");
connect(m_fillPatternOpacity, SIGNAL(triggered()), this, SLOT(fillPatternOpacity()));
m_strokeShapes = actionManager->createAction("stroke_shapes");
connect(m_strokeShapes, SIGNAL(triggered()), this, SLOT(paintSelectedShapes()));
m_toggleDisplaySelection = actionManager->createAction("toggle_display_selection");
connect(m_toggleDisplaySelection, SIGNAL(triggered()), this, SLOT(toggleDisplaySelection()));
m_toggleDisplaySelection->setChecked(true);
m_imageResizeToSelection = actionManager->createAction("resizeimagetoselection");
connect(m_imageResizeToSelection, SIGNAL(triggered()), this, SLOT(imageResizeToSelection()));
action = actionManager->createAction("edit_selection");
connect(action, SIGNAL(triggered()), SLOT(editSelection()));
action = actionManager->createAction("convert_to_vector_selection");
connect(action, SIGNAL(triggered()), SLOT(convertToVectorSelection()));
action = actionManager->createAction("convert_to_raster_selection");
connect(action, SIGNAL(triggered()), SLOT(convertToRasterSelection()));
action = actionManager->createAction("convert_shapes_to_vector_selection");
connect(action, SIGNAL(triggered()), SLOT(convertShapesToVectorSelection()));
action = actionManager->createAction("convert_selection_to_shape");
connect(action, SIGNAL(triggered()), SLOT(convertToShape()));
m_toggleSelectionOverlayMode = actionManager->createAction("toggle-selection-overlay-mode");
connect(m_toggleSelectionOverlayMode, SIGNAL(triggered()), SLOT(slotToggleSelectionDecoration()));
m_strokeSelected = actionManager->createAction("stroke_selection");
connect(m_strokeSelected, SIGNAL(triggered()), SLOT(slotStrokeSelection()));
QClipboard *cb = QApplication::clipboard();
connect(cb, SIGNAL(dataChanged()), SLOT(clipboardDataChanged()));
}
void KisSelectionManager::setView(QPointer<KisView>imageView)
{
if (m_imageView && m_imageView->canvasBase()) {
disconnect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), this, SLOT(clipboardDataChanged()));
KoSelection *selection = m_imageView->canvasBase()->globalShapeManager()->selection();
selection->disconnect(this, SLOT(shapeSelectionChanged()));
KisSelectionDecoration *decoration = qobject_cast<KisSelectionDecoration*>(m_imageView->canvasBase()->decoration("selection").data());
if (decoration) {
disconnect(SIGNAL(currentSelectionChanged()), decoration);
}
m_imageView->image()->undoAdapter()->disconnect(this);
m_selectionDecoration = 0;
}
m_imageView = imageView;
if (m_imageView) {
connect(m_imageView->canvasBase()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()), Qt::UniqueConnection);
KisSelectionDecoration* decoration = qobject_cast<KisSelectionDecoration*>(m_imageView->canvasBase()->decoration("selection").data());
if (!decoration) {
decoration = new KisSelectionDecoration(m_imageView);
decoration->setVisible(true);
m_imageView->canvasBase()->addDecoration(decoration);
}
m_selectionDecoration = decoration;
connect(this, SIGNAL(currentSelectionChanged()), decoration, SLOT(selectionChanged()));
connect(m_imageView->image()->undoAdapter(), SIGNAL(selectionChanged()), SLOT(selectionChanged()));
connect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), SLOT(clipboardDataChanged()));
}
}
void KisSelectionManager::clipboardDataChanged()
{
m_view->updateGUI();
}
bool KisSelectionManager::havePixelsSelected()
{
KisSelectionSP activeSelection = m_view->selection();
return activeSelection && !activeSelection->selectedRect().isEmpty();
}
bool KisSelectionManager::havePixelsInClipboard()
{
return m_clipboard->hasClip();
}
bool KisSelectionManager::haveShapesSelected()
{
if (m_view && m_view->canvasBase()) {
return m_view->canvasBase()->selectedShapesProxy()->selection()->count() > 0;
}
return false;
}
bool KisSelectionManager::haveShapesInClipboard()
{
KoSvgPaste paste;
return paste.hasShapes();
}
bool KisSelectionManager::haveAnySelectionWithPixels()
{
KisSelectionSP selection = m_view->selection();
return selection && selection->hasPixelSelection();
}
bool KisSelectionManager::haveShapeSelectionWithShapes()
{
KisSelectionSP selection = m_view->selection();
return selection && selection->hasShapeSelection();
}
bool KisSelectionManager::haveRasterSelectionWithPixels()
{
KisSelectionSP selection = m_view->selection();
return selection && selection->hasPixelSelection() && !selection->hasShapeSelection();
}
void KisSelectionManager::updateGUI()
{
Q_ASSERT(m_view);
Q_ASSERT(m_clipboard);
if (!m_view || !m_clipboard) return;
bool havePixelsSelected = this->havePixelsSelected();
bool havePixelsInClipboard = this->havePixelsInClipboard();
bool haveShapesSelected = this->haveShapesSelected();
bool haveShapesInClipboard = this->haveShapesInClipboard();
bool haveDevice = m_view->activeDevice();
KisLayerSP activeLayer = m_view->activeLayer();
KisImageWSP image = activeLayer ? activeLayer->image() : 0;
bool canReselect = image && image->canReselectGlobalSelection();
bool canDeselect = image && image->globalSelection();
m_clear->setEnabled(haveDevice || havePixelsSelected || haveShapesSelected);
m_cut->setEnabled(havePixelsSelected || haveShapesSelected);
m_copy->setEnabled(havePixelsSelected || haveShapesSelected);
m_paste->setEnabled(havePixelsInClipboard || haveShapesInClipboard);
m_pasteAt->setEnabled(havePixelsInClipboard || haveShapesInClipboard);
// FIXME: how about pasting shapes?
m_pasteNew->setEnabled(havePixelsInClipboard);
m_pasteAsReference->setEnabled(haveDevice);
m_selectAll->setEnabled(true);
m_deselect->setEnabled(canDeselect);
m_reselect->setEnabled(canReselect);
// m_load->setEnabled(true);
// m_save->setEnabled(havePixelsSelected);
updateStatusBar();
emit signalUpdateGUI();
}
void KisSelectionManager::updateStatusBar()
{
if (m_view && m_view->statusBar()) {
m_view->statusBar()->setSelection(m_view->image());
}
}
void KisSelectionManager::selectionChanged()
{
m_view->updateGUI();
emit currentSelectionChanged();
}
void KisSelectionManager::cut()
{
KisCutCopyActionFactory factory;
factory.run(true, false, m_view);
}
void KisSelectionManager::copy()
{
KisCutCopyActionFactory factory;
factory.run(false, false, m_view);
}
void KisSelectionManager::cutSharp()
{
KisCutCopyActionFactory factory;
factory.run(true, true, m_view);
}
void KisSelectionManager::copySharp()
{
KisCutCopyActionFactory factory;
factory.run(false, true, m_view);
}
void KisSelectionManager::copyMerged()
{
KisCopyMergedActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::paste()
{
KisPasteActionFactory factory;
factory.run(false, m_view);
}
void KisSelectionManager::pasteAt()
{
KisPasteActionFactory factory;
factory.run(true, m_view);
}
void KisSelectionManager::pasteAsReference()
{
KisPasteReferenceActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::pasteNew()
{
KisPasteNewActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::selectAll()
{
KisSelectAllActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::deselect()
{
KisDeselectActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::invert()
{
if(m_invert)
m_invert->trigger();
}
void KisSelectionManager::reselect()
{
KisReselectActionFactory factory;
factory.run(m_view);
}
#include <KoToolManager.h>
#include <KoInteractionTool.h>
void KisSelectionManager::editSelection()
{
KisSelectionSP selection = m_view->selection();
if (!selection) return;
KisAction *action = m_view->actionManager()->actionByName("show-global-selection-mask");
KIS_SAFE_ASSERT_RECOVER_RETURN(action);
if (!action->isChecked()) {
action->setChecked(true);
emit action->toggled(true);
emit action->triggered(true);
}
KisNodeSP node = selection->parentNode();
KIS_SAFE_ASSERT_RECOVER_RETURN(node);
m_view->nodeManager()->slotNonUiActivatedNode(node);
if (selection->hasShapeSelection()) {
KisShapeSelection *shapeSelection = dynamic_cast<KisShapeSelection*>(selection->shapeSelection());
KIS_SAFE_ASSERT_RECOVER_RETURN(shapeSelection);
KoToolManager::instance()->switchToolRequested(KoInteractionTool_ID);
QList<KoShape*> shapes = shapeSelection->shapes();
if (shapes.isEmpty()) {
KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "no shapes");
return;
}
Q_FOREACH (KoShape *shape, shapes) {
m_view->canvasBase()->selectedShapesProxy()->selection()->select(shape);
}
} else {
KoToolManager::instance()->switchToolRequested("KisToolTransform");
}
}
void KisSelectionManager::convertToVectorSelection()
{
KisSelectionToVectorActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::convertToRasterSelection()
{
KisSelectionToRasterActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::convertShapesToVectorSelection()
{
KisShapesToVectorSelectionActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::convertToShape()
{
KisSelectionToShapeActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::clear()
{
KisClearActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::fillForegroundColor()
{
KisFillActionFactory factory;
factory.run("fg", m_view);
}
void KisSelectionManager::fillBackgroundColor()
{
KisFillActionFactory factory;
factory.run("bg", m_view);
}
void KisSelectionManager::fillPattern()
{
KisFillActionFactory factory;
factory.run("pattern", m_view);
}
void KisSelectionManager::fillForegroundColorOpacity()
{
KisFillActionFactory factory;
factory.run("fg_opacity", m_view);
}
void KisSelectionManager::fillBackgroundColorOpacity()
{
KisFillActionFactory factory;
factory.run("bg_opacity", m_view);
}
void KisSelectionManager::fillPatternOpacity()
{
KisFillActionFactory factory;
factory.run("pattern_opacity", m_view);
}
void KisSelectionManager::copySelectionToNewLayer()
{
copy();
paste();
}
void KisSelectionManager::cutToNewLayer()
{
cut();
paste();
}
void KisSelectionManager::toggleDisplaySelection()
{
KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration);
m_selectionDecoration->toggleVisibility();
m_toggleDisplaySelection->blockSignals(true);
m_toggleDisplaySelection->setChecked(m_selectionDecoration->visible());
m_toggleDisplaySelection->blockSignals(false);
emit displaySelectionChanged();
}
bool KisSelectionManager::displaySelection()
{
return m_toggleDisplaySelection->isChecked();
}
void KisSelectionManager::shapeSelectionChanged()
{
KoShapeManager* shapeManager = m_view->canvasBase()->globalShapeManager();
KoSelection * selection = shapeManager->selection();
QList<KoShape*> selectedShapes = selection->selectedShapes();
KoShapeStrokeSP border(new KoShapeStroke(0, Qt::lightGray));
Q_FOREACH (KoShape* shape, shapeManager->shapes()) {
if (dynamic_cast<KisShapeSelection*>(shape->parent())) {
if (selectedShapes.contains(shape))
shape->setStroke(border);
else
shape->setStroke(KoShapeStrokeSP());
}
}
m_view->updateGUI();
}
void KisSelectionManager::imageResizeToSelection()
{
KisImageResizeToSelectionActionFactory factory;
factory.run(m_view);
}
void KisSelectionManager::paintSelectedShapes()
{
KisImageWSP image = m_view->image();
if (!image) return;
KisLayerSP layer = m_view->activeLayer();
if (!layer) return;
QList<KoShape*> shapes = m_view->canvasBase()->shapeManager()->selection()->selectedShapes();
KisPaintLayerSP paintLayer = new KisPaintLayer(image, i18n("Stroked Shapes"), OPACITY_OPAQUE_U8);
KUndo2MagicString actionName = kundo2_i18n("Stroke Shapes");
m_adapter->beginMacro(actionName);
m_adapter->addNode(paintLayer.data(), layer->parent().data(), layer.data());
KisFigurePaintingToolHelper helper(actionName,
image,
paintLayer.data(),
m_view->canvasResourceProvider()->resourceManager(),
KisToolShapeUtils::StrokeStyleForeground,
KisToolShapeUtils::FillStyleNone);
Q_FOREACH (KoShape* shape, shapes) {
- QTransform matrix = shape->absoluteTransformation(0) * QTransform::fromScale(image->xRes(), image->yRes());
+ QTransform matrix = shape->absoluteTransformation() * QTransform::fromScale(image->xRes(), image->yRes());
QPainterPath mapedOutline = matrix.map(shape->outline());
helper.paintPainterPath(mapedOutline);
}
m_adapter->endMacro();
}
void KisSelectionManager::slotToggleSelectionDecoration()
{
KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration);
KisSelectionDecoration::Mode mode =
m_selectionDecoration->mode() ?
KisSelectionDecoration::Ants : KisSelectionDecoration::Mask;
m_selectionDecoration->setMode(mode);
emit displaySelectionChanged();
}
bool KisSelectionManager::showSelectionAsMask() const
{
if (m_selectionDecoration) {
return m_selectionDecoration->mode() == KisSelectionDecoration::Mask;
}
return false;
}
void KisSelectionManager::slotStrokeSelection()
{
KisImageWSP image = m_view->image();
if (!image ) {
return;
}
KisNodeSP currentNode = m_view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value<KisNodeWSP>();
bool isVectorLayer = false;
if (currentNode->inherits("KisShapeLayer")) {
isVectorLayer = true;
}
+ qDebug() << "isVectorLayer" << isVectorLayer;
+
QPointer<KisDlgStrokeSelection> dlg = new KisDlgStrokeSelection(image, m_view, isVectorLayer);
if (dlg->exec() == QDialog::Accepted) {
StrokeSelectionOptions params = dlg->getParams();
if (params.brushSelected){
KisStrokeBrushSelectionActionFactory factory;
factory.run(m_view, params);
}
else {
KisStrokeSelectionActionFactory factory;
factory.run(m_view, params);
}
}
delete dlg;
}
#include "kis_image_barrier_locker.h"
#include "kis_selection_tool_helper.h"
void KisSelectionManager::selectOpaqueOnNode(KisNodeSP node, SelectionAction action)
{
KisImageSP image = m_view->image();
if (!m_view->blockUntilOperationsFinished(image)) {
return;
}
KUndo2MagicString actionName;
KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
KisCanvas2 *canvas = m_view->canvasBase();
{
KisImageBarrierLocker locker(image);
KisPaintDeviceSP device = node->projection();
if (!device) device = node->paintDevice();
if (!device) device = node->original();
if (!device) return;
QRect rc = device->exactBounds();
if (rc.isEmpty()) return;
KIS_ASSERT_RECOVER_RETURN(canvas);
/**
* If there is nothing selected, just create a new selection
*/
if (!canvas->imageView()->selection()) {
action = SELECTION_REPLACE;
}
switch (action) {
case SELECTION_ADD:
actionName = kundo2_i18n("Select Opaque (Add)");
break;
case SELECTION_SUBTRACT:
actionName = kundo2_i18n("Select Opaque (Subtract)");
break;
case SELECTION_INTERSECT:
actionName = kundo2_i18n("Select Opaque (Intersect)");
break;
case SELECTION_SYMMETRICDIFFERENCE:
actionName = kundo2_i18n("Select Opaque (Symmetric Difference)");
break;
default:
actionName = kundo2_i18n("Select Opaque");
break;
}
qint32 x, y, w, h;
rc.getRect(&x, &y, &w, &h);
const KoColorSpace * cs = device->colorSpace();
KisHLineConstIteratorSP deviter = device->createHLineConstIteratorNG(x, y, w);
KisHLineIteratorSP selIter = tmpSel ->createHLineIteratorNG(x, y, w);
for (int row = y; row < h + y; ++row) {
do {
*selIter->rawData() = cs->opacityU8(deviter->oldRawData());
} while (deviter->nextPixel() && selIter->nextPixel());
deviter->nextRow();
selIter->nextRow();
}
}
KisSelectionToolHelper helper(canvas, actionName);
tmpSel->invalidateOutlineCache();
helper.selectPixelSelection(tmpSel, action);
}
diff --git a/libs/ui/kis_selection_manager.h b/libs/ui/kis_selection_manager.h
index 24c80b7fc9..733cf28327 100644
--- a/libs/ui/kis_selection_manager.h
+++ b/libs/ui/kis_selection_manager.h
@@ -1,180 +1,179 @@
/*
* 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_SELECTION_MANAGER_
#define KIS_SELECTION_MANAGER_
#include <QObject>
#include <QList>
#include <QPointer>
#include <kis_image.h>
#include "KisView.h"
#include <KisSelectionTags.h>
#include <kritaui_export.h>
class KisActionManager;
class KisAction;
class QAction;
-class KoViewConverter;
class KisDocument;
class KisViewManager;
class KisClipboard;
class KisNodeCommandsAdapter;
class KisView;
class KisSelectionFilter;
class KisSelectionDecoration;
/**
* The selection manager is responsible selections
* and the clipboard.
*/
class KRITAUI_EXPORT KisSelectionManager : public QObject
{
Q_OBJECT
Q_PROPERTY(bool displaySelection READ displaySelection NOTIFY displaySelectionChanged);
Q_PROPERTY(bool havePixelsSelected READ havePixelsSelected NOTIFY currentSelectionChanged);
public:
KisSelectionManager(KisViewManager * view);
~KisSelectionManager() override;
void setup(KisActionManager* actionManager);
void setView(QPointer<KisView>imageView);
public:
/**
* This function return if the selection should be displayed
*/
bool displaySelection();
bool showSelectionAsMask() const;
public Q_SLOTS:
void updateGUI();
void selectionChanged();
void clipboardDataChanged();
void cut();
void copy();
void cutSharp();
void copySharp();
void copyMerged();
void paste();
void pasteNew();
void pasteAt();
void pasteAsReference();
void cutToNewLayer();
void selectAll();
void deselect();
void invert();
void clear();
void fillForegroundColor();
void fillBackgroundColor();
void fillPattern();
void fillForegroundColorOpacity();
void fillBackgroundColorOpacity();
void fillPatternOpacity();
void reselect();
void editSelection();
void convertToVectorSelection();
void convertToRasterSelection();
void convertShapesToVectorSelection();
void convertToShape();
void copySelectionToNewLayer();
void toggleDisplaySelection();
void shapeSelectionChanged();
void imageResizeToSelection();
void paintSelectedShapes();
void slotToggleSelectionDecoration();
void slotStrokeSelection();
void selectOpaqueOnNode(KisNodeSP node, SelectionAction action);
Q_SIGNALS:
void currentSelectionChanged();
void signalUpdateGUI();
void displaySelectionChanged();
void strokeSelected();
public:
bool havePixelsSelected();
bool havePixelsInClipboard();
bool haveShapesSelected();
bool haveShapesInClipboard();
/// Checks if the current selection is editable and has some pixels selected in the pixel selection
bool haveAnySelectionWithPixels();
bool haveShapeSelectionWithShapes();
bool haveRasterSelectionWithPixels();
private:
void fill(const KoColor& color, bool fillWithPattern, const QString& transactionText);
void updateStatusBar();
- KisViewManager * m_view;
- KisDocument * m_doc;
- QPointer<KisView>m_imageView;
- KisClipboard * m_clipboard;
-
- KisNodeCommandsAdapter* m_adapter;
-
- KisAction *m_copy;
- KisAction *m_copyMerged;
- KisAction *m_cut;
- KisAction *m_paste;
- KisAction *m_pasteAt;
- KisAction *m_pasteAsReference;
- KisAction *m_pasteNew;
- KisAction *m_cutToNewLayer;
- KisAction *m_selectAll;
- KisAction *m_deselect;
- KisAction *m_clear;
- KisAction *m_reselect;
- KisAction *m_invert;
- KisAction *m_copyToNewLayer;
- KisAction *m_fillForegroundColor;
- KisAction *m_fillBackgroundColor;
- KisAction *m_fillPattern;
- KisAction *m_fillForegroundColorOpacity;
- KisAction *m_fillBackgroundColorOpacity;
- KisAction *m_fillPatternOpacity;
- KisAction *m_imageResizeToSelection;
- KisAction *m_strokeShapes;
- KisAction *m_toggleDisplaySelection;
- KisAction *m_toggleSelectionOverlayMode;
- KisAction *m_strokeSelected;
+ KisViewManager * m_view {0};
+ KisDocument * m_doc {0};
+ QPointer<KisView>m_imageView {0};
+ KisClipboard * m_clipboard {0};
+
+ KisNodeCommandsAdapter* m_adapter {0};
+
+ KisAction *m_copy {0};
+ KisAction *m_copyMerged {0};
+ KisAction *m_cut {0};
+ KisAction *m_paste {0};
+ KisAction *m_pasteAt {0};
+ KisAction *m_pasteAsReference {0};
+ KisAction *m_pasteNew {0};
+ KisAction *m_cutToNewLayer {0};
+ KisAction *m_selectAll {0};
+ KisAction *m_deselect {0};
+ KisAction *m_clear {0};
+ KisAction *m_reselect {0};
+ KisAction *m_invert {0};
+ KisAction *m_copyToNewLayer {0};
+ KisAction *m_fillForegroundColor {0};
+ KisAction *m_fillBackgroundColor {0};
+ KisAction *m_fillPattern {0};
+ KisAction *m_fillForegroundColorOpacity {0};
+ KisAction *m_fillBackgroundColorOpacity {0};
+ KisAction *m_fillPatternOpacity {0};
+ KisAction *m_imageResizeToSelection {0};
+ KisAction *m_strokeShapes {0};
+ KisAction *m_toggleDisplaySelection {0};
+ KisAction *m_toggleSelectionOverlayMode {0};
+ KisAction *m_strokeSelected {0};
QList<QAction*> m_pluginActions;
QPointer<KisSelectionDecoration> m_selectionDecoration;
};
#endif // KIS_SELECTION_MANAGER_
diff --git a/libs/ui/opengl/KisOpenGLModeProber.cpp b/libs/ui/opengl/KisOpenGLModeProber.cpp
index 1fe495b541..6589e67034 100644
--- a/libs/ui/opengl/KisOpenGLModeProber.cpp
+++ b/libs/ui/opengl/KisOpenGLModeProber.cpp
@@ -1,333 +1,333 @@
/*
* Copyright (c) 2017 Alvin Wong <alvinhochun@gmail.com>
* Copyright (c) 2019 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 "KisOpenGLModeProber.h"
#include <config-hdr.h>
#include <QApplication>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QWindow>
#include <QGlobalStatic>
Q_GLOBAL_STATIC(KisOpenGLModeProber, s_instance)
KisOpenGLModeProber::KisOpenGLModeProber()
{
}
KisOpenGLModeProber::~KisOpenGLModeProber()
{
}
KisOpenGLModeProber *KisOpenGLModeProber::instance()
{
return s_instance;
}
bool KisOpenGLModeProber::useHDRMode() const
{
return isFormatHDR(QSurfaceFormat::defaultFormat());
}
QSurfaceFormat KisOpenGLModeProber::surfaceformatInUse() const
{
// TODO: use information provided by KisOpenGL instead
QOpenGLContext *sharedContext = QOpenGLContext::globalShareContext();
QSurfaceFormat format = sharedContext ? sharedContext->format() : QSurfaceFormat::defaultFormat();
return format;
}
const KoColorProfile *KisOpenGLModeProber::rootSurfaceColorProfile() const
{
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->p709SRGBProfile();
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
const KisSurfaceColorSpace surfaceColorSpace = surfaceformatInUse().colorSpace();
if (surfaceColorSpace == KisSurfaceColorSpace::sRGBColorSpace) {
// use the default one!
#ifdef HAVE_HDR
} else if (surfaceColorSpace == KisSurfaceColorSpace::scRGBColorSpace) {
profile = KoColorSpaceRegistry::instance()->p709G10Profile();
} else if (surfaceColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace) {
profile = KoColorSpaceRegistry::instance()->p2020PQProfile();
#endif
}
#endif
return profile;
}
namespace {
struct AppAttributeSetter
{
AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES)
: m_attribute(attribute),
m_oldValue(QCoreApplication::testAttribute(attribute))
{
QCoreApplication::setAttribute(attribute, useOpenGLES);
}
~AppAttributeSetter() {
QCoreApplication::setAttribute(m_attribute, m_oldValue);
}
private:
Qt::ApplicationAttribute m_attribute;
bool m_oldValue = false;
};
struct SurfaceFormatSetter
{
SurfaceFormatSetter(const QSurfaceFormat &format)
: m_oldFormat(QSurfaceFormat::defaultFormat())
{
QSurfaceFormat::setDefaultFormat(format);
}
~SurfaceFormatSetter() {
QSurfaceFormat::setDefaultFormat(m_oldFormat);
}
private:
QSurfaceFormat m_oldFormat;
};
#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
QString qEnvironmentVariable(const char *varName) {
return qgetenv(varName);
}
#endif
struct EnvironmentSetter
{
EnvironmentSetter(const QLatin1String &env, const QString &value)
: m_env(env)
{
if (qEnvironmentVariableIsEmpty(m_env.latin1())) {
m_oldValue = qgetenv(env.latin1());
}
if (!value.isEmpty()) {
qputenv(env.latin1(), value.toLatin1());
} else {
qunsetenv(env.latin1());
}
}
~EnvironmentSetter() {
if (m_oldValue) {
qputenv(m_env.latin1(), (*m_oldValue).toLatin1());
} else {
qunsetenv(m_env.latin1());
}
}
private:
const QLatin1String m_env;
boost::optional<QString> m_oldValue;
};
}
boost::optional<KisOpenGLModeProber::Result>
KisOpenGLModeProber::probeFormat(const KisOpenGL::RendererConfig &rendererConfig,
bool adjustGlobalState)
{
const QSurfaceFormat &format = rendererConfig.format;
QScopedPointer<AppAttributeSetter> sharedContextSetter;
QScopedPointer<AppAttributeSetter> glSetter;
QScopedPointer<AppAttributeSetter> glesSetter;
QScopedPointer<SurfaceFormatSetter> formatSetter;
QScopedPointer<EnvironmentSetter> rendererSetter;
QScopedPointer<QGuiApplication> application;
int argc = 1;
QByteArray probeAppName("krita");
char *argv = probeAppName.data();
if (adjustGlobalState) {
sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false));
if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) {
glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES));
glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES));
}
rendererSetter.reset(new EnvironmentSetter(QLatin1String("QT_ANGLE_PLATFORM"), angleRendererToString(rendererConfig.angleRenderer)));
formatSetter.reset(new SurfaceFormatSetter(format));
QGuiApplication::setDesktopSettingsAware(false);
application.reset(new QGuiApplication(argc, &argv));
QGuiApplication::setDesktopSettingsAware(true);
}
QWindow surface;
surface.setFormat(format);
surface.setSurfaceType(QSurface::OpenGLSurface);
surface.create();
QOpenGLContext context;
context.setFormat(format);
if (!context.create()) {
dbgOpenGL << "OpenGL context cannot be created";
return boost::none;
}
if (!context.isValid()) {
dbgOpenGL << "OpenGL context is not valid while checking Qt's OpenGL status";
return boost::none;
}
if (!context.makeCurrent(&surface)) {
dbgOpenGL << "OpenGL context cannot be made current";
return boost::none;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
if (!fuzzyCompareColorSpaces(context.format().colorSpace(), format.colorSpace())) {
dbgOpenGL << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace();
return boost::none;
}
#endif
return Result(context);
}
bool KisOpenGLModeProber::fuzzyCompareColorSpaces(const KisSurfaceColorSpace &lhs, const KisSurfaceColorSpace &rhs)
{
return lhs == rhs ||
((lhs == KisSurfaceColorSpace::DefaultColorSpace ||
lhs == KisSurfaceColorSpace::sRGBColorSpace) &&
(rhs == KisSurfaceColorSpace::DefaultColorSpace ||
rhs == KisSurfaceColorSpace::sRGBColorSpace));
}
void KisOpenGLModeProber::initSurfaceFormatFromConfig(KisConfig::RootSurfaceFormat config,
QSurfaceFormat *format)
{
#ifdef HAVE_HDR
if (config == KisConfig::BT2020_PQ) {
format->setRedBufferSize(10);
format->setGreenBufferSize(10);
format->setBlueBufferSize(10);
format->setAlphaBufferSize(2);
format->setColorSpace(KisSurfaceColorSpace::bt2020PQColorSpace);
} else if (config == KisConfig::BT709_G10) {
format->setRedBufferSize(16);
format->setGreenBufferSize(16);
format->setBlueBufferSize(16);
format->setAlphaBufferSize(16);
format->setColorSpace(KisSurfaceColorSpace::scRGBColorSpace);
} else
#else
if (config == KisConfig::BT2020_PQ) {
- qWarning() << "WARNING: Bt.2020 PQ surface type is not supoprted by this build of Krita";
+ qWarning() << "WARNING: Bt.2020 PQ surface type is not supported by this build of Krita";
} else if (config == KisConfig::BT709_G10) {
- qWarning() << "WARNING: scRGB surface type is not supoprted by this build of Krita";
+ qWarning() << "WARNING: scRGB surface type is not supported by this build of Krita";
}
#endif
{
format->setRedBufferSize(8);
format->setGreenBufferSize(8);
format->setBlueBufferSize(8);
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
format->setAlphaBufferSize(8);
#else
format->setAlphaBufferSize(0);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
// TODO: check if we can use real sRGB space here
format->setColorSpace(KisSurfaceColorSpace::DefaultColorSpace);
#endif
}
}
bool KisOpenGLModeProber::isFormatHDR(const QSurfaceFormat &format)
{
#ifdef HAVE_HDR
bool isBt2020PQ =
format.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace &&
format.redBufferSize() == 10 &&
format.greenBufferSize() == 10 &&
format.blueBufferSize() == 10 &&
format.alphaBufferSize() == 2;
bool isBt709G10 =
format.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace &&
format.redBufferSize() == 16 &&
format.greenBufferSize() == 16 &&
format.blueBufferSize() == 16 &&
format.alphaBufferSize() == 16;
return isBt2020PQ || isBt709G10;
#else
Q_UNUSED(format);
return false;
#endif
}
QString KisOpenGLModeProber::angleRendererToString(KisOpenGL::AngleRenderer renderer)
{
QString value;
switch (renderer) {
case KisOpenGL::AngleRendererDefault:
break;
case KisOpenGL::AngleRendererD3d9:
value = "d3d9";
break;
case KisOpenGL::AngleRendererD3d11:
value = "d3d11";
break;
case KisOpenGL::AngleRendererD3d11Warp:
value = "warp";
break;
};
return value;
}
KisOpenGLModeProber::Result::Result(QOpenGLContext &context) {
if (!context.isValid()) {
return;
}
QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used
m_rendererString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_RENDERER)));
m_driverVersionString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_VERSION)));
m_vendorString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_VENDOR)));
m_shadingLanguageString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION)));
m_glMajorVersion = context.format().majorVersion();
m_glMinorVersion = context.format().minorVersion();
m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions);
m_isOpenGLES = context.isOpenGLES();
m_format = context.format();
}
diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp
index bcfa3a7c9c..32171dc3c1 100644
--- a/libs/ui/opengl/kis_opengl.cpp
+++ b/libs/ui/opengl/kis_opengl.cpp
@@ -1,913 +1,913 @@
/*
* 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 <config-hdr.h>
#include "opengl/kis_opengl.h"
#include "opengl/kis_opengl_p.h"
#include <QOpenGLContext>
#include <QOpenGLDebugLogger>
#include <QOpenGLFunctions>
#include <QApplication>
#include <QDesktopWidget>
#include <QPixmapCache>
#include <QDir>
#include <QFile>
#include <QStandardPaths>
#include <QVector>
#include <QWindow>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kis_config.h>
#include "KisOpenGLModeProber.h"
#include <KisUsageLogger.h>
#include <boost/optional.hpp>
#include "kis_assert.h"
#include <QRegularExpression>
#include <QSettings>
#include <QScreen>
#ifndef GL_RENDERER
# define GL_RENDERER 0x1F01
#endif
using namespace KisOpenGLPrivate;
namespace
{
// config option, set manually by main()
bool g_isDebugSynchronous = false;
bool g_sanityDefaultFormatIsSet = false;
boost::optional<KisOpenGLModeProber::Result> openGLCheckResult;
bool g_needsFenceWorkaround = false;
bool g_needsPixmapCacheWorkaround = false;
QString g_surfaceFormatDetectionLog;
QString g_debugText("OpenGL Info\n **OpenGL not initialized**");
QVector<KLocalizedString> g_openglWarningStrings;
KisOpenGL::OpenGLRenderers g_supportedRenderers;
KisOpenGL::OpenGLRenderer g_rendererPreferredByQt;
void overrideSupportedRenderers(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt) {
g_supportedRenderers = supportedRenderers;
g_rendererPreferredByQt = preferredByQt;
}
void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) {
qDebug() << "OpenGL:" << debugMessage;
}
KisOpenGL::OpenGLRenderer getRendererFromProbeResult(KisOpenGLModeProber::Result info) {
KisOpenGL::OpenGLRenderer result = KisOpenGL::RendererDesktopGL;
if (info.isOpenGLES()) {
const QString rendererString = info.rendererString().toLower();
if (rendererString.contains("basic render driver") ||
rendererString.contains("software")) {
result = KisOpenGL::RendererSoftware;
} else {
result = KisOpenGL::RendererOpenGLES;
}
}
return result;
}
}
KisOpenGLPrivate::OpenGLCheckResult::OpenGLCheckResult(QOpenGLContext &context) {
if (!context.isValid()) {
return;
}
QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used
m_rendererString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_RENDERER)));
m_driverVersionString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_VERSION)));
m_glMajorVersion = context.format().majorVersion();
m_glMinorVersion = context.format().minorVersion();
m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions);
m_isOpenGLES = context.isOpenGLES();
}
void KisOpenGLPrivate::appendOpenGLWarningString(KLocalizedString warning)
{
g_openglWarningStrings << warning;
}
void KisOpenGLPrivate::overrideOpenGLWarningString(QVector<KLocalizedString> warnings)
{
g_openglWarningStrings = warnings;
}
void KisOpenGL::initialize()
{
if (openGLCheckResult) return;
KIS_SAFE_ASSERT_RECOVER_NOOP(g_sanityDefaultFormatIsSet);
KisOpenGL::RendererConfig config;
config.format = QSurfaceFormat::defaultFormat();
openGLCheckResult =
KisOpenGLModeProber::instance()->probeFormat(config, false);
g_debugText.clear();
QDebug debugOut(&g_debugText);
debugOut << "OpenGL Info\n";
if (openGLCheckResult) {
debugOut << "\n Vendor: " << openGLCheckResult->vendorString();
debugOut << "\n Renderer: " << openGLCheckResult->rendererString();
debugOut << "\n Version: " << openGLCheckResult->driverVersionString();
debugOut << "\n Shading language: " << openGLCheckResult->shadingLanguageString();
debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat();
debugOut << "\n Current format: " << openGLCheckResult->format();
debugOut.nospace();
debugOut << "\n Version: " << openGLCheckResult->glMajorVersion() << "." << openGLCheckResult->glMinorVersion();
debugOut.resetFormat();
debugOut << "\n Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions();
debugOut << "\n is OpenGL ES:" << openGLCheckResult->isOpenGLES();
}
debugOut << "\n\nQPA OpenGL Detection Info";
debugOut << "\n supportsDesktopGL:" << bool(g_supportedRenderers & RendererDesktopGL);
#ifdef Q_OS_WIN
debugOut << "\n supportsAngleD3D11:" << bool(g_supportedRenderers & RendererOpenGLES);
debugOut << "\n isQtPreferAngle:" << bool(g_rendererPreferredByQt == RendererOpenGLES);
#else
debugOut << "\n supportsOpenGLES:" << bool(g_supportedRenderers & RendererOpenGLES);
debugOut << "\n isQtPreferOpenGLES:" << bool(g_rendererPreferredByQt == RendererOpenGLES);
#endif
// debugOut << "\n== log ==\n";
// debugOut.noquote();
// debugOut << g_surfaceFormatDetectionLog;
// debugOut.resetFormat();
// debugOut << "\n== end log ==";
dbgOpenGL.noquote().nospace() << g_debugText;
- KisUsageLogger::write(g_debugText);
+ KisUsageLogger::writeSysInfo(g_debugText);
if (!openGLCheckResult) {
return;
}
// Check if we have a bugged driver that needs fence workaround
bool isOnX11 = false;
#ifdef HAVE_X11
isOnX11 = true;
#endif
KisConfig cfg(true);
if ((isOnX11 && openGLCheckResult->rendererString().startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) {
g_needsFenceWorkaround = true;
}
/**
* NVidia + Qt's openGL don't play well together and one cannot
* draw a pixmap on a widget more than once in one rendering cycle.
*
* It can be workarounded by drawing strictly via QPixmapCache and
* only when the pixmap size in bigger than doubled size of the
* display framebuffer. That is for 8-bit HD display, you should have
* a cache bigger than 16 MiB. Don't ask me why. (DK)
*
* See bug: https://bugs.kde.org/show_bug.cgi?id=361709
*
* TODO: check if this workaround is still needed after merging
* Qt5+openGL3 branch.
*/
if (openGLCheckResult->vendorString().toUpper().contains("NVIDIA")) {
g_needsPixmapCacheWorkaround = true;
const QRect screenSize = QGuiApplication::primaryScreen()->availableGeometry();
const int minCacheSize = 20 * 1024;
const int cacheSize = 2048 + 2 * 4 * screenSize.width() * screenSize.height() / 1024; //KiB
QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize));
}
}
void KisOpenGL::initializeContext(QOpenGLContext *ctx)
{
KisConfig cfg(true);
initialize();
const bool isDebugEnabled = ctx->format().testOption(QSurfaceFormat::DebugContext);
dbgUI << "OpenGL: Opening new context";
if (isDebugEnabled) {
// Passing ctx for ownership management only, not specifying context.
// QOpenGLDebugLogger only function on the current active context.
// FIXME: Do we need to make sure ctx is the active context?
QOpenGLDebugLogger* openglLogger = new QOpenGLDebugLogger(ctx);
if (openglLogger->initialize()) {
qDebug() << "QOpenGLDebugLogger is initialized. Check whether you get a message below.";
QObject::connect(openglLogger, &QOpenGLDebugLogger::messageLogged, &openglOnMessageLogged);
openglLogger->startLogging(g_isDebugSynchronous ? QOpenGLDebugLogger::SynchronousLogging : QOpenGLDebugLogger::AsynchronousLogging);
openglLogger->logMessage(QOpenGLDebugMessage::createApplicationMessage(QStringLiteral("QOpenGLDebugLogger is logging.")));
} else {
qDebug() << "QOpenGLDebugLogger cannot be initialized.";
delete openglLogger;
}
}
// Double check we were given the version we requested
QSurfaceFormat format = ctx->format();
QOpenGLFunctions *f = ctx->functions();
f->initializeOpenGLFunctions();
QFile log(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/krita-opengl.txt");
log.open(QFile::WriteOnly);
QString vendor((const char*)f->glGetString(GL_VENDOR));
log.write(vendor.toLatin1());
log.write(", ");
log.write(openGLCheckResult->rendererString().toLatin1());
log.write(", ");
QString version((const char*)f->glGetString(GL_VERSION));
log.write(version.toLatin1());
log.close();
}
const QString &KisOpenGL::getDebugText()
{
initialize();
return g_debugText;
}
QStringList KisOpenGL::getOpenGLWarnings() {
QStringList strings;
Q_FOREACH (const KLocalizedString &item, g_openglWarningStrings) {
strings << item.toString();
}
return strings;
}
// XXX Temporary function to allow LoD on OpenGL3 without triggering
// all of the other 3.2 functionality, can be removed once we move to Qt5.7
bool KisOpenGL::supportsLoD()
{
initialize();
return openGLCheckResult && openGLCheckResult->supportsLoD();
}
bool KisOpenGL::hasOpenGL3()
{
initialize();
return openGLCheckResult && openGLCheckResult->hasOpenGL3();
}
bool KisOpenGL::hasOpenGLES()
{
initialize();
return openGLCheckResult && openGLCheckResult->isOpenGLES();
}
bool KisOpenGL::supportsFenceSync()
{
initialize();
return openGLCheckResult && openGLCheckResult->supportsFenceSync();
}
bool KisOpenGL::needsFenceWorkaround()
{
initialize();
return g_needsFenceWorkaround;
}
bool KisOpenGL::needsPixmapCacheWorkaround()
{
initialize();
return g_needsPixmapCacheWorkaround;
}
void KisOpenGL::testingInitializeDefaultSurfaceFormat()
{
setDefaultSurfaceConfig(selectSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false));
}
void KisOpenGL::setDebugSynchronous(bool value)
{
g_isDebugSynchronous = value;
}
KisOpenGL::OpenGLRenderer KisOpenGL::getCurrentOpenGLRenderer()
{
if (!openGLCheckResult) return RendererAuto;
return getRendererFromProbeResult(*openGLCheckResult);
}
KisOpenGL::OpenGLRenderer KisOpenGL::getQtPreferredOpenGLRenderer()
{
return g_rendererPreferredByQt;
}
KisOpenGL::OpenGLRenderers KisOpenGL::getSupportedOpenGLRenderers()
{
return g_supportedRenderers;
}
KisOpenGL::OpenGLRenderer KisOpenGL::getUserPreferredOpenGLRendererConfig()
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
return convertConfigToOpenGLRenderer(kritarc.value("OpenGLRenderer", "auto").toString());
}
void KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::OpenGLRenderer renderer)
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer));
}
QString KisOpenGL::convertOpenGLRendererToConfig(KisOpenGL::OpenGLRenderer renderer)
{
switch (renderer) {
case RendererNone:
return QStringLiteral("none");
case RendererSoftware:
return QStringLiteral("software");
case RendererDesktopGL:
return QStringLiteral("desktop");
case RendererOpenGLES:
return QStringLiteral("angle");
default:
return QStringLiteral("auto");
}
}
KisOpenGL::OpenGLRenderer KisOpenGL::convertConfigToOpenGLRenderer(QString renderer)
{
if (renderer == "desktop") {
return RendererDesktopGL;
} else if (renderer == "angle") {
return RendererOpenGLES;
} else if (renderer == "software") {
return RendererSoftware;
} else if (renderer == "none") {
return RendererNone;
} else {
return RendererAuto;
}
}
KisOpenGL::OpenGLRenderer KisOpenGL::RendererConfig::rendererId() const
{
KisOpenGL::OpenGLRenderer result = RendererAuto;
if (format.renderableType() == QSurfaceFormat::OpenGLES &&
angleRenderer == AngleRendererD3d11Warp) {
result = RendererSoftware;
} else if (format.renderableType() == QSurfaceFormat::OpenGLES &&
angleRenderer == AngleRendererD3d11) {
result = RendererOpenGLES;
} else if (format.renderableType() == QSurfaceFormat::OpenGL) {
result = RendererDesktopGL;
} else if (format.renderableType() == QSurfaceFormat::DefaultRenderableType &&
angleRenderer == AngleRendererD3d11) {
// noop
} else {
qWarning() << "WARNING: unsupported combination of OpenGL renderer" << ppVar(format.renderableType()) << ppVar(angleRenderer);
}
return result;
}
namespace {
typedef std::pair<QSurfaceFormat::RenderableType, KisOpenGL::AngleRenderer> RendererInfo;
RendererInfo getRendererInfo(KisOpenGL::OpenGLRenderer renderer)
{
RendererInfo info = {QSurfaceFormat::DefaultRenderableType,
KisOpenGL::AngleRendererD3d11};
switch (renderer) {
case KisOpenGL::RendererNone:
info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererDefault};
break;
case KisOpenGL::RendererAuto:
break;
case KisOpenGL::RendererDesktopGL:
info = {QSurfaceFormat::OpenGL, KisOpenGL::AngleRendererD3d11};
break;
case KisOpenGL::RendererOpenGLES:
info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11};
break;
case KisOpenGL::RendererSoftware:
info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11Warp};
break;
}
return info;
}
KisOpenGL::RendererConfig generateSurfaceConfig(KisOpenGL::OpenGLRenderer renderer,
KisConfig::RootSurfaceFormat rootSurfaceFormat,
bool debugContext)
{
RendererInfo info = getRendererInfo(renderer);
KisOpenGL::RendererConfig config;
config.angleRenderer = info.second;
QSurfaceFormat &format = config.format;
#ifdef Q_OS_MACOS
format.setVersion(3, 2);
format.setProfile(QSurfaceFormat::CoreProfile);
#elif !defined(Q_OS_ANDROID)
// XXX This can be removed once we move to Qt5.7
format.setVersion(3, 0);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setOptions(QSurfaceFormat::DeprecatedFunctions);
#endif
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
KisOpenGLModeProber::initSurfaceFormatFromConfig(rootSurfaceFormat, &format);
format.setRenderableType(info.first);
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
format.setSwapInterval(0); // Disable vertical refresh syncing
if (debugContext) {
format.setOption(QSurfaceFormat::DebugContext, true);
}
return config;
}
bool isOpenGLRendererBlacklisted(const QString &rendererString,
const QString &driverVersionString,
QVector<KLocalizedString> *warningMessage)
{
bool isBlacklisted = false;
#ifndef Q_OS_WIN
Q_UNUSED(rendererString);
Q_UNUSED(driverVersionString);
Q_UNUSED(warningMessage);
#else
// Special blacklisting of OpenGL/ANGLE is tracked on:
// https://phabricator.kde.org/T7411
// HACK: Specifically detect for Intel driver build number
// See https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html
if (rendererString.startsWith("Intel")) {
KLocalizedString knownBadIntelWarning = ki18n("The Intel graphics driver in use is known to have issues with OpenGL.");
KLocalizedString grossIntelWarning = ki18n(
"Intel graphics drivers tend to have issues with OpenGL so ANGLE will be used by default. "
"You may manually switch to OpenGL but it is not guaranteed to work properly."
);
QRegularExpression regex("\\b\\d{1,2}\\.\\d{2}\\.\\d{1,3}\\.(\\d{4})\\b");
QRegularExpressionMatch match = regex.match(driverVersionString);
if (match.hasMatch()) {
int driverBuild = match.captured(1).toInt();
if (driverBuild > 4636 && driverBuild < 4729) {
// Make ANGLE the preferred renderer for Intel driver versions
// between build 4636 and 4729 (exclusive) due to an UI offset bug.
// See https://communities.intel.com/thread/116003
// (Build 4636 is known to work from some test results)
qDebug() << "Detected Intel driver build between 4636 and 4729, making ANGLE the preferred renderer";
isBlacklisted = true;
*warningMessage << knownBadIntelWarning;
} else if (driverBuild == 4358) {
// There are several reports on a bug where the canvas is not being
// updated properly which has debug info pointing to this build.
qDebug() << "Detected Intel driver build 4358, making ANGLE the preferred renderer";
isBlacklisted = true;
*warningMessage << knownBadIntelWarning;
} else {
// Intel tends to randomly break OpenGL in some of their new driver
// builds, therefore we just shouldn't use OpenGL by default to
// reduce bug report noises.
qDebug() << "Detected Intel driver, making ANGLE the preferred renderer";
isBlacklisted = true;
*warningMessage << grossIntelWarning;
}
} else {
// In case Intel changed the driver version format to something that
// we don't understand, we still select ANGLE.
qDebug() << "Detected Intel driver with unknown version format, making ANGLE the preferred renderer";
isBlacklisted = true;
*warningMessage << grossIntelWarning;
}
}
#endif
return isBlacklisted;
}
boost::optional<bool> orderPreference(bool lhs, bool rhs)
{
if (lhs == rhs) return boost::none;
if (lhs && !rhs) return true;
if (!lhs && rhs) return false;
return false;
}
#define ORDER_BY(lhs, rhs) if (auto res = orderPreference((lhs), (rhs))) { return *res; }
class FormatPositionLess
{
public:
FormatPositionLess()
{
}
bool operator()(const KisOpenGL::RendererConfig &lhs, const KisOpenGL::RendererConfig &rhs) const {
KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredColorSpace != KisSurfaceColorSpace::DefaultColorSpace);
if (m_preferredRendererByUser != KisOpenGL::RendererSoftware) {
ORDER_BY(!isFallbackOnly(lhs.rendererId()), !isFallbackOnly(rhs.rendererId()));
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
ORDER_BY(isPreferredColorSpace(lhs.format.colorSpace()),
isPreferredColorSpace(rhs.format.colorSpace()));
#endif
if (doPreferHDR()) {
ORDER_BY(isHDRFormat(lhs.format), isHDRFormat(rhs.format));
} else {
ORDER_BY(!isHDRFormat(lhs.format), !isHDRFormat(rhs.format));
}
if (m_preferredRendererByUser != KisOpenGL::RendererAuto) {
ORDER_BY(lhs.rendererId() == m_preferredRendererByUser,
rhs.rendererId() == m_preferredRendererByUser);
}
ORDER_BY(!isBlacklisted(lhs.rendererId()), !isBlacklisted(rhs.rendererId()));
if (doPreferHDR() &&
m_preferredRendererByHDR != KisOpenGL::RendererAuto) {
ORDER_BY(lhs.rendererId() == m_preferredRendererByHDR,
rhs.rendererId() == m_preferredRendererByHDR);
}
KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredRendererByQt != KisOpenGL::RendererAuto);
ORDER_BY(lhs.rendererId() == m_preferredRendererByQt,
rhs.rendererId() == m_preferredRendererByQt);
return false;
}
public:
void setPreferredColorSpace(const KisSurfaceColorSpace &preferredColorSpace) {
m_preferredColorSpace = preferredColorSpace;
}
void setPreferredRendererByQt(const KisOpenGL::OpenGLRenderer &preferredRendererByQt) {
m_preferredRendererByQt = preferredRendererByQt;
}
void setPreferredRendererByUser(const KisOpenGL::OpenGLRenderer &preferredRendererByUser) {
m_preferredRendererByUser = preferredRendererByUser;
}
void setPreferredRendererByHDR(const KisOpenGL::OpenGLRenderer &preferredRendererByHDR) {
m_preferredRendererByHDR = preferredRendererByHDR;
}
void setOpenGLBlacklisted(bool openGLBlacklisted) {
m_openGLBlacklisted = openGLBlacklisted;
}
void setOpenGLESBlacklisted(bool openGLESBlacklisted) {
m_openGLESBlacklisted = openGLESBlacklisted;
}
bool isOpenGLBlacklisted() const {
return m_openGLBlacklisted;
}
bool isOpenGLESBlacklisted() const {
return m_openGLESBlacklisted;
}
KisSurfaceColorSpace preferredColorSpace() const {
return m_preferredColorSpace;
}
KisOpenGL::OpenGLRenderer preferredRendererByUser() const {
return m_preferredRendererByUser;
}
private:
bool isHDRFormat(const QSurfaceFormat &f) const {
#ifdef HAVE_HDR
return f.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace ||
f.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace;
#else
Q_UNUSED(f);
return false;
#endif
}
bool isFallbackOnly(KisOpenGL::OpenGLRenderer r) const {
return r == KisOpenGL::RendererSoftware;
}
bool isBlacklisted(KisOpenGL::OpenGLRenderer r) const {
KIS_SAFE_ASSERT_RECOVER_NOOP(r == KisOpenGL::RendererAuto ||
r == KisOpenGL::RendererDesktopGL ||
r == KisOpenGL::RendererOpenGLES ||
r == KisOpenGL::RendererSoftware ||
r == KisOpenGL::RendererNone);
return (r == KisOpenGL::RendererDesktopGL && m_openGLBlacklisted) ||
(r == KisOpenGL::RendererOpenGLES && m_openGLESBlacklisted) ||
(r == KisOpenGL::RendererSoftware && m_openGLESBlacklisted);
}
bool doPreferHDR() const {
#ifdef HAVE_HDR
return m_preferredColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace ||
m_preferredColorSpace == KisSurfaceColorSpace::scRGBColorSpace;
#else
return false;
#endif
}
bool isPreferredColorSpace(const KisSurfaceColorSpace cs) const {
return KisOpenGLModeProber::fuzzyCompareColorSpaces(m_preferredColorSpace, cs);
return false;
}
private:
KisSurfaceColorSpace m_preferredColorSpace = KisSurfaceColorSpace::DefaultColorSpace;
KisOpenGL::OpenGLRenderer m_preferredRendererByQt = KisOpenGL::RendererDesktopGL;
KisOpenGL::OpenGLRenderer m_preferredRendererByUser = KisOpenGL::RendererAuto;
KisOpenGL::OpenGLRenderer m_preferredRendererByHDR = KisOpenGL::RendererAuto;
bool m_openGLBlacklisted = false;
bool m_openGLESBlacklisted = false;
};
struct DetectionDebug : public QDebug
{
DetectionDebug(QString *string)
: QDebug(string),
m_string(string),
m_originalSize(string->size())
{}
~DetectionDebug() { dbgOpenGL << m_string->right(m_string->size() - m_originalSize); *this << endl; }
QString *m_string;
int m_originalSize;
};
}
#define dbgDetection() DetectionDebug(&g_surfaceFormatDetectionLog)
KisOpenGL::RendererConfig KisOpenGL::selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer,
KisConfig::RootSurfaceFormat preferredRootSurfaceFormat,
bool enableDebug)
{
QVector<KLocalizedString> warningMessages;
using Info = boost::optional<KisOpenGLModeProber::Result>;
QHash<OpenGLRenderer, Info> renderersToTest;
renderersToTest.insert(RendererDesktopGL, Info());
renderersToTest.insert(RendererOpenGLES, Info());
#ifdef Q_OS_WIN
renderersToTest.insert(RendererSoftware, Info());
#endif
#ifdef HAVE_HDR
QVector<KisConfig::RootSurfaceFormat> formatSymbols({KisConfig::BT709_G22, KisConfig::BT709_G10, KisConfig::BT2020_PQ});
#else
QVector<KisConfig::RootSurfaceFormat> formatSymbols({KisConfig::BT709_G22});
#endif
KisOpenGL::RendererConfig defaultConfig = generateSurfaceConfig(KisOpenGL::RendererAuto,
KisConfig::BT709_G22, false);
Info info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig);
#ifdef Q_OS_WIN
if (!info) {
// try software rasterizer (WARP)
defaultConfig = generateSurfaceConfig(KisOpenGL::RendererSoftware,
KisConfig::BT709_G22, false);
info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig);
if (!info) {
renderersToTest.remove(RendererSoftware);
}
}
#endif
if (!info) return KisOpenGL::RendererConfig();
const OpenGLRenderer defaultRenderer = getRendererFromProbeResult(*info);
OpenGLRenderers supportedRenderers = RendererNone;
FormatPositionLess compareOp;
compareOp.setPreferredRendererByQt(defaultRenderer);
#ifdef HAVE_HDR
compareOp.setPreferredColorSpace(
preferredRootSurfaceFormat == KisConfig::BT709_G22 ? KisSurfaceColorSpace::sRGBColorSpace :
preferredRootSurfaceFormat == KisConfig::BT709_G10 ? KisSurfaceColorSpace::scRGBColorSpace :
KisSurfaceColorSpace::bt2020PQColorSpace);
#else
Q_UNUSED(preferredRootSurfaceFormat);
compareOp.setPreferredColorSpace(KisSurfaceColorSpace::sRGBColorSpace);
#endif
#ifdef Q_OS_WIN
compareOp.setPreferredRendererByHDR(KisOpenGL::RendererOpenGLES);
#endif
compareOp.setPreferredRendererByUser(preferredRenderer);
compareOp.setOpenGLESBlacklisted(false); // We cannot blacklist ES drivers atm
renderersToTest[defaultRenderer] = info;
for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) {
Info info = it.value();
if (!info) {
info = KisOpenGLModeProber::instance()->
probeFormat(generateSurfaceConfig(it.key(),
KisConfig::BT709_G22, false));
*it = info;
}
compareOp.setOpenGLBlacklisted(
!info ||
isOpenGLRendererBlacklisted(info->rendererString(),
info->driverVersionString(),
&warningMessages));
if (info && info->isSupportedVersion()) {
supportedRenderers |= it.key();
}
}
OpenGLRenderer preferredByQt = defaultRenderer;
if (preferredByQt == RendererDesktopGL &&
supportedRenderers & RendererDesktopGL &&
compareOp.isOpenGLBlacklisted()) {
preferredByQt = RendererOpenGLES;
} else if (preferredByQt == RendererOpenGLES &&
supportedRenderers & RendererOpenGLES &&
compareOp.isOpenGLESBlacklisted()) {
preferredByQt = RendererDesktopGL;
}
QVector<RendererConfig> preferredConfigs;
for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) {
// if default mode of the renderer doesn't work, then custom won't either
if (!it.value()) continue;
Q_FOREACH (const KisConfig::RootSurfaceFormat formatSymbol, formatSymbols) {
preferredConfigs << generateSurfaceConfig(it.key(), formatSymbol, enableDebug);
}
}
std::stable_sort(preferredConfigs.begin(), preferredConfigs.end(), compareOp);
dbgDetection() << "Supported renderers:" << supportedRenderers;
dbgDetection() << "Surface format preference list:";
Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) {
dbgDetection() << "*" << config.format;
dbgDetection() << " " << config.rendererId();
}
KisOpenGL::RendererConfig resultConfig = defaultConfig;
if (preferredRenderer != RendererNone) {
Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
dbgDetection() <<"Probing format..." << config.format.colorSpace() << config.rendererId();
#else
dbgDetection() <<"Probing format..." << config.rendererId();
#endif
Info info = KisOpenGLModeProber::instance()->probeFormat(config);
if (info && info->isSupportedVersion()) {
#ifdef Q_OS_WIN
// HACK: Block ANGLE with Direct3D9
// Direct3D9 does not give OpenGL ES 3.0
// Some versions of ANGLE returns OpenGL version 3.0 incorrectly
if (info->isUsingAngle() &&
info->rendererString().contains("Direct3D9", Qt::CaseInsensitive)) {
dbgDetection() << "Skipping Direct3D 9 Angle implementation, it shouldn't have happened.";
continue;
}
#endif
dbgDetection() << "Found format:" << config.format;
dbgDetection() << " " << config.rendererId();
resultConfig = config;
break;
}
}
{
const bool colorSpaceIsCorrect =
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
KisOpenGLModeProber::fuzzyCompareColorSpaces(compareOp.preferredColorSpace(),
resultConfig.format.colorSpace());
#else
true;
#endif
const bool rendererIsCorrect =
compareOp.preferredRendererByUser() == KisOpenGL::RendererAuto ||
compareOp.preferredRendererByUser() == resultConfig.rendererId();
if (!rendererIsCorrect && colorSpaceIsCorrect) {
warningMessages << ki18n("Preferred renderer doesn't support requested surface format. Another renderer has been selected.");
} else if (!colorSpaceIsCorrect) {
warningMessages << ki18n("Preferred output format is not supported by available renderers");
}
}
} else {
resultConfig.format = QSurfaceFormat();
resultConfig.angleRenderer = AngleRendererDefault;
}
overrideSupportedRenderers(supportedRenderers, preferredByQt);
overrideOpenGLWarningString(warningMessages);
return resultConfig;
}
void KisOpenGL::setDefaultSurfaceConfig(const KisOpenGL::RendererConfig &config)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(!g_sanityDefaultFormatIsSet);
g_sanityDefaultFormatIsSet = true;
QSurfaceFormat::setDefaultFormat(config.format);
#ifdef Q_OS_WIN
// Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP
// might get weird crashes atm.
qputenv("QT_ANGLE_PLATFORM", KisOpenGLModeProber::angleRendererToString(config.angleRenderer).toLatin1());
#endif
if (config.format.renderableType() == QSurfaceFormat::OpenGLES) {
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true);
} else if (config.format.renderableType() == QSurfaceFormat::OpenGL) {
QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true);
}
}
bool KisOpenGL::hasOpenGL()
{
return openGLCheckResult->isSupportedVersion();
}
diff --git a/libs/ui/osx.mm b/libs/ui/osx.mm
index bf2bc26d10..ebf2206d44 100644
--- a/libs/ui/osx.mm
+++ b/libs/ui/osx.mm
@@ -1,32 +1,32 @@
/*
* Copyright (c) 2017 Bernhard Liebl
*
* 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.
*/
-#import <AppKit/Appkit.h>
+#import <AppKit/AppKit.h>
extern "C" {
bool isMouseCoalescingEnabled();
void setMouseCoalescingEnabled(bool enabled);
}
bool isMouseCoalescingEnabled() {
return NSEvent.isMouseCoalescingEnabled;
}
void setMouseCoalescingEnabled(bool enabled) {
NSEvent.mouseCoalescingEnabled = enabled;
}
diff --git a/libs/ui/qtsingleapplication/qtlocalpeer.cpp b/libs/ui/qtsingleapplication/qtlocalpeer.cpp
index 76761e28dd..d6eeae213c 100644
--- a/libs/ui/qtsingleapplication/qtlocalpeer.cpp
+++ b/libs/ui/qtsingleapplication/qtlocalpeer.cpp
@@ -1,180 +1,180 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** 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 Digia. For licensing terms and
-** conditions see http://www.qt.io/licensing. For further information
-** use the contact form at http://www.qt.io/contact-us.
+** conditions see https://www.qt.io/licensing. For further information
+** use the contact form at https://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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "qtlocalpeer.h"
#include <QCoreApplication>
#include <QDataStream>
#include <QTime>
#if defined(Q_OS_WIN)
#include <QLibrary>
#include <qt_windows.h>
typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
static PProcessIdToSessionId pProcessIdToSessionId = 0;
#endif
#if defined(Q_OS_UNIX)
#include <time.h>
#include <unistd.h>
#endif
static const char ack[] = "ack";
QString QtLocalPeer::appSessionId(const QString &appId)
{
QByteArray idc = appId.toUtf8();
quint16 idNum = qChecksum(idc.constData(), idc.size());
//### could do: two 16bit checksums over separate halves of id, for a 32bit result - improved uniqeness probability. Every-other-char split would be best.
QString res = QLatin1String("qtsingleapplication-")
+ QString::number(idNum, 16);
#if defined(Q_OS_WIN)
if (!pProcessIdToSessionId) {
QLibrary lib(QLatin1String("kernel32"));
pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
}
if (pProcessIdToSessionId) {
DWORD sessionId = 0;
pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
res += QLatin1Char('-') + QString::number(sessionId, 16);
}
#else
res += QLatin1Char('-') + QString::number(::getuid(), 16);
#endif
return res;
}
QtLocalPeer::QtLocalPeer(QObject *parent, const QString &appId)
: QObject(parent), id(appId)
{
if (id.isEmpty())
id = QCoreApplication::applicationFilePath(); //### On win, check if this returns .../argv[0] without casefolding; .\MYAPP == .\myapp on Win
socketName = appSessionId(id);
server = new QLocalServer(this);
QString lockName = QDir(QDir::tempPath()).absolutePath()
+ QLatin1Char('/') + socketName
+ QLatin1String("-lockfile");
lockFile.setFileName(lockName);
lockFile.open(QIODevice::ReadWrite);
}
bool QtLocalPeer::isClient()
{
if (lockFile.isLocked())
return false;
if (!lockFile.lock(QtLockedFile::WriteLock, false))
return true;
if (!QLocalServer::removeServer(socketName))
qWarning("QtSingleCoreApplication: could not cleanup socket");
bool res = server->listen(socketName);
if (!res)
qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
return false;
}
bool QtLocalPeer::sendMessage(const QByteArray &message, int timeout, bool block)
{
if (!isClient())
return false;
QLocalSocket socket;
bool connOk = false;
for (int i = 0; i < 2; i++) {
// Try twice, in case the other instance is just starting up
socket.connectToServer(socketName);
connOk = socket.waitForConnected(timeout/2);
if (connOk || i)
break;
int ms = 250;
#if defined(Q_OS_WIN)
Sleep(DWORD(ms));
#else
struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
nanosleep(&ts, 0);
#endif
}
if (!connOk)
return false;
QByteArray uMsg(message);
QDataStream ds(&socket);
ds.writeBytes(uMsg.constData(), uMsg.size());
bool res = socket.waitForBytesWritten(timeout);
res &= socket.waitForReadyRead(timeout); // wait for ack
res &= (socket.read(qstrlen(ack)) == ack);
if (block) // block until peer disconnects
socket.waitForDisconnected(-1);
return res;
}
void QtLocalPeer::receiveConnection()
{
QLocalSocket* socket = server->nextPendingConnection();
if (!socket)
return;
// Why doesn't Qt have a blocking stream that takes care of this shait???
while (socket->bytesAvailable() < static_cast<int>(sizeof(quint32))) {
if (!socket->isValid()) // stale request
return;
socket->waitForReadyRead(1000);
}
QDataStream ds(socket);
QByteArray uMsg;
quint32 remaining;
ds >> remaining;
uMsg.resize(remaining);
int got = 0;
char* uMsgBuf = uMsg.data();
//dbgKrita << "RCV: remaining" << remaining;
do {
got = ds.readRawData(uMsgBuf, remaining);
remaining -= got;
uMsgBuf += got;
//qDebug() << "RCV: got" << got << "remaining" << remaining;
} while (remaining && got >= 0 && socket->waitForReadyRead(2000));
//### error check: got<0
if (got < 0) {
qWarning() << "QtLocalPeer: Message reception failed" << socket->errorString();
delete socket;
return;
}
// ### async this
socket->write(ack, qstrlen(ack));
socket->waitForBytesWritten(1000);
emit messageReceived(uMsg, socket); // ##(might take a long time to return)
}
diff --git a/libs/ui/qtsingleapplication/qtlocalpeer.h b/libs/ui/qtsingleapplication/qtlocalpeer.h
index 5a48afc37e..d103781c96 100644
--- a/libs/ui/qtsingleapplication/qtlocalpeer.h
+++ b/libs/ui/qtsingleapplication/qtlocalpeer.h
@@ -1,62 +1,62 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** 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 Digia. For licensing terms and
-** conditions see http://www.qt.io/licensing. For further information
-** use the contact form at http://www.qt.io/contact-us.
+** conditions see https://www.qt.io/licensing. For further information
+** use the contact form at https://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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include <qtlockedfile.h>
#include <QLocalServer>
#include <QLocalSocket>
#include <QDir>
class QtLocalPeer : public QObject
{
Q_OBJECT
public:
explicit QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
bool isClient();
bool sendMessage(const QByteArray &message, int timeout, bool block);
QString applicationId() const
{ return id; }
static QString appSessionId(const QString &appId);
Q_SIGNALS:
void messageReceived(const QByteArray &message, QObject *socket);
protected Q_SLOTS:
void receiveConnection();
protected:
QString id;
QString socketName;
QLocalServer* server;
QtLockedFile lockFile;
};
diff --git a/libs/ui/qtsingleapplication/qtsingleapplication.cpp b/libs/ui/qtsingleapplication/qtsingleapplication.cpp
index 6345b807fb..c0be620928 100644
--- a/libs/ui/qtsingleapplication/qtsingleapplication.cpp
+++ b/libs/ui/qtsingleapplication/qtsingleapplication.cpp
@@ -1,192 +1,192 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** 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 Digia. For licensing terms and
-** conditions see http://www.qt.io/licensing. For further information
-** use the contact form at http://www.qt.io/contact-us.
+** conditions see https://www.qt.io/licensing. For further information
+** use the contact form at https://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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "qtsingleapplication.h"
#include "qtlocalpeer.h"
#include <qtlockedfile.h>
#include <QDir>
#include <QFileOpenEvent>
#include <QSharedMemory>
#include <QWidget>
static const int instancesSize = 1024;
static QString instancesLockFilename(const QString &appSessionId)
{
const QChar slash(QLatin1Char('/'));
QString res = QDir::tempPath();
if (!res.endsWith(slash))
res += slash;
return res + appSessionId + QLatin1String("-instances");
}
QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
: QApplication(argc, argv),
firstPeer(-1),
pidPeer(0)
{
this->appId = appId;
const QString appSessionId = QtLocalPeer::appSessionId(appId);
// This shared memory holds a zero-terminated array of active (or crashed) instances
instances = new QSharedMemory(appSessionId, this);
actWin = 0;
block = false;
// First instance creates the shared memory, later instances attach to it
const bool created = instances->create(instancesSize);
if (!created) {
if (!instances->attach()) {
qWarning() << "Failed to initialize instances shared memory: "
<< instances->errorString();
delete instances;
instances = 0;
return;
}
}
// QtLockedFile is used to workaround QTBUG-10364
QtLockedFile lockfile(instancesLockFilename(appSessionId));
lockfile.open(QtLockedFile::ReadWrite);
lockfile.lock(QtLockedFile::WriteLock);
qint64 *pids = static_cast<qint64 *>(instances->data());
if (!created) {
// Find the first instance that it still running
// The whole list needs to be iterated in order to append to it
for (; *pids; ++pids) {
if (firstPeer == -1 && isRunning(*pids))
firstPeer = *pids;
}
}
// Add current pid to list and terminate it
*pids++ = QCoreApplication::applicationPid();
*pids = 0;
pidPeer = new QtLocalPeer(this, appId + QLatin1Char('-') +
QString::number(QCoreApplication::applicationPid()));
connect(pidPeer, SIGNAL(messageReceived(QByteArray,QObject*)), SIGNAL(messageReceived(QByteArray,QObject*)));
pidPeer->isClient();
lockfile.unlock();
}
QtSingleApplication::~QtSingleApplication()
{
if (!instances)
return;
const qint64 appPid = QCoreApplication::applicationPid();
QtLockedFile lockfile(instancesLockFilename(QtLocalPeer::appSessionId(appId)));
lockfile.open(QtLockedFile::ReadWrite);
lockfile.lock(QtLockedFile::WriteLock);
// Rewrite array, removing current pid and previously crashed ones
qint64 *pids = static_cast<qint64 *>(instances->data());
qint64 *newpids = pids;
for (; *pids; ++pids) {
if (*pids != appPid && isRunning(*pids))
*newpids++ = *pids;
}
*newpids = 0;
lockfile.unlock();
}
bool QtSingleApplication::event(QEvent *event)
{
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent *foe = static_cast<QFileOpenEvent*>(event);
emit fileOpenRequest(foe->file());
return true;
}
return QApplication::event(event);
}
bool QtSingleApplication::isRunning(qint64 pid)
{
if (pid == -1) {
pid = firstPeer;
if (pid == -1) {
return false;
}
}
QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10));
return peer.isClient();
}
bool QtSingleApplication::sendMessage(const QByteArray &message, int timeout, qint64 pid)
{
if (pid == -1) {
pid = firstPeer;
if (pid == -1)
return false;
}
QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10));
return peer.sendMessage(message, timeout, block);
}
QString QtSingleApplication::applicationId() const
{
return appId;
}
void QtSingleApplication::setBlock(bool value)
{
block = value;
}
void QtSingleApplication::setActivationWindow(QWidget *aw, bool activateOnMessage)
{
actWin = aw;
if (!pidPeer)
return;
if (activateOnMessage)
connect(pidPeer, SIGNAL(messageReceived(QByteArray,QObject*)), this, SLOT(activateWindow()));
else
disconnect(pidPeer, SIGNAL(messageReceived(QByteArray,QObject*)), this, SLOT(activateWindow()));
}
QWidget* QtSingleApplication::activationWindow() const
{
return actWin;
}
void QtSingleApplication::activateWindow()
{
if (actWin) {
actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
actWin->raise();
actWin->activateWindow();
}
}
diff --git a/libs/ui/qtsingleapplication/qtsingleapplication.h b/libs/ui/qtsingleapplication/qtsingleapplication.h
index 54261424f0..6fb39e15d0 100644
--- a/libs/ui/qtsingleapplication/qtsingleapplication.h
+++ b/libs/ui/qtsingleapplication/qtsingleapplication.h
@@ -1,79 +1,79 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** 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 Digia. For licensing terms and
-** conditions see http://www.qt.io/licensing. For further information
-** use the contact form at http://www.qt.io/contact-us.
+** conditions see https://www.qt.io/licensing. For further information
+** use the contact form at https://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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef QTSINGLEAPPLICATION
#define QTSINGLEAPPLICATION
#include <QApplication>
QT_FORWARD_DECLARE_CLASS(QSharedMemory)
class QtLocalPeer;
#include <kritaui_export.h>
class KRITAUI_EXPORT QtSingleApplication : public QApplication
{
Q_OBJECT
public:
QtSingleApplication(const QString &id, int &argc, char **argv);
~QtSingleApplication() override;
bool isRunning(qint64 pid = -1);
void setActivationWindow(QWidget* aw, bool activateOnMessage = true);
QWidget* activationWindow() const;
bool event(QEvent *event) override;
QString applicationId() const;
void setBlock(bool value);
public Q_SLOTS:
bool sendMessage(const QByteArray &message, int timeout = 5000, qint64 pid = -1);
void activateWindow();
Q_SIGNALS:
void messageReceived(const QByteArray &message, QObject *socket);
void fileOpenRequest(const QString &file);
private:
QString instancesFileName(const QString &appId);
qint64 firstPeer;
QSharedMemory *instances;
QtLocalPeer *pidPeer;
QWidget *actWin;
QString appId;
bool block;
};
#endif
diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt
index 99d892a829..e3da0f545c 100644
--- a/libs/ui/tests/CMakeLists.txt
+++ b/libs/ui/tests/CMakeLists.txt
@@ -1,183 +1,170 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
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
kis_shape_selection_test.cpp
kis_doc2_test.cpp
kis_coordinates_converter_test.cpp
kis_grid_config_test.cpp
kis_stabilized_events_sampler_test.cpp
kis_brush_hud_properties_config_test.cpp
kis_shape_commands_test.cpp
- kis_shape_layer_test.cpp
kis_stop_gradient_editor_test.cpp
kis_file_layer_test.cpp
kis_multinode_property_test.cpp
KisFrameSerializerTest.cpp
KisFrameCacheStoreTest.cpp
kis_animation_exporter_test.cpp
kis_prescaled_projection_test.cpp
kis_asl_layer_style_serializer_test.cpp
kis_animation_importer_test.cpp
KisSpinBoxSplineUnitConverterTest.cpp
KisDocumentReplaceTest.cpp
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-"
)
ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME KisSelectionDecorationTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisNodeDummiesGraphTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisNodeShapesGraphTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisModelIndexConverterTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp
TEST_NAME KisCategorizedListModelTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisNodeJugglerCompressedTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test(
kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisInputManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
ecm_add_test(
kis_node_model_test.cpp modeltest.cpp
TEST_NAME kis_node_model_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
##### Tests that currently fail and should be fixed #####
include(KritaAddBrokenUnitTest)
-
-krita_add_broken_unit_test(
- kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp
+krita_add_broken_unit_test( kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp
TEST_NAME kis_shape_controller_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- kis_exiv2_test.cpp
+krita_add_broken_unit_test( kis_exiv2_test.cpp
TEST_NAME KisExiv2Test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- kis_clipboard_test.cpp
+krita_add_broken_unit_test( kis_clipboard_test.cpp
TEST_NAME KisClipboardTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
+krita_add_broken_unit_test( freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME FreehandStrokeTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
+krita_add_broken_unit_test( FreehandStrokeBenchmark.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME FreehandStrokeBenchmark
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
+krita_add_broken_unit_test( KisPaintOnTransparencyMaskTest.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME KisPaintOnTransparencyMaskTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-ecm_add_test(
- fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
+krita_add_broken_unit_test( fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME
FillProcessingVisitorTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
+krita_add_broken_unit_test( filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME FilterStrokeTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- kis_selection_manager_test.cpp
+krita_add_broken_unit_test( kis_selection_manager_test.cpp
TEST_NAME KisSelectionManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
#set_tests_properties(libs-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300)
-krita_add_broken_unit_test(
- kis_node_manager_test.cpp
+krita_add_broken_unit_test( kis_node_manager_test.cpp
TEST_NAME KisNodeManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp
+krita_add_broken_unit_test( kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisDummiesFacadeTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp
+krita_add_broken_unit_test( kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME KisZoomAndPanTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
#set_tests_properties(libs-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300)
-krita_add_broken_unit_test(
- kis_action_manager_test.cpp
+krita_add_broken_unit_test( kis_action_manager_test.cpp
TEST_NAME KisActionManagerTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- kis_categories_mapper_test.cpp testing_categories_mapper.cpp
+krita_add_broken_unit_test( kis_categories_mapper_test.cpp testing_categories_mapper.cpp
TEST_NAME KisCategoriesMapperTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- kis_animation_frame_cache_test.cpp
+krita_add_broken_unit_test( kis_animation_frame_cache_test.cpp
TEST_NAME kis_animation_frame_cache_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
-krita_add_broken_unit_test(
- kis_derived_resources_test.cpp
+krita_add_broken_unit_test( kis_derived_resources_test.cpp
TEST_NAME kis_derived_resources_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "libs-ui-")
+
+krita_add_broken_unit_test( kis_shape_layer_test.cpp
+ TEST_NAME KisShapeLayerTest
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "libs-ui-")
diff --git a/libs/ui/thememanager.cpp b/libs/ui/thememanager.cpp
index 52c7995f18..d4013c2916 100644
--- a/libs/ui/thememanager.cpp
+++ b/libs/ui/thememanager.cpp
@@ -1,312 +1,312 @@
/* ============================================================
*
* This file is a part of digiKam project
- * http://www.digikam.org
+ * https://www.digikam.org
*
* Date : 2004-08-02
* Description : theme manager
*
* Copyright (C) 2006-2011 by Gilles Caulier <caulier dot gilles at gmail dot 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, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT 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 "thememanager.h"
// Qt includes
#include <QStringList>
#include <QFileInfo>
#include <QFile>
#include <QApplication>
#include <QPalette>
#include <QColor>
#include <QActionGroup>
#include <QBitmap>
#include <QPainter>
#include <QPixmap>
#include <QDate>
#include <QDesktopWidget>
#include <QMenu>
#include <QMenuBar>
#include <QStatusBar>
#include <QDebug>
#include <QStandardPaths>
#include <QAction>
#include <QMessageBox>
// KDE includes
#include <klocalizedstring.h>
#include <kcolorscheme.h>
#include <kactioncollection.h>
#include <KoResourcePaths.h>
#include <kactionmenu.h>
#include <kconfig.h>
#include <kconfiggroup.h>
// Calligra
#include <kis_icon.h>
#ifdef __APPLE__
#include <QStyle>
#endif
namespace Digikam
{
// ---------------------------------------------------------------
class ThemeManager::ThemeManagerPriv
{
public:
ThemeManagerPriv()
: themeMenuActionGroup(0)
, themeMenuAction(0)
{
}
QString currentThemeName;
QMap<QString, QString> themeMap; // map<theme name, theme config path>
QActionGroup* themeMenuActionGroup;
KActionMenu* themeMenuAction;
};
ThemeManager::ThemeManager(const QString &theme, QObject *parent)
: QObject(parent)
, d(new ThemeManagerPriv)
{
//qDebug() << "Creating theme manager with theme" << theme;
d->currentThemeName = theme;
populateThemeMap();
}
ThemeManager::~ThemeManager()
{
delete d;
}
QString ThemeManager::currentThemeName() const
{
//qDebug() << "getting current themename";
QString themeName;
if (d->themeMenuAction && d->themeMenuActionGroup) {
QAction* action = d->themeMenuActionGroup->checkedAction();
if (action) {
themeName = action->text().remove('&');
}
//qDebug() << "\tthemename from action" << themeName;
}
else if (!d->currentThemeName.isEmpty()) {
//qDebug() << "\tcurrent themename" << d->currentThemeName;
themeName = d->currentThemeName;
}
else {
//qDebug() << "\tfallback";
themeName = "Krita dark";
}
//qDebug() << "\tresult" << themeName;
return themeName;
}
void ThemeManager::setCurrentTheme(const QString& name)
{
//qDebug() << "setCurrentTheme();" << d->currentThemeName << "to" << name;
d->currentThemeName = name;
if (d->themeMenuAction && d->themeMenuActionGroup) {
QList<QAction*> list = d->themeMenuActionGroup->actions();
Q_FOREACH (QAction* action, list) {
if (action->text().remove('&') == name) {
action->setChecked(true);
}
}
}
slotChangePalette();
}
void ThemeManager::slotChangePalette()
{
//qDebug() << "slotChangePalette" << sender();
QString theme(currentThemeName());
QString filename = d->themeMap.value(theme);
KSharedConfigPtr config = KSharedConfig::openConfig(filename);
QPalette palette = qApp->palette();
QPalette::ColorGroup states[3] = { QPalette::Active, QPalette::Inactive, QPalette::Disabled };
// TT thinks tooltips shouldn't use active, so we use our active colors for all states
KColorScheme schemeTooltip(QPalette::Active, KColorScheme::Tooltip, config);
for ( int i = 0; i < 3 ; ++i ) {
QPalette::ColorGroup state = states[i];
KColorScheme schemeView(state, KColorScheme::View, config);
KColorScheme schemeWindow(state, KColorScheme::Window, config);
KColorScheme schemeButton(state, KColorScheme::Button, config);
KColorScheme schemeSelection(state, KColorScheme::Selection, config);
palette.setBrush(state, QPalette::WindowText, schemeWindow.foreground());
palette.setBrush(state, QPalette::Window, schemeWindow.background());
palette.setBrush(state, QPalette::Base, schemeView.background());
palette.setBrush(state, QPalette::Text, schemeView.foreground());
palette.setBrush(state, QPalette::Button, schemeButton.background());
palette.setBrush(state, QPalette::ButtonText, schemeButton.foreground());
palette.setBrush(state, QPalette::Highlight, schemeSelection.background());
palette.setBrush(state, QPalette::HighlightedText, schemeSelection.foreground());
palette.setBrush(state, QPalette::ToolTipBase, schemeTooltip.background());
palette.setBrush(state, QPalette::ToolTipText, schemeTooltip.foreground());
palette.setColor(state, QPalette::Light, schemeWindow.shade(KColorScheme::LightShade));
palette.setColor(state, QPalette::Midlight, schemeWindow.shade(KColorScheme::MidlightShade));
palette.setColor(state, QPalette::Mid, schemeWindow.shade(KColorScheme::MidShade));
palette.setColor(state, QPalette::Dark, schemeWindow.shade(KColorScheme::DarkShade));
palette.setColor(state, QPalette::Shadow, schemeWindow.shade(KColorScheme::ShadowShade));
palette.setBrush(state, QPalette::AlternateBase, schemeView.background(KColorScheme::AlternateBackground));
palette.setBrush(state, QPalette::Link, schemeView.foreground(KColorScheme::LinkText));
palette.setBrush(state, QPalette::LinkVisited, schemeView.foreground(KColorScheme::VisitedText));
}
//qDebug() << ">>>>>>>>>>>>>>>>>> going to set palette on app" << theme;
// hint for the style to synchronize the color scheme with the window manager/compositor
qApp->setProperty("KDE_COLOR_SCHEME_PATH", filename);
qApp->setPalette(palette);
#ifdef Q_OS_MACOS
if (theme == "Krita bright" || theme.isEmpty()) {
qApp->setStyle("Macintosh");
qApp->style()->polish(qApp);
} else {
qApp->setStyle("Fusion");
qApp->style()->polish(qApp);
}
#endif
KisIconUtils::clearIconCache();
emit signalThemeChanged();
}
void ThemeManager::setThemeMenuAction(KActionMenu* const action)
{
d->themeMenuAction = action;
populateThemeMenu();
}
void ThemeManager::registerThemeActions(KActionCollection *actionCollection)
{
if (!d->themeMenuAction) return;
actionCollection->addAction("theme_menu", d->themeMenuAction);
}
void ThemeManager::populateThemeMenu()
{
if (!d->themeMenuAction) return;
d->themeMenuAction->menu()->clear();
delete d->themeMenuActionGroup;
d->themeMenuActionGroup = new QActionGroup(d->themeMenuAction);
connect(d->themeMenuActionGroup, SIGNAL(triggered(QAction*)),
this, SLOT(slotChangePalette()));
QAction * action;
const QStringList schemeFiles = KoResourcePaths::findAllResources("data", "color-schemes/*.colors");
QMap<QString, QAction*> actionMap;
for (int i = 0; i < schemeFiles.size(); ++i) {
const QString filename = schemeFiles.at(i);
const QFileInfo info(filename);
KSharedConfigPtr config = KSharedConfig::openConfig(filename);
QIcon icon = createSchemePreviewIcon(config);
KConfigGroup group(config, "General");
const QString name = group.readEntry("Name", info.completeBaseName());
action = new QAction(name, d->themeMenuActionGroup);
action->setIcon(icon);
action->setCheckable(true);
actionMap.insert(name, action);
}
// sort the list
QStringList actionMapKeys = actionMap.keys();
actionMapKeys.sort();
Q_FOREACH (const QString& name, actionMapKeys) {
if ( name == currentThemeName()) {
actionMap.value(name)->setChecked(true);
}
d->themeMenuAction->addAction(actionMap.value(name));
}
}
QPixmap ThemeManager::createSchemePreviewIcon(const KSharedConfigPtr& config)
{
// code taken from kdebase/workspace/kcontrol/colors/colorscm.cpp
const uchar bits1[] = { 0xff, 0xff, 0xff, 0x2c, 0x16, 0x0b };
const uchar bits2[] = { 0x68, 0x34, 0x1a, 0xff, 0xff, 0xff };
const QSize bitsSize(24, 2);
const QBitmap b1 = QBitmap::fromData(bitsSize, bits1);
const QBitmap b2 = QBitmap::fromData(bitsSize, bits2);
QPixmap pixmap(23, 16);
pixmap.fill(Qt::black); // FIXME use some color other than black for borders?
KConfigGroup group(config, "WM");
QPainter p(&pixmap);
KColorScheme windowScheme(QPalette::Active, KColorScheme::Window, config);
p.fillRect(1, 1, 7, 7, windowScheme.background());
p.fillRect(2, 2, 5, 2, QBrush(windowScheme.foreground().color(), b1));
KColorScheme buttonScheme(QPalette::Active, KColorScheme::Button, config);
p.fillRect(8, 1, 7, 7, buttonScheme.background());
p.fillRect(9, 2, 5, 2, QBrush(buttonScheme.foreground().color(), b1));
p.fillRect(15, 1, 7, 7, group.readEntry("activeBackground", QColor(96, 148, 207)));
p.fillRect(16, 2, 5, 2, QBrush(group.readEntry("activeForeground", QColor(255, 255, 255)), b1));
KColorScheme viewScheme(QPalette::Active, KColorScheme::View, config);
p.fillRect(1, 8, 7, 7, viewScheme.background());
p.fillRect(2, 12, 5, 2, QBrush(viewScheme.foreground().color(), b2));
KColorScheme selectionScheme(QPalette::Active, KColorScheme::Selection, config);
p.fillRect(8, 8, 7, 7, selectionScheme.background());
p.fillRect(9, 12, 5, 2, QBrush(selectionScheme.foreground().color(), b2));
p.fillRect(15, 8, 7, 7, group.readEntry("inactiveBackground", QColor(224, 223, 222)));
p.fillRect(16, 12, 5, 2, QBrush(group.readEntry("inactiveForeground", QColor(20, 19, 18)), b2));
p.end();
return pixmap;
}
void ThemeManager::populateThemeMap()
{
const QStringList schemeFiles = KoResourcePaths::findAllResources("data", "color-schemes/*.colors");
for (int i = 0; i < schemeFiles.size(); ++i) {
const QString filename = schemeFiles.at(i);
const QFileInfo info(filename);
KSharedConfigPtr config = KSharedConfig::openConfig(filename);
KConfigGroup group(config, "General");
const QString name = group.readEntry("Name", info.completeBaseName());
d->themeMap.insert(name, filename);
}
}
} // namespace Digikam
diff --git a/libs/ui/thememanager.h b/libs/ui/thememanager.h
index 0e2dc818e8..338c518822 100644
--- a/libs/ui/thememanager.h
+++ b/libs/ui/thememanager.h
@@ -1,85 +1,85 @@
/* ============================================================
*
* This file is a part of digiKam project
- * http://www.digikam.org
+ * https://www.digikam.org
*
* Date : 2004-08-02
* Description : theme manager
*
* Copyright (C) 2006-2011 by Gilles Caulier <caulier dot gilles at gmail dot 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, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef THEMEMANAGER_H
#define THEMEMANAGER_H
// Qt includes
#include <QObject>
#include <QPixmap>
#include <QString>
// KDE includes
#include <ksharedconfig.h>
class KActionCollection;
class KActionMenu;
namespace Digikam
{
class ThemeManager : public QObject
{
Q_OBJECT
public:
/**
* @brief ThemeManager
* @param theme the currently active theme: the palette will not be changed to this theme
* @param parent
*/
explicit ThemeManager(const QString &theme = "", QObject *parent = 0);
~ThemeManager() override;
QString currentThemeName() const;
void setCurrentTheme(const QString& name);
void setThemeMenuAction(KActionMenu* const action);
void registerThemeActions(KActionCollection *actionCollection);
Q_SIGNALS:
void signalThemeChanged();
private Q_SLOTS:
void slotChangePalette();
private:
void populateThemeMap();
void populateThemeMenu();
QPixmap createSchemePreviewIcon(const KSharedConfigPtr& config);
private:
class ThemeManagerPriv;
ThemeManagerPriv* const d;
};
} // namespace Digikam
#endif /* THEMEMANAGER_H */
diff --git a/libs/ui/tool/kis_selection_tool_helper.cpp b/libs/ui/tool/kis_selection_tool_helper.cpp
index 5cac907762..6804772cf0 100644
--- a/libs/ui/tool/kis_selection_tool_helper.cpp
+++ b/libs/ui/tool/kis_selection_tool_helper.cpp
@@ -1,370 +1,370 @@
/*
* 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_selection_tool_helper.h"
#include <kundo2command.h>
#include <kactioncollection.h>
#include <KoShapeController.h>
#include <KoPathShape.h>
#include "kis_pixel_selection.h"
#include "kis_shape_selection.h"
#include "kis_image.h"
#include "canvas/kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_selection_manager.h"
#include "kis_transaction.h"
#include "commands/kis_selection_commands.h"
#include "kis_shape_controller.h"
#include <kis_icon.h>
#include "kis_processing_applicator.h"
#include "commands_new/kis_transaction_based_command.h"
#include "kis_gui_context_command.h"
#include "kis_command_utils.h"
#include "commands/kis_deselect_global_selection_command.h"
#include "kis_algebra_2d.h"
#include "kis_config.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include <QMenu>
KisSelectionToolHelper::KisSelectionToolHelper(KisCanvas2* canvas, const KUndo2MagicString& name)
: m_canvas(canvas)
, m_name(name)
{
m_image = m_canvas->viewManager()->image();
}
KisSelectionToolHelper::~KisSelectionToolHelper()
{
}
struct LazyInitGlobalSelection : public KisTransactionBasedCommand {
LazyInitGlobalSelection(KisView *view) : m_view(view) {}
KisView *m_view;
KUndo2Command* paint() override {
return !m_view->selection() ?
new KisSetEmptyGlobalSelectionCommand(m_view->image()) : 0;
}
};
void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection, SelectionAction action)
{
KisView* view = m_canvas->imageView();
if (selection->selectedExactRect().isEmpty()) {
m_canvas->viewManager()->selectionManager()->deselect();
return;
}
KisProcessingApplicator applicator(view->image(),
0 /* we need no automatic updates */,
KisProcessingApplicator::SUPPORTS_WRAPAROUND_MODE,
KisImageSignalVector() << ModifiedSignal,
m_name);
applicator.applyCommand(new LazyInitGlobalSelection(view));
struct ApplyToPixelSelection : public KisTransactionBasedCommand {
ApplyToPixelSelection(KisView *view,
KisPixelSelectionSP selection,
SelectionAction action) : m_view(view),
m_selection(selection),
m_action(action) {}
KisView *m_view;
KisPixelSelectionSP m_selection;
SelectionAction m_action;
KUndo2Command* paint() override {
KisSelectionSP selection = m_view->selection();
KIS_SAFE_ASSERT_RECOVER(selection) { return 0; }
KisPixelSelectionSP pixelSelection = selection->pixelSelection();
KIS_SAFE_ASSERT_RECOVER(pixelSelection) { return 0; }
bool hasSelection = !pixelSelection->isEmpty();
KisSelectionTransaction transaction(pixelSelection);
if (!hasSelection && m_action == SELECTION_SYMMETRICDIFFERENCE) {
m_action = SELECTION_REPLACE;
}
if (!hasSelection && m_action == SELECTION_SUBTRACT) {
pixelSelection->invert();
}
pixelSelection->applySelection(m_selection, m_action);
QRect dirtyRect = m_view->image()->bounds();
if (hasSelection &&
m_action != SELECTION_REPLACE &&
m_action != SELECTION_INTERSECT &&
m_action != SELECTION_SYMMETRICDIFFERENCE) {
dirtyRect = m_selection->selectedRect();
}
m_view->selection()->updateProjection(dirtyRect);
KUndo2Command *savedCommand = transaction.endAndTake();
pixelSelection->setDirty(dirtyRect);
if (m_view->selection()->selectedExactRect().isEmpty()) {
KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand();
cmd->addCommand(savedCommand);
cmd->addCommand(new KisDeselectGlobalSelectionCommand(m_view->image()));
savedCommand = cmd;
}
return savedCommand;
}
};
applicator.applyCommand(new ApplyToPixelSelection(view, selection, action));
applicator.end();
}
void KisSelectionToolHelper::addSelectionShape(KoShape* shape, SelectionAction action)
{
QList<KoShape*> shapes;
shapes.append(shape);
addSelectionShapes(shapes, action);
}
void KisSelectionToolHelper::addSelectionShapes(QList< KoShape* > shapes, SelectionAction action)
{
KisView *view = m_canvas->imageView();
if (view->image()->wrapAroundModePermitted()) {
view->showFloatingMessage(
i18n("Shape selection does not fully "
"support wraparound mode. Please "
"use pixel selection instead"),
KisIconUtils::loadIcon("selection-info"));
}
KisProcessingApplicator applicator(view->image(),
0 /* we need no automatic updates */,
KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
m_name);
applicator.applyCommand(new LazyInitGlobalSelection(view));
struct ClearPixelSelection : public KisTransactionBasedCommand {
ClearPixelSelection(KisView *view) : m_view(view) {}
KisView *m_view;
KUndo2Command* paint() override {
KisPixelSelectionSP pixelSelection = m_view->selection()->pixelSelection();
KIS_ASSERT_RECOVER(pixelSelection) { return 0; }
KisSelectionTransaction transaction(pixelSelection);
pixelSelection->clear();
return transaction.endAndTake();
}
};
if (action == SELECTION_REPLACE || action == SELECTION_DEFAULT) {
applicator.applyCommand(new ClearPixelSelection(view));
}
struct AddSelectionShape : public KisTransactionBasedCommand {
AddSelectionShape(KisView *view, KoShape* shape, SelectionAction action)
: m_view(view),
m_shape(shape),
m_action(action) {}
KisView *m_view;
KoShape* m_shape;
SelectionAction m_action;
KUndo2Command* paint() override {
KUndo2Command *resultCommand = 0;
KisSelectionSP selection = m_view->selection();
if (selection) {
KisShapeSelection * shapeSelection = static_cast<KisShapeSelection*>(selection->shapeSelection());
if (shapeSelection) {
QList<KoShape*> existingShapes = shapeSelection->shapes();
if (existingShapes.size() == 1) {
KoShape *currentShape = existingShapes.first();
- QPainterPath path1 = currentShape->absoluteTransformation(0).map(currentShape->outline());
- QPainterPath path2 = m_shape->absoluteTransformation(0).map(m_shape->outline());
+ QPainterPath path1 = currentShape->absoluteTransformation().map(currentShape->outline());
+ QPainterPath path2 = m_shape->absoluteTransformation().map(m_shape->outline());
QPainterPath path = path2;
switch (m_action) {
case SELECTION_DEFAULT:
case SELECTION_REPLACE:
path = path2;
break;
case SELECTION_INTERSECT:
path = path1 & path2;
break;
case SELECTION_ADD:
path = path1 | path2;
break;
case SELECTION_SUBTRACT:
path = path1 - path2;
break;
case SELECTION_SYMMETRICDIFFERENCE:
path = (path1 | path2) - (path1 & path2);
break;
}
KoShape *newShape = KoPathShape::createShapeFromPainterPath(path);
newShape->setUserData(new KisShapeSelectionMarker);
KUndo2Command *parentCommand = new KUndo2Command();
m_view->canvasBase()->shapeController()->removeShape(currentShape, parentCommand);
m_view->canvasBase()->shapeController()->addShape(newShape, 0, parentCommand);
resultCommand = parentCommand;
}
}
}
if (!resultCommand) {
/**
* Mark a shape that it belongs to a shape selection
*/
if(!m_shape->userData()) {
m_shape->setUserData(new KisShapeSelectionMarker);
}
resultCommand = m_view->canvasBase()->shapeController()->addShape(m_shape, 0);
}
return resultCommand;
}
};
Q_FOREACH (KoShape* shape, shapes) {
applicator.applyCommand(
new KisGuiContextCommand(new AddSelectionShape(view, shape, action), view));
}
applicator.end();
}
bool KisSelectionToolHelper::canShortcutToDeselect(const QRect &rect, SelectionAction action)
{
return rect.isEmpty() && (action == SELECTION_INTERSECT || action == SELECTION_REPLACE);
}
bool KisSelectionToolHelper::canShortcutToNoop(const QRect &rect, SelectionAction action)
{
return rect.isEmpty() && action == SELECTION_ADD;
}
bool KisSelectionToolHelper::tryDeselectCurrentSelection(const QRectF selectionViewRect, SelectionAction action)
{
bool result = false;
if (KisAlgebra2D::maxDimension(selectionViewRect) < KisConfig(true).selectionViewSizeMinimum() &&
(action == SELECTION_INTERSECT || action == SELECTION_SYMMETRICDIFFERENCE || action == SELECTION_REPLACE)) {
// Queueing this action to ensure we avoid a race condition when unlocking the node system
QTimer::singleShot(0, m_canvas->viewManager()->selectionManager(), SLOT(deselect()));
result = true;
}
return result;
}
QMenu* KisSelectionToolHelper::getSelectionContextMenu(KisCanvas2* canvas)
{
QMenu *m_contextMenu = new QMenu();
KActionCollection *actionCollection = canvas->viewManager()->actionCollection();
m_contextMenu->addSection(i18n("Selection Actions"));
m_contextMenu->addSeparator();
m_contextMenu->addAction(actionCollection->action("deselect"));
m_contextMenu->addAction(actionCollection->action("invert"));
m_contextMenu->addAction(actionCollection->action("select_all"));
m_contextMenu->addSeparator();
m_contextMenu->addAction(actionCollection->action("cut_selection_to_new_layer"));
m_contextMenu->addAction(actionCollection->action("copy_selection_to_new_layer"));
m_contextMenu->addSeparator();
KisSelectionSP selection = canvas->viewManager()->selection();
if (selection && canvas->viewManager()->selectionEditable()) {
m_contextMenu->addAction(actionCollection->action("edit_selection"));
if (!selection->hasShapeSelection()) {
m_contextMenu->addAction(actionCollection->action("convert_to_vector_selection"));
} else {
m_contextMenu->addAction(actionCollection->action("convert_to_raster_selection"));
}
QMenu *transformMenu = m_contextMenu->addMenu(i18n("Transform"));
transformMenu->addAction(actionCollection->action("KisToolTransform"));
transformMenu->addAction(actionCollection->action("selectionscale"));
transformMenu->addAction(actionCollection->action("growselection"));
transformMenu->addAction(actionCollection->action("shrinkselection"));
transformMenu->addAction(actionCollection->action("borderselection"));
transformMenu->addAction(actionCollection->action("smoothselection"));
transformMenu->addAction(actionCollection->action("featherselection"));
transformMenu->addAction(actionCollection->action("stroke_selection"));
m_contextMenu->addSeparator();
}
m_contextMenu->addAction(actionCollection->action("resizeimagetoselection"));
m_contextMenu->addSeparator();
m_contextMenu->addAction(actionCollection->action("toggle_display_selection"));
m_contextMenu->addAction(actionCollection->action("show-global-selection-mask"));
return m_contextMenu;
}
SelectionMode KisSelectionToolHelper::tryOverrideSelectionMode(KisSelectionSP activeSelection, SelectionMode currentMode, SelectionAction currentAction) const
{
if (currentAction != SELECTION_DEFAULT && currentAction != SELECTION_REPLACE) {
if (activeSelection) {
currentMode = activeSelection->hasShapeSelection() ? SHAPE_PROTECTION : PIXEL_SELECTION;
}
}
return currentMode;
}
diff --git a/libs/ui/tool/kis_tool_freehand_helper.cpp b/libs/ui/tool/kis_tool_freehand_helper.cpp
index d4665eeb04..fe2692c67e 100644
--- a/libs/ui/tool/kis_tool_freehand_helper.cpp
+++ b/libs/ui/tool/kis_tool_freehand_helper.cpp
@@ -1,964 +1,964 @@
/*
* 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 <KoCanvasResourceProvider.h>
#include "kis_algebra_2d.h"
#include "kis_distance_information.h"
#include "kis_painting_information_builder.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 "kis_random_source.h"
#include "KisPerStrokeRandomSource.h"
#include "strokes/freehand_stroke.h"
#include "strokes/KisFreehandStrokeInfo.h"
#include "KisAsyncronousStrokeUpdateHelper.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;
KisStrokesFacade *strokesFacade;
KisAsyncronousStrokeUpdateHelper asyncUpdateHelper;
KUndo2MagicString transactionText;
bool haveTangent;
QPointF previousTangent;
bool hasPaintAtLeastOnce;
QTime strokeTime;
QTimer strokeTimeoutTimer;
QVector<KisFreehandStrokeInfo*> strokeInfos;
KisResourcesSnapshotSP resources;
KisStrokeId strokeId;
KisPaintInformation previousPaintInformation;
KisPaintInformation olderPaintInformation;
KisSmoothingOptionsSP smoothingOptions;
// fake random sources for hovering outline *only*
KisRandomSourceSP fakeDabRandomSource;
KisPerStrokeRandomSourceSP fakeStrokeRandomSource;
// 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
bool usingStabilizer;
QQueue<KisPaintInformation> stabilizerDeque;
QTimer stabilizerPollTimer;
KisStabilizedEventsSampler stabilizedSampler;
KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper;
qreal effectiveSmoothnessDistance() const;
};
KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder,
const KUndo2MagicString &transactionText,
KisSmoothingOptions *smoothingOptions)
: m_d(new Private())
{
m_d->infoBuilder = infoBuilder;
m_d->transactionText = transactionText;
m_d->smoothingOptions = KisSmoothingOptionsSP(
smoothingOptions ? smoothingOptions : new KisSmoothingOptions());
m_d->fakeDabRandomSource = new KisRandomSource();
m_d->fakeStrokeRandomSource = new KisPerStrokeRandomSource();
m_d->strokeTimeoutTimer.setSingleShot(true);
connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke()));
connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing()));
connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint()));
connect(m_d->smoothingOptions.data(), SIGNAL(sigSmoothingTypeChanged()), SLOT(slotSmoothingTypeChanged()));
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);
KisDistanceInformation distanceInfo(prevPoint, startAngle);
if (!m_d->strokeInfos.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->strokeInfos.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, startAngle);
} else if (m_d->strokeInfos.first()->dragDistance->isStarted()) {
distanceInfo = *m_d->strokeInfos.first()->dragDistance;
}
}
KisPaintInformation::DistanceInformationRegistrar registrar =
info.registerDistanceInformation(&distanceInfo);
info.setRandomSource(m_d->fakeDabRandomSource);
info.setPerStrokeRandomSource(m_d->fakeStrokeRandomSource);
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,
KoCanvasResourceProvider *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,
KoCanvasResourceProvider *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(),
startAngle,
useSpacingUpdates ? SPACING_UPDATE_INTERVAL : LONG_TIME,
airbrushing ? TIMING_UPDATE_INTERVAL : LONG_TIME,
0);
KisDistanceInformation startDist = startDistInfo.makeDistInfo();
createPainters(m_d->strokeInfos,
startDist);
KisStrokeStrategy *stroke =
new FreehandStrokeStrategy(m_d->resources, m_d->strokeInfos, m_d->transactionText);
m_d->strokeId = m_d->strokesFacade->startStroke(stroke);
m_d->history.clear();
m_d->distanceHistory.clear();
if (airbrushing) {
m_d->airbrushingTimer.setInterval(computeAirbrushTimerInterval());
m_d->airbrushingTimer.start();
} else if (m_d->resources->presetNeedsAsynchronousUpdates()) {
m_d->asyncUpdateHelper.startUpdateStream(m_d->strokesFacade, m_d->strokeId);
}
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 << "WARNING: there is no intersection point "
// << "in the basic smoothing algorithms";
}
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());
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
+ * https://w.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 controlling 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->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
stabilizerEnd();
}
if (m_d->asyncUpdateHelper.isActive()) {
m_d->asyncUpdateHelper.endUpdateStream();
}
/**
* 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->strokeInfos.clear();
m_d->strokesFacade->endStroke(m_d->strokeId);
m_d->strokeId.clear();
}
void KisToolFreehandHelper::cancelPaint()
{
if (!m_d->strokeId) return;
m_d->strokeTimeoutTimer.stop();
if (m_d->airbrushingTimer.isActive()) {
m_d->airbrushingTimer.stop();
}
if (m_d->asyncUpdateHelper.isActive()) {
m_d->asyncUpdateHelper.cancelUpdateStream();
}
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->strokeInfos.clear();
m_d->strokesFacade->cancelStroke(m_d->strokeId);
m_d->strokeId.clear();
}
int KisToolFreehandHelper::elapsedStrokeTime() const
{
return m_d->strokeTime.elapsed();
}
void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo)
{
m_d->usingStabilizer = true;
// 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(true);
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();
}
m_d->usingStabilizer = false;
}
void KisToolFreehandHelper::slotSmoothingTypeChanged()
{
if (!isRunning()) {
return;
}
KisSmoothingOptions::SmoothingType currentSmoothingType =
m_d->smoothingOptions->smoothingType();
if (m_d->usingStabilizer
&& (currentSmoothingType != KisSmoothingOptions::STABILIZER)) {
stabilizerEnd();
} else if (!m_d->usingStabilizer
&& (currentSmoothingType == KisSmoothingOptions::STABILIZER)) {
stabilizerStart(m_d->previousPaintInformation);
}
}
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->strokeInfos.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);
}
}
int KisToolFreehandHelper::computeAirbrushTimerInterval() const
{
qreal realInterval = m_d->resources->airbrushingInterval() * AIRBRUSH_INTERVAL_FACTOR;
return qMax(1, qFloor(realInterval));
}
void KisToolFreehandHelper::paintAt(int strokeInfoId,
const KisPaintInformation &pi)
{
m_d->hasPaintAtLeastOnce = true;
m_d->strokesFacade->addJob(m_d->strokeId,
new FreehandStrokeStrategy::Data(strokeInfoId, pi));
}
void KisToolFreehandHelper::paintLine(int strokeInfoId,
const KisPaintInformation &pi1,
const KisPaintInformation &pi2)
{
m_d->hasPaintAtLeastOnce = true;
m_d->strokesFacade->addJob(m_d->strokeId,
new FreehandStrokeStrategy::Data(strokeInfoId, pi1, pi2));
}
void KisToolFreehandHelper::paintBezierCurve(int strokeInfoId,
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(strokeInfoId,
pi1, control1, control2, pi2));
}
void KisToolFreehandHelper::createPainters(QVector<KisFreehandStrokeInfo*> &strokeInfos,
const KisDistanceInformation &startDist)
{
strokeInfos << new KisFreehandStrokeInfo(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);
}
diff --git a/libs/ui/utils/KisClipboardUtil.cpp b/libs/ui/utils/KisClipboardUtil.cpp
index 49bc209a08..9f1039cc14 100644
--- a/libs/ui/utils/KisClipboardUtil.cpp
+++ b/libs/ui/utils/KisClipboardUtil.cpp
@@ -1,76 +1,76 @@
/*
* Copyright (c) 2019 Dmitrii Utkin <loentar@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 "KisClipboardUtil.h"
#include <QApplication>
#include <QClipboard>
#include <QMimeData>
#include <QImage>
#include <QList>
#include <QSet>
#include <QPair>
namespace KisClipboardUtil {
struct ClipboardImageFormat
{
QSet<QString> mimeTypes;
QString format;
};
QImage getImageFromClipboard()
{
static const QList<ClipboardImageFormat> supportedFormats = {
{{"image/png"}, "PNG"},
{{"image/tiff"}, "TIFF"},
{{"image/bmp", "image/x-bmp", "image/x-MS-bmp", "image/x-win-bitmap"}, "BMP"},
{{"image/jpeg"}, "JPG"}
};
QClipboard *clipboard = QApplication::clipboard();
QImage image;
const QSet<QString> &clipboardMimeTypes = clipboard->mimeData()->formats().toSet();
Q_FOREACH (const ClipboardImageFormat &item, supportedFormats) {
const QSet<QString> &intersection = item.mimeTypes & clipboardMimeTypes;
if (intersection.isEmpty()) {
continue;
}
const QString &format = *intersection.constBegin();
const QByteArray &imageData = clipboard->mimeData()->data(format);
if (imageData.isEmpty()) {
continue;
}
if (image.loadFromData(imageData, item.format.toLatin1())) {
break;
}
}
if (image.isNull()) {
image = clipboard->image();
}
return image;
}
-}
\ No newline at end of file
+}
diff --git a/libs/ui/widgets/KoDualColorButton.cpp b/libs/ui/widgets/KoDualColorButton.cpp
index 8158fae70b..78b785faf5 100644
--- a/libs/ui/widgets/KoDualColorButton.cpp
+++ b/libs/ui/widgets/KoDualColorButton.cpp
@@ -1,402 +1,402 @@
/* This file is part of the KDE libraries
Copyright (C) 1999 Daniel M. Duley <mosfet@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 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 "KoDualColorButton.h"
#include "KoColor.h"
#include "KoColorDisplayRendererInterface.h"
#include <kcolormimedata.h>
#include "dcolorarrow.xbm"
#include "dcolorreset.xpm"
#include <QColorDialog>
#include "KisDlgInternalColorSelector.h"
#include "kis_signals_blocker.h"
#include <QBrush>
#include <QDrag>
#include <QDragEnterEvent>
#include <QPainter>
#include <qdrawutil.h>
#include <QApplication>
class Q_DECL_HIDDEN KoDualColorButton::Private
{
public:
Private(const KoColor &fgColor, const KoColor &bgColor,
QWidget *_dialogParent,
const KoColorDisplayRendererInterface *_displayRenderer)
: dialogParent(_dialogParent)
, dragFlag( false )
, miniCtlFlag( false )
, foregroundColor(fgColor)
, backgroundColor(bgColor)
, displayRenderer(_displayRenderer)
{
updateArrows();
resetPixmap = QPixmap( (const char **)dcolorreset_xpm );
popDialog = true;
}
void updateArrows() {
arrowBitmap = QPixmap(12,12);
arrowBitmap.fill(Qt::transparent);
QPainter p(&arrowBitmap);
- p.setPen(dialogParent->palette().window().color());
+ p.setPen(dialogParent->palette().windowText().color());
// arrow pointing left
p.drawLine(0, 3, 7, 3);
p.drawLine(1, 2, 1, 4);
p.drawLine(2, 1, 2, 5);
p.drawLine(3, 0, 3, 6);
// arrow pointing down
p.drawLine(8, 4, 8, 11);
p.drawLine(5, 8, 11, 8);
p.drawLine(6, 9, 10, 9);
p.drawLine(7, 10, 9, 10);
}
QWidget* dialogParent;
QPixmap arrowBitmap;
QPixmap resetPixmap;
bool dragFlag, miniCtlFlag;
KoColor foregroundColor;
KoColor backgroundColor;
KisDlgInternalColorSelector *colorSelectorDialog;
QPoint dragPosition;
Selection tmpSelection;
bool popDialog;
const KoColorDisplayRendererInterface *displayRenderer;
void init(KoDualColorButton *q);
};
void KoDualColorButton::Private::init(KoDualColorButton *q)
{
if ( q->sizeHint().isValid() )
q->setMinimumSize( q->sizeHint() );
q->setAcceptDrops( true );
QString caption = i18n("Select a Color");
KisDlgInternalColorSelector::Config config = KisDlgInternalColorSelector::Config();
config.modal = false;
colorSelectorDialog = new KisDlgInternalColorSelector(q, foregroundColor, config, caption, displayRenderer);
connect(colorSelectorDialog, SIGNAL(signalForegroundColorChosen(KoColor)), q, SLOT(slotSetForeGroundColorFromDialog(KoColor)));
connect(q, SIGNAL(foregroundColorChanged(KoColor)), colorSelectorDialog, SLOT(slotColorUpdated(KoColor)));
}
KoDualColorButton::KoDualColorButton(const KoColor &foregroundColor, const KoColor &backgroundColor, QWidget *parent, QWidget* dialogParent )
: QWidget( parent ),
d( new Private(foregroundColor, backgroundColor,
dialogParent,
KoDumbColorDisplayRenderer::instance()) )
{
d->init(this);
}
KoDualColorButton::KoDualColorButton(const KoColor &foregroundColor, const KoColor &backgroundColor,
const KoColorDisplayRendererInterface *displayRenderer,
QWidget *parent, QWidget* dialogParent)
: QWidget( parent ),
d( new Private(foregroundColor, backgroundColor,
dialogParent,
displayRenderer) )
{
d->init(this);
}
KoDualColorButton::~KoDualColorButton()
{
delete d;
}
KoColor KoDualColorButton::foregroundColor() const
{
return d->foregroundColor;
}
KoColor KoDualColorButton::backgroundColor() const
{
return d->backgroundColor;
}
bool KoDualColorButton::popDialog() const
{
return d->popDialog;
}
QSize KoDualColorButton::sizeHint() const
{
return QSize( 34, 34 );
}
void KoDualColorButton::setForegroundColor(const KoColor &color)
{
d->foregroundColor = color;
{
/**
* The internal color selector might emit the color of a different profile, so
* we should break this cycling dependency somehow.
*/
KisSignalsBlocker b(d->colorSelectorDialog);
d->colorSelectorDialog->slotColorUpdated(color);
}
repaint();
}
void KoDualColorButton::setBackgroundColor( const KoColor &color )
{
d->backgroundColor = color;
repaint();
}
void KoDualColorButton::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer)
{
if (displayRenderer) {
d->displayRenderer = displayRenderer;
d->colorSelectorDialog->setDisplayRenderer(displayRenderer);
connect(d->displayRenderer, SIGNAL(destroyed()), this, SLOT(setDisplayRenderer()), Qt::UniqueConnection);
} else {
d->displayRenderer = KoDumbColorDisplayRenderer::instance();
}
}
void KoDualColorButton::setColorSpace(const KoColorSpace *cs)
{
d->colorSelectorDialog->lockUsedColorSpace(cs);
}
QColor KoDualColorButton::getColorFromDisplayRenderer(KoColor c)
{
QColor col;
if (d->displayRenderer) {
c.convertTo(d->displayRenderer->getPaintingColorSpace());
col = d->displayRenderer->toQColor(c);
} else {
col = c.toQColor();
}
return col;
}
void KoDualColorButton::setPopDialog( bool popDialog )
{
d->popDialog = popDialog;
}
void KoDualColorButton::metrics( QRect &foregroundRect, QRect &backgroundRect )
{
foregroundRect = QRect( 0, 0, width() - 14, height() - 14 );
backgroundRect = QRect( 14, 14, width() - 14, height() - 14 );
}
void KoDualColorButton::paintEvent(QPaintEvent *)
{
QRect foregroundRect;
QRect backgroundRect;
QPainter painter( this );
metrics( foregroundRect, backgroundRect );
QBrush defBrush = palette().brush( QPalette::Button );
QBrush foregroundBrush( getColorFromDisplayRenderer(d->foregroundColor), Qt::SolidPattern );
QBrush backgroundBrush( getColorFromDisplayRenderer(d->backgroundColor), Qt::SolidPattern );
qDrawShadeRect( &painter, backgroundRect, palette(), false, 1, 0,
isEnabled() ? &backgroundBrush : &defBrush );
qDrawShadeRect( &painter, foregroundRect, palette(), false, 1, 0,
isEnabled() ? &foregroundBrush : &defBrush );
painter.setPen( palette().color( QPalette::Shadow ) );
painter.drawPixmap( foregroundRect.right() + 2, 1, d->arrowBitmap );
painter.drawPixmap( 1, foregroundRect.bottom() + 2, d->resetPixmap );
}
void KoDualColorButton::dragEnterEvent( QDragEnterEvent *event )
{
event->setAccepted( isEnabled() && KColorMimeData::canDecode( event->mimeData() ) );
}
void KoDualColorButton::dropEvent( QDropEvent *event )
{
Q_UNUSED(event);
/* QColor color = KColorMimeData::fromMimeData( event->mimeData() );
if ( color.isValid() ) {
if ( d->selection == Foreground ) {
d->foregroundColor = color;
emit foregroundColorChanged( color );
} else {
d->backgroundColor = color;
emit backgroundColorChanged( color );
}
repaint();
}
*/
}
void KoDualColorButton::slotSetForeGroundColorFromDialog(const KoColor color)
{
d->foregroundColor = color;
repaint();
emit foregroundColorChanged(d->foregroundColor);
}
void KoDualColorButton::mousePressEvent( QMouseEvent *event )
{
QRect foregroundRect;
QRect backgroundRect;
metrics( foregroundRect, backgroundRect );
d->dragPosition = event->pos();
d->dragFlag = false;
if ( foregroundRect.contains( d->dragPosition ) ) {
d->tmpSelection = Foreground;
d->miniCtlFlag = false;
}
else if( backgroundRect.contains( d->dragPosition ) ) {
d->tmpSelection = Background;
d->miniCtlFlag = false;
}
else if ( event->pos().x() > foregroundRect.width() ) {
// We handle the swap and reset controls as soon as the mouse is
// is pressed and ignore further events on this click (mosfet).
KoColor tmp = d->foregroundColor;
d->foregroundColor = d->backgroundColor;
d->backgroundColor = tmp;
emit backgroundColorChanged( d->backgroundColor );
emit foregroundColorChanged( d->foregroundColor );
d->miniCtlFlag = true;
}
else if ( event->pos().x() < backgroundRect.x() ) {
d->foregroundColor = d->displayRenderer->approximateFromRenderedQColor(Qt::black);
d->backgroundColor = d->displayRenderer->approximateFromRenderedQColor(Qt::white);
emit backgroundColorChanged( d->backgroundColor );
emit foregroundColorChanged( d->foregroundColor );
d->miniCtlFlag = true;
}
repaint();
}
void KoDualColorButton::mouseMoveEvent( QMouseEvent *event )
{
if ( !d->miniCtlFlag ) {
int delay = QApplication::startDragDistance();
if ( event->x() >= d->dragPosition.x() + delay || event->x() <= d->dragPosition.x() - delay ||
event->y() >= d->dragPosition.y() + delay || event->y() <= d->dragPosition.y() - delay ) {
KColorMimeData::createDrag( d->tmpSelection == Foreground ?
getColorFromDisplayRenderer(d->foregroundColor) :
getColorFromDisplayRenderer(d->backgroundColor),
this )->exec();
d->dragFlag = true;
}
}
}
void KoDualColorButton::mouseReleaseEvent( QMouseEvent *event )
{
d->dragFlag = false;
if ( d->miniCtlFlag )
return;
d->miniCtlFlag = false;
QRect foregroundRect;
QRect backgroundRect;
metrics( foregroundRect, backgroundRect );
if (foregroundRect.contains( event->pos())) {
if (d->tmpSelection == Foreground) {
if (d->popDialog) {
#ifndef Q_OS_MACOS
d->colorSelectorDialog->setPreviousColor(d->foregroundColor);
// this should toggle, but I don't know how to implement that...
d->colorSelectorDialog->show();
#else
QColor c = d->foregroundColor.toQColor();
c = QColorDialog::getColor(c, this);
if (c.isValid()) {
d->foregroundColor = d->displayRenderer->approximateFromRenderedQColor(c);
emit foregroundColorChanged(d->foregroundColor);
}
#endif
}
}
else {
d->foregroundColor = d->backgroundColor;
emit foregroundColorChanged( d->foregroundColor );
}
}
else if ( backgroundRect.contains( event->pos() )) {
if(d->tmpSelection == Background ) {
if( d->popDialog) {
#ifndef Q_OS_MACOS
KoColor c = d->backgroundColor;
c = KisDlgInternalColorSelector::getModalColorDialog(c, this, d->colorSelectorDialog->windowTitle());
d->backgroundColor = c;
emit backgroundColorChanged(d->backgroundColor);
#else
QColor c = d->backgroundColor.toQColor();
c = QColorDialog::getColor(c, this);
if (c.isValid()) {
d->backgroundColor = d->displayRenderer->approximateFromRenderedQColor(c);
emit backgroundColorChanged(d->backgroundColor);
}
#endif
}
} else {
d->backgroundColor = d->foregroundColor;
emit backgroundColorChanged( d->backgroundColor );
}
}
repaint();
}
void KoDualColorButton::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);
switch (event->type()) {
case QEvent::StyleChange:
case QEvent::PaletteChange:
d->updateArrows();
default:
break;
}
}
diff --git a/libs/ui/widgets/KoFillConfigWidget.cpp b/libs/ui/widgets/KoFillConfigWidget.cpp
index 603a748854..09be5326f2 100644
--- a/libs/ui/widgets/KoFillConfigWidget.cpp
+++ b/libs/ui/widgets/KoFillConfigWidget.cpp
@@ -1,926 +1,923 @@
/* This file is part of the KDE project
* Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr)
* Copyright (C) 2012 Jean-Nicolas Artaud <jeannicolasartaud@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 "KoFillConfigWidget.h"
#include <QToolButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QButtonGroup>
#include <QLabel>
#include <QSizePolicy>
#include <QBitmap>
#include <QAction>
#include <QSharedPointer>
#include <klocalizedstring.h>
#include <KoIcon.h>
#include <KoColor.h>
#include <KoColorPopupAction.h>
#include "KoResourceServerProvider.h"
#include "KoResourceServerAdapter.h"
#include <KoSelection.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceProvider.h>
#include <KoDocumentResourceManager.h>
#include <KoShape.h>
#include <KoShapeController.h>
#include <KoShapeBackground.h>
#include <KoShapeBackgroundCommand.h>
#include <KoShapeStrokeCommand.h>
#include <KoShapeStroke.h>
#include <KoSelectedShapesProxy.h>
#include <KoColorBackground.h>
#include <KoGradientBackground.h>
#include <KoPatternBackground.h>
#include <KoImageCollection.h>
#include <KoResourcePopupAction.h>
#include "KoZoomHandler.h"
#include "KoColorPopupButton.h"
#include "ui_KoFillConfigWidget.h"
#include <kis_signals_blocker.h>
#include <kis_signal_compressor.h>
#include <kis_acyclic_signal_connector.h>
#include <kis_assert.h>
#include "kis_canvas_resource_provider.h"
#include <KoStopGradient.h>
#include <QInputDialog>
#include <KoShapeFillWrapper.h>
#include "kis_global.h"
#include "kis_debug.h"
static const char* const buttonnone[]={
"16 16 3 1",
"# c #000000",
"e c #ff0000",
"- c #ffffff",
"################",
"#--------------#",
"#-e----------e-#",
"#--e--------e--#",
"#---e------e---#",
"#----e----e----#",
"#-----e--e-----#",
"#------ee------#",
"#------ee------#",
"#-----e--e-----#",
"#----e----e----#",
"#---e------e---#",
"#--e--------e--#",
"#-e----------e-#",
"#--------------#",
"################"};
static const char* const buttonsolid[]={
"16 16 2 1",
"# c #000000",
". c #969696",
"################",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"#..............#",
"################"};
// FIXME: Smoother gradient button.
static const char* const buttongradient[]={
"16 16 15 1",
"# c #000000",
"n c #101010",
"m c #202020",
"l c #303030",
"k c #404040",
"j c #505050",
"i c #606060",
"h c #707070",
"g c #808080",
"f c #909090",
"e c #a0a0a0",
"d c #b0b0b0",
"c c #c0c0c0",
"b c #d0d0d0",
"a c #e0e0e0",
"################",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"#abcdefghijklmn#",
"################"};
static const char* const buttonpattern[]={
"16 16 4 1",
". c #0a0a0a",
"# c #333333",
"a c #a0a0a0",
"b c #ffffffff",
"################",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#bbbbbaaaabbbbb#",
"#bbbbbaaaabbbbb#",
"#bbbbbaaaabbbbb#",
"#bbbbbaaaabbbbb#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"#aaaaabbbbaaaaa#",
"################"};
class Q_DECL_HIDDEN KoFillConfigWidget::Private
{
public:
Private(KoFlake::FillVariant _fillVariant)
: canvas(0),
colorChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE),
gradientChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE),
shapeChangedCompressor(200,KisSignalCompressor::FIRST_ACTIVE),
fillVariant(_fillVariant),
noSelectionTrackingMode(false)
{
}
KoColorPopupAction *colorAction;
KoResourcePopupAction *gradientAction;
KoResourcePopupAction *patternAction;
QButtonGroup *group;
KoCanvasBase *canvas;
KisSignalCompressor colorChangedCompressor;
KisAcyclicSignalConnector shapeChangedAcyclicConnector;
KisAcyclicSignalConnector resourceManagerAcyclicConnector;
KoFillConfigWidget::StyleButton selectedFillIndex {KoFillConfigWidget::None};
QSharedPointer<KoStopGradient> activeGradient;
KisSignalCompressor gradientChangedCompressor;
KisSignalCompressor shapeChangedCompressor;
KoFlake::FillVariant fillVariant;
bool noSelectionTrackingMode;
Ui_KoFillConfigWidget *ui;
std::vector<KisAcyclicSignalConnector::Blocker> deactivationLocks;
boost::optional<KoColor> overriddenColorFromProvider;
};
KoFillConfigWidget::KoFillConfigWidget(KoCanvasBase *canvas, KoFlake::FillVariant fillVariant, bool trackShapeSelection, QWidget *parent)
: QWidget(parent)
, d(new Private(fillVariant))
{
d->canvas = canvas;
if (trackShapeSelection) {
d->shapeChangedAcyclicConnector.connectBackwardVoid(
d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()),
&d->shapeChangedCompressor, SLOT(start()));
d->shapeChangedAcyclicConnector.connectBackwardVoid(
d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
&d->shapeChangedCompressor, SLOT(start()));
connect(&d->shapeChangedCompressor, SIGNAL(timeout()), this, SLOT(shapeChanged()));
}
d->resourceManagerAcyclicConnector.connectBackwardResourcePair(
d->canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(slotCanvasResourceChanged(int,QVariant)));
d->resourceManagerAcyclicConnector.connectForwardVoid(
this, SIGNAL(sigInternalRequestColorToResourceManager()),
this, SLOT(slotProposeCurrentColorToResourceManager()));
KisAcyclicSignalConnector *resetConnector = d->resourceManagerAcyclicConnector.createCoordinatedConnector();
resetConnector->connectForwardVoid(
this, SIGNAL(sigInternalRecoverColorInResourceManager()),
this, SLOT(slotRecoverColorInResourceManager()));
// configure GUI
d->ui = new Ui_KoFillConfigWidget();
d->ui->setupUi(this);
d->group = new QButtonGroup(this);
d->group->setExclusive(true);
d->ui->btnNoFill->setIcon(QPixmap((const char **) buttonnone));
d->group->addButton(d->ui->btnNoFill, None);
d->ui->btnSolidFill->setIcon(QPixmap((const char **) buttonsolid));
d->group->addButton(d->ui->btnSolidFill, Solid);
d->ui->btnGradientFill->setIcon(QPixmap((const char **) buttongradient));
d->group->addButton(d->ui->btnGradientFill, Gradient);
d->ui->btnPatternFill->setIcon(QPixmap((const char **) buttonpattern));
d->group->addButton(d->ui->btnPatternFill, Pattern);
d->ui->btnPatternFill->setVisible(false);
d->colorAction = new KoColorPopupAction(d->ui->btnChooseSolidColor);
d->colorAction->setToolTip(i18n("Change the filling color"));
d->colorAction->setCurrentColor(Qt::white);
d->ui->btnChooseSolidColor->setDefaultAction(d->colorAction);
d->ui->btnChooseSolidColor->setPopupMode(QToolButton::InstantPopup);
d->ui->btnSolidColorPick->setIcon(KisIconUtils::loadIcon("krita_tool_color_picker"));
// TODO: for now the color picking button is disabled!
d->ui->btnSolidColorPick->setEnabled(false);
d->ui->btnSolidColorPick->setVisible(false);
connect(d->colorAction, SIGNAL(colorChanged(KoColor)), &d->colorChangedCompressor, SLOT(start()));
connect(&d->colorChangedCompressor, SIGNAL(timeout()), SLOT(colorChanged()));
connect(d->ui->btnChooseSolidColor, SIGNAL(iconSizeChanged()), d->colorAction, SLOT(updateIcon()));
connect(d->group, SIGNAL(buttonClicked(int)), SLOT(styleButtonPressed(int)));
connect(d->group, SIGNAL(buttonClicked(int)), SLOT(slotUpdateFillTitle()));
slotUpdateFillTitle();
styleButtonPressed(d->group->checkedId());
// Gradient selector
d->ui->wdgGradientEditor->setCompactMode(true);
connect(d->ui->wdgGradientEditor, SIGNAL(sigGradientChanged()), &d->gradientChangedCompressor, SLOT(start()));
connect(&d->gradientChangedCompressor, SIGNAL(timeout()), SLOT(activeGradientChanged()));
KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance();
QSharedPointer<KoAbstractResourceServerAdapter> gradientResourceAdapter(
new KoResourceServerAdapter<KoAbstractGradient>(serverProvider->gradientServer()));
d->gradientAction = new KoResourcePopupAction(gradientResourceAdapter,
d->ui->btnChoosePredefinedGradient);
d->gradientAction->setToolTip(i18n("Change filling gradient"));
d->ui->btnChoosePredefinedGradient->setDefaultAction(d->gradientAction);
d->ui->btnChoosePredefinedGradient->setPopupMode(QToolButton::InstantPopup);
connect(d->gradientAction, SIGNAL(resourceSelected(QSharedPointer<KoShapeBackground>)),
SLOT(gradientResourceChanged()));
connect(d->ui->btnChoosePredefinedGradient, SIGNAL(iconSizeChanged()), d->gradientAction, SLOT(updateIcon()));
d->ui->btnSaveGradient->setIcon(KisIconUtils::loadIcon("document-save"));
connect(d->ui->btnSaveGradient, SIGNAL(clicked()), SLOT(slotSavePredefinedGradientClicked()));
connect(d->ui->cmbGradientRepeat, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientRepeatChanged()));
connect(d->ui->cmbGradientType, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientTypeChanged()));
deactivate();
#if 0
// Pattern selector
QSharedPointer<KoAbstractResourceServerAdapter>patternResourceAdapter(new KoResourceServerAdapter<KoPattern>(serverProvider->patternServer()));
d->patternAction = new KoResourcePopupAction(patternResourceAdapter, d->colorButton);
d->patternAction->setToolTip(i18n("Change the filling pattern"));
connect(d->patternAction, SIGNAL(resourceSelected(QSharedPointer<KoShapeBackground>)), this, SLOT(patternChanged(QSharedPointer<KoShapeBackground>)));
connect(d->colorButton, SIGNAL(iconSizeChanged()), d->patternAction, SLOT(updateIcon()));
#endif
}
KoFillConfigWidget::~KoFillConfigWidget()
{
delete d;
}
void KoFillConfigWidget::activate()
{
- KIS_SAFE_ASSERT_RECOVER_RETURN(!d->deactivationLocks.empty());
d->deactivationLocks.clear();
-
if (!d->noSelectionTrackingMode) {
d->shapeChangedCompressor.start();
} else {
loadCurrentFillFromResourceServer();
}
updateWidgetComponentVisbility();
}
void KoFillConfigWidget::deactivate()
{
emit sigInternalRecoverColorInResourceManager();
-
- KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty());
+ d->deactivationLocks.clear();
d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector));
d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector));
}
void KoFillConfigWidget::forceUpdateOnSelectionChanged()
{
d->shapeChangedCompressor.start();
}
void KoFillConfigWidget::setNoSelectionTrackingMode(bool value)
{
d->noSelectionTrackingMode = value;
if (!d->noSelectionTrackingMode) {
d->shapeChangedCompressor.start();
}
}
void KoFillConfigWidget::slotUpdateFillTitle()
{
QString text = d->group->checkedButton() ? d->group->checkedButton()->text() : QString();
text.replace('&', QString());
d->ui->lblFillTitle->setText(text);
}
void KoFillConfigWidget::slotCanvasResourceChanged(int key, const QVariant &value)
{
if ((key == KoCanvasResourceProvider::ForegroundColor && d->fillVariant == KoFlake::Fill) ||
(key == KoCanvasResourceProvider::BackgroundColor &&
d->fillVariant == KoFlake::StrokeFill && !d->noSelectionTrackingMode) ||
(key == KoCanvasResourceProvider::ForegroundColor && d->noSelectionTrackingMode)) {
KoColor color = value.value<KoColor>();
const int checkedId = d->group->checkedId();
if ((checkedId < 0 || checkedId == None || checkedId == Solid) &&
!(checkedId == Solid && d->colorAction->currentKoColor() == color)) {
d->group->button(Solid)->setChecked(true);
d->selectedFillIndex = Solid;
d->colorAction->setCurrentColor(color);
d->colorChangedCompressor.start();
} else if (checkedId == Gradient && key == KoCanvasResourceProvider::ForegroundColor) {
d->ui->wdgGradientEditor->notifyGlobalColorChanged(color);
}
} else if (key == KisCanvasResourceProvider::CurrentGradient) {
KoResource *gradient = value.value<KoAbstractGradient*>();
const int checkedId = d->group->checkedId();
if (gradient && (checkedId < 0 || checkedId == None || checkedId == Gradient)) {
d->group->button(Gradient)->setChecked(true);
d->gradientAction->setCurrentResource(gradient);
}
}
}
QList<KoShape*> KoFillConfigWidget::currentShapes()
{
return d->canvas->selectedShapesProxy()->selection()->selectedEditableShapes();
}
int KoFillConfigWidget::selectedFillIndex() {
return d->selectedFillIndex;
}
void KoFillConfigWidget::styleButtonPressed(int buttonId)
{
QList<KoShape*> shapes = currentShapes();
switch (buttonId) {
case KoFillConfigWidget::None:
noColorSelected();
break;
case KoFillConfigWidget::Solid:
colorChanged();
break;
case KoFillConfigWidget::Gradient:
if (d->activeGradient) {
setNewGradientBackgroundToShape();
updateGradientSaveButtonAvailability();
} else {
gradientResourceChanged();
}
break;
case KoFillConfigWidget::Pattern:
// Only select mode in the widget, don't set actual pattern :/
//d->colorButton->setDefaultAction(d->patternAction);
//patternChanged(d->patternAction->currentBackground());
break;
}
// update tool option fields with first selected object
if (shapes.isEmpty() == false) {
KoShape *firstShape = shapes.first();
updateFillIndexFromShape(firstShape);
updateFillColorFromShape(firstShape);
}
updateWidgetComponentVisbility();
}
KoShapeStrokeSP KoFillConfigWidget::createShapeStroke()
{
KoShapeStrokeSP stroke(new KoShapeStroke());
KIS_ASSERT_RECOVER_RETURN_VALUE(d->fillVariant == KoFlake::StrokeFill, stroke);
switch (d->group->checkedId()) {
case KoFillConfigWidget::None:
stroke->setColor(Qt::transparent);
break;
case KoFillConfigWidget::Solid:
stroke->setColor(d->colorAction->currentColor());
break;
case KoFillConfigWidget::Gradient: {
QScopedPointer<QGradient> g(d->activeGradient->toQGradient());
QBrush newBrush = *g;
stroke->setLineBrush(newBrush);
stroke->setColor(Qt::transparent);
break;
}
case KoFillConfigWidget::Pattern:
break;
}
return stroke;
}
void KoFillConfigWidget::noColorSelected()
{
KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector);
QList<KoShape*> selectedShapes = currentShapes();
if (selectedShapes.isEmpty()) {
emit sigFillChanged();
return;
}
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
KUndo2Command *command = wrapper.setColor(QColor());
if (command) {
d->canvas->addCommand(command);
}
if (d->fillVariant == KoFlake::StrokeFill) {
KUndo2Command *lineCommand = wrapper.setLineWidth(0.0);
if (lineCommand) {
d->canvas->addCommand(lineCommand);
}
}
emit sigFillChanged();
}
void KoFillConfigWidget::colorChanged()
{
KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector);
QList<KoShape*> selectedShapes = currentShapes();
if (selectedShapes.isEmpty()) {
emit sigInternalRequestColorToResourceManager();
emit sigFillChanged();
return;
}
d->overriddenColorFromProvider = boost::none;
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
KUndo2Command *command = wrapper.setColor(d->colorAction->currentColor());
if (command) {
d->canvas->addCommand(command);
}
// only returns true if it is a stroke object that has a 0 for line width
if (wrapper.hasZeroLineWidth() ) {
KUndo2Command *lineCommand = wrapper.setLineWidth(1.0);
if (lineCommand) {
d->canvas->addCommand(lineCommand);
}
// * line to test out
QColor solidColor = d->colorAction->currentColor();
solidColor.setAlpha(255);
command = wrapper.setColor(solidColor);
if (command) {
d->canvas->addCommand(command);
}
}
d->colorAction->setCurrentColor(wrapper.color());
emit sigFillChanged();
emit sigInternalRequestColorToResourceManager();
}
void KoFillConfigWidget::slotProposeCurrentColorToResourceManager()
{
const int checkedId = d->group->checkedId();
bool hasColor = false;
KoColor color;
KoCanvasResourceProvider::CanvasResource colorSlot = KoCanvasResourceProvider::ForegroundColor;
if (checkedId == Solid) {
if (d->fillVariant == KoFlake::StrokeFill) {
colorSlot = KoCanvasResourceProvider::BackgroundColor;
}
color = d->colorAction->currentKoColor();
hasColor = true;
} else if (checkedId == Gradient) {
if (boost::optional<KoColor> gradientColor = d->ui->wdgGradientEditor->currentActiveStopColor()) {
color = *gradientColor;
hasColor = true;
}
}
if (hasColor) {
if (!d->overriddenColorFromProvider) {
d->overriddenColorFromProvider =
d->canvas->resourceManager()->resource(colorSlot).value<KoColor>();
}
/**
* Don't let opacity leak to our resource manager system
*
* NOTE: theoretically, we could guarantee it on a level of the
* resource manager itself,
*/
color.setOpacity(OPACITY_OPAQUE_U8);
d->canvas->resourceManager()->setResource(colorSlot, QVariant::fromValue(color));
}
}
void KoFillConfigWidget::slotRecoverColorInResourceManager()
{
if (d->overriddenColorFromProvider) {
KoCanvasResourceProvider::CanvasResource colorSlot = KoCanvasResourceProvider::ForegroundColor;
if (d->fillVariant == KoFlake::StrokeFill) {
colorSlot = KoCanvasResourceProvider::BackgroundColor;
}
d->canvas->resourceManager()->setResource(colorSlot, QVariant::fromValue(*d->overriddenColorFromProvider));
d->overriddenColorFromProvider = boost::none;
}
}
template <class ResourceServer>
QString findFirstAvailableResourceName(const QString &baseName, ResourceServer *server)
{
if (!server->resourceByName(baseName)) return baseName;
int counter = 1;
QString result;
while ((result = QString("%1%2").arg(baseName).arg(counter)),
server->resourceByName(result)) {
counter++;
}
return result;
}
void KoFillConfigWidget::slotSavePredefinedGradientClicked()
{
KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance();
auto server = serverProvider->gradientServer();
const QString defaultGradientNamePrefix = i18nc("default prefix for the saved gradient", "gradient");
QString name = d->activeGradient->name().isEmpty() ? defaultGradientNamePrefix : d->activeGradient->name();
name = findFirstAvailableResourceName(name, server);
name = QInputDialog::getText(this, i18nc("@title:window", "Save Gradient"), i18n("Enter gradient name:"), QLineEdit::Normal, name);
// TODO: currently we do not allow the user to
// create two resources with the same name!
// Please add some feedback for it!
name = findFirstAvailableResourceName(name, server);
d->activeGradient->setName(name);
const QString saveLocation = server->saveLocation();
d->activeGradient->setFilename(saveLocation + d->activeGradient->name() + d->activeGradient->defaultFileExtension());
KoAbstractGradient *newGradient = d->activeGradient->clone();
server->addResource(newGradient);
d->gradientAction->setCurrentResource(newGradient);
}
void KoFillConfigWidget::activeGradientChanged()
{
setNewGradientBackgroundToShape();
updateGradientSaveButtonAvailability();
emit sigInternalRequestColorToResourceManager();
}
void KoFillConfigWidget::gradientResourceChanged()
{
QSharedPointer<KoGradientBackground> bg =
qSharedPointerDynamicCast<KoGradientBackground>(
d->gradientAction->currentBackground());
uploadNewGradientBackground(bg->gradient());
setNewGradientBackgroundToShape();
updateGradientSaveButtonAvailability();
}
void KoFillConfigWidget::slotGradientTypeChanged()
{
QGradient::Type type =
d->ui->cmbGradientType->currentIndex() == 0 ?
QGradient::LinearGradient : QGradient::RadialGradient;
d->activeGradient->setType(type);
activeGradientChanged();
}
void KoFillConfigWidget::slotGradientRepeatChanged()
{
QGradient::Spread spread =
QGradient::Spread(d->ui->cmbGradientRepeat->currentIndex());
d->activeGradient->setSpread(spread);
activeGradientChanged();
}
void KoFillConfigWidget::uploadNewGradientBackground(const QGradient *gradient)
{
KisSignalsBlocker b1(d->ui->wdgGradientEditor,
d->ui->cmbGradientType,
d->ui->cmbGradientRepeat);
d->ui->wdgGradientEditor->setGradient(0);
d->activeGradient.reset(KoStopGradient::fromQGradient(gradient));
d->ui->wdgGradientEditor->setGradient(d->activeGradient.data());
d->ui->cmbGradientType->setCurrentIndex(d->activeGradient->type() != QGradient::LinearGradient);
d->ui->cmbGradientRepeat->setCurrentIndex(int(d->activeGradient->spread()));
}
void KoFillConfigWidget::setNewGradientBackgroundToShape()
{
QList<KoShape*> selectedShapes = currentShapes();
if (selectedShapes.isEmpty()) {
emit sigFillChanged();
return;
}
KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector);
KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
QScopedPointer<QGradient> srcQGradient(d->activeGradient->toQGradient());
KUndo2Command *command = wrapper.applyGradientStopsOnly(srcQGradient.data());
if (command) {
d->canvas->addCommand(command);
}
emit sigFillChanged();
}
void KoFillConfigWidget::updateGradientSaveButtonAvailability()
{
bool savingEnabled = false;
QScopedPointer<QGradient> currentGradient(d->activeGradient->toQGradient());
QSharedPointer<KoShapeBackground> bg = d->gradientAction->currentBackground();
if (bg) {
QSharedPointer<KoGradientBackground> resourceBackground =
qSharedPointerDynamicCast<KoGradientBackground>(bg);
savingEnabled = resourceBackground->gradient()->stops() != currentGradient->stops();
savingEnabled |= resourceBackground->gradient()->type() != currentGradient->type();
savingEnabled |= resourceBackground->gradient()->spread() != currentGradient->spread();
}
d->ui->btnSaveGradient->setEnabled(savingEnabled);
}
void KoFillConfigWidget::patternChanged(QSharedPointer<KoShapeBackground> background)
{
Q_UNUSED(background);
#if 0
QSharedPointer<KoPatternBackground> patternBackground = qSharedPointerDynamicCast<KoPatternBackground>(background);
if (! patternBackground) {
return;
}
QList<KoShape*> selectedShapes = currentShapes();
if (selectedShapes.isEmpty()) {
return;
}
KoImageCollection *imageCollection = d->canvas->shapeController()->resourceManager()->imageCollection();
if (imageCollection) {
QSharedPointer<KoPatternBackground> fill(new KoPatternBackground(imageCollection));
fill->setPattern(patternBackground->pattern());
d->canvas->addCommand(new KoShapeBackgroundCommand(selectedShapes, fill));
}
#endif
}
void KoFillConfigWidget::loadCurrentFillFromResourceServer()
{
{
KoColor color = d->canvas->resourceManager()->backgroundColor();
slotCanvasResourceChanged(KoCanvasResourceProvider::BackgroundColor, QVariant::fromValue(color));
}
{
KoColor color = d->canvas->resourceManager()->foregroundColor();
slotCanvasResourceChanged(KoCanvasResourceProvider::ForegroundColor, QVariant::fromValue(color));
}
Q_FOREACH (QAbstractButton *button, d->group->buttons()) {
button->setEnabled(true);
}
emit sigFillChanged();
}
void KoFillConfigWidget::shapeChanged()
{
if (d->noSelectionTrackingMode) return;
QList<KoShape*> shapes = currentShapes();
bool shouldUploadColorToResourceManager = false;
if (shapes.isEmpty() ||
(shapes.size() > 1 && KoShapeFillWrapper(shapes, d->fillVariant).isMixedFill())) {
Q_FOREACH (QAbstractButton *button, d->group->buttons()) {
button->setEnabled(!shapes.isEmpty());
}
} else {
// only one vector object selected
Q_FOREACH (QAbstractButton *button, d->group->buttons()) {
button->setEnabled(true);
}
// update active index of shape
KoShape *shape = shapes.first();
updateFillIndexFromShape(shape);
updateFillColorFromShape(shape); // updates tool options fields
shouldUploadColorToResourceManager = true;
}
// updates the UI
d->group->button(d->selectedFillIndex)->setChecked(true);
updateWidgetComponentVisbility();
slotUpdateFillTitle();
if (shouldUploadColorToResourceManager) {
emit sigInternalRequestColorToResourceManager();
} else {
emit sigInternalRecoverColorInResourceManager();
}
}
void KoFillConfigWidget::updateFillIndexFromShape(KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
KoShapeFillWrapper wrapper(shape, d->fillVariant);
switch (wrapper.type()) {
case KoFlake::None:
d->selectedFillIndex = KoFillConfigWidget::None;
break;
case KoFlake::Solid:
d->selectedFillIndex = KoFillConfigWidget::Solid;
break;
case KoFlake::Gradient:
d->selectedFillIndex = KoFillConfigWidget::Gradient;
break;
case KoFlake::Pattern:
d->selectedFillIndex = KoFillConfigWidget::Pattern;
break;
}
}
void KoFillConfigWidget::updateFillColorFromShape(KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
KoShapeFillWrapper wrapper(shape, d->fillVariant);
switch (wrapper.type()) {
case KoFlake::None:
break;
case KoFlake::Solid: {
QColor color = wrapper.color();
if (color.alpha() > 0) {
d->colorAction->setCurrentColor(wrapper.color());
}
break;
}
case KoFlake::Gradient:
uploadNewGradientBackground(wrapper.gradient());
updateGradientSaveButtonAvailability();
break;
case KoFlake::Pattern:
break;
}
}
void KoFillConfigWidget::updateWidgetComponentVisbility()
{
// The UI is showing/hiding things like this because the 'stacked widget' isn't very flexible
// and makes it difficult to put anything underneath it without a lot empty space
// hide everything first
d->ui->wdgGradientEditor->setVisible(false);
d->ui->btnChoosePredefinedGradient->setVisible(false);
d->ui->btnChooseSolidColor->setVisible(false);
d->ui->typeLabel->setVisible(false);
d->ui->repeatLabel->setVisible(false);
d->ui->cmbGradientRepeat->setVisible(false);
d->ui->cmbGradientType->setVisible(false);
d->ui->btnSolidColorPick->setVisible(false);
d->ui->btnSaveGradient->setVisible(false);
d->ui->gradientTypeLine->setVisible(false);
d->ui->soldStrokeColorLabel->setVisible(false);
d->ui->presetLabel->setVisible(false);
// keep options hidden if no vector shapes are selected
if(currentShapes().isEmpty()) {
return;
}
switch (d->selectedFillIndex) {
case KoFillConfigWidget::None:
break;
case KoFillConfigWidget::Solid:
d->ui->btnChooseSolidColor->setVisible(true);
d->ui->btnSolidColorPick->setVisible(false);
d->ui->soldStrokeColorLabel->setVisible(true);
break;
case KoFillConfigWidget::Gradient:
d->ui->wdgGradientEditor->setVisible(true);
d->ui->btnChoosePredefinedGradient->setVisible(true);
d->ui->typeLabel->setVisible(true);
d->ui->repeatLabel->setVisible(true);
d->ui->cmbGradientRepeat->setVisible(true);
d->ui->cmbGradientType->setVisible(true);
d->ui->btnSaveGradient->setVisible(true);
d->ui->gradientTypeLine->setVisible(true);
d->ui->presetLabel->setVisible(true);
break;
case KoFillConfigWidget::Pattern:
break;
}
}
diff --git a/libs/ui/widgets/KoStrokeConfigWidget.cpp b/libs/ui/widgets/KoStrokeConfigWidget.cpp
index 608e57a261..cbdc24ca28 100644
--- a/libs/ui/widgets/KoStrokeConfigWidget.cpp
+++ b/libs/ui/widgets/KoStrokeConfigWidget.cpp
@@ -1,804 +1,800 @@
/* This file is part of the KDE project
* Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr)
* Copyright (C) 2002 Tomislav Lukman <tomislav.lukman@ck.t-com.hr>
* Copyright (C) 2002-2003 Rob Buis <buis@kde.org>
* Copyright (C) 2005-2006 Tim Beaulen <tbscope@gmail.com>
* Copyright (C) 2005-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2005-2006, 2011 Inge Wallin <inge@lysator.liu.se>
* Copyright (C) 2005-2008 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2006 Peter Simonsson <psn@linux.se>
* Copyright (C) 2006 Laurent Montel <montel@kde.org>
* Copyright (C) 2007,2011 Thorsten Zachmann <t.zachmann@zagge.de>
* Copyright (C) 2011 Jean-Nicolas Artaud <jeannicolasartaud@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.
*/
// Own
#include "KoStrokeConfigWidget.h"
// Qt
#include <QMenu>
#include <QToolButton>
#include <QButtonGroup>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QSizePolicy>
#include <KisSignalMapper.h>
// KDE
#include <klocalizedstring.h>
// Calligra
#include <KoIcon.h>
#include <KoUnit.h>
#include <KoLineStyleSelector.h>
#include <KoUnitDoubleSpinBox.h>
#include <KoMarkerSelector.h>
#include <KoColorPopupAction.h>
#include <KoMarker.h>
#include <KoShapeStroke.h>
#include <KoPathShape.h>
#include <KoMarkerCollection.h>
#include <KoPathShapeMarkerCommand.h>
#include <KoCanvasBase.h>
#include <KoCanvasController.h>
#include <KoCanvasResourceProvider.h>
#include <KoDocumentResourceManager.h>
#include <KoSelection.h>
#include <KoShapeController.h>
#include <KoShapeStrokeCommand.h>
#include <KoShapeStrokeModel.h>
#include <KoSelectedShapesProxy.h>
#include "ui_KoStrokeConfigWidget.h"
#include <KoFlakeUtils.h>
#include <KoFillConfigWidget.h>
#include "kis_canvas_resource_provider.h"
#include "kis_acyclic_signal_connector.h"
#include <kis_signal_compressor.h>
// Krita
#include "kis_double_parse_unit_spin_box.h"
class CapNJoinMenu : public QMenu
{
public:
CapNJoinMenu(QWidget *parent = 0);
QSize sizeHint() const override;
KisDoubleParseUnitSpinBox *miterLimit {0};
QButtonGroup *capGroup {0};
QButtonGroup *joinGroup {0};
};
CapNJoinMenu::CapNJoinMenu(QWidget *parent)
: QMenu(parent)
{
QGridLayout *mainLayout = new QGridLayout();
mainLayout->setMargin(2);
// The cap group
capGroup = new QButtonGroup(this);
capGroup->setExclusive(true);
QToolButton *button = 0;
button = new QToolButton(this);
button->setIcon(koIcon("stroke-cap-butt"));
button->setCheckable(true);
button->setToolTip(i18n("Butt cap"));
capGroup->addButton(button, Qt::FlatCap);
mainLayout->addWidget(button, 2, 0);
button = new QToolButton(this);
button->setIcon(koIcon("stroke-cap-round"));
button->setCheckable(true);
button->setToolTip(i18n("Round cap"));
capGroup->addButton(button, Qt::RoundCap);
mainLayout->addWidget(button, 2, 1);
button = new QToolButton(this);
button->setIcon(koIcon("stroke-cap-square"));
button->setCheckable(true);
button->setToolTip(i18n("Square cap"));
capGroup->addButton(button, Qt::SquareCap);
mainLayout->addWidget(button, 2, 2, Qt::AlignLeft);
// The join group
joinGroup = new QButtonGroup(this);
joinGroup->setExclusive(true);
button = new QToolButton(this);
button->setIcon(koIcon("stroke-join-miter"));
button->setCheckable(true);
button->setToolTip(i18n("Miter join"));
joinGroup->addButton(button, Qt::MiterJoin);
mainLayout->addWidget(button, 3, 0);
button = new QToolButton(this);
button->setIcon(koIcon("stroke-join-round"));
button->setCheckable(true);
button->setToolTip(i18n("Round join"));
joinGroup->addButton(button, Qt::RoundJoin);
mainLayout->addWidget(button, 3, 1);
button = new QToolButton(this);
button->setIcon(koIcon("stroke-join-bevel"));
button->setCheckable(true);
button->setToolTip(i18n("Bevel join"));
joinGroup->addButton(button, Qt::BevelJoin);
mainLayout->addWidget(button, 3, 2, Qt::AlignLeft);
// Miter limit
// set min/max/step and value in points, then set actual unit
miterLimit = new KisDoubleParseUnitSpinBox(this);
miterLimit->setMinMaxStep(0.0, 1000.0, 0.5);
miterLimit->setDecimals(2);
miterLimit->setUnit(KoUnit(KoUnit::Point));
miterLimit->setToolTip(i18n("Miter limit"));
mainLayout->addWidget(miterLimit, 4, 0, 1, 3);
mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
setLayout(mainLayout);
}
QSize CapNJoinMenu::sizeHint() const
{
return layout()->sizeHint();
}
class Q_DECL_HIDDEN KoStrokeConfigWidget::Private
{
public:
Private()
: selectionChangedCompressor(200, KisSignalCompressor::FIRST_ACTIVE)
{
}
KoLineStyleSelector *lineStyle {0};
KisDoubleParseUnitSpinBox *lineWidth {0};
KoMarkerSelector *startMarkerSelector {0};
KoMarkerSelector *midMarkerSelector {0};
KoMarkerSelector *endMarkerSelector {0};
CapNJoinMenu *capNJoinMenu {0};
QWidget*spacer {0};
KoCanvasBase *canvas {0};
bool active {true};
bool allowLocalUnitManagement {false};
KoFillConfigWidget *fillConfigWidget {0};
bool noSelectionTrackingMode {false};
KisAcyclicSignalConnector shapeChangedAcyclicConnector;
KisAcyclicSignalConnector resourceManagerAcyclicConnector;
KisSignalCompressor selectionChangedCompressor;
std::vector<KisAcyclicSignalConnector::Blocker> deactivationLocks;
Ui_KoStrokeConfigWidget *ui;
};
KoStrokeConfigWidget::KoStrokeConfigWidget(KoCanvasBase *canvas, QWidget * parent)
: QWidget(parent)
, d(new Private())
{
// configure GUI
d->ui = new Ui_KoStrokeConfigWidget();
d->ui->setupUi(this);
setObjectName("Stroke widget");
{ // connect the canvas
d->shapeChangedAcyclicConnector.connectBackwardVoid(
canvas->selectedShapesProxy(), SIGNAL(selectionChanged()),
&d->selectionChangedCompressor, SLOT(start()));
d->shapeChangedAcyclicConnector.connectBackwardVoid(
canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
&d->selectionChangedCompressor, SLOT(start()));
connect(&d->selectionChangedCompressor, SIGNAL(timeout()), this, SLOT(selectionChanged()));
d->resourceManagerAcyclicConnector.connectBackwardResourcePair(
canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(canvasResourceChanged(int,QVariant)));
d->canvas = canvas;
}
{
d->fillConfigWidget = new KoFillConfigWidget(canvas, KoFlake::StrokeFill, true, this);
d->fillConfigWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
d->ui->fillConfigWidgetLayout->addWidget(d->fillConfigWidget);
connect(d->fillConfigWidget, SIGNAL(sigFillChanged()), SIGNAL(sigStrokeChanged()));
}
d->ui->thicknessLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
d->ui->thicknessLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
// set min/max/step and value in points, then set actual unit
d->ui->lineWidth->setMinMaxStep(0.5, 1000.0, 0.5); // if someone wants 0, just set to "none" on UI
d->ui->lineWidth->setDecimals(2);
d->ui->lineWidth->setUnit(KoUnit(KoUnit::Point));
d->ui->lineWidth->setToolTip(i18n("Set line width of actual selection"));
d->ui->capNJoinButton->setMinimumHeight(25);
d->capNJoinMenu = new CapNJoinMenu(this);
d->ui->capNJoinButton->setMenu(d->capNJoinMenu);
d->ui->capNJoinButton->setText("...");
d->ui->capNJoinButton->setPopupMode(QToolButton::InstantPopup);
{
// Line style
d->ui->strokeStyleLabel->setText(i18n("Line Style:"));
d->ui->strokeStyleLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
d->ui->lineStyle->setToolTip(i18nc("@info:tooltip", "Line style"));
d->ui->lineStyle->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector<qreal>());
}
{
QList<KoMarker*> emptyMarkers;
d->startMarkerSelector = new KoMarkerSelector(KoFlake::StartMarker, this);
d->startMarkerSelector->setToolTip(i18nc("@info:tooltip", "Start marker"));
d->startMarkerSelector->updateMarkers(emptyMarkers);
d->startMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred );
d->ui->markerLayout->addWidget(d->startMarkerSelector);
d->midMarkerSelector = new KoMarkerSelector(KoFlake::MidMarker, this);
d->midMarkerSelector->setToolTip(i18nc("@info:tooltip", "Node marker"));
d->midMarkerSelector->updateMarkers(emptyMarkers);
d->midMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred );
d->ui->markerLayout->addWidget(d->midMarkerSelector);
d->endMarkerSelector = new KoMarkerSelector(KoFlake::EndMarker, this);
d->endMarkerSelector->setToolTip(i18nc("@info:tooltip", "End marker"));
d->endMarkerSelector->updateMarkers(emptyMarkers);
d->endMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred );
d->ui->markerLayout->addWidget(d->endMarkerSelector);
}
// Spacer
d->spacer = new QWidget();
d->spacer->setObjectName("SpecialSpacer");
d->ui->markerLayout->addWidget(d->spacer);
connect(d->ui->lineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(applyDashStyleChanges()));
connect(d->ui->lineWidth, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyLineWidthChanges()));
connect(d->capNJoinMenu->capGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyJoinCapChanges()));
connect(d->capNJoinMenu->joinGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyJoinCapChanges()));
connect(d->capNJoinMenu->miterLimit, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyJoinCapChanges()));
{ // Map the marker signals correctly
KisSignalMapper *mapper = new KisSignalMapper(this);
connect(mapper, SIGNAL(mapped(int)), SLOT(applyMarkerChanges(int)));
connect(d->startMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map()));
connect(d->midMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map()));
connect(d->endMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map()));
mapper->setMapping(d->startMarkerSelector, KoFlake::StartMarker);
mapper->setMapping(d->midMarkerSelector, KoFlake::MidMarker);
mapper->setMapping(d->endMarkerSelector, KoFlake::EndMarker);
}
KoDocumentResourceManager *resourceManager = canvas->shapeController()->resourceManager();
if (resourceManager) {
KoMarkerCollection *collection = resourceManager->resource(KoDocumentResourceManager::MarkerCollection).value<KoMarkerCollection*>();
if (collection) {
updateMarkers(collection->markers());
}
}
d->selectionChangedCompressor.start();
d->fillConfigWidget->activate();
deactivate();
}
KoStrokeConfigWidget::~KoStrokeConfigWidget()
{
delete d;
}
void KoStrokeConfigWidget::setNoSelectionTrackingMode(bool value)
{
d->fillConfigWidget->setNoSelectionTrackingMode(value);
d->noSelectionTrackingMode = value;
if (!d->noSelectionTrackingMode) {
d->selectionChangedCompressor.start();
}
}
// ----------------------------------------------------------------
// getters and setters
Qt::PenStyle KoStrokeConfigWidget::lineStyle() const
{
return d->ui->lineStyle->lineStyle();
}
QVector<qreal> KoStrokeConfigWidget::lineDashes() const
{
return d->ui->lineStyle->lineDashes();
}
qreal KoStrokeConfigWidget::lineWidth() const
{
return d->ui->lineWidth->value();
}
qreal KoStrokeConfigWidget::miterLimit() const
{
return d->capNJoinMenu->miterLimit->value();
}
KoMarker *KoStrokeConfigWidget::startMarker() const
{
return d->startMarkerSelector->marker();
}
KoMarker *KoStrokeConfigWidget::endMarker() const
{
return d->endMarkerSelector->marker();
}
Qt::PenCapStyle KoStrokeConfigWidget::capStyle() const
{
return static_cast<Qt::PenCapStyle>(d->capNJoinMenu->capGroup->checkedId());
}
Qt::PenJoinStyle KoStrokeConfigWidget::joinStyle() const
{
return static_cast<Qt::PenJoinStyle>(d->capNJoinMenu->joinGroup->checkedId());
}
KoShapeStrokeSP KoStrokeConfigWidget::createShapeStroke()
{
KoShapeStrokeSP stroke(d->fillConfigWidget->createShapeStroke());
stroke->setLineWidth(lineWidth());
stroke->setCapStyle(capStyle());
stroke->setJoinStyle(joinStyle());
stroke->setMiterLimit(miterLimit());
stroke->setLineStyle(lineStyle(), lineDashes());
return stroke;
}
// ----------------------------------------------------------------
// Other public functions
void KoStrokeConfigWidget::updateStyleControlsAvailability(bool enabled)
{
d->ui->lineWidth->setEnabled(enabled);
d->capNJoinMenu->setEnabled(enabled);
d->ui->lineStyle->setEnabled(enabled);
d->startMarkerSelector->setEnabled(enabled);
d->midMarkerSelector->setEnabled(enabled);
d->endMarkerSelector->setEnabled(enabled);
}
void KoStrokeConfigWidget::setUnit(const KoUnit &unit, KoShape *representativeShape)
{
if (!d->allowLocalUnitManagement) {
return; //the unit management is completely transferred to the unitManagers.
}
blockChildSignals(true);
/**
* KoStrokeShape knows nothing about the transformations applied
* to the shape, which doesn't prevent the shape to apply them and
* display the stroke differently. So just take that into account
* and show the user correct values using the multiplier in KoUnit.
*/
KoUnit newUnit(unit);
if (representativeShape) {
- newUnit.adjustByPixelTransform(representativeShape->absoluteTransformation(0));
+ newUnit.adjustByPixelTransform(representativeShape->absoluteTransformation());
}
d->ui->lineWidth->setUnit(newUnit);
d->capNJoinMenu->miterLimit->setUnit(newUnit);
d->ui->lineWidth->setLineStep(1.0);
d->capNJoinMenu->miterLimit->setLineStep(1.0);
blockChildSignals(false);
}
void KoStrokeConfigWidget::setUnitManagers(KisSpinBoxUnitManager* managerLineWidth,
KisSpinBoxUnitManager *managerMitterLimit)
{
blockChildSignals(true);
d->allowLocalUnitManagement = false;
d->ui->lineWidth->setUnitManager(managerLineWidth);
d->capNJoinMenu->miterLimit->setUnitManager(managerMitterLimit);
blockChildSignals(false);
}
void KoStrokeConfigWidget::updateMarkers(const QList<KoMarker*> &markers)
{
d->startMarkerSelector->updateMarkers(markers);
d->midMarkerSelector->updateMarkers(markers);
d->endMarkerSelector->updateMarkers(markers);
}
void KoStrokeConfigWidget::activate()
{
- KIS_SAFE_ASSERT_RECOVER_RETURN(!d->deactivationLocks.empty());
d->deactivationLocks.clear();
d->fillConfigWidget->activate();
-
if (!d->noSelectionTrackingMode) {
- // selectionChanged();
d->selectionChangedCompressor.start();
} else {
loadCurrentStrokeFillFromResourceServer();
}
}
void KoStrokeConfigWidget::deactivate()
{
- KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty());
-
+ d->deactivationLocks.clear();
d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector));
d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector));
d->fillConfigWidget->deactivate();
}
void KoStrokeConfigWidget::blockChildSignals(bool block)
{
d->ui->lineWidth->blockSignals(block);
d->capNJoinMenu->capGroup->blockSignals(block);
d->capNJoinMenu->joinGroup->blockSignals(block);
d->capNJoinMenu->miterLimit->blockSignals(block);
d->ui->lineStyle->blockSignals(block);
d->startMarkerSelector->blockSignals(block);
d->midMarkerSelector->blockSignals(block);
d->endMarkerSelector->blockSignals(block);
}
void KoStrokeConfigWidget::setActive(bool active)
{
d->active = active;
}
//------------------------
template <typename ModifyFunction>
auto applyChangeToStrokes(KoCanvasBase *canvas, ModifyFunction modifyFunction)
-> decltype(modifyFunction(KoShapeStrokeSP()), void())
{
KoSelection *selection = canvas->selectedShapesProxy()->selection();
if (!selection) return;
QList<KoShape*> shapes = selection->selectedEditableShapes();
KUndo2Command *command = KoFlake::modifyShapesStrokes(shapes, modifyFunction);
if (command) {
canvas->addCommand(command);
}
}
void KoStrokeConfigWidget::applyDashStyleChanges()
{
applyChangeToStrokes(
d->canvas,
[this] (KoShapeStrokeSP stroke) {
stroke->setLineStyle(lineStyle(), lineDashes());
});
emit sigStrokeChanged();
}
void KoStrokeConfigWidget::applyLineWidthChanges()
{
applyChangeToStrokes(
d->canvas,
[this] (KoShapeStrokeSP stroke) {
stroke->setLineWidth(lineWidth());
});
emit sigStrokeChanged();
}
void KoStrokeConfigWidget::applyJoinCapChanges()
{
applyChangeToStrokes(
d->canvas,
[this] (KoShapeStrokeSP stroke) {
stroke->setCapStyle(static_cast<Qt::PenCapStyle>(d->capNJoinMenu->capGroup->checkedId()));
stroke->setJoinStyle(static_cast<Qt::PenJoinStyle>(d->capNJoinMenu->joinGroup->checkedId()));
stroke->setMiterLimit(miterLimit());
});
emit sigStrokeChanged();
}
void KoStrokeConfigWidget::applyMarkerChanges(int rawPosition)
{
KoSelection *selection = d->canvas->selectedShapesProxy()->selection();
if (!selection) {
emit sigStrokeChanged();
return;
}
QList<KoShape*> shapes = selection->selectedEditableShapes();
QList<KoPathShape*> pathShapes;
Q_FOREACH (KoShape *shape, shapes) {
KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
if (pathShape) {
pathShapes << pathShape;
}
}
if (pathShapes.isEmpty()) {
emit sigStrokeChanged();
return;
}
KoFlake::MarkerPosition position = KoFlake::MarkerPosition(rawPosition);
QScopedPointer<KoMarker> marker;
switch (position) {
case KoFlake::StartMarker:
if (d->startMarkerSelector->marker()) {
marker.reset(new KoMarker(*d->startMarkerSelector->marker()));
}
break;
case KoFlake::MidMarker:
if (d->midMarkerSelector->marker()) {
marker.reset(new KoMarker(*d->midMarkerSelector->marker()));
}
break;
case KoFlake::EndMarker:
if (d->endMarkerSelector->marker()) {
marker.reset(new KoMarker(*d->endMarkerSelector->marker()));
}
break;
}
KUndo2Command* command = new KoPathShapeMarkerCommand(pathShapes, marker.take(), position);
d->canvas->addCommand(command);
emit sigStrokeChanged();
}
// ----------------------------------------------------------------
struct CheckShapeStrokeStyleBasePolicy
{
typedef KoShapeStrokeSP PointerType;
static PointerType getProperty(KoShape *shape) {
return qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
}
};
struct CheckShapeStrokeDashesPolicy : public CheckShapeStrokeStyleBasePolicy
{
static bool compareTo(PointerType p1, PointerType p2) {
return p1->lineStyle() == p2->lineStyle() &&
p1->lineDashes() == p2->lineDashes() &&
p1->dashOffset() == p2->dashOffset();
}
};
struct CheckShapeStrokeCapJoinPolicy : public CheckShapeStrokeStyleBasePolicy
{
static bool compareTo(PointerType p1, PointerType p2) {
return p1->capStyle() == p2->capStyle() &&
p1->joinStyle() == p2->joinStyle() &&
p1->miterLimit() == p2->miterLimit();
}
};
struct CheckShapeStrokeWidthPolicy : public CheckShapeStrokeStyleBasePolicy
{
static bool compareTo(PointerType p1, PointerType p2) {
return p1->lineWidth() == p2->lineWidth();
}
};
struct CheckShapeMarkerPolicy
{
CheckShapeMarkerPolicy(KoFlake::MarkerPosition position)
: m_position(position)
{
}
typedef KoMarker* PointerType;
PointerType getProperty(KoShape *shape) const {
KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
return pathShape ? pathShape->marker(m_position) : 0;
}
bool compareTo(PointerType p1, PointerType p2) const {
if ((!p1 || !p2) && p1 != p2) return false;
if (!p1 && p1 == p2) return true;
return p1 == p2 || *p1 == *p2;
}
KoFlake::MarkerPosition m_position;
};
void KoStrokeConfigWidget::selectionChanged()
{
if (d->noSelectionTrackingMode) return;
KoSelection *selection = d->canvas->selectedShapesProxy()->selection();
if (!selection) return;
// we need to linearize update order, and force the child widget to update
// before we start doing it
QList<KoShape*> shapes = selection->selectedEditableShapes();
d->fillConfigWidget->forceUpdateOnSelectionChanged(); // calls shapeChanged() logic
KoShape *shape = !shapes.isEmpty() ? shapes.first() : 0;
const KoShapeStrokeSP stroke = shape ? qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) : KoShapeStrokeSP();
// setUnit uses blockChildSignals() so take care not to use it inside the block
setUnit(d->canvas->unit(), shape);
blockChildSignals(true);
// line width
if (stroke && KoFlake::compareShapePropertiesEqual<CheckShapeStrokeWidthPolicy>(shapes)) {
d->ui->lineWidth->changeValue(stroke->lineWidth());
} else {
d->ui->lineWidth->changeValue(0);
}
// caps & joins
if (stroke && KoFlake::compareShapePropertiesEqual<CheckShapeStrokeCapJoinPolicy>(shapes)) {
Qt::PenCapStyle capStyle = stroke->capStyle() >= 0 ? stroke->capStyle() : Qt::FlatCap;
Qt::PenJoinStyle joinStyle = stroke->joinStyle() >= 0 ? stroke->joinStyle() : Qt::MiterJoin;
{
QAbstractButton *button = d->capNJoinMenu->capGroup->button(capStyle);
KIS_SAFE_ASSERT_RECOVER_RETURN(button);
button->setChecked(true);
}
{
QAbstractButton *button = d->capNJoinMenu->joinGroup->button(joinStyle);
KIS_SAFE_ASSERT_RECOVER_RETURN(button);
button->setChecked(true);
}
d->capNJoinMenu->miterLimit->changeValue(stroke->miterLimit());
d->capNJoinMenu->miterLimit->setEnabled(joinStyle == Qt::MiterJoin);
} else {
d->capNJoinMenu->capGroup->button(Qt::FlatCap)->setChecked(true);
d->capNJoinMenu->joinGroup->button(Qt::MiterJoin)->setChecked(true);
d->capNJoinMenu->miterLimit->changeValue(0.0);
d->capNJoinMenu->miterLimit->setEnabled(true);
}
// dashes style
if (stroke && KoFlake::compareShapePropertiesEqual<CheckShapeStrokeDashesPolicy>(shapes)) {
d->ui->lineStyle->setLineStyle(stroke->lineStyle(), stroke->lineDashes());
} else {
d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector<qreal>());
}
// markers
KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
if (pathShape) {
if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::StartMarker))) {
d->startMarkerSelector->setMarker(pathShape->marker(KoFlake::StartMarker));
}
if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::MidMarker))) {
d->midMarkerSelector->setMarker(pathShape->marker(KoFlake::MidMarker));
}
if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::EndMarker))) {
d->endMarkerSelector->setMarker(pathShape->marker(KoFlake::EndMarker));
}
}
const bool lineOptionsVisible = (d->fillConfigWidget->selectedFillIndex() != 0);
// This switch statement is to help the tab widget "pages" to be closer to the correct size
// if we don't do this the internal widgets get rendered, then the tab page has to get resized to
// fill up the space, then the internal widgets have to resize yet again...causing flicker
switch(d->fillConfigWidget->selectedFillIndex()) {
case 0: // no fill
this->setMinimumHeight(130);
break;
case 1: // solid fill
this->setMinimumHeight(200);
break;
case 2: // gradient fill
this->setMinimumHeight(350);
case 3: // pattern fill
break;
}
d->ui->thicknessLineBreak->setVisible(lineOptionsVisible);
d->ui->lineWidth->setVisible(lineOptionsVisible);
d->ui->capNJoinButton->setVisible(lineOptionsVisible);
d->ui->lineStyle->setVisible(lineOptionsVisible);
d->startMarkerSelector->setVisible(lineOptionsVisible);
d->midMarkerSelector->setVisible(lineOptionsVisible);
d->endMarkerSelector->setVisible(lineOptionsVisible);
d->ui->thicknessLabel->setVisible(lineOptionsVisible);
d->ui->strokeStyleLabel->setVisible(lineOptionsVisible);
blockChildSignals(false);
updateStyleControlsAvailability(!shapes.isEmpty());
}
void KoStrokeConfigWidget::canvasResourceChanged(int key, const QVariant &value)
{
switch (key) {
case KoCanvasResourceProvider::Unit:
// we request the whole selection to reload because the
// unit of the stroke width depends on the selected shape
d->selectionChangedCompressor.start();
break;
case KisCanvasResourceProvider::Size:
if (d->noSelectionTrackingMode) {
d->ui->lineWidth->changeValue(d->canvas->unit().fromUserValue(value.toReal()));
}
break;
}
}
void KoStrokeConfigWidget::loadCurrentStrokeFillFromResourceServer()
{
if (d->canvas) {
const QVariant value = d->canvas->resourceManager()->resource(KisCanvasResourceProvider::Size);
canvasResourceChanged(KisCanvasResourceProvider::Size, value);
updateStyleControlsAvailability(true);
emit sigStrokeChanged();
}
}
diff --git a/libs/ui/widgets/kis_advanced_color_space_selector.cc b/libs/ui/widgets/kis_advanced_color_space_selector.cc
index 2550e89840..3fbc2b1ada 100644
--- a/libs/ui/widgets/kis_advanced_color_space_selector.cc
+++ b/libs/ui/widgets/kis_advanced_color_space_selector.cc
@@ -1,795 +1,795 @@
/*
* 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 <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(KoID)),
this, SLOT(fillCmbDepths(KoID)));
connect(d->colorSpaceSelector->cmbColorDepth, SIGNAL(activated(KoID)),
this, SLOT(fillLstProfiles()));
connect(d->colorSpaceSelector->cmbColorModels, SIGNAL(activated(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 grayscale 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 number of colors per channel. Each channel will have 256 values available, "
"leading to a total amount of colors of 256 to the power of the number 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>"));
+ "<a href=\"https://ninedegreesbelow.com\">Elle Stone</a>. This is a summary. Please check "
+ "<a href=\"https://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\">"
+ "<a href=\"https://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><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 I recommend the Rec.2020 or ACEScg profiles over "
"ProPhotoRGB. But if you have an already well-established workflow using ProPhotoRGB, you "
"might find a shift to another RGB working space a little odd, at least at first, and so you "
"have to weight the pros and cons of changing your workflow.</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("-rec709")) {
d->colorSpaceSelector->textProfileDescription->append(i18nc("From Elle's notes.",
"<p>The profiles that end in '-srgbtrc.icc', '-g22.icc', and '-rec709.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) (average/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 Fluorescent)
//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(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_cmb_gradient.cpp b/libs/ui/widgets/kis_cmb_gradient.cpp
index f46b3f39a1..e98cb40be0 100644
--- a/libs/ui/widgets/kis_cmb_gradient.cpp
+++ b/libs/ui/widgets/kis_cmb_gradient.cpp
@@ -1,78 +1,77 @@
/*
* 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 "kis_cmb_gradient.h"
#include <QPainter>
#include <QResizeEvent>
#include <QStyleOptionComboBox>
#include <QApplication>
#include <KoCheckerBoardPainter.h>
#include <resources/KoResource.h>
#include <resources/KoAbstractGradient.h>
#include "kis_gradient_chooser.h"
KisCmbGradient::KisCmbGradient(QWidget *parent)
: KisPopupButton(parent)
, m_gradientChooser(new KisGradientChooser(this))
{
connect(m_gradientChooser, SIGNAL(resourceSelected(KoResource*)), SLOT(gradientSelected(KoResource*)));
setPopupWidget(m_gradientChooser);
}
void KisCmbGradient::setGradient(KoAbstractGradient *gradient)
{
m_gradientChooser->setCurrentResource(gradient);
}
KoAbstractGradient *KisCmbGradient::gradient() const
{
return dynamic_cast<KoAbstractGradient*>(m_gradientChooser->currentResource());
}
void KisCmbGradient::gradientSelected(KoResource *resource)
{
KoAbstractGradient *gradient = dynamic_cast<KoAbstractGradient*>(resource);
if (!gradient) return;
QImage pm = gradient->generatePreview(iconSize().width(), iconSize().height());
setIcon(QIcon(QPixmap::fromImage(pm)));
emit gradientChanged(gradient);
}
QSize KisCmbGradient::sizeHint() const
{
ensurePolished();
QFontMetrics fm = fontMetrics();
int maxW = 7 * fm.width(QChar('x')) + 18;
int maxH = qMax(fm.lineSpacing(), 14) + 2;
QStyleOptionComboBox options;
options.initFrom(this);
- return style()->sizeFromContents(QStyle::CT_ComboBox, &options,
- QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut());
+ return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this);
}
void KisCmbGradient::resizeEvent(QResizeEvent *event)
{
setIconSize(QSize(event->size().width() - 30, event->size().height() - 4));
KisPopupButton::resizeEvent(event);
}
diff --git a/libs/ui/widgets/kis_color_filter_combo.cpp b/libs/ui/widgets/kis_color_filter_combo.cpp
index 518d2e8eb7..224054391f 100644
--- a/libs/ui/widgets/kis_color_filter_combo.cpp
+++ b/libs/ui/widgets/kis_color_filter_combo.cpp
@@ -1,348 +1,371 @@
/*
* 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_color_filter_combo.h"
#include <klocalizedstring.h>
#include <QStylePainter>
#include <QtCore/qmath.h>
#include <QApplication>
+#include <QProxyStyle>
#include <QStyleOption>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QListView>
#include <QMouseEvent>
#include "kis_node_view_color_scheme.h"
#include "kis_debug.h"
#include "kis_icon_utils.h"
#include "krita_utils.h"
#include "kis_node.h"
enum AdditionalRoles {
OriginalLabelIndex = Qt::UserRole + 1000
};
struct LabelFilteringModel : public QSortFilterProxyModel
{
LabelFilteringModel(QObject *parent) : QSortFilterProxyModel(parent) {}
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override {
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
const int labelIndex = index.data(OriginalLabelIndex).toInt();
return labelIndex < 0 || m_acceptedLabels.contains(labelIndex);
}
void setAcceptedLabels(const QSet<int> &value) {
m_acceptedLabels = value;
invalidateFilter();
}
private:
QSet<int> m_acceptedLabels;
};
class ComboEventFilter : public QObject
{
public:
ComboEventFilter(KisColorFilterCombo *parent) : m_parent(parent), m_buttonPressed(false) {}
protected:
bool eventFilter(QObject *obj, QEvent *event) override {
if (event->type() == QEvent::Leave) {
m_buttonPressed = false;
} else if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
m_buttonPressed = mevent->button() == Qt::LeftButton;
} else if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
QModelIndex index = m_parent->view()->indexAt(mevent->pos());
if (!index.isValid()) return false;
/**
* We should eat the first event that arrives exactly when
* the drop down appears on screen.
*/
if (!m_buttonPressed) return true;
const bool toUncheckedState = index.data(Qt::CheckStateRole) == Qt::Checked;
if (toUncheckedState) {
m_parent->model()->setData(index, Qt::Unchecked, Qt::CheckStateRole);
} else {
m_parent->model()->setData(index, Qt::Checked, Qt::CheckStateRole);
}
if (index.data(OriginalLabelIndex).toInt() == -1) {
for (int i = 0; i < m_parent->model()->rowCount(); i++) {
const QModelIndex &other = m_parent->model()->index(i, 0);
if (other.data(OriginalLabelIndex) != -1) {
m_parent->model()->setData(other, toUncheckedState ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole);
}
}
} else {
bool prevChecked = false;
bool checkedVaries = false;
QModelIndex allLabelsIndex;
for (int i = 0; i < m_parent->model()->rowCount(); i++) {
const QModelIndex &other = m_parent->model()->index(i, 0);
if (other.data(OriginalLabelIndex) != -1) {
const bool currentChecked = other.data(Qt::CheckStateRole) == Qt::Checked;
if (i == 0) {
prevChecked = currentChecked;
} else {
if (prevChecked != currentChecked) {
checkedVaries = true;
break;
}
}
} else {
allLabelsIndex = other;
}
}
const bool allLabelsIndexShouldBeChecked =
prevChecked && !checkedVaries;
if (allLabelsIndexShouldBeChecked !=
(allLabelsIndex.data(Qt::CheckStateRole) == Qt::Checked)) {
m_parent->model()->setData(allLabelsIndex, allLabelsIndexShouldBeChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
}
}
emit m_parent->selectedColorsChanged();
m_buttonPressed = false;
return true;
}
return QObject::eventFilter(obj, event);
}
private:
KisColorFilterCombo *m_parent;
bool m_buttonPressed;
};
class FullSizedListView : public QListView
{
public:
QSize sizeHint() const override {
return contentsSize();
}
};
+class PopupComboBoxStyle : public QProxyStyle
+{
+public:
+ PopupComboBoxStyle(QStyle *baseStyle = nullptr) : QProxyStyle(baseStyle) {}
+
+ int styleHint(QStyle::StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const override
+ {
+ // This flag makes ComboBox popup float ontop of its parent ComboBox, like in Fusion style.
+ // Only when this hint is set will Qt respect combobox popup size hints, otherwise the popup
+ // can never exceed the width of its parent ComboBox, like in Breeze style.
+ if (hint == QStyle::SH_ComboBox_Popup) {
+ return true;
+ }
+
+ return QProxyStyle::styleHint(hint, option, widget, returnData);
+ }
+};
+
struct KisColorFilterCombo::Private
{
LabelFilteringModel *filteringModel;
};
KisColorFilterCombo::KisColorFilterCombo(QWidget *parent)
: QComboBox(parent),
m_d(new Private)
{
QStandardItemModel *newModel = new QStandardItemModel(this);
setModel(newModel);
+ PopupComboBoxStyle *proxyStyle = new PopupComboBoxStyle(style());
+ proxyStyle->setParent(this);
+ setStyle(proxyStyle);
+
setView(new FullSizedListView);
m_eventFilters.append(new ComboEventFilter(this));
m_eventFilters.append(new ComboEventFilter(this));
view()->installEventFilter(m_eventFilters[0]);
view()->viewport()->installEventFilter(m_eventFilters[1]);
KisNodeViewColorScheme scm;
QStandardItem* item = new QStandardItem(i18nc("combo box: show all layers", "All"));
item->setCheckable(true);
item->setCheckState(Qt::Unchecked);
item->setData(QColor(Qt::transparent), Qt::BackgroundColorRole);
item->setData(int(-1), OriginalLabelIndex);
item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole);
newModel->appendRow(item);
int labelIndex = 0;
foreach (const QColor &color, scm.allColorLabels()) {
const QString title = color.alpha() > 0 ? "" : i18nc("combo box: select all layers without a label", "No Label");
QStandardItem* item = new QStandardItem(title);
item->setCheckable(true);
item->setCheckState(Qt::Unchecked);
item->setData(color, Qt::BackgroundColorRole);
item->setData(labelIndex, OriginalLabelIndex);
item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole);
newModel->appendRow(item);
labelIndex++;
}
m_d->filteringModel = new LabelFilteringModel(this);
QAbstractItemModel *originalModel = model();
originalModel->setParent(m_d->filteringModel);
m_d->filteringModel->setSourceModel(originalModel);
setModel(m_d->filteringModel);
}
KisColorFilterCombo::~KisColorFilterCombo()
{
qDeleteAll(m_eventFilters);
}
void collectAvailableLabels(KisNodeSP root, QSet<int> *labels)
{
labels->insert(root->colorLabelIndex());
KisNodeSP node = root->firstChild();
while (node) {
collectAvailableLabels(node, labels);
node = node->nextSibling();
}
}
void KisColorFilterCombo::updateAvailableLabels(KisNodeSP rootNode)
{
QSet<int> labels;
collectAvailableLabels(rootNode, &labels);
updateAvailableLabels(labels);
}
void KisColorFilterCombo::updateAvailableLabels(const QSet<int> &labels)
{
m_d->filteringModel->setAcceptedLabels(labels);
}
QList<int> KisColorFilterCombo::selectedColors() const
{
QList<int> colors;
for (int i = 0; i < model()->rowCount(); i++) {
const QModelIndex &other = model()->index(i, 0);
const int label = other.data(OriginalLabelIndex).toInt();
if (label != -1 &&
other.data(Qt::CheckStateRole) == Qt::Checked) {
colors << label;
}
}
return colors;
}
void KisColorFilterCombo::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));
// draw the combobox frame, focusrect and selected etc.
QStyleOptionComboBox opt;
initStyleOption(&opt);
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
{
KisNodeViewColorScheme scm;
const QRect editRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this);
const int size = qMin(editRect.width(), editRect.height());
const QList<int> selectedColors = this->selectedColors();
if (selectedColors.size() == 0 || selectedColors.size() == model()->rowCount() - 1) {
QIcon icon = KisIconUtils::loadIcon("view-filter");
QPixmap pixmap = icon.pixmap(QSize(size, size), !isEnabled() ? QIcon::Disabled : QIcon::Normal);
painter.drawPixmap(editRect.right() - size, editRect.top(), pixmap);
} else if (selectedColors.size() == 1) {
const int currentLabel = selectedColors.first();
QColor currentColor = scm.colorLabel(currentLabel);
if (currentColor.alpha() > 0) {
painter.fillRect(editRect, currentColor);
} else if (currentLabel == 0) {
QPen oldPen = painter.pen();
const int border = 4;
QRect crossRect(0, 0, size - 2 * border, size - 2 * border);
crossRect.moveCenter(editRect.center());
QColor shade = opt.palette.dark().color();
painter.setPen(QPen(shade, 2));
painter.drawLine(crossRect.topLeft(), crossRect.bottomRight());
painter.drawLine(crossRect.bottomLeft(), crossRect.topRight());
}
} else {
const int border = 0;
QRect pieRect(0, 0, size - 2 * border, size - 2 * border);
pieRect.moveCenter(editRect.center());
const int numColors = selectedColors.size();
const int step = 16 * 360 / numColors;
int currentAngle = 0;
//painter.save(); // optimize out!
painter.setRenderHint(QPainter::Antialiasing);
for (int i = 0; i < numColors; i++) {
QColor color = scm.colorLabel(selectedColors[i]);
QBrush brush = color.alpha() > 0 ? QBrush(color) : QBrush(Qt::black, Qt::Dense4Pattern);
painter.setPen(color);
painter.setBrush(brush);
painter.drawPie(pieRect, currentAngle, step);
currentAngle += step;
}
//painter.restore(); // optimize out!
}
}
// draw the icon and text
//painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
}
QSize KisColorFilterCombo::minimumSizeHint() const
{
return sizeHint();
}
QSize KisColorFilterCombo::sizeHint() const
{
QStyleOptionComboBox opt;
initStyleOption(&opt);
const QStyleOption *baseOption = qstyleoption_cast<const QStyleOption *>(&opt);
const int arrowSize = style()->pixelMetric(QStyle::PM_ScrollBarExtent, baseOption, this);
const QSize originalHint = QComboBox::sizeHint();
QSize sh(3 * arrowSize, originalHint.height());
- return sh.expandedTo(QApplication::globalStrut());
+ return sh;
}
diff --git a/libs/ui/widgets/kis_curve_widget.h b/libs/ui/widgets/kis_curve_widget.h
index 5ffca8bdf8..5e0c644434 100644
--- a/libs/ui/widgets/kis_curve_widget.h
+++ b/libs/ui/widgets/kis_curve_widget.h
@@ -1,178 +1,178 @@
/*
* 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)
+ * (From: https://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::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();
/**
* Emitted to notify that the start() function in compressor can be activated.
* Thanks to that, blocking signals in curve widget blocks "sending signals"
* (calling start() function) *to* the signal compressor.
* It effectively makes signals work nearly the same way they worked before
* adding the signal compressor in between.
*/
void compressorShouldEmitModified();
protected Q_SLOTS:
void inOutChanged(int);
void notifyModified();
/**
* This function is called when compressorShouldEmitModified() is emitted.
* For why it's needed, \see compressorShouldEmitModified()
*/
void slotCompressorShouldEmitModified();
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 @p inlist.
*/
void setCurve(KisCubicCurve inlist);
/**
* Connect/disconnect external spinboxes to the curve
* @p inMin / @p inMax - is the range for input values
* @p outMin / @p outMax - is the range for output values
*/
void setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax);
void dropInOutControls();
/**
* Handy function that creates new point in the middle
* of the curve and sets focus on the @p 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_filter_selector_widget.cc b/libs/ui/widgets/kis_filter_selector_widget.cc
index dcf73c9f14..68717dc03d 100644
--- a/libs/ui/widgets/kis_filter_selector_widget.cc
+++ b/libs/ui/widgets/kis_filter_selector_widget.cc
@@ -1,363 +1,367 @@
/*
* Copyright (c) 2007-2008 Cyrille Berger <cberger@cberger.net>
* 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.
*/
#include "kis_filter_selector_widget.h"
#include <QHeaderView>
#include <QTreeView>
#include <QLabel>
#include <QComboBox>
#include <QPushButton>
#include <QScrollArea>
#include <QLayout>
#include <QDialogButtonBox>
#include <QPlainTextEdit>
#include <QDomDocument>
#include <QDomElement>
#include "ui_wdgfilterselector.h"
#include <kis_layer.h>
#include <kis_paint_device.h>
#include <filter/kis_filter.h>
#include <kis_config_widget.h>
#include <filter/kis_filter_configuration.h>
#include "kis_default_bounds.h"
#include <KisKineticScroller.h>
// From krita/ui
#include "kis_bookmarked_configurations_editor.h"
#include "kis_bookmarked_filter_configurations_model.h"
#include "kis_filters_model.h"
#include "kis_config.h"
class ThumbnailBounds : public KisDefaultBounds {
public:
ThumbnailBounds() : KisDefaultBounds() {}
~ThumbnailBounds() override {}
QRect bounds() const override
{
return QRect(0, 0, 100, 100);
}
private:
Q_DISABLE_COPY(ThumbnailBounds)
};
struct KisFilterSelectorWidget::Private {
QWidget *currentCentralWidget {0};
KisConfigWidget *currentFilterConfigurationWidget {0};
KisFilterSP currentFilter;
KisPaintDeviceSP paintDevice;
Ui_FilterSelector uiFilterSelector;
KisPaintDeviceSP thumb;
KisBookmarkedFilterConfigurationsModel *currentBookmarkedFilterConfigurationsModel {0};
KisFiltersModel *filtersModel {};
QGridLayout *widgetLayout {};
KisViewManager *view{};
bool showFilterGallery {true};
bool usedForMask {false};
};
KisFilterSelectorWidget::KisFilterSelectorWidget(QWidget* parent)
: d(new Private)
{
Q_UNUSED(parent);
setObjectName("KisFilterSelectorWidget");
d->uiFilterSelector.setupUi(this);
d->widgetLayout = new QGridLayout(d->uiFilterSelector.centralWidgetHolder);
d->widgetLayout->setContentsMargins(0,0,0,0);
d->widgetLayout->setHorizontalSpacing(0);
showFilterGallery(false);
connect(d->uiFilterSelector.filtersSelector, SIGNAL(clicked(QModelIndex)), SLOT(setFilterIndex(QModelIndex)));
connect(d->uiFilterSelector.filtersSelector, SIGNAL(activated(QModelIndex)), SLOT(setFilterIndex(QModelIndex)));
connect(d->uiFilterSelector.comboBoxPresets, SIGNAL(activated(int)),SLOT(slotBookmarkedFilterConfigurationSelected(int)));
connect(d->uiFilterSelector.pushButtonEditPressets, SIGNAL(pressed()), SLOT(editConfigurations()));
connect(d->uiFilterSelector.btnXML, SIGNAL(clicked()), this, SLOT(showXMLdialog()));
KisConfig cfg(true);
d->uiFilterSelector.chkRememberPreset->setChecked(cfg.readEntry<bool>("filterdialog/rememberlastpreset", false));
}
KisFilterSelectorWidget::~KisFilterSelectorWidget()
{
KisConfig cfg(false);
cfg.writeEntry<bool>("filterdialog/rememberlastpreset", d->uiFilterSelector.chkRememberPreset->isChecked());
delete d->filtersModel;
delete d->currentBookmarkedFilterConfigurationsModel;
delete d->currentCentralWidget;
delete d->widgetLayout;
delete d;
}
void KisFilterSelectorWidget::setView(KisViewManager *view)
{
d->view = view;
}
void KisFilterSelectorWidget::setPaintDevice(bool showAll, KisPaintDeviceSP _paintDevice)
{
if (!_paintDevice) return;
if (d->filtersModel) delete d->filtersModel;
d->usedForMask = !showAll;
d->paintDevice = _paintDevice;
d->thumb = d->paintDevice->createThumbnailDevice(100, 100);
d->thumb->setDefaultBounds(new ThumbnailBounds());
d->filtersModel = new KisFiltersModel(showAll, d->thumb);
d->uiFilterSelector.filtersSelector->setFilterModel(d->filtersModel);
d->uiFilterSelector.filtersSelector->header()->setVisible(false);
KisConfig cfg(true);
QModelIndex idx = d->filtersModel->indexForFilter(cfg.readEntry<QString>("FilterSelector/LastUsedFilter", "levels"));
if (!idx.isValid()) {
idx = d->filtersModel->indexForFilter("levels");
}
- if (isFilterGalleryVisible()) {
+ if (d->usedForMask && isFilterGalleryVisible()) {
d->uiFilterSelector.filtersSelector->activateFilter(idx);
}
}
void KisFilterSelectorWidget::showFilterGallery(bool visible)
{
if (d->showFilterGallery == visible) {
return;
}
d->showFilterGallery = visible;
update();
emit sigFilterGalleryToggled(visible);
emit sigSizeChanged();
}
void KisFilterSelectorWidget::showXMLdialog()
{
if (currentFilter()->showConfigurationWidget()) {
QDialog *xmlDialog = new QDialog();
xmlDialog->setMinimumWidth(500);
xmlDialog->setWindowTitle(i18n("Filter configuration XML"));
QVBoxLayout *xmllayout = new QVBoxLayout(xmlDialog);
QPlainTextEdit *text = new QPlainTextEdit(xmlDialog);
KisFilterConfigurationSP config = configuration();
text->setPlainText(config->toXML());
xmllayout->addWidget(text);
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, xmlDialog);
connect(buttons, SIGNAL(accepted()), xmlDialog, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), xmlDialog, SLOT(reject()));
xmllayout->addWidget(buttons);
if (xmlDialog->exec()==QDialog::Accepted) {
QDomDocument doc;
doc.setContent(text->toPlainText());
config->fromXML(doc.documentElement());
if (config) {
d->currentFilterConfigurationWidget->setConfiguration(config);
}
}
}
}
bool KisFilterSelectorWidget::isFilterGalleryVisible() const
{
return d->showFilterGallery;
}
KisFilterSP KisFilterSelectorWidget::currentFilter() const
{
return d->currentFilter;
}
void KisFilterSelectorWidget::setFilter(KisFilterSP f)
{
Q_ASSERT(f);
Q_ASSERT(d->filtersModel);
setWindowTitle(f->name());
dbgKrita << "setFilter: " << f;
d->currentFilter = f;
delete d->currentCentralWidget;
{
bool v = d->uiFilterSelector.filtersSelector->blockSignals(true);
d->uiFilterSelector.filtersSelector->setCurrentIndex(d->filtersModel->indexForFilter(f->id()));
d->uiFilterSelector.filtersSelector->blockSignals(v);
}
KisConfigWidget* widget =
d->currentFilter->createConfigurationWidget(d->uiFilterSelector.centralWidgetHolder, d->paintDevice, d->usedForMask);
if (!widget) { // No widget, so display a label instead
d->uiFilterSelector.comboBoxPresets->setEnabled(false);
d->uiFilterSelector.pushButtonEditPressets->setEnabled(false);
d->uiFilterSelector.btnXML->setEnabled(false);
d->currentFilterConfigurationWidget = 0;
d->currentCentralWidget = new QLabel(i18n("No configuration options"),
d->uiFilterSelector.centralWidgetHolder);
d->uiFilterSelector.scrollArea->setMinimumSize(d->currentCentralWidget->sizeHint());
qobject_cast<QLabel*>(d->currentCentralWidget)->setAlignment(Qt::AlignCenter);
} else {
d->uiFilterSelector.comboBoxPresets->setEnabled(true);
d->uiFilterSelector.pushButtonEditPressets->setEnabled(true);
d->uiFilterSelector.btnXML->setEnabled(true);
d->currentFilterConfigurationWidget = widget;
d->currentCentralWidget = widget;
widget->layout()->setContentsMargins(0,0,0,0);
d->currentFilterConfigurationWidget->setView(d->view);
d->currentFilterConfigurationWidget->blockSignals(true);
d->currentFilterConfigurationWidget->setConfiguration(d->currentFilter->defaultConfiguration());
d->currentFilterConfigurationWidget->blockSignals(false);
d->uiFilterSelector.scrollArea->setContentsMargins(0,0,0,0);
d->uiFilterSelector.scrollArea->setMinimumWidth(widget->sizeHint().width() + 18);
connect(d->currentFilterConfigurationWidget, SIGNAL(sigConfigurationUpdated()), this, SIGNAL(configurationChanged()));
}
// Change the list of presets
delete d->currentBookmarkedFilterConfigurationsModel;
d->currentBookmarkedFilterConfigurationsModel = new KisBookmarkedFilterConfigurationsModel(d->thumb, f);
d->uiFilterSelector.comboBoxPresets->setModel(d->currentBookmarkedFilterConfigurationsModel);
// Add the widget to the layout
d->currentCentralWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
d->widgetLayout->addWidget(d->currentCentralWidget, 0 , 0);
if (d->uiFilterSelector.chkRememberPreset->isChecked()) {
int lastBookmarkedFilterConfiguration = KisConfig(true).readEntry<int>("lastBookmarkedFilterConfiguration/" + f->id(), 0);
if (d->uiFilterSelector.comboBoxPresets->count() > lastBookmarkedFilterConfiguration) {
d->uiFilterSelector.comboBoxPresets->setCurrentIndex(lastBookmarkedFilterConfiguration);
slotBookmarkedFilterConfigurationSelected(lastBookmarkedFilterConfiguration);
}
}
update();
}
void KisFilterSelectorWidget::setFilterIndex(const QModelIndex& idx)
{
if (!idx.isValid()) return;
Q_ASSERT(d->filtersModel);
KisFilter* filter = const_cast<KisFilter*>(d->filtersModel->indexToFilter(idx));
if (filter) {
setFilter(filter);
}
else {
if (d->currentFilter) {
bool v = d->uiFilterSelector.filtersSelector->blockSignals(true);
QModelIndex idx = d->filtersModel->indexForFilter(d->currentFilter->id());
d->uiFilterSelector.filtersSelector->setCurrentIndex(idx);
d->uiFilterSelector.filtersSelector->scrollTo(idx);
d->uiFilterSelector.filtersSelector->blockSignals(v);
}
}
+ slotBookMarkCurrentFilter();
+ emit(configurationChanged());
+ }
+
+ void KisFilterSelectorWidget::slotBookMarkCurrentFilter() {
KisConfig cfg(false);
cfg.writeEntry<QString>("FilterSelector/LastUsedFilter", d->currentFilter->id());
- emit(configurationChanged());
}
void KisFilterSelectorWidget::slotBookmarkedFilterConfigurationSelected(int index)
{
if (d->currentFilterConfigurationWidget) {
QModelIndex modelIndex = d->currentBookmarkedFilterConfigurationsModel->index(index, 0);
KisFilterConfigurationSP config = d->currentBookmarkedFilterConfigurationsModel->configuration(modelIndex);
d->currentFilterConfigurationWidget->setConfiguration(config);
if (d->currentFilter && index != KisConfig(true).readEntry<int>("lastBookmarkedFilterConfiguration/" + d->currentFilter->id(), 0)) {
KisConfig(false).writeEntry<int>("lastBookmarkedFilterConfiguration/" + d->currentFilter->id(), index);
}
}
}
void KisFilterSelectorWidget::editConfigurations()
{
KisSerializableConfigurationSP config =
d->currentFilterConfigurationWidget ? d->currentFilterConfigurationWidget->configuration() : 0;
KisBookmarkedConfigurationsEditor editor(this, d->currentBookmarkedFilterConfigurationsModel, config);
editor.exec();
}
void KisFilterSelectorWidget::update()
{
d->uiFilterSelector.filtersSelector->setVisible(d->showFilterGallery);
if (d->showFilterGallery) {
setMinimumWidth(qMax(sizeHint().width(), 700));
d->uiFilterSelector.scrollArea->setMinimumHeight(400);
setMinimumHeight(d->uiFilterSelector.verticalLayout->sizeHint().height());
if (d->currentFilter) {
bool v = d->uiFilterSelector.filtersSelector->blockSignals(true);
d->uiFilterSelector.filtersSelector->setCurrentIndex(d->filtersModel->indexForFilter(d->currentFilter->id()));
d->uiFilterSelector.filtersSelector->blockSignals(v);
}
}
else {
if (d->currentCentralWidget) {
d->uiFilterSelector.scrollArea->setMinimumHeight(qMin(400, d->currentCentralWidget->sizeHint().height()));
}
setMinimumSize(d->uiFilterSelector.verticalLayout->sizeHint());
}
}
KisFilterConfigurationSP KisFilterSelectorWidget::configuration()
{
if (d->currentFilterConfigurationWidget) {
KisFilterConfigurationSP config = dynamic_cast<KisFilterConfiguration*>(d->currentFilterConfigurationWidget->configuration().data());
if (config) {
return config;
}
} else if (d->currentFilter) {
return d->currentFilter->defaultConfiguration();
}
return 0;
}
void KisFilterTree::setFilterModel(QAbstractItemModel *model)
{
m_model = model;
}
void KisFilterTree::activateFilter(QModelIndex idx)
{
setModel(m_model);
selectionModel()->select(idx, QItemSelectionModel::SelectCurrent);
expand(idx);
scrollTo(idx);
emit activated(idx);
}
void KisFilterSelectorWidget::setVisible(bool visible)
{
QWidget::setVisible(visible);
if (visible) {
update();
}
}
diff --git a/libs/ui/widgets/kis_filter_selector_widget.h b/libs/ui/widgets/kis_filter_selector_widget.h
index d09cb19161..a8dd40a09d 100644
--- a/libs/ui/widgets/kis_filter_selector_widget.h
+++ b/libs/ui/widgets/kis_filter_selector_widget.h
@@ -1,153 +1,154 @@
/*
* Copyright (c) 2007-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.
*/
#ifndef _KIS_FILTER_SELECTOR_WIDGET_H_
#define _KIS_FILTER_SELECTOR_WIDGET_H_
#include <QWidget>
#include <QTreeView>
#include <QHeaderView>
#include <QResizeEvent>
#include <QSize>
#include <kis_debug.h>
#include <KisKineticScroller.h>
#include <kis_types.h>
class QModelIndex;
class KisFilterConfiguration;
class KisViewManager;
class QAbstractItemModel;
class QHideEvent;
class QShowEvent;
/**
* Widget for selecting the filter. This shows the widget if there is any.
*/
class KisFilterSelectorWidget : public QWidget
{
Q_OBJECT
public:
KisFilterSelectorWidget(QWidget* parent);
~KisFilterSelectorWidget() override;
void setFilter(KisFilterSP f);
void setView(KisViewManager *view);
void setPaintDevice(bool showAll, KisPaintDeviceSP);
KisFilterConfigurationSP configuration();
bool isFilterGalleryVisible() const;
KisFilterSP currentFilter() const;
public Q_SLOTS:
void setVisible(bool visible) override;
void showFilterGallery(bool visible);
protected Q_SLOTS:
void slotBookmarkedFilterConfigurationSelected(int);
+ void slotBookMarkCurrentFilter();
void setFilterIndex(const QModelIndex&);
void editConfigurations();
void update();
void showXMLdialog();
Q_SIGNALS:
void configurationChanged();
void sigFilterGalleryToggled(bool visible);
void sigSizeChanged();
private:
struct Private;
Private* const d;
};
class KisFilterTree: public QTreeView
{
Q_OBJECT
public:
KisFilterTree(QWidget *parent) : QTreeView(parent) {
QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this);
if (scroller) {
connect(scroller, SIGNAL(stateChanged(QScroller::State)),
this, SLOT(slotScrollerStateChanged(QScroller::State)));
}
connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(update_scroll_area(QModelIndex)));
connect(this, SIGNAL(collapsed(QModelIndex)), this, SLOT(update_scroll_area(QModelIndex)));
}
void setFilterModel(QAbstractItemModel * model);
void activateFilter(QModelIndex idx);
QSize minimumSizeHint() const override
{
return QSize(200, QTreeView::sizeHint().height());
}
QSize sizeHint() const override
{
return QSize(header()->width(), QTreeView::sizeHint().height());
}
void setModel(QAbstractItemModel *model) override
{
QTreeView::setModel(model);
if (header()->visualIndex(0) != -1) {
header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
}
}
protected:
void resizeEvent(QResizeEvent *event) override
{
if (event->size().width() > 10) {
setModel(m_model);
}
else {
setModel(0);
}
QTreeView::resizeEvent(event);
}
void showEvent(QShowEvent * event) override
{
setModel(m_model);
QTreeView::showEvent(event);
}
void hideEvent(QHideEvent * event) override
{
setModel(0);
QTreeView::hideEvent(event);
}
private Q_SLOTS:
void update_scroll_area(const QModelIndex& i)
{
resizeColumnToContents(i.column());
}
public Q_SLOTS:
void slotScrollerStateChanged(QScroller::State state){ KisKineticScroller::updateCursor(this, state); }
private:
QAbstractItemModel *m_model;
};
#endif
diff --git a/libs/ui/widgets/kis_slider_spin_box.cpp b/libs/ui/widgets/kis_slider_spin_box.cpp
index 8db1984887..5e36564366 100644
--- a/libs/ui/widgets/kis_slider_spin_box.cpp
+++ b/libs/ui/widgets/kis_slider_spin_box.cpp
@@ -1,1052 +1,1052 @@
/* This file is part of the KDE project
* Copyright (c) 2010 Justin Noel <justin@ics.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2015 Moritz Molch <kde@moritzmolch.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_slider_spin_box.h"
#include <math.h>
#include <QPainter>
#include <QStyle>
#include <QLineEdit>
#include <QApplication>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QIntValidator>
#include <QTimer>
#include <QtDebug>
#include <QDoubleSpinBox>
#include "kis_cursor.h"
#include "KisPart.h"
#include "input/kis_input_manager.h"
#include "kis_num_parser.h"
class KisAbstractSliderSpinBoxPrivate {
public:
enum Style {
STYLE_NOQUIRK,
STYLE_PLASTIQUE,
STYLE_BREEZE,
STYLE_FUSION,
};
QLineEdit* edit;
QDoubleValidator* validator;
bool upButtonDown;
bool downButtonDown;
int factor;
int fastSliderStep;
qreal slowFactor;
qreal shiftPercent;
bool shiftMode;
QString prefix;
QString suffix;
qreal exponentRatio;
int value;
int maximum;
int minimum;
int singleStep;
QSpinBox* dummySpinBox;
Style style;
bool blockUpdateSignalOnDrag;
bool isDragging;
bool parseInt;
};
KisAbstractSliderSpinBox::KisAbstractSliderSpinBox(QWidget* parent, KisAbstractSliderSpinBoxPrivate* _d)
: QWidget(parent)
, d_ptr(_d)
{
Q_D(KisAbstractSliderSpinBox);
QEvent e(QEvent::StyleChange);
changeEvent(&e);
d->upButtonDown = false;
d->downButtonDown = false;
d->edit = new QLineEdit(this);
d->edit->setFrame(false);
d->edit->setAlignment(Qt::AlignCenter);
d->edit->hide();
d->edit->setContentsMargins(0,0,0,0);
d->edit->installEventFilter(this);
//Make edit transparent
d->edit->setAutoFillBackground(false);
QPalette pal = d->edit->palette();
pal.setColor(QPalette::Base, Qt::transparent);
d->edit->setPalette(pal);
d->edit->setContextMenuPolicy(Qt::PreventContextMenu);
connect(d->edit, SIGNAL(editingFinished()), this, SLOT(editLostFocus()));
d->validator = new QDoubleValidator(d->edit);
d->value = 0;
d->minimum = 0;
d->maximum = 100;
d->factor = 1.0;
d->singleStep = 1;
d->fastSliderStep = 5;
d->slowFactor = 0.1;
d->shiftMode = false;
d->blockUpdateSignalOnDrag = false;
d->isDragging = false;
d->parseInt = false;
setExponentRatio(1.0);
setCursor(KisCursor::splitHCursor());
//Set sane defaults
setFocusPolicy(Qt::StrongFocus);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
//dummy needed to fix a bug in the polyester theme
d->dummySpinBox = new QSpinBox(this);
d->dummySpinBox->hide();
}
KisAbstractSliderSpinBox::~KisAbstractSliderSpinBox()
{
Q_D(KisAbstractSliderSpinBox);
delete d;
}
void KisAbstractSliderSpinBox::showEdit()
{
Q_D(KisAbstractSliderSpinBox);
if (d->edit->isVisible()) return;
if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE) {
d->edit->setGeometry(progressRect(spinBoxOptions()).adjusted(0,0,-2,0));
}
else {
d->edit->setGeometry(progressRect(spinBoxOptions()));
}
d->edit->setText(valueString());
d->edit->selectAll();
d->edit->show();
d->edit->setFocus(Qt::OtherFocusReason);
update();
}
void KisAbstractSliderSpinBox::hideEdit()
{
Q_D(KisAbstractSliderSpinBox);
d->edit->hide();
update();
}
void KisAbstractSliderSpinBox::paintEvent(QPaintEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
Q_UNUSED(e)
QPainter painter(this);
switch (d->style) {
case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION:
paintFusion(painter);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
paintPlastique(painter);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
paintBreeze(painter);
break;
default:
paint(painter);
break;
}
painter.end();
}
void KisAbstractSliderSpinBox::paint(QPainter &painter)
{
Q_D(KisAbstractSliderSpinBox);
//Create options to draw spin box parts
QStyleOptionSpinBox spinOpts = spinBoxOptions();
spinOpts.rect.adjust(0, 2, 0, -2);
//Draw "SpinBox".Clip off the area of the lineEdit to avoid double
//borders being drawn
painter.save();
painter.setClipping(true);
QRect eraseRect(QPoint(rect().x(), rect().y()),
QPoint(progressRect(spinOpts).right(), rect().bottom()));
painter.setClipRegion(QRegion(rect()).subtracted(eraseRect));
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
painter.setClipping(false);
painter.restore();
QStyleOptionProgressBar progressOpts = progressBarOptions();
progressOpts.rect.adjust(0, 2, 0, -2);
style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0);
//Draw focus if necessary
if (hasFocus() &&
d->edit->hasFocus()) {
QStyleOptionFocusRect focusOpts;
focusOpts.initFrom(this);
focusOpts.rect = progressOpts.rect;
focusOpts.backgroundColor = palette().color(QPalette::Window);
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this);
}
}
void KisAbstractSliderSpinBox::paintFusion(QPainter &painter)
{
Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
spinOpts.frame = true;
spinOpts.rect.adjust(0, -1, 0, 1);
//spinOpts.palette().setBrush(QPalette::Base, palette().highlight());
QStyleOptionProgressBar progressOpts = progressBarOptions();
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
painter.save();
QRect rect = progressOpts.rect.adjusted(1,2,-4,-2);
QRect leftRect;
int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0),
qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width();
if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) {
leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height());
} else if (progressIndicatorPos > rect.width()) {
painter.setPen(palette().highlightedText().color());
} else {
painter.setPen(palette().buttonText().color());
}
QRegion rightRect = rect;
rightRect = rightRect.subtracted(leftRect);
QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
textOption.setWrapMode(QTextOption::NoWrap);
if (!(d->edit && d->edit->isVisible())) {
painter.setClipRegion(rightRect);
painter.setClipping(true);
painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption);
painter.setClipping(false);
}
if (!leftRect.isNull()) {
painter.setClipRect(leftRect.adjusted(0, -1, 1, 1));
painter.setPen(palette().highlight().color());
painter.setBrush(palette().highlight());
spinOpts.palette.setBrush(QPalette::Base, palette().highlight());
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
if (!(d->edit && d->edit->isVisible())) {
painter.setPen(palette().highlightedText().color());
painter.setClipping(true);
painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption);
}
painter.setClipping(false);
}
painter.restore();
}
void KisAbstractSliderSpinBox::paintPlastique(QPainter &painter)
{
Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QStyleOptionProgressBar progressOpts = progressBarOptions();
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
painter.save();
QRect rect = progressOpts.rect.adjusted(2,0,-2,0);
QRect leftRect;
int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0),
qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width();
if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) {
leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height());
} else if (progressIndicatorPos > rect.width()) {
painter.setPen(palette().highlightedText().color());
} else {
painter.setPen(palette().buttonText().color());
}
QRegion rightRect = rect;
rightRect = rightRect.subtracted(leftRect);
QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
textOption.setWrapMode(QTextOption::NoWrap);
if (!(d->edit && d->edit->isVisible())) {
painter.setClipRegion(rightRect);
painter.setClipping(true);
painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption);
painter.setClipping(false);
}
if (!leftRect.isNull()) {
painter.setPen(palette().highlight().color());
painter.setBrush(palette().highlight());
painter.drawRect(leftRect.adjusted(0,0,0,-1));
if (!(d->edit && d->edit->isVisible())) {
painter.setPen(palette().highlightedText().color());
painter.setClipRect(leftRect.adjusted(0,0,1,0));
painter.setClipping(true);
painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption);
painter.setClipping(false);
}
}
painter.restore();
}
void KisAbstractSliderSpinBox::paintBreeze(QPainter &painter)
{
Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QStyleOptionProgressBar progressOpts = progressBarOptions();
QString valueText = progressOpts.text;
progressOpts.text = "";
progressOpts.rect.adjust(0, 1, 0, -1);
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, this);
style()->drawControl(QStyle::CE_ProgressBarGroove, &progressOpts, &painter, this);
painter.save();
QRect leftRect;
int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0),
qreal(progressOpts.maximum) - progressOpts.minimum) * progressOpts.rect.width();
if (progressIndicatorPos >= 0 && progressIndicatorPos <= progressOpts.rect.width()) {
leftRect = QRect(progressOpts.rect.left(), progressOpts.rect.top(), progressIndicatorPos, progressOpts.rect.height());
} else if (progressIndicatorPos > progressOpts.rect.width()) {
painter.setPen(palette().highlightedText().color());
} else {
painter.setPen(palette().buttonText().color());
}
QRegion rightRect = progressOpts.rect;
rightRect = rightRect.subtracted(leftRect);
painter.setClipRegion(rightRect);
QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
textOption.setWrapMode(QTextOption::NoWrap);
if (!(d->edit && d->edit->isVisible())) {
painter.drawText(progressOpts.rect, valueText, textOption);
}
if (!leftRect.isNull()) {
painter.setPen(palette().highlightedText().color());
painter.setClipRect(leftRect);
style()->drawControl(QStyle::CE_ProgressBarContents, &progressOpts, &painter, this);
if (!(d->edit && d->edit->isVisible())) {
painter.drawText(progressOpts.rect, valueText, textOption);
}
}
painter.restore();
}
void KisAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
//Depress buttons or highlight slider
//Also used to emulate mouse grab...
if (e->buttons() & Qt::LeftButton) {
if (upButtonRect(spinOpts).contains(e->pos())) {
d->upButtonDown = true;
} else if (downButtonRect(spinOpts).contains(e->pos())) {
d->downButtonDown = true;
}
} else if (e->buttons() & Qt::RightButton) {
showEdit();
}
update();
}
void KisAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
d->isDragging = false;
//Step up/down for buttons
//Emualting mouse grab too
if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown) {
setInternalValue(d->value + d->singleStep);
} else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown) {
setInternalValue(d->value - d->singleStep);
} else if (progressRect(spinOpts).contains(e->pos()) &&
!(d->edit->isVisible()) &&
!(d->upButtonDown || d->downButtonDown)) {
//Snap to percentage for progress area
setInternalValue(valueForX(e->pos().x(),e->modifiers()));
} else { // Confirm the last known value, since we might be ignoring move events
setInternalValue(d->value);
}
d->upButtonDown = false;
d->downButtonDown = false;
update();
}
void KisAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
if( e->modifiers() & Qt::ShiftModifier ) {
if( !d->shiftMode ) {
d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) );
d->shiftMode = true;
}
} else {
d->shiftMode = false;
}
//Respect emulated mouse grab.
if (e->buttons() & Qt::LeftButton &&
!(d->downButtonDown || d->upButtonDown)) {
d->isDragging = true;
setInternalValue(valueForX(e->pos().x(),e->modifiers()), d->blockUpdateSignalOnDrag);
update();
}
}
void KisAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
switch (e->key()) {
case Qt::Key_Up:
case Qt::Key_Right:
setInternalValue(d->value + d->singleStep);
break;
case Qt::Key_Down:
case Qt::Key_Left:
setInternalValue(d->value - d->singleStep);
break;
case Qt::Key_Shift:
d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) );
d->shiftMode = true;
break;
case Qt::Key_Enter: //Line edit isn't "accepting" key strokes...
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Control:
case Qt::Key_Alt:
case Qt::Key_AltGr:
case Qt::Key_Super_L:
case Qt::Key_Super_R:
break;
default:
showEdit();
d->edit->event(e);
break;
}
}
void KisAbstractSliderSpinBox::wheelEvent(QWheelEvent *e)
{
Q_D(KisAbstractSliderSpinBox);
if ( e->delta() > 0) {
setInternalValue(d->value + d->singleStep);
} else {
setInternalValue(d->value - d->singleStep);
}
update();
e->accept();
}
bool KisAbstractSliderSpinBox::event(QEvent *event)
{
if (event->type() == QEvent::ShortcutOverride){
QKeyEvent* key = static_cast<QKeyEvent*>(event);
if (key->modifiers() == Qt::NoModifier){
switch(key->key()){
case Qt::Key_Up:
case Qt::Key_Right:
case Qt::Key_Down:
case Qt::Key_Left:
event->accept();
return true;
default: break;
}
}
}
return QWidget::event(event);
}
void KisAbstractSliderSpinBox::commitEnteredValue()
{
Q_D(KisAbstractSliderSpinBox);
//QLocale locale;
bool ok = false;
//qreal value = locale.toDouble(d->edit->text(), &ok) * d->factor;
qreal value;
if (d->parseInt) {
value = KisNumericParser::parseIntegerMathExpr(d->edit->text(), &ok) * d->factor;
} else {
value = KisNumericParser::parseSimpleMathExpr(d->edit->text(), &ok) * d->factor;
}
if (ok) {
setInternalValue(value);
}
}
bool KisAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e)
{
Q_D(KisAbstractSliderSpinBox);
if (recv == static_cast<QObject*>(d->edit) &&
e->type() == QEvent::KeyRelease) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);
switch (keyEvent->key()) {
case Qt::Key_Enter:
case Qt::Key_Return: {
commitEnteredValue();
hideEdit();
return true;
}
case Qt::Key_Escape:
d->edit->setText(valueString());
hideEdit();
return true;
default:
break;
}
}
return false;
}
QSize KisAbstractSliderSpinBox::sizeHint() const
{
const Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QFont ft(font());
if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK) {
// Some styles use bold font in progressbars
// unfortunately there is no reliable way to check for that
ft.setBold(true);
}
QFontMetrics fm(ft);
QSize hint(fm.boundingRect(d->prefix + QString::number(d->maximum) + d->suffix).size());
hint += QSize(0, 2);
switch (d->style) {
case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION:
hint += QSize(8, 8);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
hint += QSize(8, 0);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
hint += QSize(2, 0);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK:
// almost all "modern" styles have a margin around controls
hint += QSize(6, 6);
break;
default:
break;
}
//Getting the size of the buttons is a pain as the calcs require a rect
//that is "big enough". We run the calc twice to get the "smallest" buttons
//This code was inspired by QAbstractSpinBox
QSize extra(1000, 0);
spinOpts.rect.setSize(hint + extra);
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts,
QStyle::SC_SpinBoxEditField, this).size();
spinOpts.rect.setSize(hint + extra);
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts,
QStyle::SC_SpinBoxEditField, this).size();
hint += extra;
spinOpts.rect.setSize(hint);
return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint)
.expandedTo(QApplication::globalStrut());
}
QSize KisAbstractSliderSpinBox::minimumSizeHint() const
{
return sizeHint();
}
QSize KisAbstractSliderSpinBox::minimumSize() const
{
- return QWidget::minimumSize().expandedTo(minimumSizeHint());
+ return QWidget::minimumSize();
}
QStyleOptionSpinBox KisAbstractSliderSpinBox::spinBoxOptions() const
{
const Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox opts;
opts.initFrom(this);
opts.frame = false;
opts.buttonSymbols = QAbstractSpinBox::UpDownArrows;
opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown;
//Disable non-logical buttons
if (d->value == d->minimum) {
opts.stepEnabled = QAbstractSpinBox::StepUpEnabled;
} else if (d->value == d->maximum) {
opts.stepEnabled = QAbstractSpinBox::StepDownEnabled;
} else {
opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled;
}
//Deal with depressed buttons
if (d->upButtonDown) {
opts.activeSubControls = QStyle::SC_SpinBoxUp;
} else if (d->downButtonDown) {
opts.activeSubControls = QStyle::SC_SpinBoxDown;
} else {
opts.activeSubControls = 0;
}
return opts;
}
QStyleOptionProgressBar KisAbstractSliderSpinBox::progressBarOptions() const
{
const Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
//Create opts for drawing the progress portion
QStyleOptionProgressBar progressOpts;
progressOpts.initFrom(this);
progressOpts.maximum = d->maximum;
progressOpts.minimum = d->minimum;
qreal minDbl = d->minimum;
qreal dValues = (d->maximum - minDbl);
progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl;
progressOpts.text = d->prefix + valueString() + d->suffix;
progressOpts.textAlignment = Qt::AlignCenter;
progressOpts.textVisible = !(d->edit->isVisible());
//Change opts rect to be only the ComboBox's text area
progressOpts.rect = progressRect(spinOpts);
return progressOpts;
}
QRect KisAbstractSliderSpinBox::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
const Q_D(KisAbstractSliderSpinBox);
QRect ret = style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxEditField);
switch (d->style) {
case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
ret.adjust(-2, 0, 1, 0);
break;
case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
ret.adjust(1, 0, 0, 0);
break;
default:
break;
}
return ret;
}
QRect KisAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxUp);
}
QRect KisAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxDown);
}
int KisAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const
{
const Q_D(KisAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QRect correctedProgRect;
if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_FUSION) {
correctedProgRect = progressRect(spinOpts).adjusted(2, 0, -2, 0);
}
else if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE) {
correctedProgRect = progressRect(spinOpts);
}
else {
//Adjust for magic number in style code (margins)
correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2);
}
//Compute the distance of the progress bar, in pixel
qreal leftDbl = correctedProgRect.left();
qreal xDbl = x - leftDbl;
//Compute the ration of the progress bar used, linearly (ignoring the exponent)
qreal rightDbl = correctedProgRect.right();
qreal minDbl = d->minimum;
qreal maxDbl = d->maximum;
qreal dValues = (maxDbl - minDbl);
qreal percent = (xDbl / (rightDbl - leftDbl));
//If SHIFT is pressed, movement should be slowed.
if( modifiers & Qt::ShiftModifier ) {
percent = d->shiftPercent + ( percent - d->shiftPercent ) * d->slowFactor;
}
//Final value
qreal exp_percent = pow(percent, d->exponentRatio);
qreal realvalue = ((dValues * (percent * exp_percent >= 0 ? exp_percent : -exp_percent)) + minDbl);
//If key CTRL is pressed, round to the closest step.
if( modifiers & Qt::ControlModifier ) {
qreal fstep = d->fastSliderStep;
if( modifiers & Qt::ShiftModifier ) {
fstep*=d->slowFactor;
}
realvalue = floor( (realvalue+fstep/2) / fstep ) * fstep;
}
//Return the value
return int(realvalue);
}
void KisAbstractSliderSpinBox::setPrefix(const QString& prefix)
{
Q_D(KisAbstractSliderSpinBox);
d->prefix = prefix;
}
void KisAbstractSliderSpinBox::setSuffix(const QString& suffix)
{
Q_D(KisAbstractSliderSpinBox);
d->suffix = suffix;
}
void KisAbstractSliderSpinBox::setExponentRatio(qreal dbl)
{
Q_D(KisAbstractSliderSpinBox);
Q_ASSERT(dbl > 0);
d->exponentRatio = dbl;
}
void KisAbstractSliderSpinBox::setBlockUpdateSignalOnDrag(bool blockUpdateSignal)
{
Q_D(KisAbstractSliderSpinBox);
d->blockUpdateSignalOnDrag = blockUpdateSignal;
}
void KisAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event)
{
event->accept();
}
void KisAbstractSliderSpinBox::editLostFocus()
{
Q_D(KisAbstractSliderSpinBox);
if (!d->edit->hasFocus()) {
commitEnteredValue();
hideEdit();
}
}
void KisAbstractSliderSpinBox::setInternalValue(int value)
{
setInternalValue(value, false);
}
bool KisAbstractSliderSpinBox::isDragging() const
{
Q_D(const KisAbstractSliderSpinBox);
return d->isDragging;
}
void KisAbstractSliderSpinBox::setPrivateValue(int value)
{
Q_D(KisAbstractSliderSpinBox);
d->value = qBound(d->minimum, value, d->maximum);
}
class KisSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate {
};
KisSliderSpinBox::KisSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisSliderSpinBoxPrivate)
{
Q_D(KisSliderSpinBox);
d->parseInt = true;
setRange(0,99);
}
KisSliderSpinBox::~KisSliderSpinBox()
{
}
void KisSliderSpinBox::setRange(int minimum, int maximum)
{
Q_D(KisSliderSpinBox);
d->minimum = minimum;
d->maximum = maximum;
d->fastSliderStep = (maximum-minimum+1)/20;
d->validator->setRange(minimum, maximum, 0);
update();
}
int KisSliderSpinBox::minimum() const
{
const Q_D(KisSliderSpinBox);
return d->minimum;
}
void KisSliderSpinBox::setMinimum(int minimum)
{
Q_D(KisSliderSpinBox);
setRange(minimum, d->maximum);
}
int KisSliderSpinBox::maximum() const
{
const Q_D(KisSliderSpinBox);
return d->maximum;
}
void KisSliderSpinBox::setMaximum(int maximum)
{
Q_D(KisSliderSpinBox);
setRange(d->minimum, maximum);
}
int KisSliderSpinBox::fastSliderStep() const
{
const Q_D(KisSliderSpinBox);
return d->fastSliderStep;
}
void KisSliderSpinBox::setFastSliderStep(int step)
{
Q_D(KisSliderSpinBox);
d->fastSliderStep = step;
}
int KisSliderSpinBox::value()
{
Q_D(KisSliderSpinBox);
return d->value;
}
void KisSliderSpinBox::setValue(int value)
{
setInternalValue(value, false);
update();
}
QString KisSliderSpinBox::valueString() const
{
const Q_D(KisSliderSpinBox);
QLocale locale;
return locale.toString((qreal)d->value, 'f', d->validator->decimals());
}
void KisSliderSpinBox::setSingleStep(int value)
{
Q_D(KisSliderSpinBox);
d->singleStep = value;
}
void KisSliderSpinBox::setPageStep(int value)
{
Q_UNUSED(value);
}
void KisSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal)
{
Q_D(KisAbstractSliderSpinBox);
d->value = qBound(d->minimum, _value, d->maximum);
if(!blockUpdateSignal) {
emit(valueChanged(value()));
}
}
class KisDoubleSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate {
};
KisDoubleSliderSpinBox::KisDoubleSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisDoubleSliderSpinBoxPrivate)
{
Q_D(KisDoubleSliderSpinBox);
d->parseInt = false;
}
KisDoubleSliderSpinBox::~KisDoubleSliderSpinBox()
{
}
void KisDoubleSliderSpinBox::setRange(qreal minimum, qreal maximum, int decimals)
{
Q_D(KisDoubleSliderSpinBox);
d->factor = pow(10.0, decimals);
d->minimum = minimum * d->factor;
d->maximum = maximum * d->factor;
//This code auto-compute a new step when pressing control.
//A flag defaulting to "do not change the fast step" should be added, but it implies changing every call
if(maximum - minimum >= 2.0 || decimals <= 0) { //Quick step on integers
d->fastSliderStep = int(pow(10.0, decimals));
} else if(decimals == 1) {
d->fastSliderStep = (maximum-minimum)*d->factor/10;
} else {
d->fastSliderStep = (maximum-minimum)*d->factor/20;
}
d->validator->setRange(minimum, maximum, decimals);
update();
setValue(value());
}
qreal KisDoubleSliderSpinBox::minimum() const
{
const Q_D(KisAbstractSliderSpinBox);
return d->minimum / d->factor;
}
void KisDoubleSliderSpinBox::setMinimum(qreal minimum)
{
Q_D(KisAbstractSliderSpinBox);
setRange(minimum, d->maximum);
}
qreal KisDoubleSliderSpinBox::maximum() const
{
const Q_D(KisAbstractSliderSpinBox);
return d->maximum / d->factor;
}
void KisDoubleSliderSpinBox::setMaximum(qreal maximum)
{
Q_D(KisAbstractSliderSpinBox);
setRange(d->minimum, maximum);
}
qreal KisDoubleSliderSpinBox::fastSliderStep() const
{
const Q_D(KisAbstractSliderSpinBox);
return d->fastSliderStep;
}
void KisDoubleSliderSpinBox::setFastSliderStep(qreal step)
{
Q_D(KisAbstractSliderSpinBox);
d->fastSliderStep = step;
}
qreal KisDoubleSliderSpinBox::value()
{
Q_D(KisAbstractSliderSpinBox);
return (qreal)d->value / d->factor;
}
void KisDoubleSliderSpinBox::setValue(qreal value)
{
Q_D(KisAbstractSliderSpinBox);
setInternalValue(d->value = qRound(value * d->factor), false);
update();
}
void KisDoubleSliderSpinBox::setSingleStep(qreal value)
{
Q_D(KisAbstractSliderSpinBox);
d->singleStep = value * d->factor;
}
QString KisDoubleSliderSpinBox::valueString() const
{
const Q_D(KisAbstractSliderSpinBox);
QLocale locale;
return locale.toString((qreal)d->value / d->factor, 'f', d->validator->decimals());
}
void KisDoubleSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal)
{
Q_D(KisAbstractSliderSpinBox);
d->value = qBound(d->minimum, _value, d->maximum);
if(!blockUpdateSignal) {
emit(valueChanged(value()));
}
}
void KisAbstractSliderSpinBox::changeEvent(QEvent *e)
{
Q_D(KisAbstractSliderSpinBox);
QWidget::changeEvent(e);
switch (e->type()) {
case QEvent::StyleChange:
if (style()->objectName() == "fusion") {
d->style = KisAbstractSliderSpinBoxPrivate::STYLE_FUSION;
}
else if (style()->objectName() == "plastique") {
d->style = KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE;
}
else if (style()->objectName() == "breeze") {
d->style = KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE;
}
else {
d->style = KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK;
}
break;
default:
break;
}
}
diff --git a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp
index df43af3e4b..210486ab72 100644
--- a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp
+++ b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp
@@ -1,368 +1,367 @@
/*
* 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::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.width();
}
void KisStopGradientSliderWidget::setGradientResource(KoStopGradient* gradient)
{
m_gradient = gradient ? gradient : m_defaultGradient.data();
if (m_gradient && m_selectedStop >= 0) {
m_selectedStop = qBound(0, m_selectedStop, m_gradient->stops().size() - 1);
emit sigSelectedStop(m_selectedStop);
} else {
m_selectedStop = -1;
}
}
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;
}
if (e->buttons() != Qt::LeftButton ) {
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()));
+ QSize sz = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(h, h), this);
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/version/kritaversion.h.cmake b/libs/version/kritaversion.h.cmake
index d9f56ab093..8cbd89ad03 100644
--- a/libs/version/kritaversion.h.cmake
+++ b/libs/version/kritaversion.h.cmake
@@ -1,172 +1,162 @@
/* This file is part of the Krita libraries
Copyright (c) 2003 David Faure <faure@kde.org>
Copyright (c) 2003 Lukas Tinkl <lukas@kde.org>
Copyright (c) 2004 Nicolas Goutte <goutte@kde.org>
Copyright (C) 2015 Jarosław Staniek <staniek@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 _KRITA_VERSION_H_
#define _KRITA_VERSION_H_
#include "kritaversion_export.h"
// -- WARNING: do not edit values below, instead edit KRITA_* in /CMakeLists.txt --
/**
* @def KRITA_VERSION_STRING
* @ingroup KritaMacros
* @brief Version of Krita as string, at compile time
*
* This macro contains the Krita version in string form. As it is a macro,
* it contains the version at compile time.
*
* @note The version string might contain spaces and special characters,
* especially for development versions of Krita.
* If you use that macro directly for a file format (e.g. OASIS Open Document)
* or for a protocol (e.g. http) be careful that it is appropriate.
* (Fictional) example: "3.0 Alpha"
*/
#define KRITA_VERSION_STRING "@KRITA_VERSION_STRING@"
/**
* @def KRITA_STABLE_VERSION_MAJOR
* @ingroup KritaMacros
* @brief Major version of stable Krita, at compile time
* KRITA_VERSION_MAJOR is computed based on this value.
*/
#define KRITA_STABLE_VERSION_MAJOR @KRITA_STABLE_VERSION_MAJOR@
/**
* @def KRITA_VERSION_MAJOR
* @ingroup KritaMacros
* @brief Major version of Krita, at compile time
*
* Generally it's the same as KRITA_STABLE_VERSION_MAJOR but for unstable x.0
* x is decreased by one, e.g. 3.0 Beta is 2.99.
*/
#if !defined KRITA_STABLE && @KRITA_STABLE_VERSION_MINOR@ == 0
# define KRITA_VERSION_MAJOR (KRITA_STABLE_VERSION_MAJOR - 1)
#else
# define KRITA_VERSION_MAJOR KRITA_STABLE_VERSION_MAJOR
#endif
/**
* @def KRITA_STABLE_VERSION_MINOR
* @ingroup KritaMacros
* @brief Minor version of stable Krita, at compile time
* KRITA_VERSION_MINOR is computed based on this value.
*/
#define KRITA_STABLE_VERSION_MINOR @KRITA_STABLE_VERSION_MINOR@
/**
* @def KRITA_VERSION_MINOR
* @ingroup KritaMacros
* @brief Minor version of Krita, at compile time
*
* Generally it's equal to KRITA_STABLE_VERSION_MINOR for stable releases,
* equal to 99 for x.0 unstable releases (e.g. it's 3.0 Beta has minor version 99),
* and equal to KRITA_STABLE_VERSION_MINOR-1 for unstable releases other than x.0.
*/
#ifdef KRITA_STABLE
# define KRITA_VERSION_MINOR KRITA_STABLE_VERSION_MINOR
#elif KRITA_STABLE_VERSION_MINOR == 0
# define KRITA_VERSION_MINOR 99
#else
# define KRITA_VERSION_MINOR (KRITA_STABLE_VERSION_MINOR - 1)
#endif
/**
* @def KRITA_VERSION_RELEASE
* @ingroup KritaMacros
* @brief Release version of Krita, at compile time.
* 89 for Alpha.
*/
#define KRITA_VERSION_RELEASE @KRITA_VERSION_RELEASE@
/**
* @def KRITA_STABLE_VERSION_RELEASE
* @ingroup KritaMacros
* @brief Release version of Krita, at compile time.
*
* Equal to KRITA_VERSION_RELEASE for stable releases and 0 for unstable ones.
*/
#ifdef KRITA_STABLE
# define KRITA_STABLE_VERSION_RELEASE 0
#else
# define KRITA_STABLE_VERSION_RELEASE @KRITA_VERSION_RELEASE@
#endif
/**
* @def KRITA_ALPHA
* @ingroup KritaMacros
* @brief If defined (1..9), indicates at compile time that Krita is in alpha stage
*/
#cmakedefine KRITA_ALPHA @KRITA_ALPHA@
/**
* @def KRITA_BETA
* @ingroup KritaMacros
* @brief If defined (1..9), indicates at compile time that Krita is in beta stage
*/
#cmakedefine KRITA_BETA @KRITA_BETA@
/**
* @def KRITA_RC
* @ingroup KritaMacros
* @brief If defined (1..9), indicates at compile time that Krita is in "release candidate" stage
*/
#cmakedefine KRITA_RC @KRITA_RC@
/**
* @def KRITA_STABLE
* @ingroup KritaMacros
* @brief If defined, indicates at compile time that Krita is in stable stage
*/
#cmakedefine KRITA_STABLE @KRITA_STABLE@
/**
* @ingroup KritaMacros
* @brief Make a number from the major, minor and release number of a Krita version
*
* This function can be used for preprocessing when KRITA_IS_VERSION is not
* appropriate.
*/
#define KRITA_MAKE_VERSION( a,b,c ) (((a) << 16) | ((b) << 8) | (c))
/**
* @ingroup KritaMacros
* @brief Version of Krita as number, at compile time
*
* This macro contains the Krita version in number form. As it is a macro,
* it contains the version at compile time. See version() if you need
* the Krita version used at runtime.
*/
#define KRITA_VERSION \
KRITA_MAKE_VERSION(KRITA_VERSION_MAJOR,KRITA_VERSION_MINOR,KRITA_VERSION_RELEASE)
-/**
- * @def KRITA_YEAR
- * @ingroup KritaMacros
- * @brief Year of the Krita release, set at compile time
- *
- * This macro is used in "About application" dialog for strings such as "© 2012-..., The Author Team".
-*/
-#define KRITA_YEAR "@KRITA_YEAR@"
-
-
#endif // _KRITA_VERSION_H_
diff --git a/libs/widgets/KisDlgInternalColorSelector.cpp b/libs/widgets/KisDlgInternalColorSelector.cpp
index f228e43b6f..dfce1dac69 100644
--- a/libs/widgets/KisDlgInternalColorSelector.cpp
+++ b/libs/widgets/KisDlgInternalColorSelector.cpp
@@ -1,354 +1,348 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>, (C) 2016
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* 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 <QAbstractSpinBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QPointer>
#include <QCompleter>
#include <functional>
#include <KConfigGroup>
#include "KoColorSpaceRegistry.h"
#include <KoColorSet.h>
#include <KisPaletteModel.h>
#include <KisPaletteListWidget.h>
#include <kis_palette_view.h>
#include <KoResourceServerProvider.h>
#include <KoResourceServer.h>
#include "kis_signal_compressor.h"
#include "KoColorDisplayRendererInterface.h"
#include "kis_spinbox_color_selector.h"
#include "KisDlgInternalColorSelector.h"
#include "ui_WdgDlgInternalColorSelector.h"
#include "kis_config_notifier.h"
#include "kis_color_input.h"
#include "kis_icon_utils.h"
#include "KisSqueezedComboBox.h"
std::function<KisScreenColorPickerBase *(QWidget *)> KisDlgInternalColorSelector::s_screenColorPickerFactory = 0;
struct KisDlgInternalColorSelector::Private
{
bool allowUpdates = true;
KoColor currentColor;
KoColor previousColor;
KoColor sRGB = KoColor(KoColorSpaceRegistry::instance()->rgb8());
const KoColorSpace *currentColorSpace;
bool lockUsedCS = false;
bool chooseAlpha = false;
KisSignalCompressor *compressColorChanges;
const KoColorDisplayRendererInterface *displayRenderer;
KisHexColorInput *hexColorInput = 0;
KisPaletteModel *paletteModel = 0;
KisPaletteListWidget *paletteChooser = 0;
KisScreenColorPickerBase *screenColorPicker = 0;
};
KisDlgInternalColorSelector::KisDlgInternalColorSelector(QWidget *parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer)
: QDialog(parent)
, m_d(new Private)
{
setModal(config.modal);
setFocusPolicy(Qt::ClickFocus);
m_ui = new Ui_WdgDlgInternalColorSelector();
m_ui->setupUi(this);
setWindowTitle(caption);
m_d->currentColor = color;
m_d->currentColorSpace = m_d->currentColor.colorSpace();
m_d->displayRenderer = displayRenderer;
m_ui->spinboxselector->slotSetColor(color);
connect(m_ui->spinboxselector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor)));
m_ui->visualSelector->slotSetColor(color);
m_ui->visualSelector->setDisplayRenderer(displayRenderer);
m_ui->visualSelector->setConfig(false, config.modal);
if (config.visualColorSelector) {
connect(m_ui->visualSelector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_ui->visualSelector, SLOT(configurationChanged()));
} else {
m_ui->visualSelector->hide();
}
m_d->paletteChooser = new KisPaletteListWidget(this);
m_d->paletteModel = new KisPaletteModel(this);
m_ui->bnPaletteChooser->setIcon(KisIconUtils::loadIcon("hi16-palette_library"));
m_ui->paletteBox->setPaletteModel(m_d->paletteModel);
m_ui->paletteBox->setDisplayRenderer(displayRenderer);
m_ui->cmbNameList->setCompanionView(m_ui->paletteBox);
connect(m_d->paletteChooser, SIGNAL(sigPaletteSelected(KoColorSet*)), this, SLOT(slotChangePalette(KoColorSet*)));
connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(KoColor)), SLOT(slotColorUpdated(KoColor)));
// For some bizarre reason, the modal dialog doesn't like having the colorset set, so let's not.
if (config.paletteBox) {
//TODO: Add disable signal as well. Might be not necessary...?
KConfigGroup cfg(KSharedConfig::openConfig()->group(""));
QString paletteName = cfg.readEntry("internal_selector_active_color_set", QString());
KoResourceServer<KoColorSet>* rServer = KoResourceServerProvider::instance()->paletteServer();
KoColorSet *savedPal = rServer->resourceByName(paletteName);
if (savedPal) {
this->slotChangePalette(savedPal);
} else {
if (rServer->resources().count()) {
savedPal = rServer->resources().first();
if (savedPal) {
this->slotChangePalette(savedPal);
}
}
}
connect(m_ui->paletteBox, SIGNAL(sigColorSelected(KoColor)), this,
SLOT(slotColorUpdated(KoColor)));
m_ui->bnPaletteChooser->setPopupWidget(m_d->paletteChooser);
} else {
m_ui->paletteBox->setEnabled(false);
m_ui->cmbNameList->setEnabled(false);
m_ui->bnPaletteChooser->setEnabled(false);
}
if (config.prevNextButtons) {
m_ui->currentColor->setColor(m_d->currentColor);
m_ui->currentColor->setDisplayRenderer(displayRenderer);
m_ui->previousColor->setColor(m_d->currentColor);
m_ui->previousColor->setDisplayRenderer(displayRenderer);
connect(m_ui->previousColor, SIGNAL(triggered(KoColorPatch*)), SLOT(slotSetColorFromPatch(KoColorPatch*)));
} else {
m_ui->currentColor->hide();
m_ui->previousColor->hide();
}
if (config.hexInput) {
m_d->sRGB.fromKoColor(m_d->currentColor);
m_d->hexColorInput = new KisHexColorInput(this, &m_d->sRGB);
m_d->hexColorInput->update();
connect(m_d->hexColorInput, SIGNAL(updated()), SLOT(slotSetColorFromHex()));
m_ui->rightPane->addWidget(m_d->hexColorInput);
m_d->hexColorInput->setToolTip(i18n("This is a hexcode input, for webcolors. It can only get colors in the sRGB space."));
}
// KisScreenColorPicker is in the kritaui module, so dependency inversion is used to access it.
m_ui->screenColorPickerWidget->setLayout(new QHBoxLayout(m_ui->screenColorPickerWidget));
if (s_screenColorPickerFactory) {
m_d->screenColorPicker = s_screenColorPickerFactory(m_ui->screenColorPickerWidget);
m_ui->screenColorPickerWidget->layout()->addWidget(m_d->screenColorPicker);
if (config.screenColorPicker) {
connect(m_d->screenColorPicker, SIGNAL(sigNewColorPicked(KoColor)),this, SLOT(slotColorUpdated(KoColor)));
} else {
m_d->screenColorPicker->hide();
}
}
m_d->compressColorChanges = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE, this);
connect(m_d->compressColorChanges, SIGNAL(timeout()), this, SLOT(endUpdateWithNewColor()));
connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()), Qt::UniqueConnection);
connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()), Qt::UniqueConnection);
connect(this, SIGNAL(finished(int)), SLOT(slotFinishUp()));
}
KisDlgInternalColorSelector::~KisDlgInternalColorSelector()
{
delete m_ui;
}
void KisDlgInternalColorSelector::slotColorUpdated(KoColor newColor)
{
// not-so-nice solution: if someone calls this slot directly and that code was
// triggered by our compressor signal, our compressor is technically the sender()!
if (sender() == m_d->compressColorChanges) {
return;
}
// Do not accept external updates while a color update emit is pending;
// Note: Assumes external updates only come from parent(), a separate slot might be better
if (m_d->allowUpdates || (QObject::sender() && QObject::sender() != this->parent())) {
// Enforce palette colors
KConfigGroup group(KSharedConfig::openConfig(), "");
if (group.readEntry("colorsettings/forcepalettecolors", false)) {
newColor = m_ui->paletteBox->closestColor(newColor);
}
if (m_d->lockUsedCS){
newColor.convertTo(m_d->currentColorSpace);
m_d->currentColor = newColor;
} else {
m_d->currentColor = newColor;
}
updateAllElements(QObject::sender());
}
}
void KisDlgInternalColorSelector::slotSetColorFromPatch(KoColorPatch *patch)
{
slotColorUpdated(patch->color());
}
void KisDlgInternalColorSelector::colorSpaceChanged(const KoColorSpace *cs)
{
if (cs == m_d->currentColorSpace) {
return;
}
m_d->currentColorSpace = KoColorSpaceRegistry::instance()->colorSpace(cs->colorModelId().id(), cs->colorDepthId().id(), cs->profile());
m_ui->spinboxselector->slotSetColorSpace(m_d->currentColorSpace);
m_ui->visualSelector->slotsetColorSpace(m_d->currentColorSpace);
}
void KisDlgInternalColorSelector::lockUsedColorSpace(const KoColorSpace *cs)
{
colorSpaceChanged(cs);
if (m_d->currentColor.colorSpace() != m_d->currentColorSpace) {
m_d->currentColor.convertTo(m_d->currentColorSpace);
}
m_d->lockUsedCS = true;
}
void KisDlgInternalColorSelector::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer)
{
if (displayRenderer) {
m_d->displayRenderer = displayRenderer;
m_ui->visualSelector->setDisplayRenderer(displayRenderer);
m_ui->currentColor->setDisplayRenderer(displayRenderer);
m_ui->previousColor->setDisplayRenderer(displayRenderer);
m_ui->paletteBox->setDisplayRenderer(displayRenderer);
} else {
m_d->displayRenderer = KoDumbColorDisplayRenderer::instance();
}
}
KoColor KisDlgInternalColorSelector::getModalColorDialog(const KoColor color, QWidget* parent, QString caption)
{
Config config = Config();
KisDlgInternalColorSelector dialog(parent, color, config, caption);
dialog.setPreviousColor(color);
dialog.exec();
return dialog.getCurrentColor();
}
KoColor KisDlgInternalColorSelector::getCurrentColor()
{
return m_d->currentColor;
}
void KisDlgInternalColorSelector::chooseAlpha(bool chooseAlpha)
{
m_d->chooseAlpha = chooseAlpha;
}
-void KisDlgInternalColorSelector::slotConfigurationChanged()
-{
- //m_d->canvas->displayColorConverter()->
- //slotColorSpaceChanged(m_d->canvas->image()->colorSpace());
-}
-
void KisDlgInternalColorSelector::setPreviousColor(KoColor c)
{
m_d->previousColor = c;
}
void KisDlgInternalColorSelector::reject()
{
slotColorUpdated(m_d->previousColor);
QDialog::reject();
}
void KisDlgInternalColorSelector::updateAllElements(QObject *source)
{
//update everything!!!
if (source != m_ui->spinboxselector) {
m_ui->spinboxselector->slotSetColor(m_d->currentColor);
}
if (source != m_ui->visualSelector) {
m_ui->visualSelector->slotSetColor(m_d->currentColor);
}
if (source != m_d->hexColorInput) {
m_d->sRGB.fromKoColor(m_d->currentColor);
m_d->hexColorInput->update();
}
if (source != m_ui->paletteBox) {
m_ui->paletteBox->selectClosestColor(m_d->currentColor);
}
m_ui->previousColor->setColor(m_d->previousColor);
m_ui->currentColor->setColor(m_d->currentColor);
if (source && source != this->parent()) {
m_d->allowUpdates = false;
m_d->compressColorChanges->start();
}
if (m_d->screenColorPicker) {
m_d->screenColorPicker->updateIcons();
}
}
void KisDlgInternalColorSelector::endUpdateWithNewColor()
{
emit signalForegroundColorChosen(m_d->currentColor);
m_d->allowUpdates = true;
}
void KisDlgInternalColorSelector::focusInEvent(QFocusEvent *)
{
//setPreviousColor();
}
void KisDlgInternalColorSelector::slotFinishUp()
{
setPreviousColor(m_d->currentColor);
KConfigGroup cfg(KSharedConfig::openConfig()->group(""));
if (m_d->paletteModel) {
if (m_d->paletteModel->colorSet()) {
cfg.writeEntry("internal_selector_active_color_set", m_d->paletteModel->colorSet()->name());
}
}
}
void KisDlgInternalColorSelector::slotSetColorFromHex()
{
slotColorUpdated(m_d->sRGB);
}
void KisDlgInternalColorSelector::slotChangePalette(KoColorSet *set)
{
if (!set) {
return;
}
m_d->paletteModel->setPalette(set);
}
void KisDlgInternalColorSelector::showEvent(QShowEvent *event)
{
updateAllElements(0);
QDialog::showEvent(event);
}
diff --git a/libs/widgets/KisDlgInternalColorSelector.h b/libs/widgets/KisDlgInternalColorSelector.h
index 0536d0a42f..424e2c8042 100644
--- a/libs/widgets/KisDlgInternalColorSelector.h
+++ b/libs/widgets/KisDlgInternalColorSelector.h
@@ -1,192 +1,187 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>, (C) 2016
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public 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 KISDLGINTERNALCOLORSELECTOR_H
#define KISDLGINTERNALCOLORSELECTOR_H
#include "kritawidgets_export.h"
#include "KoColor.h"
#include "KoColorSpace.h"
#include "KoColorDisplayRendererInterface.h"
#include "KoColorSet.h"
#include <QScopedPointer>
#include <QDialog>
#include "KisScreenColorPickerBase.h"
class Ui_WdgDlgInternalColorSelector;
class KoColorPatch;
/**
* @brief The KisInternalColorSelector class
*
* A non-modal color selector dialog that is not a plugin and can thus be used for filters.
*/
class KRITAWIDGETS_EXPORT KisDlgInternalColorSelector : public QDialog
{
Q_OBJECT
static std::function<KisScreenColorPickerBase *(QWidget *)> s_screenColorPickerFactory;
public:
static void setScreenColorPickerFactory(std::function<KisScreenColorPickerBase *(QWidget *)> f) {
s_screenColorPickerFactory = f;
}
struct Config
{
Config() :
modal(true),
visualColorSelector(true),
paletteBox(true),
screenColorPicker(true),
prevNextButtons(true),
hexInput(true),
useAlpha(false){}
bool modal;
bool visualColorSelector;
bool paletteBox;
bool screenColorPicker;
bool prevNextButtons;
bool hexInput;
bool useAlpha;
};
KisDlgInternalColorSelector(QWidget* parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance());
~KisDlgInternalColorSelector() override;
/**
* @brief slotColorSpaceChanged
* Color space has changed, use this dialog to change the colorspace.
*/
void colorSpaceChanged(const KoColorSpace *cs);
/**
* @brief lockUsedColorSpace
* Lock the used colorspace of this selector.
* @param cs
*/
void lockUsedColorSpace(const KoColorSpace *cs);
/**
* @brief setDisplayRenderer
* Set the display renderer. This is necessary for HDR color manage support.
* @param displayRenderer
*/
void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer);
/**
* @brief getModalColorDialog
* Execute this dialog modally. The function returns
* the KoColor you want.
* @param color - The current color. Make sure this is in the color space you want your
* end color to be in.
* @param parent parent widget.
* @param caption the dialog caption.
*/
static KoColor getModalColorDialog(const KoColor color, QWidget* parent = 0, QString caption = QString());
/**
* @brief getCurrentColor
* @return gives currently active color;
*/
KoColor getCurrentColor();
void chooseAlpha(bool chooseAlpha);
Q_SIGNALS:
/**
* @brief signalForegroundColorChosen
* The most important signal. This will sent out when a color has been picked from the selector.
* There will be a small delay to make sure that the selector causes too many updates.
*
* Do not connect this to slotColorUpdated.
* @param color The new color chosen
*/
void signalForegroundColorChosen(KoColor color);
public Q_SLOTS:
/**
* @brief slotColorUpdated
* Very important slot. Is connected to krita's resources to make sure it has
* the currently active color. It's very important that this function is able to understand
* when the signal came from itself.
* @param newColor This is the new color.
*/
void slotColorUpdated(KoColor newColor);
/**
* @brief slotSetColorFromPatch
* update current color from kocolorpatch.
* @param patch
*/
void slotSetColorFromPatch(KoColorPatch* patch);
/**
* @brief setPreviousColor
* set the previous color.
*/
void setPreviousColor(KoColor c);
void reject() override;
private Q_SLOTS:
- /**
- * @brief slotConfigurationChanged
- * Wrapper slot for changes to the colorspace.
- */
- void slotConfigurationChanged();
void endUpdateWithNewColor();
/**
* @brief slotFinishUp
* This is called when the selector is closed, for saving the current palette.
*/
void slotFinishUp();
/**
* @brief slotSetColorFromHex
* Update from the hex color input.
*/
void slotSetColorFromHex();
void slotChangePalette(KoColorSet *set);
protected:
void showEvent(QShowEvent *event) override;
private:
void focusInEvent(QFocusEvent *) override;
/**
* @brief updateAllElements
* Updates each widget with the new element, and if it's responsible for the update sents
* a signal out that there's a new color.
*/
void updateAllElements(QObject *source);
private:
Ui_WdgDlgInternalColorSelector *m_ui;
struct Private; //The private struct
const QScopedPointer<Private> m_d; //the private pointer
};
#endif // KISDLGINTERNALCOLORSELECTOR_H
diff --git a/libs/widgets/KisPaletteModel.cpp b/libs/widgets/KisPaletteModel.cpp
index f1c4e71f8b..f7a4526d01 100644
--- a/libs/widgets/KisPaletteModel.cpp
+++ b/libs/widgets/KisPaletteModel.cpp
@@ -1,508 +1,511 @@
/*
* Copyright (c) 2013 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2018 Michael Zhou <simeirxh@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 <KoColorModelStandardIds.h>
#include <resources/KoColorSet.h>
#include <KoColorDisplayRendererInterface.h>
KisPaletteModel::KisPaletteModel(QObject* parent)
: QAbstractTableModel(parent)
, m_colorSet(0)
, m_displayRenderer(KoDumbColorDisplayRenderer::instance())
{
connect(this, SIGNAL(sigPaletteModified()), SLOT(slotPaletteModified()));
}
KisPaletteModel::~KisPaletteModel()
{
}
QVariant KisPaletteModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()) { return QVariant(); }
bool groupNameRow = m_rowGroupNameMap.contains(index.row());
if (role == IsGroupNameRole) {
return groupNameRow;
}
if (groupNameRow) {
return dataForGroupNameRow(index, role);
} else {
return dataForSwatch(index, role);
}
}
int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const
{
if (!m_colorSet)
return 0;
return m_colorSet->rowCount() // count of color rows
+ m_rowGroupNameMap.size() // rows for names
- 1; // global doesn't have a name
}
int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const
{
if (m_colorSet && m_colorSet->columnCount() > 0) {
return m_colorSet->columnCount();
}
if (!m_colorSet) {
return 0;
}
return 16;
}
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
{
- Q_UNUSED(parent);
+ Q_UNUSED(parent)
Q_ASSERT(m_colorSet);
+ if (m_rowGroupNameMap.isEmpty()) {
+ return {};
+ }
int groupNameRow = groupNameRowForRow(row);
KisSwatchGroup *group = m_colorSet->getGroup(m_rowGroupNameMap[groupNameRow]);
- Q_ASSERT(group);
+ KIS_ASSERT_RECOVER_RETURN_VALUE(group,QModelIndex());
return createIndex(row, column, group);
}
void KisPaletteModel::resetGroupNameRows()
{
m_rowGroupNameMap.clear();
int row = -1;
for (const QString &groupName : m_colorSet->getGroupNames()) {
m_rowGroupNameMap[row] = groupName;
row += m_colorSet->getGroup(groupName)->rowCount();
row += 1; // row for group name
}
}
void KisPaletteModel::setPalette(KoColorSet* palette)
{
beginResetModel();
m_colorSet = palette;
if (palette) {
resetGroupNameRows();
}
endResetModel();
emit sigPaletteChanged();
}
KoColorSet* KisPaletteModel::colorSet() const
{
return m_colorSet;
}
int KisPaletteModel::rowNumberInGroup(int rowInModel) const
{
if (m_rowGroupNameMap.contains(rowInModel)) {
return -1;
}
QList<int> rowNumberList = m_rowGroupNameMap.keys();
for (auto it = rowNumberList.rbegin(); it != rowNumberList.rend(); it++) {
if (*it < rowInModel) {
return rowInModel - *it - 1;
}
}
return rowInModel;
}
int KisPaletteModel::groupNameRowForName(const QString &groupName)
{
for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) {
if (it.value() == groupName) {
return it.key();
}
}
return -1;
}
bool KisPaletteModel::addEntry(const KisSwatch &entry, const QString &groupName)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1);
m_colorSet->add(entry, groupName);
endInsertRows();
if (m_colorSet->isGlobal()) {
m_colorSet->save();
}
emit sigPaletteModified();
return true;
}
bool KisPaletteModel::removeEntry(const QModelIndex &index, bool keepColors)
{
if (!qvariant_cast<bool>(data(index, IsGroupNameRole))) {
static_cast<KisSwatchGroup*>(index.internalPointer())->removeEntry(index.column(),
rowNumberInGroup(index.row()));
emit dataChanged(index, index);
} else {
int groupNameRow = groupNameRowForRow(index.row());
QString groupName = m_rowGroupNameMap[groupNameRow];
removeGroup(groupName, keepColors);
}
emit sigPaletteModified();
return true;
}
void KisPaletteModel::removeGroup(const QString &groupName, bool keepColors)
{
int removeStart = groupNameRowForName(groupName);
int removedRowCount = m_colorSet->getGroup(groupName)->rowCount();
int insertStart = m_colorSet->getGlobalGroup()->rowCount();
beginRemoveRows(QModelIndex(),
removeStart,
removeStart + removedRowCount);
m_colorSet->removeGroup(groupName, keepColors);
resetGroupNameRows();
endRemoveRows();
beginInsertRows(QModelIndex(),
insertStart, m_colorSet->getGlobalGroup()->rowCount());
endInsertRows();
emit sigPaletteModified();
}
bool KisPaletteModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent)
{
Q_UNUSED(row);
Q_UNUSED(column);
if (!data->hasFormat("krita/x-colorsetentry") && !data->hasFormat("krita/x-colorsetgroup")) {
return false;
}
if (action == Qt::IgnoreAction) {
return false;
}
QModelIndex finalIndex = parent;
if (!finalIndex.isValid()) { return false; }
if (data->hasFormat("krita/x-colorsetgroup")) {
// dragging group not supported for now
QByteArray encodedData = data->data("krita/x-colorsetgroup");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
while (!stream.atEnd()) {
QString groupNameDroppedOn = qvariant_cast<QString>(finalIndex.data(GroupNameRole));
if (groupNameDroppedOn == KoColorSet::GLOBAL_GROUP_NAME) {
return false;
}
QString groupNameDragged;
stream >> groupNameDragged;
KisSwatchGroup *groupDragged = m_colorSet->getGroup(groupNameDragged);
int start = groupNameRowForName(groupNameDragged);
int end = start + groupDragged->rowCount();
if (!beginMoveRows(QModelIndex(), start, end, QModelIndex(), groupNameRowForName(groupNameDroppedOn))) {
return false;
}
m_colorSet->moveGroup(groupNameDragged, groupNameDroppedOn);
resetGroupNameRows();
endMoveRows();
emit sigPaletteModified();
if (m_colorSet->isGlobal()) {
m_colorSet->save();
}
}
return true;
}
if (qvariant_cast<bool>(finalIndex.data(KisPaletteModel::IsGroupNameRole))) {
return true;
}
QByteArray encodedData = data->data("krita/x-colorsetentry");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
while (!stream.atEnd()) {
KisSwatch entry;
QString name, id;
bool spotColor;
QString oldGroupName;
int oriRow;
int oriColumn;
QString colorXml;
stream >> name >> id >> spotColor
>> oriRow >> oriColumn
>> oldGroupName
>> colorXml;
entry.setName(name);
entry.setId(id);
entry.setSpotColor(spotColor);
QDomDocument doc;
doc.setContent(colorXml);
QDomElement e = doc.documentElement();
QDomElement c = e.firstChildElement();
if (!c.isNull()) {
QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id());
entry.setColor(KoColor::fromXML(c, colorDepthId));
}
if (action == Qt::MoveAction){
KisSwatchGroup *g = m_colorSet->getGroup(oldGroupName);
if (g) {
if (qvariant_cast<bool>(finalIndex.data(KisPaletteModel::CheckSlotRole))) {
g->setEntry(getEntry(finalIndex), oriColumn, oriRow);
} else {
g->removeEntry(oriColumn, oriRow);
}
}
setEntry(entry, finalIndex);
emit sigPaletteModified();
if (m_colorSet->isGlobal()) {
m_colorSet->save();
}
}
}
return true;
}
QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
QModelIndex index = indexes.last();
if (index.isValid() && qvariant_cast<bool>(index.data(CheckSlotRole))) {
QString mimeTypeName = "krita/x-colorsetentry";
if (qvariant_cast<bool>(index.data(IsGroupNameRole))==false) {
KisSwatch entry = getEntry(index);
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()
<< rowNumberInGroup(index.row()) << index.column()
<< qvariant_cast<QString>(index.data(GroupNameRole))
<< doc.toString();
} else {
mimeTypeName = "krita/x-colorsetgroup";
QString groupName = qvariant_cast<QString>(index.data(GroupNameRole));
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;
}
void KisPaletteModel::setEntry(const KisSwatch &entry,
const QModelIndex &index)
{
KisSwatchGroup *group = static_cast<KisSwatchGroup*>(index.internalPointer());
Q_ASSERT(group);
group->setEntry(entry, index.column(), rowNumberInGroup(index.row()));
emit sigPaletteModified();
emit dataChanged(index, index);
if (m_colorSet->isGlobal()) {
m_colorSet->save();
}
}
bool KisPaletteModel::renameGroup(const QString &groupName, const QString &newName)
{
beginResetModel();
bool success = m_colorSet->changeGroupName(groupName, newName);
for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) {
if (it.value() == groupName) {
m_rowGroupNameMap[it.key()] = newName;
break;
}
}
endResetModel();
emit sigPaletteModified();
return success;
}
void KisPaletteModel::addGroup(const KisSwatchGroup &group)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount() + group.rowCount());
m_colorSet->addGroup(group.name());
*m_colorSet->getGroup(group.name()) = group;
endInsertColumns();
emit sigPaletteModified();
}
void KisPaletteModel::setRowNumber(const QString &groupName, int rowCount)
{
beginResetModel();
KisSwatchGroup *g = m_colorSet->getGroup(groupName);
if (g) {
g->setRowCount(rowCount);
}
endResetModel();
}
void KisPaletteModel::clear()
{
beginResetModel();
m_colorSet->clear();
endResetModel();
}
QVariant KisPaletteModel::dataForGroupNameRow(const QModelIndex &idx, int role) const
{
KisSwatchGroup *group = static_cast<KisSwatchGroup*>(idx.internalPointer());
Q_ASSERT(group);
QString groupName = group->name();
switch (role) {
case Qt::ToolTipRole:
case Qt::DisplayRole: {
return groupName;
}
case GroupNameRole: {
return groupName;
}
case CheckSlotRole: {
return true;
}
case RowInGroupRole: {
return -1;
}
default: {
return QVariant();
}
}
}
QVariant KisPaletteModel::dataForSwatch(const QModelIndex &idx, int role) const
{
KisSwatchGroup *group = static_cast<KisSwatchGroup*>(idx.internalPointer());
Q_ASSERT(group);
int rowInGroup = rowNumberInGroup(idx.row());
bool entryPresent = group->checkEntry(idx.column(), rowInGroup);
KisSwatch entry;
if (entryPresent) {
entry = group->getEntry(idx.column(), rowInGroup);
}
switch (role) {
case Qt::ToolTipRole:
case Qt::DisplayRole: {
return entryPresent ? entry.name() : i18n("Empty slot");
}
case Qt::BackgroundRole: {
QColor color(0, 0, 0, 0);
if (entryPresent) {
color = m_displayRenderer->toQColor(entry.color());
}
return QBrush(color);
}
case GroupNameRole: {
return group->name();
}
case CheckSlotRole: {
return entryPresent;
}
case RowInGroupRole: {
return rowInGroup;
}
default: {
return QVariant();
}
}
}
void KisPaletteModel::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer)
{
if (displayRenderer) {
if (m_displayRenderer) {
disconnect(m_displayRenderer, 0, this, 0);
}
m_displayRenderer = displayRenderer;
connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()),
SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection);
} else {
m_displayRenderer = KoDumbColorDisplayRenderer::instance();
}
}
void KisPaletteModel::slotDisplayConfigurationChanged()
{
beginResetModel();
endResetModel();
}
void KisPaletteModel::slotPaletteModified() {
m_colorSet->setPaletteType(KoColorSet::KPL);
}
QModelIndex KisPaletteModel::indexForClosest(const KoColor &compare)
{
KisSwatchGroup::SwatchInfo info = colorSet()->getClosestColorInfo(compare);
return createIndex(indexRowForInfo(info), info.column, colorSet()->getGroup(info.group));
}
int KisPaletteModel::indexRowForInfo(const KisSwatchGroup::SwatchInfo &info)
{
for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) {
if (it.value() == info.group) {
return it.key() + info.row + 1;
}
}
return info.row;
}
KisSwatch KisPaletteModel::getEntry(const QModelIndex &index) const
{
KisSwatchGroup *group = static_cast<KisSwatchGroup*>(index.internalPointer());
if (!group || !group->checkEntry(index.column(), rowNumberInGroup(index.row()))) {
return KisSwatch();
}
return group->getEntry(index.column(), rowNumberInGroup(index.row()));
}
int KisPaletteModel::groupNameRowForRow(int rowInModel) const
{
return rowInModel - rowNumberInGroup(rowInModel) - 1;
}
diff --git a/libs/widgets/KisVisualColorSelector.cpp b/libs/widgets/KisVisualColorSelector.cpp
index d87947a2e1..e18eb62ee9 100644
--- a/libs/widgets/KisVisualColorSelector.cpp
+++ b/libs/widgets/KisVisualColorSelector.cpp
@@ -1,566 +1,563 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>, (C) 2016
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KisVisualColorSelector.h"
#include <QColor>
#include <QPixmap>
#include <QPainter>
#include <QPainterPath>
#include <QRect>
#include <QVector>
#include <QVector3D>
#include <QVector4D>
#include <QVBoxLayout>
#include <QList>
#include <QPolygon>
#include <QtMath>
#include <KSharedConfig>
#include <KConfigGroup>
#include "KoColorConversions.h"
#include "KoColorDisplayRendererInterface.h"
#include "KoColorProfile.h"
#include "KoChannelInfo.h"
#include <KoColorModelStandardIds.h>
#include <QPointer>
#include "kis_signal_compressor.h"
#include "kis_debug.h"
#include "KisVisualColorSelectorShape.h"
#include "KisVisualRectangleSelectorShape.h"
#include "KisVisualTriangleSelectorShape.h"
#include "KisVisualEllipticalSelectorShape.h"
struct KisVisualColorSelector::Private
{
KoColor currentcolor;
const KoColorSpace *currentCS {0};
QList<KisVisualColorSelectorShape*> widgetlist;
bool updateLonesome {false}; // currently redundant; remove?
bool circular {false};
bool exposureSupported = false;
bool isRGBA = false;
bool isLinear = false;
int displayPosition[4]; // map channel index to storage index for display
int colorChannelCount;
QVector4D channelValues;
QVector4D channelMaxValues;
ColorModel model;
const KoColorDisplayRendererInterface *displayRenderer {0};
KisColorSelectorConfiguration acs_config;
KisSignalCompressor *updateTimer {0};
};
KisVisualColorSelector::KisVisualColorSelector(QWidget *parent)
: KisColorSelectorInterface(parent)
, m_d(new Private)
{
this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE);
connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(slotRebuildSelectors()), Qt::UniqueConnection);
}
KisVisualColorSelector::~KisVisualColorSelector()
{
delete m_d->updateTimer;
}
void KisVisualColorSelector::slotSetColor(const KoColor &c)
{
m_d->currentcolor = c;
if (m_d->currentCS != c.colorSpace()) {
slotsetColorSpace(c.colorSpace());
}
else {
m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor);
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setChannelValues(m_d->channelValues, true);
}
}
}
void KisVisualColorSelector::slotsetColorSpace(const KoColorSpace *cs)
{
if (m_d->currentCS != cs) {
m_d->currentCS = cs;
slotRebuildSelectors();
}
}
void KisVisualColorSelector::setConfig(bool forceCircular, bool forceSelfUpdate)
{
m_d->circular = forceCircular;
m_d->updateLonesome = forceSelfUpdate;
}
KoColor KisVisualColorSelector::getCurrentColor() const
{
return m_d->currentcolor;
}
QVector4D KisVisualColorSelector::getChannelValues() const
{
return m_d->channelValues;
}
KoColor KisVisualColorSelector::convertShapeCoordsToKoColor(const QVector4D &coordinates) const
{
KoColor c(m_d->currentCS);
QVector4D baseValues(coordinates);
QVector <float> channelValues(c.colorSpace()->channelCount());
channelValues.fill(1.0);
if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) {
if (m_d->model == ColorModel::HSV) {
HSVToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]);
}
else if (m_d->model == ColorModel::HSL) {
HSLToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]);
}
else if (m_d->model == ColorModel::HSI) {
// why suddenly qreal?
qreal temp[3];
HSIToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2]);
baseValues.setX(temp[0]);
baseValues.setY(temp[1]);
baseValues.setZ(temp[2]);
}
else /*if (m_d->model == ColorModel::HSY)*/ {
QVector <qreal> luma= m_d->currentCS->lumaCoefficients();
qreal temp[3];
HSYToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2],
luma[0], luma[1], luma[2]);
baseValues.setX(temp[0]);
baseValues.setY(temp[1]);
baseValues.setZ(temp[2]);
}
if (m_d->isLinear) {
for (int i=0; i<3; i++) {
baseValues[i] = pow(baseValues[i], 2.2);
}
}
}
if (m_d->exposureSupported) {
baseValues *= m_d->channelMaxValues;
}
for (int i=0; i<m_d->colorChannelCount; i++) {
// TODO: proper exposure control
channelValues[m_d->displayPosition[i]] = baseValues[i] /* *(maxvalue[i]) */;
}
c.colorSpace()->fromNormalisedChannelsValue(c.data(), channelValues);
return c;
}
QVector4D KisVisualColorSelector::convertKoColorToShapeCoordinates(KoColor c) const
{
if (c.colorSpace() != m_d->currentCS) {
c.convertTo(m_d->currentCS);
}
QVector <float> channelValues (c.colorSpace()->channelCount());
channelValues.fill(1.0);
m_d->currentCS->normalisedChannelsValue(c.data(), channelValues);
QVector4D channelValuesDisplay(0, 0, 0, 0), coordinates(0, 0, 0, 0);
// TODO: L*a*b is apparently not [0, 1]^3 as "normalized" values, needs extra transform (old bug)
for (int i =0; i<m_d->colorChannelCount; i++) {
channelValuesDisplay[i] = channelValues[m_d->displayPosition[i]];
}
if (m_d->exposureSupported) {
channelValuesDisplay /= m_d->channelMaxValues;
}
if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) {
if (m_d->isRGBA == true) {
if (m_d->isLinear) {
for (int i=0; i<3; i++) {
channelValuesDisplay[i] = pow(channelValuesDisplay[i], 1/2.2);
}
}
if (m_d->model == ColorModel::HSV){
QVector3D hsv;
// TODO: handle undefined hue case (returns -1)
RGBToHSV(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsv[0], &hsv[1], &hsv[2]);
hsv[0] /= 360;
coordinates = QVector4D(hsv, 0.f);
} else if (m_d->model == ColorModel::HSL) {
QVector3D hsl;
RGBToHSL(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsl[0], &hsl[1], &hsl[2]);
hsl[0] /= 360;
coordinates = QVector4D(hsl, 0.f);
} else if (m_d->model == ColorModel::HSI) {
qreal hsi[3];
RGBToHSI(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsi[0], &hsi[1], &hsi[2]);
coordinates = QVector4D(hsi[0], hsi[1], hsi[2], 0.f);
} else if (m_d->model == ColorModel::HSY) {
QVector <qreal> luma = m_d->currentCS->lumaCoefficients();
qreal hsy[3];
RGBToHSY(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsy[0], &hsy[1], &hsy[2], luma[0], luma[1], luma[2]);
coordinates = QVector4D(hsy[0], hsy[1], hsy[2], 0.f);
}
for (int i=0; i<3; i++) {
coordinates[i] = qBound(0.f, coordinates[i], 1.f);
}
}
} else {
for (int i=0; i<4; i++) {
coordinates[i] = qBound(0.f, channelValuesDisplay[i], 1.f);
}
}
return coordinates;
}
void KisVisualColorSelector::configurationChanged()
{
if (m_d->updateTimer) {
m_d->updateTimer->start();
}
}
void KisVisualColorSelector::slotDisplayConfigurationChanged()
{
Q_ASSERT(m_d->displayRenderer);
if (m_d->currentCS)
{
m_d->channelMaxValues = QVector4D(1, 1, 1, 1);
QList<KoChannelInfo *> channels = m_d->currentCS->channels();
for (int i=0; i<m_d->colorChannelCount; ++i)
{
m_d->channelMaxValues[i] = m_d->displayRenderer->maxVisibleFloatValue(channels[m_d->displayPosition[i]]);
}
// need to re-scale our normalized channel values on exposure changes:
m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor);
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setChannelValues(m_d->channelValues, true);
}
}
}
void KisVisualColorSelector::slotRebuildSelectors()
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
QList<KoChannelInfo *> channelList = m_d->currentCS->channels();
int cCount = 0;
Q_FOREACH(const KoChannelInfo *channel, channelList)
{
if (channel->channelType() != KoChannelInfo::ALPHA)
{
m_d->displayPosition[cCount] = channel->displayPosition();
++cCount;
}
}
Q_ASSERT_X(cCount < 5, "", "unsupported channel count!");
m_d->colorChannelCount = cCount;
// TODO: The following is done because the IDs are actually strings. Ideally, in the future, we
// refactor everything so that the IDs are actually proper enums or something faster.
if (m_d->displayRenderer
&& (m_d->currentCS->colorDepthId() == Float16BitsColorDepthID
|| m_d->currentCS->colorDepthId() == Float32BitsColorDepthID
|| m_d->currentCS->colorDepthId() == Float64BitsColorDepthID)
&& m_d->currentCS->colorModelId() != LABAColorModelID
&& m_d->currentCS->colorModelId() != CMYKAColorModelID) {
m_d->exposureSupported = true;
} else {
m_d->exposureSupported = false;
}
m_d->isRGBA = (m_d->currentCS->colorModelId() == RGBAColorModelID);
const KoColorProfile *profile = m_d->currentCS->profile();
m_d->isLinear = (profile && profile->isLinear());
qDeleteAll(children());
m_d->widgetlist.clear();
// TODO: Layout only used for monochrome selector currently, but always present
QLayout *layout = new QHBoxLayout;
//recreate all the widgets.
m_d->model = KisVisualColorSelector::Channel;
if (m_d->currentCS->colorChannelCount() == 1) {
KisVisualColorSelectorShape *bar;
if (m_d->circular==false) {
bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, 0, 0,m_d->displayRenderer, 20);
bar->setMaximumWidth(width()*0.1);
bar->setMaximumHeight(height());
}
else {
bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, 0, 0,m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored);
layout->setMargin(0);
}
connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
layout->addWidget(bar);
m_d->widgetlist.append(bar);
}
else if (m_d->currentCS->colorChannelCount() == 3) {
KisVisualColorSelector::ColorModel modelS = KisVisualColorSelector::HSV;
int channel1 = 0;
int channel2 = 1;
int channel3 = 2;
switch(m_d->acs_config.subTypeParameter)
{
case KisColorSelectorConfiguration::H:
channel1 = 0;
break;
case KisColorSelectorConfiguration::hsyS:
case KisColorSelectorConfiguration::hsiS:
case KisColorSelectorConfiguration::hslS:
case KisColorSelectorConfiguration::hsvS:
channel1 = 1;
break;
case KisColorSelectorConfiguration::V:
case KisColorSelectorConfiguration::L:
case KisColorSelectorConfiguration::I:
case KisColorSelectorConfiguration::Y:
channel1 = 2;
break;
default:
Q_ASSERT_X(false, "", "Invalid acs_config.subTypeParameter");
}
switch(m_d->acs_config.mainTypeParameter)
{
case KisColorSelectorConfiguration::hsySH:
modelS = KisVisualColorSelector::HSY;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::hsiSH:
modelS = KisVisualColorSelector::HSI;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::hslSH:
modelS = KisVisualColorSelector::HSL;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::hsvSH:
modelS = KisVisualColorSelector::HSV;
channel2 = 0;
channel3 = 1;
break;
case KisColorSelectorConfiguration::YH:
modelS = KisVisualColorSelector::HSY;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::LH:
modelS = KisVisualColorSelector::HSL;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::IH:
modelS = KisVisualColorSelector::HSL;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::VH:
modelS = KisVisualColorSelector::HSV;
channel2 = 0;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SY:
modelS = KisVisualColorSelector::HSY;
channel2 = 1;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SI:
modelS = KisVisualColorSelector::HSI;
channel2 = 1;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SL:
modelS = KisVisualColorSelector::HSL;
channel2 = 1;
channel3 = 2;
break;
case KisColorSelectorConfiguration::SV:
case KisColorSelectorConfiguration::SV2:
modelS = KisVisualColorSelector::HSV;
channel2 = 1;
channel3 = 2;
break;
default:
Q_ASSERT_X(false, "", "Invalid acs_config.mainTypeParameter");
}
if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
modelS = KisVisualColorSelector::HSV;
//Triangle only really works in HSV mode.
}
m_d->model = modelS;
KisVisualColorSelectorShape *bar;
if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) {
bar = new KisVisualEllipticalSelectorShape(this,
KisVisualColorSelectorShape::onedimensional,
m_d->currentCS, channel1, channel1,
m_d->displayRenderer, 20,KisVisualEllipticalSelectorShape::border);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == false) {
bar = new KisVisualRectangleSelectorShape(this,
KisVisualColorSelectorShape::onedimensional,
m_d->currentCS, channel1, channel1,
m_d->displayRenderer, 20);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == true) {
bar = new KisVisualEllipticalSelectorShape(this,
KisVisualColorSelectorShape::onedimensional,
m_d->currentCS, channel1, channel1,
m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored);
} else {
// Accessing bar below would crash since it's not initialized.
// Hopefully this can never happen.
warnUI << "Invalid subType, cannot initialize KisVisualColorSelectorShape";
Q_ASSERT_X(false, "", "Invalid subType, cannot initialize KisVisualColorSelectorShape");
return;
}
m_d->widgetlist.append(bar);
KisVisualColorSelectorShape *block;
if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
block = new KisVisualTriangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional,
m_d->currentCS, channel2, channel3,
m_d->displayRenderer);
}
else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) {
block = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional,
m_d->currentCS, channel2, channel3,
m_d->displayRenderer);
}
else {
block = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::twodimensional,
m_d->currentCS, channel2, channel3,
m_d->displayRenderer);
}
connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
m_d->widgetlist.append(block);
}
else if (m_d->currentCS->colorChannelCount() == 4) {
KisVisualRectangleSelectorShape *block = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional, m_d->currentCS, 0, 1);
KisVisualRectangleSelectorShape *block2 = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional, m_d->currentCS, 2, 3);
connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
connect(block2, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF)));
m_d->widgetlist.append(block);
m_d->widgetlist.append(block2);
}
this->setLayout(layout);
// make sure we call "our" resize function
KisVisualColorSelector::resizeEvent(0);
// finally recalculate channel values and update widgets
if (m_d->displayRenderer) {
slotDisplayConfigurationChanged();
}
m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor);
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setChannelValues(m_d->channelValues, true);
// if this widget is currently visible, new children are hidden by default
shape->show();
}
}
void KisVisualColorSelector::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) {
m_d->displayRenderer = displayRenderer;
if (m_d->widgetlist.size()>0) {
Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) {
shape->setDisplayRenderer(displayRenderer);
}
}
connect(m_d->displayRenderer, SIGNAL(displayConfigurationChanged()),
SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection);
slotDisplayConfigurationChanged();
}
void KisVisualColorSelector::slotCursorMoved(QPointF pos)
{
const KisVisualColorSelectorShape *shape = qobject_cast<KisVisualColorSelectorShape *>(sender());
Q_ASSERT(shape);
QVector<int> channels = shape->getChannels();
m_d->channelValues[channels.at(0)] = pos.x();
- if (shape->getDimensions() == KisVisualColorSelectorShape::twodimensional)
- {
+ if (shape->getDimensions() == KisVisualColorSelectorShape::twodimensional) {
m_d->channelValues[channels.at(1)] = pos.y();
}
KoColor newColor = convertShapeCoordsToKoColor(m_d->channelValues);
- if (newColor != m_d->currentcolor)
- {
+ if (newColor != m_d->currentcolor) {
m_d->currentcolor = newColor;
-
- Q_FOREACH (KisVisualColorSelectorShape *widget, m_d->widgetlist) {
- if (widget != shape){
- widget->setChannelValues(m_d->channelValues, false);
- }
- }
emit sigNewColor(m_d->currentcolor);
}
+ Q_FOREACH (KisVisualColorSelectorShape *widget, m_d->widgetlist) {
+ if (widget != shape){
+ widget->setChannelValues(m_d->channelValues, false);
+ }
+ }
}
void KisVisualColorSelector::resizeEvent(QResizeEvent *) {
int sizeValue = qMin(width(), height());
int borderWidth = qMax(sizeValue*0.1, 20.0);
QRect newrect(0,0, this->geometry().width(), this->geometry().height());
if (!m_d->currentCS) {
slotsetColorSpace(m_d->currentcolor.colorSpace());
}
if (m_d->currentCS->colorChannelCount()==3) {
// set border width first, else the resized painting may have happened already, and we'd have to re-render
m_d->widgetlist.at(0)->setBorderWidth(borderWidth);
if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) {
m_d->widgetlist.at(0)->resize(sizeValue,sizeValue);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==false) {
m_d->widgetlist.at(0)->resize(borderWidth, sizeValue);
}
else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==true) {
m_d->widgetlist.at(0)->resize(sizeValue,sizeValue);
}
if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForTriangle(newrect));
}
else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) {
m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForSquare(newrect));
}
else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Wheel) {
m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForCircle(newrect));
}
}
else if (m_d->currentCS->colorChannelCount() == 4) {
int sizeBlock = qMin(width()/2 - 8, height());
m_d->widgetlist.at(0)->setGeometry(0, 0, sizeBlock, sizeBlock);
m_d->widgetlist.at(1)->setGeometry(sizeBlock + 8, 0, sizeBlock, sizeBlock);
}
}
diff --git a/libs/widgets/KoFontComboBox.h b/libs/widgets/KoFontComboBox.h
deleted file mode 100644
index 5f7d607e1a..0000000000
--- a/libs/widgets/KoFontComboBox.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (c) 2014 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.
- */
-#ifndef _KO_FONT_COMBO_BOX_H_
-#define _KO_FONT_COMBO_BOX_H_
-
-#include <QFontComboBox>
-typedef QFontComboBox KoFontComboBox;
-
-#endif // _KO_FONT_COMBO_BOX_H_
diff --git a/libs/widgets/KoItemToolTip.cpp b/libs/widgets/KoItemToolTip.cpp
index 3834ff54bf..0a378d1915 100644
--- a/libs/widgets/KoItemToolTip.cpp
+++ b/libs/widgets/KoItemToolTip.cpp
@@ -1,141 +1,140 @@
/*
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 "KoItemToolTip.h"
#include <QApplication>
#include <QBasicTimer>
#include <QDesktopWidget>
#include <QModelIndex>
#include <QPainter>
#include <QPaintEvent>
#include <QPersistentModelIndex>
#include <QStyleOptionViewItem>
#include <QTextDocument>
#include <QTimerEvent>
#include <QToolTip>
class Q_DECL_HIDDEN KoItemToolTip::Private
{
public:
QTextDocument *document;
QPersistentModelIndex index;
QPoint pos;
QBasicTimer timer;
Private(): document(0) { }
};
KoItemToolTip::KoItemToolTip()
: d(new Private)
{
d->document = new QTextDocument(this);
setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip
| Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
QApplication::instance()->installEventFilter(this);
}
KoItemToolTip::~KoItemToolTip()
{
delete d;
}
void KoItemToolTip::showTip(QWidget *widget, const QPoint &pos, const QStyleOptionViewItem &option, const QModelIndex &index)
{
QTextDocument *doc = createDocument(index);
QPoint p = (isVisible() && index == d->index) ? d->pos : pos;
if (!isVisible() || index != d->index || doc->toHtml() != d->document->toHtml())
{
d->pos = p;
d->index = index;
delete d->document;
d->document = doc;
updatePosition(widget, p, option);
if (!isVisible())
show();
else
update();
d->timer.start(10000, this);
}
else
delete doc;
}
void KoItemToolTip::updatePosition(QWidget *widget, const QPoint &pos, const QStyleOptionViewItem &option)
{
const QRect drect = QApplication::desktop()->availableGeometry(widget);
const QSize size = sizeHint();
const int width = size.width(), height = size.height();
const QPoint gpos = widget->mapToGlobal(pos);
const QRect irect(widget->mapToGlobal(option.rect.topLeft()), option.rect.size());
int y = gpos.y() + 20;
if (y + height > drect.bottom())
y = qMax(drect.top(), irect.top() - height);
int x;
if (gpos.x() + width < drect.right())
x = gpos.x();
else
x = qMax(drect.left(), gpos.x() - width);
move(x, y);
resize(sizeHint());
}
QSize KoItemToolTip::sizeHint() const
{
return d->document->size().toSize();
}
void KoItemToolTip::paintEvent(QPaintEvent*)
{
QPainter p(this);
- p.begin(this);
d->document->drawContents(&p, rect());
p.drawRect(0, 0, width() - 1, height() - 1);
}
void KoItemToolTip::timerEvent(QTimerEvent *e)
{
if (e->timerId() == d->timer.timerId()) {
hide();
}
}
bool KoItemToolTip::eventFilter(QObject *object, QEvent *event)
{
switch(event->type())
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::FocusIn:
case QEvent::FocusOut:
case QEvent::Enter:
case QEvent::Leave:
hide();
default: break;
}
return QFrame::eventFilter(object, event);
}
diff --git a/libs/widgets/KoSliderCombo.cpp b/libs/widgets/KoSliderCombo.cpp
index f3d78735b3..15493157b9 100644
--- a/libs/widgets/KoSliderCombo.cpp
+++ b/libs/widgets/KoSliderCombo.cpp
@@ -1,291 +1,291 @@
/* This file is part of the KDE project
Copyright (c) 2007 C. Boemann <cbo@boemann.dk>
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 "KoSliderCombo.h"
#include "KoSliderCombo_p.h"
#include <QTimer>
#include <QApplication>
#include <QSize>
#include <QSlider>
#include <QStyle>
#include <QStylePainter>
#include <QStyleOptionSlider>
#include <QLineEdit>
#include <QValidator>
#include <QHBoxLayout>
#include <QFrame>
#include <QMenu>
#include <QMouseEvent>
#include <QDoubleSpinBox>
#include <QDesktopWidget>
#include <klocalizedstring.h>
#include <WidgetsDebug.h>
KoSliderCombo::KoSliderCombo(QWidget *parent)
: QComboBox(parent)
,d(new KoSliderComboPrivate())
{
d->thePublic = this;
d->minimum = 0.0;
d->maximum = 100.0;
d->decimals = 2;
d->container = new KoSliderComboContainer(this);
d->container->setAttribute(Qt::WA_WindowPropagation);
QStyleOptionComboBox opt;
opt.initFrom(this);
// d->container->setFrameStyle(style()->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &opt, this));
d->slider = new QSlider(Qt::Horizontal);
d->slider->setMinimum(0);
d->slider->setMaximum(256);
d->slider->setPageStep(10);
d->slider->setValue(0);
// When set to true, causes flicker on Qt 4.6. Any reason to keep it?
d->firstShowOfSlider = false; //true;
QHBoxLayout * l = new QHBoxLayout();
l->setMargin(2);
l->setSpacing(2);
l->addWidget(d->slider);
d->container->setLayout(l);
d->container->resize(200, 30);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
setEditable(true);
setEditText(QLocale().toString(0.0, d->decimals));
connect(d->slider, SIGNAL(valueChanged(int)), SLOT(sliderValueChanged(int)));
connect(d->slider, SIGNAL(sliderReleased()), SLOT(sliderReleased()));
connect(lineEdit(), SIGNAL(editingFinished()), SLOT(lineEditFinished()));
}
KoSliderCombo::~KoSliderCombo()
{
delete d;
}
QSize KoSliderCombo::sizeHint() const
{
return minimumSizeHint();
}
QSize KoSliderCombo::minimumSizeHint() const
{
QSize sh;
const QFontMetrics &fm = fontMetrics();
sh.setWidth(5 * fm.width(QLatin1Char('8')));
sh.setHeight(qMax(fm.lineSpacing(), 14) + 2);
// add style and strut values
QStyleOptionComboBox opt;
opt.initFrom(this);
opt.subControls = QStyle::SC_All;
opt.editable = true;
sh = style()->sizeFromContents(QStyle::CT_ComboBox, &opt, sh, this);
- return sh.expandedTo(QApplication::globalStrut());
+ return sh;
}
void KoSliderCombo::KoSliderComboPrivate::showPopup()
{
if(firstShowOfSlider) {
container->show(); //show container a bit early so the slider can be layout'ed
firstShowOfSlider = false;
}
QStyleOptionSlider opt;
opt.initFrom(slider);
opt.maximum=256;
opt.sliderPosition = opt.sliderValue = slider->value();
int hdlPos = thePublic->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle).center().x();
QStyleOptionComboBox optThis;
optThis.initFrom(thePublic);
optThis.subControls = QStyle::SC_All;
optThis.editable = true;
int arrowPos = thePublic->style()->subControlRect(QStyle::CC_ComboBox, &optThis, QStyle::SC_ComboBoxArrow).center().x();
QSize popSize = container->size();
QRect popupRect(thePublic->mapToGlobal(QPoint(arrowPos - hdlPos - slider->x(), thePublic->size().height())), popSize);
// Make sure the popup is not drawn outside the screen area
QRect screenRect = QApplication::desktop()->availableGeometry(thePublic);
if (popupRect.right() > screenRect.right())
popupRect.translate(screenRect.right() - popupRect.right(), 0);
if (popupRect.left() < screenRect.left())
popupRect.translate(screenRect.left() - popupRect.left(), 0);
if (popupRect.bottom() > screenRect.bottom())
popupRect.translate(0, -(thePublic->height() + container->height()));
container->setGeometry(popupRect);
container->raise();
container->show();
slider->setFocus();
}
void KoSliderCombo::KoSliderComboPrivate::hidePopup()
{
container->hide();
}
void KoSliderCombo::hideEvent(QHideEvent *)
{
d->hidePopup();
}
void KoSliderCombo::changeEvent(QEvent *e)
{
switch (e->type())
{
case QEvent::EnabledChange:
if (!isEnabled())
d->hidePopup();
break;
case QEvent::PaletteChange:
d->container->setPalette(palette());
break;
default:
break;
}
QComboBox::changeEvent(e);
}
void KoSliderCombo::paintEvent(QPaintEvent *)
{
QStylePainter gc(this);
gc.setPen(palette().color(QPalette::Text));
QStyleOptionComboBox opt;
opt.initFrom(this);
opt.subControls = QStyle::SC_All;
opt.editable = true;
gc.drawComplexControl(QStyle::CC_ComboBox, opt);
gc.drawControl(QStyle::CE_ComboBoxLabel, opt);
}
void KoSliderCombo::mousePressEvent(QMouseEvent *e)
{
QStyleOptionComboBox opt;
opt.initFrom(this);
opt.subControls = QStyle::SC_All;
opt.editable = true;
QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, e->pos(),
this);
if (sc == QStyle::SC_ComboBoxArrow && !d->container->isVisible())
{
d->showPopup();
}
else
QComboBox::mousePressEvent(e);
}
void KoSliderCombo::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Up) setValue(value() + d->slider->singleStep() * (maximum() - minimum()) / 256 + 0.5);
else if (e->key() == Qt::Key_Down) setValue(value() - d->slider->singleStep() * (maximum() - minimum()) / 256 - 0.5);
else QComboBox::keyPressEvent(e);
}
void KoSliderCombo::wheelEvent(QWheelEvent *e)
{
if (e->delta() > 0) setValue(value() + d->slider->singleStep() * (maximum() - minimum()) / 256 + 0.5);
else setValue(value() - d->slider->singleStep() * (maximum() - minimum()) / 256 - 0.5);
}
void KoSliderCombo::KoSliderComboPrivate::lineEditFinished()
{
qreal value = QLocale().toDouble(thePublic->currentText());
slider->blockSignals(true);
slider->setValue(int((value - minimum) * 256 / (maximum - minimum) + 0.5));
slider->blockSignals(false);
emit thePublic->valueChanged(value, true);
}
void KoSliderCombo::KoSliderComboPrivate::sliderValueChanged(int slidervalue)
{
thePublic->setEditText(QLocale().toString(minimum + (maximum - minimum)*slidervalue/256, decimals));
qreal value = QLocale().toDouble(thePublic->currentText());
emit thePublic->valueChanged(value, false);
}
void KoSliderCombo::KoSliderComboPrivate::sliderReleased()
{
qreal value = QLocale().toDouble(thePublic->currentText());
emit thePublic->valueChanged(value, true);
}
qreal KoSliderCombo::maximum() const
{
return d->maximum;
}
qreal KoSliderCombo::minimum() const
{
return d->minimum;
}
qreal KoSliderCombo::decimals() const
{
return d->decimals;
}
qreal KoSliderCombo::value() const
{
return QLocale().toDouble(currentText());
}
void KoSliderCombo::setDecimals(int dec)
{
d->decimals = dec;
if (dec == 0) lineEdit()->setValidator(new QIntValidator(this));
else lineEdit()->setValidator(new QDoubleValidator(this));
}
void KoSliderCombo::setMinimum(qreal min)
{
d->minimum = min;
}
void KoSliderCombo::setMaximum(qreal max)
{
d->maximum = max;
}
void KoSliderCombo::setValue(qreal value)
{
if(value < d->minimum)
value = d->minimum;
if(value > d->maximum)
value = d->maximum;
setEditText(QLocale().toString(value, d->decimals));
d->slider->blockSignals(true);
d->slider->setValue(int((value - d->minimum) * 256 / (d->maximum - d->minimum) + 0.5));
d->slider->blockSignals(false);
emit valueChanged(value, true);
}
//have to include this because of Q_PRIVATE_SLOT
#include <moc_KoSliderCombo.cpp>
diff --git a/libs/widgets/KoStrokeConfigWidget.cpp b/libs/widgets/KoStrokeConfigWidget.cpp
index fe0775bf34..2e394d97ab 100644
--- a/libs/widgets/KoStrokeConfigWidget.cpp
+++ b/libs/widgets/KoStrokeConfigWidget.cpp
@@ -1,571 +1,571 @@
/* This file is part of the KDE project
* Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr)
* Copyright (C) 2002 Tomislav Lukman <tomislav.lukman@ck.t-com.hr>
* Copyright (C) 2002-2003 Rob Buis <buis@kde.org>
* Copyright (C) 2005-2006 Tim Beaulen <tbscope@gmail.com>
* Copyright (C) 2005-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2005-2006, 2011 Inge Wallin <inge@lysator.liu.se>
* Copyright (C) 2005-2008 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2006 Peter Simonsson <psn@linux.se>
* Copyright (C) 2006 Laurent Montel <montel@kde.org>
* Copyright (C) 2007,2011 Thorsten Zachmann <t.zachmann@zagge.de>
* Copyright (C) 2011 Jean-Nicolas Artaud <jeannicolasartaud@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.
*/
// Own
#include "KoStrokeConfigWidget.h"
// Qt
#include <QMenu>
#include <QLabel>
#include <QToolButton>
#include <QButtonGroup>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QSizePolicy>
// KDE
#include <klocalizedstring.h>
// Calligra
#include <KoIcon.h>
#include <KoUnit.h>
#include <KoLineStyleSelector.h>
#include <KoUnitDoubleSpinBox.h>
#include <KoMarkerSelector.h>
#include <KoColorPopupAction.h>
#include <KoMarker.h>
#include <KoShapeStroke.h>
#include <KoPathShape.h>
#include <KoMarkerCollection.h>
#include <KoPathShapeMarkerCommand.h>
#include <KoCanvasBase.h>
#include <KoCanvasController.h>
#include <KoCanvasResourceProvider.h>
#include <KoDocumentResourceManager.h>
#include <KoToolManager.h>
#include <KoSelection.h>
#include <KoShapeController.h>
#include <KoShapeManager.h>
#include <KoShapeStrokeCommand.h>
#include <KoShapeStrokeModel.h>
// Krita
#include "kis_double_parse_unit_spin_box.h"
class CapNJoinMenu : public QMenu
{
public:
CapNJoinMenu(QWidget *parent = 0);
QSize sizeHint() const override;
KisDoubleParseUnitSpinBox *miterLimit;
QButtonGroup *capGroup;
QButtonGroup *joinGroup;
};
CapNJoinMenu::CapNJoinMenu(QWidget *parent)
: QMenu(parent)
{
QGridLayout *mainLayout = new QGridLayout();
mainLayout->setMargin(2);
// The cap group
capGroup = new QButtonGroup(this);
capGroup->setExclusive(true);
QToolButton *button = 0;
button = new QToolButton(this);
button->setIcon(koIcon("stroke-cap-butt"));
button->setCheckable(true);
button->setToolTip(i18n("Butt cap"));
capGroup->addButton(button, Qt::FlatCap);
mainLayout->addWidget(button, 2, 0);
button = new QToolButton(this);
button->setIcon(koIcon("stroke-cap-round"));
button->setCheckable(true);
button->setToolTip(i18n("Round cap"));
capGroup->addButton(button, Qt::RoundCap);
mainLayout->addWidget(button, 2, 1);
button = new QToolButton(this);
button->setIcon(koIcon("stroke-cap-square"));
button->setCheckable(true);
button->setToolTip(i18n("Square cap"));
capGroup->addButton(button, Qt::SquareCap);
mainLayout->addWidget(button, 2, 2, Qt::AlignLeft);
// The join group
joinGroup = new QButtonGroup(this);
joinGroup->setExclusive(true);
button = new QToolButton(this);
button->setIcon(koIcon("stroke-join-miter"));
button->setCheckable(true);
button->setToolTip(i18n("Miter join"));
joinGroup->addButton(button, Qt::MiterJoin);
mainLayout->addWidget(button, 3, 0);
button = new QToolButton(this);
button->setIcon(koIcon("stroke-join-round"));
button->setCheckable(true);
button->setToolTip(i18n("Round join"));
joinGroup->addButton(button, Qt::RoundJoin);
mainLayout->addWidget(button, 3, 1);
button = new QToolButton(this);
button->setIcon(koIcon("stroke-join-bevel"));
button->setCheckable(true);
button->setToolTip(i18n("Bevel join"));
joinGroup->addButton(button, Qt::BevelJoin);
mainLayout->addWidget(button, 3, 2, Qt::AlignLeft);
// Miter limit
// set min/max/step and value in points, then set actual unit
miterLimit = new KisDoubleParseUnitSpinBox(this);
miterLimit->setMinMaxStep(0.0, 1000.0, 0.5);
miterLimit->setDecimals(2);
miterLimit->setToolTip(i18n("Miter limit"));
miterLimit->setUnitChangeFromOutsideBehavior(false);
mainLayout->addWidget(miterLimit, 4, 0, 1, 3);
mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
setLayout(mainLayout);
}
QSize CapNJoinMenu::sizeHint() const
{
return layout()->sizeHint();
}
class Q_DECL_HIDDEN KoStrokeConfigWidget::Private
{
public:
Private()
: canvas(0),
active(true),
allowLocalUnitManagement(true)
{
}
KoLineStyleSelector *lineStyle;
KisDoubleParseUnitSpinBox *lineWidth;
KoMarkerSelector *startMarkerSelector;
KoMarkerSelector *endMarkerSelector;
CapNJoinMenu *capNJoinMenu;
QToolButton *colorButton;
KoColorPopupAction *colorAction;
QWidget *spacer;
KoCanvasBase *canvas;
bool active;
bool allowLocalUnitManagement;
};
KoStrokeConfigWidget::KoStrokeConfigWidget(QWidget * parent)
: QWidget(parent)
, d(new Private())
{
setObjectName("Stroke widget");
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setMargin(0);
QHBoxLayout *firstLineLayout = new QHBoxLayout();
// Start marker
QList<KoMarker*> markers;
d->startMarkerSelector = new KoMarkerSelector(KoMarkerData::MarkerStart, this);
d->startMarkerSelector->updateMarkers(markers);
d->startMarkerSelector->setMaximumWidth(50);
firstLineLayout->addWidget(d->startMarkerSelector);
// Line style
d->lineStyle = new KoLineStyleSelector(this);
d->lineStyle->setMinimumWidth(70);
firstLineLayout->addWidget(d->lineStyle);
// End marker
d->endMarkerSelector = new KoMarkerSelector(KoMarkerData::MarkerEnd, this);
d->endMarkerSelector->updateMarkers(markers);
d->endMarkerSelector->setMaximumWidth(50);
firstLineLayout->addWidget(d->endMarkerSelector);
QHBoxLayout *secondLineLayout = new QHBoxLayout();
// Line width
QLabel *l = new QLabel(this);
l->setText(i18n("Thickness:"));
l->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
secondLineLayout->addWidget(l);
// set min/max/step and value in points, then set actual unit
d->lineWidth = new KisDoubleParseUnitSpinBox(this);
d->lineWidth->setMinMaxStep(0.0, 1000.0, 0.5);
d->lineWidth->setDecimals(2);
d->lineWidth->setUnitChangeFromOutsideBehavior(false);
d->lineWidth->setToolTip(i18n("Set line width of actual selection"));
secondLineLayout->addWidget(d->lineWidth);
QToolButton *capNJoinButton = new QToolButton(this);
capNJoinButton->setMinimumHeight(25);
d->capNJoinMenu = new CapNJoinMenu(this);
capNJoinButton->setMenu(d->capNJoinMenu);
capNJoinButton->setText("...");
capNJoinButton->setPopupMode(QToolButton::InstantPopup);
secondLineLayout->addWidget(capNJoinButton);
d->colorButton = new QToolButton(this);
secondLineLayout->addWidget(d->colorButton);
d->colorAction = new KoColorPopupAction(this);
d->colorAction->setIcon(koIcon("format-stroke-color"));
d->colorAction->setToolTip(i18n("Change the color of the line/border"));
d->colorButton->setDefaultAction(d->colorAction);
mainLayout->addLayout(firstLineLayout);
mainLayout->addLayout(secondLineLayout);
// Spacer
d->spacer = new QWidget();
d->spacer->setObjectName("SpecialSpacer");
mainLayout->addWidget(d->spacer);
// set sensitive defaults
d->lineStyle->setLineStyle(Qt::SolidLine);
d->lineWidth->changeValue(1);
d->colorAction->setCurrentColor(Qt::black);
// Make the signals visible on the outside of this widget.
connect(d->lineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(applyChanges()));
connect(d->lineWidth, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyChanges()));
connect(d->colorAction, SIGNAL(colorChanged(KoColor)), this, SLOT(applyChanges()));
connect(d->capNJoinMenu->capGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyChanges()));
connect(d->capNJoinMenu->joinGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyChanges()));
connect(d->capNJoinMenu->miterLimit, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyChanges()));
connect(d->startMarkerSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(startMarkerChanged()));
connect(d->endMarkerSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(endMarkerChanged()));
}
KoStrokeConfigWidget::~KoStrokeConfigWidget()
{
delete d;
}
// ----------------------------------------------------------------
// getters and setters
Qt::PenStyle KoStrokeConfigWidget::lineStyle() const
{
return d->lineStyle->lineStyle();
}
QVector<qreal> KoStrokeConfigWidget::lineDashes() const
{
return d->lineStyle->lineDashes();
}
qreal KoStrokeConfigWidget::lineWidth() const
{
return d->lineWidth->value();
}
QColor KoStrokeConfigWidget::color() const
{
return d->colorAction->currentColor();
}
qreal KoStrokeConfigWidget::miterLimit() const
{
return d->capNJoinMenu->miterLimit->value();
}
KoMarker *KoStrokeConfigWidget::startMarker() const
{
return d->startMarkerSelector->marker();
}
KoMarker *KoStrokeConfigWidget::endMarker() const
{
return d->endMarkerSelector->marker();
}
Qt::PenCapStyle KoStrokeConfigWidget::capStyle() const
{
return static_cast<Qt::PenCapStyle>(d->capNJoinMenu->capGroup->checkedId());
}
Qt::PenJoinStyle KoStrokeConfigWidget::joinStyle() const
{
return static_cast<Qt::PenJoinStyle>(d->capNJoinMenu->joinGroup->checkedId());
}
KoShapeStroke* KoStrokeConfigWidget::createShapeStroke() const
{
KoShapeStroke *stroke = new KoShapeStroke();
stroke->setColor(color());
stroke->setLineWidth(lineWidth());
stroke->setCapStyle(capStyle());
stroke->setJoinStyle(joinStyle());
stroke->setMiterLimit(miterLimit());
stroke->setLineStyle(lineStyle(), lineDashes());
return stroke;
}
// ----------------------------------------------------------------
// Other public functions
void KoStrokeConfigWidget::updateControls(KoShapeStrokeModel *stroke, KoMarker *startMarker, KoMarker *endMarker)
{
blockChildSignals(true);
const KoShapeStroke *lineStroke = dynamic_cast<const KoShapeStroke*>(stroke);
if (lineStroke) {
d->lineWidth->changeValue(lineStroke->lineWidth());
QAbstractButton *button = d->capNJoinMenu->capGroup->button(lineStroke->capStyle());
if (button) {
button->setChecked(true);
}
button = d->capNJoinMenu->joinGroup->button(lineStroke->joinStyle());
if (button) {
button->setChecked(true);
}
d->capNJoinMenu->miterLimit->changeValue(lineStroke->miterLimit());
d->capNJoinMenu->miterLimit->setEnabled(lineStroke->joinStyle() == Qt::MiterJoin);
d->lineStyle->setLineStyle(lineStroke->lineStyle(), lineStroke->lineDashes());
d->colorAction->setCurrentColor(lineStroke->color());
}
else {
d->lineWidth->changeValue(0.0);
d->capNJoinMenu->capGroup->button(Qt::FlatCap)->setChecked(true);
d->capNJoinMenu->joinGroup->button(Qt::MiterJoin)->setChecked(true);
d->capNJoinMenu->miterLimit->changeValue(0.0);
d->capNJoinMenu->miterLimit->setEnabled(true);
d->lineStyle->setLineStyle(Qt::NoPen, QVector<qreal>());
}
d->startMarkerSelector->setMarker(startMarker);
d->endMarkerSelector->setMarker(endMarker);
blockChildSignals(false);
}
void KoStrokeConfigWidget::setUnit(const KoUnit &unit)
{
if (!d->allowLocalUnitManagement) {
return; //the unit management is completely transferred to the unitManagers.
}
blockChildSignals(true);
KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController();
KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
KoShape * shape = selection->firstSelectedShape();
/**
* KoStrokeShape knows nothing about the transformations applied
* to the shape, which doesn't prevent the shape to apply them and
* display the stroke differently. So just take that into account
* and show the user correct values using the multiplier in KoUnit.
*/
KoUnit newUnit(unit);
if (shape) {
- newUnit.adjustByPixelTransform(shape->absoluteTransformation(0));
+ newUnit.adjustByPixelTransform(shape->absoluteTransformation());
}
d->lineWidth->setUnit(newUnit);
d->capNJoinMenu->miterLimit->setUnit(newUnit);
blockChildSignals(false);
}
void KoStrokeConfigWidget::updateMarkers(const QList<KoMarker*> &markers)
{
d->startMarkerSelector->updateMarkers(markers);
d->endMarkerSelector->updateMarkers(markers);
}
void KoStrokeConfigWidget::blockChildSignals(bool block)
{
d->colorAction->blockSignals(block);
d->lineWidth->blockSignals(block);
d->capNJoinMenu->capGroup->blockSignals(block);
d->capNJoinMenu->joinGroup->blockSignals(block);
d->capNJoinMenu->miterLimit->blockSignals(block);
d->lineStyle->blockSignals(block);
d->startMarkerSelector->blockSignals(block);
d->endMarkerSelector->blockSignals(block);
}
void KoStrokeConfigWidget::setActive(bool active)
{
d->active = active;
}
void KoStrokeConfigWidget::setUnitManagers(KisSpinBoxUnitManager* managerLineWidth,
KisSpinBoxUnitManager *managerMitterLimit)
{
blockChildSignals(true);
d->allowLocalUnitManagement = false;
d->lineWidth->setUnitManager(managerLineWidth);
d->capNJoinMenu->miterLimit->setUnitManager(managerMitterLimit);
blockChildSignals(false);
}
//------------------------
void KoStrokeConfigWidget::applyChanges()
{
KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController();
KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
//FIXME d->canvas->resourceManager()->setActiveStroke( d->stroke );
if (!selection || !selection->count()) {
return;
}
KoShapeStroke *newStroke = new KoShapeStroke();
KoShapeStroke *oldStroke = dynamic_cast<KoShapeStroke*>( selection->firstSelectedShape()->stroke() );
if (oldStroke) {
newStroke->setLineBrush(oldStroke->lineBrush());
}
newStroke->setColor(color());
newStroke->setLineWidth(lineWidth());
newStroke->setCapStyle(static_cast<Qt::PenCapStyle>(d->capNJoinMenu->capGroup->checkedId()));
newStroke->setJoinStyle(static_cast<Qt::PenJoinStyle>(d->capNJoinMenu->joinGroup->checkedId()));
newStroke->setMiterLimit(miterLimit());
newStroke->setLineStyle(lineStyle(), lineDashes());
if (d->active) {
KoShapeStrokeCommand *cmd = new KoShapeStrokeCommand(selection->selectedShapes(), newStroke);
canvasController->canvas()->addCommand(cmd);
}
}
void KoStrokeConfigWidget::applyMarkerChanges(KoMarkerData::MarkerPosition position)
{
KoMarker *marker = 0;
if (position == KoMarkerData::MarkerStart) {
marker = startMarker();
}
else if (position == KoMarkerData::MarkerEnd) {
marker = endMarker();
}
KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController();
KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
if (! selection || !selection->count()) {
return;
}
QList<KoShape*> shapeList = selection->selectedShapes();
QList<KoPathShape*> pathShapeList;
for (QList<KoShape*>::iterator itShape = shapeList.begin(); itShape != shapeList.end(); ++itShape) {
KoPathShape* pathShape = dynamic_cast<KoPathShape*>(*itShape);
if (pathShape) {
pathShapeList << pathShape;
}
}
if (pathShapeList.size()) {
KoPathShapeMarkerCommand* cmdMarker = new KoPathShapeMarkerCommand(pathShapeList, marker, position);
canvasController->canvas()->addCommand(cmdMarker);
}
}
void KoStrokeConfigWidget::startMarkerChanged()
{
applyMarkerChanges(KoMarkerData::MarkerStart);
}
void KoStrokeConfigWidget::endMarkerChanged()
{
applyMarkerChanges(KoMarkerData::MarkerEnd);
}
// ----------------------------------------------------------------
void KoStrokeConfigWidget::selectionChanged()
{
// see a comment in setUnit()
setUnit(d->canvas->unit());
KoCanvasController* canvasController = KoToolManager::instance()->activeCanvasController();
KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
KoShape * shape = selection->firstSelectedShape();
if (shape && shape->stroke()) {
KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
if (pathShape) {
updateControls(shape->stroke(), pathShape->marker(KoMarkerData::MarkerStart),
pathShape->marker(KoMarkerData::MarkerEnd));
}
else {
updateControls(shape->stroke(), 0 ,0);
}
}
}
void KoStrokeConfigWidget::setCanvas( KoCanvasBase *canvas )
{
if (canvas) {
connect(canvas->shapeManager()->selection(), SIGNAL(selectionChanged()),
this, SLOT(selectionChanged()));
connect(canvas->shapeManager(), SIGNAL(selectionContentChanged()),
this, SLOT(selectionChanged()));
connect(canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(canvasResourceChanged(int,QVariant)));
setUnit(canvas->unit());
KoDocumentResourceManager *resourceManager = canvas->shapeController()->resourceManager();
if (resourceManager) {
KoMarkerCollection *collection = resourceManager->resource(KoDocumentResourceManager::MarkerCollection).value<KoMarkerCollection*>();
if (collection) {
updateMarkers(collection->markers());
}
}
}
d->canvas = canvas;
}
void KoStrokeConfigWidget::canvasResourceChanged(int key, const QVariant &value)
{
switch (key) {
case KoCanvasResourceProvider::Unit:
setUnit(value.value<KoUnit>());
break;
}
}
diff --git a/libs/widgets/KoToolBox.cpp b/libs/widgets/KoToolBox.cpp
index efeaa4090f..360aa26f1d 100644
--- a/libs/widgets/KoToolBox.cpp
+++ b/libs/widgets/KoToolBox.cpp
@@ -1,342 +1,338 @@
/*
* Copyright (c) 2005-2009 Thomas Zander <zander@kde.org>
* Copyright (c) 2009 Peter Simonsson <peter.simonsson@gmail.com>
* Copyright (c) 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 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 "KoToolBox_p.h"
#include "KoToolBoxLayout_p.h"
#include "KoToolBoxButton_p.h"
+#include "kis_assert.h"
#include <QButtonGroup>
#include <QToolButton>
#include <QStyleOptionFrameV3>
#include <QPainter>
#include <QHash>
#include <QApplication>
#include <QStyle>
#include <QTimer>
#include <QMenu>
#include <QAction>
#include <QScreen>
#include <klocalizedstring.h>
#include <WidgetsDebug.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <KoCanvasController.h>
#include <KoShapeLayer.h>
#define BUTTON_MARGIN 10
static int buttonSize(int screen)
{
+ KIS_ASSERT_RECOVER_RETURN_VALUE(screen < QGuiApplication::screens().size() && screen >= 0, 16);
+
QRect rc = QGuiApplication::screens().at(screen)->availableGeometry();
if (rc.width() <= 1024) {
return 12;
}
else if (rc.width() <= 1377) {
return 14;
}
else if (rc.width() <= 1920 ) {
return 16;
}
else {
return 22;
}
+
}
class KoToolBox::Private
{
public:
- Private()
- : layout(0)
- , buttonGroup(0)
- , floating(false)
- , contextSize(0)
- {
- }
-
void addSection(Section *section, const QString &name);
QList<QToolButton*> buttons;
QMap<QString, Section*> sections;
- KoToolBoxLayout *layout;
- QButtonGroup *buttonGroup;
+ KoToolBoxLayout *layout {0};
+ QButtonGroup *buttonGroup {0};
QHash<QToolButton*, QString> visibilityCodes;
- bool floating;
+ bool floating {false};
QMap<QAction*,int> contextIconSizes;
- QMenu* contextSize;
- Qt::Orientation orientation;
+ QMenu *contextSize {0};
+ Qt::Orientation orientation {Qt::Vertical};
};
void KoToolBox::Private::addSection(Section *section, const QString &name)
{
section->setName(name);
layout->addSection(section);
sections.insert(name, section);
}
KoToolBox::KoToolBox()
: d(new Private)
{
d->layout = new KoToolBoxLayout(this);
// add defaults
d->addSection(new Section(this), "main");
d->addSection(new Section(this), "dynamic");
d->buttonGroup = new QButtonGroup(this);
setLayout(d->layout);
Q_FOREACH (KoToolAction *toolAction, KoToolManager::instance()->toolActionList()) {
addButton(toolAction);
}
// Update visibility of buttons
setButtonsVisible(QList<QString>());
connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)),
this, SLOT(setActiveTool(KoCanvasController*,int)));
connect(KoToolManager::instance(), SIGNAL(currentLayerChanged(const KoCanvasController*,const KoShapeLayer*)),
this, SLOT(setCurrentLayer(const KoCanvasController*,const KoShapeLayer*)));
connect(KoToolManager::instance(), SIGNAL(toolCodesSelected(QList<QString>)), this, SLOT(setButtonsVisible(QList<QString>)));
connect(KoToolManager::instance(),
SIGNAL(addedTool(KoToolAction*,KoCanvasController*)),
this, SLOT(toolAdded(KoToolAction*,KoCanvasController*)));
}
KoToolBox::~KoToolBox()
{
delete d;
}
void KoToolBox::addButton(KoToolAction *toolAction)
{
KoToolBoxButton *button = new KoToolBoxButton(toolAction, this);
d->buttons << button;
int toolbuttonSize = buttonSize(qApp->desktop()->screenNumber(this));
KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox");
int iconSize = cfg.readEntry("iconSize", toolbuttonSize);
button->setIconSize(QSize(iconSize, iconSize));
foreach (Section *section, d->sections.values()) {
section->setButtonSize(QSize(iconSize + BUTTON_MARGIN, iconSize + BUTTON_MARGIN));
}
QString sectionToBeAddedTo;
const QString section = toolAction->section();
if (section.contains(qApp->applicationName())) {
sectionToBeAddedTo = "main";
} else if (section.contains("main")) {
sectionToBeAddedTo = "main";
} else if (section.contains("dynamic")) {
sectionToBeAddedTo = "dynamic";
} else {
sectionToBeAddedTo = section;
}
Section *sectionWidget = d->sections.value(sectionToBeAddedTo);
if (sectionWidget == 0) {
sectionWidget = new Section(this);
d->addSection(sectionWidget, sectionToBeAddedTo);
}
sectionWidget->addButton(button, toolAction->priority());
d->buttonGroup->addButton(button, toolAction->buttonGroupId());
d->visibilityCodes.insert(button, toolAction->visibilityCode());
}
void KoToolBox::setActiveTool(KoCanvasController *canvas, int id)
{
Q_UNUSED(canvas);
QAbstractButton *button = d->buttonGroup->button(id);
if (button) {
button->setChecked(true);
(qobject_cast<KoToolBoxButton*>(button))->setHighlightColor();
}
else {
warnWidgets << "KoToolBox::setActiveTool(" << id << "): no such button found";
}
}
void KoToolBox::setButtonsVisible(const QList<QString> &codes)
{
Q_FOREACH (QToolButton *button, d->visibilityCodes.keys()) {
QString code = d->visibilityCodes.value(button);
if (code.startsWith(QLatin1String("flake/"))) {
continue;
}
if (code.endsWith( QLatin1String( "/always"))) {
button->setVisible(true);
button->setEnabled(true);
}
else if (code.isEmpty()) {
button->setVisible(true);
button->setEnabled( codes.count() != 0 );
}
else {
button->setVisible( codes.contains(code) );
}
}
layout()->invalidate();
update();
}
void KoToolBox::setCurrentLayer(const KoCanvasController *canvas, const KoShapeLayer *layer)
{
Q_UNUSED(canvas);
const bool enabled = layer == 0 || (layer->isShapeEditable() && layer->isVisible());
foreach (QToolButton *button, d->visibilityCodes.keys()) {
if (d->visibilityCodes[button].endsWith( QLatin1String( "/always") ) ) {
continue;
}
button->setEnabled(enabled);
}
}
void KoToolBox::paintEvent(QPaintEvent *)
{
QPainter painter(this);
const QList<Section*> sections = d->sections.values();
QList<Section*>::const_iterator iterator = sections.begin();
int halfSpacing = layout()->spacing();
if (halfSpacing > 0) {
halfSpacing /= 2;
}
while(iterator != sections.end()) {
Section *section = *iterator;
QStyleOption styleoption;
styleoption.palette = palette();
if (section->separators() & Section::SeparatorTop) {
int y = section->y() - halfSpacing;
styleoption.state = QStyle::State_None;
styleoption.rect = QRect(section->x(), y-1, section->width(), 2);
style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &styleoption, &painter);
}
if (section->separators() & Section::SeparatorLeft) {
int x = section->x() - halfSpacing;
styleoption.state = QStyle::State_Horizontal;
styleoption.rect = QRect(x-1, section->y(), 2, section->height());
style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &styleoption, &painter);
}
++iterator;
}
painter.end();
}
void KoToolBox::setOrientation(Qt::Orientation orientation)
{
d->orientation = orientation;
d->layout->setOrientation(orientation);
QTimer::singleShot(0, this, SLOT(update()));
Q_FOREACH (Section* section, d->sections) {
section->setOrientation(orientation);
}
}
void KoToolBox::setFloating(bool v)
{
d->floating = v;
}
void KoToolBox::toolAdded(KoToolAction *toolAction, KoCanvasController *canvas)
{
Q_UNUSED(canvas);
addButton(toolAction);
setButtonsVisible(QList<QString>());
}
void KoToolBox::slotContextIconSize()
{
QAction* action = qobject_cast<QAction*>(sender());
if (action && d->contextIconSizes.contains(action)) {
const int iconSize = d->contextIconSizes.value(action);
KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox");
cfg.writeEntry("iconSize", iconSize);
Q_FOREACH (QToolButton *button, d->buttons) {
button->setIconSize(QSize(iconSize, iconSize));
}
Q_FOREACH (Section *section, d->sections.values()) {
section->setButtonSize(QSize(iconSize + BUTTON_MARGIN, iconSize + BUTTON_MARGIN));
}
}
}
void KoToolBox::contextMenuEvent(QContextMenuEvent *event)
{
int toolbuttonSize = buttonSize(qApp->desktop()->screenNumber(this));
if (!d->contextSize) {
d->contextSize = new QMenu(i18n("Icon Size"), this);
d->contextIconSizes.insert(d->contextSize->addAction(i18nc("@item:inmenu Icon size", "Default"),
this, SLOT(slotContextIconSize())),
toolbuttonSize);
QList<int> sizes;
sizes << 12 << 14 << 16 << 22 << 32 << 48 << 64; //<< 96 << 128 << 192 << 256;
Q_FOREACH (int i, sizes) {
d->contextIconSizes.insert(d->contextSize->addAction(i18n("%1x%2", i, i), this, SLOT(slotContextIconSize())), i);
}
QActionGroup *sizeGroup = new QActionGroup(d->contextSize);
foreach (QAction *action, d->contextSize->actions()) {
action->setActionGroup(sizeGroup);
action->setCheckable(true);
}
}
KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox");
toolbuttonSize = cfg.readEntry("iconSize", toolbuttonSize);
QMapIterator< QAction*, int > it = d->contextIconSizes;
while (it.hasNext()) {
it.next();
if (it.value() == toolbuttonSize) {
it.key()->setChecked(true);
break;
}
}
d->contextSize->exec(event->globalPos());
}
KoToolBoxLayout *KoToolBox::toolBoxLayout() const
{
return d->layout;
}
#include "moc_KoToolBoxScrollArea_p.cpp"
diff --git a/libs/widgets/kis_color_button.cpp b/libs/widgets/kis_color_button.cpp
index 2e60eb5e47..49ba3729ce 100644
--- a/libs/widgets/kis_color_button.cpp
+++ b/libs/widgets/kis_color_button.cpp
@@ -1,387 +1,385 @@
/* This file is part of the KDE libraries
Copyright (C) 1997 Martin Jones (mjones@kde.org)
Copyright (C) 1999 Cristian Tibirna (ctibirna@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 "kis_color_button.h"
#include <QPointer>
#include <QPainter>
#include <qdrawutil.h>
#include <QApplication>
#include <QColorDialog>
#include <QClipboard>
#include <QMimeData>
#include <QDrag>
#include <QStyle>
#include <QMouseEvent>
#include <QStyleOptionButton>
#include <KoColor.h>
#include <KisDlgInternalColorSelector.h>
class KisColorButton::KisColorButtonPrivate
{
public:
KisColorButtonPrivate(KisColorButton *q);
~KisColorButtonPrivate() {
if (dialogPtr) {
dialogPtr.data()->close();
}
}
void _k_chooseColor();
void _k_colorChosen();
KisColorButton *q;
KoColor m_defaultColor;
bool m_bdefaultColor : 1;
bool m_alphaChannel : 1;
bool m_palette : 1;
KoColor col;
QPoint mPos;
#ifndef Q_OS_MACOS
QPointer<KisDlgInternalColorSelector> dialogPtr;
#else
QPointer<QColorDialog> dialogPtr;
#endif
void initStyleOption(QStyleOptionButton *opt) const;
};
/////////////////////////////////////////////////////////////////////
// Functions duplicated from KColorMimeData
// Should be kept in sync
void _k_populateMimeData(QMimeData *mimeData, const KoColor &color)
{
mimeData->setColorData(color.toQColor());
mimeData->setText(color.toQColor().name());
}
bool _k_canDecode(const QMimeData *mimeData)
{
if (mimeData->hasColor()) {
return true;
}
if (mimeData->hasText()) {
const QString colorName = mimeData->text();
if ((colorName.length() >= 4) && (colorName[0] == QLatin1Char('#'))) {
return true;
}
}
return false;
}
QColor _k_fromMimeData(const QMimeData *mimeData)
{
if (mimeData->hasColor()) {
return mimeData->colorData().value<QColor>();
}
if (_k_canDecode(mimeData)) {
return QColor(mimeData->text());
}
return QColor();
}
QDrag *_k_createDrag(const KoColor &color, QObject *dragsource)
{
QDrag *drag = new QDrag(dragsource);
QMimeData *mime = new QMimeData;
_k_populateMimeData(mime, color);
drag->setMimeData(mime);
QPixmap colorpix(25, 20);
colorpix.fill(color.toQColor());
QPainter p(&colorpix);
p.setPen(Qt::black);
p.drawRect(0, 0, 24, 19);
p.end();
drag->setPixmap(colorpix);
drag->setHotSpot(QPoint(-5, -7));
return drag;
}
/////////////////////////////////////////////////////////////////////
KisColorButton::KisColorButtonPrivate::KisColorButtonPrivate(KisColorButton *q)
: q(q)
{
m_bdefaultColor = false;
m_alphaChannel = false;
m_palette = true;
q->setAcceptDrops(true);
connect(q, SIGNAL(clicked()), q, SLOT(_k_chooseColor()));
}
KisColorButton::KisColorButton(QWidget *parent)
: QPushButton(parent)
, d(new KisColorButtonPrivate(this))
{
}
KisColorButton::KisColorButton(const KoColor &c, QWidget *parent)
: QPushButton(parent)
, d(new KisColorButtonPrivate(this))
{
d->col = c;
}
KisColorButton::KisColorButton(const KoColor &c, const KoColor &defaultColor, QWidget *parent)
: QPushButton(parent)
, d(new KisColorButtonPrivate(this))
{
d->col = c;
setDefaultColor(defaultColor);
}
KisColorButton::~KisColorButton()
{
delete d;
}
KoColor KisColorButton::color() const
{
return d->col;
}
void KisColorButton::setColor(const KoColor &c)
{
d->col = c;
update();
emit changed(d->col);
}
void KisColorButton::setAlphaChannelEnabled(bool alpha)
{
d->m_alphaChannel = alpha;
}
bool KisColorButton::isAlphaChannelEnabled() const
{
return d->m_alphaChannel;
}
void KisColorButton::setPaletteViewEnabled(bool enable)
{
d->m_palette = enable;
}
bool KisColorButton::paletteViewEnabled() const
{
return d->m_palette;
}
KoColor KisColorButton::defaultColor() const
{
return d->m_defaultColor;
}
void KisColorButton::setDefaultColor(const KoColor &c)
{
d->m_bdefaultColor = true;
d->m_defaultColor = c;
}
void KisColorButton::KisColorButtonPrivate::initStyleOption(QStyleOptionButton *opt) const
{
opt->initFrom(q);
opt->state |= q->isDown() ? QStyle::State_Sunken : QStyle::State_Raised;
opt->features = QStyleOptionButton::None;
if (q->isDefault()) {
opt->features |= QStyleOptionButton::DefaultButton;
}
opt->text.clear();
opt->icon = QIcon();
}
void KisColorButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QStyle *style = QWidget::style();
//First, we need to draw the bevel.
QStyleOptionButton butOpt;
d->initStyleOption(&butOpt);
style->drawControl(QStyle::CE_PushButtonBevel, &butOpt, &painter, this);
//OK, now we can muck around with drawing out pretty little color box
//First, sort out where it goes
QRect labelRect = style->subElementRect(QStyle::SE_PushButtonContents,
&butOpt, this);
int shift = style->pixelMetric(QStyle::PM_ButtonMargin, &butOpt, this) / 2;
labelRect.adjust(shift, shift, -shift, -shift);
int x, y, w, h;
labelRect.getRect(&x, &y, &w, &h);
if (isChecked() || isDown()) {
x += style->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &butOpt, this);
y += style->pixelMetric(QStyle::PM_ButtonShiftVertical, &butOpt, this);
}
QColor fillCol = isEnabled() ? d->col.toQColor() : palette().color(backgroundRole());
qDrawShadePanel(&painter, x, y, w, h, palette(), true, 1, NULL);
if (fillCol.isValid()) {
const QRect rect(x + 1, y + 1, w - 2, h - 2);
if (fillCol.alpha() < 255) {
QPixmap chessboardPattern(16, 16);
QPainter patternPainter(&chessboardPattern);
patternPainter.fillRect(0, 0, 8, 8, Qt::black);
patternPainter.fillRect(8, 8, 8, 8, Qt::black);
patternPainter.fillRect(0, 8, 8, 8, Qt::white);
patternPainter.fillRect(8, 0, 8, 8, Qt::white);
patternPainter.end();
painter.fillRect(rect, QBrush(chessboardPattern));
}
painter.fillRect(rect, fillCol);
}
if (hasFocus()) {
QRect focusRect = style->subElementRect(QStyle::SE_PushButtonFocusRect, &butOpt, this);
QStyleOptionFocusRect focusOpt;
focusOpt.init(this);
focusOpt.rect = focusRect;
focusOpt.backgroundColor = palette().window().color();
style->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter, this);
}
}
QSize KisColorButton::sizeHint() const
{
QStyleOptionButton opt;
d->initStyleOption(&opt);
- return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this).
- expandedTo(QApplication::globalStrut());
+ return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this);
}
QSize KisColorButton::minimumSizeHint() const
{
QStyleOptionButton opt;
d->initStyleOption(&opt);
- return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(3, 3), this).
- expandedTo(QApplication::globalStrut());
+ return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(3, 3), this);
}
void KisColorButton::dragEnterEvent(QDragEnterEvent *event)
{
event->setAccepted(_k_canDecode(event->mimeData()) && isEnabled());
}
void KisColorButton::dropEvent(QDropEvent *event)
{
QColor c = _k_fromMimeData(event->mimeData());
if (c.isValid()) {
KoColor col;
col.fromQColor(c);
setColor(col);
}
}
void KisColorButton::keyPressEvent(QKeyEvent *e)
{
int key = e->key() | e->modifiers();
if (QKeySequence::keyBindings(QKeySequence::Copy).contains(key)) {
QMimeData *mime = new QMimeData;
_k_populateMimeData(mime, color());
QApplication::clipboard()->setMimeData(mime, QClipboard::Clipboard);
} else if (QKeySequence::keyBindings(QKeySequence::Paste).contains(key)) {
QColor color = _k_fromMimeData(QApplication::clipboard()->mimeData(QClipboard::Clipboard));
KoColor col;
col.fromQColor(color);
setColor(col);
} else {
QPushButton::keyPressEvent(e);
}
}
void KisColorButton::mousePressEvent(QMouseEvent *e)
{
d->mPos = e->pos();
QPushButton::mousePressEvent(e);
}
void KisColorButton::mouseMoveEvent(QMouseEvent *e)
{
if ((e->buttons() & Qt::LeftButton) &&
(e->pos() - d->mPos).manhattanLength() > QApplication::startDragDistance()) {
_k_createDrag(color(), this)->exec();
setDown(false);
}
}
void KisColorButton::KisColorButtonPrivate::_k_chooseColor()
{
#ifndef Q_OS_MACOS
KisDlgInternalColorSelector *dialog = dialogPtr.data();
#else
QColorDialog *dialog = dialogPtr.data();
#endif
if (dialog) {
#ifndef Q_OS_MACOS
dialog->setPreviousColor(q->color());
#else
dialog->setCurrentColor(q->color().toQColor());
#endif
dialog->show();
dialog->raise();
dialog->activateWindow();
return;
}
KisDlgInternalColorSelector::Config cfg;
cfg.paletteBox = q->paletteViewEnabled();
#ifndef Q_OS_MACOS
dialog = new KisDlgInternalColorSelector(q,
q->color(),
cfg,
i18n("Choose a color"));
#else
dialog = new QColorDialog(q);
dialog->setOption(QColorDialog::ShowAlphaChannel, m_alphaChannel);
#endif
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, SIGNAL(accepted()), q, SLOT(_k_colorChosen()));
dialogPtr = dialog;
#ifndef Q_OS_MACOS
dialog->setPreviousColor(q->color());
#else
dialog->setCurrentColor(q->color().toQColor());
#endif
dialog->show();
}
void KisColorButton::KisColorButtonPrivate::_k_colorChosen()
{
#ifndef Q_OS_MACOS
KisDlgInternalColorSelector *dialog = dialogPtr.data();
#else
QColorDialog *dialog = dialogPtr.data();
#endif
if (!dialog) {
return;
}
#ifndef Q_OS_MACOS
q->setColor(dialog->getCurrentColor());
#else
KoColor c;
c.fromQColor(dialog->currentColor());
q->setColor(c);
#endif
}
#include "moc_kis_color_button.cpp"
diff --git a/libs/widgets/kis_color_input.cpp b/libs/widgets/kis_color_input.cpp
index 01b7b0dd74..8687176162 100644
--- a/libs/widgets/kis_color_input.cpp
+++ b/libs/widgets/kis_color_input.cpp
@@ -1,436 +1,439 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2015 Moritz Molch <kde@moritzmolch.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 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_color_input.h"
#include <KoConfig.h>
#ifdef HAVE_OPENEXR
#include <half.h>
#endif
#include <cmath>
#include <kis_debug.h>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <klocalizedstring.h>
#include <KoChannelInfo.h>
#include <KoColor.h>
#include <KoColorSlider.h>
#include <KoColorSpace.h>
#include "kis_double_parse_spin_box.h"
#include "kis_int_parse_spin_box.h"
KisColorInput::KisColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) :
QWidget(parent), m_channelInfo(channelInfo), m_color(color), m_displayRenderer(displayRenderer),
m_usePercentage(usePercentage)
{
}
void KisColorInput::init()
{
QHBoxLayout* m_layout = new QHBoxLayout(this);
m_layout->setContentsMargins(0,0,0,0);
m_layout->setSpacing(1);
QLabel* m_label = new QLabel(i18n("%1:", m_channelInfo->name()), this);
m_layout->addWidget(m_label);
m_colorSlider = new KoColorSlider(Qt::Horizontal, this, m_displayRenderer);
m_colorSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_layout->addWidget(m_colorSlider);
QWidget* m_input = createInput();
m_colorSlider->setFixedHeight(m_input->sizeHint().height());
m_layout->addWidget(m_input);
}
KisIntegerColorInput::KisIntegerColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) :
KisColorInput(parent, channelInfo, color, displayRenderer, usePercentage)
{
init();
}
void KisIntegerColorInput::setValue(int v)
{
quint8* data = m_color->data() + m_channelInfo->pos();
switch (m_channelInfo->channelValueType()) {
case KoChannelInfo::UINT8:
*(reinterpret_cast<quint8*>(data)) = v;
break;
case KoChannelInfo::UINT16:
*(reinterpret_cast<quint16*>(data)) = v;
break;
case KoChannelInfo::UINT32:
*(reinterpret_cast<quint32*>(data)) = v;
break;
default:
Q_ASSERT(false);
}
emit(updated());
}
void KisIntegerColorInput::update()
{
KoColor min = *m_color;
KoColor max = *m_color;
quint8* data = m_color->data() + m_channelInfo->pos();
quint8* dataMin = min.data() + m_channelInfo->pos();
quint8* dataMax = max.data() + m_channelInfo->pos();
m_intNumInput->blockSignals(true);
m_colorSlider->blockSignals(true);
switch (m_channelInfo->channelValueType()) {
case KoChannelInfo::UINT8:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
m_intNumInput->setValue(round(*(reinterpret_cast<quint8*>(data))*1.0 / 255.0 * 100.0));
} else {
m_intNumInput->setMaximum(0xFF);
m_intNumInput->setValue(*(reinterpret_cast<quint8*>(data)));
}
m_colorSlider->setValue(*(reinterpret_cast<quint8*>(data)));
*(reinterpret_cast<quint8*>(dataMin)) = 0x0;
*(reinterpret_cast<quint8*>(dataMax)) = 0xFF;
break;
case KoChannelInfo::UINT16:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
m_intNumInput->setValue(round(*(reinterpret_cast<quint16*>(data))*1.0 / 65535.0 * 100.0));
} else {
m_intNumInput->setMaximum(0xFFFF);
m_intNumInput->setValue(*(reinterpret_cast<quint16*>(data)));
}
m_colorSlider->setValue(*(reinterpret_cast<quint16*>(data)));
*(reinterpret_cast<quint16*>(dataMin)) = 0x0;
*(reinterpret_cast<quint16*>(dataMax)) = 0xFFFF;
break;
case KoChannelInfo::UINT32:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
m_intNumInput->setValue(round(*(reinterpret_cast<quint32*>(data))*1.0 / 4294967295.0 * 100.0));
} else {
m_intNumInput->setMaximum(0xFFFF);
m_intNumInput->setValue(*(reinterpret_cast<quint32*>(data)));
}
m_colorSlider->setValue(*(reinterpret_cast<quint32*>(data)));
*(reinterpret_cast<quint32*>(dataMin)) = 0x0;
*(reinterpret_cast<quint32*>(dataMax)) = 0xFFFFFFFF;
break;
default:
Q_ASSERT(false);
}
m_colorSlider->setColors(min, max);
m_intNumInput->blockSignals(false);
m_colorSlider->blockSignals(false);
}
QWidget* KisIntegerColorInput::createInput()
{
m_intNumInput = new KisIntParseSpinBox(this);
m_intNumInput->setMinimum(0);
m_colorSlider->setMinimum(0);
if (m_usePercentage) {
m_intNumInput->setSuffix(i18n("%"));
} else {
m_intNumInput->setSuffix("");
}
switch (m_channelInfo->channelValueType()) {
case KoChannelInfo::UINT8:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
} else {
m_intNumInput->setMaximum(0xFF);
}
m_colorSlider->setMaximum(0xFF);
break;
case KoChannelInfo::UINT16:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
} else {
m_intNumInput->setMaximum(0xFFFF);
}
m_colorSlider->setMaximum(0xFFFF);
break;
case KoChannelInfo::UINT32:
if (m_usePercentage) {
m_intNumInput->setMaximum(100);
} else {
m_intNumInput->setMaximum(0xFFFFFFFF);
}
m_colorSlider->setMaximum(0xFFFFFFFF);
break;
default:
Q_ASSERT(false);
}
connect(m_colorSlider, SIGNAL(valueChanged(int)), this, SLOT(onColorSliderChanged(int)));
connect(m_intNumInput, SIGNAL(valueChanged(int)), this, SLOT(onNumInputChanged(int)));
return m_intNumInput;
}
void KisIntegerColorInput::setPercentageWise(bool val)
{
m_usePercentage = val;
if (m_usePercentage) {
m_intNumInput->setSuffix(i18n("%"));
} else {
m_intNumInput->setSuffix("");
}
}
void KisIntegerColorInput::onColorSliderChanged(int val)
{
m_intNumInput->blockSignals(true);
if (m_usePercentage) {
switch (m_channelInfo->channelValueType()) {
case KoChannelInfo::UINT8:
m_intNumInput->setValue(round((val*1.0) / 255.0 * 100.0));
break;
case KoChannelInfo::UINT16:
m_intNumInput->setValue(round((val*1.0) / 65535.0 * 100.0));
break;
case KoChannelInfo::UINT32:
m_intNumInput->setValue(round((val*1.0) / 4294967295.0 * 100.0));
break;
default:
Q_ASSERT(false);
}
} else {
m_intNumInput->setValue(val);
}
m_intNumInput->blockSignals(false);
setValue(val);
}
void KisIntegerColorInput::onNumInputChanged(int val)
{
m_colorSlider->blockSignals(true);
if (m_usePercentage) {
switch (m_channelInfo->channelValueType()) {
case KoChannelInfo::UINT8:
m_colorSlider->setValue((val*1.0)/100.0 * 255.0);
m_colorSlider->blockSignals(false);
setValue((val*1.0)/100.0 * 255.0);
break;
case KoChannelInfo::UINT16:
m_colorSlider->setValue((val*1.0)/100.0 * 65535.0);
m_colorSlider->blockSignals(false);
setValue((val*1.0)/100.0 * 65535.0);
break;
case KoChannelInfo::UINT32:
m_colorSlider->setValue((val*1.0)/100.0 * 4294967295.0);
m_colorSlider->blockSignals(false);
setValue((val*1.0)/100.0 * 4294967295.0);
break;
default:
Q_ASSERT(false);
}
} else {
m_colorSlider->setValue(val);
m_colorSlider->blockSignals(false);
setValue(val);
}
}
KisFloatColorInput::KisFloatColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) :
KisColorInput(parent, channelInfo, color, displayRenderer, usePercentage)
{
init();
}
void KisFloatColorInput::setValue(double v)
{
quint8* data = m_color->data() + m_channelInfo->pos();
switch (m_channelInfo->channelValueType()) {
#ifdef HAVE_OPENEXR
case KoChannelInfo::FLOAT16:
*(reinterpret_cast<half*>(data)) = v;
break;
#endif
case KoChannelInfo::FLOAT32:
*(reinterpret_cast<float*>(data)) = v;
break;
default:
Q_ASSERT(false);
}
emit(updated());
}
QWidget* KisFloatColorInput::createInput()
{
m_dblNumInput = new KisDoubleParseSpinBox(this);
m_dblNumInput->setMinimum(0);
m_dblNumInput->setMaximum(1.0);
connect(m_colorSlider, SIGNAL(valueChanged(int)), this, SLOT(sliderChanged(int)));
connect(m_dblNumInput, SIGNAL(valueChanged(double)), this, SLOT(setValue(double)));
m_dblNumInput->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
m_dblNumInput->setMinimumWidth(60);
m_dblNumInput->setMaximumWidth(60);
quint8* data = m_color->data() + m_channelInfo->pos();
qreal value = 1.0;
switch (m_channelInfo->channelValueType()) {
#ifdef HAVE_OPENEXR
case KoChannelInfo::FLOAT16:
value = *(reinterpret_cast<half*>(data));
break;
#endif
case KoChannelInfo::FLOAT32:
value = *(reinterpret_cast<float*>(data));
break;
default:
Q_ASSERT(false);
}
m_dblNumInput->setValue(value);
return m_dblNumInput;
}
void KisFloatColorInput::sliderChanged(int i)
{
const qreal floatRange = m_maxValue - m_minValue;
m_dblNumInput->setValue(m_minValue + (i / 255.0) * floatRange);
}
void KisFloatColorInput::update()
{
KoColor min = *m_color;
KoColor max = *m_color;
quint8* data = m_color->data() + m_channelInfo->pos();
quint8* dataMin = min.data() + m_channelInfo->pos();
quint8* dataMax = max.data() + m_channelInfo->pos();
qreal value = 1.0;
m_minValue = m_displayRenderer->minVisibleFloatValue(m_channelInfo);
m_maxValue = m_displayRenderer->maxVisibleFloatValue(m_channelInfo);
+ m_dblNumInput->blockSignals(true);
m_colorSlider->blockSignals(true);
switch (m_channelInfo->channelValueType()) {
#ifdef HAVE_OPENEXR
case KoChannelInfo::FLOAT16:
value = *(reinterpret_cast<half*>(data));
m_minValue = qMin(value, m_minValue);
m_maxValue = qMax(value, m_maxValue);
*(reinterpret_cast<half*>(dataMin)) = m_minValue;
*(reinterpret_cast<half*>(dataMax)) = m_maxValue;
break;
#endif
case KoChannelInfo::FLOAT32:
value = *(reinterpret_cast<float*>(data));
m_minValue = qMin(value, m_minValue);
m_maxValue = qMax(value, m_maxValue);
*(reinterpret_cast<float*>(dataMin)) = m_minValue;
*(reinterpret_cast<float*>(dataMax)) = m_maxValue;
break;
default:
Q_ASSERT(false);
}
m_dblNumInput->setMinimum(m_minValue);
m_dblNumInput->setMaximum(m_maxValue);
// ensure at least 3 significant digits are always shown
int newPrecision = 2 + qMax(qreal(0.0), std::ceil(-std::log10(m_maxValue)));
if (newPrecision != m_dblNumInput->decimals()) {
m_dblNumInput->setDecimals(newPrecision);
m_dblNumInput->updateGeometry();
}
+ m_dblNumInput->setValue(value);
m_colorSlider->setColors(min, max);
const qreal floatRange = m_maxValue - m_minValue;
m_colorSlider->setValue((value - m_minValue) / floatRange * 255);
+ m_dblNumInput->blockSignals(false);
m_colorSlider->blockSignals(false);
}
KisHexColorInput::KisHexColorInput(QWidget* parent, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) :
KisColorInput(parent, 0, color, displayRenderer, usePercentage)
{
QHBoxLayout* m_layout = new QHBoxLayout(this);
m_layout->setContentsMargins(0,0,0,0);
m_layout->setSpacing(1);
QLabel* m_label = new QLabel(i18n("Color name:"), this);
m_label->setMinimumWidth(50);
m_layout->addWidget(m_label);
QWidget* m_input = createInput();
m_input->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
m_layout->addWidget(m_input);
}
void KisHexColorInput::setValue()
{
QString valueString = m_hexInput->text();
valueString.remove(QChar('#'));
QList<KoChannelInfo*> channels = m_color->colorSpace()->channels();
channels = KoChannelInfo::displayOrderSorted(channels);
Q_FOREACH (KoChannelInfo* channel, channels) {
if (channel->channelType() == KoChannelInfo::COLOR) {
Q_ASSERT(channel->channelValueType() == KoChannelInfo::UINT8);
quint8* data = m_color->data() + channel->pos();
int value = valueString.left(2).toInt(0, 16);
*(reinterpret_cast<quint8*>(data)) = value;
valueString.remove(0, 2);
}
}
emit(updated());
}
void KisHexColorInput::update()
{
QString hexString("#");
QList<KoChannelInfo*> channels = m_color->colorSpace()->channels();
channels = KoChannelInfo::displayOrderSorted(channels);
Q_FOREACH (KoChannelInfo* channel, channels) {
if (channel->channelType() == KoChannelInfo::COLOR) {
Q_ASSERT(channel->channelValueType() == KoChannelInfo::UINT8);
quint8* data = m_color->data() + channel->pos();
hexString.append(QString("%1").arg(*(reinterpret_cast<quint8*>(data)), 2, 16, QChar('0')));
}
}
m_hexInput->setText(hexString);
}
QWidget* KisHexColorInput::createInput()
{
m_hexInput = new QLineEdit(this);
m_hexInput->setAlignment(Qt::AlignRight);
int digits = 2*m_color->colorSpace()->colorChannelCount();
QString pattern = QString("#?[a-fA-F0-9]{%1,%2}").arg(digits).arg(digits);
m_hexInput->setValidator(new QRegExpValidator(QRegExp(pattern), this));
connect(m_hexInput, SIGNAL(editingFinished()), this, SLOT(setValue()));
return m_hexInput;
}
diff --git a/libs/widgets/kis_spinbox_color_selector.cpp b/libs/widgets/kis_spinbox_color_selector.cpp
index 30d31035d9..9ca41e3080 100644
--- a/libs/widgets/kis_spinbox_color_selector.cpp
+++ b/libs/widgets/kis_spinbox_color_selector.cpp
@@ -1,273 +1,267 @@
/*
* Copyright (C) Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>, (C) 2016
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* 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_spinbox_color_selector.h"
#include <QFormLayout>
#include <QLabel>
#include "kis_double_parse_spin_box.h"
#include "kis_int_parse_spin_box.h"
#include "kis_signal_compressor.h"
#include <KoConfig.h>
#ifdef HAVE_OPENEXR
#include <half.h>
#endif
#include <KoChannelInfo.h>
#include <KoColorSpaceTraits.h>
#include <KoColorSpaceMaths.h>
#include <KoColorSpaceRegistry.h>
struct KisSpinboxColorSelector::Private
{
QList <QLabel*> labels;
QList <KisIntParseSpinBox*> spinBoxList;
QList <KisDoubleParseSpinBox*> doubleSpinBoxList;
KoColor color;
const KoColorSpace *cs {0};
bool chooseAlpha {false};
QFormLayout *layout {0};
};
KisSpinboxColorSelector::KisSpinboxColorSelector(QWidget *parent)
: QWidget(parent)
, m_d(new Private)
{
this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
m_d->layout = new QFormLayout(this);
}
KisSpinboxColorSelector::~KisSpinboxColorSelector()
{
}
void KisSpinboxColorSelector::slotSetColor(KoColor color)
{
m_d->color = color;
slotSetColorSpace(m_d->color.colorSpace());
updateSpinboxesWithNewValues();
}
void KisSpinboxColorSelector::slotSetColorSpace(const KoColorSpace *cs)
{
if (cs == m_d->cs) {
return;
}
m_d->cs = cs;
//remake spinboxes
delete m_d->layout;
m_d->layout = new QFormLayout(this);
Q_FOREACH(QObject *o, m_d->labels) {
o->deleteLater();
}
Q_FOREACH(QObject *o, m_d->spinBoxList) {
o->deleteLater();
}
Q_FOREACH(QObject *o, m_d->doubleSpinBoxList) {
o->deleteLater();
}
- Q_FOREACH(QObject *o, m_d->labels) {
- o->deleteLater();
- }
-
m_d->labels.clear();
m_d->spinBoxList.clear();
m_d->doubleSpinBoxList.clear();
QList<KoChannelInfo *> channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels());
Q_FOREACH (KoChannelInfo* channel, channels) {
QString inputLabel = channel->name();
QLabel *inlb = new QLabel(this);
m_d->labels << inlb;
inlb->setText(inputLabel);
switch (channel->channelValueType()) {
case KoChannelInfo::UINT8: {
KisIntParseSpinBox *input = new KisIntParseSpinBox(this);
input->setMinimum(0);
input->setMaximum(0xFF);
m_d->spinBoxList.append(input);
m_d->layout->addRow(inlb, input);
connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes()));
if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) {
inlb->setVisible(false);
input->setVisible(false);
input->blockSignals(true);
}
}
break;
case KoChannelInfo::UINT16: {
KisIntParseSpinBox *input = new KisIntParseSpinBox(this);
input->setMinimum(0);
input->setMaximum(0xFFFF);
m_d->spinBoxList.append(input);
m_d->layout->addRow(inlb,input);
connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes()));
if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) {
inlb->setVisible(false);
input->setVisible(false);
input->blockSignals(true);
}
}
break;
case KoChannelInfo::UINT32: {
KisIntParseSpinBox *input = new KisIntParseSpinBox(this);
input->setMinimum(0);
input->setMaximum(0xFFFFFFFF);
m_d->spinBoxList.append(input);
m_d->layout->addRow(inlb,input);
connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes()));
if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) {
inlb->setVisible(false);
input->setVisible(false);
input->blockSignals(true);
}
}
break;
#ifdef HAVE_OPENEXR
case KoChannelInfo::FLOAT16: {
KisDoubleParseSpinBox *input = new KisDoubleParseSpinBox(this);
input->setMinimum(0);
input->setMaximum(KoColorSpaceMathsTraits<half>::max);
input->setSingleStep(0.1);
m_d->doubleSpinBoxList.append(input);
m_d->layout->addRow(inlb,input);
connect(input, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateFromSpinBoxes()));
if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) {
inlb->setVisible(false);
input->setVisible(false);
input->blockSignals(true);
}
}
break;
#endif
case KoChannelInfo::FLOAT32: {
KisDoubleParseSpinBox *input = new KisDoubleParseSpinBox(this);
input->setMinimum(0);
input->setMaximum(KoColorSpaceMathsTraits<float>::max);
input->setSingleStep(0.1);
m_d->doubleSpinBoxList.append(input);
m_d->layout->addRow(inlb,input);
connect(input, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateFromSpinBoxes()));
if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) {
inlb->setVisible(false);
input->setVisible(false);
input->blockSignals(true);
}
}
break;
default:
Q_ASSERT(false);
}
}
this->setLayout(m_d->layout);
}
void KisSpinboxColorSelector::createColorFromSpinboxValues()
{
- KoColor newColor;
+ KoColor newColor(m_d->cs);
int channelcount = m_d->cs->channelCount();
- quint8 *data = new quint8[m_d->cs->pixelSize()];
QVector <float> channelValues(channelcount);
channelValues.fill(1.0);
QList<KoChannelInfo *> channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels());
for (int i = 0; i < (int)qAbs(m_d->cs->colorChannelCount()); i++) {
int channelposition = KoChannelInfo::displayPositionToChannelIndex(i, m_d->cs->channels());
if (channels.at(i)->channelValueType()==KoChannelInfo::UINT8 && m_d->spinBoxList.at(i)){
int value = m_d->spinBoxList.at(i)->value();
channelValues[channelposition] = KoColorSpaceMaths<quint8,float>::scaleToA(value);
} else if (channels.at(i)->channelValueType()==KoChannelInfo::UINT16 && m_d->spinBoxList.at(i)){
channelValues[channelposition] = KoColorSpaceMaths<quint16,float>::scaleToA(m_d->spinBoxList.at(i)->value());
} else if ((channels.at(i)->channelValueType()==KoChannelInfo::FLOAT16 ||
channels.at(i)->channelValueType()==KoChannelInfo::FLOAT32 ||
channels.at(i)->channelValueType()==KoChannelInfo::FLOAT64) && m_d->doubleSpinBoxList.at(i)) {
channelValues[channelposition] = m_d->doubleSpinBoxList.at(i)->value();
}
}
- m_d->cs->fromNormalisedChannelsValue(data, channelValues);
- newColor.setColor(data, m_d->cs);
+ m_d->cs->fromNormalisedChannelsValue(newColor.data(), channelValues);
newColor.setOpacity(m_d->color.opacityU8());
m_d->color = newColor;
}
void KisSpinboxColorSelector::slotUpdateFromSpinBoxes()
{
createColorFromSpinboxValues();
emit sigNewColor(m_d->color);
}
void KisSpinboxColorSelector::updateSpinboxesWithNewValues()
{
int channelcount = m_d->cs->channelCount();
QVector <float> channelValues(channelcount);
channelValues.fill(1.0);
m_d->cs->normalisedChannelsValue(m_d->color.data(), channelValues);
QList<KoChannelInfo *> channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels());
int i;
/*while (QLayoutItem *item = this->layout()->takeAt(0))
{
item->widget()->blockSignals(true);
}*/
for (i=0; i<m_d->spinBoxList.size(); i++) {
m_d->spinBoxList.at(i)->blockSignals(true);
}
for (i=0; i<m_d->doubleSpinBoxList.size(); i++) {
m_d->doubleSpinBoxList.at(i)->blockSignals(true);
}
for (i = 0; i < (int)qAbs(m_d->cs->colorChannelCount()); i++) {
int channelposition = KoChannelInfo::displayPositionToChannelIndex(i, m_d->cs->channels());
if (channels.at(i)->channelValueType() == KoChannelInfo::UINT8 && m_d->spinBoxList.at(i)) {
int value = KoColorSpaceMaths<float, quint8>::scaleToA(channelValues[channelposition]);
m_d->spinBoxList.at(i)->setValue(value);
} else if (channels.at(i)->channelValueType() == KoChannelInfo::UINT16 && m_d->spinBoxList.at(i)) {
m_d->spinBoxList.at(i)->setValue(KoColorSpaceMaths<float, quint16>::scaleToA(channelValues[channelposition]));
} else if ((channels.at(i)->channelValueType()==KoChannelInfo::FLOAT16 ||
channels.at(i)->channelValueType()==KoChannelInfo::FLOAT32 ||
channels.at(i)->channelValueType()==KoChannelInfo::FLOAT64) && m_d->doubleSpinBoxList.at(i)) {
m_d->doubleSpinBoxList.at(i)->setValue(channelValues[channelposition]);
}
}
for (i=0; i<m_d->spinBoxList.size(); i++) {
m_d->spinBoxList.at(i)->blockSignals(false);
}
for (i=0; i<m_d->doubleSpinBoxList.size(); i++) {
m_d->doubleSpinBoxList.at(i)->blockSignals(false);
}
/*while (QLayoutItem *item = this->layout()->takeAt(0))
{
item->widget()->blockSignals(false);
}*/
}
diff --git a/libs/widgetutils/KisSqueezedComboBox.cpp b/libs/widgetutils/KisSqueezedComboBox.cpp
index 6e5d35baec..afd5de755d 100644
--- a/libs/widgetutils/KisSqueezedComboBox.cpp
+++ b/libs/widgetutils/KisSqueezedComboBox.cpp
@@ -1,179 +1,178 @@
/* ============================================================
* Author: Tom Albers <tomalbers@kde.nl>
* Date : 2005-01-01
* Description :
*
* Copyright 2005 by Tom Albers <tomalbers@kde.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, or (at your option)* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT 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 "KisSqueezedComboBox.h"
/** @file KisSqueezedComboBox.cpp */
// Qt includes.
#include <QComboBox>
#include <QPair>
#include <QTimer>
#include <QStyle>
#include <QApplication>
#include <QResizeEvent>
KisSqueezedComboBox::KisSqueezedComboBox(QWidget *parent, const char *name)
: QComboBox(parent)
{
setObjectName(name);
setMinimumWidth(100);
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
connect(m_timer, SIGNAL(timeout()),
SLOT(slotTimeOut()));
}
KisSqueezedComboBox::~KisSqueezedComboBox()
{
delete m_timer;
}
bool KisSqueezedComboBox::contains(const QString& _text) const
{
if (_text.isEmpty())
return false;
for (QMap<int, QString>::const_iterator it = m_originalItems.begin() ; it != m_originalItems.end();
++it) {
if (it.value() == _text) {
return true;
}
}
return false;
}
qint32 KisSqueezedComboBox::findOriginalText(const QString& text) const
{
for (int i = 0; i < m_originalItems.size(); i++) {
if(m_originalItems.value(i) == text) {
return i;
}
}
return -1;
}
QStringList KisSqueezedComboBox::originalTexts() const
{
return m_originalItems.values();
}
void KisSqueezedComboBox::resetOriginalTexts(const QStringList &texts)
{
if (texts == m_originalItems.values()) return;
clear();
m_originalItems.clear();
Q_FOREACH (const QString &item, texts) {
addSqueezedItem(item);
}
}
QSize KisSqueezedComboBox::sizeHint() const
{
ensurePolished();
QFontMetrics fm = fontMetrics();
int maxW = count() ? 18 : 7 * fm.boundingRect(QChar('x')).width() + 18;
int maxH = qMax(fm.lineSpacing(), 14) + 2;
QStyleOptionComboBox options;
options.initFrom(this);
- return style()->sizeFromContents(QStyle::CT_ComboBox, &options,
- QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut());
+ return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this);
}
void KisSqueezedComboBox::insertSqueezedItem(const QString& newItem, int index, QVariant userData)
{
m_originalItems[index] = newItem;
QComboBox::insertItem(index, squeezeText(newItem, this), userData);
}
void KisSqueezedComboBox::insertSqueezedItem(const QIcon &icon, const QString &newItem, int index, QVariant userData)
{
m_originalItems[index] = newItem;
QComboBox::insertItem(index, icon, squeezeText(newItem, this), userData);
}
void KisSqueezedComboBox::addSqueezedItem(const QString& newItem, QVariant userData)
{
insertSqueezedItem(newItem, count(), userData);
}
void KisSqueezedComboBox::addSqueezedItem(const QIcon &icon, const QString &newItem, QVariant userData)
{
insertSqueezedItem(icon, newItem, count(), userData);
}
void KisSqueezedComboBox::setCurrent(const QString& itemText)
{
qint32 itemIndex = findOriginalText(itemText);
if (itemIndex >= 0) {
setCurrentIndex(itemIndex);
}
}
void KisSqueezedComboBox::resizeEvent(QResizeEvent *)
{
m_timer->start(200);
}
void KisSqueezedComboBox::slotTimeOut()
{
for (QMap<int, QString>::iterator it = m_originalItems.begin() ; it != m_originalItems.end();
++it) {
setItemText(it.key(), squeezeText(it.value(), this));
}
}
QString KisSqueezedComboBox::squeezeText(const QString& original, const QWidget *widget)
{
// not the complete widgetSize is usable. Need to compensate for that.
int widgetSize = widget->width() - 30;
QFontMetrics fm(widget->fontMetrics());
// If we can fit the full text, return that.
if (fm.boundingRect(original).width() < widgetSize)
return(original);
// We need to squeeze.
QString sqItem = original; // prevent empty return value;
widgetSize = widgetSize - fm.boundingRect("...").width();
for (int i = 0 ; i != original.length(); ++i) {
if ((int)fm.boundingRect(original.right(i)).width() > widgetSize) {
sqItem = QString("..." + original.right(--i));
break;
}
}
return sqItem;
}
QString KisSqueezedComboBox::currentUnsqueezedText()
{
int curItem = currentIndex();
return m_originalItems[curItem];
}
void KisSqueezedComboBox::removeSqueezedItem(int index)
{
removeItem(index);
m_originalItems.remove(index);
}
diff --git a/libs/widgetutils/config/kstandardaction.h b/libs/widgetutils/config/kstandardaction.h
index 9055e1614b..729bcb9d6c 100644
--- a/libs/widgetutils/config/kstandardaction.h
+++ b/libs/widgetutils/config/kstandardaction.h
@@ -1,598 +1,597 @@
/* This file is part of the KDE libraries
Copyright (C) 1999,2000 Kurt Granroth <granroth@kde.org>
Copyright (C) 2001,2002 Ellis Whitehead <ellis@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 version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KSTANDARDACTION_H
#define KSTANDARDACTION_H
#include <kritawidgetutils_export.h>
#include <kstandardshortcut.h>
#include <QList>
class QObject;
class QStringList;
class QWidget;
class QAction;
class KRecentFilesAction;
class KDualAction;
class KToggleAction;
class KToggleFullScreenAction;
/**
* Convenience methods to access all standard KDE actions.
*
* These actions should be used instead of hardcoding menubar and
* toolbar items. Using these actions helps your application easily
- * conform to the KDE UI Style Guide
- * @see http://developer.kde.org/documentation/standards/kde/style/basics/index.html .
+ * conform to the KDE UI Style Guide.
*
* All of the documentation for QAction holds for KStandardAction
* also. When in doubt on how things work, check the QAction
* documentation first.
* Please note that calling any of these methods automatically adds the action
* to the actionCollection() of the QObject given by the 'parent' parameter.
*
* <b>Simple Example:</b>\n
*
* In general, using standard actions should be a drop in replacement
* for regular actions. For example, if you previously had:
*
* \code
* QAction *newAct = new QAction(i18n("&New"), KisIconUtils::loadIcon("document-new"),
* KStandardShortcut::shortcut(KStandardShortcut::New), this,
* SLOT(fileNew()), actionCollection());
* \endcode
*
* You could drop that and replace it with:
*
* \code
* QAction *newAct = KStandardAction::openNew(this, SLOT(fileNew()),
* actionCollection());
* \endcode
*
* <b>Non-standard Usages</b>\n
*
* It is possible to use the standard actions in various
* non-recommended ways. Say, for instance, you wanted to have a
* standard action (with the associated correct text and icon and
* accelerator, etc) but you didn't want it to go in the standard
* place (this is not recommended, by the way). One way to do this is
* to simply not use the XML UI framework and plug it into wherever
* you want. If you do want to use the XML UI framework (good!), then
* it is still possible.
*
* Basically, the XML building code matches names in the XML code with
* the internal names of the actions. You can find out the internal
* names of each of the standard actions by using the name
* method like so: KStandardAction::name(KStandardAction::Cut) would return
* 'edit_cut'. The XML building code will match 'edit_cut' to the
* attribute in the global XML file and place your action there.
*
* However, you can change the internal name. In this example, just
* do something like:
*
* \code
* (void)KStandardAction::cut(this, SLOT(editCut()), actionCollection(), "my_cut");
* \endcode
*
* Now, in your local XML resource file (e.g., yourappui.rc), simply
* put 'my_cut' where you want it to go.
*
* Another non-standard usage concerns getting a pointer to an
* existing action if, say, you want to enable or disable the action.
* You could do it the recommended way and just grab a pointer when
* you instantiate it as in the 'openNew' example above... or you
* could do it the hard way:
*
* \code
* QAction *cut = actionCollection()->action(KStandardAction::name(KStandardAction::Cut));
* \endcode
*
* Another non-standard usage concerns instantiating the action in the
* first place. Usually, you would use the member functions as
* shown above (e.g., KStandardAction::cut(this, SLOT, parent)). You
* may, however, do this using the enums provided. This author can't
* think of a reason why you would want to, but, hey, if you do,
* here's how:
*
* \code
* (void)KStandardAction::action(KStandardAction::New, this, SLOT(fileNew()), actionCollection());
* (void)KStandardAction::action(KStandardAction::Cut, this, SLOT(editCut()), actionCollection());
* \endcode
*
* @author Kurt Granroth <granroth@kde.org>
*/
namespace KStandardAction
{
/**
* The standard menubar and toolbar actions.
*/
enum StandardAction {
ActionNone,
// File Menu
New, Open, OpenRecent, Save, SaveAs, Revert, Close,
Print, PrintPreview, Mail, Quit,
// Edit Menu
Undo, Redo, Cut, Copy, Paste, SelectAll, Deselect, Find, FindNext, FindPrev,
Replace,
// View Menu
ActualSize, FitToPage, FitToWidth, FitToHeight, ZoomIn, ZoomOut,
Zoom, Redisplay,
// Go Menu
Up, Back, Forward, Home /*Home page*/, Prior, Next, Goto, GotoPage, GotoLine,
FirstPage, LastPage, DocumentBack, DocumentForward,
// Bookmarks Menu
AddBookmark, EditBookmarks,
// Tools Menu
Spelling,
// Settings Menu
ShowMenubar, ShowToolbar, ShowStatusbar,
SaveOptions, KeyBindings,
Preferences, ConfigureToolbars,
// Help Menu
Help, HelpContents, WhatsThis, ReportBug, AboutApp, AboutKDE,
TipofDay,
// Other standard actions
ConfigureNotifications,
FullScreen,
Clear,
PasteText,
SwitchApplicationLanguage
};
/**
* Creates an action corresponding to one of the
* KStandardAction::StandardAction actions, which is connected to the given
* object and @p slot, and is owned by @p parent.
*
* The signal that is connected to @p slot is triggered(bool), except for the case of
* OpenRecent standard action, which uses the urlSelected(const QUrl &) signal of
* KRecentFilesAction.
*
* @param id The StandardAction identifier to create a QAction for.
* @param recvr The QObject to receive the signal, or 0 if no notification
* is needed.
* @param slot The slot to connect the signal to (remember to use the SLOT() macro).
* @param parent The QObject that should own the created QAction, or 0 if no parent will
* own the QAction returned (ensure you delete it manually in this case).
*/
KRITAWIDGETUTILS_EXPORT QAction *create(StandardAction id, const QObject *recvr, const char *slot,
QObject *parent);
/**
* This will return the internal name of a given standard action.
*/
KRITAWIDGETUTILS_EXPORT const char *name(StandardAction id);
/// @deprecated use name()
#ifndef KDE_NO_DEPRECATED
inline KRITAWIDGETUTILS_DEPRECATED const char *stdName(StandardAction act_enum)
{
return name(act_enum);
}
#endif
/**
* Returns a list of all standard names. Used by KAccelManager
* to give those higher weight.
*/
KRITAWIDGETUTILS_EXPORT QStringList stdNames();
/**
* Returns a list of all actionIds.
*
* @since 4.2
*/
KRITAWIDGETUTILS_EXPORT QList<StandardAction> actionIds();
/**
* Returns the standardshortcut associated with @a actionId.
*
* @param id The actionId whose associated shortcut is wanted.
*
* @since 4.2
*/
KRITAWIDGETUTILS_EXPORT KStandardShortcut::StandardShortcut shortcutForActionId(StandardAction id);
/**
* Create a new document or window.
*/
KRITAWIDGETUTILS_EXPORT QAction *openNew(const QObject *recvr, const char *slot, QObject *parent);
/**
* Open an existing file.
*/
KRITAWIDGETUTILS_EXPORT QAction *open(const QObject *recvr, const char *slot, QObject *parent);
/**
* Open a recently used document. The signature of the slot being called
* is of the form slotURLSelected( const QUrl & ).
* @param recvr object to receive slot
* @param slot The SLOT to invoke when a URL is selected. The slot's
* signature is slotURLSelected( const QUrl & ).
* @param parent parent widget
*/
KRITAWIDGETUTILS_EXPORT KRecentFilesAction *openRecent(const QObject *recvr, const char *slot, QObject *parent);
/**
* Save the current document.
*/
KRITAWIDGETUTILS_EXPORT QAction *save(const QObject *recvr, const char *slot, QObject *parent);
/**
* Save the current document under a different name.
*/
KRITAWIDGETUTILS_EXPORT QAction *saveAs(const QObject *recvr, const char *slot, QObject *parent);
/**
* Revert the current document to the last saved version
* (essentially will undo all changes).
*/
KRITAWIDGETUTILS_EXPORT QAction *revert(const QObject *recvr, const char *slot, QObject *parent);
/**
* Close the current document.
*/
KRITAWIDGETUTILS_EXPORT QAction *close(const QObject *recvr, const char *slot, QObject *parent);
/**
* Print the current document.
*/
KRITAWIDGETUTILS_EXPORT QAction *print(const QObject *recvr, const char *slot, QObject *parent);
/**
* Show a print preview of the current document.
*/
KRITAWIDGETUTILS_EXPORT QAction *printPreview(const QObject *recvr, const char *slot, QObject *parent);
/**
* Mail this document.
*/
KRITAWIDGETUTILS_EXPORT QAction *mail(const QObject *recvr, const char *slot, QObject *parent);
/**
* Quit the program.
*
* Note that you probably want to connect this action to either QWidget::close()
* or QApplication::closeAllWindows(), but not QApplication::quit(), so that
* KMainWindow::queryClose() is called on any open window (to warn the user
* about unsaved changes for example).
*/
KRITAWIDGETUTILS_EXPORT QAction *quit(const QObject *recvr, const char *slot, QObject *parent);
/**
* Undo the last operation.
*/
KRITAWIDGETUTILS_EXPORT QAction *undo(const QObject *recvr, const char *slot, QObject *parent);
/**
* Redo the last operation.
*/
KRITAWIDGETUTILS_EXPORT QAction *redo(const QObject *recvr, const char *slot, QObject *parent);
/**
* Cut selected area and store it in the clipboard.
* Calls cut() on the widget with the current focus.
*/
KRITAWIDGETUTILS_EXPORT QAction *cut(QObject *parent);
/**
* Copy selected area and store it in the clipboard.
* Calls copy() on the widget with the current focus.
*/
KRITAWIDGETUTILS_EXPORT QAction *copy(QObject *parent);
/**
* Paste the contents of clipboard at the current mouse or cursor
* Calls paste() on the widget with the current focus.
*/
KRITAWIDGETUTILS_EXPORT QAction *paste(QObject *parent);
/**
* Clear selected area. Calls clear() on the widget with the current focus.
* Note that for some widgets, this may not provide the intended behavior. For
* example if you make use of the code above and a K3ListView has the focus, clear()
* will clear all of the items in the list. If this is not the intened behavior
* and you want to make use of this slot, you can subclass K3ListView and reimplement
* this slot. For example the following code would implement a K3ListView without this
* behavior:
*
* \code
* class MyListView : public K3ListView {
* Q_OBJECT
* public:
* MyListView( QWidget * parent = 0, const char * name = 0, WFlags f = 0 ) : K3ListView( parent, name, f ) {}
* virtual ~MyListView() {}
* public Q_SLOTS:
* virtual void clear() {}
* };
* \endcode
*/
KRITAWIDGETUTILS_EXPORT QAction *clear(QObject *parent);
/**
* Calls selectAll() on the widget with the current focus.
*/
KRITAWIDGETUTILS_EXPORT QAction *selectAll(QObject *parent);
/**
* Cut selected area and store it in the clipboard.
*/
KRITAWIDGETUTILS_EXPORT QAction *cut(const QObject *recvr, const char *slot, QObject *parent);
/**
* Copy the selected area into the clipboard.
*/
KRITAWIDGETUTILS_EXPORT QAction *copy(const QObject *recvr, const char *slot, QObject *parent);
/**
* Paste the contents of clipboard at the current mouse or cursor
* position.
*/
KRITAWIDGETUTILS_EXPORT QAction *paste(const QObject *recvr, const char *slot, QObject *parent);
/**
* Paste the contents of clipboard at the current mouse or cursor
* position. Provide a button on the toolbar with the clipboard history
* menu if Klipper is running.
*/
KRITAWIDGETUTILS_EXPORT QAction *pasteText(const QObject *recvr, const char *slot, QObject *parent);
/**
* Clear the content of the focus widget
*/
KRITAWIDGETUTILS_EXPORT QAction *clear(const QObject *recvr, const char *slot, QObject *parent);
/**
* Select all elements in the current document.
*/
KRITAWIDGETUTILS_EXPORT QAction *selectAll(const QObject *recvr, const char *slot, QObject *parent);
/**
* Deselect any selected elements in the current document.
*/
KRITAWIDGETUTILS_EXPORT QAction *deselect(const QObject *recvr, const char *slot, QObject *parent);
/**
* Initiate a 'find' request in the current document.
*/
KRITAWIDGETUTILS_EXPORT QAction *find(const QObject *recvr, const char *slot, QObject *parent);
/**
* Find the next instance of a stored 'find'.
*/
KRITAWIDGETUTILS_EXPORT QAction *findNext(const QObject *recvr, const char *slot, QObject *parent);
/**
* Find a previous instance of a stored 'find'.
*/
KRITAWIDGETUTILS_EXPORT QAction *findPrev(const QObject *recvr, const char *slot, QObject *parent);
/**
* Find and replace matches.
*/
KRITAWIDGETUTILS_EXPORT QAction *replace(const QObject *recvr, const char *slot, QObject *parent);
/**
* View the document at its actual size.
*/
KRITAWIDGETUTILS_EXPORT QAction *actualSize(const QObject *recvr, const char *slot, QObject *parent);
/**
* Fit the document view to the size of the current window.
*/
KRITAWIDGETUTILS_EXPORT QAction *fitToPage(const QObject *recvr, const char *slot, QObject *parent);
/**
* Fit the document view to the width of the current window.
*/
KRITAWIDGETUTILS_EXPORT QAction *fitToWidth(const QObject *recvr, const char *slot, QObject *parent);
/**
* Fit the document view to the height of the current window.
*/
KRITAWIDGETUTILS_EXPORT QAction *fitToHeight(const QObject *recvr, const char *slot, QObject *parent);
/**
* Zoom in.
*/
KRITAWIDGETUTILS_EXPORT QAction *zoomIn(const QObject *recvr, const char *slot, QObject *parent);
/**
* Zoom out.
*/
KRITAWIDGETUTILS_EXPORT QAction *zoomOut(const QObject *recvr, const char *slot, QObject *parent);
/**
* Popup a zoom dialog.
*/
KRITAWIDGETUTILS_EXPORT QAction *zoom(const QObject *recvr, const char *slot, QObject *parent);
/**
* Redisplay or redraw the document.
*/
KRITAWIDGETUTILS_EXPORT QAction *redisplay(const QObject *recvr, const char *slot, QObject *parent);
/**
* Move up (web style menu).
*/
KRITAWIDGETUTILS_EXPORT QAction *up(const QObject *recvr, const char *slot, QObject *parent);
/**
* Move back (web style menu).
*/
KRITAWIDGETUTILS_EXPORT QAction *back(const QObject *recvr, const char *slot, QObject *parent);
/**
* Move forward (web style menu).
*/
KRITAWIDGETUTILS_EXPORT QAction *forward(const QObject *recvr, const char *slot, QObject *parent);
/**
* Go to the "Home" position or document.
*/
KRITAWIDGETUTILS_EXPORT QAction *home(const QObject *recvr, const char *slot, QObject *parent);
/**
* Scroll up one page.
*/
KRITAWIDGETUTILS_EXPORT QAction *prior(const QObject *recvr, const char *slot, QObject *parent);
/**
* Scroll down one page.
*/
KRITAWIDGETUTILS_EXPORT QAction *next(const QObject *recvr, const char *slot, QObject *parent);
/**
* Go to somewhere in general.
*/
KRITAWIDGETUTILS_EXPORT QAction *goTo(const QObject *recvr, const char *slot, QObject *parent);
/**
* Go to a specific page (dialog).
*/
KRITAWIDGETUTILS_EXPORT QAction *gotoPage(const QObject *recvr, const char *slot, QObject *parent);
/**
* Go to a specific line (dialog).
*/
KRITAWIDGETUTILS_EXPORT QAction *gotoLine(const QObject *recvr, const char *slot, QObject *parent);
/**
* Jump to the first page.
*/
KRITAWIDGETUTILS_EXPORT QAction *firstPage(const QObject *recvr, const char *slot, QObject *parent);
/**
* Jump to the last page.
*/
KRITAWIDGETUTILS_EXPORT QAction *lastPage(const QObject *recvr, const char *slot, QObject *parent);
/**
* Move back (document style menu).
*/
KRITAWIDGETUTILS_EXPORT QAction *documentBack(const QObject *recvr, const char *slot, QObject *parent);
/**
* Move forward (document style menu).
*/
KRITAWIDGETUTILS_EXPORT QAction *documentForward(const QObject *recvr, const char *slot, QObject *parent);
/**
* Add the current page to the bookmarks tree.
*/
KRITAWIDGETUTILS_EXPORT QAction *addBookmark(const QObject *recvr, const char *slot, QObject *parent);
/**
* Edit the application bookmarks.
*/
KRITAWIDGETUTILS_EXPORT QAction *editBookmarks(const QObject *recvr, const char *slot, QObject *parent);
/**
* Pop up the spell checker.
*/
KRITAWIDGETUTILS_EXPORT QAction *spelling(const QObject *recvr, const char *slot, QObject *parent);
/**
* Show/Hide the menubar.
*/
KRITAWIDGETUTILS_EXPORT KToggleAction *showMenubar(const QObject *recvr, const char *slot, QObject *parent);
/**
* Show/Hide the statusbar.
*/
KRITAWIDGETUTILS_EXPORT KToggleAction *showStatusbar(const QObject *recvr, const char *slot, QObject *parent);
/**
* Switch to/from full screen mode
*/
KRITAWIDGETUTILS_EXPORT KToggleFullScreenAction *fullScreen(const QObject *recvr, const char *slot, QWidget *window, QObject *parent);
/**
* Display the save options dialog.
*/
KRITAWIDGETUTILS_EXPORT QAction *saveOptions(const QObject *recvr, const char *slot, QObject *parent);
/**
* Display the configure key bindings dialog.
*
* Note that you might be able to use the pre-built KXMLGUIFactory's function:
* KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection());
*/
KRITAWIDGETUTILS_EXPORT QAction *keyBindings(const QObject *recvr, const char *slot, QObject *parent);
/**
* Display the preferences/options dialog.
*/
KRITAWIDGETUTILS_EXPORT QAction *preferences(const QObject *recvr, const char *slot, QObject *parent);
/**
* The Customize Toolbar dialog.
*/
KRITAWIDGETUTILS_EXPORT QAction *configureToolbars(const QObject *recvr, const char *slot, QObject *parent);
/**
* The Configure Notifications dialog.
*/
KRITAWIDGETUTILS_EXPORT QAction *configureNotifications(const QObject *recvr, const char *slot, QObject *parent);
/**
* Display the help.
*/
KRITAWIDGETUTILS_EXPORT QAction *help(const QObject *recvr, const char *slot, QObject *parent);
/**
* Display the help contents.
*/
KRITAWIDGETUTILS_EXPORT QAction *helpContents(const QObject *recvr, const char *slot, QObject *parent);
/**
* Trigger the What's This cursor.
*/
KRITAWIDGETUTILS_EXPORT QAction *whatsThis(const QObject *recvr, const char *slot, QObject *parent);
/**
* Display "Tip of the Day"
*/
KRITAWIDGETUTILS_EXPORT QAction *tipOfDay(const QObject *recvr, const char *slot, QObject *parent);
/**
* Open up the Report Bug dialog.
*/
KRITAWIDGETUTILS_EXPORT QAction *reportBug(const QObject *recvr, const char *slot, QObject *parent);
/**
* Display the application's About box.
*/
KRITAWIDGETUTILS_EXPORT QAction *aboutApp(const QObject *recvr, const char *slot, QObject *parent);
/**
* Display the About KDE dialog.
*/
KRITAWIDGETUTILS_EXPORT QAction *aboutKDE(const QObject *recvr, const char *slot, QObject *parent);
}
#endif // KSTDACTION_H
diff --git a/libs/widgetutils/kis_double_parse_spin_box.cpp b/libs/widgetutils/kis_double_parse_spin_box.cpp
index 50005053dd..47c4ead442 100644
--- a/libs/widgetutils/kis_double_parse_spin_box.cpp
+++ b/libs/widgetutils/kis_double_parse_spin_box.cpp
@@ -1,236 +1,237 @@
/*
* Copyright (c) 2016 Laurent Valentin Jospin <laurent.valentin@famillejospin.ch>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* 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_double_parse_spin_box.h"
#include "kis_num_parser.h"
#include <QLabel>
#include <QPixmap>
#include <QIcon>
#include <QFile>
#include <QLineEdit>
#include <qnumeric.h> // for qIsNaN
KisDoubleParseSpinBox::KisDoubleParseSpinBox(QWidget *parent) :
QDoubleSpinBox(parent),
boolLastValid(true),
lastExprParsed(QStringLiteral("0.0"))
{
setAlignment(Qt::AlignRight);
connect(this, SIGNAL(noMoreParsingError()),
this, SLOT(clearErrorStyle()));
//hack to let the clearError be called, even if the value changed method is the one from QDoubleSpinBox.
connect(this, SIGNAL(valueChanged(double)),
this, SLOT(clearError()));
connect(this, SIGNAL(errorWhileParsing(QString)),
this, SLOT(setErrorStyle()));
oldValue = value();
warningIcon = new QLabel(this);
if (QFile(":/./16_light_warning.svg").exists()) {
warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(16, 16));
} else {
warningIcon->setText("!");
}
warningIcon->setStyleSheet("background:transparent;");
warningIcon->move(1, 1);
warningIcon->setVisible(false);
isOldPaletteSaved = false;
areOldMarginsSaved = false;
}
KisDoubleParseSpinBox::~KisDoubleParseSpinBox()
{
}
double KisDoubleParseSpinBox::valueFromText(const QString & text) const
{
lastExprParsed = text;
bool ok;
double ret;
if ( (suffix().isEmpty() || !text.endsWith(suffix())) &&
(prefix().isEmpty() || !text.startsWith(prefix())) ) {
ret = KisNumericParser::parseSimpleMathExpr(text, &ok);
} else {
QString expr = text;
if (text.endsWith(suffix())) {
expr.remove(text.size()-suffix().size(), suffix().size());
}
if(text.startsWith(prefix())){
expr.remove(0, prefix().size());
}
lastExprParsed = expr;
ret = KisNumericParser::parseSimpleMathExpr(expr, &ok);
}
if(qIsNaN(ret) || qIsInf(ret)){
ok = false;
}
if (!ok) {
if (boolLastValid) {
oldValue = value();
}
boolLastValid = false;
ret = oldValue; //in case of error set to minimum.
} else {
if (!boolLastValid) {
oldValue = ret;
}
boolLastValid = true;
}
return ret;
}
QString KisDoubleParseSpinBox::textFromValue(double val) const
{
if (!boolLastValid) {
emit errorWhileParsing(lastExprParsed);
return lastExprParsed;
}
emit noMoreParsingError();
return QDoubleSpinBox::textFromValue(val);
}
QString KisDoubleParseSpinBox::veryCleanText() const
{
return cleanText();
}
QValidator::State KisDoubleParseSpinBox::validate ( QString & input, int & pos ) const
{
Q_UNUSED(input);
Q_UNUSED(pos);
return QValidator::Acceptable;
}
void KisDoubleParseSpinBox::stepBy(int steps)
{
boolLastValid = true; //reset to valid state so we can use the up and down buttons.
emit noMoreParsingError();
QDoubleSpinBox::stepBy(steps);
}
void KisDoubleParseSpinBox::setValue(double value)
{
- if(value == oldValue && hasFocus()){ //avoid to reset the button when it set the value of something that will recall this slot.
+ // Avoid to reset the button when it set the val of something that will recall this slot.
+ if(hasFocus() && QString::number( value, 'f', this->decimals()) == QString::number( oldValue, 'f', this->decimals())){
return;
}
QDoubleSpinBox::setValue(value);
if (!hasFocus()) {
clearError();
}
}
void KisDoubleParseSpinBox::setErrorStyle()
{
if (!boolLastValid) {
//setStyleSheet(_oldStyleSheet + "Background: red; color: white; padding-left: 18px;");
if (!isOldPaletteSaved) {
oldPalette = palette();
}
isOldPaletteSaved = true;
QPalette nP = oldPalette;
nP.setColor(QPalette::Background, Qt::red);
nP.setColor(QPalette::Base, Qt::red);
nP.setColor(QPalette::Text, Qt::white);
setPalette(nP);
if (!areOldMarginsSaved) {
oldMargins = lineEdit()->textMargins();
}
areOldMarginsSaved = true;
if (width() - height() >= 3*height()) { //if we have twice as much place as needed by the warning icon then display it.
QMargins newMargins = oldMargins;
newMargins.setLeft( newMargins.left() + height() - 4 );
lineEdit()->setTextMargins(newMargins);
int h = warningIcon->height();
int hp = height()-2;
if (h != hp) {
warningIcon->resize(hp, hp);
if (QFile(":/./16_light_warning.svg").exists()) {
warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(hp-7, hp-7));
}
}
warningIcon->move(oldMargins.left()+4, 1);
warningIcon->setVisible(true);
}
}
}
void KisDoubleParseSpinBox::clearErrorStyle()
{
if (boolLastValid) {
warningIcon->setVisible(false);
//setStyleSheet(QString());
setPalette(oldPalette);
isOldPaletteSaved = false;
lineEdit()->setTextMargins(oldMargins);
areOldMarginsSaved = false;
}
}
void KisDoubleParseSpinBox::clearError()
{
boolLastValid = true;
emit noMoreParsingError();
oldValue = value();
clearErrorStyle();
}
diff --git a/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp b/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp
index 8e52aa2fc6..55c9d2a113 100644
--- a/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp
+++ b/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp
@@ -1,150 +1,148 @@
/* This file is part of the KDE libraries
Copyright (C) 1998 Mark Donohoe <donohoe@kde.org>
Copyright (C) 1997 Nicolas Hadacek <hadacek@kde.org>
Copyright (C) 1998 Mark Donohoe <donohoe@kde.org>
Copyright (C) 1998 Matthias Ettrich <ettrich@kde.org>
Copyright (C) 1999 Espen Sand <espen@kde.org>
Copyright (C) 2001 Ellis Whitehead <ellis@kde.org>
Copyright (C) 2006 Hamish Rodda <rodda@kde.org>
Copyright (C) 2007 Roberto Raggi <roberto@kdevelop.org>
Copyright (C) 2007 Andreas Hartmetz <ahartmetz@gmail.com>
Copyright (C) 2008 Michael Jansen <kde@michael-jansen.biz>
Copyright (C) 2008 Alexander Dymo <adymo@kdevelop.org>
Copyright (C) 2009 Chani Armitage <chani@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 "KisShortcutsDialog.h"
#include "KisShortcutsDialog_p.h"
#include "kshortcutschemeshelper_p.h"
#include "kshortcutschemeseditor.h"
#include <QApplication>
#include <QDialogButtonBox>
#include <QDomDocument>
#include <QVBoxLayout>
#include <klocalizedstring.h>
#include <kconfiggroup.h>
#include <kmessagebox.h>
#include <ksharedconfig.h>
#include "kxmlguiclient.h"
#include "kxmlguifactory.h"
#include "kactioncollection.h"
KisShortcutsDialog::KisShortcutsDialog(KisShortcutsEditor::ActionTypes types,
KisShortcutsEditor::LetterShortcuts allowLetterShortcuts,
QWidget *parent)
: QWidget(parent)
, d(new KisShortcutsDialogPrivate(this))
{
d->m_shortcutsEditor = new KisShortcutsEditor(this, types, allowLetterShortcuts);
-
-
/* Construct & Connect UI */
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout(mainLayout);
mainLayout->addWidget(d->m_shortcutsEditor);
QHBoxLayout *bottomLayout = new QHBoxLayout;
d->m_schemeEditor = new KShortcutSchemesEditor(this);
connect(d->m_schemeEditor, SIGNAL(shortcutsSchemeChanged(QString)),
this, SLOT(changeShortcutScheme(QString)));
bottomLayout->addLayout(d->m_schemeEditor);
QPushButton *printButton = new QPushButton;
KGuiItem::assign(printButton, KStandardGuiItem::print());
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
buttonBox->addButton(printButton, QDialogButtonBox::ActionRole);
bottomLayout->addWidget(buttonBox);
mainLayout->addLayout(bottomLayout);
connect(printButton, SIGNAL(clicked()), d->m_shortcutsEditor, SLOT(printShortcuts()));
KConfigGroup group(KSharedConfig::openConfig(), "KisShortcutsDialog Settings");
resize(group.readEntry("Dialog Size", sizeHint()));
}
KisShortcutsDialog::~KisShortcutsDialog()
{
KConfigGroup group(KSharedConfig::openConfig(), "KisShortcutsDialog Settings");
group.writeEntry("Dialog Size", size());
delete d;
}
void KisShortcutsDialog::addCollection(KActionCollection *collection, const QString &title)
{
d->m_shortcutsEditor->addCollection(collection, title);
d->m_collections.insert(title, collection);
}
void KisShortcutsDialog::save()
{
d->save();
}
QList<KActionCollection *> KisShortcutsDialog::actionCollections() const
{
return d->m_collections.values();
}
QSize KisShortcutsDialog::sizeHint() const
{
return QSize(600, 480);
}
void KisShortcutsDialog::allDefault()
{
d->m_shortcutsEditor->allDefault();
}
void KisShortcutsDialog::undo()
{
d->undo();
}
void KisShortcutsDialog::importConfiguration(const QString &path)
{
auto config = KSharedConfig::openConfig(path);
d->m_shortcutsEditor->importConfiguration(config.data(), true);
}
void KisShortcutsDialog::exportConfiguration(const QString &path) const
{
auto config = KSharedConfig::openConfig(path);
d->m_shortcutsEditor->exportConfiguration(config.data());
}
void KisShortcutsDialog::saveCustomShortcuts(const QString &path) const
{
auto cg = KSharedConfig::openConfig(path)->group(QStringLiteral("Shortcuts"));
d->m_shortcutsEditor->saveShortcuts(&cg);
d->m_shortcutsEditor->commit();
}
void KisShortcutsDialog::loadCustomShortcuts(const QString &path)
{
auto config = KSharedConfig::openConfig(path);
d->m_shortcutsEditor->importConfiguration(config.data(), false);
}
#include "moc_KisShortcutsDialog.cpp"
diff --git a/libs/widgetutils/xmlgui/KisShortcutsDialog_p.h b/libs/widgetutils/xmlgui/KisShortcutsDialog_p.h
index eb1b9c2a66..de1105abd0 100644
--- a/libs/widgetutils/xmlgui/KisShortcutsDialog_p.h
+++ b/libs/widgetutils/xmlgui/KisShortcutsDialog_p.h
@@ -1,236 +1,235 @@
/* This file is part of the KDE libraries
Copyright (C) 2006,2007 Andreas Hartmetz (ahartmetz@gmail.com)
Copyright (C) 2008 Michael Jansen <kde@michael-jansen.biz>
Copyright (C) 2008 Alexander Dymo <adymo@kdevelop.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 KISSHORTCUTSDIALOG_P_H
#define KISSHORTCUTSDIALOG_P_H
#include "KisShortcutsEditor.h"
#include "kkeysequencewidget.h"
#include "KisShortcutsDialog.h"
#include <kextendableitemdelegate.h>
#include <klocalizedstring.h>
#include <kmessagebox.h>
#include <QKeySequence>
#include <QMetaType>
#include <QModelIndex>
#include <QList>
#include <QCollator>
#include <QHBoxLayout>
class QLabel;
class QTreeWidget;
class QTreeWidgetItem;
class QRadioButton;
class QAction;
class KActionCollection;
class QPushButton;
class QComboBox;
class KisShortcutsDialog;
class KShortcutSchemesEditor;
class QAction;
enum ColumnDesignation {
Name = 0,
LocalPrimary,
LocalAlternate,
Id
};
// XXX: Hmm
enum MyRoles {
ShortcutRole = Qt::UserRole,
DefaultShortcutRole,
ObjectRole
};
/**
* Type used for QTreeWidgetItems
*
* @internal
*/
enum ItemTypes {
NonActionItem = 0,
ActionItem = 1
};
// Return the first item of the list, if it exists
QKeySequence primarySequence(const QList<QKeySequence> &sequences);
// Return the second item of the list, if it exists
QKeySequence alternateSequence(const QList<QKeySequence> &sequences);
class KisShortcutsDialog::KisShortcutsDialogPrivate
{
public:
KisShortcutsDialogPrivate(KisShortcutsDialog *q);
void changeShortcutScheme(const QString &scheme);
void undo();
void save();
QHash<QString, KActionCollection *> m_collections;
KisShortcutsDialog *q;
- KisShortcutsEditor *m_shortcutsEditor;
-
+ KisShortcutsEditor *m_shortcutsEditor {0};
KShortcutSchemesEditor *m_schemeEditor{0};
};
/**
* Mixes the KShortcutWidget into the treeview used by KisShortcutsEditor. When
* selecting an shortcut it changes the display from "CTRL-W" to the Widget.
*
* @bug That delegate uses KExtendableItemDelegate. That means a cell can be
* expanded. When selected a cell is replaced by a KisShortcutsEditor. When
* painting the widget KExtendableItemDelegate reparents the widget to the
* viewport of the itemview it belongs to. The widget is destroyed when the user
* selects another shortcut or explicitly issues a contractItem event. But when
* the user clears the model the delegate misses that event and doesn't delete
* the KShortcutseditor. And remains as a visible artifact in your treeview.
* Additionally when closing your application you get an assertion failure from
* KExtendableItemDelegate.
*
* @internal
*/
class KisShortcutsEditorDelegate : public KExtendableItemDelegate
{
Q_OBJECT
public:
KisShortcutsEditorDelegate(QTreeWidget *parent, bool allowLetterShortcuts);
//reimplemented to have some extra height
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
/**
* Set a list of action collections to check against for conflicting
* shortcuts.
*
* @see KKeySequenceWidget::setCheckActionCollections
*/
void setCheckActionCollections(const QList<KActionCollection *> checkActionCollections);
bool eventFilter(QObject *, QEvent *) override;
private:
mutable QPersistentModelIndex m_editingIndex;
bool m_allowLetterShortcuts;
QWidget *m_editor;
//! List of actionCollections to check for conflicts.
QList<KActionCollection *> m_checkActionCollections;
Q_SIGNALS:
void shortcutChanged(QVariant, const QModelIndex &);
public Q_SLOTS:
void hiddenBySearchLine(QTreeWidgetItem *, bool);
private Q_SLOTS:
void itemActivated(QModelIndex index);
/**
* When the user collapses a hole subtree of shortcuts then remove eventually
* extended items. Else we get that artifact bug. See above.
*/
void itemCollapsed(QModelIndex index);
/**
* If the user allowed stealing a shortcut we want to be able to undo
* that.
*/
void stealShortcut(const QKeySequence &seq, QAction *action);
void keySequenceChanged(const QKeySequence &);
};
/**
* Edit a shortcut. This widget is displayed when a user clicks on a shortcut
* from the list. It contains radio buttons choosing between default and custom
* shortcuts, and a button to configure the custom shortcut.
*
* @see KisShortcutsEditorDeligate::itemActivated
* @see KisShortcutWidget.cpp
*
* @internal
*/
class ShortcutEditWidget : public QWidget
{
Q_OBJECT
public:
ShortcutEditWidget(QWidget *viewport, const QKeySequence &defaultSeq, const QKeySequence &activeSeq,
bool allowLetterShortcuts);
//! @see KKeySequenceWidget::setCheckActionCollections()
void setCheckActionCollections(const QList<KActionCollection *> checkActionCollections);
//@{
//! @see KKeySequenceWidget::checkAgainstStandardShortcuts()
KKeySequenceWidget::ShortcutTypes checkForConflictsAgainst() const;
void setCheckForConflictsAgainst(KKeySequenceWidget::ShortcutTypes);
//@}
//@{
//! @see KKeySequenceWidget::checkAgainstStandardShortcuts()
bool multiKeyShortcutsAllowed() const;
void setMultiKeyShortcutsAllowed(bool);
//@}
//! @see KKeySequenceWidget::setComponentName
void setComponentName(const QString componentName);
void setAction(QObject *action);
void paintEvent(QPaintEvent *pe) override;
Q_SIGNALS:
//! Emitted when the key sequence is changed.
void keySequenceChanged(const QKeySequence &);
//! @see KKeySequenceWidget::stealShortcut()
void stealShortcut(const QKeySequence &seq, QAction *action);
public Q_SLOTS:
//! Set the displayed sequences
void setKeySequence(const QKeySequence &activeSeq);
private Q_SLOTS:
void defaultToggled(bool);
void setCustom(const QKeySequence &);
private:
QLabel *m_defaultLabel;
QKeySequence m_defaultKeySequence;
QRadioButton *m_defaultRadio;
QRadioButton *m_customRadio;
KKeySequenceWidget *m_customEditor;
bool m_isUpdating;
QObject *m_action;
};
#endif /* KISSHORTCUTSDIALOG_P_H */
diff --git a/libs/widgetutils/xmlgui/kaboutkdedialog_p.cpp b/libs/widgetutils/xmlgui/kaboutkdedialog_p.cpp
index cdc1a27ac6..316f0da8a7 100644
--- a/libs/widgetutils/xmlgui/kaboutkdedialog_p.cpp
+++ b/libs/widgetutils/xmlgui/kaboutkdedialog_p.cpp
@@ -1,159 +1,159 @@
/* This file is part of the KDE libraries
Copyright (C) 2007 Urs Wolfer <uwolfer at kde.org>
Parts of this class have been take from the KAboutKDE class, which was
Copyright (C) 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 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 "kaboutkdedialog_p.h"
#include <QDialogButtonBox>
#include <QFrame>
#include <QLabel>
#include <QLayout>
#include <QTabWidget>
#include <QStandardPaths>
#include <klocalizedstring.h>
#include <ktitlewidget.h>
#include <kis_icon_utils.h>
namespace KDEPrivate
{
KAboutKdeDialog::KAboutKdeDialog(QWidget *parent)
: QDialog(parent),
d(0)
{
setWindowTitle(i18n("About KDE"));
KTitleWidget *titleWidget = new KTitleWidget(this);
titleWidget->setText(i18n("<html><font size=\"5\">KDE - Be Free!</font></html>"));
titleWidget->setPixmap(KisIconUtils::loadIcon(QStringLiteral("kde")).pixmap(48), KTitleWidget::ImageLeft);
QLabel *about = new QLabel;
about->setMargin(10);
about->setAlignment(Qt::AlignTop);
about->setWordWrap(true);
about->setOpenExternalLinks(true);
about->setTextInteractionFlags(Qt::TextBrowserInteraction);
about->setText(i18n("<html>"
"<b>KDE</b> is a world-wide network of software engineers, artists, writers, translators and facilitators "
"who are committed to <a href=\"%1\">Free Software</a> development. "
"This community has created hundreds of Free Software applications as part of the KDE "
"frameworks, workspaces and applications.<br /><br />"
"KDE is a cooperative enterprise in which no single entity controls the "
"efforts or products of KDE to the exclusion of others. Everyone is welcome to join and "
"contribute to KDE, including you.<br /><br />"
"Visit <a href=\"%2\">%2</a> for "
"more information about the KDE community and the software we produce.</html>",
- QStringLiteral("http://www.gnu.org/philosophy/free-sw.html"),
- QStringLiteral("http://www.kde.org/")));
+ QStringLiteral("https://www.gnu.org/philosophy/free-sw.html"),
+ QStringLiteral("https://www.kde.org/")));
QLabel *report = new QLabel;
report->setMargin(10);
report->setAlignment(Qt::AlignTop);
report->setWordWrap(true);
report->setOpenExternalLinks(true);
report->setTextInteractionFlags(Qt::TextBrowserInteraction);
report->setText(i18n("<html>"
"Software can always be improved, and the KDE team is ready to "
"do so. However, you - the user - must tell us when "
"something does not work as expected or could be done better.<br /><br />"
"KDE has a bug tracking system. Visit "
"<a href=\"%1\">%1</a> or "
"use the \"Report Bug...\" dialog from the \"Help\" menu to report bugs.<br /><br />"
"If you have a suggestion for improvement then you are welcome to use "
"the bug tracking system to register your wish. Make sure you use the "
"severity called \"Wishlist\".</html>",
QStringLiteral("https://bugs.kde.org/")));
QLabel *join = new QLabel;
join->setMargin(10);
join->setAlignment(Qt::AlignTop);
join->setWordWrap(true);
join->setOpenExternalLinks(true);
join->setTextInteractionFlags(Qt::TextBrowserInteraction);
join->setText(i18n("<html>"
"You do not have to be a software developer to be a member of the "
"KDE team. You can join the national teams that translate "
"program interfaces. You can provide graphics, themes, sounds, and "
"improved documentation. You decide!"
"<br /><br />"
"Visit "
"<a href=\"%1\">%1</a> "
"for information on some projects in which you can participate."
"<br /><br />"
"If you need more information or documentation, then a visit to "
"<a href=\"%2\">%2</a> "
"will provide you with what you need.</html>",
- QStringLiteral("http://www.kde.org/community/getinvolved/"),
- QStringLiteral("http://techbase.kde.org/")));
+ QStringLiteral("https://community.kde.org/Get_Involved"),
+ QStringLiteral("https://techbase.kde.org/")));
QLabel *support = new QLabel;
support->setMargin(10);
support->setAlignment(Qt::AlignTop);
support->setWordWrap(true);
support->setOpenExternalLinks(true);
support->setTextInteractionFlags(Qt::TextBrowserInteraction);
support->setText(i18n("<html>"
"KDE software is and will always be available free of charge, however creating it is not free.<br /><br />"
"To support development the KDE community has formed the KDE e.V., a non-profit organization "
"legally founded in Germany. KDE e.V. represents the KDE community in legal and financial matters. "
"See <a href=\"%1\">%1</a>"
" for information on KDE e.V.<br /><br />"
"KDE benefits from many kinds of contributions, including financial. "
"We use the funds to reimburse members and others for expenses "
"they incur when contributing. Further funds are used for legal "
"support and organizing conferences and meetings. <br /> <br />"
"We would like to encourage you to support our efforts with a "
"financial donation, using one of the ways described at "
"<a href=\"%2\">%2</a>."
"<br /><br />Thank you very much in advance for your support.</html>",
- QStringLiteral("http://ev.kde.org/"),
- QStringLiteral("http://www.kde.org/community/donations/")) + QLatin1String("<br /><br />")); // FIXME: ugly <br /> at the end...
+ QStringLiteral("https://ev.kde.org/"),
+ QStringLiteral("https://www.kde.org/community/donations/")) + QLatin1String("<br /><br />")); // FIXME: ugly <br /> at the end...
QTabWidget *tabWidget = new QTabWidget;
tabWidget->setUsesScrollButtons(false);
tabWidget->addTab(about, i18nc("About KDE", "&About"));
tabWidget->addTab(report, i18n("&Report Bugs or Wishes"));
tabWidget->addTab(join, i18n("&Join KDE"));
tabWidget->addTab(support, i18n("&Support KDE"));
QLabel *image = new QLabel;
image->setPixmap(QStringLiteral(":/kxmlgui5/aboutkde.png"));
QHBoxLayout *midLayout = new QHBoxLayout;
midLayout->addWidget(image);
midLayout->addWidget(tabWidget);
QDialogButtonBox *buttonBox = new QDialogButtonBox;
buttonBox->setStandardButtons(QDialogButtonBox::Close);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(titleWidget);
mainLayout->addLayout(midLayout);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
}
}
diff --git a/libs/widgetutils/xmlgui/kbugreport.cpp b/libs/widgetutils/xmlgui/kbugreport.cpp
index 9d2cdebbfc..3ba99a225b 100644
--- a/libs/widgetutils/xmlgui/kbugreport.cpp
+++ b/libs/widgetutils/xmlgui/kbugreport.cpp
@@ -1,233 +1,233 @@
/* 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 <QProcess>
#include <QCoreApplication>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QLayout>
#include <QRadioButton>
#include <QGroupBox>
#include <QLocale>
#include <QCloseEvent>
#include <QLabel>
#include <QUrl>
#include <QUrlQuery>
#include <QStandardPaths>
#include <QComboBox>
#include <QLineEdit>
#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>"
"<p>Please read <b><a href=\"https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html\">this guide</a></b> for reporting bugs first!</p>"
"<p>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 "
+ "window on <a href=\"https://bugs.kde.org\">https://bugs.kde.org</a> where you will find "
"a form to fill in. The information displayed above will be transferred to that server.</p></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("&Submit Bug Report"));
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);
// TODO: guess and fill OS(sys_os) and Platform(rep_platform) fields
#ifdef Q_OS_WIN
query.addQueryItem(QStringLiteral("op_sys"), QStringLiteral("MS Windows"));
query.addQueryItem(QStringLiteral("rep_platform"), QStringLiteral("MS Windows"));
#endif
url.setQuery(query);
}
void KBugReport::accept()
{
QDesktopServices::openUrl(d->url);
}
#include "moc_kbugreport.cpp"
diff --git a/libs/widgetutils/xmlgui/kxmlguibuilder.cpp b/libs/widgetutils/xmlgui/kxmlguibuilder.cpp
index c4944d55ec..532c27fdec 100644
--- a/libs/widgetutils/xmlgui/kxmlguibuilder.cpp
+++ b/libs/widgetutils/xmlgui/kxmlguibuilder.cpp
@@ -1,433 +1,433 @@
/* This file is part of the KDE project
Copyright (C) 2000 Simon Hausmann <hausmann@kde.org>
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 "kxmlguibuilder.h"
#include "kxmlguiclient.h"
#include "ktoolbar.h"
#include "kmainwindow.h"
#include "kxmlguiwindow.h"
#include "kmenumenuhandler_p.h"
#include <kauthorized.h>
#include <klocalizedstring.h>
#include <QDomElement>
#include <QObject>
#include <QMutableStringListIterator>
#include <QAction>
#include <QMenu>
#include <QMenuBar>
#include <QStatusBar>
#include <QDebug>
#include <kis_icon_utils.h>
#if defined(KCONFIG_BEFORE_5_24)
# define authorizeAction authorizeKAction
#endif
using namespace KDEPrivate;
class KXMLGUIBuilderPrivate
{
public:
- KXMLGUIBuilderPrivate() : m_client(0L) {}
+ KXMLGUIBuilderPrivate() { }
~KXMLGUIBuilderPrivate() { }
- QWidget *m_widget;
+ QWidget *m_widget {0};
QString tagMainWindow;
QString tagMenuBar;
QString tagMenu;
QString tagToolBar;
QString tagStatusBar;
QString tagSeparator;
QString tagTearOffHandle;
QString tagMenuTitle;
QString attrName;
QString attrLineSeparator;
QString attrDomain;
QString attrText1;
QString attrText2;
QString attrContext;
QString attrIcon;
- KXMLGUIClient *m_client;
+ KXMLGUIClient *m_client {0};
- KMenuMenuHandler *m_menumenuhandler;
+ KMenuMenuHandler *m_menumenuhandler {0};
};
KXMLGUIBuilder::KXMLGUIBuilder(QWidget *widget)
: d(new KXMLGUIBuilderPrivate)
{
d->m_widget = widget;
d->tagMainWindow = QStringLiteral("mainwindow");
d->tagMenuBar = QStringLiteral("menubar");
d->tagMenu = QStringLiteral("menu");
d->tagToolBar = QStringLiteral("toolbar");
d->tagStatusBar = QStringLiteral("statusbar");
d->tagSeparator = QStringLiteral("separator");
d->tagTearOffHandle = QStringLiteral("tearoffhandle");
d->tagMenuTitle = QStringLiteral("title");
d->attrName = QStringLiteral("name");
d->attrLineSeparator = QStringLiteral("lineseparator");
d->attrDomain = QStringLiteral("translationDomain");
d->attrText1 = QStringLiteral("text");
d->attrText2 = QStringLiteral("Text");
d->attrContext = QStringLiteral("context");
d->attrIcon = QStringLiteral("icon");
d->m_menumenuhandler = new KMenuMenuHandler(this);
}
KXMLGUIBuilder::~KXMLGUIBuilder()
{
delete d->m_menumenuhandler;
delete d;
}
QWidget *KXMLGUIBuilder::widget()
{
return d->m_widget;
}
QStringList KXMLGUIBuilder::containerTags() const
{
QStringList res;
res << d->tagMenu << d->tagToolBar << d->tagMainWindow << d->tagMenuBar << d->tagStatusBar;
return res;
}
QWidget *KXMLGUIBuilder::createContainer(QWidget *parent, int index, const QDomElement &element, QAction *&containerAction)
{
containerAction = 0;
if (element.attribute(QStringLiteral("deleted")).toLower() == QLatin1String("true")) {
return 0;
}
const QString tagName = element.tagName().toLower();
if (tagName == d->tagMainWindow) {
KMainWindow *mainwindow = qobject_cast<KMainWindow *>(d->m_widget); // could be 0
return mainwindow;
}
if (tagName == d->tagMenuBar) {
KMainWindow *mainWin = qobject_cast<KMainWindow *>(d->m_widget);
QMenuBar *bar = 0;
if (mainWin) {
bar = mainWin->menuBar();
}
if (!bar) {
bar = new QMenuBar(d->m_widget);
}
bar->show();
return bar;
}
if (tagName == d->tagMenu) {
// Look up to see if we are inside a mainwindow. If yes, then
// use it as parent widget (to get kaction to plug itself into the
// mainwindow). Don't use a popupmenu as parent widget, otherwise
// the popup won't be hidden if it is used as a standalone menu as well.
//
// Note: menus with a parent of 0, coming from child clients, can be
// leaked if the child client is deleted without a proper removeClient call, though.
QWidget *p = parent;
if (!p && qobject_cast<QMainWindow *>(d->m_widget)) {
p = d->m_widget;
}
while (p && !qobject_cast<QMainWindow *>(p)) {
p = p->parentWidget();
}
QString name = element.attribute(d->attrName);
if (!KAuthorized::authorizeAction(name)) {
return 0;
}
QMenu *popup = new QMenu(p);
popup->setObjectName(name);
d->m_menumenuhandler->insertMenu(popup);
QString i18nText;
QDomElement textElem = element.namedItem(d->attrText1).toElement();
if (textElem.isNull()) { // try with capital T
textElem = element.namedItem(d->attrText2).toElement();
}
const QString text = textElem.text();
const QString context = textElem.attribute(d->attrContext);
//qDebug(260) << "DOMAIN" << KLocalizedString::applicationDomain();
//qDebug(260) << "ELEMENT TEXT:" << text;
if (text.isEmpty()) { // still no luck
i18nText = i18n("No text");
} else {
QByteArray domain = textElem.attribute(d->attrDomain).toUtf8();
if (domain.isEmpty()) {
domain = element.ownerDocument().documentElement().attribute(d->attrDomain).toUtf8();
if (domain.isEmpty()) {
domain = KLocalizedString::applicationDomain();
}
}
if (context.isEmpty()) {
i18nText = i18nd(domain.constData(), text.toUtf8().constData());
} else {
i18nText = i18ndc(domain.constData(), context.toUtf8().constData(), text.toUtf8().constData());
}
}
//qDebug(260) << "ELEMENT i18n TEXT:" << i18nText;
const QString icon = element.attribute(d->attrIcon);
QIcon pix;
if (!icon.isEmpty()) {
pix = KisIconUtils::loadIcon(icon);
}
if (parent) {
QAction *act = popup->menuAction();
if (!icon.isEmpty()) {
act->setIcon(pix);
}
act->setText(i18nText);
if (index == -1 || index >= parent->actions().count()) {
parent->addAction(act);
} else {
parent->insertAction(parent->actions().value(index), act);
}
containerAction = act;
containerAction->setObjectName(name);
}
return popup;
}
if (tagName == d->tagToolBar) {
QString name = element.attribute(d->attrName);
KToolBar *bar = static_cast<KToolBar *>(d->m_widget->findChild<KToolBar *>(name));
if (!bar) {
bar = new KToolBar(name, d->m_widget, false);
}
if (qobject_cast<KMainWindow *>(d->m_widget)) {
if (d->m_client && !d->m_client->xmlFile().isEmpty()) {
bar->addXMLGUIClient(d->m_client);
}
}
bar->loadState(element);
return bar;
}
if (tagName == d->tagStatusBar) {
KMainWindow *mainWin = qobject_cast<KMainWindow *>(d->m_widget);
if (mainWin) {
mainWin->statusBar()->show();
return mainWin->statusBar();
}
QStatusBar *bar = new QStatusBar(d->m_widget);
return bar;
}
return 0L;
}
void KXMLGUIBuilder::removeContainer(QWidget *container, QWidget *parent, QDomElement &element, QAction *containerAction)
{
// Warning parent can be 0L
if (qobject_cast<QMenu *>(container)) {
if (parent) {
parent->removeAction(containerAction);
}
delete container;
} else if (qobject_cast<KToolBar *>(container)) {
KToolBar *tb = static_cast<KToolBar *>(container);
tb->saveState(element);
delete tb;
} else if (qobject_cast<QMenuBar *>(container)) {
QMenuBar *mb = static_cast<QMenuBar *>(container);
mb->hide();
// Don't delete menubar - it can be reused by createContainer.
// If you decide that you do need to delete the menubar, make
// sure that QMainWindow::d->mb does not point to a deleted
// menubar object.
} else if (qobject_cast<QStatusBar *>(container)) {
if (qobject_cast<KMainWindow *>(d->m_widget)) {
container->hide();
} else {
delete static_cast<QStatusBar *>(container);
}
} else {
qWarning() << "Unhandled container to remove : " << container->metaObject()->className();
}
}
QStringList KXMLGUIBuilder::customTags() const
{
QStringList res;
res << d->tagSeparator << d->tagTearOffHandle << d->tagMenuTitle;
return res;
}
QAction *KXMLGUIBuilder::createCustomElement(QWidget *parent, int index, const QDomElement &element)
{
QAction *before = 0L;
if (index > 0 && index < parent->actions().count()) {
before = parent->actions().at(index);
}
const QString tagName = element.tagName().toLower();
if (tagName == d->tagSeparator) {
if (QMenu *menu = qobject_cast<QMenu *>(parent)) {
// QMenu already cares for leading/trailing/repeated separators
// no need to check anything
return menu->insertSeparator(before);
} else if (QMenuBar *bar = qobject_cast<QMenuBar *>(parent)) {
QAction *separatorAction = new QAction(bar);
separatorAction->setSeparator(true);
bar->insertAction(before, separatorAction);
return separatorAction;
} else if (KToolBar *bar = qobject_cast<KToolBar *>(parent)) {
/* FIXME KAction port - any need to provide a replacement for lineSeparator/normal separator?
bool isLineSep = true;
QDomNamedNodeMap attributes = element.attributes();
unsigned int i = 0;
for (; i < attributes.length(); i++ )
{
QDomAttr attr = attributes.item( i ).toAttr();
if ( attr.name().toLower() == d->attrLineSeparator &&
attr.value().toLower() == QStringLiteral("false") )
{
isLineSep = false;
break;
}
}
if ( isLineSep )
return bar->insertSeparator( index ? bar->actions()[index - 1] : 0L );
else*/
return bar->insertSeparator(before);
}
} else if (tagName == d->tagTearOffHandle) {
static_cast<QMenu *>(parent)->setTearOffEnabled(true);
} else if (tagName == d->tagMenuTitle) {
if (QMenu *m = qobject_cast<QMenu *>(parent)) {
QString i18nText;
const QString text = element.text();
if (text.isEmpty()) {
i18nText = i18n("No text");
} else {
QByteArray domain = element.attribute(d->attrDomain).toUtf8();
if (domain.isEmpty()) {
domain = element.ownerDocument().documentElement().attribute(d->attrDomain).toUtf8();
if (domain.isEmpty()) {
domain = KLocalizedString::applicationDomain();
}
}
i18nText = i18nd(domain.constData(), qPrintable(text));
}
QString icon = element.attribute(d->attrIcon);
QIcon pix;
if (!icon.isEmpty()) {
pix = KisIconUtils::loadIcon(icon);
}
if (!icon.isEmpty()) {
return m->insertSection(before, pix, i18nText);
} else {
return m->insertSection(before, i18nText);
}
}
}
QAction *blank = new QAction(parent);
blank->setVisible(false);
parent->insertAction(before, blank);
return blank;
}
void KXMLGUIBuilder::removeCustomElement(QWidget *parent, QAction *action)
{
parent->removeAction(action);
}
KXMLGUIClient *KXMLGUIBuilder::builderClient() const
{
return d->m_client;
}
void KXMLGUIBuilder::setBuilderClient(KXMLGUIClient *client)
{
d->m_client = client;
}
void KXMLGUIBuilder::finalizeGUI(KXMLGUIClient *)
{
KXmlGuiWindow *window = qobject_cast<KXmlGuiWindow *>(d->m_widget);
if (!window) {
return;
}
#if 0
KToolBar *toolbar = 0;
QListIterator<KToolBar> it(((KMainWindow *)d->m_widget)->toolBarIterator());
while ((toolbar = it.current())) {
//qDebug(260) << "KXMLGUIBuilder::finalizeGUI toolbar=" << (void*)toolbar;
++it;
toolbar->positionYourself();
}
#else
window->finalizeGUI(false);
#endif
}
void KXMLGUIBuilder::virtual_hook(int, void *)
{
/*BASE::virtual_hook( id, data );*/
}
diff --git a/packaging/android/apk/AndroidManifest.xml b/packaging/android/apk/AndroidManifest.xml
index 6903d72053..960fc3957d 100644
--- a/packaging/android/apk/AndroidManifest.xml
+++ b/packaging/android/apk/AndroidManifest.xml
@@ -1,87 +1,87 @@
<?xml version='1.0' encoding='utf-8'?>
-<manifest package="org.krita" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
+<manifest package="org.krita" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="4.2.8" android:versionCode="4280" android:installLocation="auto">
<application android:hardwareAccelerated="true"
android:name="org.qtproject.qt5.android.bindings.QtApplication"
android:label="Krita"
android:icon="@mipmap/ic_launcher" >
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:name="org.krita.android.MainActivity"
android:label="Krita"
android:screenOrientation="unspecified"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="krita"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:plugins/mediaservice/libqtmedia_android.so"/>
<meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroidBearer.jar:jar/QtMultimedia.jar"/>
<meta-data android:name="android.app.static_init_classes" android:value="org.qtproject.qt5.android.multimedia.QtMultimediaUtils"/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
</activity>
<activity android:name="org.qtproject.qt5.android.bindings.QtActivity" />
</application>
<supports-screens
android:xlargeScreens="true"
android:largeScreens="true"
android:normalScreens="false"
android:smallScreens="false"/>
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
diff --git a/packaging/android/fdroid/build.gradle b/packaging/android/fdroid/build.gradle
new file mode 100644
index 0000000000..dab0cd85b6
--- /dev/null
+++ b/packaging/android/fdroid/build.gradle
@@ -0,0 +1,5 @@
+// Hack!
+// This is a placeholder file, the real build.gradle is in $KRITA_SRC/packaging/android/apk/
+// without this f-droid build will fail.
+task clean() {}
+task assembleRelease() {}
diff --git a/packaging/linux/flatpak/org.kde.krita-nightly.json b/packaging/linux/flatpak/org.kde.krita-nightly.json
deleted file mode 100644
index 304487b6c1..0000000000
--- a/packaging/linux/flatpak/org.kde.krita-nightly.json
+++ /dev/null
@@ -1,355 +0,0 @@
-{
- "app-id": "org.kde.krita",
- "branch": "master",
- "runtime": "org.kde.Platform",
- "runtime-version": "5.9",
- "sdk": "org.kde.Sdk",
- "command": "krita",
- "rename-icon": "krita",
- "tags": [
- "nightly"
- ],
- "desktop-file-name-prefix": "(Nightly) ",
- "finish-args": [
- "--share=ipc",
- "--socket=x11",
- "--share=network",
- "--device=dri",
- "--filesystem=home",
- "--env=PYTHONPATH=/app/lib/python3/dist-packages"
- ],
- "cleanup": [
- "/include",
- "/lib/pkgconfig",
- "/lib/cmake",
- "/share/aclocal",
- "/share/pkgconfig",
- "*.la",
- "*.cmake"
- ],
- "modules": [
- {
- "name": "boost",
- "buildsystem": "simple",
- "build-commands": [
- "./bootstrap.sh --prefix=/app --with-libraries=system",
- "./b2 -j `nproc` install"
- ],
- "cleanup": [
- "*.a"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://sourceforge.net/projects/boost/files/boost/1.63.0/boost_1_63_0.tar.bz2",
- "sha256": "beae2529f759f6b3bf3f4969a19c2e9d6f0c503edcb2de4a61d1428519fcb3b0"
- }
- ]
- },
- {
- "name": "eigen",
- "buildsystem": "cmake-ninja",
- "builddir": true,
- "config-opts": [
- "-DCMAKE_BUILD_TYPE=Release"
- ],
- "cleanup": [
- "/share"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://bitbucket.org/eigen/eigen/get/3.3.4.tar.bz2",
- "sha256": "dd254beb0bafc695d0f62ae1a222ff85b52dbaa3a16f76e781dce22d0d20a4a6"
- }
- ]
- },
- {
- "name": "exiv2",
- "buildsystem": "cmake-ninja",
- "builddir": true,
- "config-opts": [
- "-DCMAKE_BUILD_TYPE=Release"
- ],
- "cleanup": [
- "/bin",
- "*.a",
- "/share/man"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "http://www.exiv2.org/builds/exiv2-0.26-trunk.tar.gz",
- "sha256": "c75e3c4a0811bf700d92c82319373b7a825a2331c12b8b37d41eb58e4f18eafb"
- }
- ]
- },
- {
- "name": "ilmbase",
- "config-opts": [
- "--disable-static"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://download.savannah.nongnu.org/releases/openexr/ilmbase-2.2.1.tar.gz",
- "sha256": "cac206e63be68136ef556c2b555df659f45098c159ce24804e9d5e9e0286609e"
- }
- ]
- },
- {
- "name": "openexr",
- "config-opts": [
- "--disable-static"
- ],
- "cleanup": [
- "/bin",
- "/share/doc"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://download.savannah.nongnu.org/releases/openexr/openexr-2.2.1.tar.gz",
- "sha256": "8f9a5af6131583404261931d9a5c83de0a425cb4b8b25ddab2b169fbf113aecd"
- }
- ]
- },
- {
- "name": "libraw",
- "config-opts": [
- "--disable-static"
- ],
- "cleanup": [
- "/bin",
- "/share/doc"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://www.libraw.org/data/LibRaw-0.18.6.tar.gz",
- "sha256": "e5b8acca558aa457bc9214802004320c5610d1434c2adb1f3ea367f026afa53b"
- }
- ]
- },
- {
- "name": "fftw",
- "config-opts": [
- "--disable-static",
- "--enable-shared",
- "--disable-doc",
- "--enable-threads"
- ],
- "cleanup": [
- "/bin",
- "/share/man"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "http://www.fftw.org/fftw-3.3.7.tar.gz",
- "sha256": "3b609b7feba5230e8f6dd8d245ddbefac324c5a6ae4186947670d9ac2cd25573"
- }
- ]
- },
- {
- "name": "opencolorio",
- "buildsystem": "cmake",
- "builddir": true,
- "build-options": {
- "arch": {
- "arm": {
- "config-opts": [
- "-DOCIO_USE_SSE=OFF"
- ]
- },
- "aarch64": {
- "config-opts": [
- "-DOCIO_USE_SSE=OFF"
- ]
- }
- }
- },
- "config-opts": [
- "-DCMAKE_BUILD_TYPE=Release",
- "-DOCIO_BUILD_STATIC=OFF"
- ],
- "cleanup": [
- "/bin"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://github.com/imageworks/OpenColorIO/archive/v1.0.9.tar.gz",
- "sha256": "27c81e691c15753cd2b560c2ca4bd5679a60c2350eedd43c99d44ca25d65ea7f"
- }
- ]
- },
- {
- "name": "vc",
- "skip-arches": [
- "aarch64",
- "arm"
- ],
- "buildsystem": "cmake-ninja",
- "builddir": true,
- "config-opts": [
- "-DCMAKE_BUILD_TYPE=Release"
- ],
- "cleanup": [
- "*.a"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://github.com/VcDevel/Vc/releases/download/1.3.3/Vc-1.3.3.tar.gz",
- "sha256": "08c629d2e14bfb8e4f1a10f09535e4a3c755292503c971ab46637d2986bdb4fe"
- },
- {
- "type": "shell",
- "commands": [
- "sed -i 's/x86|/x86|i686|/' CMakeLists.txt"
- ]
- }
- ]
- },
- {
- "name": "poppler-data",
- "buildsystem": "cmake-ninja",
- "builddir": true,
- "config-opts": [
- "-DCMAKE_BUILD_TYPE=Release"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://poppler.freedesktop.org/poppler-data-0.4.8.tar.gz",
- "sha256": "1096a18161f263cccdc6d8a2eb5548c41ff8fcf9a3609243f1b6296abdf72872"
- }
- ]
- },
- {
- "name": "poppler",
- "buildsystem": "cmake-ninja",
- "builddir": true,
- "config-opts": [
- "-DCMAKE_BUILD_TYPE=Release",
- "-DBUILD_GTK_TESTS=OFF",
- "-DBUILD_QT5_TESTS=OFF",
- "-DBUILD_CPP_TESTS=OFF",
- "-DENABLE_UTILS=OFF",
- "-DENABLE_CPP=OFF",
- "-DENABLE_GLIB=OFF"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://poppler.freedesktop.org/poppler-0.62.0.tar.xz",
- "sha256": "5b9a73dfd4d6f61d165ada1e4f0abd2d420494bf9d0b1c15d0db3f7b83a729c6"
- }
- ]
- },
- {
- "name": "gsl",
- "config-opts": [
- "--disable-static"
- ],
- "cleanup": [
- "/bin",
- "/share/info",
- "/share/man"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://ftpmirror.gnu.org/gnu/gsl/gsl-2.4.tar.gz",
- "sha256": "4d46d07b946e7b31c19bbf33dda6204d7bedc2f5462a1bae1d4013426cd1ce9b"
- }
- ]
- },
- {
- "name": "gmic-qt",
- "buildsystem": "cmake-ninja",
- "builddir": true,
- "config-opts": [
- "-DCMAKE_BUILD_TYPE=Release",
- "-DGMIC_QT_HOST=krita",
- "-DGMIC_PATH=./gmic/src"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://github.com/c-koi/gmic-qt/archive/v.218.tar.gz",
- "sha256": "697861e5a1e024a3ccf9c96e513ec9f60f65d6e3c438ba355afcd10839b06f39"
- },
- {
- "type": "file",
- "url": "https://gmic.eu/files/source/gmic_2.1.8.tar.gz",
- "sha256": "f22783f14cb202dec4a840733f2028f6e2c464fdd2f0166fc38943702cea6bde",
- "dest-filename": "gmic.tar.gz"
- },
- {
- "type": "shell",
- "commands": [
- "tar xf gmic.tar.gz",
- "mv gmic-* gmic"
- ]
- }
- ],
- "post-install": [
- "install -Dm755 gmic_krita_qt /app/bin/gmic_krita_qt"
- ]
- },
- {
- "name": "sip",
- "buildsystem": "simple",
- "build-commands": [
- "python3 configure.py --bindir=/app/bin --destdir=/app/lib/python3/dist-packages --incdir=/app/include/python3 --sipdir=/app/share/sip --stubsdir=/app/lib/python3/dist-packages",
- "make -j `nproc`",
- "make install"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://sourceforge.net/projects/pyqt/files/sip/sip-4.19.6/sip-4.19.6.tar.gz",
- "sha256": "9dda27ae181bea782ebc8768d29f22f85ab6e5128ee3ab21f491febad707925a"
- }
- ]
- },
- {
- "name": "pyqt",
- "buildsystem": "simple",
- "build-commands": [
- "python3 configure.py --confirm-license --sip-incdir=/app/include/python3 --bindir=/app/bin --destdir=/app/lib/python3/dist-packages --designer-plugindir=/app/lib/plugins/designer --qml-plugindir=/app/lib/plugins/PyQt5 --sipdir=/app/share/sip --stubsdir=/app/lib/python3/dist-packages/PyQt5",
- "make -j `nproc`",
- "make install"
- ],
- "sources": [
- {
- "type": "archive",
- "url": "https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.9.2/PyQt5_gpl-5.9.2.tar.gz",
- "sha256": "c190dac598c97b0113ca5e7a37c71c623f02d1d713088addfacac4acfa4b8394"
- }
- ]
- },
- {
- "name": "krita",
- "buildsystem": "cmake-ninja",
- "builddir": true,
- "build-options": {
- "env": {
- "PYTHONPATH": "/app/lib/python3/dist-packages"
- }
- },
- "config-opts": [
- "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
- ],
- "sources": [
- {
- "type": "git",
- "url": "git://anongit.kde.org/krita.git",
- "branch": "master"
- }
- ]
- }
- ]
-}
diff --git a/packaging/linux/snap/build_in_container.sh b/packaging/linux/snap/build_in_container.sh
index 0edfffe8c5..afc3d0f20d 100755
--- a/packaging/linux/snap/build_in_container.sh
+++ b/packaging/linux/snap/build_in_container.sh
@@ -1,19 +1,19 @@
#!/bin/bash
set -ex
cd /workspace/snap
ping -c1 networkcheck.kde.org
apt-key adv --keyserver keyserver.ubuntu.com --recv E6D4736255751E5D
echo 'deb http://archive.neon.kde.org/unstable bionic main' > /etc/apt/sources.list.d/neon.list
apt update
-snap install --edge --classic snapcraft
+snap install --classic snapcraft
snapcraft --version
snapcraft --destructive-mode
mkdir -p /workspace/result
mv *.snap /workspace/result/
diff --git a/packaging/linux/snap/snapcraft.yaml b/packaging/linux/snap/snapcraft.yaml
index 6a71aa7a57..6165c1163f 100644
--- a/packaging/linux/snap/snapcraft.yaml
+++ b/packaging/linux/snap/snapcraft.yaml
@@ -1,179 +1,173 @@
name: krita
-version: 4.2.7.1
-summary: Krita is the digital painting studio for artists
-description: Krita is a creative application for raster images. Whether you want to create
- from scratch or work with existing images, Krita is for you. You can work with
- photos or scanned images, or start with a blank slate. Krita supports most
- graphics tablets out of the box.
+version: 4.2.8.2
+adopt-info: krita
+
base: core18
+confinement: strict
apps:
krita:
+ common-id: org.kde.krita
command: usr/bin/krita
command-chain:
- bin/qt5-launch
plugs: [x11, unity7, home, opengl, network, network-bind, removable-media, desktop, desktop-legacy]
- desktop: usr/share/applications/org.kde.krita.desktop
layout:
/usr/bin/ffmpeg:
bind-file: $SNAP/usr/bin/ffmpeg
parts:
krita:
plugin: cmake
configflags:
- "-DCMAKE_INSTALL_PREFIX=/usr"
- "-DCMAKE_BUILD_TYPE=Release"
- "-DENABLE_TESTING=OFF"
- "-DBUILD_TESTING=OFF"
- "-DHIDE_SAFE_ASSERTS=OFF"
- "-DKDE_SKIP_TEST_SETTINGS=ON"
- source: https://download.kde.org/stable/krita/4.2.7.1/krita-4.2.7.1.tar.xz
-# Use these instead to build from the git source
-# source: https://anongit.kde.org/krita.git
-# source-type: git
-# source-branch: master
- override-stage: |
- # Stage the part
- snapcraftctl stage
- # Modify the .desktop file in the stage to point to where the icon will be in the installed snap
- sed -i 's|Icon=\(.*\)|Icon=${SNAP}/usr/share/icons/hicolor/1024x1024/apps/\1.png|' usr/share/applications/org.kde.krita.desktop
+ source: https://download.kde.org/stable/krita/4.2.8/krita-$SNAPCRAFT_PROJECT_VERSION.tar.xz
+ # Use these instead to build from the git source
+ # source: https://anongit.kde.org/krita.git
+ # source-type: git
+ # source-branch: master
+ parse-info: ["usr/share/metainfo/org.kde.krita.appdata.xml"]
build-packages:
- gettext
- build-essential
- cmake
- libboost-dev
- libboost-system-dev
- libeigen3-dev
- libexiv2-dev
- libfftw3-dev
- libfontconfig1-dev
- libfreetype6-dev
- libgl1-mesa-dev
- libglew-dev
- libglib2.0-dev
- libglu1-mesa-dev
- libgsf-1-dev
- libgsl-dev
- libjpeg-dev
- liblcms2-dev
- libopenexr-dev
- libpng-dev
- libpoppler-qt5-dev
- - libopenjpeg-dev
+ - libopenjp2-7-dev
- libtiff5-dev
- libvc-dev
- libopencolorio-dev
- libx11-dev
- libxml2-dev
- libxslt1-dev
- libxi-dev
- pkg-config
- vc-dev
- zlib1g-dev
- libkf5kdcraw-dev
- shared-mime-info
- libopenimageio-dev
- extra-cmake-modules
- libkf5archive-dev
- libkf5coreaddons-dev
- libkf5guiaddons-dev
- libkf5itemmodels-dev
- libkf5itemviews-dev
- libkf5widgetsaddons-dev
- libkf5i18n-dev
- libkf5windowsystem-dev
- libkf5completion-dev
- libkf5iconthemes-dev
- libkf5kiocore5
- libqt5svg5-dev
- libqt5x11extras5-dev
- libqt5opengl5-dev
- libquazip5-dev
- qtmultimedia5-dev
- qtdeclarative5-dev
- libkf5crash-dev
runtime:
plugin: nil
stage-packages:
- libexiv2-27
- libfftw3-double3
- libgomp1
- libgsl23
- libilmbase12
- libjpeg8
- liblcms2-2
- libopencolorio1v5
- libopenexr22
- libpng16-16
- libstdc++6
- libtiff5
- libx11-6
- libxcb1
- libxi6
- zlib1g
- libpoppler-qt5-1
- shared-mime-info
- libboost-system1.65.1
- librtmp1
- libqt5quickwidgets5
- libkf5archive5
- libkf5completion5
- libkf5configcore5
- libkf5configgui5
- libkf5coreaddons5
- libkf5guiaddons5
- libkf5i18n5
- libkf5itemviews5
- libkf5widgetsaddons5
- libkf5windowsystem5
- libkf5crash5
- libqt5concurrent5
- libqt5core5a
- libqt5dbus5
- libqt5gui5
- libqt5network5
- libqt5printsupport5
- libqt5svg5
- libqt5widgets5
- libqt5x11extras5
- libqt5xml5
- locales
- libc-bin
- libquazip5-1
- libqt5quick5
- libqt5quickparticles5
- libqt5quickshapes5
- qt5-qmltooling-plugins
- libqt5qml5
- libqt5multimedia5
- libqt5multimediagsttools5
- libqt5multimediaquick5
- libqt5multimediawidgets5
# Required for rendering animations
- ffmpeg
- libglu1-mesa
- libslang2
prime:
- "-usr/share/wallpapers/*"
- "-usr/share/fonts/*"
plasma-integration:
plugin: nil
stage-packages:
- plasma-integration
- kde-style-breeze
- breeze-icon-theme
- kio # runtime slaves for kio
prime:
- "-usr/share/wallpapers/*"
- "-usr/share/fonts/*"
launcher:
plugin: dump
source: scripts
organize:
qt5-launch: bin/qt5-launch
kf5-locale-gen: bin/kf5-locale-gen
diff --git a/packaging/macos/osxbuild.sh b/packaging/macos/osxbuild.sh
index ec39bee74e..5423510ac9 100755
--- a/packaging/macos/osxbuild.sh
+++ b/packaging/macos/osxbuild.sh
@@ -1,536 +1,536 @@
#!/usr/bin/env bash
# osxbuild.sh automates building and installing of krita and krita dependencies
# for OSX, the script only needs you to set BUILDROOT environment to work
# properly.
#
# Run with no args for a short help about each command.
# builddeps: Attempts to build krita dependencies in the necessary order,
# intermediate steps for creating symlinks and fixing rpath of some
# packages midway is also managed. Order goes from top to bottom, to add
# new steps just place them in the proper place.
# rebuilddeps: This re-runs all make and make install of dependencies 3rdparty
# this was needed as deleting the entire install directory an rerunning build
# step for dependencies does not install if they are already built. This step
# forces installation. Have not tested it lately so it might not be needed anymore
# build: Runs cmake build and make step for krita sources. It always run cmake step, so
# it might take a bit longer than a pure <make> on the source tree. The script tries
# to set the make flag -jN to a proper N.
# install: Runs install step for krita sources.
# fixboost: Search for all libraries using boost and sets a proper @rpath for boost as by
# default it fails to set a proper @rpath
# buildinstall: Runs build, install and fixboost steps.#
if test -z $BUILDROOT; then
echo "ERROR: BUILDROOT env not set, exiting!"
echo "\t Must point to the root of the buildfiles as stated in 3rdparty Readme"
exit
fi
echo "BUILDROOT set to ${BUILDROOT}"
export KIS_SRC_DIR=${BUILDROOT}/krita
export KIS_TBUILD_DIR=${BUILDROOT}/depbuild
export KIS_TDEPINSTALL_DIR=${BUILDROOT}/depinstall
export KIS_DOWN_DIR=${BUILDROOT}/down
export KIS_BUILD_DIR=${BUILDROOT}/kisbuild
export KIS_INSTALL_DIR=${BUILDROOT}/i
# flags for OSX environment
# Qt only supports from 10.12 up, and https://doc.qt.io/qt-5/macos.html#target-platforms warns against setting it lower
export MACOSX_DEPLOYMENT_TARGET=10.12
export QMAKE_MACOSX_DEPLOYMENT_TARGET=10.12
# Build time variables
if test -z $(which cmake); then
echo "ERROR: cmake not found, exiting!"
exit
fi
export PATH=${KIS_INSTALL_DIR}/bin:$PATH
export C_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${C_INCLUDE_PATH}
export CPLUS_INCLUDE_PATH=${KIS_INSTALL_DIR}/include:/usr/include:${CPLUS_INCLUDE_PATH}
export LIBRARY_PATH=${KIS_INSTALL_DIR}/lib:/usr/lib:${LIBRARY_PATH}
# export CPPFLAGS=-I${KIS_INSTALL_DIR}/include
# export LDFLAGS=-L${KIS_INSTALL_DIR}/lib
export FRAMEWORK_PATH=${KIS_INSTALL_DIR}/lib/
# export PYTHONHOME=${KIS_INSTALL_DIR}
# export PYTHONPATH=${KIS_INSTALL_DIR}/sip:${KIS_INSTALL_DIR}/lib/python3.5/site-packages:${KIS_INSTALL_DIR}/lib/python3.5
# This will make the debug output prettier
export KDE_COLOR_DEBUG=1
export QTEST_COLORED=1
export OUPUT_LOG="${BUILDROOT}/osxbuild.log"
export ERROR_LOG="${BUILDROOT}/osxbuild-error.log"
printf "" > "${OUPUT_LOG}"
printf "" > "${ERROR_LOG}"
# configure max core for make compile
((MAKE_THREADS=1))
if test ${OSTYPE} == "darwin*"; then
((MAKE_THREADS = $(sysctl -n hw.ncpu) - 1))
fi
# Prints stderr and stdout to log files
# >(tee) works but breaks sigint
log_cmd () {
if [[ "${VERBOSE}" ]]; then
"$@" 1>> ${OUPUT_LOG} 2>> ${ERROR_LOG}
else
"$@" 2>> ${ERROR_LOG} | tee -a ${OUPUT_LOG} > /dev/null
fi
}
# Log messages to logfile
log () {
if [[ "${VERBOSE}" ]]; then
printf "%s\n" "${@}" | tee -a ${OUPUT_LOG}
else
printf "%s\n" "${@}" | tee -a ${OUPUT_LOG} > /dev/null
fi
}
# if previous command gives error
# print msg
print_if_error() {
error_stat="${?}"
if [ ${error_stat} -ne 0 ]; then
printf "\e[31m%s %s\e[0m\n" "Error:" "${1}" 2>> ${ERROR_LOG}
printf "%s\r" "${error_stat}"
fi
}
# print status messages
print_msg() {
printf "\e[32m%s\e[0m\n" "${1}"
printf "%s\n" "${1}" >> ${OUPUT_LOG}
}
check_dir_path () {
printf "%s" "Checking if ${1} exists and is dir... "
if test -d ${1}; then
echo -e "OK"
elif test -e ${1}; then
echo -e "\n\tERROR: file ${1} exists but is not a directory!" >&2
return 1
else
echo -e "Creating ${1}"
mkdir ${1}
fi
return 0
}
# builds dependencies for the first time
cmake_3rdparty () {
cd ${KIS_TBUILD_DIR}
local build_pkgs=("${@}") # convert to array
if [[ ${2} = "1" ]]; then
local nofix="true"
local build_pkgs=(${build_pkgs[@]:0:1})
fi
for package in ${build_pkgs[@]} ; do
print_msg "Building ${package}"
log_cmd cmake --build . --config RelWithDebInfo --target ${package}
if [[ ! $(print_if_error "Failed build ${package}") ]]; then
print_msg "Build Success! ${package}"
fi
# fixes does not depend on failure
if [[ ! ${nofix} ]]; then
build_3rdparty_fixes ${package}
fi
done
}
build_3rdparty_fixes(){
pkg=${1}
if [[ "${pkg}" = "ext_qt" && -e "${KIS_INSTALL_DIR}/bin/qmake" ]]; then
ln -sf qmake "${KIS_INSTALL_DIR}/bin/qmake-qt5"
elif [[ "${pkg}" = "ext_openexr" ]]; then
# open exr will fail the first time is called
# rpath needs to be fixed an build rerun
log "Fixing rpath on openexr file: b44ExpLogTable"
log "Fixing rpath on openexr file: dwaLookups"
log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable
log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups
# we must rerun build!
cmake_3rdparty ext_openexr "1"
elif [[ "${pkg}" = "ext_fontconfig" ]]; then
log "fixing rpath on fc-cache"
log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_TBUILD_DIR}/ext_fontconfig/ext_fontconfig-prefix/src/ext_fontconfig-build/fc-cache/.libs/fc-cache
# rerun rebuild
cmake_3rdparty ext_fontconfig "1"
fi
}
build_3rdparty () {
print_msg "building in ${KIS_TBUILD_DIR}"
log "$(check_dir_path ${KIS_TBUILD_DIR})"
log "$(check_dir_path ${KIS_DOWN_DIR})"
log "$(check_dir_path ${KIS_INSTALL_DIR})"
cd ${KIS_TBUILD_DIR}
log_cmd cmake ${KIS_SRC_DIR}/3rdparty/ \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 \
-DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \
-DEXTERNALS_DOWNLOAD_DIR=${KIS_DOWN_DIR} \
-DINSTALL_ROOT=${KIS_INSTALL_DIR}
# -DCPPFLAGS=-I${KIS_INSTALL_DIR}/include \
# -DLDFLAGS=-L${KIS_INSTALL_DIR}/lib
print_msg "finished 3rdparty build setup"
if [[ -n ${1} ]]; then
cmake_3rdparty "${@}"
exit
fi
# build 3rdparty tools
# The order must not be changed!
cmake_3rdparty \
ext_pkgconfig \
ext_gettext \
ext_openssl \
ext_qt \
ext_zlib \
ext_boost \
ext_eigen3 \
ext_exiv2 \
ext_fftw3 \
ext_ilmbase \
ext_jpeg \
ext_lcms2 \
ext_ocio \
ext_openexr
cmake_3rdparty \
ext_png \
ext_tiff \
ext_gsl \
ext_vc \
ext_libraw \
ext_giflib \
ext_freetype \
ext_fontconfig \
ext_poppler
# Stop if qmake link was not created
# this meant qt build fail and further builds will
# also fail.
log_cmd test -L "${KIS_INSTALL_DIR}/bin/qmake-qt5"
if [[ $(print_if_error "qmake link missing!") ]]; then
printf "
link: ${KIS_INSTALL_DIR}/bin/qmake-qt5 missing!
It probably means ext_qt failed!!
check, fix and rerun!\n"
exit
fi
# for python
cmake_3rdparty \
ext_python \
ext_sip \
ext_pyqt
cmake_3rdparty ext_libheif
cmake_3rdparty \
ext_extra_cmake_modules \
ext_kconfig \
ext_kwidgetsaddons \
ext_kcompletion \
ext_kcoreaddons \
ext_kguiaddons \
ext_ki18n \
ext_kitemmodels \
ext_kitemviews \
ext_kimageformats \
ext_kwindowsystem \
ext_quazip
}
# Recall cmake for all 3rd party packages
# make is only on target first run
# subsequent runs only call make install
rebuild_3rdparty () {
print_msg "starting rebuild of 3rdparty packages"
build_install_ext() {
for pkg in ${@:1:${#@}}; do
{
cd ${KIS_TBUILD_DIR}/${pkg}/${pkg}-prefix/src/${pkg}-stamp
} || {
cd ${KIS_TBUILD_DIR}/ext_frameworks/${pkg}-prefix/src/${pkg}-stamp
} || {
cd ${KIS_TBUILD_DIR}/ext_heif/${pkg}-prefix/src/${pkg}-stamp
}
log "Installing ${pkg} files..."
rm ${pkg}-configure ${pkg}-build ${pkg}-install
cmake_3rdparty ${pkg}
cd ${KIS_TBUILD_DIR}
done
}
# Do not process complete list only pkgs given.
if ! test -z ${1}; then
build_install_ext ${@}
exit
fi
build_install_ext \
ext_pkgconfig \
ext_iconv \
ext_gettext \
ext_openssl \
ext_qt \
ext_zlib \
ext_boost \
ext_eigen3 \
ext_expat \
ext_exiv2 \
ext_fftw3 \
ext_ilmbase \
ext_jpeg \
ext_patch \
ext_lcms2 \
ext_ocio \
ext_ilmbase \
ext_openexr \
ext_png \
ext_tiff \
ext_gsl \
ext_vc \
ext_libraw \
ext_giflib \
ext_fontconfig \
ext_freetype \
ext_poppler \
ext_python \
ext_sip \
ext_pyqt \
build_install_ext \
ext_yasm \
ext_nasm \
ext_libx265 \
ext_libde265 \
ext_libheif \
# Build kde_frameworks
build_install_ext \
ext_extra_cmake_modules \
ext_kconfig \
ext_kwidgetsaddons \
ext_kcompletion \
ext_kcoreaddons \
ext_kguiaddons \
ext_ki18n \
ext_kitemmodels \
ext_kitemviews \
ext_kimageformats \
ext_kwindowsystem \
ext_quazip
}
#not tested
set_krita_dirs() {
if [[ -n ${1} ]]; then
KIS_BUILD_DIR=${BUILDROOT}/b_${1}
KIS_INSTALL_DIR=${BUILDROOT}/i_${1}
KIS_SRC_DIR=${BUILDROOT}/src_${1}
fi
}
# build_krita
# run cmake krita
build_krita () {
export DYLD_FRAMEWORK_PATH=${FRAMEWORK_PATH}
echo ${KIS_BUILD_DIR}
echo ${KIS_INSTALL_DIR}
log_cmd check_dir_path ${KIS_BUILD_DIR}
cd ${KIS_BUILD_DIR}
cmake ${KIS_SRC_DIR} \
-DFOUNDATION_BUILD=ON \
-DBoost_INCLUDE_DIR=${KIS_INSTALL_DIR}/include \
-DCMAKE_INSTALL_PREFIX=${KIS_INSTALL_DIR} \
-DDEFINE_NO_DEPRECATED=1 \
-DBUILD_TESTING=OFF \
- -DHIDE_SAFE_ASSERTS=OFF \
+ -DHIDE_SAFE_ASSERTS=ON \
-DKDE_INSTALL_BUNDLEDIR=${KIS_INSTALL_DIR}/bin \
-DPYQT_SIP_DIR_OVERRIDE=${KIS_INSTALL_DIR}/share/sip/ \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.12 \
-DPYTHON_INCLUDE_DIR=${KIS_INSTALL_DIR}/lib/Python.framework/Headers
# copiling phase
make -j${MAKE_THREADS}
# compile integrations
if test ${OSTYPE} == "darwin*"; then
cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook
make -j${MAKE_THREADS}
fi
}
build_krita_tarball () {
filename="$(basename ${1})"
KIS_CUSTOM_BUILD="${BUILDROOT}/releases/${filename%.tar.gz}"
print_msg "Tarball BUILDROOT is ${KIS_CUSTOM_BUILD}"
filename_dir=$(dirname "${1}")
cd "${filename_dir}"
file_abspath="$(pwd)/${1##*/}"
mkdir "${KIS_CUSTOM_BUILD}" 2> /dev/null
cd "${KIS_CUSTOM_BUILD}"
mkdir "src" "build" 2> /dev/null
tar -xzf "${file_abspath}" --strip-components=1 --directory "src"
if [[ $(print_if_error "Untar ${file_abspath} failed!") ]]; then
exit
fi
KIS_BUILD_DIR="${KIS_CUSTOM_BUILD}/build"
KIS_SRC_DIR="${KIS_CUSTOM_BUILD}/src"
build_krita
print_msg "Build done!"
print_msg "to install run
osxbuild.sh install ${KIS_BUILD_DIR}"
}
install_krita () {
# custom install provided
if [[ -n "${1}" ]]; then
KIS_BUILD_DIR="${1}"
fi
print_msg "Install krita from ${KIS_BUILD_DIR}"
log_cmd check_dir_path ${KIS_BUILD_DIR}
cd ${KIS_BUILD_DIR}
if [[ $(print_if_error "could not cd to ${KIS_BUILD_DIR}") ]]; then
exit
fi
make install
# compile integrations
if test ${OSTYPE} == "darwin*"; then
cd ${KIS_BUILD_DIR}/krita/integration/kritaquicklook
make install
fi
}
# Runs all fixes for path and packages.
# Historically only fixed boost @rpath
fix_boost_rpath () {
# helpers to define function only once
fixboost_find () {
for FILE in "${@}"; do
if [[ -n "$(otool -L $FILE | grep boost)" ]]; then
log "Fixing -- $FILE"
log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib $FILE
fi
done
}
batch_fixboost() {
xargs -P4 -I FILE bash -c 'fixboost_find "FILE"'
}
export -f fixboost_find
export -f log
export -f log_cmd
print_msg "Fixing boost in... ${KIS_INSTALL_DIR}"
# install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib $BUILDROOT/$KRITA_INSTALL/bin/krita.app/Contents/MacOS/gmic_krita_qt
log_cmd install_name_tool -add_rpath ${KIS_INSTALL_DIR}/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita
# echo "Added rpath ${KIS_INSTALL_DIR}/lib to krita bin"
# install_name_tool -add_rpath ${BUILDROOT}/deps/lib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita
log_cmd install_name_tool -change libboost_system.dylib @rpath/libboost_system.dylib ${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita
find -L "${KIS_INSTALL_DIR}" -name '*so' -o -name '*dylib' | batch_fixboost
log "Fixing boost done!"
}
print_usage () {
printf "USAGE: osxbuild.sh <buildstep> [pkg|file]\n"
printf "BUILDSTEPS:\t\t"
printf "\n builddeps \t\t Run cmake step for 3rd party dependencies, optionally takes a [pkg] arg"
printf "\n rebuilddeps \t\t Rerun make and make install step for 3rd party deps, optionally takes a [pkg] arg
\t\t\t useful for cleaning install directory and quickly reinstall all deps."
printf "\n fixboost \t\t Fixes broken boost \@rpath on OSX"
printf "\n build \t\t\t Builds krita"
printf "\n buildtarball \t\t\t Builds krita from provided [file] tarball"
printf "\n install \t\t Installs krita. Optionally accepts a [build dir] as argument
\t\t\t this will install krita from given directory"
printf "\n buildinstall \t\t Build and Installs krita, running fixboost after installing"
printf "\n"
}
if [[ ${#} -eq 0 ]]; then
echo "ERROR: No option given!"
print_usage
exit 1
fi
if [[ ${1} = "builddeps" ]]; then
build_3rdparty "${@:2}"
elif [[ ${1} = "rebuilddeps" ]]; then
rebuild_3rdparty "${@:2}"
elif [[ ${1} = "fixboost" ]]; then
if [[ -d ${1} ]]; then
KIS_BUILD_DIR="${1}"
fi
fix_boost_rpath
elif [[ ${1} = "build" ]]; then
build_krita ${2}
elif [[ ${1} = "buildtarball" ]]; then
# uncomment line to optionally change
# install directory providing a third argument
# This is not on by default as build success requires all
# deps installed in the given dir beforehand.
# KIS_INSTALL_DIR=${3}
build_krita_tarball ${2}
elif [[ ${1} = "install" ]]; then
install_krita ${2}
fix_boost_rpath
elif [[ ${1} = "buildinstall" ]]; then
build_krita ${2}
install_krita ${2}
fix_boost_rpath ${2}
elif [[ ${1} = "test" ]]; then
${KIS_INSTALL_DIR}/bin/krita.app/Contents/MacOS/krita
else
echo "Option ${1} not supported"
print_usage
exit 1
fi
# after finishig sometimes it complains about missing matching quotes.
diff --git a/plugins/color/colorspaceextensions/kis_desaturate_adjustment.cpp b/plugins/color/colorspaceextensions/kis_desaturate_adjustment.cpp
index 7091e446c5..d870724128 100644
--- a/plugins/color/colorspaceextensions/kis_desaturate_adjustment.cpp
+++ b/plugins/color/colorspaceextensions/kis_desaturate_adjustment.cpp
@@ -1,193 +1,193 @@
/*
* Copyright (c) 2013 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
* 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_desaturate_adjustment.h"
#include <KoConfig.h>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoColorConversions.h>
#include <KoColorModelStandardIds.h>
#include <KoColorSpace.h>
#include <KoColorSpaceTraits.h>
#include <KoColorTransformation.h>
#include <KoID.h>
#define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< _channel_type_, float>::scaleToA( v )
#define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, _channel_type_>::scaleToA( v )
template<typename _channel_type_,typename traits>
class KisDesaturateAdjustment : public KoColorTransformation
{
typedef traits RGBTrait;
typedef typename RGBTrait::Pixel RGBPixel;
public:
KisDesaturateAdjustment()
{
}
public:
void transform(const quint8 *srcU8, quint8 *dstU8, qint32 nPixels) const override
{
const RGBPixel* src = reinterpret_cast<const RGBPixel*>(srcU8);
RGBPixel* dst = reinterpret_cast<RGBPixel*>(dstU8);
float r, g, b, gray;
while (nPixels > 0) {
r = SCALE_TO_FLOAT(src->red);
g = SCALE_TO_FLOAT(src->green);
b = SCALE_TO_FLOAT(src->blue);
- // http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/
+ // https://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/
switch(m_type) {
case 0: // lightness
{
gray = (qMax(qMax(r, g), b) + qMin(qMin(r, g), b)) / 2;
break;
}
case 1: // luminosity BT 709
{
gray = r * 0.2126 + g * 0.7152 + b * 0.0722;
break;
}
case 2: // luminosity BT 601
{
gray = r * 0.299 + g * 0.587 + b * 0.114;
break;
}
case 3: // average
{
gray = (r + g + b) / 3;
break;
}
case 4: // min
{
gray = qMin(qMin(r, g), b);
break;
}
case 5: // min
{
gray = qMax(qMax(r, g), b);
break;
}
default:
gray = 0;
}
dst->red = SCALE_FROM_FLOAT(gray);
dst->green = SCALE_FROM_FLOAT(gray);
dst->blue = SCALE_FROM_FLOAT(gray);
dst->alpha = src->alpha;
--nPixels;
++src;
++dst;
}
}
QList<QString> parameters() const override
{
QList<QString> list;
list << "type";
return list;
}
int parameterId(const QString& name) const override
{
if (name == "type") {
return 0;
}
return -1;
}
/**
* name - "type":
* 0: lightness
* 1: luminosity
* 2: average
*/
void setParameter(int id, const QVariant& parameter) override
{
switch(id)
{
case 0:
m_type = parameter.toDouble();
break;
default:
;
}
}
private:
int m_type;
};
KisDesaturateAdjustmentFactory::KisDesaturateAdjustmentFactory()
: KoColorTransformationFactory("desaturate_adjustment")
{
}
QList< QPair< KoID, KoID > > KisDesaturateAdjustmentFactory::supportedModels() const
{
QList< QPair< KoID, KoID > > l;
l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer8BitsColorDepthID));
l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer16BitsColorDepthID));
l.append(QPair< KoID, KoID >(RGBAColorModelID , Float16BitsColorDepthID));
l.append(QPair< KoID, KoID >(RGBAColorModelID , Float32BitsColorDepthID));
return l;
}
KoColorTransformation* KisDesaturateAdjustmentFactory::createTransformation(const KoColorSpace* colorSpace, QHash<QString, QVariant> parameters) const
{
KoColorTransformation * adj;
if (colorSpace->colorModelId() != RGBAColorModelID) {
dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisDesaturateAdjustmentFactory::createTransformation";
return 0;
}
if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) {
adj = new KisDesaturateAdjustment< quint8, KoBgrTraits < quint8 > >();
} else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) {
adj = new KisDesaturateAdjustment< quint16, KoBgrTraits < quint16 > >();
}
#ifdef HAVE_OPENEXR
else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) {
adj = new KisDesaturateAdjustment< half, KoRgbTraits < half > >();
}
#endif
else if (colorSpace->colorDepthId() == Float32BitsColorDepthID) {
adj = new KisDesaturateAdjustment< float, KoRgbTraits < float > >();
}
else {
dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisDesaturateAdjustmentFactory::createTransformation";
return 0;
}
adj->setParameters(parameters);
return adj;
}
diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp
index 8c2deba887..0394623b61 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_color_selector_base.cpp
@@ -1,573 +1,580 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
*/
#include "kis_color_selector_base.h"
#include <QMouseEvent>
#include <QApplication>
#include <QDesktopWidget>
+#include <QScreen>
#include <QTimer>
#include <QCursor>
#include <QPainter>
#include <QMimeData>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include "KoColorSpace.h"
#include "KoColorSpaceRegistry.h"
#include "kis_canvas2.h"
#include "kis_canvas_resource_provider.h"
#include "kis_node.h"
#include "KisViewManager.h"
#include <KisView.h>
#include "kis_image.h"
#include "kis_global.h"
#include "kis_display_color_converter.h"
#include <resources/KoGamutMask.h>
class KisColorPreviewPopup : public QWidget {
public:
KisColorPreviewPopup(KisColorSelectorBase* parent)
: QWidget(parent), m_parent(parent)
{
setWindowFlags(Qt::ToolTip | Qt::NoDropShadowWindowHint);
setQColor(QColor(0,0,0));
m_baseColor = QColor(0,0,0,0);
m_previousColor = QColor(0,0,0,0);
m_lastUsedColor = QColor(0,0,0,0);
}
void show()
{
updatePosition();
QWidget::show();
}
void updatePosition()
{
QPoint parentPos = m_parent->mapToGlobal(QPoint(0,0));
- QRect availRect = QApplication::desktop()->availableGeometry(this);
+ const QRect availRect = QApplication::desktop()->availableGeometry(this);
QPoint targetPos;
if ( parentPos.x() - 100 > availRect.x() ) {
targetPos = QPoint(parentPos.x() - 100, parentPos.y());
} else if ( parentPos.x() + m_parent->width() + 100 < availRect.right()) {
targetPos = m_parent->mapToGlobal(QPoint(m_parent->width(), 0));
} else if ( parentPos.y() - 100 > availRect.y() ) {
targetPos = QPoint(parentPos.x(), parentPos.y() - 100);
} else {
targetPos = QPoint(parentPos.x(), parentPos.y() + m_parent->height());
}
setGeometry(targetPos.x(), targetPos.y(), 100, 150);
setAttribute(Qt::WA_TranslucentBackground);
}
void setQColor(const QColor& color)
{
m_color = color;
update();
}
void setPreviousColor()
{
m_previousColor = m_baseColor;
}
void setBaseColor(const QColor& color)
{
m_baseColor = color;
update();
}
void setLastUsedColor(const QColor& color)
{
m_lastUsedColor = color;
update();
}
protected:
void paintEvent(QPaintEvent *e) override {
Q_UNUSED(e);
QPainter p(this);
p.fillRect(0, 0, width(), width(), m_color);
p.fillRect(50, width(), width(), height(), m_previousColor);
p.fillRect(0, width(), 50, height(), m_lastUsedColor);
}
void enterEvent(QEvent *e) override {
QWidget::enterEvent(e);
m_parent->tryHideAllPopups();
}
void leaveEvent(QEvent *e) override {
QWidget::leaveEvent(e);
m_parent->tryHideAllPopups();
}
private:
KisColorSelectorBase* m_parent;
QColor m_color;
QColor m_baseColor;
QColor m_previousColor;
QColor m_lastUsedColor;
};
KisColorSelectorBase::KisColorSelectorBase(QWidget *parent) :
QWidget(parent),
m_canvas(0),
m_popup(0),
m_parent(0),
m_colorUpdateAllowed(true),
m_colorUpdateSelf(false),
m_hideTimer(new QTimer(this)),
m_popupOnMouseOver(false),
m_popupOnMouseClick(true),
m_colorSpace(0),
m_isPopup(false),
m_hideOnMouseClick(false),
m_colorPreviewPopup(new KisColorPreviewPopup(this))
{
m_hideTimer->setInterval(0);
m_hideTimer->setSingleShot(true);
connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hidePopup()));
using namespace std::placeholders; // For _1 placeholder
auto function = std::bind(&KisColorSelectorBase::slotUpdateColorAndPreview, this, _1);
m_updateColorCompressor.reset(new ColorCompressorType(20 /* ms */, function));
}
KisColorSelectorBase::~KisColorSelectorBase()
{
delete m_popup;
delete m_colorPreviewPopup;
}
void KisColorSelectorBase::setPopupBehaviour(bool onMouseOver, bool onMouseClick)
{
m_popupOnMouseClick = onMouseClick;
m_popupOnMouseOver = onMouseOver;
if(onMouseClick) {
m_popupOnMouseOver = false;
}
if(m_popupOnMouseOver) {
setMouseTracking(true);
}
}
void KisColorSelectorBase::setColorSpace(const KoColorSpace *colorSpace)
{
m_colorSpace = colorSpace;
}
void KisColorSelectorBase::setCanvas(KisCanvas2 *canvas)
{
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
}
m_canvas = canvas;
if (m_canvas) {
connect(m_canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
SLOT(canvasResourceChanged(int,QVariant)), Qt::UniqueConnection);
connect(m_canvas->displayColorConverter(), SIGNAL(displayConfigurationChanged()),
SLOT(reset()), Qt::UniqueConnection);
connect(canvas->imageView()->resourceProvider(), SIGNAL(sigFGColorUsed(KoColor)),
this, SLOT(updateLastUsedColorPreview(KoColor)), Qt::UniqueConnection);
if (m_canvas->viewManager() && m_canvas->viewManager()->canvasResourceProvider()) {
setColor(Acs::currentColor(m_canvas->viewManager()->canvasResourceProvider(), Acs::Foreground));
}
}
if (m_popup) {
m_popup->setCanvas(canvas);
}
reset();
}
void KisColorSelectorBase::unsetCanvas()
{
if (m_popup) {
m_popup->unsetCanvas();
}
m_canvas = 0;
}
void KisColorSelectorBase::mousePressEvent(QMouseEvent* event)
{
event->accept();
if(!m_isPopup && m_popupOnMouseClick &&
event->button() == Qt::MidButton) {
lazyCreatePopup();
int x = event->globalX();
int y = event->globalY();
int popupsize = m_popup->width();
x-=popupsize/2;
y-=popupsize/2;
- QRect availRect = QApplication::desktop()->availableGeometry(this);
+ const QRect availRect = QApplication::desktop()->availableGeometry(this);
+
if(x<availRect.x())
x = availRect.x();
if(y<availRect.y())
y = availRect.y();
if(x+m_popup->width()>availRect.x()+availRect.width())
x = availRect.x()+availRect.width()-m_popup->width();
if(y+m_popup->height()>availRect.y()+availRect.height())
y = availRect.y()+availRect.height()-m_popup->height();
m_colorUpdateSelf=false;
m_popup->move(x, y);
m_popup->setHidingTime(200);
showPopup(DontMove);
} else if (m_isPopup && event->button() == Qt::MidButton) {
if (m_colorPreviewPopup) {
m_colorPreviewPopup->hide();
}
hide();
} else {
m_colorUpdateSelf=true;
showColorPreview();
event->ignore();
}
}
void KisColorSelectorBase::mouseReleaseEvent(QMouseEvent *e) {
Q_UNUSED(e);
if (e->button() == Qt::MidButton) {
e->accept();
} else if (m_isPopup &&
(m_hideOnMouseClick && !m_popupOnMouseOver) &&
!m_hideTimer->isActive()) {
if (m_colorPreviewPopup) {
m_colorPreviewPopup->hide();
}
hide();
}
}
void KisColorSelectorBase::enterEvent(QEvent *e)
{
if (m_popup && m_popup->isVisible()) {
m_popup->m_hideTimer->stop();
}
if (m_isPopup && m_hideTimer->isActive()) {
m_hideTimer->stop();
}
// do not show the popup when boxed in
// the configuration dialog (m_canvas == 0)
if (m_canvas &&
!m_isPopup && m_popupOnMouseOver &&
(!m_popup || m_popup->isHidden())) {
lazyCreatePopup();
const QRect availRect = QApplication::desktop()->availableGeometry(this);
QPoint proposedTopLeft = rect().center() - m_popup->rect().center();
proposedTopLeft = mapToGlobal(proposedTopLeft);
QRect popupRect = QRect(proposedTopLeft, m_popup->size());
popupRect = kisEnsureInRect(popupRect, availRect);
m_popup->setGeometry(popupRect);
m_popup->setHidingTime(200);
showPopup(DontMove);
}
QWidget::enterEvent(e);
}
void KisColorSelectorBase::leaveEvent(QEvent *e)
{
tryHideAllPopups();
QWidget::leaveEvent(e);
}
void KisColorSelectorBase::keyPressEvent(QKeyEvent *)
{
if (m_isPopup) {
hidePopup();
}
}
void KisColorSelectorBase::dragEnterEvent(QDragEnterEvent *e)
{
if(e->mimeData()->hasColor())
e->acceptProposedAction();
if(e->mimeData()->hasText() && QColor(e->mimeData()->text()).isValid())
e->acceptProposedAction();
}
void KisColorSelectorBase::dropEvent(QDropEvent *e)
{
QColor color;
if(e->mimeData()->hasColor()) {
color = qvariant_cast<QColor>(e->mimeData()->colorData());
}
else if(e->mimeData()->hasText()) {
color.setNamedColor(e->mimeData()->text());
if(!color.isValid())
return;
}
KoColor kocolor(color , KoColorSpaceRegistry::instance()->rgb8());
updateColor(kocolor, Acs::Foreground, true);
}
void KisColorSelectorBase::updateColor(const KoColor &color, Acs::ColorRole role, bool needsExplicitColorReset)
{
commitColor(color, role);
if (needsExplicitColorReset) {
setColor(color);
}
}
void KisColorSelectorBase::requestUpdateColorAndPreview(const KoColor &color, Acs::ColorRole role)
{
m_updateColorCompressor->start(qMakePair(color, role));
}
void KisColorSelectorBase::slotUpdateColorAndPreview(QPair<KoColor, Acs::ColorRole> color)
{
updateColorPreview(color.first);
updateColor(color.first, color.second, false);
}
void KisColorSelectorBase::setColor(const KoColor& color)
{
Q_UNUSED(color);
}
void KisColorSelectorBase::setHidingTime(int time)
{
KIS_ASSERT_RECOVER_NOOP(m_isPopup);
m_hideTimer->setInterval(time);
}
void KisColorSelectorBase::lazyCreatePopup()
{
if (!m_popup) {
m_popup = createPopup();
Q_ASSERT(m_popup);
m_popup->setParent(this);
// Setting Qt::BypassWindowManagerHint will prevent
// the WM from showing another taskbar entry,
// but will require that we handle window activation manually
m_popup->setWindowFlags(Qt::FramelessWindowHint |
- Qt::Window |
+ Qt::Popup |
Qt::NoDropShadowWindowHint |
Qt::BypassWindowManagerHint);
m_popup->m_parent = this;
m_popup->m_isPopup = true;
}
m_popup->setCanvas(m_canvas);
m_popup->updateSettings();
}
void KisColorSelectorBase::showPopup(Move move)
{
// This slot may be called by some action,
// so we need to be able to handle it
lazyCreatePopup();
QPoint cursorPos = QCursor::pos();
+ QScreen *activeScreen = 0;
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
+ activeScreen = QGuiApplication::screenAt(cursorPos);
+#endif
+ const QRect availRect = (activeScreen)? activeScreen->availableGeometry() : QApplication::desktop()->availableGeometry(this);
if (move == MoveToMousePosition) {
m_popup->move(QPoint(cursorPos.x()-m_popup->width()/2, cursorPos.y()-m_popup->height()/2));
QRect rc = m_popup->geometry();
- if (rc.x() < 0) rc.setX(0);
- if (rc.y() < 0) rc.setY(0);
+ if (rc.x() < availRect.x()) rc.setX(availRect.x());
+ if (rc.y() < availRect.y()) rc.setY(availRect.y());
m_popup->setGeometry(rc);
}
if (m_colorPreviewPopup) {
m_colorPreviewPopup->hide();
}
m_popup->show();
m_popup->m_colorPreviewPopup->show();
}
void KisColorSelectorBase::hidePopup()
{
KIS_ASSERT_RECOVER_RETURN(m_isPopup);
m_colorPreviewPopup->hide();
hide();
}
void KisColorSelectorBase::commitColor(const KoColor& color, Acs::ColorRole role)
{
if (!m_canvas)
return;
m_colorUpdateAllowed=false;
if (role == Acs::Foreground)
m_canvas->resourceManager()->setForegroundColor(color);
else
m_canvas->resourceManager()->setBackgroundColor(color);
m_colorUpdateAllowed=true;
}
void KisColorSelectorBase::showColorPreview()
{
if(m_colorPreviewPopup->isHidden()) {
m_colorPreviewPopup->show();
}
}
void KisColorSelectorBase::updateColorPreview(const KoColor &color)
{
m_colorPreviewPopup->setQColor(converter()->toQColor(color));
}
void KisColorSelectorBase::canvasResourceChanged(int key, const QVariant &v)
{
if (key == KoCanvasResourceProvider::ForegroundColor || key == KoCanvasResourceProvider::BackgroundColor) {
KoColor realColor(v.value<KoColor>());
updateColorPreview(realColor);
if (m_colorUpdateAllowed && !m_colorUpdateSelf) {
setColor(realColor);
}
}
}
const KoColorSpace* KisColorSelectorBase::colorSpace() const
{
return converter()->paintingColorSpace();
}
void KisColorSelectorBase::updateSettings()
{
if(m_popup) {
m_popup->updateSettings();
}
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
int zoomSelectorOptions = (int) cfg.readEntry("zoomSelectorOptions", 0) ;
if (zoomSelectorOptions == 0) {
setPopupBehaviour(false, true); // middle mouse button click will open zoom selector
} else if (zoomSelectorOptions == 1) {
setPopupBehaviour(true, false); // move over will open the zoom selector
}
else
{
setPopupBehaviour(false, false); // do not show zoom selector
}
if(m_isPopup) {
m_hideOnMouseClick = cfg.readEntry("hidePopupOnClickCheck", false);
const int zoomSize = cfg.readEntry("zoomSize", 280);
resize(zoomSize, zoomSize);
}
reset();
}
void KisColorSelectorBase::reset()
{
update();
}
void KisColorSelectorBase::updateBaseColorPreview(const KoColor &color)
{
m_colorPreviewPopup->setBaseColor(converter()->toQColor(color));
}
void KisColorSelectorBase::updatePreviousColorPreview()
{
m_colorPreviewPopup->setPreviousColor();
}
void KisColorSelectorBase::updateLastUsedColorPreview(const KoColor &color)
{
m_colorPreviewPopup->setLastUsedColor(converter()->toQColor(color));
}
KisDisplayColorConverter* KisColorSelectorBase::converter() const
{
return m_canvas ?
m_canvas->displayColorConverter() :
KisDisplayColorConverter::dumbConverterInstance();
}
void KisColorSelectorBase::tryHideAllPopups()
{
if (m_colorPreviewPopup->isVisible()) {
m_colorUpdateSelf=false; //this is for allowing advanced selector to listen to outside color-change events.
m_colorPreviewPopup->hide();
}
if (m_popup && m_popup->isVisible()) {
m_popup->m_hideTimer->start();
}
if (m_isPopup && !m_hideTimer->isActive()) {
m_hideTimer->start();
}
}
void KisColorSelectorBase::mouseMoveEvent(QMouseEvent *event)
{
event->accept();
}
void KisColorSelectorBase::changeEvent(QEvent *event)
{
// hide the popup when another window becomes active, e.g. due to alt+tab
if(m_isPopup && event->type() == QEvent::ActivationChange && !isActiveWindow()) {
hidePopup();
}
QWidget::changeEvent(event);
}
void KisColorSelectorBase::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
// manual activation required due to Qt::BypassWindowManagerHint
if(m_isPopup) {
activateWindow();
}
}
diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_wheel.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_wheel.cpp
index 3e489df45b..5beb47e373 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_selector_wheel.cpp
+++ b/plugins/dockers/advancedcolorselector/kis_color_selector_wheel.cpp
@@ -1,325 +1,324 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
*/
#include "kis_color_selector_wheel.h"
#include <QImage>
#include <QPainter>
#include <QColor>
#include <cmath>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
-#include <KisGamutMaskViewConverter.h>
-
#include "kis_display_color_converter.h"
#include "kis_acs_pixel_cache_renderer.h"
KisColorSelectorWheel::KisColorSelectorWheel(KisColorSelector *parent) :
KisColorSelectorComponent(parent),
m_lastClickPos(-1,-1),
m_renderAreaSize(1,1),
m_renderAreaOffsetX(0.0),
m_renderAreaOffsetY(0.0),
m_toRenderArea(QTransform())
{
- m_viewConverter = new KisGamutMaskViewConverter();
}
KoColor KisColorSelectorWheel::selectColor(int x, int y)
{
int xWheel = x-width()/2;
int yWheel = y-height()/2;
qreal radius = sqrt((double)xWheel*xWheel+yWheel*yWheel);
radius/=qMin(width(), height());
if(radius>0.5)
radius=0.5;
radius*=2.;
qreal angle = std::atan2((qreal)yWheel, (qreal)xWheel);
angle+=M_PI;
angle/=2*M_PI;
switch (m_parameter) {
case KisColorSelectorConfiguration::hsvSH:
emit paramChanged(angle, radius, -1, -1, -1, -1, -1, -1, -1);
break;
case KisColorSelectorConfiguration::hslSH:
emit paramChanged(angle, -1, -1, radius, -1, -1, -1, -1, -1);
break;
case KisColorSelectorConfiguration::hsiSH:
emit paramChanged(angle, -1, -1, -1, -1, radius, -1, -1, -1);
break;
case KisColorSelectorConfiguration::hsySH:
emit paramChanged(angle, -1, -1, -1, -1, -1, -1, radius, -1);
break;
case KisColorSelectorConfiguration::VH:
emit paramChanged(angle, -1, radius, -1, -1, -1, -1, -1, -1);
break;
case KisColorSelectorConfiguration::LH:
emit paramChanged(angle, -1, -1, -1, radius, -1, -1, -1, -1);
break;
case KisColorSelectorConfiguration::IH:
emit paramChanged(angle, -1, -1, -1, -1, -1, radius, -1, -1);
break;
case KisColorSelectorConfiguration::YH:
emit paramChanged(angle, -1, -1, -1, -1, -1, -1, -1, radius);
break;
default:
Q_ASSERT(false);
break;
}
emit update();
angle *= 2. * M_PI;
angle -= M_PI;
radius*=0.5;
m_lastClickPos.setX(cos(angle)*radius+0.5);
m_lastClickPos.setY(sin(angle)*radius+0.5);
return colorAt(x, y, true);
}
void KisColorSelectorWheel::setColor(const KoColor &color)
{
qreal hsvH, hsvS, hsvV;
qreal hslH, hslS, hslL;
qreal hsiH, hsiS, hsiI;
qreal hsyH, hsyS, hsyY;
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
R = cfg.readEntry("lumaR", 0.2126);
G = cfg.readEntry("lumaG", 0.7152);
B = cfg.readEntry("lumaB", 0.0722);
Gamma = cfg.readEntry("gamma", 2.2);
m_parent->converter()->getHsvF(color, &hsvH, &hsvS, &hsvV);
m_parent->converter()->getHslF(color, &hslH, &hslS, &hslL);
m_parent->converter()->getHsiF(color, &hsiH, &hsiS, &hsiI);
m_parent->converter()->getHsyF(color, &hsyH, &hsyS, &hsyY, R, G, B, Gamma);
//workaround, for some reason the HSI and HSY algorithms are fine, but they don't seem to update the selectors properly.
hsiH=hslH;
hsyH=hslH;
qreal angle = 0.0, radius = 0.0;
angle = hsvH;
angle *= 2. * M_PI;
angle -= M_PI;
switch (m_parameter) {
case KisColorSelectorConfiguration::LH:
emit paramChanged(hslH, -1, -1, -1, hslL, -1, -1, -1, -1);
radius = hslL;
break;
case KisColorSelectorConfiguration::VH:
emit paramChanged(hsvH, -1, hsvV, -1, -1, -1, -1, -1, -1);
radius = hsvV;
break;
case KisColorSelectorConfiguration::IH:
emit paramChanged(hslH, -1, -1, -1, -1, -1, hsiI, -1, -1);
radius = hsiI;
break;
case KisColorSelectorConfiguration::YH:
emit paramChanged(hsvH, -1, -1, -1, -1, -1, -1, -1, hsyY);
radius = hsyY;
break;
case KisColorSelectorConfiguration::hsvSH:
emit paramChanged(hsvH, hsvS, -1, -1, -1, -1, -1, -1, -1);
radius = hsvS;
break;
case KisColorSelectorConfiguration::hslSH:
emit paramChanged(hslH, -1, -1, hslS, -1, -1, -1, -1, -1);
radius = hslS;
break;
case KisColorSelectorConfiguration::hsiSH:
emit paramChanged(hsiH, -1, -1, -1, -1, hsiS, -1, -1, -1);
radius = hsiS;
break;
case KisColorSelectorConfiguration::hsySH:
emit paramChanged(hsyH, -1, -1, -1, -1, -1, -1, hsyS, -1);
radius = hsyS;
break;
default:
Q_ASSERT(false);
break;
}
radius *= 0.5;
m_lastClickPos.setX(cos(angle)*radius+0.5);
m_lastClickPos.setY(sin(angle)*radius+0.5);
//workaround for bug 279500
if(m_lastClickPos!=QPoint(-1,-1) && m_parent->displayBlip()) {
QPoint pos = (m_lastClickPos*qMin(width(), height())).toPoint();
if(width() < height()) {
pos.setY(pos.y()+height()/2-width()/2);
} else {
pos.setX(pos.x()+width()/2-height()/2);
}
setLastMousePosition(pos.x(), pos.y());
}
KisColorSelectorComponent::setColor(color);
}
void KisColorSelectorWheel::paint(QPainter* painter)
{
if(isDirty()) {
KisPaintDeviceSP realPixelCache;
Acs::PixelCacheRenderer::render(this, m_parent->converter(), QRect(0, 0, width(), height()), realPixelCache, m_pixelCache, m_pixelCacheOffset);
//antialiasing for wheel
QPainter tmpPainter(&m_pixelCache);
tmpPainter.setRenderHint(QPainter::Antialiasing);
tmpPainter.setPen(QPen(QColor(0,0,0,0), 2.5));
tmpPainter.setCompositionMode(QPainter::CompositionMode_Clear);
int size=qMin(width(), height());
m_renderAreaSize = QSize(size,size);
m_renderAreaOffsetX = ((qreal)width()-(qreal)m_renderAreaSize.width())*0.5;
m_renderAreaOffsetY = ((qreal)height()-(qreal)m_renderAreaSize.height())*0.5;
m_toRenderArea.reset();
m_toRenderArea.translate(-m_renderAreaOffsetX,-m_renderAreaOffsetY);
- m_viewConverter->setViewSize(m_renderAreaSize);
- if (m_currentGamutMask) {
- m_viewConverter->setMaskSize(m_currentGamutMask->maskSize());
- }
-
QPoint ellipseCenter(width() / 2 - size / 2, height() / 2 - size / 2);
ellipseCenter -= m_pixelCacheOffset;
tmpPainter.drawEllipse(ellipseCenter.x(), ellipseCenter.y(), size, size);
}
painter->drawImage(m_pixelCacheOffset.x(),m_pixelCacheOffset.y(), m_pixelCache);
// draw gamut mask
if (m_gamutMaskOn && m_currentGamutMask) {
QImage maskBuffer = QImage(m_renderAreaSize.width(), m_renderAreaSize.height(), QImage::Format_ARGB32_Premultiplied);
maskBuffer.fill(0);
QPainter maskPainter(&maskBuffer);
QRect rect = QRect(0, 0, m_renderAreaSize.width(), m_renderAreaSize.height());
maskPainter.setRenderHint(QPainter::Antialiasing, true);
maskPainter.resetTransform();
maskPainter.translate(rect.width()/2, rect.height()/2);
maskPainter.scale(rect.width()/2, rect.height()/2);
maskPainter.setPen(QPen(QBrush(Qt::white), 0.002));
maskPainter.setBrush(QColor(128,128,128,255)); // middle gray
maskPainter.drawEllipse(QPointF(0,0), 1.0, 1.0);
maskPainter.resetTransform();
+ maskPainter.setTransform(m_currentGamutMask->maskToViewTransform(m_renderAreaSize.width()));
maskPainter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
- m_currentGamutMask->paint(maskPainter, *m_viewConverter, m_maskPreviewActive);
+ m_currentGamutMask->paint(maskPainter, m_maskPreviewActive);
maskPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
- m_currentGamutMask->paintStroke(maskPainter, *m_viewConverter, m_maskPreviewActive);
+ m_currentGamutMask->paintStroke(maskPainter, m_maskPreviewActive);
painter->drawImage(m_renderAreaOffsetX, m_renderAreaOffsetY, maskBuffer);
}
// draw blips
if(m_lastClickPos!=QPoint(-1,-1) && m_parent->displayBlip()) {
QPoint pos = (m_lastClickPos*qMin(width(), height())).toPoint();
if(width()<height())
pos.setY(pos.y()+height()/2-width()/2);
else
pos.setX(pos.x()+width()/2-height()/2);
painter->setPen(QColor(0,0,0));
painter->drawEllipse(pos, 5, 5);
painter->setPen(QColor(255,255,255));
painter->drawEllipse(pos, 4, 4);
}
}
KoColor KisColorSelectorWheel::colorAt(int x, int y, bool forceValid)
{
KoColor color(Qt::transparent, m_parent->colorSpace());
Q_ASSERT(x>=0 && x<=width());
Q_ASSERT(y>=0 && y<=height());
const qreal xRel = x - 0.5 * width();
const qreal yRel = y - 0.5 * height();
const qreal minDimension = qMin(width(), height());
qreal radius = sqrt(xRel*xRel+yRel*yRel);
if (radius > 0.5 * minDimension) {
if (!forceValid) {
return color;
} else {
radius = 0.5 * minDimension;
}
}
radius /= 0.5 * minDimension;
qreal angle = std::atan2(yRel, xRel);
angle += M_PI;
angle /= 2 * M_PI;
switch(m_parameter) {
case KisColorSelectorConfiguration::hsvSH:
color = m_parent->converter()->fromHsvF(angle, radius, m_value);
break;
case KisColorSelectorConfiguration::hslSH:
color = m_parent->converter()->fromHslF(angle, radius, m_lightness);
break;
case KisColorSelectorConfiguration::hsiSH:
color = m_parent->converter()->fromHsiF(angle, radius, m_intensity);
break;
case KisColorSelectorConfiguration::hsySH:
color = m_parent->converter()->fromHsyF(angle, radius, m_luma, R, G, B, Gamma);
break;
case KisColorSelectorConfiguration::VH:
color = m_parent->converter()->fromHsvF(angle, m_hsvSaturation, radius);
break;
case KisColorSelectorConfiguration::LH:
color = m_parent->converter()->fromHslF(angle, m_hslSaturation, radius);
break;
case KisColorSelectorConfiguration::IH:
color = m_parent->converter()->fromHsiF(angle, m_hsiSaturation, radius);
break;
case KisColorSelectorConfiguration::YH:
color = m_parent->converter()->fromHsyF(angle, m_hsySaturation, radius, R, G, B, Gamma);
break;
default:
Q_ASSERT(false);
return color;
}
return color;
}
bool KisColorSelectorWheel::allowsColorSelectionAtPoint(const QPoint &pt) const
{
- return !m_gamutMaskOn || !m_currentGamutMask ||
- m_currentGamutMask->coordIsClear(m_toRenderArea.map(QPointF(pt)),
- *m_viewConverter, m_maskPreviewActive);
+ if (!m_gamutMaskOn || !m_currentGamutMask) {
+ return true;
+ }
+
+ QPointF colorCoord = m_toRenderArea.map(QPointF(pt));
+ QPointF translatedPoint = m_currentGamutMask->viewToMaskTransform(m_renderAreaSize.width()).map(colorCoord);
+ bool isClear = m_currentGamutMask->coordIsClear(translatedPoint, m_maskPreviewActive);
+
+ return isClear;
}
diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_wheel.h b/plugins/dockers/advancedcolorselector/kis_color_selector_wheel.h
index 9bbda767c7..6900f9d950 100644
--- a/plugins/dockers/advancedcolorselector/kis_color_selector_wheel.h
+++ b/plugins/dockers/advancedcolorselector/kis_color_selector_wheel.h
@@ -1,68 +1,65 @@
/*
* Copyright (c) 2010 Adam Celarek <kdedev at xibo dot at>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
*/
#ifndef KIS_COLOR_SELECTOR_WHEEL_H
#define KIS_COLOR_SELECTOR_WHEEL_H
typedef unsigned int QRgb;
#include <QColor>
#include <QImage>
#include <QSize>
-#include <KisGamutMaskViewConverter.h>
-
#include "KoColor.h"
#include "kis_color_selector_component.h"
namespace Acs {
class PixelCacheRenderer;
}
class KisColorSelectorWheel : public KisColorSelectorComponent
{
Q_OBJECT
public:
explicit KisColorSelectorWheel(KisColorSelector *parent);
void setColor(const KoColor &color) override;
protected:
KoColor selectColor(int x, int y) override;
void paint(QPainter*) override;
private:
friend class Acs::PixelCacheRenderer;
KoColor colorAt(int x, int y, bool forceValid = false);
private:
bool allowsColorSelectionAtPoint(const QPoint &pt) const override;
QPointF m_lastClickPos;
QImage m_pixelCache;
QPoint m_pixelCacheOffset;
qreal R;
qreal G;
qreal B;
qreal Gamma;
QSize m_renderAreaSize;
qreal m_renderAreaOffsetX;
qreal m_renderAreaOffsetY;
QTransform m_toRenderArea;
- KisGamutMaskViewConverter* m_viewConverter;
};
#endif // KIS_COLOR_SELECTOR_WHEEL_H
diff --git a/plugins/dockers/animation/kis_animation_curves_view.cpp b/plugins/dockers/animation/kis_animation_curves_view.cpp
index b06308cc01..1f6d8e97f5 100644
--- a/plugins/dockers/animation/kis_animation_curves_view.cpp
+++ b/plugins/dockers/animation/kis_animation_curves_view.cpp
@@ -1,742 +1,738 @@
/*
* 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_animation_curves_view.h"
#include <QPaintEvent>
#include <QMouseEvent>
#include <QApplication>
#include <QScrollBar>
#include <qpainter.h>
#include <QtMath>
#include "kis_animation_curves_model.h"
#include "timeline_ruler_header.h"
#include "kis_animation_curves_value_ruler.h"
#include "kis_animation_curves_keyframe_delegate.h"
#include "kis_scalar_keyframe_channel.h"
#include "kis_zoom_button.h"
#include "kis_custom_modifiers_catcher.h"
const int VERTICAL_PADDING = 30;
struct KisAnimationCurvesView::Private
{
Private()
- : model(0)
- , isDraggingKeyframe(false)
- , isAdjustingHandle(false)
- , panning(false)
{}
- KisAnimationCurvesModel *model;
- TimelineRulerHeader *horizontalHeader;
- KisAnimationCurvesValueRuler *verticalHeader;
- KisAnimationCurvesKeyframeDelegate *itemDelegate;
- KisZoomButton *horizontalZoomButton;
- KisZoomButton *verticalZoomButton;
- KisCustomModifiersCatcher *modifiersCatcher;
-
- bool isDraggingKeyframe;
- bool isAdjustingHandle;
- int adjustedHandle; // 0 = left, 1 = right
+ KisAnimationCurvesModel *model {0};
+ TimelineRulerHeader *horizontalHeader {0};
+ KisAnimationCurvesValueRuler *verticalHeader {0};
+ KisAnimationCurvesKeyframeDelegate *itemDelegate {0};
+ KisZoomButton *horizontalZoomButton {0};
+ KisZoomButton *verticalZoomButton {0};
+ KisCustomModifiersCatcher *modifiersCatcher {0};
+
+ bool isDraggingKeyframe {false};
+ bool isAdjustingHandle {false};
+ int adjustedHandle {0}; // 0 = left, 1 = right
QPoint dragStart;
QPoint dragOffset;
- int horizontalZoomStillPointIndex;
- int horizontalZoomStillPointOriginalOffset;
- qreal verticalZoomStillPoint;
- qreal verticalZoomStillPointOriginalOffset;
+ int horizontalZoomStillPointIndex {0};
+ int horizontalZoomStillPointOriginalOffset {0};
+ qreal verticalZoomStillPoint {0.0};
+ qreal verticalZoomStillPointOriginalOffset {0.0};
- bool panning;
+ bool panning {false};
QPoint panStartOffset;
};
KisAnimationCurvesView::KisAnimationCurvesView(QWidget *parent)
: QAbstractItemView(parent)
, m_d(new Private())
{
m_d->horizontalHeader = new TimelineRulerHeader(this);
m_d->verticalHeader = new KisAnimationCurvesValueRuler(this);
m_d->itemDelegate = new KisAnimationCurvesKeyframeDelegate(m_d->horizontalHeader, m_d->verticalHeader, this);
m_d->modifiersCatcher = new KisCustomModifiersCatcher(this);
m_d->modifiersCatcher->addModifier("pan-zoom", Qt::Key_Space);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this);
if (scroller){
connect(scroller, SIGNAL(stateChanged(QScroller::State)),
this, SLOT(slotScrollerStateChanged(QScroller::State)));
}
}
KisAnimationCurvesView::~KisAnimationCurvesView()
{}
void KisAnimationCurvesView::setModel(QAbstractItemModel *model)
{
m_d->model = dynamic_cast<KisAnimationCurvesModel*>(model);
QAbstractItemView::setModel(model);
m_d->horizontalHeader->setModel(model);
connect(model, &QAbstractItemModel::rowsInserted,
this, &KisAnimationCurvesView::slotRowsChanged);
connect(model, &QAbstractItemModel::rowsRemoved,
this, &KisAnimationCurvesView::slotRowsChanged);
connect(model, &QAbstractItemModel::dataChanged,
this, &KisAnimationCurvesView::slotDataChanged);
connect(model, &QAbstractItemModel::headerDataChanged,
this, &KisAnimationCurvesView::slotHeaderDataChanged);
}
void KisAnimationCurvesView::setZoomButtons(KisZoomButton *horizontal, KisZoomButton *vertical)
{
m_d->horizontalZoomButton = horizontal;
m_d->verticalZoomButton = vertical;
connect(horizontal, &KisZoomButton::zoomStarted, this, &KisAnimationCurvesView::slotHorizontalZoomStarted);
connect(horizontal, &KisZoomButton::zoomLevelChanged, this, &KisAnimationCurvesView::slotHorizontalZoomLevelChanged);
connect(vertical, &KisZoomButton::zoomStarted, this, &KisAnimationCurvesView::slotVerticalZoomStarted);
connect(vertical, &KisZoomButton::zoomLevelChanged, this, &KisAnimationCurvesView::slotVerticalZoomLevelChanged);
}
QRect KisAnimationCurvesView::visualRect(const QModelIndex &index) const
{
return m_d->itemDelegate->itemRect(index);
}
void KisAnimationCurvesView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint)
{
// TODO
Q_UNUSED(index);
Q_UNUSED(hint);
}
QModelIndex KisAnimationCurvesView::indexAt(const QPoint &point) const
{
if (!model()) return QModelIndex();
int time = m_d->horizontalHeader->logicalIndexAt(point.x());
int rows = model()->rowCount();
for (int row=0; row < rows; row++) {
QModelIndex index = model()->index(row, time);
if (index.data(KisTimeBasedItemModel::SpecialKeyframeExists).toBool()) {
QRect nodePos = m_d->itemDelegate->itemRect(index);
if (nodePos.contains(point)) {
return index;
}
}
}
return QModelIndex();
}
void KisAnimationCurvesView::paintEvent(QPaintEvent *e)
{
QPainter painter(viewport());
QRect r = e->rect();
r.translate(dirtyRegionOffset());
int firstFrame = m_d->horizontalHeader->logicalIndexAt(r.left());
int lastFrame = m_d->horizontalHeader->logicalIndexAt(r.right());
if (lastFrame == -1) lastFrame = model()->columnCount();
paintCurves(painter, firstFrame, lastFrame);
paintKeyframes(painter, firstFrame, lastFrame);
}
void KisAnimationCurvesView::paintCurves(QPainter &painter, int firstFrame, int lastFrame)
{
int channels = model()->rowCount();
for (int channel = 0; channel < channels; channel++) {
QModelIndex index0 = model()->index(channel, 0);
if (!isIndexHidden(index0)) {
QColor color = index0.data(KisAnimationCurvesModel::CurveColorRole).value<QColor>();
painter.setPen(QPen(color, 1));
paintCurve(channel, firstFrame, lastFrame, painter);
}
}
}
void KisAnimationCurvesView::paintCurve(int channel, int firstFrame, int lastFrame, QPainter &painter)
{
int selectionOffset = m_d->isDraggingKeyframe ? (m_d->dragOffset.x() / m_d->horizontalHeader->defaultSectionSize()) : 0;
QModelIndex index = findNextKeyframeIndex(channel, firstFrame+1, selectionOffset, true);
if (!index.isValid()) {
index = findNextKeyframeIndex(channel, firstFrame, selectionOffset, false);
}
if (!index.isValid()) return;
QPointF previousKeyPos = m_d->itemDelegate->nodeCenter(index, selectionModel()->isSelected(index));
QPointF rightTangent = m_d->itemDelegate->rightHandle(index, index == currentIndex());
while(index.column() <= lastFrame) {
int interpolationMode = index.data(KisAnimationCurvesModel::InterpolationModeRole).toInt();
int time = (m_d->isDraggingKeyframe && selectionModel()->isSelected(index)) ? index.column() + selectionOffset : index.column();
index = findNextKeyframeIndex(channel, time, selectionOffset, false);
if (!index.isValid()) return;
bool active = (index == currentIndex());
QPointF nextKeyPos = m_d->itemDelegate->nodeCenter(index, selectionModel()->isSelected(index));
QPointF leftTangent = m_d->itemDelegate->leftHandle(index, active);
if (interpolationMode == KisKeyframe::Constant) {
painter.drawLine(previousKeyPos, QPointF(nextKeyPos.x(), previousKeyPos.y()));
} else if (interpolationMode == KisKeyframe::Linear) {
painter.drawLine(previousKeyPos, nextKeyPos);
} else {
paintCurveSegment(painter, previousKeyPos, rightTangent, leftTangent, nextKeyPos);
}
previousKeyPos = nextKeyPos;
rightTangent = m_d->itemDelegate->rightHandle(index, active);
}
}
void KisAnimationCurvesView::paintCurveSegment(QPainter &painter, QPointF pos1, QPointF rightTangent, QPointF leftTangent, QPointF pos2) {
const int steps = 16;
QPointF previousPos;
for (int step = 0; step <= steps; step++) {
qreal t = 1.0 * step / steps;
QPointF nextPos = KisScalarKeyframeChannel::interpolate(pos1, rightTangent, leftTangent, pos2, t);
if (step > 0) {
painter.drawLine(previousPos, nextPos);
}
previousPos = nextPos;
}
}
void KisAnimationCurvesView::paintKeyframes(QPainter &painter, int firstFrame, int lastFrame)
{
int channels = model()->rowCount();
for (int channel = 0; channel < channels; channel++) {
for (int time=firstFrame; time <= lastFrame; time++) {
QModelIndex index = model()->index(channel, time);
bool keyframeExists = model()->data(index, KisAnimationCurvesModel::SpecialKeyframeExists).toReal();
if (keyframeExists && !isIndexHidden(index)) {
QStyleOptionViewItem opt;
if (selectionModel()->isSelected(index)) {
opt.state |= QStyle::State_Selected;
}
if (index == selectionModel()->currentIndex()) {
opt.state |= QStyle::State_HasFocus;
}
m_d->itemDelegate->paint(&painter, opt, index);
}
}
}
}
QModelIndex KisAnimationCurvesView::findNextKeyframeIndex(int channel, int time, int selectionOffset, bool backward)
{
KisAnimationCurvesModel::ItemDataRole role =
backward ? KisAnimationCurvesModel::PreviousKeyframeTime : KisAnimationCurvesModel::NextKeyframeTime;
QModelIndex currentIndex = model()->index(channel, time);
if (!selectionOffset) {
QVariant next = currentIndex.data(role);
return (next.isValid()) ? model()->index(channel, next.toInt()) : QModelIndex();
} else {
// Find the next unselected index
QModelIndex nextIndex = currentIndex;
do {
QVariant next = nextIndex.data(role);
nextIndex = (next.isValid()) ? model()->index(channel, next.toInt()) : QModelIndex();
} while(nextIndex.isValid() && selectionModel()->isSelected(nextIndex));
// Find the next selected index, accounting for D&D offset
QModelIndex draggedIndex = model()->index(channel, qMax(0, time - selectionOffset));
do {
QVariant next = draggedIndex.data(role);
draggedIndex = (next.isValid()) ? model()->index(channel, next.toInt()) : QModelIndex();
} while(draggedIndex.isValid() && !selectionModel()->isSelected(draggedIndex));
// Choose the earlier of the two
if (draggedIndex.isValid() && nextIndex.isValid()) {
if (draggedIndex.column() + selectionOffset <= nextIndex.column()) {
return draggedIndex;
} else {
return nextIndex;
}
} else if (draggedIndex.isValid()) {
return draggedIndex;
} else {
return nextIndex;
}
}
}
void KisAnimationCurvesView::findExtremes(qreal *minimum, qreal *maximum)
{
if (!model()) return;
qreal min = qInf();
qreal max = -qInf();
int rows = model()->rowCount();
for (int row = 0; row < rows; row++) {
QModelIndex index = model()->index(row, 0);
if (isIndexHidden(index)) continue;
QVariant nextTime;
do {
qreal value = index.data(KisAnimationCurvesModel::ScalarValueRole).toReal();
if (value < min) min = value;
if (value > max) max = value;
nextTime = index.data(KisAnimationCurvesModel::NextKeyframeTime);
if (nextTime.isValid()) index = model()->index(row, nextTime.toInt());
} while (nextTime.isValid());
}
if (qIsFinite(min)) *minimum = min;
if (qIsFinite(max)) *maximum = max;
}
void KisAnimationCurvesView::updateVerticalRange()
{
if (!model()) return;
qreal minimum = 0;
qreal maximum = 0;
findExtremes(&minimum, &maximum);
int viewMin = maximum * m_d->verticalHeader->scaleFactor();
int viewMax = minimum * m_d->verticalHeader->scaleFactor();
viewMin -= VERTICAL_PADDING;
viewMax += VERTICAL_PADDING;
verticalScrollBar()->setRange(viewMin, viewMax - viewport()->height());
}
void KisAnimationCurvesView::startPan(QPoint mousePos)
{
m_d->dragStart = mousePos;
m_d->panStartOffset = QPoint(horizontalOffset(), verticalOffset());
m_d->panning = true;
}
QModelIndex KisAnimationCurvesView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
// TODO
Q_UNUSED(cursorAction);
Q_UNUSED(modifiers);
return QModelIndex();
}
int KisAnimationCurvesView::horizontalOffset() const
{
return m_d->horizontalHeader->offset();
}
int KisAnimationCurvesView::verticalOffset() const
{
return m_d->verticalHeader->offset();
}
bool KisAnimationCurvesView::isIndexHidden(const QModelIndex &index) const
{
return !index.data(KisAnimationCurvesModel::CurveVisibleRole).toBool();
}
void KisAnimationCurvesView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
int timeFrom = m_d->horizontalHeader->logicalIndexAt(rect.left());
int timeTo = m_d->horizontalHeader->logicalIndexAt(rect.right());
QItemSelection selection;
int rows = model()->rowCount();
for (int row=0; row < rows; row++) {
for (int time = timeFrom; time <= timeTo; time++) {
QModelIndex index = model()->index(row, time);
if (index.data(KisTimeBasedItemModel::SpecialKeyframeExists).toBool()) {
QRect itemRect = m_d->itemDelegate->itemRect(index);
if (itemRect.intersects(rect)) {
selection.select(index, index);
}
}
}
}
selectionModel()->select(selection, command);
}
QRegion KisAnimationCurvesView::visualRegionForSelection(const QItemSelection &selection) const
{
QRegion region;
Q_FOREACH(QModelIndex index, selection.indexes()) {
region += m_d->itemDelegate->visualRect(index);
}
return region;
}
void KisAnimationCurvesView::mousePressEvent(QMouseEvent *e)
{
if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) {
if (e->button() == Qt::LeftButton) {
startPan(e->pos());
} else {
qreal horizontalStaticPoint = m_d->horizontalHeader->logicalIndexAt(e->pos().x());
qreal verticalStaticPoint = m_d->verticalHeader->mapViewToValue(e->pos().y());
m_d->horizontalZoomButton->beginZoom(QPoint(e->pos().x(), 0), horizontalStaticPoint);
m_d->verticalZoomButton->beginZoom(QPoint(0, e->pos().y()), verticalStaticPoint);
}
} else if (e->button() == Qt::LeftButton) {
m_d->dragStart = e->pos();
Q_FOREACH(QModelIndex index, selectedIndexes()) {
QPointF center = m_d->itemDelegate->nodeCenter(index, false);
bool hasLeftHandle = m_d->itemDelegate->hasHandle(index, 0);
bool hasRightHandle = m_d->itemDelegate->hasHandle(index, 1);
QPointF leftHandle = center + m_d->itemDelegate->leftHandle(index, false);
QPointF rightHandle = center + m_d->itemDelegate->rightHandle(index, false);
if (hasLeftHandle && (e->localPos() - leftHandle).manhattanLength() < 8) {
m_d->isAdjustingHandle = true;
m_d->adjustedHandle = 0;
setCurrentIndex(index);
return;
} else if (hasRightHandle && (e->localPos() - rightHandle).manhattanLength() < 8) {
m_d->isAdjustingHandle = true;
m_d->adjustedHandle = 1;
setCurrentIndex(index);
return;
}
}
}
QAbstractItemView::mousePressEvent(e);
}
void KisAnimationCurvesView::mouseMoveEvent(QMouseEvent *e)
{
if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) {
if (e->buttons() & Qt::LeftButton) {
if (!m_d->panning) startPan(e->pos());
QPoint diff = e->pos() - m_d->dragStart;
QPoint newOffset = m_d->panStartOffset - diff;
horizontalScrollBar()->setValue(newOffset.x());
verticalScrollBar()->setValue(newOffset.y());
m_d->verticalHeader->setOffset(newOffset.y());
viewport()->update();
} else {
m_d->horizontalZoomButton->continueZoom(QPoint(e->pos().x(), 0));
m_d->verticalZoomButton->continueZoom(QPoint(0, e->pos().y()));
}
} else if (e->buttons() & Qt::LeftButton) {
m_d->dragOffset = e->pos() - m_d->dragStart;
if (m_d->isAdjustingHandle) {
m_d->itemDelegate->setHandleAdjustment(m_d->dragOffset, m_d->adjustedHandle);
viewport()->update();
return;
} else if (m_d->isDraggingKeyframe) {
m_d->itemDelegate->setSelectedItemVisualOffset(m_d->dragOffset);
viewport()->update();
return;
} else if (selectionModel()->hasSelection()) {
if ((e->pos() - m_d->dragStart).manhattanLength() > QApplication::startDragDistance()) {
m_d->isDraggingKeyframe = true;
}
}
}
QAbstractItemView::mouseMoveEvent(e);
}
void KisAnimationCurvesView::mouseReleaseEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton) {
m_d->panning = false;
if (m_d->isDraggingKeyframe) {
QModelIndexList indexes = selectedIndexes();
int timeOffset = m_d->dragOffset.x() / m_d->horizontalHeader->defaultSectionSize();
qreal valueOffset = m_d->dragOffset.y() / m_d->verticalHeader->scaleFactor();
KisAnimationCurvesModel *curvesModel = dynamic_cast<KisAnimationCurvesModel*>(model());
curvesModel->adjustKeyframes(indexes, timeOffset, valueOffset);
m_d->isDraggingKeyframe = false;
m_d->itemDelegate->setSelectedItemVisualOffset(QPointF());
viewport()->update();
} else if (m_d->isAdjustingHandle) {
QModelIndex index = currentIndex();
int mode = index.data(KisAnimationCurvesModel::TangentsModeRole).toInt();
m_d->model->beginCommand(kundo2_i18n("Adjust tangent"));
if (mode == KisKeyframe::Smooth) {
QPointF leftHandle = m_d->itemDelegate->leftHandle(index, true);
QPointF rightHandle = m_d->itemDelegate->rightHandle(index, true);
QPointF leftTangent = m_d->itemDelegate->unscaledTangent(leftHandle);
QPointF rightTangent = m_d->itemDelegate->unscaledTangent(rightHandle);
model()->setData(index, leftTangent, KisAnimationCurvesModel::LeftTangentRole);
model()->setData(index, rightTangent, KisAnimationCurvesModel::RightTangentRole);
} else {
if (m_d->adjustedHandle == 0) {
QPointF leftHandle = m_d->itemDelegate->leftHandle(index, true);
model()->setData(index, m_d->itemDelegate->unscaledTangent(leftHandle), KisAnimationCurvesModel::LeftTangentRole);
} else {
QPointF rightHandle = m_d->itemDelegate->rightHandle(index, true);
model()->setData(index, m_d->itemDelegate->unscaledTangent(rightHandle), KisAnimationCurvesModel::RightTangentRole);
}
}
m_d->model->endCommand();
m_d->isAdjustingHandle = false;
m_d->itemDelegate->setHandleAdjustment(QPointF(), m_d->adjustedHandle);
}
}
QAbstractItemView::mouseReleaseEvent(e);
}
void KisAnimationCurvesView::scrollContentsBy(int dx, int dy)
{
m_d->horizontalHeader->setOffset(horizontalScrollBar()->value());
m_d->verticalHeader->setOffset(verticalScrollBar()->value());
scrollDirtyRegion(dx, dy);
viewport()->scroll(dx, dy);
}
void KisAnimationCurvesView::updateGeometries()
{
int topMargin = qMax(m_d->horizontalHeader->minimumHeight(),
m_d->horizontalHeader->sizeHint().height());
int leftMargin = m_d->verticalHeader->sizeHint().width();
setViewportMargins(leftMargin, topMargin, 0, 0);
QRect viewRect = viewport()->geometry();
m_d->horizontalHeader->setGeometry(leftMargin, 0, viewRect.width(), topMargin);
m_d->verticalHeader->setGeometry(0, topMargin, leftMargin, viewRect.height());
horizontalScrollBar()->setRange(0, m_d->horizontalHeader->length() - viewport()->width());
updateVerticalRange();
QAbstractItemView::updateGeometries();
}
void KisAnimationCurvesView::slotRowsChanged(const QModelIndex &parentIndex, int first, int last)
{
Q_UNUSED(parentIndex);
Q_UNUSED(first);
Q_UNUSED(last);
updateVerticalRange();
viewport()->update();
}
void KisAnimationCurvesView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
Q_UNUSED(topLeft);
Q_UNUSED(bottomRight);
updateVerticalRange();
viewport()->update();
}
void KisAnimationCurvesView::slotHeaderDataChanged(Qt::Orientation orientation, int first, int last)
{
Q_UNUSED(orientation);
Q_UNUSED(first);
Q_UNUSED(last);
viewport()->update();
}
void KisAnimationCurvesView::slotHorizontalZoomStarted(qreal staticPoint)
{
m_d->horizontalZoomStillPointIndex =
qIsNaN(staticPoint) ? currentIndex().column() : staticPoint;
const int w = m_d->horizontalHeader->defaultSectionSize();
m_d->horizontalZoomStillPointOriginalOffset =
w * m_d->horizontalZoomStillPointIndex -
horizontalScrollBar()->value();
}
void KisAnimationCurvesView::slotHorizontalZoomLevelChanged(qreal zoomLevel)
{
if (m_d->horizontalHeader->setZoom(zoomLevel)) {
const int w = m_d->horizontalHeader->defaultSectionSize();
horizontalScrollBar()->setValue(w * m_d->horizontalZoomStillPointIndex - m_d->horizontalZoomStillPointOriginalOffset);
viewport()->update();
}
}
void KisAnimationCurvesView::slotVerticalZoomStarted(qreal staticPoint)
{
m_d->verticalZoomStillPoint = qIsNaN(staticPoint) ? 0 : staticPoint;
const float scale = m_d->verticalHeader->scaleFactor();
m_d->verticalZoomStillPointOriginalOffset =
scale * m_d->verticalZoomStillPoint - m_d->verticalHeader->offset();
}
void KisAnimationCurvesView::slotVerticalZoomLevelChanged(qreal zoomLevel)
{
if (!qFuzzyCompare((float)zoomLevel, m_d->verticalHeader->scaleFactor())) {
m_d->verticalHeader->setScale(zoomLevel);
m_d->verticalHeader->setOffset(-zoomLevel * m_d->verticalZoomStillPoint - m_d->verticalZoomStillPointOriginalOffset);
verticalScrollBar()->setValue(m_d->verticalHeader->offset());
viewport()->update();
}
}
void KisAnimationCurvesView::applyConstantMode()
{
m_d->model->beginCommand(kundo2_i18n("Set interpolation mode"));
Q_FOREACH(QModelIndex index, selectedIndexes()) {
m_d->model->setData(index, KisKeyframe::Constant, KisAnimationCurvesModel::InterpolationModeRole);
}
m_d->model->endCommand();
}
void KisAnimationCurvesView::applyLinearMode()
{
m_d->model->beginCommand(kundo2_i18n("Set interpolation mode"));
Q_FOREACH(QModelIndex index, selectedIndexes()) {
m_d->model->setData(index, KisKeyframe::Linear, KisAnimationCurvesModel::InterpolationModeRole);
}
m_d->model->endCommand();
}
void KisAnimationCurvesView::applyBezierMode()
{
m_d->model->beginCommand(kundo2_i18n("Set interpolation mode"));
Q_FOREACH(QModelIndex index, selectedIndexes()) {
m_d->model->setData(index, KisKeyframe::Bezier, KisAnimationCurvesModel::InterpolationModeRole);
}
m_d->model->endCommand();
}
void KisAnimationCurvesView::applySmoothMode()
{
m_d->model->beginCommand(kundo2_i18n("Set interpolation mode"));
Q_FOREACH(QModelIndex index, selectedIndexes()) {
QVector2D leftVisualTangent(m_d->itemDelegate->leftHandle(index, false));
QVector2D rightVisualTangent(m_d->itemDelegate->rightHandle(index, false));
if (leftVisualTangent.lengthSquared() > 0 && rightVisualTangent.lengthSquared() > 0) {
float leftAngle = qAtan2(-leftVisualTangent.y(), -leftVisualTangent.x());
float rightAngle = qAtan2(rightVisualTangent.y(), rightVisualTangent.x());
float angle = (leftAngle + rightAngle) / 2;
QVector2D unit(qCos(angle), qSin(angle));
leftVisualTangent = -unit * QVector2D(leftVisualTangent).length();
rightVisualTangent = unit * QVector2D(rightVisualTangent).length();
QPointF leftTangent = m_d->itemDelegate->unscaledTangent(leftVisualTangent.toPointF());
QPointF rightTangent = m_d->itemDelegate->unscaledTangent(rightVisualTangent.toPointF());
model()->setData(index, leftTangent, KisAnimationCurvesModel::LeftTangentRole);
model()->setData(index, rightTangent, KisAnimationCurvesModel::RightTangentRole);
}
model()->setData(index, KisKeyframe::Smooth, KisAnimationCurvesModel::TangentsModeRole);
}
m_d->model->endCommand();
}
void KisAnimationCurvesView::applySharpMode()
{
m_d->model->beginCommand(kundo2_i18n("Set interpolation mode"));
Q_FOREACH(QModelIndex index, selectedIndexes()) {
model()->setData(index, KisKeyframe::Sharp, KisAnimationCurvesModel::TangentsModeRole);
}
m_d->model->endCommand();
}
void KisAnimationCurvesView::createKeyframe()
{
QModelIndex active = currentIndex();
int channel = active.isValid() ? active.row() : 0;
int time = m_d->model->currentTime();
QModelIndex index = m_d->model->index(channel, time);
qreal value = index.data(KisAnimationCurvesModel::ScalarValueRole).toReal();
m_d->model->setData(index, value, KisAnimationCurvesModel::ScalarValueRole);
}
void KisAnimationCurvesView::removeKeyframes()
{
m_d->model->removeFrames(selectedIndexes());
}
void KisAnimationCurvesView::zoomToFit()
{
if (!model()) return;
qreal minimum, maximum;
findExtremes(&minimum, &maximum);
if (minimum == maximum) return;
qreal zoomLevel = (viewport()->height() - 2 * VERTICAL_PADDING) / (maximum - minimum);
qreal offset = -VERTICAL_PADDING - zoomLevel * maximum;
m_d->verticalHeader->setScale(zoomLevel);
m_d->verticalHeader->setOffset(offset);
verticalScrollBar()->setValue(offset);
viewport()->update();
}
diff --git a/plugins/dockers/animation/kis_zoom_button.cpp b/plugins/dockers/animation/kis_zoom_button.cpp
index 2ead7d47da..c0eb407e57 100644
--- a/plugins/dockers/animation/kis_zoom_button.cpp
+++ b/plugins/dockers/animation/kis_zoom_button.cpp
@@ -1,70 +1,69 @@
/*
* 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_zoom_button.h"
#include <cmath>
#include <QMouseEvent>
KisZoomButton::KisZoomButton(QWidget *parent)
: KisDraggableToolButton(parent)
- , m_zoomLevel(1.0)
{
connect(this, &KisZoomButton::valueChanged,
this, &KisZoomButton::slotValueChanged);
}
KisZoomButton::~KisZoomButton()
{}
qreal KisZoomButton::zoomLevel() const
{
return m_zoomLevel;
}
void KisZoomButton::setZoomLevel(qreal level)
{
m_zoomLevel = level;
}
void KisZoomButton::beginZoom(QPoint mousePos, qreal staticPoint)
{
m_initialDragZoomLevel = m_zoomLevel;
beginDrag(mousePos);
emit zoomStarted(staticPoint);
}
void KisZoomButton::continueZoom(QPoint mousePos)
{
int delta = continueDrag(mousePos);
slotValueChanged(delta);
}
void KisZoomButton::mousePressEvent(QMouseEvent *e)
{
beginZoom(e->pos(), qQNaN());
}
void KisZoomButton::slotValueChanged(int value)
{
qreal zoomCoeff = std::pow(2.0, qreal(value) / unitRadius());
m_zoomLevel = m_initialDragZoomLevel * zoomCoeff;
emit zoomLevelChanged(m_zoomLevel);
}
diff --git a/plugins/dockers/animation/kis_zoom_button.h b/plugins/dockers/animation/kis_zoom_button.h
index 34aab3339c..2fd64f6e68 100644
--- a/plugins/dockers/animation/kis_zoom_button.h
+++ b/plugins/dockers/animation/kis_zoom_button.h
@@ -1,51 +1,51 @@
/*
* 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.
*/
#ifndef _KIS_ZOOM_BUTTON_H
#define _KIS_ZOOM_BUTTON_H
#include "kis_draggable_tool_button.h"
class KisZoomButton : public KisDraggableToolButton
{
Q_OBJECT
public:
KisZoomButton(QWidget *parent);
~KisZoomButton() override;
qreal zoomLevel() const;
void setZoomLevel(qreal level);
void beginZoom(QPoint mousePos, qreal staticPoint);
void continueZoom(QPoint mousePos);
void mousePressEvent(QMouseEvent *e) override;
Q_SIGNALS:
void zoomStarted(qreal staticPoint);
void zoomLevelChanged(qreal level);
private Q_SLOTS:
void slotValueChanged(int value);
private:
- qreal m_zoomLevel;
- qreal m_initialDragZoomLevel;
+ qreal m_zoomLevel {1.0};
+ qreal m_initialDragZoomLevel {1.0};
};
#endif
diff --git a/plugins/dockers/animation/tests/CMakeLists.txt b/plugins/dockers/animation/tests/CMakeLists.txt
index a9683dc01e..cc9d500c27 100644
--- a/plugins/dockers/animation/tests/CMakeLists.txt
+++ b/plugins/dockers/animation/tests/CMakeLists.txt
@@ -1,12 +1,18 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_SOURCE_DIR}/sdk/tests ${CMAKE_CURRENT_BINARY_DIR}/..)
macro_add_unittest_definitions()
-########### next target ###############
+include(KritaAddBrokenUnitTest)
+
+krita_add_broken_unit_test(timeline_model_test.cpp
+ TEST_NAME timeline_model_test.cpp
+ NAME_PREFIX "plugins-dockers-animation-"
+ LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaanimationdocker kritaui kritaimage Qt5::Test)
ecm_add_tests(
- timeline_model_test.cpp
kis_animation_utils_test.cpp
NAME_PREFIX "plugins-dockers-animation-"
LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaanimationdocker kritaui kritaimage Qt5::Test)
+
+
diff --git a/plugins/dockers/artisticcolorselector/kis_color_selector.cpp b/plugins/dockers/artisticcolorselector/kis_color_selector.cpp
index e83e79f8fb..689353da82 100644
--- a/plugins/dockers/artisticcolorselector/kis_color_selector.cpp
+++ b/plugins/dockers/artisticcolorselector/kis_color_selector.cpp
@@ -1,1186 +1,1186 @@
/*
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_arcs_constants.h>
#include <resources/KoGamutMask.h>
-#include <KisGamutMaskViewConverter.h>
+//#include <KisGamutMaskViewConverter.h>
+#include <QTransform>
#include "kis_color_selector.h"
//#define DEBUG_ARC_SELECTOR
KisColorSelector::KisColorSelector(QWidget* parent, KisColor::Type type)
: QWidget(parent)
, m_colorConverter(KisDisplayColorConverter::dumbConverterInstance())
, m_colorSpace(type)
, m_inverseSaturation(false)
, m_selectedColor(m_colorConverter)
, m_fgColor(m_colorConverter)
, m_bgColor(m_colorConverter)
, m_clickedRing(-1)
, m_gamutMaskOn(false)
, m_currentGamutMask(nullptr)
, m_maskPreviewActive(true)
+ , m_gamutMaskViewTransform(QTransform())
, m_widgetUpdatesSelf(false)
, m_isDirtyWheel(false)
, m_isDirtyLightStrip(false)
, m_isDirtyGamutMask(false)
, m_isDirtyColorPreview(false)
{
- m_viewConverter = new KisGamutMaskViewConverter();
+// m_viewConverter = new KisGamutMaskViewConverter();
setLumaCoefficients(DEFAULT_LUMA_R, DEFAULT_LUMA_G, DEFAULT_LUMA_B,DEFAULT_LUMA_GAMMA);
recalculateRings(DEFAULT_SATURATION_STEPS, DEFAULT_HUE_STEPS);
recalculateAreas(DEFAULT_VALUE_SCALE_STEPS);
selectColor(KisColor(Qt::red, m_colorConverter, KisColor::HSY, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma));
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_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
m_isDirtyLightStrip = true;
m_isDirtyWheel = true;
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::setColorSpace: set to:" << m_colorSpace;
#endif
update();
}
void KisColorSelector::setColorConverter(KisDisplayColorConverter *colorConverter)
{
m_colorConverter = colorConverter;
m_selectedColor = KisColor(m_selectedColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
m_fgColor = KisColor(m_fgColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
m_bgColor = KisColor(m_bgColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
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_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
m_selectedPiece = getHueIndex(m_selectedColor.getH() * PI2);
m_selectedRing = getSaturationIndex(m_selectedColor.getS());
m_selectedLightPiece = getLightIndex(m_selectedColor.getX());
update();
}
void KisColorSelector::setFgColor(const KoColor& fgColor)
{
if (!m_widgetUpdatesSelf) {
m_fgColor = KisColor(fgColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
m_selectedColor = KisColor(fgColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
m_isDirtyWheel = true;
m_isDirtyLightStrip = true;
m_isDirtyColorPreview = true;
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::setFgColor: m_fgColor set to:"
<< "H:" << m_fgColor.getH()
<< "S:" << m_fgColor.getS()
<< "X:" << m_fgColor.getX();
dbgPlugins << "KisColorSelector::setFgColor: m_selectedColor set to:"
<< "H:" << m_selectedColor.getH()
<< "S:" << m_selectedColor.getS()
<< "X:" << m_selectedColor.getX();
#endif
update();
}
}
void KisColorSelector::setBgColor(const KoColor& bgColor)
{
if (!m_widgetUpdatesSelf) {
m_bgColor = KisColor(bgColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
m_isDirtyColorPreview = true;
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::setBgColor: m_bgColor set to:"
<< "H:" << m_bgColor.getH()
<< "S:" << m_bgColor.getS()
<< "X:" << m_bgColor.getX();
#endif
update();
}
}
void KisColorSelector::setLight(qreal light)
{
m_selectedColor.setX(qBound(0.0, light, 1.0));
m_selectedLightPiece = getLightIndex(m_selectedColor.getX());
m_isDirtyLightStrip = true;
update();
}
void KisColorSelector::setLumaCoefficients(qreal lR, qreal lG, qreal lB, qreal lGamma)
{
m_lumaR = lR;
m_lumaG = lG;
m_lumaB = lB;
m_lumaGamma = lGamma;
m_selectedColor = KisColor(m_selectedColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
m_isDirtyLightStrip = true;
m_isDirtyWheel = true;
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::setLumaCoefficients: " << m_lumaR
<< " " << m_lumaG << " " << m_lumaB << " " << m_lumaGamma;
#endif
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();
}
}
void KisColorSelector::setGamutMask(KoGamutMask* gamutMask)
{
if (!gamutMask) {
return;
}
m_currentGamutMask = gamutMask;
- m_viewConverter->setViewSize(m_renderAreaSize);
- m_viewConverter->setMaskSize(m_currentGamutMask->maskSize());
+ m_gamutMaskViewTransform = m_currentGamutMask->maskToViewTransform(m_renderArea.width());
if (m_enforceGamutMask) {
m_isDirtyWheel = true;
} else {
m_isDirtyGamutMask = true;
}
update();
}
void KisColorSelector::setDirty()
{
m_isDirtyWheel = true;
m_isDirtyLightStrip = true;
m_isDirtyGamutMask = true;
m_isDirtyColorPreview = true;
update();
}
KoGamutMask* KisColorSelector::gamutMask()
{
return m_currentGamutMask;
}
bool KisColorSelector::gamutMaskOn()
{
return m_gamutMaskOn;
}
void KisColorSelector::setGamutMaskOn(bool gamutMaskOn)
{
if (m_currentGamutMask) {
m_gamutMaskOn = gamutMaskOn;
if (m_enforceGamutMask) {
m_isDirtyWheel = true;
} else {
m_isDirtyGamutMask = true;
}
update();
}
}
void KisColorSelector::setEnforceGamutMask(bool enforce)
{
m_enforceGamutMask = enforce;
m_isDirtyGamutMask = true;
m_isDirtyWheel = true;
update();
}
QPointF KisColorSelector::mapCoordToView(const QPointF& pt, const QRectF& viewRect) const
{
qreal w = viewRect.width() / 2.0;
qreal h = viewRect.height() / 2.0;
qreal x = pt.x() + 1.0;
qreal y = (pt.y()) + 1.0;
return QPointF(x*w, y*h);
}
QPointF KisColorSelector::mapCoordToUnit(const QPointF& pt, const QRectF& viewRect) const
{
qreal w = viewRect.width() / 2.0;
qreal h = viewRect.height() / 2.0;
qreal x = pt.x() - (viewRect.x() + w);
qreal y = pt.y() - (viewRect.y() + h);
return QPointF(x/w, y/h);
}
QPointF KisColorSelector::mapColorToUnit(const KisColor& color, bool invertSaturation) const
{
qreal radius;
if (invertSaturation && m_inverseSaturation) {
radius = 1.0 - color.getS();
} else {
radius = color.getS();
}
QPointF hueCoord = mapHueToAngle(color.getH());
qreal x = hueCoord.x()*radius;
qreal y = hueCoord.y()*radius;
return QPointF(x,y);
}
KisColorSelector::Radian KisColorSelector::mapCoordToAngle(qreal x, qreal y) const
{
qreal angle = std::atan2(-y, -x);
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::mapCoordToAngle: "
<< "X:" << x
<< "Y:" << y
<< "angle:" << angle;
#endif
return angle;
}
QPointF KisColorSelector::mapHueToAngle(qreal hue) const
{
qreal angle = hue * 2.0 * M_PI - M_PI;
qreal x = std::cos(angle);
qreal y = std::sin(angle);
return QPointF(x,y);
}
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(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) const
{
qreal partSize = 1.0 / qreal(getNumPieces());
return qint8(qRound(hue.scaled(0.0, 1.0) / partSize) % getNumPieces());
}
qreal KisColorSelector::getHue(int hueIdx, Radian shift) const
{
Radian hue = (qreal(hueIdx) / qreal(getNumPieces())) * PI2;
hue += shift;
return hue.scaled(0.0, 1.0);
}
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)
{
qreal LIGHT_STRIP_RATIO = 0.075;
if (m_showValueScaleNumbers) {
LIGHT_STRIP_RATIO = 0.25;
}
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_renderAreaSize = QSize(size,size);
- m_viewConverter->setViewSize(m_renderAreaSize);
-
m_widgetArea = QRect(0, 0, QWidget::width(), QWidget::height());
m_renderArea = QRect(x+stripThick, y, size, size);
m_lightStripArea = QRect(0, 0, stripThick, QWidget::height());
m_renderBuffer = QImage(size, size, QImage::Format_ARGB32_Premultiplied);
m_colorPreviewBuffer = QImage(QWidget::width(), QWidget::height(), QImage::Format_ARGB32_Premultiplied);
m_maskBuffer = QImage(size, size, QImage::Format_ARGB32_Premultiplied);
m_lightStripBuffer = QImage(stripThick, QWidget::height(), QImage::Format_ARGB32_Premultiplied);
m_numLightPieces = numLightPieces;
m_isDirtyGamutMask = true;
m_isDirtyLightStrip = true;
m_isDirtyWheel = true;
m_isDirtyColorPreview = true;
}
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;
}
m_isDirtyWheel = true;
}
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);
}
}
bool KisColorSelector::colorIsClear(const KisColor &color)
{
if (m_gamutMaskOn && m_currentGamutMask) {
QPointF colorCoord = mapCoordToView(mapColorToUnit(color, false), m_renderArea);
- bool isClear = m_currentGamutMask->coordIsClear(colorCoord, *m_viewConverter, m_maskPreviewActive);
+
+ QPointF translatedPoint = m_currentGamutMask->viewToMaskTransform(m_renderArea.width()).map(colorCoord);
+ bool isClear = m_currentGamutMask->coordIsClear(translatedPoint, m_maskPreviewActive);
if (isClear) {
return true;
} else {
return false;
}
} else {
return true;
}
return false;
}
void KisColorSelector::requestUpdateColorAndPreview(const KisColor &color, Acs::ColorRole role)
{
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::requestUpdateColorAndPreview: requesting update to: "
<< "H:" << color.getH()
<< "S:" << color.getS()
<< "X:" << color.getX();
#endif
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 = KisColor(color.first, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
} else {
m_bgColor = KisColor(color.first, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
}
m_selectedColor = KisColor(color.first, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
m_isDirtyLightStrip = true;
m_isDirtyColorPreview = true;
m_isDirtyWheel = true;
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::slotUpdateColorAndPreview: m_selectedColor set to:"
<< "H:" << m_selectedColor.getH()
<< "S:" << m_selectedColor.getS()
<< "X:" << m_selectedColor.getX();
#endif
if (selectAsFgColor) { emit sigFgColorChanged(m_selectedColor); }
else { emit sigBgColorChanged(m_selectedColor); }
}
void KisColorSelector::drawRing(QPainter& painter, KisColorSelector::ColorRing& ring, const QRect& rect)
{
painter.save();
painter.setRenderHint(QPainter::Antialiasing, true);
painter.resetTransform();
painter.translate(rect.width()/2, rect.height()/2);
if (ring.pieced.size() > 1) {
QTransform mirror;
mirror.rotate(180, Qt::YAxis);
painter.setTransform(mirror, true);
painter.scale(rect.width()/2, rect.height()/2);
QPen normalPen = QPen(QBrush(COLOR_NORMAL_OUTLINE), 0.005);
QPen clearMaskPen = QPen(QBrush(COLOR_MASK_CLEAR), 0.005);
QBrush brush(Qt::SolidPattern);
for(int i=0; i<ring.pieced.size(); ++i) {
qreal hue = qreal(i) / qreal(ring.pieced.size());
hue = (hue >= 1.0) ? (hue - 1.0) : hue;
hue = (hue < 0.0) ? (hue + 1.0) : hue;
KisColor color(hue, m_colorConverter, m_colorSpace);
color.setS(ring.saturation);
color.setX(m_selectedColor.getX());
if(m_gamutMaskOn && m_enforceGamutMask && colorIsClear(color)) {
painter.setPen(clearMaskPen);
} else {
painter.setPen(normalPen);
}
if ((m_enforceGamutMask) && (!colorIsClear(color))) {
brush.setColor(COLOR_MASK_FILL);
} else {
brush.setColor(color.toQColor());
}
painter.setBrush(brush);
painter.drawPath(ring.pieced[i]);
}
}
else {
KisColor colors[7] = {
KisColor(Qt::cyan , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma),
KisColor(Qt::green , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma),
KisColor(Qt::yellow , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma),
KisColor(Qt::red , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma),
KisColor(Qt::magenta, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma),
KisColor(Qt::blue , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma),
KisColor(Qt::cyan , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma)
};
QConicalGradient gradient(0, 0, 0);
for(int i=0; i<=6; ++i) {
qreal hue = qreal(i) / 6.0;
colors[i].setS(ring.saturation);
colors[i].setX(m_selectedColor.getX());
gradient.setColorAt(hue, colors[i].toQColor());
}
painter.scale(rect.width()/2, rect.height()/2);
painter.fillPath(ring.pieced[0], QBrush(gradient));
}
painter.restore();
}
void KisColorSelector::drawOutline(QPainter& painter, const QRect& rect)
{
painter.save();
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);
QPen normalPen = QPen(QBrush(COLOR_NORMAL_OUTLINE), 0.005);
QPen selectedPen;
painter.setPen(normalPen);
if (getNumPieces() > 1) {
if (m_selectedRing >= 0 && m_selectedPiece >= 0) {
painter.resetTransform();
painter.translate(rect.x() + rect.width()/2, rect.y() + rect.height()/2);
QTransform mirror;
mirror.rotate(180, Qt::YAxis);
painter.setTransform(mirror, true);
painter.scale(rect.width()/2, rect.height()/2);
if (m_selectedColor.getX() < 0.55) {
selectedPen = QPen(QBrush(COLOR_SELECTED_LIGHT), 0.007);
} else {
selectedPen = QPen(QBrush(COLOR_SELECTED_DARK), 0.007);
}
painter.setPen(selectedPen);
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;
if (m_selectedColor.getX() < 0.55) {
selectedPen = QPen(QBrush(COLOR_SELECTED_LIGHT), 0.005);
} else {
selectedPen = QPen(QBrush(COLOR_SELECTED_DARK), 0.005);
}
painter.setPen(selectedPen);
painter.drawEllipse(QRectF(-iRad, -iRad, iRad*2.0, iRad*2.0));
painter.drawEllipse(QRectF(-oRad, -oRad, oRad*2.0, oRad*2.0));
QPointF lineCoords = mapHueToAngle(m_selectedColor.getH());
painter.drawLine(QPointF(lineCoords.x()*iRad, lineCoords.y()*iRad), QPointF(lineCoords.x()*oRad, lineCoords.y()*oRad));
}
}
painter.restore();
}
void KisColorSelector::drawLightStrip(QPainter& painter, const QRect& rect)
{
qreal penSize = qreal(qMin(QWidget::width(), QWidget::height())) / 200.0;
qreal penSizeSmall = penSize / 1.2;
QPen selectedPen;
KisColor valueScaleColor(m_selectedColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
KisColor grayScaleColor(Qt::gray, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma);
int rectSize = rect.height();
painter.save();
painter.resetTransform();
painter.setRenderHint(QPainter::Antialiasing, true);
QTransform matrix;
matrix.translate(rect.x(), rect.y());
matrix.scale(rect.width(), rect.height());
qreal rectColorLeftX;
qreal rectColorWidth;
if (m_showValueScaleNumbers) {
rectColorLeftX = 0.6;
rectColorWidth = 0.4;
} else {
rectColorLeftX = 0.0;
rectColorWidth = 1.0;
}
if (getNumLightPieces() > 1) {
for(int i=0; i<getNumLightPieces(); ++i) {
qreal t1 = qreal(i) / qreal(getNumLightPieces());
qreal t2 = qreal(i+1) / qreal(getNumLightPieces());
qreal light = 1.0 - (qreal(i) / qreal(getNumLightPieces()-1));
qreal diff = t2 - t1;// + 0.001;
QRectF rectColor = QRectF(rectColorLeftX, t1, rectColorWidth, diff);
rectColor = matrix.mapRect(rectColor);
valueScaleColor.setX(light);
painter.fillRect(rectColor, valueScaleColor.toQColor());
if (i == m_selectedLightPiece) {
if (light < 0.55) {
selectedPen = QPen(QBrush(COLOR_SELECTED_LIGHT), penSize);
} else {
selectedPen = QPen(QBrush(COLOR_SELECTED_DARK), penSize);
}
painter.setPen(selectedPen);
painter.drawRect(rectColor);
}
}
} else {
painter.setRenderHint(QPainter::Antialiasing, false);
for(int i=0; i<rectSize; ++i) {
int y = rect.y() + i;
qreal light = 1.0 - (qreal(i) / qreal(rectSize-1));
valueScaleColor.setX(light);
painter.setPen(QPen(QBrush(valueScaleColor.toQColor()), penSize));
painter.drawLine(rect.left(), y, rect.right(), y);
}
}
// draw color blip
painter.setRenderHint(QPainter::Antialiasing, false);
// draw position of fg color value on the strip
qreal fgColorValue = 1.0 - m_fgColor.getX();
int y = rect.y() + int(rectSize * fgColorValue);
painter.setPen(QPen(QBrush(COLOR_SELECTED_LIGHT), penSizeSmall));
painter.drawLine(rect.left(), y, rect.right(), y);
painter.setPen(QPen(QBrush(COLOR_SELECTED_DARK), penSizeSmall));
painter.drawLine(rect.left(), y+2*penSizeSmall, rect.right(), y+2*penSizeSmall);
// draw color blip
if (m_showValueScaleNumbers) {
painter.setRenderHint(QPainter::Antialiasing, true);
int valueScalePieces = getNumLightPieces();
if (getNumLightPieces() == 1) {
valueScalePieces = 11;
}
QFont font = painter.font();
QFontMetrics fm = painter.fontMetrics();
int retries = 10; // limit number of font size searches to prevent endless cycles
while ((retries > 0) && (fm.boundingRect("100%").width() > rect.width()*rectColorLeftX)) {
font.setPointSize(font.pointSize() - 1);
painter.setFont(font);
fm = painter.fontMetrics();
retries--;
}
for(int i=0; i<valueScalePieces; ++i) {
qreal t1 = qreal(i) / qreal(valueScalePieces);
qreal t2 = qreal(i+1) / qreal(valueScalePieces);
qreal light = 1.0 - (qreal(i) / qreal(valueScalePieces-1));
qreal diff = t2 - t1;// + 0.001;
grayScaleColor.setX(light);
QRectF rectValue = QRectF(0.0, t1, rectColorLeftX, diff);
rectValue = matrix.mapRect(rectValue);
painter.fillRect(rectValue, grayScaleColor.toQColor());
// if the right font size was not found in time,
// skip drawing the numbers
if (retries > 0) {
int valueNumber = 0;
if (m_colorSpace == KisColor::HSY) {
valueNumber = 100 - round(pow(pow(grayScaleColor.getX(), m_lumaGamma), 1.0/2.2)*100);
} else {
valueNumber = 100 - grayScaleColor.getX()*100;
}
if (valueNumber < 55) {
painter.setPen(QPen(QBrush(COLOR_DARK), penSize));
} else {
painter.setPen(QPen(QBrush(COLOR_LIGHT), penSize));
}
painter.drawText(rectValue, Qt::AlignRight|Qt::AlignBottom, QString("%1%").arg(QString::number(valueNumber)));
}
}
}
painter.restore();
}
void KisColorSelector::drawBlip(QPainter& painter, const QRect& rect)
{
painter.save();
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);
QPointF fgColorPos = mapColorToUnit(m_fgColor);
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::drawBlip: "
<< "colorPoint H:" << m_fgColor.getH() << " S:" << m_fgColor.getS()
<< "-> coord X:" << fgColorPos.x() << " Y:" << fgColorPos.y();
#endif
painter.setPen(QPen(QBrush(COLOR_SELECTED_DARK), 0.01));
painter.drawEllipse(fgColorPos, 0.05, 0.05);
painter.setPen(QPen(QBrush(COLOR_SELECTED_LIGHT), 0.01));
painter.drawEllipse(fgColorPos, 0.04, 0.04);
painter.restore();
}
void KisColorSelector::drawGamutMaskShape(QPainter &painter, const QRect &rect)
{
painter.save();
painter.setRenderHint(QPainter::Antialiasing, true);
painter.resetTransform();
painter.translate(rect.width()/2, rect.height()/2);
painter.scale(rect.width()/2, rect.height()/2);
painter.setPen(Qt::NoPen);
painter.setBrush(COLOR_MASK_FILL);
painter.drawEllipse(QPointF(0,0), 1.0, 1.0);
- painter.resetTransform();
+ painter.setWorldTransform(m_gamutMaskViewTransform);
painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
- m_currentGamutMask->paint(painter, *m_viewConverter, m_maskPreviewActive);
+ m_currentGamutMask->paint(painter, m_maskPreviewActive);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
- m_currentGamutMask->paintStroke(painter, *m_viewConverter, m_maskPreviewActive);
+ m_currentGamutMask->paintStroke(painter, m_maskPreviewActive);
painter.restore();
}
void KisColorSelector::drawColorPreview(QPainter &painter, const QRect &rect)
{
painter.save();
painter.setRenderHint(QPainter::Antialiasing, true);
painter.fillRect(rect, m_fgColor.toQColor());
int bgSide = qMin(rect.width()*0.15,rect.height()*0.15);
if (m_showBgColor) {
QPointF bgPolyPoints[3] = {
QPointF(rect.width(), rect.height()),
QPointF(rect.width()-bgSide, rect.height()),
QPointF(rect.width(), rect.height()-bgSide)
};
painter.setBrush(m_bgColor.toQColor());
painter.setPen(m_bgColor.toQColor());
painter.drawPolygon(bgPolyPoints, 3);
}
painter.restore();
}
void KisColorSelector::paintEvent(QPaintEvent* /*event*/)
{
QPainter wdgPainter(this);
// draw the fg and bg color previews
if (m_isDirtyColorPreview) {
m_colorPreviewBuffer.fill(Qt::transparent);
QPainter colorPreviewPainter(&m_colorPreviewBuffer);
drawColorPreview(colorPreviewPainter, m_widgetArea);
m_isDirtyColorPreview = false;
}
wdgPainter.drawImage(m_widgetArea, m_colorPreviewBuffer);
// draw the fg and bg color previews
// draw the wheel
if (m_isDirtyWheel) {
m_renderBuffer.fill(Qt::transparent);
QPainter wheelPainter(&m_renderBuffer);
for(int i=0; i<m_colorRings.size(); ++i)
drawRing(wheelPainter, m_colorRings[i], m_renderArea);
m_isDirtyWheel = false;
}
wdgPainter.drawImage(m_renderArea, m_renderBuffer);
// draw the wheel
// draw the mask either in continuous mode or in discrete mode when enforcing is turned off
// if enforcing is turned on in discrete mode,
// drawRing function takes care of delineating the mask swatches
if (m_gamutMaskOn
&& ((getNumPieces() == 1) || (!m_enforceGamutMask))) {
if (m_isDirtyGamutMask) {
m_maskBuffer.fill(Qt::transparent);
QPainter maskPainter(&m_maskBuffer);
drawGamutMaskShape(maskPainter, m_renderArea);
m_isDirtyGamutMask = false;
}
wdgPainter.drawImage(m_renderArea, m_maskBuffer);
}
// draw gamut mask
drawOutline(wdgPainter, m_renderArea);
// draw light strip
if (m_isDirtyLightStrip) {
m_lightStripBuffer.fill(Qt::transparent);
QPainter lightStripPainter(&m_lightStripBuffer);
drawLightStrip(lightStripPainter, m_lightStripArea);
m_isDirtyLightStrip = false;
}
wdgPainter.drawImage(m_lightStripArea, m_lightStripBuffer);
// draw light strip
drawBlip(wdgPainter, m_renderArea);
}
void KisColorSelector::mousePressEvent(QMouseEvent* event)
{
m_widgetUpdatesSelf = true;
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::mousePressEvent: m_widgetUpdatesSelf = true";
#endif
m_clickPos = mapCoordToUnit(event->localPos(), m_renderArea);
m_mouseMoved = false;
m_pressedButtons = event->buttons();
m_clickedRing = getSaturationIndex(m_clickPos);
Acs::ColorRole colorRole = Acs::buttonsToRole(Qt::NoButton, m_pressedButtons);
qint8 clickedLightPiece = getLightIndex(event->localPos());
if (clickedLightPiece >= 0) {
setLight(getLight(event->localPos()));
m_selectedLightPiece = clickedLightPiece;
requestUpdateColorAndPreview(m_selectedColor, colorRole);
m_mouseMoved = true;
}
else if (m_clickedRing >= 0) {
if (getNumPieces() == 1) {
Radian angle = mapCoordToAngle(m_clickPos.x(), m_clickPos.y());
KisColor color(m_colorConverter, m_colorSpace);
color.setHSX(angle.scaled(0.0, 1.0)
, getSaturation(m_clickedRing)
, m_selectedColor.getX()
);
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::mousePressEvent: picked color: "
<< "H:" << color.getH()
<< "S:" << color.getS()
<< "X:" << color.getX();
#endif
if ((!m_enforceGamutMask) || colorIsClear(color)) {
m_selectedColor.setHSX(color.getH(), color.getS(), color.getX());
requestUpdateColorAndPreview(m_selectedColor, colorRole);
m_selectedRing = m_clickedRing;
m_mouseMoved = true;
update();
}
}
}
}
void KisColorSelector::mouseMoveEvent(QMouseEvent* event)
{
QPointF dragPos = mapCoordToUnit(event->localPos(), m_renderArea);
qint8 clickedLightPiece = getLightIndex(event->localPos());
Acs::ColorRole colorRole = Acs::buttonsToRole(Qt::NoButton, m_pressedButtons);
if (clickedLightPiece >= 0) {
setLight(getLight(event->localPos()));
m_selectedLightPiece = clickedLightPiece;
requestUpdateColorAndPreview(m_selectedColor, colorRole);
}
if (m_clickedRing < 0)
return;
if (getNumPieces() == 1) {
Radian angle = mapCoordToAngle(dragPos.x(), dragPos.y());
KisColor color(m_colorConverter, m_colorSpace);
color.setHSX(angle.scaled(0.0, 1.0)
, getSaturation(m_clickedRing)
, m_selectedColor.getX()
);
if ((!m_enforceGamutMask) || colorIsClear(color)) {
m_selectedColor.setHSX(color.getH(), color.getS(), color.getX());
requestUpdateColorAndPreview(m_selectedColor, colorRole);
}
}
update();
}
void KisColorSelector::mouseReleaseEvent(QMouseEvent* /*event*/)
{
Acs::ColorRole colorRole = Acs::buttonsToRole(Qt::NoButton, m_pressedButtons);
if (!m_mouseMoved && m_clickedRing >= 0) {
Radian angle = mapCoordToAngle(m_clickPos.x(), m_clickPos.y());
KisColor color(m_colorConverter, m_colorSpace);
qint8 hueIndex = getHueIndex(angle);
if (getNumPieces() > 1) {
color.setH(getHue(hueIndex));
} else {
color.setH(angle.scaled(0.0, 1.0));
}
color.setS(getSaturation(m_clickedRing));
color.setX(m_selectedColor.getX());
if ((!m_enforceGamutMask) || colorIsClear(color)) {
m_selectedColor.setHSX(color.getH(), color.getS(), color.getX());
m_selectedPiece = hueIndex;
m_selectedRing = m_clickedRing;
requestUpdateColorAndPreview(m_selectedColor, colorRole);
}
}
else if (m_mouseMoved)
requestUpdateColorAndPreview(m_selectedColor, colorRole);
m_clickedRing = -1;
m_widgetUpdatesSelf = false;
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::ReleasePressEvent: m_widgetUpdatesSelf = false";
#endif
update();
}
void KisColorSelector::resizeEvent(QResizeEvent* /*event*/)
{
recalculateAreas(quint8(getNumLightPieces()));
}
void KisColorSelector::leaveEvent(QEvent* /*e*/)
{
m_widgetUpdatesSelf = false;
#ifdef DEBUG_ARC_SELECTOR
dbgPlugins << "KisColorSelector::leaveEvent: m_widgetUpdatesSelf = false";
#endif
}
void KisColorSelector::saveSettings()
{
KisConfig cfg(false);
cfg.writeEntry("ArtColorSel.ColorSpace" , qint32(m_colorSpace));
cfg.writeEntry("ArtColorSel.lumaR", qreal(m_lumaR));
cfg.writeEntry("ArtColorSel.lumaG", qreal(m_lumaG));
cfg.writeEntry("ArtColorSel.lumaB", qreal(m_lumaB));
cfg.writeEntry("ArtColorSel.lumaGamma", qreal(m_lumaGamma));
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.Light" , m_selectedColor.getX());
cfg.writeEntry("ArtColorSel.SelColorH", m_selectedColor.getH());
cfg.writeEntry("ArtColorSel.SelColorS", m_selectedColor.getS());
cfg.writeEntry("ArtColorSel.SelColorX", m_selectedColor.getX());
cfg.writeEntry("ArtColorSel.defaultHueSteps", quint32(m_defaultHueSteps));
cfg.writeEntry("ArtColorSel.defaultSaturationSteps", quint32(m_defaultSaturationSteps));
cfg.writeEntry("ArtColorSel.defaultValueScaleSteps", quint32(m_defaultValueScaleSteps));
cfg.writeEntry("ArtColorSel.showBgColor", m_showBgColor);
cfg.writeEntry("ArtColorSel.showValueScale", m_showValueScaleNumbers);
cfg.writeEntry("ArtColorSel.enforceGamutMask", m_enforceGamutMask);
}
void KisColorSelector::loadSettings()
{
KisConfig cfg(true);
m_defaultHueSteps = cfg.readEntry("ArtColorSel.defaultHueSteps", DEFAULT_HUE_STEPS);
m_defaultSaturationSteps = cfg.readEntry("ArtColorSel.defaultSaturationSteps", DEFAULT_SATURATION_STEPS);
m_defaultValueScaleSteps = cfg.readEntry("ArtColorSel.defaultValueScaleSteps", DEFAULT_VALUE_SCALE_STEPS);
setNumLightPieces(cfg.readEntry("ArtColorSel.LightPieces", DEFAULT_VALUE_SCALE_STEPS));
KisColor::Type colorSpace = KisColor::Type(cfg.readEntry<qint32>("ArtColorSel.ColorSpace" , KisColor::HSY));
setColorSpace(colorSpace);
setLumaCoefficients(cfg.readEntry("ArtColorSel.lumaR", DEFAULT_LUMA_R),
cfg.readEntry("ArtColorSel.lumaG", DEFAULT_LUMA_G),
cfg.readEntry("ArtColorSel.lumaB", DEFAULT_LUMA_B),
cfg.readEntry("ArtColorSel.lumaGamma", DEFAULT_LUMA_GAMMA));
m_selectedColor.setH(cfg.readEntry<qreal>("ArtColorSel.SelColorH", 0.0));
m_selectedColor.setS(cfg.readEntry<qreal>("ArtColorSel.SelColorS", 0.0));
m_selectedColor.setX(cfg.readEntry<qreal>("ArtColorSel.SelColorX", 0.0));
setInverseSaturation(cfg.readEntry<bool>("ArtColorSel.InversedSaturation", false));
setLight(cfg.readEntry<qreal>("ArtColorSel.Light", 0.5f));
setNumRings(cfg.readEntry("ArtColorSel.NumRings", DEFAULT_SATURATION_STEPS));
setNumPieces(cfg.readEntry("ArtColorSel.RingPieces", DEFAULT_HUE_STEPS));
m_showBgColor = cfg.readEntry("ArtColorSel.showBgColor", true);
m_showValueScaleNumbers = cfg.readEntry("ArtColorSel.showValueScale", false);
m_enforceGamutMask = cfg.readEntry("ArtColorSel.enforceGamutMask", false);
selectColor(m_selectedColor);
update();
}
void KisColorSelector::setDefaultHueSteps(int num)
{
num = qBound(MIN_NUM_HUE_PIECES, num, MAX_NUM_HUE_PIECES);
m_defaultHueSteps = num;
}
void KisColorSelector::setDefaultSaturationSteps(int num)
{
num = qBound(MIN_NUM_SATURATION_RINGS, num, MAX_NUM_SATURATION_RINGS);
m_defaultSaturationSteps = num;
}
void KisColorSelector::setDefaultValueScaleSteps(int num)
{
num = qBound(MIN_NUM_LIGHT_PIECES, num, MAX_NUM_LIGHT_PIECES);
m_defaultValueScaleSteps = num;
}
void KisColorSelector::setShowBgColor(bool value)
{
m_showBgColor = value;
m_isDirtyColorPreview = true;
update();
}
void KisColorSelector::setShowValueScaleNumbers(bool value)
{
m_showValueScaleNumbers = value;
recalculateAreas(quint8(getNumLightPieces()));
update();
}
diff --git a/plugins/dockers/artisticcolorselector/kis_color_selector.h b/plugins/dockers/artisticcolorselector/kis_color_selector.h
index 4db4adafbe..3d4395646b 100644
--- a/plugins/dockers/artisticcolorselector/kis_color_selector.h
+++ b/plugins/dockers/artisticcolorselector/kis_color_selector.h
@@ -1,209 +1,210 @@
/*
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 H_KIS_COLOR_SELECTOR_H
#define H_KIS_COLOR_SELECTOR_H
#include <QWidget>
#include <QVector>
#include <QImage>
#include <QPainterPath>
#include "kis_color.h"
#include "kis_radian.h"
#include "kis_acs_types.h"
#include "kis_signal_compressor_with_param.h"
#include <resources/KoGamutMask.h>
-#include <KisGamutMaskViewConverter.h>
class QPainter;
class KisDisplayColorConverter;
+class QTransform;
class KisColorSelector: public QWidget
{
Q_OBJECT
typedef KisRadian<qreal> Radian;
struct ColorRing
{
ColorRing()
: saturation(0)
, outerRadius(0)
, innerRadius(0)
{ }
qreal saturation;
qreal outerRadius;
qreal innerRadius;
QVector<QPainterPath> pieced;
};
public:
KisColorSelector(QWidget* parent, KisColor::Type type=KisColor::HSL);
void setColorSpace(KisColor::Type type);
void setColorConverter(KisDisplayColorConverter* colorConverter);
void setNumPieces(int num);
void setNumLightPieces(int num);
void setNumRings(int num);
void setLight(qreal light=0.0f);
void setLumaCoefficients(qreal lR, qreal lG, qreal lB, qreal lGamma);
inline qreal lumaR() const { return m_lumaR; }
inline qreal lumaG() const { return m_lumaG; }
inline qreal lumaB() const { return m_lumaB; }
inline qreal lumaGamma() const { return m_lumaGamma; }
void setInverseSaturation(bool inverse);
void selectColor(const KisColor& color);
void setFgColor(const KoColor& fgColor);
void setBgColor(const KoColor& bgColor);
void setDefaultHueSteps(int num);
void setDefaultSaturationSteps(int num);
void setDefaultValueScaleSteps(int num);
void setShowBgColor(bool value);
void setShowValueScaleNumbers(bool value);
void setGamutMask(KoGamutMask* gamutMask);
void setDirty();
bool gamutMaskOn();
void setGamutMaskOn(bool gamutMaskOn);
void setEnforceGamutMask(bool enforce);
KoGamutMask* gamutMask();
void saveSettings();
void loadSettings();
KisColor::Type getColorSpace () const { return m_colorSpace; }
qint32 getNumRings () const { return m_colorRings.size(); }
qint32 getNumPieces () const { return m_numPieces; }
qint32 getNumLightPieces () const { return m_numLightPieces; }
bool isSaturationInverted() const { return m_inverseSaturation; }
quint32 getDefaultHueSteps () const { return m_defaultHueSteps; }
quint32 getDefaultSaturationSteps () const { return m_defaultSaturationSteps; }
quint32 getDefaultValueScaleSteps () const { return m_defaultValueScaleSteps; }
bool getShowBgColor () const { return m_showBgColor; }
bool getShowValueScaleNumbers () const { return m_showValueScaleNumbers; }
bool enforceGamutMask () const { return m_enforceGamutMask; }
Q_SIGNALS:
void sigFgColorChanged(const KisColor& color);
void sigBgColorChanged(const KisColor& color);
private:
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void paintEvent(QPaintEvent* event) override;
void leaveEvent(QEvent* e) override;
bool colorIsClear(const KisColor &color);
bool colorIsClear(const QPointF &colorPoint);
void requestUpdateColorAndPreview(const KisColor &color, Acs::ColorRole role);
void recalculateAreas(quint8 numLightPieces);
void recalculateRings(quint8 numRings, quint8 numPieces);
void createRing(ColorRing& wheel, quint8 numPieces, qreal innerRadius, qreal outerRadius);
void drawRing(QPainter& painter, ColorRing& wheel, const QRect& rect);
void drawOutline(QPainter& painter, const QRect& rect);
void drawBlip(QPainter& painter, const QRect& rect);
void drawLightStrip(QPainter& painter, const QRect& rect);
void drawGamutMaskShape(QPainter& painter, const QRect& rect);
void drawColorPreview(QPainter& painter, const QRect& rect);
qint8 getHueIndex(Radian hue) const;
qreal getHue(int hueIdx, Radian shift=0.0f) const;
qint8 getLightIndex(const QPointF& pt) const;
qint8 getLightIndex(qreal light) const;
qreal getLight(const QPointF& pt) const;
qint8 getSaturationIndex(const QPointF& pt) const;
qint8 getSaturationIndex(qreal saturation) const;
qreal getSaturation(int saturationIdx) const;
QPointF mapCoordToView(const QPointF& pt, const QRectF& viewRect) const;
QPointF mapCoordToUnit(const QPointF& pt, const QRectF& viewRect) const;
QPointF mapColorToUnit(const KisColor& color, bool invertSaturation = true) const;
Radian mapCoordToAngle(qreal x, qreal y) const;
QPointF mapHueToAngle(qreal hue) const;
public:
// This is a private interface for signal compressor, don't use it.
// Use requestUpdateColorAndPreview() instead
void slotUpdateColorAndPreview(QPair<KisColor, Acs::ColorRole> color);
private:
KisDisplayColorConverter* m_colorConverter;
KisColor::Type m_colorSpace;
quint8 m_numPieces;
quint8 m_numLightPieces;
bool m_inverseSaturation;
qint8 m_selectedRing;
qint8 m_selectedPiece;
qint8 m_selectedLightPiece;
KisColor m_selectedColor;
KisColor m_fgColor;
KisColor m_bgColor;
QImage m_renderBuffer;
QImage m_maskBuffer;
QImage m_lightStripBuffer;
QImage m_colorPreviewBuffer;
QRect m_widgetArea;
QRect m_renderArea;
QRect m_lightStripArea;
bool m_mouseMoved;
QPointF m_clickPos;
qint8 m_clickedRing;
QVector<ColorRing> m_colorRings;
Qt::MouseButtons m_pressedButtons;
// docker settings
quint8 m_defaultHueSteps;
quint8 m_defaultSaturationSteps;
quint8 m_defaultValueScaleSteps;
bool m_showValueScaleNumbers {false};
bool m_showBgColor {true};
bool m_gamutMaskOn;
KoGamutMask* m_currentGamutMask;
bool m_enforceGamutMask;
- QSize m_renderAreaSize;
+// QSize m_renderAreaSize;
bool m_maskPreviewActive;
- KisGamutMaskViewConverter* m_viewConverter;
+// KisGamutMaskViewConverter* m_viewConverter;
+ QTransform m_gamutMaskViewTransform;
bool m_widgetUpdatesSelf;
bool m_isDirtyWheel;
bool m_isDirtyLightStrip;
bool m_isDirtyGamutMask;
bool m_isDirtyColorPreview;
qreal m_lumaR;
qreal m_lumaG;
qreal m_lumaB;
qreal m_lumaGamma;
typedef KisSignalCompressorWithParam<QPair<KisColor, Acs::ColorRole>> ColorCompressorType;
QScopedPointer<ColorCompressorType> m_updateColorCompressor;
};
#endif // H_KIS_COLOR_SELECTOR_H
diff --git a/plugins/dockers/layerdocker/NodeView.h b/plugins/dockers/layerdocker/NodeView.h
index abcd06d9af..489c2c1c85 100644
--- a/plugins/dockers/layerdocker/NodeView.h
+++ b/plugins/dockers/layerdocker/NodeView.h
@@ -1,187 +1,187 @@
/*
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.
*/
#ifndef KIS_DOCUMENT_SECTION_VIEW_H
#define KIS_DOCUMENT_SECTION_VIEW_H
#include <QTreeView>
#include <QScroller>
class QStyleOptionViewItem;
class KisNodeModel;
/**
* A widget displaying the Krita nodes (layers, masks, local selections, etc.)
*
* The widget can show the document sections as big thumbnails,
* in a listview with two rows of informative text and icons,
* or as single rows of text and property icons.
*
* This class is designed as a Qt model-view widget.
*
* The Qt documentation explains the design and terminology for these classes:
- * http://doc.qt.io/qt-5/model-view-programming.html
+ * https://doc.qt.io/qt-5/model-view-programming.html
*
* This widget should work correctly in your Qt designer .ui file.
*/
class NodeView: public QTreeView
{
Q_OBJECT
Q_SIGNALS:
/**
* Emitted whenever the user clicks with the secondary mouse
* button on an item. It is up to the application to design the
* contents of the context menu and show it.
*/
void contextMenuRequested(const QPoint &globalPos, const QModelIndex &index);
void selectionChanged(const QModelIndexList &);
public:
/**
* Create a new NodeView.
*/
explicit NodeView(QWidget *parent = 0);
~NodeView() override;
/// how items should be displayed
enum DisplayMode {
/// large fit-to-width thumbnails, with only titles or page numbers
ThumbnailMode,
/// smaller thumbnails, with titles and property icons in two rows
DetailedMode,
/// no thumbnails, with titles and property icons in a single row
MinimalMode
};
void resizeEvent(QResizeEvent * event) override;
void paintEvent (QPaintEvent *event) override;
void drawBranches(QPainter *painter, const QRect &rect,
const QModelIndex &index) const override;
void dropEvent(QDropEvent *ev) override;
void dragEnterEvent(QDragEnterEvent *e) override;
void dragMoveEvent(QDragMoveEvent *ev) override;
void dragLeaveEvent(QDragLeaveEvent *e) override;
/**
* Set the display mode of the view to one of the options.
*
* @param mode The NodeView::DisplayMode mode
*/
void setDisplayMode(DisplayMode mode);
/**
* @return the currently active display mode
*/
DisplayMode displayMode() const;
/**
* Add toggle actions for all the properties associated with the
* current document section associated with the model index to the
* specified menu.
*
* For instance, if a document section can be locked and visible,
* the menu will be expanded with locked and visible toggle
* actions.
*
* For instance
@code
NodeView * nodeView;
QModelIndex index = getCurrentNode();
QMenu menu;
if (index.isValid()) {
sectionView->addPropertyActions(&menu, index);
} else {
menu.addAction(...); // Something to create a new document section, for example.
}
@endcode
*
* @param menu A pointer to the menu that will be expanded with
* the toglge actions
* @param index The model index associated with the document
* section that may or may not provide a number of toggle actions.
*/
void addPropertyActions(QMenu *menu, const QModelIndex &index);
void updateNode(const QModelIndex &index);
QRect originalVisualRect(const QModelIndex &index) const;
protected:
QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index,
const QEvent *event) const override;
QRect visualRect(const QModelIndex &index) const override;
QModelIndex indexAt(const QPoint &point) const override;
bool viewportEvent(QEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
virtual void showContextMenu(const QPoint &globalPos, const QModelIndex &index);
void startDrag (Qt::DropActions supportedActions) override;
QPixmap createDragPixmap() const;
/**
* Calculates the index of the nearest item to the cursor position
*/
int cursorPageIndex() const;
public Q_SLOTS:
/// called with a theme change to refresh icon colors
void slotUpdateIcons();
void slotScrollerStateChanged(QScroller::State state);
protected Q_SLOTS:
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override;
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) override;
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
private Q_SLOTS:
void slotActionToggled(bool on, const QPersistentModelIndex &index, int property);
private:
/**
* Permit to know if a slide is dragging
*
* @return boolean
*/
bool isDragging() const;
/**
* Setter for the dragging flag
*
* @param flag boolean
*/
void setDraggingFlag(bool flag = true);
bool m_draggingFlag;
QStyleOptionViewItem optionForIndex(const QModelIndex &index) const;
typedef KisNodeModel Model;
class PropertyAction;
class Private;
Private* const d;
};
#endif
diff --git a/plugins/dockers/lut/lutdocker_dock.cpp b/plugins/dockers/lut/lutdocker_dock.cpp
index ccd665216d..c7e68dce06 100644
--- a/plugins/dockers/lut/lutdocker_dock.cpp
+++ b/plugins/dockers/lut/lutdocker_dock.cpp
@@ -1,685 +1,685 @@
/*
* 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 "lutdocker_dock.h"
#include <config-hdr.h>
#include <sstream>
#include <QLayout>
#include <QLabel>
#include <QPixmap>
#include <QPainter>
#include <QImage>
#include <QFormLayout>
#include <QCheckBox>
#include <QApplication>
#include <QDesktopWidget>
#include <QToolButton>
#include <QDir>
#include <klocalizedstring.h>
#include <KisMainWindow.h>
#include <KoFileDialog.h>
#include <KoChannelInfo.h>
#include <KoColorSpace.h>
#include <KoColorSpaceFactory.h>
#include <KoColorProfile.h>
#include <KoColorModelStandardIds.h>
#include "kis_icon_utils.h"
#include <KisViewManager.h>
#include <KisDocument.h>
#include <kis_config.h>
#include <kis_canvas2.h>
#include <kis_canvas_resource_provider.h>
#include <kis_config_notifier.h>
#include <widgets/kis_double_widget.h>
#include <kis_image.h>
#include <KisSqueezedComboBox.h>
#include "kis_signals_blocker.h"
#include "krita_utils.h"
#include "ocio_display_filter.h"
#include "black_white_point_chooser.h"
#include "KisOcioConfiguration.h"
#include <opengl/KisOpenGLModeProber.h>
OCIO::ConstConfigRcPtr defaultRawProfile()
{
/**
* Copied from OCIO, just a noop profile
*/
const char * INTERNAL_RAW_PROFILE =
"ocio_profile_version: 1\n"
"strictparsing: false\n"
"roles:\n"
" default: raw\n"
"displays:\n"
" sRGB:\n"
" - !<View> {name: Raw, colorspace: raw}\n"
"colorspaces:\n"
" - !<ColorSpace>\n"
" name: raw\n"
" family: raw\n"
" equalitygroup:\n"
" bitdepth: 32f\n"
" isdata: true\n"
" allocation: uniform\n"
" description: 'A raw color space. Conversions to and from this space are no-ops.'\n";
std::istringstream istream;
istream.str(INTERNAL_RAW_PROFILE);
return OCIO::Config::CreateFromStream(istream);
}
LutDockerDock::LutDockerDock()
: QDockWidget(i18n("LUT Management"))
, m_canvas(0)
, m_draggingSlider(false)
{
using namespace std::placeholders; // For _1
m_exposureCompressor.reset(
new KisSignalCompressorWithParam<qreal>(40, std::bind(&LutDockerDock::setCurrentExposureImpl, this, _1)));
m_gammaCompressor.reset(
new KisSignalCompressorWithParam<qreal>(40, std::bind(&LutDockerDock::setCurrentGammaImpl, this, _1)));
m_page = new QWidget(this);
setupUi(m_page);
setWidget(m_page);
KisConfig cfg(true);
m_chkUseOcio->setChecked(cfg.useOcio());
connect(m_chkUseOcio, SIGNAL(toggled(bool)), SLOT(updateDisplaySettings()));
connect(m_colorManagement, SIGNAL(currentIndexChanged(int)), SLOT(slotColorManagementModeChanged()));
m_bnSelectConfigurationFile->setToolTip(i18n("Select custom configuration file."));
connect(m_bnSelectConfigurationFile,SIGNAL(clicked()), SLOT(selectOcioConfiguration()));
KisOcioConfiguration ocioOptions = cfg.ocioConfiguration();
m_txtConfigurationPath->setText(ocioOptions.configurationPath);
m_txtLut->setText(ocioOptions.lutPath);
m_bnSelectLut->setToolTip(i18n("Select LUT file"));
connect(m_bnSelectLut, SIGNAL(clicked()), SLOT(selectLut()));
connect(m_bnClearLut, SIGNAL(clicked()), SLOT(clearLut()));
- // See http://groups.google.com/group/ocio-dev/browse_thread/thread/ec95c5f54a74af65 -- maybe need to be reinstated
+ // See https://groups.google.com/group/ocio-dev/browse_thread/thread/ec95c5f54a74af65 -- maybe need to be reinstated
// when people ask for it.
m_lblLut->hide();
m_txtLut->hide();
m_bnSelectLut->hide();
m_bnClearLut->hide();
connect(m_cmbDisplayDevice, SIGNAL(currentIndexChanged(int)), SLOT(refillViewCombobox()));
m_exposureDoubleWidget->setToolTip(i18n("Select the exposure (stops) for HDR images."));
m_exposureDoubleWidget->setRange(-10, 10);
m_exposureDoubleWidget->setPrecision(1);
m_exposureDoubleWidget->setValue(0.0);
m_exposureDoubleWidget->setSingleStep(0.25);
m_exposureDoubleWidget->setPageStep(1);
connect(m_exposureDoubleWidget, SIGNAL(valueChanged(double)), SLOT(exposureValueChanged(double)));
connect(m_exposureDoubleWidget, SIGNAL(sliderPressed()), SLOT(exposureSliderPressed()));
connect(m_exposureDoubleWidget, SIGNAL(sliderReleased()), SLOT(exposureSliderReleased()));
// Gamma needs to be exponential (gamma *= 1.1f, gamma /= 1.1f as steps)
m_gammaDoubleWidget->setToolTip(i18n("Select the amount of gamma modification for display. This does not affect the pixels of your image."));
m_gammaDoubleWidget->setRange(0.1, 5);
m_gammaDoubleWidget->setPrecision(2);
m_gammaDoubleWidget->setValue(1.0);
m_gammaDoubleWidget->setSingleStep(0.1);
m_gammaDoubleWidget->setPageStep(1);
connect(m_gammaDoubleWidget, SIGNAL(valueChanged(double)), SLOT(gammaValueChanged(double)));
connect(m_gammaDoubleWidget, SIGNAL(sliderPressed()), SLOT(gammaSliderPressed()));
connect(m_gammaDoubleWidget, SIGNAL(sliderReleased()), SLOT(gammaSliderReleased()));
m_bwPointChooser = new BlackWhitePointChooser(this);
connect(m_bwPointChooser, SIGNAL(sigBlackPointChanged(qreal)), SLOT(updateDisplaySettings()));
connect(m_bwPointChooser, SIGNAL(sigWhitePointChanged(qreal)), SLOT(updateDisplaySettings()));
connect(m_btnConvertCurrentColor, SIGNAL(toggled(bool)), SLOT(updateDisplaySettings()));
connect(m_btmShowBWConfiguration, SIGNAL(clicked()), SLOT(slotShowBWConfiguration()));
slotUpdateIcons();
connect(m_cmbInputColorSpace, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings()));
connect(m_cmbDisplayDevice, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings()));
connect(m_cmbView, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings()));
connect(m_cmbLook, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings()));
connect(m_cmbComponents, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings()));
m_draggingSlider = false;
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetOcioConfiguration()));
resetOcioConfiguration();
}
LutDockerDock::~LutDockerDock()
{
}
void LutDockerDock::setCanvas(KoCanvasBase* _canvas)
{
if (m_canvas) {
m_canvas->disconnect(this);
}
setEnabled(_canvas != 0);
if (KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(_canvas)) {
m_canvas = canvas;
if (m_canvas) {
if (!m_canvas->displayFilter()) {
resetOcioConfiguration();
updateDisplaySettings();
}
else {
m_displayFilter = m_canvas->displayFilter();
OcioDisplayFilter *displayFilter = qobject_cast<OcioDisplayFilter*>(m_displayFilter.data());
Q_ASSERT(displayFilter);
m_ocioConfig = displayFilter->config;
KisSignalsBlocker exposureBlocker(m_exposureDoubleWidget);
m_exposureDoubleWidget->setValue(displayFilter->exposure);
KisSignalsBlocker gammaBlocker(m_gammaDoubleWidget);
m_gammaDoubleWidget->setValue(displayFilter->gamma);
KisSignalsBlocker componentsBlocker(m_cmbComponents);
m_cmbComponents->setCurrentIndex((int)displayFilter->swizzle);
KisSignalsBlocker bwBlocker(m_bwPointChooser);
m_bwPointChooser->setBlackPoint(displayFilter->blackPoint);
m_bwPointChooser->setWhitePoint(displayFilter->whitePoint);
}
connect(m_canvas->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged()), Qt::UniqueConnection);
connect(m_canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), SLOT(slotUpdateIcons()), Qt::UniqueConnection);
}
}
}
void LutDockerDock::unsetCanvas()
{
m_canvas = 0;
setEnabled(false);
m_displayFilter = QSharedPointer<KisDisplayFilter>(0);
}
void LutDockerDock::slotUpdateIcons()
{
m_btnConvertCurrentColor->setIcon(KisIconUtils::loadIcon("krita_tool_freehand"));
m_btmShowBWConfiguration->setIcon(KisIconUtils::loadIcon("properties"));
}
void LutDockerDock::slotShowBWConfiguration()
{
m_bwPointChooser->showPopup(m_btmShowBWConfiguration->mapToGlobal(QPoint()));
}
bool LutDockerDock::canChangeExposureAndGamma() const
{
if (!m_chkUseOcio->isChecked() || !m_ocioConfig) return false;
const bool externalColorManagementEnabled =
m_colorManagement->currentIndex() != (int)KisOcioConfiguration::INTERNAL;
#ifdef HAVE_HDR
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
KisSurfaceColorSpace currentColorSpace = KisOpenGLModeProber::instance()->surfaceformatInUse().colorSpace();
#else
KisSurfaceColorSpace currentColorSpace = KisSurfaceColorSpace::DefaultColorSpace;
#endif
#endif
const bool exposureManagementEnabled =
externalColorManagementEnabled
#ifdef HAVE_HDR
|| currentColorSpace == KisSurfaceColorSpace::scRGBColorSpace
#endif
;
return exposureManagementEnabled;
}
qreal LutDockerDock::currentExposure() const
{
if (!m_displayFilter) return 0.0;
OcioDisplayFilter *displayFilter = qobject_cast<OcioDisplayFilter*>(m_displayFilter.data());
return canChangeExposureAndGamma() ? displayFilter->exposure : 0.0;
}
void LutDockerDock::setCurrentExposure(qreal value)
{
if (!canChangeExposureAndGamma()) return;
m_exposureCompressor->start(value);
}
qreal LutDockerDock::currentGamma() const
{
if (!m_displayFilter) return 1.0;
OcioDisplayFilter *displayFilter = qobject_cast<OcioDisplayFilter*>(m_displayFilter.data());
return canChangeExposureAndGamma() ? displayFilter->gamma : 1.0;
}
void LutDockerDock::setCurrentGamma(qreal value)
{
if (!canChangeExposureAndGamma()) return;
m_gammaCompressor->start(value);
}
void LutDockerDock::setCurrentExposureImpl(qreal value)
{
m_exposureDoubleWidget->setValue(value);
if (!m_canvas) return;
m_canvas->viewManager()->showFloatingMessage(
i18nc("floating message about exposure", "Exposure: %1",
KritaUtils::prettyFormatReal(m_exposureDoubleWidget->value())),
QIcon(), 500, KisFloatingMessage::Low);
}
void LutDockerDock::setCurrentGammaImpl(qreal value)
{
m_gammaDoubleWidget->setValue(value);
if (!m_canvas) return;
m_canvas->viewManager()->showFloatingMessage(
i18nc("floating message about gamma", "Gamma: %1",
KritaUtils::prettyFormatReal(m_gammaDoubleWidget->value())),
QIcon(), 500, KisFloatingMessage::Low);
}
void LutDockerDock::slotImageColorSpaceChanged()
{
enableControls();
writeControls();
resetOcioConfiguration();
}
void LutDockerDock::exposureValueChanged(double exposure)
{
if (m_canvas && !m_draggingSlider) {
m_canvas->viewManager()->canvasResourceProvider()->setHDRExposure(exposure);
updateDisplaySettings();
}
}
void LutDockerDock::exposureSliderPressed()
{
m_draggingSlider = true;
}
void LutDockerDock::exposureSliderReleased()
{
m_draggingSlider = false;
exposureValueChanged(m_exposureDoubleWidget->value());
}
void LutDockerDock::gammaValueChanged(double gamma)
{
if (m_canvas && !m_draggingSlider) {
m_canvas->viewManager()->canvasResourceProvider()->setHDRGamma(gamma);
updateDisplaySettings();
}
}
void LutDockerDock::gammaSliderPressed()
{
m_draggingSlider = true;
}
void LutDockerDock::gammaSliderReleased()
{
m_draggingSlider = false;
gammaValueChanged(m_gammaDoubleWidget->value());
}
void LutDockerDock::enableControls()
{
bool canDoExternalColorCorrection = false;
if (m_canvas) {
KisImageSP image = m_canvas->viewManager()->image();
canDoExternalColorCorrection =
image->colorSpace()->colorModelId() == RGBAColorModelID;
}
if (!canDoExternalColorCorrection) {
KisSignalsBlocker colorManagementBlocker(m_colorManagement);
Q_UNUSED(colorManagementBlocker);
m_colorManagement->setCurrentIndex((int) KisOcioConfiguration::INTERNAL);
}
const bool ocioEnabled = m_chkUseOcio->isChecked();
m_colorManagement->setEnabled(ocioEnabled && canDoExternalColorCorrection);
const bool externalColorManagementEnabled =
m_colorManagement->currentIndex() != (int)KisOcioConfiguration::INTERNAL;
m_lblInputColorSpace->setEnabled(ocioEnabled && externalColorManagementEnabled);
m_cmbInputColorSpace->setEnabled(ocioEnabled && externalColorManagementEnabled);
m_lblDisplayDevice->setEnabled(ocioEnabled && externalColorManagementEnabled);
m_cmbDisplayDevice->setEnabled(ocioEnabled && externalColorManagementEnabled);
m_lblView->setEnabled(ocioEnabled && externalColorManagementEnabled);
m_cmbView->setEnabled(ocioEnabled && externalColorManagementEnabled);
m_lblLook->setEnabled(ocioEnabled && externalColorManagementEnabled);
m_cmbLook->setEnabled(ocioEnabled && externalColorManagementEnabled);
const bool exposureManagementEnabled = canChangeExposureAndGamma();
m_exposureDoubleWidget->setEnabled(exposureManagementEnabled);
m_gammaDoubleWidget->setEnabled(exposureManagementEnabled);
m_lblExposure->setEnabled(exposureManagementEnabled);
m_lblGamma->setEnabled(exposureManagementEnabled);
QString exposureToolTip;
if (!exposureManagementEnabled) {
exposureToolTip = i18nc("@info:tooltip", "Exposure and Gamma corrections are disabled in Internal mode. Switch to OCIO mode to use them");
}
m_exposureDoubleWidget->setToolTip(exposureToolTip);
m_gammaDoubleWidget->setToolTip(exposureToolTip);
m_lblExposure->setToolTip(exposureToolTip);
m_lblGamma->setToolTip(exposureToolTip);
bool enableConfigPath = m_colorManagement->currentIndex() == (int) KisOcioConfiguration::OCIO_CONFIG;
lblConfig->setEnabled(ocioEnabled && enableConfigPath);
m_txtConfigurationPath->setEnabled(ocioEnabled && enableConfigPath);
m_bnSelectConfigurationFile->setEnabled(ocioEnabled && enableConfigPath);
}
void LutDockerDock::updateDisplaySettings()
{
if (!m_canvas || !m_canvas->viewManager() || !m_canvas->viewManager()->image()) {
return;
}
enableControls();
writeControls();
if (m_chkUseOcio->isChecked() && m_ocioConfig) {
KIS_SAFE_ASSERT_RECOVER_NOOP(!m_canvas->displayFilter() ||
m_canvas->displayFilter() == m_displayFilter);
if (!m_displayFilter) {
m_displayFilter =
m_canvas->displayFilter() ?
m_canvas->displayFilter() :
QSharedPointer<KisDisplayFilter>(new OcioDisplayFilter(this));
}
OcioDisplayFilter *displayFilter = qobject_cast<OcioDisplayFilter*>(m_displayFilter.data());
displayFilter->config = m_ocioConfig;
displayFilter->inputColorSpaceName = m_ocioConfig->getColorSpaceNameByIndex(m_cmbInputColorSpace->currentIndex());
displayFilter->displayDevice = m_ocioConfig->getDisplay(m_cmbDisplayDevice->currentIndex());
displayFilter->view = m_ocioConfig->getView(displayFilter->displayDevice, m_cmbView->currentIndex());
displayFilter->look = m_ocioConfig->getLookNameByIndex(m_cmbLook->currentIndex());
displayFilter->gamma = m_gammaDoubleWidget->isEnabled() ? m_gammaDoubleWidget->value() : 1.0;
displayFilter->exposure = m_exposureDoubleWidget->isEnabled() ? m_exposureDoubleWidget->value() : 0.0;
displayFilter->swizzle = (OCIO_CHANNEL_SWIZZLE)m_cmbComponents->currentIndex();
displayFilter->blackPoint = m_bwPointChooser->blackPoint();
displayFilter->whitePoint = m_bwPointChooser->whitePoint();
displayFilter->forceInternalColorManagement =
m_colorManagement->currentIndex() == (int)KisOcioConfiguration::INTERNAL;
displayFilter->setLockCurrentColorVisualRepresentation(m_btnConvertCurrentColor->isChecked());
displayFilter->updateProcessor();
m_canvas->setDisplayFilter(m_displayFilter);
}
else {
m_canvas->setDisplayFilter(QSharedPointer<KisDisplayFilter>(0));
}
m_canvas->updateCanvas();
}
void LutDockerDock::writeControls()
{
KisOcioConfiguration ocioOptions;
ocioOptions.mode = (KisOcioConfiguration::Mode)m_colorManagement->currentIndex();
ocioOptions.configurationPath = m_txtConfigurationPath->text();
ocioOptions.lutPath = m_txtLut->text();
ocioOptions.inputColorSpace = m_cmbInputColorSpace->currentUnsqueezedText();
ocioOptions.displayDevice = m_cmbDisplayDevice->currentUnsqueezedText();
ocioOptions.displayView = m_cmbView->currentUnsqueezedText();
ocioOptions.look = m_cmbLook->currentUnsqueezedText();
KisConfig cfg(false);
cfg.setUseOcio(m_chkUseOcio->isChecked());
cfg.setOcioConfiguration(ocioOptions);
cfg.setOcioLockColorVisualRepresentation(m_btnConvertCurrentColor->isChecked());
}
void LutDockerDock::slotColorManagementModeChanged()
{
enableControls();
writeControls();
resetOcioConfiguration();
}
void LutDockerDock::selectOcioConfiguration()
{
QString filename = m_txtConfigurationPath->text();
KoFileDialog dialog(this, KoFileDialog::OpenFile, "lutdocker");
dialog.setCaption(i18n("Select OpenColorIO Configuration"));
dialog.setDefaultDir(QDir::cleanPath(filename.isEmpty() ? QDir::homePath() : filename));
dialog.setMimeTypeFilters(QStringList() << "application/x-opencolorio-configuration");
filename = dialog.filename();
QFile f(filename);
if (f.exists()) {
m_txtConfigurationPath->setText(filename);
writeControls();
resetOcioConfiguration();
}
}
void LutDockerDock::resetOcioConfiguration()
{
KisConfig cfg(true);
KisOcioConfiguration ocioOptions = cfg.ocioConfiguration();
m_ocioConfig.reset();
try {
if (ocioOptions.mode == KisOcioConfiguration::INTERNAL) {
m_ocioConfig = defaultRawProfile();
} else if (ocioOptions.mode == KisOcioConfiguration::OCIO_ENVIRONMENT) {
m_ocioConfig = OCIO::Config::CreateFromEnv();
}
else if (ocioOptions.mode == KisOcioConfiguration::OCIO_CONFIG) {
QString configFile = ocioOptions.configurationPath;
if (QFile::exists(configFile)) {
m_ocioConfig = OCIO::Config::CreateFromFile(configFile.toUtf8());
} else {
m_ocioConfig = defaultRawProfile();
}
}
if (m_ocioConfig) {
OCIO::SetCurrentConfig(m_ocioConfig);
}
}
catch (OCIO::Exception &exception) {
dbgKrita << "OpenColorIO Error:" << exception.what() << "Cannot create the LUT docker";
}
if (m_ocioConfig) {
refillControls();
}
}
void LutDockerDock::refillControls()
{
if (!m_canvas) return;
if (!m_canvas->viewManager()) return;
if (!m_canvas->viewManager()->canvasResourceProvider()) return;
if (!m_canvas->viewManager()->image()) return;
KIS_ASSERT_RECOVER_RETURN(m_ocioConfig);
KisConfig cfg(true);
KisOcioConfiguration ocioOptions = cfg.ocioConfiguration();
{ // Color Management Mode
KisSignalsBlocker modeBlocker(m_colorManagement);
m_colorManagement->setCurrentIndex((int) ocioOptions.mode);
}
{ // Exposure
KisSignalsBlocker exposureBlocker(m_exposureDoubleWidget);
m_exposureDoubleWidget->setValue(m_canvas->viewManager()->canvasResourceProvider()->HDRExposure());
}
{ // Gamma
KisSignalsBlocker gammaBlocker(m_gammaDoubleWidget);
m_gammaDoubleWidget->setValue(m_canvas->viewManager()->canvasResourceProvider()->HDRGamma());
}
{ // Components
const KoColorSpace *cs = m_canvas->viewManager()->image()->colorSpace();
QStringList itemsList;
itemsList << i18n("Luminance");
itemsList << i18n("All Channels");
Q_FOREACH (KoChannelInfo *channel, KoChannelInfo::displayOrderSorted(cs->channels())) {
itemsList << channel->name();
}
if (m_cmbComponents->originalTexts() != itemsList) {
KisSignalsBlocker componentsBlocker(m_cmbComponents);
m_cmbComponents->resetOriginalTexts(itemsList);
m_cmbComponents->setCurrentIndex(1); // All Channels...
}
}
{ // Input Color Space
QStringList itemsList;
int numOcioColorSpaces = m_ocioConfig->getNumColorSpaces();
for(int i = 0; i < numOcioColorSpaces; ++i) {
const char *cs = m_ocioConfig->getColorSpaceNameByIndex(i);
OCIO::ConstColorSpaceRcPtr colorSpace = m_ocioConfig->getColorSpace(cs);
itemsList << QString::fromUtf8(colorSpace->getName());
}
KisSignalsBlocker inputCSBlocker(m_cmbInputColorSpace);
if (itemsList != m_cmbInputColorSpace->originalTexts()) {
m_cmbInputColorSpace->resetOriginalTexts(itemsList);
}
m_cmbInputColorSpace->setCurrent(ocioOptions.inputColorSpace);
}
{ // Display Device
QStringList itemsList;
int numDisplays = m_ocioConfig->getNumDisplays();
for (int i = 0; i < numDisplays; ++i) {
itemsList << QString::fromUtf8(m_ocioConfig->getDisplay(i));
}
KisSignalsBlocker displayDeviceLocker(m_cmbDisplayDevice);
if (itemsList != m_cmbDisplayDevice->originalTexts()) {
m_cmbDisplayDevice->resetOriginalTexts(itemsList);
}
m_cmbDisplayDevice->setCurrent(ocioOptions.displayDevice);
}
{ // Lock Current Color
KisSignalsBlocker locker(m_btnConvertCurrentColor);
m_btnConvertCurrentColor->setChecked(cfg.ocioLockColorVisualRepresentation());
}
refillViewCombobox();
{
QStringList itemsList;
int numLooks = m_ocioConfig->getNumLooks();
for (int k = 0; k < numLooks; k++) {
itemsList << QString::fromUtf8(m_ocioConfig->getLookNameByIndex(k));
}
itemsList << i18nc("Item to indicate no look transform being selected","None");
KisSignalsBlocker LookComboLocker(m_cmbLook);
if (itemsList != m_cmbLook->originalTexts()) {
m_cmbLook->resetOriginalTexts(itemsList);
}
m_cmbLook->setCurrent(ocioOptions.look);
}
updateDisplaySettings();
}
void LutDockerDock::refillViewCombobox()
{
KisSignalsBlocker viewComboLocker(m_cmbView);
m_cmbView->clear();
if (!m_canvas || !m_ocioConfig) return;
const char *display = m_ocioConfig->getDisplay(m_cmbDisplayDevice->currentIndex());
int numViews = m_ocioConfig->getNumViews(display);
for (int j = 0; j < numViews; ++j) {
m_cmbView->addSqueezedItem(QString::fromUtf8(m_ocioConfig->getView(display, j)));
}
KisConfig cfg(true);
KisOcioConfiguration ocioOptions = cfg.ocioConfiguration();
m_cmbView->setCurrent(ocioOptions.displayView);
}
void LutDockerDock::selectLut()
{
QString filename = m_txtLut->text();
KoFileDialog dialog(this, KoFileDialog::OpenFile, "lutdocker");
dialog.setCaption(i18n("Select LUT file"));
dialog.setDefaultDir(QDir::cleanPath(filename));
dialog.setMimeTypeFilters(QStringList() << "application/octet-stream", "application/octet-stream");
filename = dialog.filename();
QFile f(filename);
if (f.exists() && filename != m_txtLut->text()) {
m_txtLut->setText(filename);
writeControls();
updateDisplaySettings();
}
}
void LutDockerDock::clearLut()
{
m_txtLut->clear();
updateDisplaySettings();
}
diff --git a/plugins/dockers/presethistory/presethistory_dock.cpp b/plugins/dockers/presethistory/presethistory_dock.cpp
index 5a80109735..d01e9335ff 100644
--- a/plugins/dockers/presethistory/presethistory_dock.cpp
+++ b/plugins/dockers/presethistory/presethistory_dock.cpp
@@ -1,151 +1,151 @@
/*
* 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; version 2 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "presethistory_dock.h"
#include <QHBoxLayout>
#include <QPushButton>
#include <QListWidget>
#include <QImage>
#include <klocalizedstring.h>
#include <KoCanvasResourceProvider.h>
#include <KoCanvasBase.h>
#include "kis_config.h"
#include "kis_canvas2.h"
#include "KisViewManager.h"
#include "kis_paintop_box.h"
#include "kis_paintop_presets_chooser_popup.h"
#include "kis_canvas_resource_provider.h"
#include "KisResourceServerProvider.h"
#include <KisKineticScroller.h>
#include <brushengine/kis_paintop_preset.h>
#include <kis_types.h>
#define ICON_SIZE 48
PresetHistoryDock::PresetHistoryDock( )
: QDockWidget(i18n("Brush Preset History"))
, m_canvas(0)
, m_block(false)
, m_initialized(false)
{
m_presetHistory = new QListWidget(this);
m_presetHistory->setIconSize(QSize(ICON_SIZE, ICON_SIZE));
m_presetHistory->setDragEnabled(false);
m_presetHistory->setSelectionBehavior(QAbstractItemView::SelectRows);
m_presetHistory->setSelectionMode(QAbstractItemView::SingleSelection);
m_presetHistory->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
setWidget(m_presetHistory);
QScroller* scroller = KisKineticScroller::createPreconfiguredScroller(m_presetHistory);
if( scroller ) {
connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State)));
}
connect(m_presetHistory, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(presetSelected(QListWidgetItem*)));
}
void PresetHistoryDock::setCanvas(KoCanvasBase * canvas)
{
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
disconnect(m_canvas->resourceManager());
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (!m_canvas || !m_canvas->viewManager() || !m_canvas->resourceManager()) return;
connect(m_canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(canvasResourceChanged(int,QVariant)));
if (!m_initialized) {
KisConfig cfg(true);
QStringList presetHistory = cfg.readEntry<QString>("presethistory", "").split(",", QString::SkipEmptyParts);
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
Q_FOREACH (const QString &p, presetHistory) {
KisPaintOpPresetSP preset = rserver->resourceByName(p);
addPreset(preset);
}
m_initialized = true;
}
}
void PresetHistoryDock::unsetCanvas()
{
m_canvas = 0;
setEnabled(false);
QStringList presetHistory;
for(int i = m_presetHistory->count() -1; i >=0; --i) {
QListWidgetItem *item = m_presetHistory->item(i);
QVariant v = item->data(Qt::UserRole);
KisPaintOpPresetSP preset = v.value<KisPaintOpPresetSP>();
presetHistory << preset->name();
}
KisConfig cfg(false);
cfg.writeEntry("presethistory", presetHistory.join(","));
}
void PresetHistoryDock::presetSelected(QListWidgetItem *item)
{
if (item) {
QVariant v = item->data(Qt::UserRole);
KisPaintOpPresetSP preset = v.value<KisPaintOpPresetSP>();
m_block = true;
m_canvas->viewManager()->paintOpBox()->resourceSelected(preset.data());
m_block = false;
}
}
void PresetHistoryDock::canvasResourceChanged(int key, const QVariant& /*v*/)
{
if (m_block) return;
if (m_canvas && key == KisCanvasResourceProvider::CurrentPaintOpPreset) {
KisPaintOpPresetSP preset = m_canvas->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
if (preset) {
for (int i = 0; i < m_presetHistory->count(); ++i) {
if (preset->name() == m_presetHistory->item(i)->text()) {
m_presetHistory->setCurrentRow(i);
return;
}
}
addPreset(preset);
}
}
}
void PresetHistoryDock::addPreset(KisPaintOpPresetSP preset)
{
if (preset) {
QListWidgetItem *item = new QListWidgetItem(QPixmap::fromImage(preset->image()), preset->name());
QVariant v = QVariant::fromValue<KisPaintOpPresetSP>(preset);
item->setData(Qt::UserRole, v);
m_presetHistory->insertItem(0, item);
m_presetHistory->setCurrentRow(0);
if (m_presetHistory->count() > 10) {
- m_presetHistory->takeItem(10);
+ delete m_presetHistory->takeItem(10);
}
}
}
diff --git a/plugins/dockers/touchdocker/TouchDockerDock.cpp b/plugins/dockers/touchdocker/TouchDockerDock.cpp
index c81463d8a5..8502a2c08a 100644
--- a/plugins/dockers/touchdocker/TouchDockerDock.cpp
+++ b/plugins/dockers/touchdocker/TouchDockerDock.cpp
@@ -1,429 +1,440 @@
/*
* 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 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 "TouchDockerDock.h"
#include <QtQuickWidgets/QQuickWidget>
#include <QQmlEngine>
#include <QQmlContext>
#include <QAction>
#include <QUrl>
#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 <KoCanvasController.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 <Theme.h>
#include <Settings.h>
#include <DocumentManager.h>
#include <KisSketchView.h>
#include <QVersionNumber>
namespace
{
bool shouldSetAcceptTouchEvents()
{
// See https://bugreports.qt.io/browse/QTBUG-66718
static QVersionNumber qtVersion = QVersionNumber::fromString(qVersion());
static bool retval = qtVersion > QVersionNumber(5, 9, 3) && qtVersion.normalized() != QVersionNumber(5, 10);
return retval;
}
} // namespace
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(true).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);
if (shouldSetAcceptTouchEvents()) {
m_quickWidget->setAttribute(Qt::WA_AcceptTouchEvents);
}
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);
+ if(m_canvas->viewManager()->document()->url().isEmpty()) {
+ showFileSaveAsDialog();
+ } else {
+ 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;
}
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;
}
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;
}
}
}
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);
+
+ QAction *action = m_canvas->viewManager()->actionManager()->actionByName(id);
+ if (!action) {
+ return m_canvas->canvasController()->actionCollection()->action(id);
+ }
+
+ return action;
}
return 0;
}
void TouchDockerDock::showFileOpenDialog()
{
if (!d->openDialog) {
d->openDialog = createDialog("qrc:/opendialog.qml");
}
d->openDialog->exec();
}
void TouchDockerDock::showFileSaveAsDialog()
{
if (!d->saveAsDialog) {
d->saveAsDialog = createDialog("qrc:/saveasdialog.qml");
}
d->saveAsDialog->exec();
}
void TouchDockerDock::changeEvent(QEvent *event)
{
if (event->type() == QEvent::PaletteChange) {
m_quickWidget->setSource(QUrl("qrc:/touchstrip.qml"));
event->accept();
} else {
event->ignore();
}
}
void TouchDockerDock::tabletEvent(QTabletEvent *event)
{
#ifdef Q_OS_WIN
/**
* On Windows (only in WinInk mode), unless we accept the tablet event,
* OS will start windows gestures, like click+hold for right click.
* It will block any mouse events generation.
*
* In our own (hacky) implementation, if we accept the event, we block
* the gesture, but still generate a fake mouse event.
*/
event->accept();
#else
QDockWidget::tabletEvent(event);
#endif
}
KoDialog *TouchDockerDock::createDialog(const QString qml)
{
KoDialog *dlg = new KoDialog(this);
dlg->setButtons(KoDialog::None);
QQuickWidget *quickWidget = new QQuickWidget(this);
if (shouldSetAcceptTouchEvents()) {
quickWidget->setAttribute(Qt::WA_AcceptTouchEvents);
}
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/animationrenderer/AnimationRenderer.cpp b/plugins/extensions/animationrenderer/AnimationRenderer.cpp
index c053c3c537..b8229a2ce4 100644
--- a/plugins/extensions/animationrenderer/AnimationRenderer.cpp
+++ b/plugins/extensions/animationrenderer/AnimationRenderer.cpp
@@ -1,206 +1,208 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "AnimationRenderer.h"
#include <QMessageBox>
#include <klocalizedstring.h>
#include <kpluginfactory.h>
#include <kis_image.h>
#include <KisViewManager.h>
#include <KoUpdater.h>
#include <kis_node_manager.h>
#include <kis_image_manager.h>
#include <kis_action.h>
#include <kis_image_animation_interface.h>
#include <kis_properties_configuration.h>
#include <kis_config.h>
#include <KisDocument.h>
#include <KisMimeDatabase.h>
#include <kis_time_range.h>
#include <KisImportExportManager.h>
#include <KisImportExportErrorCode.h>
#include "DlgAnimationRenderer.h"
#include <dialogs/KisAsyncAnimationFramesSaveDialog.h>
#include "video_saver.h"
#include "KisAnimationRenderingOptions.h"
K_PLUGIN_FACTORY_WITH_JSON(AnimaterionRendererFactory, "kritaanimationrenderer.json", registerPlugin<AnimaterionRenderer>();)
AnimaterionRenderer::AnimaterionRenderer(QObject *parent, const QVariantList &)
: KisActionPlugin(parent)
{
// Shows the big dialog
KisAction *action = createAction("render_animation");
action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION);
connect(action, SIGNAL(triggered()), this, SLOT(slotRenderAnimation()));
// Re-renders the image sequence as defined in the last render
action = createAction("render_animation_again");
action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION);
connect(action, SIGNAL(triggered()), this, SLOT(slotRenderSequenceAgain()));
}
AnimaterionRenderer::~AnimaterionRenderer()
{
}
void AnimaterionRenderer::slotRenderAnimation()
{
KisImageWSP image = viewManager()->image();
if (!image) return;
if (!image->animationInterface()->hasAnimation()) return;
KisDocument *doc = viewManager()->document();
DlgAnimationRenderer dlgAnimationRenderer(doc, viewManager()->mainWindow());
dlgAnimationRenderer.setCaption(i18n("Render Animation"));
if (dlgAnimationRenderer.exec() == QDialog::Accepted) {
KisAnimationRenderingOptions encoderOptions = dlgAnimationRenderer.getEncoderOptions();
renderAnimationImpl(doc, encoderOptions);
}
}
void AnimaterionRenderer::slotRenderSequenceAgain()
{
KisImageWSP image = viewManager()->image();
if (!image) return;
if (!image->animationInterface()->hasAnimation()) return;
KisDocument *doc = viewManager()->document();
KisConfig cfg(true);
KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT");
KisAnimationRenderingOptions encoderOptions;
encoderOptions.fromProperties(settings);
renderAnimationImpl(doc, encoderOptions);
}
void AnimaterionRenderer::renderAnimationImpl(KisDocument *doc, KisAnimationRenderingOptions encoderOptions)
{
const QString frameMimeType = encoderOptions.frameMimeType;
const QString framesDirectory = encoderOptions.resolveAbsoluteFramesDirectory();
const QString extension = KisMimeDatabase::suffixesForMimeType(frameMimeType).first();
const QString baseFileName = QString("%1/%2.%3").arg(framesDirectory)
.arg(encoderOptions.basename)
.arg(extension);
/**
* The dialog should ensure that the size of the video is even
*/
KIS_SAFE_ASSERT_RECOVER(
!((encoderOptions.width & 0x1 || encoderOptions.height & 0x1)
&& (encoderOptions.videoMimeType == "video/mp4" ||
- encoderOptions.videoMimeType == "video/x-matroska"))) {
+ encoderOptions.videoMimeType == "video/x-matroska") && !(encoderOptions.renderMode() == encoderOptions.RENDER_FRAMES_ONLY))) {
encoderOptions.width = encoderOptions.width + (encoderOptions.width & 0x1);
encoderOptions.height = encoderOptions.height + (encoderOptions.height & 0x1);
}
const QSize scaledSize =
doc->image()->bounds().size().scaled(
encoderOptions.width, encoderOptions.height,
Qt::KeepAspectRatio);
+
if ((scaledSize.width() & 0x1 || scaledSize.height() & 0x1)
&& (encoderOptions.videoMimeType == "video/mp4" ||
- encoderOptions.videoMimeType == "video/x-matroska")) {
+ encoderOptions.videoMimeType == "video/x-matroska") && !(encoderOptions.renderMode() == encoderOptions.RENDER_FRAMES_ONLY)) {
QString m = "Mastroska (.mkv)";
if (encoderOptions.videoMimeType == "video/mp4") {
m = "Mpeg4 (.mp4)";
}
qWarning() << m <<"requires width and height to be even, resize and try again!";
doc->setErrorMessage(i18n("%1 requires width and height to be even numbers. Please resize or crop the image before exporting.", m));
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage()));
return;
}
+
const bool batchMode = false; // TODO: fetch correctly!
KisAsyncAnimationFramesSaveDialog exporter(doc->image(),
KisTimeRange::fromTime(encoderOptions.firstFrame,
encoderOptions.lastFrame),
baseFileName,
encoderOptions.sequenceStart,
encoderOptions.frameExportConfig);
exporter.setBatchMode(batchMode);
KisAsyncAnimationFramesSaveDialog::Result result =
exporter.regenerateRange(viewManager()->mainWindow()->viewManager());
// the folder could have been read-only or something else could happen
if (encoderOptions.shouldEncodeVideo &&
result == KisAsyncAnimationFramesSaveDialog::RenderComplete) {
const QString savedFilesMask = exporter.savedFilesMask();
const QString resultFile = encoderOptions.resolveAbsoluteVideoFilePath();
KIS_SAFE_ASSERT_RECOVER_NOOP(QFileInfo(resultFile).isAbsolute());
{
const QFileInfo info(resultFile);
QDir dir(info.absolutePath());
if (!dir.exists()) {
dir.mkpath(info.absolutePath());
}
KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists());
}
KisImportExportErrorCode res;
QFile fi(resultFile);
if (!fi.open(QIODevice::WriteOnly)) {
qWarning() << "Could not open" << fi.fileName() << "for writing!";
res = KisImportExportErrorCannotWrite(fi.error());
} else {
fi.close();
}
QScopedPointer<VideoSaver> encoder(new VideoSaver(doc, batchMode));
res = encoder->convert(doc, savedFilesMask, encoderOptions, batchMode);
if (!res.isOk()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", res.errorMessage()));
}
if (encoderOptions.shouldDeleteSequence) {
QDir d(framesDirectory);
QStringList sequenceFiles = d.entryList(QStringList() << encoderOptions.basename + "*." + extension, QDir::Files);
Q_FOREACH(const QString &f, sequenceFiles) {
d.remove(f);
}
}
} else if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) {
viewManager()->mainWindow()->viewManager()->showFloatingMessage(i18n("Failed to render animation frames!"), QIcon());
}
}
#include "AnimationRenderer.moc"
diff --git a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp
index a48d47335e..b76b16303b 100644
--- a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp
+++ b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp
@@ -1,594 +1,594 @@
/*
* 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 General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "DlgAnimationRenderer.h"
#include <QStandardPaths>
#include <QPluginLoader>
#include <QJsonObject>
#include <QMessageBox>
#include <QStringList>
#include <QProcess>
#include <klocalizedstring.h>
#include <kpluginfactory.h>
#include <KoResourcePaths.h>
#include <kis_properties_configuration.h>
#include <kis_debug.h>
#include <KisMimeDatabase.h>
#include <KoJsonTrader.h>
#include <KisImportExportFilter.h>
#include <kis_image.h>
#include <kis_image_animation_interface.h>
#include <kis_time_range.h>
#include <KisImportExportManager.h>
#include <kis_config_widget.h>
#include <KisDocument.h>
#include <QHBoxLayout>
#include <kis_config.h>
#include <kis_file_name_requester.h>
#include <KoDialog.h>
#include "kis_slider_spin_box.h"
#include "kis_acyclic_signal_connector.h"
#include "video_saver.h"
#include "KisAnimationRenderingOptions.h"
#include "video_export_options_dialog.h"
DlgAnimationRenderer::DlgAnimationRenderer(KisDocument *doc, QWidget *parent)
: KoDialog(parent)
, m_image(doc->image())
, m_doc(doc)
{
KisConfig cfg(true);
setCaption(i18n("Render Animation"));
setButtons(Ok | Cancel);
setDefaultButton(Ok);
m_page = new WdgAnimationRenderer(this);
m_page->layout()->setMargin(0);
m_page->dirRequester->setMode(KoFileDialog::OpenDirectory);
m_page->intStart->setMinimum(0);
m_page->intStart->setMaximum(doc->image()->animationInterface()->fullClipRange().end());
m_page->intStart->setValue(doc->image()->animationInterface()->playbackRange().start());
m_page->intEnd->setMinimum(doc->image()->animationInterface()->fullClipRange().start());
// animators sometimes want to export after end frame
//m_page->intEnd->setMaximum(doc->image()->animationInterface()->fullClipRange().end());
m_page->intEnd->setValue(doc->image()->animationInterface()->playbackRange().end());
m_page->intHeight->setMinimum(1);
- m_page->intHeight->setMaximum(10000);
+ m_page->intHeight->setMaximum(100000);
m_page->intHeight->setValue(doc->image()->height());
m_page->intWidth->setMinimum(1);
- m_page->intWidth->setMaximum(10000);
+ m_page->intWidth->setMaximum(100000);
m_page->intWidth->setValue(doc->image()->width());
// try to lock the width and height being updated
KisAcyclicSignalConnector *constrainsConnector = new KisAcyclicSignalConnector(this);
constrainsConnector->createCoordinatedConnector()->connectBackwardInt(m_page->intWidth, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsWidth(int)));
constrainsConnector->createCoordinatedConnector()->connectForwardInt(m_page->intHeight, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsHeight(int)));
m_page->intFramesPerSecond->setValue(doc->image()->animationInterface()->framerate());
QFileInfo audioFileInfo(doc->image()->animationInterface()->audioChannelFileName());
const bool hasAudio = audioFileInfo.exists();
m_page->chkIncludeAudio->setEnabled(hasAudio);
m_page->chkIncludeAudio->setChecked(hasAudio && !doc->image()->animationInterface()->isAudioMuted());
QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
mimes.sort();
Q_FOREACH(const QString &mime, mimes) {
QString description = KisMimeDatabase::descriptionForMimeType(mime);
if (description.isEmpty()) {
description = mime;
}
m_page->cmbMimetype->addItem(description, mime);
if (mime == "image/png") {
m_page->cmbMimetype->setCurrentIndex(m_page->cmbMimetype->count() - 1);
}
}
setMainWidget(m_page);
QVector<QString> supportedMimeType;
supportedMimeType << "video/x-matroska";
supportedMimeType << "image/gif";
supportedMimeType << "video/ogg";
supportedMimeType << "video/mp4";
Q_FOREACH (const QString &mime, supportedMimeType) {
QString description = KisMimeDatabase::descriptionForMimeType(mime);
if (description.isEmpty()) {
description = mime;
}
m_page->cmbRenderType->addItem(description, mime);
}
m_page->videoFilename->setMode(KoFileDialog::SaveFile);
connect(m_page->bnExportOptions, SIGNAL(clicked()), this, SLOT(sequenceMimeTypeOptionsClicked()));
connect(m_page->bnRenderOptions, SIGNAL(clicked()), this, SLOT(selectRenderOptions()));
m_page->ffmpegLocation->setMode(KoFileDialog::OpenFile);
m_page->cmbRenderType->setCurrentIndex(cfg.readEntry<int>("AnimationRenderer/render_type", 0));
connect(m_page->shouldExportOnlyImageSequence, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged()));
connect(m_page->shouldExportOnlyVideo, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged()));
connect(m_page->shouldExportAll, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged()));
connect(m_page->intFramesPerSecond, SIGNAL(valueChanged(int)), SLOT(frameRateChanged(int)));
// connect and cold init
connect(m_page->cmbRenderType, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRenderType(int)));
selectRenderType(m_page->cmbRenderType->currentIndex());
resize(m_page->sizeHint());
connect(this, SIGNAL(accepted()), SLOT(slotDialogAccepted()));
{
KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT");
KisAnimationRenderingOptions options;
options.fromProperties(settings);
loadAnimationOptions(options);
}
}
DlgAnimationRenderer::~DlgAnimationRenderer()
{
delete m_page;
}
void DlgAnimationRenderer::getDefaultVideoEncoderOptions(const QString &mimeType,
KisPropertiesConfigurationSP cfg,
QString *customFFMpegOptionsString,
bool *forceHDRVideo)
{
const VideoExportOptionsDialog::ContainerType containerType =
mimeType == "video/ogg" ?
VideoExportOptionsDialog::OGV :
VideoExportOptionsDialog::DEFAULT;
QScopedPointer<VideoExportOptionsDialog> encoderConfigWidget(
new VideoExportOptionsDialog(containerType, 0));
// we always enable HDR, letting the user to force it
encoderConfigWidget->setSupportsHDR(true);
encoderConfigWidget->setConfiguration(cfg);
*customFFMpegOptionsString = encoderConfigWidget->customUserOptionsString();
*forceHDRVideo = encoderConfigWidget->forceHDRModeForFrames();
}
void DlgAnimationRenderer::loadAnimationOptions(const KisAnimationRenderingOptions &options)
{
const QString documentPath = m_doc->localFilePath();
m_page->txtBasename->setText(options.basename);
if (!options.lastDocuemntPath.isEmpty() &&
options.lastDocuemntPath == documentPath) {
m_page->intStart->setValue(options.firstFrame);
m_page->intEnd->setValue(options.lastFrame);
m_page->sequenceStart->setValue(options.sequenceStart);
m_page->intWidth->setValue(options.width);
m_page->intHeight->setValue(options.height);
m_page->intFramesPerSecond->setValue(options.frameRate);
m_page->videoFilename->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath));
m_page->videoFilename->setFileName(options.videoFileName);
m_page->dirRequester->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath));
m_page->dirRequester->setFileName(options.directory);
} else {
m_page->intStart->setValue(m_image->animationInterface()->playbackRange().start());
m_page->intEnd->setValue(m_image->animationInterface()->playbackRange().end());
m_page->sequenceStart->setValue(m_image->animationInterface()->playbackRange().start());
m_page->intWidth->setValue(m_image->width());
m_page->intHeight->setValue(m_image->height());
m_page->intFramesPerSecond->setValue(m_image->animationInterface()->framerate());
m_page->videoFilename->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath));
m_page->videoFilename->setFileName(defaultVideoFileName(m_doc, options.videoMimeType));
m_page->dirRequester->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath));
m_page->dirRequester->setFileName(options.directory);
}
for (int i = 0; i < m_page->cmbMimetype->count(); ++i) {
if (m_page->cmbMimetype->itemData(i).toString() == options.frameMimeType) {
m_page->cmbMimetype->setCurrentIndex(i);
break;
}
}
for (int i = 0; i < m_page->cmbRenderType->count(); ++i) {
if (m_page->cmbRenderType->itemData(i).toString() == options.videoMimeType) {
m_page->cmbRenderType->setCurrentIndex(i);
break;
}
}
m_page->chkIncludeAudio->setChecked(options.includeAudio);
if (options.shouldDeleteSequence) {
KIS_SAFE_ASSERT_RECOVER_NOOP(options.shouldEncodeVideo);
m_page->shouldExportOnlyVideo->setChecked(true);
} else if (!options.shouldEncodeVideo) {
KIS_SAFE_ASSERT_RECOVER_NOOP(!options.shouldDeleteSequence);
m_page->shouldExportOnlyImageSequence->setChecked(true);
} else {
m_page->shouldExportAll->setChecked(true); // export to both
}
{
KisConfig cfg(true);
KisPropertiesConfigurationSP settings = cfg.exportConfiguration("VIDEO_ENCODER");
getDefaultVideoEncoderOptions(options.videoMimeType, settings,
&m_customFFMpegOptionsString,
&m_forceHDRVideo);
}
m_page->ffmpegLocation->setStartDir(QFileInfo(m_doc->localFilePath()).path());
m_page->ffmpegLocation->setFileName(findFFMpeg(options.ffmpegPath));
}
QString DlgAnimationRenderer::defaultVideoFileName(KisDocument *doc, const QString &mimeType)
{
const QString docFileName = !doc->localFilePath().isEmpty() ?
doc->localFilePath() : i18n("Untitled");
return
QString("%1.%2")
.arg(QFileInfo(docFileName).completeBaseName())
.arg(KisMimeDatabase::suffixesForMimeType(mimeType).first());
}
void DlgAnimationRenderer::selectRenderType(int index)
{
const QString mimeType = m_page->cmbRenderType->itemData(index).toString();
m_page->bnRenderOptions->setEnabled(mimeType != "image/gif");
m_page->lblGifWarning->setVisible((mimeType == "image/gif" && m_page->intFramesPerSecond->value() > 50));
QString videoFileName = defaultVideoFileName(m_doc, mimeType);
if (!m_page->videoFilename->fileName().isEmpty()) {
const QFileInfo info = QFileInfo(m_page->videoFilename->fileName());
const QString baseName = info.completeBaseName();
const QString path = info.path();
videoFileName =
QString("%1%2%3.%4").arg(path).arg(QDir::separator()).arg(baseName).arg(KisMimeDatabase::suffixesForMimeType(mimeType).first());
}
m_page->videoFilename->setMimeTypeFilters(QStringList() << mimeType, mimeType);
m_page->videoFilename->setFileName(videoFileName);
}
void DlgAnimationRenderer::selectRenderOptions()
{
const int index = m_page->cmbRenderType->currentIndex();
const QString mimetype = m_page->cmbRenderType->itemData(index).toString();
const VideoExportOptionsDialog::ContainerType containerType =
mimetype == "video/ogg" ?
VideoExportOptionsDialog::OGV :
VideoExportOptionsDialog::DEFAULT;
VideoExportOptionsDialog *encoderConfigWidget =
new VideoExportOptionsDialog(containerType, this);
// we always enable HDR, letting the user to force it
encoderConfigWidget->setSupportsHDR(true);
{
KisConfig cfg(true);
KisPropertiesConfigurationSP settings = cfg.exportConfiguration("VIDEO_ENCODER");
encoderConfigWidget->setConfiguration(settings);
}
KoDialog dlg(this);
dlg.setMainWidget(encoderConfigWidget);
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
if (dlg.exec() == QDialog::Accepted) {
KisConfig cfg(false);
cfg.setExportConfiguration("VIDEO_ENCODER", encoderConfigWidget->configuration());
m_customFFMpegOptionsString = encoderConfigWidget->customUserOptionsString();
m_forceHDRVideo = encoderConfigWidget->forceHDRModeForFrames();
}
dlg.setMainWidget(0);
encoderConfigWidget->deleteLater();
}
void DlgAnimationRenderer::sequenceMimeTypeOptionsClicked()
{
int index = m_page->cmbMimetype->currentIndex();
KisConfigWidget *frameExportConfigWidget = 0;
QString mimetype = m_page->cmbMimetype->itemData(index).toString();
QSharedPointer<KisImportExportFilter> filter(KisImportExportManager::filterForMimeType(mimetype, KisImportExportManager::Export));
if (filter) {
frameExportConfigWidget = filter->createConfigurationWidget(0, KisDocument::nativeFormatMimeType(), mimetype.toLatin1());
if (frameExportConfigWidget) {
KisPropertiesConfigurationSP config = filter->lastSavedConfiguration("", mimetype.toLatin1());
if (config) {
KisImportExportManager::fillStaticExportConfigurationProperties(config, m_image);
}
frameExportConfigWidget->setConfiguration(config);
KoDialog dlg(this);
dlg.setMainWidget(frameExportConfigWidget);
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
if (dlg.exec() == QDialog::Accepted) {
KisConfig cfg(false);
cfg.setExportConfiguration(mimetype, frameExportConfigWidget->configuration());
}
frameExportConfigWidget->hide();
dlg.setMainWidget(0);
frameExportConfigWidget->setParent(0);
frameExportConfigWidget->deleteLater();
}
}
}
inline int roundByTwo(int value) {
return value + (value & 0x1);
}
KisAnimationRenderingOptions DlgAnimationRenderer::getEncoderOptions() const
{
KisAnimationRenderingOptions options;
options.lastDocuemntPath = m_doc->localFilePath();
options.videoMimeType = m_page->cmbRenderType->currentData().toString();
options.frameMimeType = m_page->cmbMimetype->currentData().toString();
options.basename = m_page->txtBasename->text();
options.directory = m_page->dirRequester->fileName();
options.firstFrame = m_page->intStart->value();
options.lastFrame = m_page->intEnd->value();
options.sequenceStart = m_page->sequenceStart->value();
options.shouldEncodeVideo = !m_page->shouldExportOnlyImageSequence->isChecked();
options.shouldDeleteSequence = m_page->shouldExportOnlyVideo->isChecked();
options.includeAudio = m_page->chkIncludeAudio->isChecked();
options.ffmpegPath = m_page->ffmpegLocation->fileName();
options.frameRate = m_page->intFramesPerSecond->value();
if (options.frameRate > 50 && options.videoMimeType == "image/gif") {
options.frameRate = 50;
}
options.width = roundByTwo(m_page->intWidth->value());
options.height = roundByTwo(m_page->intHeight->value());
options.videoFileName = m_page->videoFilename->fileName();
options.customFFMpegOptions = m_customFFMpegOptionsString;
{
KisConfig config(true);
KisPropertiesConfigurationSP cfg = config.exportConfiguration(options.frameMimeType);
if (cfg) {
KisImportExportManager::fillStaticExportConfigurationProperties(cfg, m_image);
}
const bool forceHDR = m_forceHDRVideo && !m_page->shouldExportOnlyImageSequence->isChecked();
if (forceHDR) {
KIS_SAFE_ASSERT_RECOVER_NOOP(options.frameMimeType == "image/png");
cfg->setProperty("forceSRGB", false);
cfg->setProperty("saveAsHDR", true);
}
options.frameExportConfig = cfg;
}
return options;
}
void DlgAnimationRenderer::slotButtonClicked(int button)
{
if (button == KoDialog::Ok && !m_page->shouldExportOnlyImageSequence->isChecked()) {
QString ffmpeg = m_page->ffmpegLocation->fileName();
if (m_page->videoFilename->fileName().isEmpty()) {
QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Please enter a file name to render to."));
return;
}
else if (ffmpeg.isEmpty()) {
QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is unknown. Please install FFmpeg first: Krita cannot render animations without FFmpeg. (<a href=\"https://www.ffmpeg.org\">www.ffmpeg.org</a>)"));
return;
}
else {
QFileInfo fi(ffmpeg);
if (!fi.exists()) {
QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is invalid. Please select the correct location of the FFmpeg executable on your system."));
return;
}
}
}
KoDialog::slotButtonClicked(button);
}
void DlgAnimationRenderer::slotDialogAccepted()
{
KisConfig cfg(false);
KisAnimationRenderingOptions options = getEncoderOptions();
cfg.setExportConfiguration("ANIMATION_EXPORT", options.toProperties());
}
QString DlgAnimationRenderer::findFFMpeg(const QString &customLocation)
{
QString result;
QStringList proposedPaths;
if (!customLocation.isEmpty()) {
proposedPaths << customLocation;
proposedPaths << customLocation + QDir::separator() + "ffmpeg";
}
proposedPaths << KoResourcePaths::getApplicationRoot() +
QDir::separator() + "bin" + QDir::separator() + "ffmpeg";
#ifndef Q_OS_WIN
proposedPaths << QDir::homePath() + "/bin/ffmpeg";
proposedPaths << "/usr/bin/ffmpeg";
proposedPaths << "/usr/local/bin/ffmpeg";
#endif
Q_FOREACH (QString path, proposedPaths) {
if (path.isEmpty()) continue;
#ifdef Q_OS_WIN
path = QDir::toNativeSeparators(QDir::cleanPath(path));
if (path.endsWith(QDir::separator())) {
continue;
}
if (!path.endsWith(".exe")) {
if (!QFile::exists(path)) {
path += ".exe";
if (!QFile::exists(path)) {
continue;
}
}
}
#endif
QProcess testProcess;
testProcess.start(path, QStringList() << "-version");
if (testProcess.waitForStarted(1000)) {
testProcess.waitForFinished(1000);
}
const bool successfulStart =
testProcess.state() == QProcess::NotRunning &&
testProcess.error() == QProcess::UnknownError;
if (successfulStart) {
result = path;
break;
}
}
return result;
}
void DlgAnimationRenderer::slotExportTypeChanged()
{
KisConfig cfg(false);
bool willEncodeVideo =
m_page->shouldExportAll->isChecked() || m_page->shouldExportOnlyVideo->isChecked();
// if a video format needs to be outputted
if (willEncodeVideo) {
// videos always uses PNG for creating video, so disable the ability to change the format
m_page->cmbMimetype->setEnabled(false);
for (int i = 0; i < m_page->cmbMimetype->count(); ++i) {
if (m_page->cmbMimetype->itemData(i).toString() == "image/png") {
m_page->cmbMimetype->setCurrentIndex(i);
break;
}
}
}
m_page->intWidth->setVisible(willEncodeVideo);
m_page->intHeight->setVisible(willEncodeVideo);
m_page->intFramesPerSecond->setVisible(willEncodeVideo);
m_page->fpsLabel->setVisible(willEncodeVideo);
m_page->lblWidth->setVisible(willEncodeVideo);
m_page->lblHeight->setVisible(willEncodeVideo);
// if only exporting video
if (m_page->shouldExportOnlyVideo->isChecked()) {
m_page->cmbMimetype->setEnabled(false); // allow to change image format
m_page->imageSequenceOptionsGroup->setVisible(false);
m_page->videoOptionsGroup->setVisible(false); //shrinks the horizontal space temporarily to help resize() work
m_page->videoOptionsGroup->setVisible(true);
}
// if only an image sequence needs to be output
if (m_page->shouldExportOnlyImageSequence->isChecked()) {
m_page->cmbMimetype->setEnabled(true); // allow to change image format
m_page->videoOptionsGroup->setVisible(false);
m_page->imageSequenceOptionsGroup->setVisible(false);
m_page->imageSequenceOptionsGroup->setVisible(true);
}
// show all options
if (m_page->shouldExportAll->isChecked() ) {
m_page->imageSequenceOptionsGroup->setVisible(true);
m_page->videoOptionsGroup->setVisible(true);
}
// for the resize to work as expected, try to hide elements first before displaying other ones.
// if the widget gets bigger at any point, the resize will use that, even if elements are hidden later to make it smaller
resize(m_page->sizeHint());
}
void DlgAnimationRenderer::frameRateChanged(int framerate)
{
const QString mimeType = m_page->cmbRenderType->itemData(m_page->cmbRenderType->currentIndex()).toString();
m_page->lblGifWarning->setVisible((mimeType == "image/gif" && framerate > 50));
}
void DlgAnimationRenderer::slotLockAspectRatioDimensionsWidth(int width)
{
Q_UNUSED(width);
float aspectRatio = (float)m_image->width() / (float)m_image->height();
// update height here
float newHeight = m_page->intWidth->value() / aspectRatio ;
m_page->intHeight->setValue(newHeight);
}
void DlgAnimationRenderer::slotLockAspectRatioDimensionsHeight(int height)
{
Q_UNUSED(height);
float aspectRatio = (float)m_image->width() / (float)m_image->height();
// update width here
float newWidth = aspectRatio * m_page->intHeight->value();
m_page->intWidth->setValue(newWidth);
}
diff --git a/plugins/extensions/buginfo/CMakeLists.txt b/plugins/extensions/buginfo/CMakeLists.txt
index a102ed6838..a236446476 100644
--- a/plugins/extensions/buginfo/CMakeLists.txt
+++ b/plugins/extensions/buginfo/CMakeLists.txt
@@ -1,10 +1,16 @@
-set(kritabuginfo_SOURCES
- buginfo.cpp
- dlg_buginfo.cpp
+set(kritabuginfo_SOURCES
+ buginfo.cpp
+ dlg_buginfo.cpp
+ DlgSysInfo.cpp
+ DlgKritaLog.cpp
)
ki18n_wrap_ui(kritabuginfo_SOURCES wdg_buginfo.ui )
+
add_library(kritabuginfo MODULE ${kritabuginfo_SOURCES})
+
target_link_libraries(kritabuginfo kritaui)
+
install(TARGETS kritabuginfo DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
+
install( FILES buginfo.xmlgui DESTINATION ${DATA_INSTALL_DIR}/kritaplugins)
diff --git a/plugins/extensions/buginfo/dlg_buginfo.h b/plugins/extensions/buginfo/DlgKritaLog.cpp
similarity index 56%
copy from plugins/extensions/buginfo/dlg_buginfo.h
copy to plugins/extensions/buginfo/DlgKritaLog.cpp
index 619a737b05..7af1b39431 100644
--- a/plugins/extensions/buginfo/dlg_buginfo.h
+++ b/plugins/extensions/buginfo/DlgKritaLog.cpp
@@ -1,47 +1,46 @@
/*
* 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.
*/
-#ifndef DLG_BUGINFO
-#define DLG_BUGINFO
+#include "DlgKritaLog.h"
+#include <QStandardPaths>
-#include <KoDialog.h>
-
-#include "ui_wdg_buginfo.h"
+DlgKritaLog::DlgKritaLog(QWidget *parent)
+ : DlgBugInfo(parent)
+{
+ initialize();
+}
+QString DlgKritaLog::originalFileName()
+{
+ return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log";
+}
-class WdgBugInfo : public QWidget, public Ui::WdgBugInfo
+QString DlgKritaLog::captionText()
{
- Q_OBJECT
+ return i18nc("Caption of the dialog with Krita usage log for bug reports", "Krita Usage Log: please paste this information to the bug report");
+}
-public:
- WdgBugInfo(QWidget *parent) : QWidget(parent) {
- setupUi(this);
- }
-};
+QString DlgKritaLog::replacementWarningText()
+{
+ return "WARNING: The Krita usage log file doesn't exist.";
+}
-class DlgBugInfo: public KoDialog
+DlgKritaLog::~DlgKritaLog()
{
- Q_OBJECT
-public:
- DlgBugInfo(QWidget * parent = 0);
- ~DlgBugInfo() override;
-private:
- WdgBugInfo *m_page;
-};
-#endif // DLG_BUGINFO
+}
diff --git a/libs/flake/tests/KisGamutMaskViewConverterTest.h b/plugins/extensions/buginfo/DlgKritaLog.h
similarity index 54%
rename from libs/flake/tests/KisGamutMaskViewConverterTest.h
rename to plugins/extensions/buginfo/DlgKritaLog.h
index fdde941568..3294891a86 100644
--- a/libs/flake/tests/KisGamutMaskViewConverterTest.h
+++ b/plugins/extensions/buginfo/DlgKritaLog.h
@@ -1,45 +1,49 @@
/*
- * Copyright (c) 2019 Anna Medonosova <anna.medonosova@gmail.com>
+ * 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.
*/
-#ifndef KISGAMUTMASKVIEWCONVERTERTEST_H
-#define KISGAMUTMASKVIEWCONVERTERTEST_H
+#ifndef DLG_KRITA_LOG
+#define DLG_KRITA_LOG
-#include <QObject>
-#include <QTest>
+#include <KoDialog.h>
+#include <dlg_buginfo.h>
-class KisGamutMaskViewConverterTest : public QObject
+class QSettings;
+
+class DlgKritaLog: public DlgBugInfo
{
Q_OBJECT
public:
- explicit KisGamutMaskViewConverterTest(QObject *parent = nullptr);
+ DlgKritaLog(QWidget * parent = 0);
+ ~DlgKritaLog() override;
+
-private Q_SLOTS:
- void testDocumentToViewX();
- void testDocumentToViewX_data();
+ QString defaultNewFileName() override {
+ return "KritaUsageLog.txt";
+ }
- void testDocumentToViewY();
- void testDocumentToViewY_data();
+ QString originalFileName() override;
- void testViewToDocumentX();
- void testViewToDocumentX_data();
+public:
+ QString replacementWarningText() override;
+ QString captionText() override;
- void testViewToDocumentY();
- void testViewToDocumentY_data();
+private:
+ WdgBugInfo *m_page;
};
-#endif // KISGAMUTMASKVIEWCONVERTERTEST_H
+#endif // DLG_BUGINFO
diff --git a/plugins/extensions/buginfo/dlg_buginfo.h b/plugins/extensions/buginfo/DlgSysInfo.cpp
similarity index 56%
copy from plugins/extensions/buginfo/dlg_buginfo.h
copy to plugins/extensions/buginfo/DlgSysInfo.cpp
index 619a737b05..6f51270454 100644
--- a/plugins/extensions/buginfo/dlg_buginfo.h
+++ b/plugins/extensions/buginfo/DlgSysInfo.cpp
@@ -1,47 +1,45 @@
/*
* 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.
*/
-#ifndef DLG_BUGINFO
-#define DLG_BUGINFO
-
-#include <KoDialog.h>
-
-#include "ui_wdg_buginfo.h"
+#include "DlgSysInfo.h"
+#include <QStandardPaths>
+DlgSysInfo::DlgSysInfo(QWidget *parent)
+ : DlgBugInfo(parent)
+{
+ initialize();
+}
-class WdgBugInfo : public QWidget, public Ui::WdgBugInfo
+QString DlgSysInfo::originalFileName()
{
- Q_OBJECT
+ return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita-sysinfo.log";
+}
-public:
- WdgBugInfo(QWidget *parent) : QWidget(parent) {
- setupUi(this);
- }
-};
+QString DlgSysInfo::captionText()
+{
+ return i18nc("Caption of the dialog with system information for bug reports", "Krita System Information: please paste this information to the bug report");
+}
-class DlgBugInfo: public KoDialog
+QString DlgSysInfo::replacementWarningText()
{
- Q_OBJECT
-public:
- DlgBugInfo(QWidget * parent = 0);
- ~DlgBugInfo() override;
-private:
- WdgBugInfo *m_page;
-};
+ return "WARNING: The system information file doesn't exist.";
+}
-#endif // DLG_BUGINFO
+DlgSysInfo::~DlgSysInfo()
+{
+}
diff --git a/plugins/extensions/buginfo/dlg_buginfo.h b/plugins/extensions/buginfo/DlgSysInfo.h
similarity index 68%
copy from plugins/extensions/buginfo/dlg_buginfo.h
copy to plugins/extensions/buginfo/DlgSysInfo.h
index 619a737b05..117d44578c 100644
--- a/plugins/extensions/buginfo/dlg_buginfo.h
+++ b/plugins/extensions/buginfo/DlgSysInfo.h
@@ -1,47 +1,49 @@
/*
* 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.
*/
-#ifndef DLG_BUGINFO
-#define DLG_BUGINFO
+#ifndef DLG_SYSINFO
+#define DLG_SYSINFO
#include <KoDialog.h>
+#include "dlg_buginfo.h"
-#include "ui_wdg_buginfo.h"
+class QSettings;
-class WdgBugInfo : public QWidget, public Ui::WdgBugInfo
+class DlgSysInfo: public DlgBugInfo
{
Q_OBJECT
-
public:
- WdgBugInfo(QWidget *parent) : QWidget(parent) {
- setupUi(this);
+ DlgSysInfo(QWidget * parent = 0);
+ ~DlgSysInfo() override;
+
+ QString defaultNewFileName() override {
+ return "KritaSystemInformation.txt";
}
-};
-class DlgBugInfo: public KoDialog
-{
- Q_OBJECT
+ QString originalFileName() override;
+
public:
- DlgBugInfo(QWidget * parent = 0);
- ~DlgBugInfo() override;
+ QString replacementWarningText() override;
+ QString captionText() override;
+
private:
WdgBugInfo *m_page;
};
-#endif // DLG_BUGINFO
+#endif // DLG_SYSINFO
diff --git a/plugins/extensions/buginfo/buginfo.cpp b/plugins/extensions/buginfo/buginfo.cpp
index c7ec1c84a7..2fe052e2a0 100644
--- a/plugins/extensions/buginfo/buginfo.cpp
+++ b/plugins/extensions/buginfo/buginfo.cpp
@@ -1,51 +1,65 @@
/*
* 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 "buginfo.h"
#include <cmath>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <kis_icon.h>
#include <KisViewManager.h>
#include <kis_action.h>
-#include "dlg_buginfo.h"
+#include "DlgKritaLog.h"
+#include "DlgSysInfo.h"
+
K_PLUGIN_FACTORY_WITH_JSON(BugInfoFactory, "kritabuginfo.json", registerPlugin<BugInfo>();)
+
+
BugInfo::BugInfo(QObject *parent, const QVariantList &)
: KisActionPlugin(parent)
{
- KisAction *action = createAction("buginfo");
- connect(action, SIGNAL(triggered()), this, SLOT(slotBugInfo()));
+ KisAction *actionBug = createAction("buginfo");
+ KisAction *actionSys = createAction("sysinfo");
+ connect(actionBug, SIGNAL(triggered()), this, SLOT(slotKritaLog()));
+ connect(actionSys, SIGNAL(triggered()), this, SLOT(slotSysInfo()));
+
}
BugInfo::~BugInfo()
{
}
-void BugInfo::slotBugInfo()
+void BugInfo::slotKritaLog()
{
- DlgBugInfo dlgBugInfo(viewManager()->mainWindow());
- dlgBugInfo.exec();
+ DlgKritaLog dlgKritaLog(viewManager()->mainWindow());
+ dlgKritaLog.exec();
}
+void BugInfo::slotSysInfo()
+{
+ DlgSysInfo dlgSysInfo(viewManager()->mainWindow());
+ dlgSysInfo.exec();
+}
+
+
#include "buginfo.moc"
diff --git a/plugins/extensions/buginfo/buginfo.h b/plugins/extensions/buginfo/buginfo.h
index 21b24a90dc..48762ca3f2 100644
--- a/plugins/extensions/buginfo/buginfo.h
+++ b/plugins/extensions/buginfo/buginfo.h
@@ -1,40 +1,41 @@
/*
* 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.
*/
#ifndef BUGINFO_H
#define BUGINFO_H
#include <QVariant>
#include <KisActionPlugin.h>
class KUndo2MagicString;
class BugInfo : public KisActionPlugin
{
Q_OBJECT
public:
BugInfo(QObject *parent, const QVariantList &);
~BugInfo() override;
public Q_SLOTS:
- void slotBugInfo();
+ void slotKritaLog();
+ void slotSysInfo();
};
#endif // BUGINFO_H
diff --git a/plugins/extensions/buginfo/buginfo.xmlgui b/plugins/extensions/buginfo/buginfo.xmlgui
index 1002c9d910..d8b769dd4c 100644
--- a/plugins/extensions/buginfo/buginfo.xmlgui
+++ b/plugins/extensions/buginfo/buginfo.xmlgui
@@ -1,8 +1,9 @@
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
-<kpartgui library="kritabuginfo" version="8" translationDomain="krita">
+<kpartgui library="kritabuginfo" version="9" translationDomain="krita">
<MenuBar>
<Menu name="Help">
<Action name="buginfo"/>
+ <Action name="sysinfo"/>
</Menu>
</MenuBar>
</kpartgui>
diff --git a/plugins/extensions/buginfo/dlg_buginfo.cpp b/plugins/extensions/buginfo/dlg_buginfo.cpp
index 2a097360a2..31ecb24643 100644
--- a/plugins/extensions/buginfo/dlg_buginfo.cpp
+++ b/plugins/extensions/buginfo/dlg_buginfo.cpp
@@ -1,118 +1,186 @@
/*
* 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 "dlg_buginfo.h"
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <opengl/kis_opengl.h>
#include <KritaVersionWrapper.h>
#include <QSysInfo>
#include <kis_image_config.h>
#include <QDesktopWidget>
#include <QClipboard>
#include <QThread>
#include <QFile>
#include <QFileInfo>
#include <QSettings>
#include <QStandardPaths>
+#include <KoFileDialog.h>
+#include <QMessageBox>
-#include "kis_document_aware_spin_box_unit_manager.h"
#include <QScreen>
DlgBugInfo::DlgBugInfo(QWidget *parent)
: KoDialog(parent)
{
setCaption(i18n("Please paste this information in your bug report"));
- setButtons(User1 | Ok);
+ setButtons(User1 | User2 | Ok);
setButtonText(User1, i18n("Copy to clipboard"));
+ setButtonText(User2, i18n("Save to file"));
setDefaultButton(Ok);
m_page = new WdgBugInfo(this);
Q_CHECK_PTR(m_page);
setMainWidget(m_page);
- QString info;
-
- const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
- QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
+ connect(this, &KoDialog::user1Clicked, this, [this](){
+ QGuiApplication::clipboard()->setText(m_page->txtBugInfo->toPlainText());
+ m_page->txtBugInfo->selectAll(); // feedback
+ });
- if (!kritarc.value("LogUsage", true).toBool() || !QFileInfo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log").exists()) {
+ connect(this, &KoDialog::user2Clicked, this, &DlgBugInfo::saveToFile);
- // NOTE: This is intentionally not translated!
+}
- // Krita version info
- info.append("Krita");
- info.append("\n Version: ").append(KritaVersionWrapper::versionString(true));
- info.append("\n\n");
+void DlgBugInfo::initialize()
+{
+ initializeText();
+ setCaption(captionText());
+}
- info.append("Qt");
- info.append("\n Version (compiled): ").append(QT_VERSION_STR);
- info.append("\n Version (loaded): ").append(qVersion());
- info.append("\n\n");
+void DlgBugInfo::initializeText()
+{
+ const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
+ QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
- // OS information
- info.append("OS Information");
- info.append("\n Build ABI: ").append(QSysInfo::buildAbi());
- info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture());
- info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture());
- info.append("\n Kernel Type: ").append(QSysInfo::kernelType());
- info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion());
- info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName());
- info.append("\n Product Type: ").append(QSysInfo::productType());
- info.append("\n Product Version: ").append(QSysInfo::productVersion());
- info.append("\n\n");
+ QString info = infoText(kritarc);
- // OpenGL information
- info.append("\n").append(KisOpenGL::getDebugText());
- info.append("\n\n");
- // Hardware information
- info.append("Hardware Information");
- info.append(QString("\n Memory: %1").arg(KisImageConfig(true).totalRAM() / 1024)).append(" Gb");
- info.append(QString("\n Cores: %1").arg(QThread::idealThreadCount()));
- info.append("\n Swap: ").append(KisImageConfig(true).swapDir());
- }
- else {
- QFile f(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log");
- f.open(QFile::ReadOnly | QFile::Text);
- info = QString::fromUtf8(f.readAll());
- f.close();
- }
// calculate a default height for the widget
int wheight = m_page->sizeHint().height();
m_page->txtBugInfo->setText(info);
QFontMetrics fm = m_page->txtBugInfo->fontMetrics();
int target_height = fm.height() * info.split('\n').size() + wheight;
QRect screen_rect = QGuiApplication::primaryScreen()->availableGeometry();
resize(m_page->size().width(), target_height > screen_rect.height() ? screen_rect.height() : target_height);
+}
+
+void DlgBugInfo::saveToFile()
+{
+ KoFileDialog dlg(this, KoFileDialog::SaveFile, i18n("Save to file"));
+ dlg.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/" + defaultNewFileName());
+ dlg.setMimeTypeFilters(QStringList("text/plain"), "text/plain");
+ QString filename = dlg.filename();
+
+ if (filename.isEmpty()) {
+ return;
+ } else {
+
+ QString originalLogFileName = originalFileName();
+ if (!originalLogFileName.isEmpty() && QFileInfo(originalLogFileName).exists())
+ {
+ QFile::copy(originalLogFileName, filename);
+ } else {
+
+ QFile file(filename);
+ if (!file.open(QIODevice::WriteOnly)) {
+ QMessageBox::information(this, i18n("Unable to open file"),
+ file.errorString());
+ return;
+ }
+ QTextStream out(&file);
+ out << m_page->txtBugInfo->toPlainText();
+ file.close();
+ }
+ }
+}
+
+QString DlgBugInfo::basicSystemInformationReplacementText()
+{
+ QString info;
+
+ // Krita version info
+ info.append("Krita");
+ info.append("\n Version: ").append(KritaVersionWrapper::versionString(true));
+ info.append("\n\n");
+
+ info.append("Qt");
+ info.append("\n Version (compiled): ").append(QT_VERSION_STR);
+ info.append("\n Version (loaded): ").append(qVersion());
+ info.append("\n\n");
+
+ // OS information
+ info.append("OS Information");
+ info.append("\n Build ABI: ").append(QSysInfo::buildAbi());
+ info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture());
+ info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture());
+ info.append("\n Kernel Type: ").append(QSysInfo::kernelType());
+ info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion());
+ info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName());
+ info.append("\n Product Type: ").append(QSysInfo::productType());
+ info.append("\n Product Version: ").append(QSysInfo::productVersion());
+ info.append("\n\n");
+
+ // OpenGL information
+ info.append("\n").append(KisOpenGL::getDebugText());
+ info.append("\n\n");
+ // Hardware information
+ info.append("Hardware Information");
+ info.append(QString("\n Memory: %1").arg(KisImageConfig(true).totalRAM() / 1024)).append(" Gb");
+ info.append(QString("\n Cores: %1").arg(QThread::idealThreadCount()));
+ info.append("\n Swap: ").append(KisImageConfig(true).swapDir());
+
+ return info;
+}
+
+QString DlgBugInfo::infoText(QSettings& kritarc)
+{
+ QString info;
+
+ if (!kritarc.value("LogUsage", true).toBool() || !QFileInfo(originalFileName()).exists()) {
+
+ // NOTE: This is intentionally not translated!
+
+ info.append(replacementWarningText());
+ info.append("File name and location: " + originalFileName());
+ info.append("------------------------------------");
+ info.append("\n\n");
+
+ info.append(basicSystemInformationReplacementText());
+ }
+ else {
+
+ QFile log(originalFileName());
+ log.open(QFile::ReadOnly | QFile::Text);
+ info += QString::fromUtf8(log.readAll());
+ log.close();
+ }
+
+ return info;
- connect(this, &KoDialog::user1Clicked, this, [this](){
- QGuiApplication::clipboard()->setText(m_page->txtBugInfo->toPlainText());
- m_page->txtBugInfo->selectAll(); // feedback
- });
}
DlgBugInfo::~DlgBugInfo()
{
delete m_page;
}
diff --git a/plugins/extensions/buginfo/dlg_buginfo.h b/plugins/extensions/buginfo/dlg_buginfo.h
index 619a737b05..3a758ce82c 100644
--- a/plugins/extensions/buginfo/dlg_buginfo.h
+++ b/plugins/extensions/buginfo/dlg_buginfo.h
@@ -1,47 +1,61 @@
/*
* 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.
*/
#ifndef DLG_BUGINFO
#define DLG_BUGINFO
#include <KoDialog.h>
#include "ui_wdg_buginfo.h"
+class QSettings;
class WdgBugInfo : public QWidget, public Ui::WdgBugInfo
{
Q_OBJECT
public:
WdgBugInfo(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class DlgBugInfo: public KoDialog
{
Q_OBJECT
public:
DlgBugInfo(QWidget * parent = 0);
~DlgBugInfo() override;
+
+ void initialize();
+ void initializeText();
+ void saveToFile();
+
+ virtual QString defaultNewFileName() = 0;
+ virtual QString originalFileName() = 0;
+ virtual QString captionText() = 0;
+ virtual QString replacementWarningText() = 0;
+ QString infoText(QSettings& kritarc);
+
+ QString basicSystemInformationReplacementText();
+
private:
WdgBugInfo *m_page;
};
#endif // DLG_BUGINFO
diff --git a/plugins/extensions/imagesize/dlg_canvassize.cc b/plugins/extensions/imagesize/dlg_canvassize.cc
index 662c2a897c..170251008d 100644
--- a/plugins/extensions/imagesize/dlg_canvassize.cc
+++ b/plugins/extensions/imagesize/dlg_canvassize.cc
@@ -1,498 +1,498 @@
/*
*
* Copyright (c) 2009 Edward Apap <schumifer@hotmail.com>
* Copyright (c) 2013 Juan Palacios <jpalaciosdev@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 "dlg_canvassize.h"
#include "kcanvaspreview.h"
#include <kis_config.h>
#include <KoUnit.h>
#include <kis_icon.h>
#include <kis_size_group.h>
#include <klocalizedstring.h>
#include <kis_document_aware_spin_box_unit_manager.h>
#include <QComboBox>
#include <QButtonGroup>
// used to extend KoUnit in comboboxes
static const QString percentStr(i18n("Percent (%)"));
const QString DlgCanvasSize::PARAM_PREFIX = "canvasizedlg";
const QString DlgCanvasSize::PARAM_WIDTH_UNIT = DlgCanvasSize::PARAM_PREFIX + "_widthunit";
const QString DlgCanvasSize::PARAM_HEIGHT_UNIT = DlgCanvasSize::PARAM_PREFIX + "_heightunit";
const QString DlgCanvasSize::PARAM_XOFFSET_UNIT = DlgCanvasSize::PARAM_PREFIX + "_xoffsetunit";
const QString DlgCanvasSize::PARAM_YOFFSET_UNIT = DlgCanvasSize::PARAM_PREFIX + "_yoffsetunit";
DlgCanvasSize::DlgCanvasSize(QWidget *parent, int width, int height, double resolution)
: KoDialog(parent)
, m_keepAspect(true)
, m_aspectRatio((double)width / height)
, m_resolution(resolution)
, m_originalWidth(width)
, m_originalHeight(height)
, m_newWidth(width)
, m_newHeight(height)
, m_xOffset(0)
, m_yOffset(0)
{
setCaption(i18n("Resize Canvas"));
setButtons(Ok | Cancel);
setDefaultButton(Ok);
m_page = new WdgCanvasSize(this);
Q_CHECK_PTR(m_page);
m_page->layout()->setMargin(0);
m_page->setObjectName("canvas_size");
_widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this);
_heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y);
KisConfig cfg(true);
_widthUnitManager->setApparentUnitFromSymbol("px");
_heightUnitManager->setApparentUnitFromSymbol("px");
m_page->newWidthDouble->setUnitManager(_widthUnitManager);
m_page->newHeightDouble->setUnitManager(_heightUnitManager);
m_page->newWidthDouble->setDecimals(2);
m_page->newHeightDouble->setDecimals(2);
m_page->newWidthDouble->setDisplayUnit(false);
m_page->newHeightDouble->setDisplayUnit(false);
m_page->newWidthDouble->setValue(width);
m_page->newWidthDouble->setFocus();
m_page->newHeightDouble->setValue(height);
m_page->widthUnit->setModel(_widthUnitManager);
m_page->heightUnit->setModel(_heightUnitManager);
QString unitw = cfg.readEntry<QString>(PARAM_WIDTH_UNIT, "px");
QString unith = cfg.readEntry<QString>(PARAM_HEIGHT_UNIT, "px");
_widthUnitManager->setApparentUnitFromSymbol(unitw);
_heightUnitManager->setApparentUnitFromSymbol(unith);
const int wUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf(unitw);
const int hUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf(unith);
m_page->widthUnit->setCurrentIndex(wUnitIndex);
m_page->heightUnit->setCurrentIndex(hUnitIndex);
_xOffsetUnitManager = new KisDocumentAwareSpinBoxUnitManager(this);
_yOffsetUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y);
_xOffsetUnitManager->setApparentUnitFromSymbol("px");
_yOffsetUnitManager->setApparentUnitFromSymbol("px");
m_page->xOffsetDouble->setUnitManager(_xOffsetUnitManager);
m_page->yOffsetDouble->setUnitManager(_yOffsetUnitManager);
m_page->xOffsetDouble->setDecimals(2);
m_page->yOffsetDouble->setDecimals(2);
m_page->xOffsetDouble->setDisplayUnit(false);
m_page->yOffsetDouble->setDisplayUnit(false);
m_page->xOffUnit->setModel(_xOffsetUnitManager);
m_page->yOffUnit->setModel(_yOffsetUnitManager);
m_page->xOffsetDouble->changeValue(m_xOffset);
m_page->yOffsetDouble->changeValue(m_yOffset);
QString unitx = cfg.readEntry<QString>(PARAM_XOFFSET_UNIT, "px");
QString unity = cfg.readEntry<QString>(PARAM_YOFFSET_UNIT, "px");
_xOffsetUnitManager->setApparentUnitFromSymbol(unitx);
_yOffsetUnitManager->setApparentUnitFromSymbol(unity);
const int xUnitIndex = _xOffsetUnitManager->getsUnitSymbolList().indexOf(unitx);
const int yUnitIndex = _yOffsetUnitManager->getsUnitSymbolList().indexOf(unity);
m_page->xOffUnit->setCurrentIndex(xUnitIndex);
m_page->yOffUnit->setCurrentIndex(yUnitIndex);
m_page->canvasPreview->setImageSize(m_originalWidth, m_originalHeight);
m_page->canvasPreview->setCanvasSize(m_originalWidth, m_originalHeight);
m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset);
m_page->aspectRatioBtn->setKeepAspectRatio(cfg.readEntry("CanvasSize/KeepAspectRatio", false));
m_page->constrainProportionsCkb->setChecked(cfg.readEntry("CanvasSize/ConstrainProportions", false));
m_keepAspect = cfg.readEntry("CanvasSize/KeepAspectRatio", false);
m_group = new QButtonGroup(m_page);
m_group->addButton(m_page->topLeft, NORTH_WEST);
m_group->addButton(m_page->topCenter, NORTH);
m_group->addButton(m_page->topRight, NORTH_EAST);
m_group->addButton(m_page->middleLeft, WEST);
m_group->addButton(m_page->middleCenter, CENTER);
m_group->addButton(m_page->middleRight, EAST);
m_group->addButton(m_page->bottomLeft, SOUTH_WEST);
m_group->addButton(m_page->bottomCenter, SOUTH);
m_group->addButton(m_page->bottomRight, SOUTH_EAST);
loadAnchorIcons();
m_group->button(CENTER)->setChecked(true);
updateAnchorIcons(CENTER);
KisSizeGroup *labelsGroup = new KisSizeGroup(this);
labelsGroup->addWidget(m_page->lblNewWidth);
labelsGroup->addWidget(m_page->lblNewHeight);
labelsGroup->addWidget(m_page->lblXOff);
labelsGroup->addWidget(m_page->lblYOff);
labelsGroup->addWidget(m_page->lblAnchor);
KisSizeGroup *spinboxesGroup = new KisSizeGroup(this);
spinboxesGroup->addWidget(m_page->newWidthDouble);
spinboxesGroup->addWidget(m_page->newHeightDouble);
spinboxesGroup->addWidget(m_page->xOffsetDouble);
spinboxesGroup->addWidget(m_page->yOffsetDouble);
KisSizeGroup *comboboxesGroup = new KisSizeGroup(this);
comboboxesGroup->addWidget(m_page->widthUnit);
comboboxesGroup->addWidget(m_page->heightUnit);
comboboxesGroup->addWidget(m_page->xOffUnit);
comboboxesGroup->addWidget(m_page->yOffUnit);
setMainWidget(m_page);
connect(this, SIGNAL(okClicked()), this, SLOT(accept()));
connect(m_page->newWidthDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotWidthChanged(double)));
connect(m_page->newHeightDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotHeightChanged(double)));
connect(m_page->widthUnit, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int)));
connect(m_page->heightUnit, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int)));
connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->widthUnit, SLOT(setCurrentIndex(int)));
connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->heightUnit, SLOT(setCurrentIndex(int)));
connect(m_page->xOffsetDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotXOffsetChanged(double)));
connect(m_page->yOffsetDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotYOffsetChanged(double)));
connect(m_page->xOffUnit, SIGNAL(currentIndexChanged(int)), _xOffsetUnitManager, SLOT(selectApparentUnitFromIndex(int)));
connect(m_page->yOffUnit, SIGNAL(currentIndexChanged(int)), _yOffsetUnitManager, SLOT(selectApparentUnitFromIndex(int)));
connect(_xOffsetUnitManager, SIGNAL(unitChanged(int)), m_page->xOffUnit, SLOT(setCurrentIndex(int)));
connect(_yOffsetUnitManager, SIGNAL(unitChanged(int)), m_page->yOffUnit, SLOT(setCurrentIndex(int)));
connect(m_page->constrainProportionsCkb, SIGNAL(toggled(bool)), this, SLOT(slotAspectChanged(bool)));
connect(m_page->aspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool)));
connect(m_group, SIGNAL(buttonClicked(int)), SLOT(slotAnchorButtonClicked(int)));
connect(m_page->canvasPreview, SIGNAL(sigModifiedXOffset(int)), this, SLOT(slotCanvasPreviewXOffsetChanged(int)));
connect(m_page->canvasPreview, SIGNAL(sigModifiedYOffset(int)), this, SLOT(slotCanvasPreviewYOffsetChanged(int)));
}
DlgCanvasSize::~DlgCanvasSize()
{
KisConfig cfg(false);
cfg.writeEntry<bool>("CanvasSize/KeepAspectRatio", m_page->aspectRatioBtn->keepAspectRatio());
cfg.writeEntry<bool>("CanvasSize/ConstrainProportions", m_page->constrainProportionsCkb->isChecked());
cfg.writeEntry<QString>(PARAM_WIDTH_UNIT, _widthUnitManager->getApparentUnitSymbol());
cfg.writeEntry<QString>(PARAM_HEIGHT_UNIT, _heightUnitManager->getApparentUnitSymbol());
cfg.writeEntry<QString>(PARAM_XOFFSET_UNIT, _xOffsetUnitManager->getApparentUnitSymbol());
cfg.writeEntry<QString>(PARAM_YOFFSET_UNIT, _yOffsetUnitManager->getApparentUnitSymbol());
delete m_page;
}
qint32 DlgCanvasSize::width()
{
return (qint32) m_newWidth;
}
qint32 DlgCanvasSize::height()
{
return (qint32) m_newHeight;
}
qint32 DlgCanvasSize::xOffset()
{
return (qint32) m_xOffset;
}
qint32 DlgCanvasSize::yOffset()
{
return (qint32) m_yOffset;
}
void DlgCanvasSize::slotAspectChanged(bool keep)
{
m_page->aspectRatioBtn->blockSignals(true);
m_page->constrainProportionsCkb->blockSignals(true);
m_page->aspectRatioBtn->setKeepAspectRatio(keep);
m_page->constrainProportionsCkb->setChecked(keep);
m_page->aspectRatioBtn->blockSignals(false);
m_page->constrainProportionsCkb->blockSignals(false);
m_keepAspect = keep;
if (keep) {
// size values may be out of sync, so we need to reset it to defaults
m_newWidth = m_originalWidth;
m_newHeight = m_originalHeight;
m_xOffset = 0;
m_yOffset = 0;
m_page->canvasPreview->blockSignals(true);
m_page->canvasPreview->setCanvasSize(m_newWidth, m_newHeight);
m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset);
m_page->canvasPreview->blockSignals(false);
updateOffset(CENTER);
updateButtons(CENTER);
}
}
void DlgCanvasSize::slotAnchorButtonClicked(int id)
{
updateOffset(id);
updateButtons(id);
}
void DlgCanvasSize::slotWidthChanged(double v)
{
//this slot receiv values in pt from the unitspinbox, so just use the resolution.
const double resValue = v*_widthUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
m_newWidth = qRound(resValue);
if (m_keepAspect) {
m_newHeight = qRound(m_newWidth / m_aspectRatio);
m_page->newHeightDouble->blockSignals(true);
m_page->newHeightDouble->changeValue(v / m_aspectRatio);
m_page->newHeightDouble->blockSignals(false);
}
int savedId = m_group->checkedId();
m_page->canvasPreview->blockSignals(true);
m_page->canvasPreview->setCanvasSize(m_newWidth, m_newHeight);
m_page->canvasPreview->blockSignals(false);
updateOffset(savedId);
updateButtons(savedId);
}
void DlgCanvasSize::slotHeightChanged(double v)
{
//this slot receiv values in pt from the unitspinbox, so just use the resolution.
const double resValue = v*_heightUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
m_newHeight = qRound(resValue);
if (m_keepAspect) {
m_newWidth = qRound(m_newHeight * m_aspectRatio);
m_page->newWidthDouble->blockSignals(true);
m_page->newWidthDouble->changeValue(v * m_aspectRatio);
m_page->newWidthDouble->blockSignals(false);
}
int savedId = m_group->checkedId();
m_page->canvasPreview->blockSignals(true);
m_page->canvasPreview->setCanvasSize(m_newWidth, m_newHeight);
m_page->canvasPreview->blockSignals(false);
updateOffset(savedId);
updateButtons(savedId);
}
void DlgCanvasSize::slotXOffsetChanged(double v)
{
//this slot receiv values in pt from the unitspinbox, so just use the resolution.
const double resValue = v*_xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
m_xOffset = qRound(resValue);
m_page->canvasPreview->blockSignals(true);
m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset);
m_page->canvasPreview->blockSignals(false);
updateButtons(-1);
}
void DlgCanvasSize::slotYOffsetChanged(double v)
{
//this slot receiv values in pt from the unitspinbox, so just use the resolution.
const double resValue = v*_xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
m_yOffset = qRound(resValue);
m_page->canvasPreview->blockSignals(true);
m_page->canvasPreview->setImageOffset(m_xOffset, m_yOffset);
m_page->canvasPreview->blockSignals(false);
updateButtons(-1);
}
void DlgCanvasSize::slotCanvasPreviewXOffsetChanged(int v)
{
double newVal = v / _xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
m_page->xOffsetDouble->changeValue(newVal);
}
void DlgCanvasSize::slotCanvasPreviewYOffsetChanged(int v)
{
double newVal = v / _yOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
m_page->yOffsetDouble->changeValue(newVal);
}
void DlgCanvasSize::loadAnchorIcons()
{
m_anchorIcons[NORTH_WEST] = KisIconUtils::loadIcon("arrow-topleft");
m_anchorIcons[NORTH] = KisIconUtils::loadIcon("arrow-up");
m_anchorIcons[NORTH_EAST] = KisIconUtils::loadIcon("arrow-topright");
m_anchorIcons[EAST] = KisIconUtils::loadIcon("arrow-right");
m_anchorIcons[CENTER] = KisIconUtils::loadIcon("arrow_center");
m_anchorIcons[WEST] = KisIconUtils::loadIcon("arrow-left");
m_anchorIcons[SOUTH_WEST] = KisIconUtils::loadIcon("arrow-downleft");
m_anchorIcons[SOUTH] = KisIconUtils::loadIcon("arrow-down");
m_anchorIcons[SOUTH_EAST] = KisIconUtils::loadIcon("arrow-downright");
}
void DlgCanvasSize::updateAnchorIcons(int id)
{
anchor iconLayout[10][9] = {
{NONE, EAST, NONE, SOUTH, SOUTH_EAST, NONE, NONE, NONE, NONE},
{WEST, NONE, EAST, SOUTH_WEST, SOUTH, SOUTH_EAST, NONE, NONE, NONE},
{NONE, WEST, NONE, NONE, SOUTH_WEST, SOUTH, NONE, NONE, NONE},
{NORTH, NORTH_EAST, NONE, NONE, EAST, NONE, SOUTH, SOUTH_EAST, NONE},
{NORTH_WEST, NORTH, NORTH_EAST, WEST, NONE, EAST, SOUTH_WEST, SOUTH, SOUTH_EAST},
{NONE, NORTH_WEST, NORTH, NONE, WEST, NONE, NONE, SOUTH_WEST, SOUTH},
{NONE, NONE, NONE, NORTH, NORTH_EAST, NONE, NONE, EAST, NONE},
{NONE, NONE, NONE, NORTH_WEST, NORTH, NORTH_EAST, WEST, NONE, EAST},
{NONE, NONE, NONE, NONE, NORTH_WEST, NORTH, NONE, WEST, NONE},
{NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE, NONE}
};
if (id == -1) {
id = SOUTH_EAST + 1;
}
// we are going to swap arrows direction based on width and height shrinking
bool shrinkWidth = (m_newWidth < m_originalWidth) ? true : false;
bool shrinkHeight = (m_newHeight < m_originalHeight) ? true : false;
for (int i = NORTH_WEST; i <= SOUTH_EAST; i++) {
anchor iconId = iconLayout[id][i];
// all corner arrows represents shrinking in some direction
if (shrinkWidth || shrinkHeight) {
switch (iconId) {
case NORTH_WEST: iconId = SOUTH_EAST; break;
case NORTH_EAST: iconId = SOUTH_WEST; break;
case SOUTH_WEST: iconId = NORTH_EAST; break;
case SOUTH_EAST: iconId = NORTH_WEST; break;
default: break;
}
}
if (shrinkWidth) {
switch (iconId) {
case WEST: iconId = EAST; break;
case EAST: iconId = WEST; break;
default: break;
}
}
if (shrinkHeight) {
switch (iconId) {
case NORTH: iconId = SOUTH; break;
case SOUTH: iconId = NORTH; break;
default: break;
}
}
QAbstractButton *button = m_group->button(i);
if (iconId == NONE) {
button->setIcon(QIcon());
} else {
button->setIcon(m_anchorIcons[iconId]);
}
}
}
void DlgCanvasSize::updateButtons(int forceId)
{
int id = m_group->checkedId();
if (forceId != -1) {
m_group->setExclusive(true);
m_group->button(forceId)->setChecked(true);
updateAnchorIcons(forceId);
} else if (id != -1) {
double xOffset, yOffset;
expectedOffset(id, xOffset, yOffset);
// convert values to internal unit
int internalXOffset = 0;
int internalYOffset = 0;
if (m_page->xOffUnit->currentText() == percentStr) {
internalXOffset = qRound((xOffset * m_newWidth) / 100.0);
internalYOffset = qRound((yOffset * m_newHeight) / 100.0);
} else {
const KoUnit xOffsetUnit = KoUnit::fromListForUi(m_page->xOffUnit->currentIndex());
internalXOffset = qRound(xOffsetUnit.fromUserValue(xOffset));
const KoUnit yOffsetUnit = KoUnit::fromListForUi(m_page->yOffUnit->currentIndex());
internalYOffset = qRound(yOffsetUnit.fromUserValue(yOffset));
}
bool offsetAsExpected =
internalXOffset == m_xOffset &&
internalYOffset == m_yOffset;
if (offsetAsExpected) {
m_group->setExclusive(true);
} else {
m_group->setExclusive(false);
m_group->button(id)->setChecked(false);
id = -1;
}
updateAnchorIcons(id);
} else {
updateAnchorIcons(id);
}
}
void DlgCanvasSize::updateOffset(int id)
{
if (id == -1) return;
double xOffset;
double yOffset;
expectedOffset(id, xOffset, yOffset);
m_page->xOffsetDouble->changeValue(xOffset);
m_page->yOffsetDouble->changeValue(yOffset);
}
void DlgCanvasSize::expectedOffset(int id, double &xOffset, double &yOffset)
{
const double xCoeff = (id % 3) * 0.5;
- const double yCoeff = (id / 3.0) * 0.5;
+ const double yCoeff = (int)(id / 3.0) * 0.5;
const int xDiff = m_newWidth - m_originalWidth;
const int yDiff = m_newHeight - m_originalHeight;
//convert to unitmanager default unit.
xOffset = xDiff * xCoeff / _xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
yOffset = yDiff * yCoeff / _yOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
}
diff --git a/plugins/extensions/offsetimage/wdg_offsetimage.ui b/plugins/extensions/offsetimage/wdg_offsetimage.ui
index 8a1eedac33..6f64221f09 100644
--- a/plugins/extensions/offsetimage/wdg_offsetimage.ui
+++ b/plugins/extensions/offsetimage/wdg_offsetimage.ui
@@ -1,132 +1,138 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgOffsetImage</class>
<widget class="QWidget" name="WdgOffsetImage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>214</width>
<height>157</height>
</rect>
</property>
<property name="windowTitle">
<string>Rotate Image</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGroupBox" name="grpAngle">
<property name="title">
<string>Offset</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout" name="offsetRowX" stretch="0,0,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>
<widget class="QLabel" name="xLabel">
<property name="text">
<string>X:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="KisDoubleParseUnitSpinBox" name="offsetXdoubleSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="maximum">
+ <double>100000.000000000000000</double>
+ </property>
</widget>
</item>
<item>
<widget class="QComboBox" name="unitXComboBox"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="offsetRowY" stretch="0,0,0">
<item>
<widget class="QLabel" name="yLabel">
<property name="text">
<string>Y:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="KisDoubleParseUnitSpinBox" name="offsetYdoubleSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
+ <property name="maximum">
+ <double>100000.000000000000000</double>
+ </property>
</widget>
</item>
<item>
<widget class="QComboBox" name="unitYComboBox"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="middleOffsetBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Offset by x/2, y/2</string>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="spacer4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisDoubleParseUnitSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>kis_double_parse_unit_spin_box.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp b/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp
index 6db844de55..268c3f9880 100644
--- a/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp
+++ b/plugins/extensions/pykrita/plugin/PythonPluginManager.cpp
@@ -1,418 +1,418 @@
/*
* This file is part of PyKrita, Krita' Python scripting plugin.
*
* Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
* Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2017 Jouni Pentikäinen (joupent@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 "PythonPluginManager.h"
#include <QFile>
#include <QFileInfo>
#include <KoResourcePaths.h>
#include <KConfigCore/KConfig>
#include <KConfigCore/KDesktopFile>
#include <KI18n/KLocalizedString>
#include <KConfigCore/KSharedConfig>
#include <KConfigCore/KConfigGroup>
#include "config.h"
#include "version_checker.h"
PythonPluginManager* instance = 0;
// PythonPlugin implementation
QString PythonPlugin::moduleFilePathPart() const
{
QString filePath = m_moduleName;
return filePath.replace(".", "/");
}
bool PythonPlugin::isValid() const
{
dbgScript << "Got Krita/PythonPlugin: " << name()
<< ", module-path=" << moduleName()
;
// Make sure mandatory properties are here
if (m_name.isEmpty()) {
dbgScript << "Ignore desktop file w/o a name";
return false;
}
if (m_moduleName.isEmpty()) {
dbgScript << "Ignore desktop file w/o a module to import";
return false;
}
#if PY_MAJOR_VERSION == 2
// Check if the plug-in is compatible with Python 2 or not.
if (m_properties["X-Python-2-Compatible"].toBool() != true) {
dbgScript << "Ignoring plug-in. It is marked incompatible with Python 2.";
return false;
}
#endif
return true;
}
// PythonPluginManager implementation
PythonPluginManager::PythonPluginManager()
: QObject(0)
, m_model(0, this)
{}
const QList<PythonPlugin>& PythonPluginManager::plugins() const
{
return m_plugins;
}
PythonPlugin * PythonPluginManager::plugin(int index) {
if (index >= 0 && index < m_plugins.count()) {
return &m_plugins[index];
}
return nullptr;
}
PythonPluginsModel * PythonPluginManager::model()
{
return &m_model;
}
void PythonPluginManager::unloadAllModules()
{
Q_FOREACH(PythonPlugin plugin, m_plugins) {
if (plugin.m_loaded) {
unloadModule(plugin);
}
}
}
bool PythonPluginManager::verifyModuleExists(PythonPlugin &plugin)
{
// Find the module:
// 0) try to locate directory based plugin first
QString rel_path = plugin.moduleFilePathPart();
rel_path = rel_path + "/" + "__init__.py";
dbgScript << "Finding Python module with rel_path:" << rel_path;
QString module_path = KoResourcePaths::findResource("pythonscripts", rel_path);
dbgScript << "module_path:" << module_path;
if (module_path.isEmpty()) {
// 1) Nothing found, then try file based plugin
rel_path = plugin.moduleFilePathPart() + ".py";
dbgScript << "Finding Python module with rel_path:" << rel_path;
module_path = KoResourcePaths::findResource("pythonscripts", rel_path);
dbgScript << "module_path:" << module_path;
}
// Is anything found at all?
if (module_path.isEmpty()) {
plugin.m_broken = true;
plugin.m_errorReason = i18nc(
"@info:tooltip"
, "Unable to find the module specified <application>%1</application>"
, plugin.moduleName()
);
dbgScript << "Cannot load module:" << plugin.m_errorReason;
return false;
}
dbgScript << "Found module path:" << module_path;
return true;
}
QPair<QString, PyKrita::version_checker> PythonPluginManager::parseDependency(const QString& d)
{
// Check if dependency has package info attached
const int pnfo = d.indexOf('(');
if (pnfo != -1) {
QString dependency = d.mid(0, pnfo);
QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed();
dbgScript << "Desired version spec [" << dependency << "]:" << version_str;
PyKrita::version_checker checker = PyKrita::version_checker::fromString(version_str);
if (!(checker.isValid() && d.endsWith(')'))) {
dbgScript << "Invalid version spec " << d;
QString reason = i18nc(
"@info:tooltip"
, "<p>Specified version has invalid format for dependency <application>%1</application>: "
"<icode>%2</icode>. Skipped</p>"
, dependency
, version_str
);
return qMakePair(reason, PyKrita::version_checker());
}
return qMakePair(dependency, checker);
}
return qMakePair(d, PyKrita::version_checker(PyKrita::version_checker::undefined));
}
/**
* Collect dependencies and check them. To do it
* just try to import a module... when unload it ;)
*
* \c X-Python-Dependencies property of \c .desktop file has the following format:
* <tt>python-module(version-info)</tt>, where <tt>python-module</tt>
* a python module name to be imported, <tt>version-spec</tt>
* is a version triplet delimited by dots, possible w/ leading compare
* operator: \c =, \c <, \c >, \c <=, \c >=
*/
void PythonPluginManager::verifyDependenciesSetStatus(PythonPlugin& plugin)
{
QStringList dependencies = plugin.property("X-Python-Dependencies").toStringList();
PyKrita::Python py = PyKrita::Python();
QString reason = i18nc("@info:tooltip", "<title>Dependency check</title>");
Q_FOREACH(const QString & d, dependencies) {
QPair<QString, PyKrita::version_checker> info_pair = parseDependency(d);
PyKrita::version_checker& checker = info_pair.second;
if (!checker.isValid()) {
plugin.m_broken = true;
reason += info_pair.first;
continue;
}
dbgScript << "Try to import dependency module/package:" << d;
// Try to import a module
const QString& dependency = info_pair.first;
PyObject* module = py.moduleImport(PQ(dependency));
if (module) {
if (checker.isEmpty()) { // Need to check smth?
dbgScript << "No version to check, just make sure it's loaded:" << dependency;
Py_DECREF(module);
continue;
}
// Try to get __version__ from module
- // See PEP396: http://www.python.org/dev/peps/pep-0396/
+ // See PEP396: https://www.python.org/dev/peps/pep-0396/
PyObject* version_obj = py.itemString("__version__", PQ(dependency));
if (!version_obj) {
dbgScript << "No __version__ for " << dependency
<< "[" << plugin.name() << "]:\n" << py.lastTraceback()
;
plugin.m_unstable = true;
reason += i18nc(
"@info:tooltip"
, "<p>Failed to check version of dependency <application>%1</application>: "
"Module do not have PEP396 <code>__version__</code> attribute. "
"It is not disabled, but behaviour is unpredictable...</p>"
, dependency
);
}
PyKrita::version dep_version = PyKrita::version::fromPythonObject(version_obj);
if (!dep_version.isValid()) {
// Dunno what is this... Giving up!
dbgScript << "***: Can't parse module version for" << dependency;
plugin.m_unstable = true;
reason += i18nc(
"@info:tooltip"
, "<p><application>%1</application>: Unexpected module's version format"
, dependency
);
} else if (!checker(dep_version)) {
dbgScript << "Version requirement check failed ["
<< plugin.name() << "] for "
<< dependency << ": wanted " << checker.operationToString()
<< QString(checker.required())
<< ", but found" << QString(dep_version)
;
plugin.m_broken = true;
reason += i18nc(
"@info:tooltip"
, "<p><application>%1</application>: No suitable version found. "
"Required version %2 %3, but found %4</p>"
, dependency
, checker.operationToString()
, QString(checker.required())
, QString(dep_version)
);
}
// Do not need this module anymore...
Py_DECREF(module);
} else {
dbgScript << "Load failure [" << plugin.name() << "]:\n" << py.lastTraceback();
plugin.m_broken = true;
reason += i18nc(
"@info:tooltip"
, "<p>Failure on module load <application>%1</application>:</p><pre>%2</pre>"
, dependency
, py.lastTraceback()
);
}
}
if (plugin.isBroken() || plugin.isUnstable()) {
plugin.m_errorReason = reason;
}
}
void PythonPluginManager::scanPlugins()
{
m_plugins.clear();
KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python");
QStringList desktopFiles = KoResourcePaths::findAllResources("data", "pykrita/*desktop");
Q_FOREACH(const QString &desktopFile, desktopFiles) {
const KDesktopFile df(desktopFile);
const KConfigGroup dg = df.desktopGroup();
if (dg.readEntry("ServiceTypes") == "Krita/PythonPlugin") {
PythonPlugin plugin;
plugin.m_comment = df.readComment();
plugin.m_name = df.readName();
plugin.m_moduleName = dg.readEntry("X-KDE-Library");
plugin.m_properties["X-Python-2-Compatible"] = dg.readEntry("X-Python-2-Compatible", false);
QString manual = dg.readEntry("X-Krita-Manual");
if (!manual.isEmpty()) {
QFile f(QFileInfo(desktopFile).path() + "/" + plugin.m_moduleName + "/" + manual);
if (f.exists()) {
f.open(QFile::ReadOnly);
QByteArray ba = f.readAll();
f.close();
plugin.m_manual = QString::fromUtf8(ba);
}
}
if (!plugin.isValid()) {
dbgScript << plugin.name() << "is not usable";
continue;
}
if (!verifyModuleExists(plugin)) {
dbgScript << "Cannot load" << plugin.name() << ": broken"
<< plugin.isBroken()
<< "because:" << plugin.errorReason();
continue;
}
verifyDependenciesSetStatus(plugin);
plugin.m_enabled = pluginSettings.readEntry(QString("enable_") + plugin.moduleName(), false);
m_plugins.append(plugin);
}
}
}
void PythonPluginManager::tryLoadEnabledPlugins()
{
for (PythonPlugin &plugin : m_plugins) {
dbgScript << "Trying to load plugin" << plugin.moduleName()
<< ". Enabled:" << plugin.isEnabled()
<< ". Broken: " << plugin.isBroken();
if (plugin.m_enabled && !plugin.isBroken()) {
loadModule(plugin);
}
}
}
void PythonPluginManager::loadModule(PythonPlugin &plugin)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(plugin.isEnabled() && !plugin.isBroken());
QString module_name = plugin.moduleName();
dbgScript << "Loading module: " << module_name;
PyKrita::Python py = PyKrita::Python();
// Get 'plugins' key from 'pykrita' module dictionary.
// Every entry has a module name as a key and 2 elements tuple as a value
PyObject* plugins = py.itemString("plugins");
KIS_SAFE_ASSERT_RECOVER_RETURN(plugins);
PyObject* module = py.moduleImport(PQ(module_name));
if (module) {
// Move just loaded module to the dict
const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module);
KIS_SAFE_ASSERT_RECOVER_NOOP(ins_result == 0);
Py_DECREF(module);
// Handle failure in release mode.
if (ins_result == 0) {
// Initialize the module from Python's side
PyObject* const args = Py_BuildValue("(s)", PQ(module_name));
PyObject* result = py.functionCall("_pluginLoaded", PyKrita::Python::PYKRITA_ENGINE, args);
Py_DECREF(args);
if (result) {
dbgScript << "\t" << "success!";
plugin.m_loaded = true;
return;
}
}
plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure");
} else {
plugin.m_errorReason = i18nc(
"@info:tooltip"
, "Module not loaded:<br/>%1"
, py.lastTraceback().replace("\n", "<br/>")
);
}
plugin.m_broken = true;
warnScript << "Error loading plugin" << module_name;
}
void PythonPluginManager::unloadModule(PythonPlugin &plugin)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(plugin.m_loaded);
KIS_SAFE_ASSERT_RECOVER_RETURN(!plugin.isBroken());
dbgScript << "Unloading module: " << plugin.moduleName();
PyKrita::Python py = PyKrita::Python();
// Get 'plugins' key from 'pykrita' module dictionary
PyObject* plugins = py.itemString("plugins");
KIS_SAFE_ASSERT_RECOVER_RETURN(plugins);
PyObject* const args = Py_BuildValue("(s)", PQ(plugin.moduleName()));
py.functionCall("_pluginUnloading", PyKrita::Python::PYKRITA_ENGINE, args);
Py_DECREF(args);
// This will just decrement a reference count for module instance
PyDict_DelItemString(plugins, PQ(plugin.moduleName()));
// Remove the module also from 'sys.modules' dict to really unload it,
// so if reloaded all @init actions will work again!
PyObject* sys_modules = py.itemString("modules", "sys");
KIS_SAFE_ASSERT_RECOVER_RETURN(sys_modules);
PyDict_DelItemString(sys_modules, PQ(plugin.moduleName()));
plugin.m_loaded = false;
}
void PythonPluginManager::setPluginEnabled(PythonPlugin &plugin, bool enabled)
{
bool wasEnabled = plugin.isEnabled();
if (wasEnabled && !enabled) {
unloadModule(plugin);
}
plugin.m_enabled = enabled;
KConfigGroup pluginSettings(KSharedConfig::openConfig(), "python");
pluginSettings.writeEntry(QString("enable_") + plugin.moduleName(), enabled);
if (!wasEnabled && enabled) {
loadModule(plugin);
}
}
diff --git a/plugins/extensions/pykrita/plugin/manager.ui b/plugins/extensions/pykrita/plugin/manager.ui
index bf61894100..378b505727 100644
--- a/plugins/extensions/pykrita/plugin/manager.ui
+++ b/plugins/extensions/pykrita/plugin/manager.ui
@@ -1,59 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ManagerPage</class>
<widget class="QWidget" name="ManagerPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>361</width>
<height>228</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
+ <item row="3" column="0">
+ <widget class="QTextBrowser" name="textBrowser"/>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="errorLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Error: The Python engine could not be initialized</string>
+ </property>
+ </widget>
+ </item>
<item row="1" column="0">
<widget class="QTreeView" name="pluginsList">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
</widget>
</item>
- <item row="0" column="0" colspan="2">
- <widget class="QLabel" name="errorLabel">
- <property name="font">
- <font>
- <weight>75</weight>
- <bold>true</bold>
- </font>
- </property>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label">
<property name="text">
- <string>Error: The Python engine could not be initialized</string>
+ <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; you need to restart Krita to enable or disable plugins.&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>
- <item row="2" column="0">
- <widget class="QTextBrowser" name="textBrowser"/>
- </item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
diff --git a/plugins/extensions/pykrita/plugin/utilities.cpp b/plugins/extensions/pykrita/plugin/utilities.cpp
index de5c6c9123..15f896eb12 100644
--- a/plugins/extensions/pykrita/plugin/utilities.cpp
+++ b/plugins/extensions/pykrita/plugin/utilities.cpp
@@ -1,706 +1,706 @@
// This file is part of PyKrita, Krita' Python scripting plugin.
//
// Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
// Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.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) version 3, or any
// later version accepted by the membership of KDE e.V. (or its
// successor approved by the membership of KDE e.V.), which shall
// act as a proxy defined in Section 6 of version 3 of the license.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library. If not, see <http://www.gnu.org/licenses/>.
//
// config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so
// on the build system
#include "config.h"
#include "utilities.h"
#include "PythonPluginManager.h"
#include <algorithm>
#include <cmath>
#include <Python.h>
#include <QDir>
#include <QLibrary>
#include <QString>
#include <QStringList>
#include <QVector>
#include <QFileInfo>
#include <kconfigbase.h>
#include <kconfiggroup.h>
#include <klocalizedstring.h>
#include <KoResourcePaths.h>
#include <kis_debug.h>
#include "PykritaModule.h"
#define THREADED 1
namespace PyKrita
{
static InitResult initStatus = INIT_UNINITIALIZED;
static QScopedPointer<PythonPluginManager> pluginManagerInstance;
InitResult initialize()
{
// Already initialized?
if (initStatus == INIT_OK) return INIT_OK;
dbgScript << "Initializing Python plugin for Python" << PY_MAJOR_VERSION << "," << PY_MINOR_VERSION;
if (!Python::libraryLoad()) {
return INIT_CANNOT_LOAD_PYTHON_LIBRARY;
}
// Update PYTHONPATH
// 0) custom plugin directories (prefer local dir over systems')
// 1) shipped krita module's dir
QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts");
dbgScript << "Plugin Directories: " << pluginDirectories;
if (!Python::setPath(pluginDirectories)) {
initStatus = INIT_CANNOT_SET_PYTHON_PATHS;
return initStatus;
}
#if defined(IS_PY3K)
if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PyInit_pykrita)) {
#else
if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, initpykrita)) {
#endif
initStatus = INIT_CANNOT_LOAD_PYKRITA_MODULE;
return initStatus;
}
Python::ensureInitialized();
Python py = Python();
// NOTE: This code is not needed on Python 3.
// This might also fail if private sip module for PyQt5 is used,
// as required by newer PyQt5. It's fine since any exceptions
// in this script will be ignored.
PyRun_SimpleString(
"import sip\n"
"sip.setapi('QDate', 2)\n"
"sip.setapi('QTime', 2)\n"
"sip.setapi('QDateTime', 2)\n"
"sip.setapi('QUrl', 2)\n"
"sip.setapi('QTextStream', 2)\n"
"sip.setapi('QString', 2)\n"
"sip.setapi('QVariant', 2)\n"
);
// Initialize 'plugins' dict of module 'pykrita'
PyObject* plugins = PyDict_New();
py.itemStringSet("plugins", plugins);
pluginManagerInstance.reset(new PythonPluginManager());
#if defined(IS_PY3K)
// Initialize our built-in module.
auto pykritaModule = PyInit_pykrita();
if (!pykritaModule) {
initStatus = INIT_CANNOT_LOAD_PYKRITA_MODULE;
return initStatus;
//return i18nc("@info:tooltip ", "No <icode>pykrita</icode> built-in module");
}
#else
initpykrita();
#endif
initStatus = INIT_OK;
return initStatus;
}
PythonPluginManager *pluginManager()
{
auto pluginManager = pluginManagerInstance.data();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(pluginManager, nullptr);
return pluginManager;
}
void finalize() {
dbgScript << "Going to destroy the Python engine";
if (pluginManagerInstance) {
pluginManagerInstance->unloadAllModules();
PyKrita::Python::maybeFinalize();
PyKrita::Python::libraryUnload();
pluginManagerInstance.reset();
initStatus = INIT_UNINITIALIZED;
}
}
namespace
{
#ifndef Q_OS_WIN
QLibrary* s_pythonLibrary = 0;
#endif
PyThreadState* s_pythonThreadState = 0;
bool isPythonPathSet = false;
} // anonymous namespace
const char* Python::PYKRITA_ENGINE = "pykrita";
Python::Python()
{
#if THREADED
m_state = PyGILState_Ensure();
#endif
}
Python::~Python()
{
#if THREADED
PyGILState_Release(m_state);
#endif
}
bool Python::prependStringToList(PyObject* const list, const QString& value)
{
PyObject* const u = unicode(value);
bool result = !PyList_Insert(list, 0, u);
Py_DECREF(u);
if (!result)
traceback(QString("Failed to prepend %1").arg(value));
return result;
}
bool Python::functionCall(const char* const functionName, const char* const moduleName)
{
PyObject* const result = functionCall(functionName, moduleName, PyTuple_New(0));
if (result)
Py_DECREF(result);
return bool(result);
}
PyObject* Python::functionCall(
const char* const functionName
, const char* const moduleName
, PyObject* const arguments
)
{
if (!arguments) {
errScript << "Missing arguments for" << moduleName << functionName;
return 0;
}
PyObject* const func = itemString(functionName, moduleName);
if (!func) {
errScript << "Failed to resolve" << moduleName << functionName;
return 0;
}
if (!PyCallable_Check(func)) {
traceback(QString("Not callable %1.%2").arg(moduleName).arg(functionName));
return 0;
}
PyObject* const result = PyObject_CallObject(func, arguments);
Py_DECREF(arguments);
if (!result)
traceback(QString("No result from %1.%2").arg(moduleName).arg(functionName));
return result;
}
bool Python::itemStringDel(const char* const item, const char* const moduleName)
{
PyObject* const dict = moduleDict(moduleName);
const bool result = dict && PyDict_DelItemString(dict, item);
if (!result)
traceback(QString("Could not delete item string %1.%2").arg(moduleName).arg(item));
return result;
}
PyObject* Python::itemString(const char* const item, const char* const moduleName)
{
if (PyObject* const value = itemString(item, moduleDict(moduleName)))
return value;
errScript << "Could not get item string" << moduleName << item;
return 0;
}
PyObject* Python::itemString(const char* item, PyObject* dict)
{
if (dict)
if (PyObject* const value = PyDict_GetItemString(dict, item))
return value;
traceback(QString("Could not get item string %1").arg(item));
return 0;
}
bool Python::itemStringSet(const char* const item, PyObject* const value, const char* const moduleName)
{
PyObject* const dict = moduleDict(moduleName);
const bool result = dict && !PyDict_SetItemString(dict, item, value);
if (!result)
traceback(QString("Could not set item string %1.%2").arg(moduleName).arg(item));
return result;
}
PyObject* Python::kritaHandler(const char* const moduleName, const char* const handler)
{
if (PyObject* const module = moduleImport(moduleName))
return functionCall(handler, "krita", Py_BuildValue("(O)", module));
return 0;
}
QString Python::lastTraceback() const
{
QString result;
result.swap(m_traceback);
return result;
}
bool Python::libraryLoad()
{
// no-op on Windows
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
if (!s_pythonLibrary) {
QFileInfo fi(PYKRITA_PYTHON_LIBRARY);
// get the filename of the configured Python library, without the .so suffix
const QString libraryName = fi.completeBaseName();
// 1.0 is the SONAME of the shared Python library
s_pythonLibrary = new QLibrary(libraryName, "1.0");
s_pythonLibrary->setLoadHints(QLibrary::ExportExternalSymbolsHint);
if (!s_pythonLibrary->load()) {
dbgScript << QString("Could not load %1 -- Reason: %2").arg(s_pythonLibrary->fileName()).arg(s_pythonLibrary->errorString());
delete s_pythonLibrary;
s_pythonLibrary = 0;
return false;
}
dbgScript << QString("Loaded %1").arg(s_pythonLibrary->fileName());
}
#endif
return true;
}
namespace
{
QString findKritaPythonLibsPath(const QString &libdir)
{
QDir rootDir(KoResourcePaths::getApplicationRoot());
QFileInfoList candidates = rootDir.entryInfoList(QStringList() << "lib*", QDir::Dirs | QDir::NoDotAndDotDot) + rootDir.entryInfoList(QStringList() << "Frameworks", QDir::Dirs | QDir::NoDotAndDotDot);
Q_FOREACH (const QFileInfo &entry, candidates) {
QDir libDir(entry.absoluteFilePath());
if (libDir.cd(libdir)) {
return libDir.absolutePath();
} else {
// Handle cases like Linux where libs are placed in a sub-dir
// with the ABI name
Q_FOREACH (const QFileInfo &subEntry, libDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
QDir subDir(subEntry.absoluteFilePath());
if (subDir.cd(libdir)) {
return subDir.absolutePath();
}
}
}
}
return QString();
}
} // namespace
bool Python::setPath(const QStringList& scriptPaths)
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!Py_IsInitialized(), false);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!isPythonPathSet, false);
// qDebug() << ">>>>>>>>>>>" << qgetenv("APPDIR")
// << KoResourcePaths::getApplicationRoot()
// << (!qgetenv("APPDIR").isNull() && KoResourcePaths::getApplicationRoot().contains(qgetenv("APPDIR")));
bool runningInBundle = ((!qgetenv("APPDIR").isNull() && KoResourcePaths::getApplicationRoot().contains(qgetenv("APPDIR"))) || KoResourcePaths::getApplicationRoot().toLower().contains("krita.app"));
dbgScript << "Python::setPath. Script paths:" << scriptPaths << runningInBundle;
#ifdef Q_OS_WIN
constexpr char pathSeparator = ';';
#else
constexpr char pathSeparator = ':';
#endif
QString originalPath;
// Start with the script paths
QStringList paths(scriptPaths);
// Append the Krita libraries path
QString pythonLibsPath = findKritaPythonLibsPath("krita-python-libs");
dbgScript << "pythonLibsPath (krita-python-libs)" << pythonLibsPath;
if (pythonLibsPath.isEmpty()) {
dbgScript << "Cannot find krita-python-libs";
return false;
}
dbgScript << "Found krita-python-libs at" << pythonLibsPath;
paths.append(pythonLibsPath);
#ifndef Q_OS_WIN
// Append the sip libraries path
pythonLibsPath = findKritaPythonLibsPath("sip");
dbgScript << "pythonLibsPath (sip)" << pythonLibsPath;
if (!pythonLibsPath.isEmpty()) {
dbgScript << "Found sip at" << pythonLibsPath;
paths.append(pythonLibsPath);
}
#endif
#ifdef Q_OS_WIN
// Find embeddable Python at <root>/python
QDir pythonDir(KoResourcePaths::getApplicationRoot());
if (pythonDir.cd("python")) {
dbgScript << "Found bundled Python at" << pythonDir.absolutePath();
- // The default paths for Windows embeddable Python is ./python36.zip;./
- // HACK: Assuming bundled Python is version 3.6.*
+ // The default paths for Windows embeddable Python is ./python38.zip;./
+ // HACK: Assuming bundled Python is version 3.8.*
// FIXME: Should we read python36._pth for the paths or use Py_GetPath?
- paths.append(pythonDir.absoluteFilePath("python36.zip"));
+ paths.append(pythonDir.absoluteFilePath("python38.zip"));
paths.append(pythonDir.absolutePath());
} else {
errScript << "Bundled Python not found, cannot set Python library paths";
return false;
}
#else
// If using a system Python install, respect the current PYTHONPATH
if (runningInBundle) {
// We're running from an appimage, so we need our local python
QString p = QFileInfo(PYKRITA_PYTHON_LIBRARY).fileName();
#ifdef Q_OS_MAC
QString p2 = p.remove("lib").remove("m.dy");
#else
QString p2 = p.remove("lib").remove("m.so");
#endif
dbgScript << "\t" << p << p2;
originalPath = findKritaPythonLibsPath(p);
#ifdef Q_OS_MAC
// Are we running with a system Python library instead?
if (originalPath.isEmpty()) {
// Keep the original Python search path.
originalPath = QString::fromWCharArray(Py_GetPath());
QString d = QFileInfo(PYKRITA_PYTHON_LIBRARY).absolutePath();
paths.append(d + "/" + p2 + "/site-packages");
paths.append(d + "/" + p2 + "/site-packages/PyQt5");
}
else {
#endif
paths.append(originalPath + "/lib-dynload");
paths.append(originalPath + "/site-packages");
paths.append(originalPath + "/site-packages/PyQt5");
#ifdef Q_OS_MAC
}
#endif
}
else {
// Use the system path
originalPath = QString::fromLocal8Bit(qgetenv("PYTHONPATH"));
}
#endif
QString joinedPaths = paths.join(pathSeparator);
if (!originalPath.isEmpty()) {
joinedPaths = joinedPaths + pathSeparator + originalPath;
}
dbgScript << "Setting python paths:" << joinedPaths;
#ifdef Q_OS_WIN
QVector<wchar_t> joinedPathsWChars(joinedPaths.size() + 1, 0);
joinedPaths.toWCharArray(joinedPathsWChars.data());
Py_SetPath(joinedPathsWChars.data());
#else
if (runningInBundle) {
QVector<wchar_t> joinedPathsWChars(joinedPaths.size() + 1, 0);
joinedPaths.toWCharArray(joinedPathsWChars.data());
Py_SetPath(joinedPathsWChars.data());
}
else {
qputenv("PYTHONPATH", joinedPaths.toLocal8Bit());
}
#endif
isPythonPathSet = true;
return true;
}
void Python::ensureInitialized()
{
if (Py_IsInitialized()) {
warnScript << "Python interpreter is already initialized, not initializing again";
} else {
dbgScript << "Initializing Python interpreter";
Py_InitializeEx(0);
if (!Py_IsInitialized()) {
errScript << "Could not initialize Python interpreter";
}
#if THREADED
PyEval_InitThreads();
s_pythonThreadState = PyGILState_GetThisThreadState();
PyEval_ReleaseThread(s_pythonThreadState);
#endif
}
}
void Python::maybeFinalize()
{
if (!Py_IsInitialized()) {
warnScript << "Python interpreter not initialized, no need to finalize";
} else {
#if THREADED
PyEval_AcquireThread(s_pythonThreadState);
#endif
Py_Finalize();
}
}
void Python::libraryUnload()
{
// no-op on Windows
#ifndef Q_OS_WIN
if (s_pythonLibrary) {
// Shut the interpreter down if it has been started.
if (s_pythonLibrary->isLoaded()) {
s_pythonLibrary->unload();
}
delete s_pythonLibrary;
s_pythonLibrary = 0;
}
#endif
}
PyObject* Python::moduleActions(const char* moduleName)
{
return kritaHandler(moduleName, "moduleGetActions");
}
PyObject* Python::moduleConfigPages(const char* const moduleName)
{
return kritaHandler(moduleName, "moduleGetConfigPages");
}
QString Python::moduleHelp(const char* moduleName)
{
QString r;
PyObject* const result = kritaHandler(moduleName, "moduleGetHelp");
if (result) {
r = unicode(result);
Py_DECREF(result);
}
return r;
}
PyObject* Python::moduleDict(const char* const moduleName)
{
PyObject* const module = moduleImport(moduleName);
if (module)
if (PyObject* const dictionary = PyModule_GetDict(module))
return dictionary;
traceback(QString("Could not get dict %1").arg(moduleName));
return 0;
}
PyObject* Python::moduleImport(const char* const moduleName)
{
PyObject* const module = PyImport_ImportModule(moduleName);
if (module)
return module;
traceback(QString("Could not import %1").arg(moduleName));
return 0;
}
-// Inspired by http://www.gossamer-threads.com/lists/python/python/150924.
+// Inspired by https://lists.gt.net/python/python/150924.
void Python::traceback(const QString& description)
{
m_traceback.clear();
if (!PyErr_Occurred())
// Return an empty string on no error.
// NOTE "Return a string?" really??
return;
PyObject* exc_typ;
PyObject* exc_val;
PyObject* exc_tb;
PyErr_Fetch(&exc_typ, &exc_val, &exc_tb);
PyErr_NormalizeException(&exc_typ, &exc_val, &exc_tb);
// Include the traceback.
if (exc_tb) {
m_traceback = "Traceback (most recent call last):\n";
PyObject* const arguments = PyTuple_New(1);
PyTuple_SetItem(arguments, 0, exc_tb);
PyObject* const result = functionCall("format_tb", "traceback", arguments);
if (result) {
for (int i = 0, j = PyList_Size(result); i < j; i++) {
PyObject* const tt = PyList_GetItem(result, i);
PyObject* const t = Py_BuildValue("(O)", tt);
char* buffer;
if (!PyArg_ParseTuple(t, "s", &buffer))
break;
m_traceback += buffer;
}
Py_DECREF(result);
}
Py_DECREF(exc_tb);
}
// Include the exception type and value.
if (exc_typ) {
PyObject* const temp = PyObject_GetAttrString(exc_typ, "__name__");
if (temp) {
m_traceback += unicode(temp);
m_traceback += ": ";
}
Py_DECREF(exc_typ);
}
if (exc_val) {
PyObject* const temp = PyObject_Str(exc_val);
if (temp) {
m_traceback += unicode(temp);
m_traceback += "\n";
}
Py_DECREF(exc_val);
}
m_traceback += description;
QStringList l = m_traceback.split("\n");
Q_FOREACH(const QString &s, l) {
errScript << s;
}
/// \todo How about to show it somewhere else than "console output"?
}
PyObject* Python::unicode(const QString& string)
{
#if PY_MAJOR_VERSION < 3
- /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */
+ /* Python 2.x. https://docs.python.org/2/c-api/unicode.html */
PyObject* s = PyString_FromString(PQ(string));
PyObject* u = PyUnicode_FromEncodedObject(s, "utf-8", "strict");
Py_DECREF(s);
return u;
#elif PY_MINOR_VERSION < 3
- /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */
+ /* Python 3.2 or less. https://docs.python.org/3.2/c-api/unicode.html#unicode-objects */
# ifdef Py_UNICODE_WIDE
return PyUnicode_DecodeUTF16((const char*)string.constData(), string.length() * 2, 0, 0);
# else
return PyUnicode_FromUnicode(string.constData(), string.length());
# endif
-#else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */
+#else /* Python 3.3 or greater. https://docs.python.org/3.3/c-api/unicode.html#unicode-objects */
return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, string.constData(), string.length());
#endif
}
QString Python::unicode(PyObject* const string)
{
#if PY_MAJOR_VERSION < 3
- /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */
+ /* Python 2.x. https://docs.python.org/2/c-api/unicode.html */
if (PyString_Check(string))
return QString(PyString_AsString(string));
else if (PyUnicode_Check(string)) {
const int unichars = PyUnicode_GetSize(string);
# ifdef HAVE_USABLE_WCHAR_T
return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars);
# else
# ifdef Py_UNICODE_WIDE
return QString::fromUcs4((const unsigned int*)PyUnicode_AsUnicode(string), unichars);
# else
return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars);
# endif
# endif
} else return QString();
#elif PY_MINOR_VERSION < 3
- /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */
+ /* Python 3.2 or less. https://docs.python.org/3.2/c-api/unicode.html#unicode-objects */
if (!PyUnicode_Check(string))
return QString();
const int unichars = PyUnicode_GetSize(string);
# ifdef HAVE_USABLE_WCHAR_T
return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars);
# else
# ifdef Py_UNICODE_WIDE
return QString::fromUcs4(PyUnicode_AsUnicode(string), unichars);
# else
return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars);
# endif
# endif
-#else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */
+#else /* Python 3.3 or greater. https://docs.python.org/3.3/c-api/unicode.html#unicode-objects */
if (!PyUnicode_Check(string))
return QString();
const int unichars = PyUnicode_GetLength(string);
if (0 != PyUnicode_READY(string))
return QString();
switch (PyUnicode_KIND(string)) {
case PyUnicode_1BYTE_KIND:
return QString::fromLatin1((const char*)PyUnicode_1BYTE_DATA(string), unichars);
case PyUnicode_2BYTE_KIND:
return QString::fromUtf16(PyUnicode_2BYTE_DATA(string), unichars);
case PyUnicode_4BYTE_KIND:
return QString::fromUcs4(PyUnicode_4BYTE_DATA(string), unichars);
default:
break;
}
return QString();
#endif
}
bool Python::isUnicode(PyObject* const string)
{
#if PY_MAJOR_VERSION < 3
return PyString_Check(string) || PyUnicode_Check(string);
#else
return PyUnicode_Check(string);
#endif
}
bool Python::prependPythonPaths(const QString& path)
{
PyObject* sys_path = itemString("path", "sys");
return bool(sys_path) && prependPythonPaths(path, sys_path);
}
bool Python::prependPythonPaths(const QStringList& paths)
{
PyObject* sys_path = itemString("path", "sys");
if (!sys_path)
return false;
/// \todo Heh, boosts' range adaptors would be good here!
QStringList reversed_paths;
std::reverse_copy(
paths.begin()
, paths.end()
, std::back_inserter(reversed_paths)
);
Q_FOREACH(const QString & path, reversed_paths)
if (!prependPythonPaths(path, sys_path))
return false;
return true;
}
bool Python::prependPythonPaths(const QString& path, PyObject* sys_path)
{
Q_ASSERT("Dir entry expected to be valid" && sys_path);
return bool(prependStringToList(sys_path, path));
}
} // namespace PyKrita
// krita: indent-width 4;
diff --git a/plugins/extensions/pykrita/sip/krita/ManagedColor.sip b/plugins/extensions/pykrita/sip/krita/ManagedColor.sip
index a8371be9ef..104c589e9c 100644
--- a/plugins/extensions/pykrita/sip/krita/ManagedColor.sip
+++ b/plugins/extensions/pykrita/sip/krita/ManagedColor.sip
@@ -1,24 +1,25 @@
class ManagedColor : QObject
{
%TypeHeaderCode
#include "ManagedColor.h"
%End
ManagedColor(const ManagedColor & __0);
public:
ManagedColor(const QString &colorModel, const QString &colorDepth, const QString &colorProfile, QObject *parent = 0);
bool operator==(const ManagedColor &other) const;
QColor colorForCanvas(Canvas *canvas) const;
+ static ManagedColor *fromQColor(const QColor &qcolor, Canvas *canvas = 0);
QString colorDepth() const;
QString colorModel() const;
QString colorProfile() const;
bool setColorProfile(const QString &colorProfile);
bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile);
QVector<float> components() const;
QVector<float> componentsOrdered() const;
void setComponents(const QVector<float> &values);
QString toXML() const;
void fromXML(const QString &xml);
QString toQString();
private:
};
diff --git a/plugins/extensions/qmic/WdgQMicSettings.ui b/plugins/extensions/qmic/WdgQMicSettings.ui
index dc1678f3a8..1d70a449b0 100644
--- a/plugins/extensions/qmic/WdgQMicSettings.ui
+++ b/plugins/extensions/qmic/WdgQMicSettings.ui
@@ -1,79 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgQMicSettings</class>
<widget class="QWidget" name="WdgQMicSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select the location of the G'Mic-Qt plugin. You can download the plugin from the &lt;a href=&quot;http://gmic.eu/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#2980b9;&quot;&gt;G'Mic website&lt;/span&gt;&lt;/a&gt;. Make sure you download the special version for Krita, not the standalone or the GIMP version.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select the location of the G'Mic-Qt plugin. You can download the plugin from the &lt;a href=&quot;https://gmic.eu/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#2980b9;&quot;&gt;G'Mic website&lt;/span&gt;&lt;/a&gt;. Make sure you download the special version for Krita, not the standalone or the GIMP version.&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>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Plugin:</string>
</property>
</widget>
</item>
<item>
<widget class="KisFileNameRequester" name="fileRequester" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</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>
</widget>
<customwidgets>
<customwidget>
<class>KisFileNameRequester</class>
<extends>QWidget</extends>
<header>kis_file_name_requester.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/filters/blur/kis_blur_filter.cpp b/plugins/filters/blur/kis_blur_filter.cpp
index 183165b751..6138bbab4f 100644
--- a/plugins/filters/blur/kis_blur_filter.cpp
+++ b/plugins/filters/blur/kis_blur_filter.cpp
@@ -1,139 +1,137 @@
/*
* This file is part of Krita
*
* Copyright (c) 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.
*/
#include <compositeops/KoVcMultiArchBuildSupport.h> //MSVC requires that Vc come first
#include "kis_blur_filter.h"
#include <KoCompositeOp.h>
#include <kis_convolution_kernel.h>
#include <kis_convolution_painter.h>
#include "kis_wdg_blur.h"
#include "ui_wdgblur.h"
#include <filter/kis_filter_category_ids.h>
#include <filter/kis_filter_configuration.h>
#include <kis_selection.h>
#include <kis_paint_device.h>
#include <kis_processing_information.h>
#include "kis_mask_generator.h"
#include "kis_lod_transform.h"
KisBlurFilter::KisBlurFilter() : KisFilter(id(), FiltersCategoryBlurId, i18n("&Blur..."))
{
setSupportsPainting(true);
setSupportsAdjustmentLayers(true);
setSupportsLevelOfDetail(true);
setColorSpaceIndependence(FULLY_INDEPENDENT);
}
KisConfigWidget * KisBlurFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const
{
return new KisWdgBlur(parent);
}
KisFilterConfigurationSP KisBlurFilter::defaultConfiguration() const
{
KisFilterConfigurationSP config = factoryConfiguration();
config->setProperty("halfWidth", 5);
config->setProperty("halfHeight", 5);
config->setProperty("rotate", 0);
config->setProperty("strength", 0);
config->setProperty("shape", 0);
return config;
}
void KisBlurFilter::processImpl(KisPaintDeviceSP device,
const QRect& rect,
const KisFilterConfigurationSP _config,
KoUpdater* progressUpdater
) const
{
QPoint srcTopLeft = rect.topLeft();
Q_ASSERT(device != 0);
KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1);
KisLodTransformScalar t(device);
QVariant value;
const uint halfWidth = t.scale((config->getProperty("halfWidth", value)) ? value.toUInt() : 5);
const uint halfHeight = t.scale((config->getProperty("halfHeight", value)) ? value.toUInt() : 5);
int shape = (config->getProperty("shape", value)) ? value.toInt() : 0;
uint width = 2 * halfWidth + 1;
uint height = 2 * halfHeight + 1;
- float aspectRatio = (float) width / height;
+ qreal aspectRatio = (qreal) height / width;
int rotate = (config->getProperty("rotate", value)) ? value.toInt() : 0;
- int strength = 100 - (config->getProperty("strength", value) ? value.toUInt() : 0);
-
- int hFade = (halfWidth * strength) / 100;
- int vFade = (halfHeight * strength) / 100;
+ qreal strength = (config->getProperty("strength", value) ? value.toUInt() : 0) / (qreal) 100;
+ qreal hFade, vFade = strength;
KisMaskGenerator* kas;
dbgKrita << width << "" << height << "" << hFade << "" << vFade;
switch (shape) {
case 1:
kas = new KisRectangleMaskGenerator(width, aspectRatio, hFade, vFade, 2, true);
break;
case 0:
default:
kas = new KisCircleMaskGenerator(width, aspectRatio, hFade, vFade, 2, true);
break;
}
QBitArray channelFlags;
if (config) {
channelFlags = config->channelFlags();
}
if (channelFlags.isEmpty() || !config) {
channelFlags = QBitArray(device->colorSpace()->channelCount(), true);
}
KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMaskGenerator(kas, rotate * M_PI / 180.0);
delete kas;
KisConvolutionPainter painter(device);
painter.setChannelFlags(channelFlags);
painter.setProgress(progressUpdater);
painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT);
}
QRect KisBlurFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const
{
KisLodTransformScalar t(lod);
QVariant value;
const int halfWidth = t.scale(_config->getProperty("halfWidth", value) ? value.toUInt() : 5);
const int halfHeight = t.scale(_config->getProperty("halfHeight", value) ? value.toUInt() : 5);
return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2);
}
QRect KisBlurFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const
{
KisLodTransformScalar t(lod);
QVariant value;
const int halfWidth = t.scale(_config->getProperty("halfWidth", value) ? value.toUInt() : 5);
const int halfHeight = t.scale(_config->getProperty("halfHeight", value) ? value.toUInt() : 5);
return rect.adjusted(-halfWidth, -halfHeight, halfWidth, halfHeight);
}
diff --git a/plugins/filters/blur/wdgblur.ui b/plugins/filters/blur/wdgblur.ui
index 8909d81fa0..d45478839b 100644
--- a/plugins/filters/blur/wdgblur.ui
+++ b/plugins/filters/blur/wdgblur.ui
@@ -1,221 +1,227 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgBlur</class>
<widget class="QWidget" name="WdgBlur">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>280</width>
<height>190</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="0" column="0">
<widget class="QLabel" name="textLabel1">
<property name="text">
<string>Horizontal Radius:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisIntParseSpinBox" name="intHalfWidth">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>1</number>
</property>
+ <property name="maximum">
+ <number>1000</number>
+ </property>
</widget>
</item>
<item row="0" column="3" rowspan="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel" name="textLabel2">
<property name="text">
<string>Vertical Radius:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisIntParseSpinBox" name="intHalfHeight">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>1</number>
</property>
+ <property name="maximum">
+ <number>1000</number>
+ </property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="textLabel3">
<property name="text">
<string>Strength:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="KisIntParseSpinBox" name="intStrength">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="textLabel5">
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Angle:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="KisIntParseSpinBox" name="intAngle">
<property name="suffix">
<string>°</string>
</property>
<property name="maximum">
<number>360</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="textLabel4">
<property name="text">
<string>Shape:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="KComboBox" name="cbShape">
<item>
<property name="text">
<string>Circle</string>
</property>
</item>
<item>
<property name="text">
<string>Rectangle</string>
</property>
</item>
</widget>
</item>
<item row="4" column="3">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0" colspan="2">
<spacer>
<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>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2" rowspan="2">
<widget class="KoAspectButton" name="aspectButton" native="true"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KComboBox</class>
<extends>QComboBox</extends>
<header>kcombobox.h</header>
</customwidget>
<customwidget>
<class>KoAspectButton</class>
<extends>QWidget</extends>
<header>KoAspectButton.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KisIntParseSpinBox</class>
<extends>QSpinBox</extends>
<header>kis_int_parse_spin_box.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/filters/tests/CMakeLists.txt b/plugins/filters/tests/CMakeLists.txt
index 80eaef3e4a..2275323b9f 100644
--- a/plugins/filters/tests/CMakeLists.txt
+++ b/plugins/filters/tests/CMakeLists.txt
@@ -1,12 +1,17 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( )
macro_add_unittest_definitions()
+##### Tests that currently fail and should be fixed #####
-########### next target ###############
+include(KritaAddBrokenUnitTest)
-ecm_add_tests(
- kis_all_filter_test.cpp
- kis_crash_filter_test.cpp
+krita_add_broken_unit_test( kis_all_filter_test.cpp
+ TEST_NAME kis_all_filter_test.cpp
+ NAME_PREFIX "krita-filters-"
+ LINK_LIBRARIES kritaimage Qt5::Test)
+
+krita_add_broken_unit_test( kis_crash_filter_test.cpp
+ TEST_NAME kis_crash_filter_test.cpp
NAME_PREFIX "krita-filters-"
LINK_LIBRARIES kritaimage Qt5::Test)
diff --git a/plugins/flake/artistictextshape/ArtisticTextShape.cpp b/plugins/flake/artistictextshape/ArtisticTextShape.cpp
index 588ed86d0d..4746a5653c 100644
--- a/plugins/flake/artistictextshape/ArtisticTextShape.cpp
+++ b/plugins/flake/artistictextshape/ArtisticTextShape.cpp
@@ -1,1401 +1,1400 @@
/* This file is part of the KDE project
* Copyright (C) 2007-2009,2011 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2008 Rob Buis <buis@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 "ArtisticTextShape.h"
#include "ArtisticTextLoadingContext.h"
#include <KoPathShape.h>
#include <KoShapeSavingContext.h>
#include <KoShapeLoadingContext.h>
#include <KoXmlNS.h>
#include <KoXmlWriter.h>
#include <KoXmlReader.h>
#include <KoPathShapeLoader.h>
#include <KoShapeBackground.h>
#include <KoEmbeddedDocumentSaver.h>
#include <SvgSavingContext.h>
#include <SvgLoadingContext.h>
#include <SvgGraphicContext.h>
#include <SvgUtil.h>
#include <SvgStyleParser.h>
#include <SvgWriter.h>
#include <SvgStyleWriter.h>
#include <KisQPainterStateSaver.h>
#include <klocalizedstring.h>
#include <QDebug>
#include <QBuffer>
#include <QPen>
#include <QPainter>
#include <QFont>
ArtisticTextShape::ArtisticTextShape()
: m_path(0)
, m_startOffset(0.0)
, m_textAnchor(AnchorStart)
, m_textUpdateCounter(0)
, m_defaultFont("ComicSans", 20)
{
setShapeId(ArtisticTextShapeID);
cacheGlyphOutlines();
updateSizeAndPosition();
}
ArtisticTextShape::~ArtisticTextShape()
{
if (m_path) {
m_path->removeDependee(this);
}
}
KoShape *ArtisticTextShape::cloneShape() const
{
ArtisticTextShape *clone = new ArtisticTextShape();
clone->m_ranges = m_ranges;
if (m_path) {
clone->m_path = static_cast<KoPathShape*>(m_path->cloneShape());
}
clone->m_charOutlines = m_charOutlines;
clone->m_startOffset = m_startOffset;
clone->m_outlineOrigin = m_outlineOrigin;
clone->m_outline = m_outline;
clone->m_baseline = m_baseline;
clone->m_textAnchor = m_textAnchor;
clone->m_charOffsets = m_charOffsets;
clone->m_charPositions = m_charPositions;
clone->m_textUpdateCounter = m_textUpdateCounter;
clone->m_defaultFont = m_defaultFont;
return clone;
}
-void ArtisticTextShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
+void ArtisticTextShape::paint(QPainter &painter, KoShapePaintingContext &paintContext) const
{
KisQPainterStateSaver saver(&painter);
- applyConversion(painter, converter);
if (background()) {
- background()->paint(painter, converter, paintContext, outline());
+ background()->paint(painter, paintContext, outline());
}
}
void ArtisticTextShape::saveOdf(KoShapeSavingContext &context) const
{
SvgWriter svgWriter(QList<KoShape *>() << const_cast<ArtisticTextShape *>(this));
QByteArray fileContent;
QBuffer fileContentDevice(&fileContent);
if (!fileContentDevice.open(QIODevice::WriteOnly)) {
return;
}
if (!svgWriter.save(fileContentDevice, size())) {
qWarning() << "Could not write svg content";
return;
}
const QString fileName = context.embeddedSaver().getFilename("SvgImages/Image");
const QString mimeType = "image/svg+xml";
context.xmlWriter().startElement("draw:frame");
context.embeddedSaver().embedFile(context.xmlWriter(), "draw:image", fileName, mimeType.toLatin1(), fileContent);
context.xmlWriter().endElement(); // draw:frame
}
bool ArtisticTextShape::loadOdf(const KoXmlElement &/*element*/, KoShapeLoadingContext &/*context*/)
{
return false;
}
QSizeF ArtisticTextShape::size() const
{
if (m_ranges.isEmpty()) {
return nullBoundBox().size();
} else {
return outline().boundingRect().size();
}
}
void ArtisticTextShape::setSize(const QSizeF &newSize)
{
QSizeF oldSize = size();
if (!oldSize.isNull()) {
qreal zoomX = newSize.width() / oldSize.width();
qreal zoomY = newSize.height() / oldSize.height();
QTransform matrix(zoomX, 0, 0, zoomY, 0, 0);
update();
applyTransformation(matrix);
update();
}
KoShape::setSize(newSize);
}
QPainterPath ArtisticTextShape::outline() const
{
return m_outline;
}
QRectF ArtisticTextShape::nullBoundBox() const
{
QFontMetrics metrics(defaultFont());
QPointF tl(0.0, -metrics.ascent());
QPointF br(metrics.averageCharWidth(), metrics.descent());
return QRectF(tl, br);
}
QFont ArtisticTextShape::defaultFont() const
{
return m_defaultFont;
}
qreal baselineShiftForFontSize(const ArtisticTextRange &range, qreal fontSize)
{
switch (range.baselineShift()) {
case ArtisticTextRange::Sub:
return fontSize / 3.; // taken from wikipedia
case ArtisticTextRange::Super:
return -fontSize / 3.; // taken from wikipedia
case ArtisticTextRange::Percent:
return range.baselineShiftValue() * fontSize;
case ArtisticTextRange::Length:
return range.baselineShiftValue();
default:
return 0.0;
}
}
QVector<QPointF> ArtisticTextShape::calculateAbstractCharacterPositions()
{
const int totalTextLength = plainText().length();
QVector<QPointF> charPositions;
// one more than the number of characters for position after the last character
charPositions.resize(totalTextLength + 1);
// the character index within the text shape
int globalCharIndex = 0;
QPointF charPos(0, 0);
QPointF advance(0, 0);
const bool attachedToPath = isOnPath();
Q_FOREACH (const ArtisticTextRange &range, m_ranges) {
QFontMetricsF metrics(QFont(range.font(), &m_paintDevice));
const QString textRange = range.text();
const qreal letterSpacing = range.letterSpacing();
const int localTextLength = textRange.length();
const bool absoluteXOffset = range.xOffsetType() == ArtisticTextRange::AbsoluteOffset;
const bool absoluteYOffset = range.yOffsetType() == ArtisticTextRange::AbsoluteOffset;
// set baseline shift
const qreal baselineShift = baselineShiftForFontSize(range, defaultFont().pointSizeF());
for (int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) {
// apply offset to character
if (range.hasXOffset(localCharIndex)) {
if (absoluteXOffset) {
charPos.rx() = range.xOffset(localCharIndex);
} else {
charPos.rx() += range.xOffset(localCharIndex);
}
} else {
charPos.rx() += advance.x();
}
if (range.hasYOffset(localCharIndex)) {
if (absoluteYOffset) {
// when attached to a path, absolute y-offsets are ignored
if (!attachedToPath) {
charPos.ry() = range.yOffset(localCharIndex);
}
} else {
charPos.ry() += range.yOffset(localCharIndex);
}
} else {
charPos.ry() += advance.y();
}
// apply baseline shift
charPos.ry() += baselineShift;
// save character position of current character
charPositions[globalCharIndex] = charPos;
// advance character position
advance = QPointF(metrics.width(textRange[localCharIndex]) + letterSpacing, 0.0);
charPos.ry() -= baselineShift;
}
}
charPositions[globalCharIndex] = charPos + advance;
return charPositions;
}
void ArtisticTextShape::createOutline()
{
// reset relevant data
m_outline = QPainterPath();
m_charPositions.clear();
m_charOffsets.clear();
// calculate character positions in baseline coordinates
m_charPositions = calculateAbstractCharacterPositions();
// the character index within the text shape
int globalCharIndex = 0;
if (isOnPath()) {
// one more than the number of characters for offset after the last character
m_charOffsets.insert(0, m_charPositions.size(), -1);
// the current character position
qreal startCharOffset = m_startOffset * m_baseline.length();
// calculate total text width
qreal totalTextWidth = 0.0;
foreach (const ArtisticTextRange &range, m_ranges) {
QFontMetricsF metrics(QFont(range.font(), &m_paintDevice));
totalTextWidth += metrics.width(range.text());
}
// adjust starting character position to anchor point
if (m_textAnchor == AnchorMiddle) {
startCharOffset -= 0.5 * totalTextWidth;
} else if (m_textAnchor == AnchorEnd) {
startCharOffset -= totalTextWidth;
}
QPointF pathPoint;
qreal rotation = 0.0;
qreal charOffset;
foreach (const ArtisticTextRange &range, m_ranges) {
QFontMetricsF metrics(QFont(range.font(), &m_paintDevice));
const QString localText = range.text();
const int localTextLength = localText.length();
for (int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) {
QPointF charPos = m_charPositions[globalCharIndex];
// apply advance along baseline
charOffset = startCharOffset + charPos.x();
const qreal charMidPoint = charOffset + 0.5 * metrics.width(localText[localCharIndex]);
// get the normalized position of the middle of the character
const qreal midT = m_baseline.percentAtLength(charMidPoint);
// is the character midpoint beyond the baseline ends?
if (midT <= 0.0 || midT >= 1.0) {
if (midT >= 1.0) {
pathPoint = m_baseline.pointAtPercent(1.0);
for (int i = globalCharIndex; i < m_charPositions.size(); ++i) {
m_charPositions[i] = pathPoint;
m_charOffsets[i] = 1.0;
}
break;
} else {
m_charPositions[globalCharIndex] = m_baseline.pointAtPercent(0.0);
m_charOffsets[globalCharIndex] = 0.0;
continue;
}
}
// get the percent value of the actual char position
qreal t = m_baseline.percentAtLength(charOffset);
// get the path point of the given path position
pathPoint = m_baseline.pointAtPercent(t);
// save character offset as fraction of baseline length
m_charOffsets[globalCharIndex] = m_baseline.percentAtLength(charOffset);
// save character position as point
m_charPositions[globalCharIndex] = pathPoint;
// get the angle at the given path position
const qreal angle = m_baseline.angleAtPercent(midT);
if (range.hasRotation(localCharIndex)) {
rotation = range.rotation(localCharIndex);
}
QTransform m;
m.translate(pathPoint.x(), pathPoint.y());
m.rotate(360. - angle + rotation);
m.translate(0.0, charPos.y());
m_outline.addPath(m.map(m_charOutlines[globalCharIndex]));
}
}
// save offset and position after last character
m_charOffsets[globalCharIndex] = m_baseline.percentAtLength(startCharOffset + m_charPositions[globalCharIndex].x());
m_charPositions[globalCharIndex] = m_baseline.pointAtPercent(m_charOffsets[globalCharIndex]);
} else {
qreal rotation = 0.0;
Q_FOREACH (const ArtisticTextRange &range, m_ranges) {
const QString textRange = range.text();
const int localTextLength = textRange.length();
for (int localCharIndex = 0; localCharIndex < localTextLength; ++localCharIndex, ++globalCharIndex) {
const QPointF &charPos = m_charPositions[globalCharIndex];
if (range.hasRotation(localCharIndex)) {
rotation = range.rotation(localCharIndex);
}
QTransform m;
m.translate(charPos.x(), charPos.y());
m.rotate(rotation);
m_outline.addPath(m.map(m_charOutlines[globalCharIndex]));
}
}
}
}
void ArtisticTextShape::setPlainText(const QString &newText)
{
if (plainText() == newText) {
return;
}
beginTextUpdate();
if (newText.isEmpty()) {
// remove all text ranges
m_ranges.clear();
} else if (isEmpty()) {
// create new text range
m_ranges.append(ArtisticTextRange(newText, defaultFont()));
} else {
// set text to first range
m_ranges.first().setText(newText);
// remove all ranges except the first
while (m_ranges.count() > 1) {
m_ranges.pop_back();
}
}
finishTextUpdate();
}
QString ArtisticTextShape::plainText() const
{
QString allText;
Q_FOREACH (const ArtisticTextRange &range, m_ranges) {
allText += range.text();
}
return allText;
}
QList<ArtisticTextRange> ArtisticTextShape::text() const
{
return m_ranges;
}
bool ArtisticTextShape::isEmpty() const
{
return m_ranges.isEmpty();
}
void ArtisticTextShape::clear()
{
beginTextUpdate();
m_ranges.clear();
finishTextUpdate();
}
void ArtisticTextShape::setFont(const QFont &newFont)
{
// no text
if (isEmpty()) {
return;
}
const int rangeCount = m_ranges.count();
// only one text range with the same font
if (rangeCount == 1 && m_ranges.first().font() == newFont) {
return;
}
beginTextUpdate();
// set font on ranges
for (int i = 0; i < rangeCount; ++i) {
m_ranges[i].setFont(newFont);
}
m_defaultFont = newFont;
finishTextUpdate();
}
void ArtisticTextShape::setFont(int charIndex, int charCount, const QFont &font)
{
if (isEmpty() || charCount <= 0) {
return;
}
if (charIndex == 0 && charCount == plainText().length()) {
setFont(font);
return;
}
CharIndex charPos = indexOfChar(charIndex);
if (charPos.first < 0 || charPos.first >= m_ranges.count()) {
return;
}
beginTextUpdate();
int remainingCharCount = charCount;
while (remainingCharCount > 0) {
ArtisticTextRange &currRange = m_ranges[charPos.first];
// does this range have a different font ?
if (currRange.font() != font) {
if (charPos.second == 0 && currRange.text().length() < remainingCharCount) {
// set font on all characters of this range
currRange.setFont(font);
remainingCharCount -= currRange.text().length();
} else {
ArtisticTextRange changedRange = currRange.extract(charPos.second, remainingCharCount);
changedRange.setFont(font);
if (charPos.second == 0) {
m_ranges.insert(charPos.first, changedRange);
} else if (charPos.second >= currRange.text().length()) {
m_ranges.insert(charPos.first + 1, changedRange);
} else {
ArtisticTextRange remainingRange = currRange.extract(charPos.second);
m_ranges.insert(charPos.first + 1, changedRange);
m_ranges.insert(charPos.first + 2, remainingRange);
}
charPos.first++;
remainingCharCount -= changedRange.text().length();
}
}
charPos.first++;
if (charPos.first >= m_ranges.count()) {
break;
}
charPos.second = 0;
}
finishTextUpdate();
}
QFont ArtisticTextShape::fontAt(int charIndex) const
{
if (isEmpty()) {
return defaultFont();
}
if (charIndex < 0) {
return m_ranges.first().font();
}
const int rangeIndex = indexOfChar(charIndex).first;
if (rangeIndex < 0) {
return m_ranges.last().font();
}
return m_ranges[rangeIndex].font();
}
void ArtisticTextShape::setStartOffset(qreal offset)
{
if (m_startOffset == offset) {
return;
}
update();
m_startOffset = qBound<qreal>(0.0, offset, 1.0);
updateSizeAndPosition();
update();
notifyChanged();
}
qreal ArtisticTextShape::startOffset() const
{
return m_startOffset;
}
qreal ArtisticTextShape::baselineOffset() const
{
return m_charPositions.value(0).y();
}
void ArtisticTextShape::setTextAnchor(TextAnchor anchor)
{
if (anchor == m_textAnchor) {
return;
}
qreal totalTextWidth = 0.0;
foreach (const ArtisticTextRange &range, m_ranges) {
QFontMetricsF metrics(QFont(range.font(), &m_paintDevice));
totalTextWidth += metrics.width(range.text());
}
qreal oldOffset = 0.0;
if (m_textAnchor == AnchorMiddle) {
oldOffset = -0.5 * totalTextWidth;
} else if (m_textAnchor == AnchorEnd) {
oldOffset = -totalTextWidth;
}
m_textAnchor = anchor;
qreal newOffset = 0.0;
if (m_textAnchor == AnchorMiddle) {
newOffset = -0.5 * totalTextWidth;
} else if (m_textAnchor == AnchorEnd) {
newOffset = -totalTextWidth;
}
update();
updateSizeAndPosition();
if (! isOnPath()) {
QTransform m;
m.translate(newOffset - oldOffset, 0.0);
setTransformation(transformation() * m);
}
update();
notifyChanged();
}
ArtisticTextShape::TextAnchor ArtisticTextShape::textAnchor() const
{
return m_textAnchor;
}
bool ArtisticTextShape::putOnPath(KoPathShape *path)
{
if (! path) {
return false;
}
if (path->outline().isEmpty()) {
return false;
}
if (! path->addDependee(this)) {
return false;
}
update();
m_path = path;
// use the paths outline converted to document coordinates as the baseline
- m_baseline = m_path->absoluteTransformation(0).map(m_path->outline());
+ m_baseline = m_path->absoluteTransformation().map(m_path->outline());
// reset transformation
setTransformation(QTransform());
updateSizeAndPosition();
// move to correct position
setAbsolutePosition(m_outlineOrigin, KoFlake::TopLeft);
update();
return true;
}
bool ArtisticTextShape::putOnPath(const QPainterPath &path)
{
if (path.isEmpty()) {
return false;
}
update();
if (m_path) {
m_path->removeDependee(this);
}
m_path = 0;
m_baseline = path;
// reset transformation
setTransformation(QTransform());
updateSizeAndPosition();
// move to correct position
setAbsolutePosition(m_outlineOrigin, KoFlake::TopLeft);
update();
return true;
}
void ArtisticTextShape::removeFromPath()
{
update();
if (m_path) {
m_path->removeDependee(this);
}
m_path = 0;
m_baseline = QPainterPath();
updateSizeAndPosition();
update();
}
bool ArtisticTextShape::isOnPath() const
{
return (m_path != 0 || ! m_baseline.isEmpty());
}
ArtisticTextShape::LayoutMode ArtisticTextShape::layout() const
{
if (m_path) {
return OnPathShape;
} else if (! m_baseline.isEmpty()) {
return OnPath;
} else {
return Straight;
}
}
QPainterPath ArtisticTextShape::baseline() const
{
return m_baseline;
}
KoPathShape *ArtisticTextShape::baselineShape() const
{
return m_path;
}
QList<ArtisticTextRange> ArtisticTextShape::removeText(int charIndex, int charCount)
{
QList<ArtisticTextRange> extractedRanges;
if (!charCount) {
return extractedRanges;
}
if (charIndex == 0 && charCount >= plainText().length()) {
beginTextUpdate();
extractedRanges = m_ranges;
m_ranges.clear();
finishTextUpdate();
return extractedRanges;
}
CharIndex charPos = indexOfChar(charIndex);
if (charPos.first < 0 || charPos.first >= m_ranges.count()) {
return extractedRanges;
}
beginTextUpdate();
int extractedTextLength = 0;
while (extractedTextLength < charCount) {
ArtisticTextRange r = m_ranges[charPos.first].extract(charPos.second, charCount - extractedTextLength);
extractedTextLength += r.text().length();
extractedRanges.append(r);
if (extractedTextLength == charCount) {
break;
}
charPos.first++;
if (charPos.first >= m_ranges.count()) {
break;
}
charPos.second = 0;
}
// now remove all empty ranges
const int rangeCount = m_ranges.count();
for (int i = charPos.first; i < rangeCount; ++i) {
if (m_ranges[charPos.first].text().isEmpty()) {
m_ranges.removeAt(charPos.first);
}
}
finishTextUpdate();
return extractedRanges;
}
QList<ArtisticTextRange> ArtisticTextShape::copyText(int charIndex, int charCount)
{
QList<ArtisticTextRange> extractedRanges;
if (!charCount) {
return extractedRanges;
}
CharIndex charPos = indexOfChar(charIndex);
if (charPos.first < 0 || charPos.first >= m_ranges.count()) {
return extractedRanges;
}
int extractedTextLength = 0;
while (extractedTextLength < charCount) {
ArtisticTextRange copy = m_ranges[charPos.first];
ArtisticTextRange r = copy.extract(charPos.second, charCount - extractedTextLength);
extractedTextLength += r.text().length();
extractedRanges.append(r);
if (extractedTextLength == charCount) {
break;
}
charPos.first++;
if (charPos.first >= m_ranges.count()) {
break;
}
charPos.second = 0;
}
return extractedRanges;
}
void ArtisticTextShape::insertText(int charIndex, const QString &str)
{
if (isEmpty()) {
appendText(str);
return;
}
CharIndex charPos = indexOfChar(charIndex);
if (charIndex < 0) {
// insert before first character
charPos = CharIndex(0, 0);
} else if (charIndex >= plainText().length()) {
// insert after last character
charPos = CharIndex(m_ranges.count() - 1, m_ranges.last().text().length());
}
// check range index, just in case
if (charPos.first < 0) {
return;
}
beginTextUpdate();
m_ranges[charPos.first].insertText(charPos.second, str);
finishTextUpdate();
}
void ArtisticTextShape::insertText(int charIndex, const ArtisticTextRange &textRange)
{
QList<ArtisticTextRange> ranges;
ranges.append(textRange);
insertText(charIndex, ranges);
}
void ArtisticTextShape::insertText(int charIndex, const QList<ArtisticTextRange> &textRanges)
{
if (isEmpty()) {
beginTextUpdate();
m_ranges = textRanges;
finishTextUpdate();
return;
}
CharIndex charPos = indexOfChar(charIndex);
if (charIndex < 0) {
// insert before first character
charPos = CharIndex(0, 0);
} else if (charIndex >= plainText().length()) {
// insert after last character
charPos = CharIndex(m_ranges.count() - 1, m_ranges.last().text().length());
}
// check range index, just in case
if (charPos.first < 0) {
return;
}
beginTextUpdate();
ArtisticTextRange &hitRange = m_ranges[charPos.first];
if (charPos.second == 0) {
// insert ranges before the hit range
Q_FOREACH (const ArtisticTextRange &range, textRanges) {
m_ranges.insert(charPos.first, range);
charPos.first++;
}
} else if (charPos.second == hitRange.text().length()) {
// insert ranges after the hit range
Q_FOREACH (const ArtisticTextRange &range, textRanges) {
m_ranges.insert(charPos.first + 1, range);
charPos.first++;
}
} else {
// insert ranges inside hit range
ArtisticTextRange right = hitRange.extract(charPos.second, hitRange.text().length());
m_ranges.insert(charPos.first + 1, right);
// now insert after the left part of hit range
Q_FOREACH (const ArtisticTextRange &range, textRanges) {
m_ranges.insert(charPos.first + 1, range);
charPos.first++;
}
}
// TODO: merge ranges with same style
finishTextUpdate();
}
void ArtisticTextShape::appendText(const QString &text)
{
beginTextUpdate();
if (isEmpty()) {
m_ranges.append(ArtisticTextRange(text, defaultFont()));
} else {
m_ranges.last().appendText(text);
}
finishTextUpdate();
}
void ArtisticTextShape::appendText(const ArtisticTextRange &text)
{
beginTextUpdate();
m_ranges.append(text);
// TODO: merge ranges with same style
finishTextUpdate();
}
bool ArtisticTextShape::replaceText(int charIndex, int charCount, const ArtisticTextRange &textRange)
{
QList<ArtisticTextRange> ranges;
ranges.append(textRange);
return replaceText(charIndex, charCount, ranges);
}
bool ArtisticTextShape::replaceText(int charIndex, int charCount, const QList<ArtisticTextRange> &textRanges)
{
CharIndex charPos = indexOfChar(charIndex);
if (charPos.first < 0 || !charCount) {
return false;
}
beginTextUpdate();
removeText(charIndex, charCount);
insertText(charIndex, textRanges);
finishTextUpdate();
return true;
}
qreal ArtisticTextShape::charAngleAt(int charIndex) const
{
if (isOnPath()) {
qreal t = m_charOffsets.value(qBound(0, charIndex, m_charOffsets.size() - 1));
return m_baseline.angleAtPercent(t);
}
return 0.0;
}
QPointF ArtisticTextShape::charPositionAt(int charIndex) const
{
return m_charPositions.value(qBound(0, charIndex, m_charPositions.size() - 1));
}
QRectF ArtisticTextShape::charExtentsAt(int charIndex) const
{
CharIndex charPos = indexOfChar(charIndex);
if (charIndex < 0 || isEmpty()) {
charPos = CharIndex(0, 0);
} else if (charPos.first < 0) {
charPos = CharIndex(m_ranges.count() - 1, m_ranges.last().text().length() - 1);
}
if (charPos.first < m_ranges.size()) {
const ArtisticTextRange &range = m_ranges.at(charPos.first);
QFontMetrics metrics(range.font());
int w = metrics.charWidth(range.text(), charPos.second);
return QRectF(0, 0, w, metrics.height());
}
return QRectF();
}
void ArtisticTextShape::updateSizeAndPosition(bool global)
{
- QTransform shapeTransform = absoluteTransformation(0);
+ QTransform shapeTransform = absoluteTransformation();
// determine baseline position in document coordinates
QPointF oldBaselinePosition = shapeTransform.map(QPointF(0, baselineOffset()));
createOutline();
QRectF bbox = m_outline.boundingRect();
if (bbox.isEmpty()) {
bbox = nullBoundBox();
}
if (isOnPath()) {
// calculate the offset we have to apply to keep our position
QPointF offset = m_outlineOrigin - bbox.topLeft();
// cache topleft corner of baseline path
m_outlineOrigin = bbox.topLeft();
// the outline position is in document coordinates
// so we adjust our position
QTransform m;
m.translate(-offset.x(), -offset.y());
global ? applyAbsoluteTransformation(m) : applyTransformation(m);
} else {
// determine the new baseline position in document coordinates
QPointF newBaselinePosition = shapeTransform.map(QPointF(0, -bbox.top()));
// apply a transformation to compensate any translation of
// our baseline position
QPointF delta = oldBaselinePosition - newBaselinePosition;
QTransform m;
m.translate(delta.x(), delta.y());
applyAbsoluteTransformation(m);
}
setSize(bbox.size());
// map outline to shape coordinate system
QTransform normalizeMatrix;
normalizeMatrix.translate(-bbox.left(), -bbox.top());
m_outline = normalizeMatrix.map(m_outline);
const int charCount = m_charPositions.count();
for (int i = 0; i < charCount; ++i) {
m_charPositions[i] = normalizeMatrix.map(m_charPositions[i]);
}
}
void ArtisticTextShape::cacheGlyphOutlines()
{
m_charOutlines.clear();
Q_FOREACH (const ArtisticTextRange &range, m_ranges) {
const QString rangeText = range.text();
const QFont rangeFont(range.font(), &m_paintDevice);
const int textLength = rangeText.length();
for (int charIdx = 0; charIdx < textLength; ++charIdx) {
QPainterPath charOutline;
charOutline.addText(QPointF(), rangeFont, rangeText[charIdx]);
m_charOutlines.append(charOutline);
}
}
}
void ArtisticTextShape::shapeChanged(ChangeType type, KoShape *shape)
{
if (m_path && shape == m_path) {
if (type == KoShape::Deleted) {
// baseline shape was deleted
m_path = 0;
} else if (type == KoShape::ParentChanged && !shape->parent()) {
// baseline shape was probably removed from the document
m_path->removeDependee(this);
m_path = 0;
} else {
update();
// use the paths outline converted to document coordinates as the baseline
- m_baseline = m_path->absoluteTransformation(0).map(m_path->outline());
+ m_baseline = m_path->absoluteTransformation().map(m_path->outline());
updateSizeAndPosition(true);
update();
}
}
}
CharIndex ArtisticTextShape::indexOfChar(int charIndex) const
{
if (isEmpty()) {
return CharIndex(-1, -1);
}
int rangeIndex = 0;
int textLength = 0;
Q_FOREACH (const ArtisticTextRange &range, m_ranges) {
const int rangeTextLength = range.text().length();
if (static_cast<int>(charIndex) < textLength + rangeTextLength) {
return CharIndex(rangeIndex, charIndex - textLength);
}
textLength += rangeTextLength;
rangeIndex++;
}
return CharIndex(-1, -1);
}
void ArtisticTextShape::beginTextUpdate()
{
if (m_textUpdateCounter) {
return;
}
m_textUpdateCounter++;
update();
}
void ArtisticTextShape::finishTextUpdate()
{
if (!m_textUpdateCounter) {
return;
}
cacheGlyphOutlines();
updateSizeAndPosition();
update();
notifyChanged();
m_textUpdateCounter--;
}
bool ArtisticTextShape::saveSvg(SvgSavingContext &context)
{
context.shapeWriter().startElement("text", false);
context.shapeWriter().addAttribute("id", context.getID(this));
SvgStyleWriter::saveSvgStyle(this, context);
const QList<ArtisticTextRange> formattedText = text();
// if we have only a single text range, save the font on the text element
const bool hasSingleRange = formattedText.size() == 1;
if (hasSingleRange) {
saveSvgFont(formattedText.first().font(), context);
}
qreal anchorOffset = 0.0;
if (textAnchor() == ArtisticTextShape::AnchorMiddle) {
anchorOffset += 0.5 * this->size().width();
context.shapeWriter().addAttribute("text-anchor", "middle");
} else if (textAnchor() == ArtisticTextShape::AnchorEnd) {
anchorOffset += this->size().width();
context.shapeWriter().addAttribute("text-anchor", "end");
}
// check if we are set on a path
if (layout() == ArtisticTextShape::Straight) {
context.shapeWriter().addAttribute("x", anchorOffset);
context.shapeWriter().addAttribute("y", baselineOffset());
SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter());
Q_FOREACH (const ArtisticTextRange &range, formattedText) {
saveSvgTextRange(range, context, !hasSingleRange, baselineOffset());
}
} else {
KoPathShape *baselineShape = KoPathShape::createShapeFromPainterPath(baseline());
QString id = context.createUID("baseline");
context.styleWriter().startElement("path");
context.styleWriter().addAttribute("id", id);
- context.styleWriter().addAttribute("d", baselineShape->toString(baselineShape->absoluteTransformation(0) * context.userSpaceTransform()));
+ context.styleWriter().addAttribute("d", baselineShape->toString(baselineShape->absoluteTransformation() * context.userSpaceTransform()));
context.styleWriter().endElement();
context.shapeWriter().startElement("textPath");
context.shapeWriter().addAttribute("xlink:href", QLatin1Char('#') + id);
if (startOffset() > 0.0) {
context.shapeWriter().addAttribute("startOffset", QString("%1%").arg(startOffset() * 100.0));
}
Q_FOREACH (const ArtisticTextRange &range, formattedText) {
saveSvgTextRange(range, context, !hasSingleRange, baselineOffset());
}
context.shapeWriter().endElement();
delete baselineShape;
}
context.shapeWriter().endElement();
return true;
}
void ArtisticTextShape::saveSvgFont(const QFont &font, SvgSavingContext &context)
{
context.shapeWriter().addAttribute("font-family", font.family());
context.shapeWriter().addAttribute("font-size", font.pointSizeF());
if (font.bold()) {
context.shapeWriter().addAttribute("font-weight", "bold");
}
if (font.italic()) {
context.shapeWriter().addAttribute("font-style", "italic");
}
}
void ArtisticTextShape::saveSvgTextRange(const ArtisticTextRange &range, SvgSavingContext &context, bool saveRangeFont, qreal baselineOffset)
{
context.shapeWriter().startElement("tspan", false);
if (range.hasXOffsets()) {
const char *attributeName = (range.xOffsetType() == ArtisticTextRange::AbsoluteOffset ? "x" : "dx");
QString attributeValue;
int charIndex = 0;
while (range.hasXOffset(charIndex)) {
if (charIndex) {
attributeValue += QLatin1Char(',');
}
attributeValue += QString("%1").arg(SvgUtil::toUserSpace(range.xOffset(charIndex++)));
}
context.shapeWriter().addAttribute(attributeName, attributeValue);
}
if (range.hasYOffsets()) {
if (range.yOffsetType() != ArtisticTextRange::AbsoluteOffset) {
baselineOffset = 0;
}
const char *attributeName = (range.yOffsetType() == ArtisticTextRange::AbsoluteOffset ? " y" : " dy");
QString attributeValue;
int charIndex = 0;
while (range.hasYOffset(charIndex)) {
if (charIndex) {
attributeValue += QLatin1Char(',');
}
attributeValue += QString("%1").arg(SvgUtil::toUserSpace(baselineOffset + range.yOffset(charIndex++)));
}
context.shapeWriter().addAttribute(attributeName, attributeValue);
}
if (range.hasRotations()) {
QString attributeValue;
int charIndex = 0;
while (range.hasRotation(charIndex)) {
if (charIndex) {
attributeValue += ',';
}
attributeValue += QString("%1").arg(range.rotation(charIndex++));
}
context.shapeWriter().addAttribute("rotate", attributeValue);
}
if (range.baselineShift() != ArtisticTextRange::None) {
switch (range.baselineShift()) {
case ArtisticTextRange::Sub:
context.shapeWriter().addAttribute("baseline-shift", "sub");
break;
case ArtisticTextRange::Super:
context.shapeWriter().addAttribute("baseline-shift", "super");
break;
case ArtisticTextRange::Percent:
context.shapeWriter().addAttribute("baseline-shift", QString("%1%").arg(range.baselineShiftValue() * 100));
break;
case ArtisticTextRange::Length:
context.shapeWriter().addAttribute("baseline-shift", QString("%1%").arg(SvgUtil::toUserSpace(range.baselineShiftValue())));
break;
default:
break;
}
}
if (saveRangeFont) {
saveSvgFont(range.font(), context);
}
context.shapeWriter().addTextNode(range.text());
context.shapeWriter().endElement();
}
bool ArtisticTextShape::loadSvg(const KoXmlElement &textElement, SvgLoadingContext &context)
{
clear();
QString anchor;
if (!textElement.attribute("text-anchor").isEmpty()) {
anchor = textElement.attribute("text-anchor");
}
SvgStyles elementStyles = context.styleParser().collectStyles(textElement);
context.styleParser().parseFont(elementStyles);
ArtisticTextLoadingContext textContext;
textContext.parseCharacterTransforms(textElement, context.currentGC());
KoXmlElement parentElement = textElement;
// first check if we have a "textPath" child element
for (KoXmlNode n = textElement.firstChild(); !n.isNull(); n = n.nextSibling()) {
KoXmlElement e = n.toElement();
if (e.tagName() == "textPath") {
parentElement = e;
break;
}
}
KoPathShape *path = 0;
bool pathInDocument = false;
double offset = 0.0;
const bool hasTextPathElement = parentElement != textElement && parentElement.hasAttribute("xlink:href");
if (hasTextPathElement) {
// create the referenced path shape
context.pushGraphicsContext(parentElement);
context.styleParser().parseFont(context.styleParser().collectStyles(parentElement));
textContext.pushCharacterTransforms();
textContext.parseCharacterTransforms(parentElement, context.currentGC());
QString href = parentElement.attribute("xlink:href").mid(1);
if (context.hasDefinition(href)) {
const KoXmlElement &p = context.definition(href);
// must be a path element as per svg spec
if (p.tagName() == "path") {
pathInDocument = false;
path = new KoPathShape();
path->clear();
KoPathShapeLoader loader(path);
loader.parseSvg(p.attribute("d"), true);
path->setPosition(path->normalize());
QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()),
SvgUtil::fromUserSpace(path->position().y()));
QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()),
SvgUtil::fromUserSpace(path->size().height()));
path->setSize(newSize);
path->setPosition(newPosition);
path->applyAbsoluteTransformation(context.currentGC()->matrix);
}
} else {
path = dynamic_cast<KoPathShape *>(context.shapeById(href));
if (path) {
pathInDocument = true;
}
}
// parse the start offset
if (! parentElement.attribute("startOffset").isEmpty()) {
QString start = parentElement.attribute("startOffset");
if (start.endsWith('%')) {
offset = 0.01 * start.remove('%').toDouble();
} else {
const float pathLength = path ? path->outline().length() : 0.0;
if (pathLength > 0.0) {
offset = start.toDouble() / pathLength;
}
}
}
}
if (parentElement.hasChildNodes()) {
// parse child elements
parseTextRanges(parentElement, context, textContext);
if (!context.currentGC()->preserveWhitespace) {
const QString text = plainText();
if (text.endsWith(' ')) {
removeText(text.length() - 1, 1);
}
}
setPosition(textContext.textPosition());
} else {
// a single text range
appendText(createTextRange(textElement.text(), textContext, context.currentGC()));
setPosition(textContext.textPosition());
}
if (hasTextPathElement) {
if (path) {
if (pathInDocument) {
putOnPath(path);
} else {
- putOnPath(path->absoluteTransformation(0).map(path->outline()));
+ putOnPath(path->absoluteTransformation().map(path->outline()));
delete path;
}
if (offset > 0.0) {
setStartOffset(offset);
}
}
textContext.popCharacterTransforms();
context.popGraphicsContext();
}
// adjust position by baseline offset
if (! isOnPath()) {
setPosition(position() - QPointF(0, baselineOffset()));
}
if (anchor == "middle") {
setTextAnchor(ArtisticTextShape::AnchorMiddle);
} else if (anchor == "end") {
setTextAnchor(ArtisticTextShape::AnchorEnd);
}
return true;
}
void ArtisticTextShape::parseTextRanges(const KoXmlElement &element, SvgLoadingContext &context, ArtisticTextLoadingContext &textContext)
{
for (KoXmlNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
KoXmlElement e = n.toElement();
if (e.isNull()) {
ArtisticTextRange range = createTextRange(n.toText().data(), textContext, context.currentGC());
appendText(range);
} else if (e.tagName() == "tspan") {
SvgGraphicsContext *gc = context.pushGraphicsContext(e);
context.styleParser().parseFont(context.styleParser().collectStyles(e));
textContext.pushCharacterTransforms();
textContext.parseCharacterTransforms(e, gc);
parseTextRanges(e, context, textContext);
textContext.popCharacterTransforms();
context.popGraphicsContext();
} else if (e.tagName() == "tref") {
if (e.attribute("xlink:href").isEmpty()) {
continue;
}
QString href = e.attribute("xlink:href").mid(1);
ArtisticTextShape *refText = dynamic_cast<ArtisticTextShape *>(context.shapeById(href));
if (refText) {
foreach (const ArtisticTextRange &range, refText->text()) {
appendText(range);
}
} else if (context.hasDefinition(href)) {
const KoXmlElement &p = context.definition(href);
SvgGraphicsContext *gc = context.currentGC();
appendText(ArtisticTextRange(textContext.simplifyText(p.text(), gc->preserveWhitespace), gc->font));
}
} else {
continue;
}
}
}
ArtisticTextRange ArtisticTextShape::createTextRange(const QString &text, ArtisticTextLoadingContext &context, SvgGraphicsContext *gc)
{
ArtisticTextRange range(context.simplifyText(text, gc->preserveWhitespace), gc->font);
const int textLength = range.text().length();
switch (context.xOffsetType()) {
case ArtisticTextLoadingContext::Absolute:
range.setXOffsets(context.xOffsets(textLength), ArtisticTextRange::AbsoluteOffset);
break;
case ArtisticTextLoadingContext::Relative:
range.setXOffsets(context.xOffsets(textLength), ArtisticTextRange::RelativeOffset);
break;
default:
// no x-offsets
break;
}
switch (context.yOffsetType()) {
case ArtisticTextLoadingContext::Absolute:
range.setYOffsets(context.yOffsets(textLength), ArtisticTextRange::AbsoluteOffset);
break;
case ArtisticTextLoadingContext::Relative:
range.setYOffsets(context.yOffsets(textLength), ArtisticTextRange::RelativeOffset);
break;
default:
// no y-offsets
break;
}
range.setRotations(context.rotations(textLength));
#if 0
range.setLetterSpacing(gc->letterSpacing);
range.setWordSpacing(gc->wordSpacing);
if (gc->baselineShift == "sub") {
range.setBaselineShift(ArtisticTextRange::Sub);
} else if (gc->baselineShift == "super") {
range.setBaselineShift(ArtisticTextRange::Super);
} else if (gc->baselineShift.endsWith('%')) {
range.setBaselineShift(ArtisticTextRange::Percent, SvgUtil::fromPercentage(gc->baselineShift));
} else {
qreal value = SvgUtil::parseUnitX(gc, gc->baselineShift);
if (value != 0.0) {
range.setBaselineShift(ArtisticTextRange::Length, value);
}
}
#endif
//range.printDebug();
return range;
}
diff --git a/plugins/flake/artistictextshape/ArtisticTextShape.h b/plugins/flake/artistictextshape/ArtisticTextShape.h
index b13b3a42c5..47ceb4d89d 100644
--- a/plugins/flake/artistictextshape/ArtisticTextShape.h
+++ b/plugins/flake/artistictextshape/ArtisticTextShape.h
@@ -1,237 +1,237 @@
/* This file is part of the KDE project
* Copyright (C) 2007-2008,2011 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2008 Rob Buis <buis@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 ARTISTICTEXTSHAPE_H
#define ARTISTICTEXTSHAPE_H
#include "ArtisticTextRange.h"
#include <KoShape.h>
#include <KoPostscriptPaintDevice.h>
#include <SvgShape.h>
#include <QFont>
#include <QPainterPath>
#include <QVector>
class QPainter;
class KoPathShape;
class ArtisticTextLoadingContext;
class SvgGraphicsContext;
#define ArtisticTextShapeID "ArtisticText"
/// Character position within text shape (range index, range character index)
typedef QPair<int, int> CharIndex;
class ArtisticTextShape : public KoShape, public SvgShape
{
public:
enum TextAnchor { AnchorStart, AnchorMiddle, AnchorEnd };
enum LayoutMode {
Straight, ///< baseline is a straight line
OnPath, ///< baseline is a QPainterPath
OnPathShape ///< baseline is the outline of a path shape
};
ArtisticTextShape();
~ArtisticTextShape() override;
virtual KoShape *cloneShape() const override;
/// reimplemented
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override;
+ void paint(QPainter &painter, KoShapePaintingContext &paintContext) const override;
/// reimplemented
void saveOdf(KoShapeSavingContext &context) const override;
/// reimplemented
bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override;
/// reimplemented
QSizeF size() const override;
/// reimplemented
void setSize(const QSizeF &size) override;
/// reimplemented
QPainterPath outline() const override;
/// reimplemented from SvgShape
bool saveSvg(SvgSavingContext &context) override;
/// reimplemented from SvgShape
bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context) override;
/// Sets the plain text to display
void setPlainText(const QString &newText);
/// Returns the plain text content
QString plainText() const;
/// Returns formatted text
QList<ArtisticTextRange> text() const;
/// Returns if text shape is empty, i.e. no text
bool isEmpty() const;
/// Clears the text shape
void clear();
/**
* Sets the font used for drawing
* Note that it is expected that the font has its point size set
* in postscript points.
*/
void setFont(const QFont &font);
/**
* Sets the font for the specified range of characters
* @param charIndex the index of the first character of the range
* @param charCount the number of characters of the range
* @param font the new font to set
*/
void setFont(int charIndex, int charCount, const QFont &font);
/**
* Returns the font at the specified character position
* If the text shape is empty it will return the default font.
* If the character index is smaller than zero it will return the font
* of the first character. If the character index is greater than the
* last character index it will return the font of the last character.
*/
QFont fontAt(int charIndex) const;
/// Returns the default font
QFont defaultFont() const;
/// Attaches this text shape to the given path shape
bool putOnPath(KoPathShape *path);
/// Puts the text on the given path, the path is expected to be in document coordinates
bool putOnPath(const QPainterPath &path);
/// Detaches this text shape from an already attached path shape
void removeFromPath();
/// Returns if shape is attached to a path shape
bool isOnPath() const;
/// Sets the offset for for text on path
void setStartOffset(qreal offset);
/// Returns the start offset for text on path
qreal startOffset() const;
/**
* Returns the y-offset from the top-left corner to the baseline.
* This is usable for being able to exactly position the texts baseline.
* Note: The value makes only sense for text not attached to a path.
*/
qreal baselineOffset() const;
/// Sets the text anchor
void setTextAnchor(TextAnchor anchor);
/// Returns the actual text anchor
TextAnchor textAnchor() const;
/// Returns the current layout mode
LayoutMode layout() const;
/// Returns the baseline path
QPainterPath baseline() const;
/// Returns a pointer to the shape used as baseline
KoPathShape *baselineShape() const;
/// Removes a range of text starting from the given character
QList<ArtisticTextRange> removeText(int charIndex, int charCount);
/// Copies a range of text starting from the given character
QList<ArtisticTextRange> copyText(int charIndex, int charCount);
/// Adds a range of text at the given index
void insertText(int charIndex, const QString &plainText);
/// Adds range of text at the given index
void insertText(int charIndex, const ArtisticTextRange &textRange);
/// Adds ranges of text at the given index
void insertText(int charIndex, const QList<ArtisticTextRange> &textRanges);
/// Appends plain text to the last text range
void appendText(const QString &plainText);
/// Appends a single formatted range of text
void appendText(const ArtisticTextRange &text);
/// Replaces a range of text with the specified text range
bool replaceText(int charIndex, int charCount, const ArtisticTextRange &textRange);
/// Replaces a range of text with the specified text ranges
bool replaceText(int charIndex, int charCount, const QList<ArtisticTextRange> &textRanges);
/// Gets the angle of the char with the given index
qreal charAngleAt(int charIndex) const;
/// Gets the position of the char with the given index in shape coordinates
QPointF charPositionAt(int charIndex) const;
/// Gets the extents of the char with the given index
QRectF charExtentsAt(int charIndex) const;
/// Returns index of range and index within range of specified character
CharIndex indexOfChar(int charIndex) const;
/// reimplemented from KoShape
void shapeChanged(ChangeType type, KoShape *shape) override;
private:
void updateSizeAndPosition(bool global = false);
void cacheGlyphOutlines();
bool pathHasChanged() const;
void createOutline();
void beginTextUpdate();
void finishTextUpdate();
/// Calculates abstract character positions in baseline coordinates
QVector<QPointF> calculateAbstractCharacterPositions();
/// Returns the bounding box for an empty text shape
QRectF nullBoundBox() const;
/// Saves svg font
void saveSvgFont(const QFont &font, SvgSavingContext &context);
/// Saves svg text range
void saveSvgTextRange(const ArtisticTextRange &range, SvgSavingContext &context, bool saveFont, qreal baselineOffset);
/// Parse nested text ranges
void parseTextRanges(const KoXmlElement &element, SvgLoadingContext &context, ArtisticTextLoadingContext &textContext);
/// Creates text range
ArtisticTextRange createTextRange(const QString &text, ArtisticTextLoadingContext &context, SvgGraphicsContext *gc);
QList<ArtisticTextRange> m_ranges;
KoPostscriptPaintDevice m_paintDevice;
KoPathShape *m_path; ///< the path shape we are attached to
QList<QPainterPath> m_charOutlines; ///< cached character oulines
qreal m_startOffset; ///< the offset from the attached path start point
QPointF m_outlineOrigin; ///< the top-left corner of the non-normalized text outline
QPainterPath m_outline; ///< the actual text outline
QPainterPath m_baseline; ///< the baseline path the text is put on
TextAnchor m_textAnchor; ///< the actual text anchor
QVector<qreal> m_charOffsets; ///< char positions [0..1] on baseline path
QVector<QPointF> m_charPositions; ///< char positions in shape coordinates
int m_textUpdateCounter;
QFont m_defaultFont;
};
#endif // ARTISTICTEXTSHAPE_H
diff --git a/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui b/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui
index b081e19102..53c0b931bc 100644
--- a/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui
+++ b/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui
@@ -1,161 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ArtisticTextShapeConfigWidget</class>
<widget class="QWidget" name="ArtisticTextShapeConfigWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>259</width>
<height>73</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="0" column="0">
- <widget class="KoFontComboBox" name="fontFamily">
+ <widget class="QFontComboBox" name="fontFamily">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisIntParseSpinBox" name="fontSize">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="anchorStart">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="anchorMiddle">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="anchorEnd">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="bold">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="italic">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="superScript">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="subScript">
<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 row="2" column="0" colspan="2">
<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>
</widget>
<customwidgets>
<customwidget>
<class>KisIntParseSpinBox</class>
<extends>QSpinBox</extends>
<header>kis_int_parse_spin_box.h</header>
</customwidget>
- <customwidget>
- <class>KoFontComboBox</class>
- <extends>QComboBox</extends>
- <header>KoFontComboBox.h</header>
- </customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/flake/artistictextshape/ArtisticTextTool.cpp b/plugins/flake/artistictextshape/ArtisticTextTool.cpp
index 5f8d81e4c2..7eb29d354a 100644
--- a/plugins/flake/artistictextshape/ArtisticTextTool.cpp
+++ b/plugins/flake/artistictextshape/ArtisticTextTool.cpp
@@ -1,1007 +1,1008 @@
/* This file is part of the KDE project
* Copyright (C) 2007,2011 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2008 Rob Buis <buis@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 "ArtisticTextTool.h"
#include "ArtisticTextToolSelection.h"
#include "AttachTextToPathCommand.h"
#include "DetachTextFromPathCommand.h"
#include "AddTextRangeCommand.h"
#include "RemoveTextRangeCommand.h"
#include "ArtisticTextShapeConfigWidget.h"
#include "ArtisticTextShapeOnPathWidget.h"
#include "MoveStartOffsetStrategy.h"
#include "SelectTextStrategy.h"
#include "ChangeTextOffsetCommand.h"
#include "ChangeTextFontCommand.h"
#include "ChangeTextAnchorCommand.h"
#include "ReplaceTextRangeCommand.h"
#include <KoCanvasBase.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxy.h>
#include <KoPointerEvent.h>
#include <KoPathShape.h>
#include <KoShapeBackground.h>
#include <KoShapeController.h>
#include <KoShapeContainer.h>
#include <KoInteractionStrategy.h>
#include <KoIcon.h>
#include <KoViewConverter.h>
#include "kis_action_registry.h"
#include <klocalizedstring.h>
#include <kstandardaction.h>
#include <QAction>
#include <QDebug>
#include <QGridLayout>
#include <QToolButton>
#include <QCheckBox>
#include <QPainter>
#include <QPainterPath>
#include <kundo2command.h>
#include <float.h>
#include <math.h>
const int BlinkInterval = 500;
static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut)
{
foreach (const QKeySequence &ks, KStandardShortcut::shortcut(shortcut)) {
if (input == ks) {
return true;
}
}
return false;
}
ArtisticTextTool::ArtisticTextTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_selection(canvas, this)
, m_currentShape(0)
, m_hoverText(0)
, m_hoverPath(0)
, m_hoverHandle(false)
, m_textCursor(-1)
, m_showCursor(true)
, m_currentStrategy(0)
{
KisActionRegistry *actionRegistry = KisActionRegistry::instance();
m_detachPath = actionRegistry->makeQAction("artistictext_detach_from_path", this);
m_detachPath->setEnabled(false);
connect(m_detachPath, SIGNAL(triggered()), this, SLOT(detachPath()));
// addAction("artistictext_detach_from_path", m_detachPath);
m_convertText = actionRegistry->makeQAction("artistictext_convert_to_path", this);
m_convertText->setEnabled(false);
connect(m_convertText, SIGNAL(triggered()), this, SLOT(convertText()));
// addAction("artistictext_convert_to_path", m_convertText);
m_fontBold = actionRegistry->makeQAction("artistictext_font_bold", this);
connect(m_fontBold, SIGNAL(toggled(bool)), this, SLOT(toggleFontBold(bool)));
// addAction("artistictext_font_bold", m_fontBold);
m_fontItalic = actionRegistry->makeQAction("artistictext_font_italic", this);
connect(m_fontItalic, SIGNAL(toggled(bool)), this, SLOT(toggleFontItalic(bool)));
// addAction("artistictext_font_italic", m_fontItalic);
m_superScript = actionRegistry->makeQAction("artistictext_superscript", this);
connect(m_superScript, SIGNAL(triggered()), this, SLOT(setSuperScript()));
// addAction("artistictext_superscript", m_superScript);
m_subScript = actionRegistry->makeQAction("artistictext_subscript", this);
connect(m_subScript, SIGNAL(triggered()), this, SLOT(setSubScript()));
// addAction("artistictext_subscript", m_subScript);
QAction *anchorStart = actionRegistry->makeQAction("artistictext_anchor_start", this);
anchorStart->setData(ArtisticTextShape::AnchorStart);
// addAction("artistictext_anchor_start", anchorStart);
QAction *anchorMiddle = actionRegistry->makeQAction("artistictext_anchor_middle", this);
anchorMiddle->setData(ArtisticTextShape::AnchorMiddle);
// addAction("artistictext_anchor_middle", anchorMiddle);
QAction *anchorEnd = actionRegistry->makeQAction("artistictext_anchor_end", this);
anchorEnd->setData(ArtisticTextShape::AnchorEnd);
// addAction("artistictext_anchor_end", anchorEnd);
m_anchorGroup = new QActionGroup(this);
m_anchorGroup->setExclusive(true);
m_anchorGroup->addAction(anchorStart);
m_anchorGroup->addAction(anchorMiddle);
m_anchorGroup->addAction(anchorEnd);
connect(m_anchorGroup, SIGNAL(triggered(QAction*)), this, SLOT(anchorChanged(QAction*)));
connect(canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(textChanged()));
// addAction("edit_select_all", KStandardAction::selectAll(this, SLOT(selectAll()), this));
// addAction("edit_deselect_all", KStandardAction::deselect(this, SLOT(deselectAll()), this));
setTextMode(true);
}
ArtisticTextTool::~ArtisticTextTool()
{
delete m_currentStrategy;
}
QTransform ArtisticTextTool::cursorTransform() const
{
if (!m_currentShape) {
return QTransform();
}
QTransform transform;
const int textLength = m_currentShape->plainText().length();
if (m_textCursor <= textLength) {
const QPointF pos = m_currentShape->charPositionAt(m_textCursor);
const qreal angle = m_currentShape->charAngleAt(m_textCursor);
QFontMetrics metrics(m_currentShape->fontAt(m_textCursor));
transform.translate(pos.x() - 1, pos.y());
transform.rotate(360. - angle);
transform.translate(0, metrics.descent());
} else if (m_textCursor <= textLength + m_linefeedPositions.size()) {
const QPointF pos = m_linefeedPositions.value(m_textCursor - textLength - 1);
QFontMetrics metrics(m_currentShape->fontAt(textLength - 1));
transform.translate(pos.x(), pos.y());
transform.translate(0, metrics.descent());
}
- return transform * m_currentShape->absoluteTransformation(0);
+ return transform * m_currentShape->absoluteTransformation();
}
void ArtisticTextTool::paint(QPainter &painter, const KoViewConverter &converter)
{
if (! m_currentShape) {
return;
}
if (m_showCursor && m_blinkingCursor.isActive() && !m_currentStrategy) {
painter.save();
- m_currentShape->applyConversion(painter, converter);
+ painter.setTransform(converter.documentToView(), true);
painter.setBrush(Qt::black);
painter.setWorldTransform(cursorTransform(), true);
painter.setClipping(false);
painter.drawPath(m_textCursorShape);
painter.restore();
}
m_showCursor = !m_showCursor;
if (m_currentShape->isOnPath()) {
painter.save();
- m_currentShape->applyConversion(painter, converter);
+ painter.setTransform(converter.documentToView(), true);
if (!m_currentShape->baselineShape()) {
painter.setPen(Qt::DotLine);
painter.setBrush(Qt::NoBrush);
painter.drawPath(m_currentShape->baseline());
}
painter.setPen(Qt::blue);
painter.setBrush(m_hoverHandle ? Qt::red : Qt::white);
painter.drawPath(offsetHandleShape());
painter.restore();
}
if (m_selection.hasSelection()) {
painter.save();
- m_selection.paint(painter, converter);
+ painter.setTransform(converter.documentToView(), true);
+ m_selection.paint(painter);
painter.restore();
}
}
void ArtisticTextTool::repaintDecorations()
{
canvas()->updateCanvas(offsetHandleShape().boundingRect());
if (m_currentShape && m_currentShape->isOnPath()) {
if (!m_currentShape->baselineShape()) {
canvas()->updateCanvas(m_currentShape->baseline().boundingRect());
}
}
m_selection.repaintDecoration();
}
int ArtisticTextTool::cursorFromMousePosition(const QPointF &mousePosition)
{
if (!m_currentShape) {
return -1;
}
const QPointF pos = m_currentShape->documentToShape(mousePosition);
const int len = m_currentShape->plainText().length();
int hit = -1;
qreal mindist = DBL_MAX;
for (int i = 0; i <= len; ++i) {
QPointF center = pos - m_currentShape->charPositionAt(i);
if ((fabs(center.x()) + fabs(center.y())) < mindist) {
hit = i;
mindist = fabs(center.x()) + fabs(center.y());
}
}
return hit;
}
void ArtisticTextTool::mousePressEvent(KoPointerEvent *event)
{
if (m_hoverHandle) {
m_currentStrategy = new MoveStartOffsetStrategy(this, m_currentShape);
}
if (m_hoverText) {
KoSelection *selection = canvas()->selectedShapesProxy()->selection();
if (m_hoverText != m_currentShape) {
// if we hit another text shape, select that shape
selection->deselectAll();
setCurrentShape(m_hoverText);
selection->select(m_currentShape);
}
// change the text cursor position
int hitCursorPos = cursorFromMousePosition(event->point);
if (hitCursorPos >= 0) {
setTextCursorInternal(hitCursorPos);
m_selection.clear();
}
m_currentStrategy = new SelectTextStrategy(this, m_textCursor);
}
event->ignore();
}
void ArtisticTextTool::mouseMoveEvent(KoPointerEvent *event)
{
m_hoverPath = 0;
m_hoverText = 0;
if (m_currentStrategy) {
m_currentStrategy->handleMouseMove(event->point, event->modifiers());
return;
}
const bool textOnPath = m_currentShape && m_currentShape->isOnPath();
if (textOnPath) {
QPainterPath handle = offsetHandleShape();
QPointF handleCenter = handle.boundingRect().center();
if (handleGrabRect(event->point).contains(handleCenter)) {
// mouse is on offset handle
if (!m_hoverHandle) {
canvas()->updateCanvas(handle.boundingRect());
}
m_hoverHandle = true;
} else {
if (m_hoverHandle) {
canvas()->updateCanvas(handle.boundingRect());
}
m_hoverHandle = false;
}
}
if (!m_hoverHandle) {
// find text or path shape at cursor position
QList<KoShape *> shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(event->point));
if (shapes.contains(m_currentShape)) {
m_hoverText = m_currentShape;
} else {
Q_FOREACH (KoShape *shape, shapes) {
ArtisticTextShape *text = dynamic_cast<ArtisticTextShape *>(shape);
if (text && !m_hoverText) {
m_hoverText = text;
}
KoPathShape *path = dynamic_cast<KoPathShape *>(shape);
if (path && !m_hoverPath) {
m_hoverPath = path;
}
if (m_hoverPath && m_hoverText) {
break;
}
}
}
}
const bool hoverOnBaseline = textOnPath && m_currentShape && m_currentShape->baselineShape() == m_hoverPath;
// update cursor and status text
if (m_hoverText) {
useCursor(QCursor(Qt::IBeamCursor));
if (m_hoverText == m_currentShape) {
emit statusTextChanged(i18n("Click to change cursor position."));
} else {
emit statusTextChanged(i18n("Click to select text shape."));
}
} else if (m_hoverPath && m_currentShape && !hoverOnBaseline) {
useCursor(QCursor(Qt::PointingHandCursor));
emit statusTextChanged(i18n("Double click to put text on path."));
} else if (m_hoverHandle) {
useCursor(QCursor(Qt::ArrowCursor));
emit statusTextChanged(i18n("Drag handle to change start offset."));
} else {
useCursor(QCursor(Qt::ArrowCursor));
if (m_currentShape) {
emit statusTextChanged(i18n("Press escape to finish editing."));
} else {
emit statusTextChanged(QString());
}
}
}
void ArtisticTextTool::mouseReleaseEvent(KoPointerEvent *event)
{
if (m_currentStrategy) {
m_currentStrategy->finishInteraction(event->modifiers());
KUndo2Command *cmd = m_currentStrategy->createCommand();
if (cmd) {
canvas()->addCommand(cmd);
}
delete m_currentStrategy;
m_currentStrategy = 0;
}
updateActions();
}
void ArtisticTextTool::shortcutOverrideEvent(QKeyEvent *event)
{
QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers()));
if (hit(item, KStandardShortcut::Begin) ||
hit(item, KStandardShortcut::End)) {
event->accept();
}
}
void ArtisticTextTool::mouseDoubleClickEvent(KoPointerEvent */*event*/)
{
if (m_hoverPath && m_currentShape) {
if (!m_currentShape->isOnPath() || m_currentShape->baselineShape() != m_hoverPath) {
m_blinkingCursor.stop();
m_showCursor = false;
updateTextCursorArea();
canvas()->addCommand(new AttachTextToPathCommand(m_currentShape, m_hoverPath));
m_blinkingCursor.start(BlinkInterval);
updateActions();
m_hoverPath = 0;
m_linefeedPositions.clear();
return;
}
}
}
void ArtisticTextTool::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
event->ignore();
return;
}
event->accept();
if (m_currentShape && textCursor() > -1) {
switch (event->key()) {
case Qt::Key_Delete:
if (m_selection.hasSelection()) {
removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount());
} else if (textCursor() >= 0 && textCursor() < m_currentShape->plainText().length()) {
removeFromTextCursor(textCursor(), 1);
}
break;
case Qt::Key_Backspace:
if (m_selection.hasSelection()) {
removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount());
} else {
removeFromTextCursor(textCursor() - 1, 1);
}
break;
case Qt::Key_Right:
if (event->modifiers() & Qt::ShiftModifier) {
int selectionStart, selectionEnd;
if (m_selection.hasSelection()) {
selectionStart = m_selection.selectionStart();
selectionEnd = selectionStart + m_selection.selectionCount();
if (textCursor() == selectionStart) {
selectionStart = textCursor() + 1;
} else if (textCursor() == selectionEnd) {
selectionEnd = textCursor() + 1;
}
} else {
selectionStart = textCursor();
selectionEnd = textCursor() + 1;
}
m_selection.selectText(selectionStart, selectionEnd);
} else {
m_selection.clear();
}
setTextCursor(m_currentShape, textCursor() + 1);
break;
case Qt::Key_Left:
if (event->modifiers() & Qt::ShiftModifier) {
int selectionStart, selectionEnd;
if (m_selection.hasSelection()) {
selectionStart = m_selection.selectionStart();
selectionEnd = selectionStart + m_selection.selectionCount();
if (textCursor() == selectionStart) {
selectionStart = textCursor() - 1;
} else if (textCursor() == selectionEnd) {
selectionEnd = textCursor() - 1;
}
} else {
selectionEnd = textCursor();
selectionStart = textCursor() - 1;
}
m_selection.selectText(selectionStart, selectionEnd);
} else {
m_selection.clear();
}
setTextCursor(m_currentShape, textCursor() - 1);
break;
case Qt::Key_Home:
if (event->modifiers() & Qt::ShiftModifier) {
const int selectionStart = 0;
const int selectionEnd = m_selection.hasSelection() ? m_selection.selectionStart() + m_selection.selectionCount() : m_textCursor;
m_selection.selectText(selectionStart, selectionEnd);
} else {
m_selection.clear();
}
setTextCursor(m_currentShape, 0);
break;
case Qt::Key_End:
if (event->modifiers() & Qt::ShiftModifier) {
const int selectionStart = m_selection.hasSelection() ? m_selection.selectionStart() : m_textCursor;
const int selectionEnd = m_currentShape->plainText().length();
m_selection.selectText(selectionStart, selectionEnd);
} else {
m_selection.clear();
}
setTextCursor(m_currentShape, m_currentShape->plainText().length());
break;
case Qt::Key_Return:
case Qt::Key_Enter: {
const int textLength = m_currentShape->plainText().length();
if (m_textCursor >= textLength) {
// get font metrics for last character
QFontMetrics metrics(m_currentShape->fontAt(textLength - 1));
const qreal offset = m_currentShape->size().height() + (m_linefeedPositions.size() + 1) * metrics.height();
m_linefeedPositions.append(QPointF(0, offset));
setTextCursor(m_currentShape, textCursor() + 1);
}
break;
}
default:
if (event->text().isEmpty()) {
event->ignore();
return;
}
if (m_selection.hasSelection()) {
removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount());
}
addToTextCursor(event->text());
}
} else {
event->ignore();
}
}
void ArtisticTextTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
KoToolBase::activate(activation, shapes);
foreach (KoShape *shape, shapes) {
ArtisticTextShape *text = dynamic_cast<ArtisticTextShape *>(shape);
if (text) {
setCurrentShape(text);
break;
}
}
if (!m_currentShape) {
// none found
emit done();
return;
}
m_hoverText = 0;
m_hoverPath = 0;
updateActions();
emit statusTextChanged(i18n("Press return to finish editing."));
repaintDecorations();
connect(canvas()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
}
void ArtisticTextTool::blinkCursor()
{
updateTextCursorArea();
}
void ArtisticTextTool::deactivate()
{
if (m_currentShape) {
if (m_currentShape->plainText().isEmpty()) {
canvas()->addCommand(canvas()->shapeController()->removeShape(m_currentShape));
}
setCurrentShape(0);
}
m_hoverPath = 0;
m_hoverText = 0;
disconnect(canvas()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
KoToolBase::deactivate();
}
void ArtisticTextTool::updateActions()
{
if (m_currentShape) {
const QFont font = m_currentShape->fontAt(textCursor());
const CharIndex index = m_currentShape->indexOfChar(textCursor());
ArtisticTextRange::BaselineShift baselineShift = ArtisticTextRange::None;
if (index.first >= 0) {
baselineShift = m_currentShape->text().at(index.first).baselineShift();
}
m_fontBold->blockSignals(true);
m_fontBold->setChecked(font.bold());
m_fontBold->blockSignals(false);
m_fontBold->setEnabled(true);
m_fontItalic->blockSignals(true);
m_fontItalic->setChecked(font.italic());
m_fontItalic->blockSignals(false);
m_fontItalic->setEnabled(true);
m_detachPath->setEnabled(m_currentShape->isOnPath());
m_convertText->setEnabled(true);
m_anchorGroup->blockSignals(true);
Q_FOREACH (QAction *action, m_anchorGroup->actions()) {
if (action->data().toInt() == m_currentShape->textAnchor()) {
action->setChecked(true);
}
}
m_anchorGroup->blockSignals(false);
m_anchorGroup->setEnabled(true);
m_superScript->blockSignals(true);
m_superScript->setChecked(baselineShift == ArtisticTextRange::Super);
m_superScript->blockSignals(false);
m_subScript->blockSignals(true);
m_subScript->setChecked(baselineShift == ArtisticTextRange::Sub);
m_subScript->blockSignals(false);
m_superScript->setEnabled(true);
m_subScript->setEnabled(true);
} else {
m_detachPath->setEnabled(false);
m_convertText->setEnabled(false);
m_fontBold->setEnabled(false);
m_fontItalic->setEnabled(false);
m_anchorGroup->setEnabled(false);
m_superScript->setEnabled(false);
m_subScript->setEnabled(false);
}
}
void ArtisticTextTool::detachPath()
{
if (m_currentShape && m_currentShape->isOnPath()) {
canvas()->addCommand(new DetachTextFromPathCommand(m_currentShape));
updateActions();
}
}
void ArtisticTextTool::convertText()
{
if (! m_currentShape) {
return;
}
KoPathShape *path = KoPathShape::createShapeFromPainterPath(m_currentShape->outline());
path->setZIndex(m_currentShape->zIndex());
path->setStroke(m_currentShape->stroke());
path->setBackground(m_currentShape->background());
path->setTransformation(m_currentShape->transformation());
path->setShapeId(KoPathShapeId);
KUndo2Command *cmd = canvas()->shapeController()->addShapeDirect(path, 0);
cmd->setText(kundo2_i18n("Convert to Path"));
canvas()->shapeController()->removeShape(m_currentShape, cmd);
canvas()->addCommand(cmd);
emit done();
}
QList<QPointer<QWidget> > ArtisticTextTool::createOptionWidgets()
{
QList<QPointer<QWidget> > widgets;
ArtisticTextShapeConfigWidget *configWidget = new ArtisticTextShapeConfigWidget(this);
configWidget->setObjectName("ArtisticTextConfigWidget");
configWidget->setWindowTitle(i18n("Text Properties"));
connect(configWidget, SIGNAL(fontFamilyChanged(QFont)), this, SLOT(setFontFamiliy(QFont)));
connect(configWidget, SIGNAL(fontSizeChanged(int)), this, SLOT(setFontSize(int)));
connect(this, SIGNAL(shapeSelected()), configWidget, SLOT(updateWidget()));
connect(canvas()->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
configWidget, SLOT(updateWidget()));
widgets.append(configWidget);
ArtisticTextShapeOnPathWidget *pathWidget = new ArtisticTextShapeOnPathWidget(this);
pathWidget->setObjectName("ArtisticTextPathWidget");
pathWidget->setWindowTitle(i18n("Text On Path"));
connect(pathWidget, SIGNAL(offsetChanged(int)), this, SLOT(setStartOffset(int)));
connect(this, SIGNAL(shapeSelected()), pathWidget, SLOT(updateWidget()));
connect(canvas()->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
pathWidget, SLOT(updateWidget()));
widgets.append(pathWidget);
if (m_currentShape) {
pathWidget->updateWidget();
configWidget->updateWidget();
}
return widgets;
}
KoToolSelection *ArtisticTextTool::selection()
{
return &m_selection;
}
void ArtisticTextTool::enableTextCursor(bool enable)
{
if (enable) {
if (m_currentShape) {
setTextCursorInternal(m_currentShape->plainText().length());
}
connect(&m_blinkingCursor, SIGNAL(timeout()), this, SLOT(blinkCursor()));
m_blinkingCursor.start(BlinkInterval);
} else {
m_blinkingCursor.stop();
disconnect(&m_blinkingCursor, SIGNAL(timeout()), this, SLOT(blinkCursor()));
setTextCursorInternal(-1);
m_showCursor = false;
}
}
void ArtisticTextTool::setTextCursor(ArtisticTextShape *textShape, int textCursor)
{
if (!m_currentShape || textShape != m_currentShape) {
return;
}
if (m_textCursor == textCursor || textCursor < 0) {
return;
}
const int textLength = m_currentShape->plainText().length() + m_linefeedPositions.size();
if (textCursor > textLength) {
return;
}
setTextCursorInternal(textCursor);
}
int ArtisticTextTool::textCursor() const
{
return m_textCursor;
}
void ArtisticTextTool::updateTextCursorArea() const
{
if (! m_currentShape || m_textCursor < 0) {
return;
}
QRectF bbox = cursorTransform().mapRect(m_textCursorShape.boundingRect());
canvas()->updateCanvas(bbox);
}
void ArtisticTextTool::setCurrentShape(ArtisticTextShape *currentShape)
{
if (m_currentShape == currentShape) {
return;
}
enableTextCursor(false);
m_currentShape = currentShape;
m_selection.setSelectedShape(m_currentShape);
if (m_currentShape) {
enableTextCursor(true);
}
emit shapeSelected();
}
void ArtisticTextTool::setTextCursorInternal(int textCursor)
{
updateTextCursorArea();
m_textCursor = textCursor;
createTextCursorShape();
updateTextCursorArea();
updateActions();
emit shapeSelected();
}
void ArtisticTextTool::createTextCursorShape()
{
if (m_textCursor < 0 || ! m_currentShape) {
return;
}
const QRectF extents = m_currentShape->charExtentsAt(m_textCursor);
m_textCursorShape = QPainterPath();
m_textCursorShape.addRect(0, 0, 1, -extents.height());
m_textCursorShape.closeSubpath();
}
void ArtisticTextTool::removeFromTextCursor(int from, unsigned int count)
{
if (from >= 0) {
if (m_selection.hasSelection()) {
// clear selection before text is removed, or else selection will be invalid
m_selection.clear();
}
KUndo2Command *cmd = new RemoveTextRangeCommand(this, m_currentShape, from, count);
canvas()->addCommand(cmd);
}
}
void ArtisticTextTool::addToTextCursor(const QString &str)
{
if (!str.isEmpty() && m_textCursor > -1) {
QString printable;
for (int i = 0; i < str.length(); i++) {
if (str[i].isPrint()) {
printable.append(str[i]);
}
}
unsigned int len = printable.length();
if (len) {
const int textLength = m_currentShape->plainText().length();
if (m_textCursor <= textLength) {
KUndo2Command *cmd = new AddTextRangeCommand(this, m_currentShape, printable, m_textCursor);
canvas()->addCommand(cmd);
} else if (m_textCursor <= textLength + m_linefeedPositions.size()) {
const QPointF pos = m_linefeedPositions.value(m_textCursor - textLength - 1);
ArtisticTextRange newLine(printable, m_currentShape->fontAt(textLength - 1));
newLine.setXOffsets(QList<qreal>() << pos.x(), ArtisticTextRange::AbsoluteOffset);
newLine.setYOffsets(QList<qreal>() << pos.y() - m_currentShape->baselineOffset(), ArtisticTextRange::AbsoluteOffset);
KUndo2Command *cmd = new AddTextRangeCommand(this, m_currentShape, newLine, m_textCursor);
canvas()->addCommand(cmd);
m_linefeedPositions.clear();
}
}
}
}
void ArtisticTextTool::textChanged()
{
if (!m_currentShape) {
return;
}
const QString currentText = m_currentShape->plainText();
if (m_textCursor > currentText.length()) {
setTextCursorInternal(currentText.length());
}
}
void ArtisticTextTool::shapeSelectionChanged()
{
KoSelection *selection = canvas()->selectedShapesProxy()->selection();
if (selection->isSelected(m_currentShape)) {
return;
}
foreach (KoShape *shape, selection->selectedShapes()) {
ArtisticTextShape *text = dynamic_cast<ArtisticTextShape *>(shape);
if (text) {
setCurrentShape(text);
break;
}
}
}
QPainterPath ArtisticTextTool::offsetHandleShape()
{
QPainterPath handle;
if (!m_currentShape || !m_currentShape->isOnPath()) {
return handle;
}
const QPainterPath baseline = m_currentShape->baseline();
const qreal offset = m_currentShape->startOffset();
QPointF offsetPoint = baseline.pointAtPercent(offset);
QSizeF paintSize = handlePaintRect(QPointF()).size();
handle.moveTo(0, 0);
handle.lineTo(0.5 * paintSize.width(), paintSize.height());
handle.lineTo(-0.5 * paintSize.width(), paintSize.height());
handle.closeSubpath();
QTransform transform;
transform.translate(offsetPoint.x(), offsetPoint.y());
transform.rotate(360. - baseline.angleAtPercent(offset));
return transform.map(handle);
}
void ArtisticTextTool::setStartOffset(int offset)
{
if (!m_currentShape || !m_currentShape->isOnPath()) {
return;
}
const qreal newOffset = static_cast<qreal>(offset) / 100.0;
if (newOffset != m_currentShape->startOffset()) {
canvas()->addCommand(new ChangeTextOffsetCommand(m_currentShape, m_currentShape->startOffset(), newOffset));
}
}
void ArtisticTextTool::changeFontProperty(FontProperty property, const QVariant &value)
{
if (!m_currentShape || !m_selection.hasSelection()) {
return;
}
// build font ranges
const int selectedCharCount = m_selection.selectionCount();
const int selectedCharStart = m_selection.selectionStart();
QList<ArtisticTextRange> ranges = m_currentShape->text();
CharIndex index = m_currentShape->indexOfChar(selectedCharStart);
if (index.first < 0) {
return;
}
KUndo2Command *cmd = new KUndo2Command;
int collectedCharCount = 0;
while (collectedCharCount < selectedCharCount) {
ArtisticTextRange &range = ranges[index.first];
QFont font = range.font();
switch (property) {
case BoldProperty:
font.setBold(value.toBool());
break;
case ItalicProperty:
font.setItalic(value.toBool());
break;
case FamiliyProperty:
font.setFamily(value.toString());
break;
case SizeProperty:
font.setPointSize(value.toInt());
break;
}
const int changeCount = qMin(selectedCharCount - collectedCharCount, range.text().count() - index.second);
const int changeStart = selectedCharStart + collectedCharCount;
new ChangeTextFontCommand(m_currentShape, changeStart, changeCount, font, cmd);
index.first++;
index.second = 0;
collectedCharCount += changeCount;
}
canvas()->addCommand(cmd);
}
void ArtisticTextTool::toggleFontBold(bool enabled)
{
changeFontProperty(BoldProperty, QVariant(enabled));
}
void ArtisticTextTool::toggleFontItalic(bool enabled)
{
changeFontProperty(ItalicProperty, QVariant(enabled));
}
void ArtisticTextTool::anchorChanged(QAction *action)
{
if (!m_currentShape) {
return;
}
ArtisticTextShape::TextAnchor newAnchor = static_cast<ArtisticTextShape::TextAnchor>(action->data().toInt());
if (newAnchor != m_currentShape->textAnchor()) {
canvas()->addCommand(new ChangeTextAnchorCommand(m_currentShape, newAnchor));
}
}
void ArtisticTextTool::setFontFamiliy(const QFont &font)
{
changeFontProperty(FamiliyProperty, QVariant(font.family()));
}
void ArtisticTextTool::setFontSize(int size)
{
changeFontProperty(SizeProperty, QVariant(size));
}
void ArtisticTextTool::setSuperScript()
{
toggleSubSuperScript(ArtisticTextRange::Super);
}
void ArtisticTextTool::setSubScript()
{
toggleSubSuperScript(ArtisticTextRange::Sub);
}
void ArtisticTextTool::toggleSubSuperScript(ArtisticTextRange::BaselineShift mode)
{
if (!m_currentShape || !m_selection.hasSelection()) {
return;
}
const int from = m_selection.selectionStart();
const int count = m_selection.selectionCount();
QList<ArtisticTextRange> ranges = m_currentShape->copyText(from, count);
const int rangeCount = ranges.count();
if (!rangeCount) {
return;
}
// determine if we want to disable the specified mode
const bool disableMode = ranges.first().baselineShift() == mode;
const qreal fontSize = m_currentShape->defaultFont().pointSizeF();
for (int i = 0; i < rangeCount; ++i) {
ArtisticTextRange &currentRange = ranges[i];
QFont font = currentRange.font();
if (disableMode) {
currentRange.setBaselineShift(ArtisticTextRange::None);
font.setPointSizeF(fontSize);
} else {
currentRange.setBaselineShift(mode);
font.setPointSizeF(fontSize * ArtisticTextRange::subAndSuperScriptSizeFactor());
}
currentRange.setFont(font);
}
canvas()->addCommand(new ReplaceTextRangeCommand(m_currentShape, ranges, from, count, this));
}
void ArtisticTextTool::selectAll()
{
if (m_currentShape) {
m_selection.selectText(0, m_currentShape->plainText().count());
}
}
void ArtisticTextTool::deselectAll()
{
if (m_currentShape) {
m_selection.clear();
}
}
QVariant ArtisticTextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const
{
if (!m_currentShape) {
return QVariant();
}
switch (query) {
case Qt::ImMicroFocus: {
// The rectangle covering the area of the input cursor in widget coordinates.
QRectF rect = m_textCursorShape.boundingRect();
rect.moveTop(rect.bottom());
- QTransform shapeMatrix = m_currentShape->absoluteTransformation(&converter);
+ QTransform shapeMatrix = m_currentShape->absoluteTransformation();
qreal zoomX, zoomY;
converter.zoom(&zoomX, &zoomY);
shapeMatrix.scale(zoomX, zoomY);
rect = shapeMatrix.mapRect(rect);
return rect.toRect();
}
case Qt::ImFont:
// The currently used font for text input.
return m_currentShape->fontAt(m_textCursor);
case Qt::ImCursorPosition:
// The logical position of the cursor within the text surrounding the input area (see ImSurroundingText).
return m_currentShape->charPositionAt(m_textCursor);
case Qt::ImSurroundingText:
// The plain text around the input area, for example the current paragraph.
return m_currentShape->plainText();
case Qt::ImCurrentSelection:
// The currently selected text.
return QVariant();
default:
; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition
}
return QVariant();
}
diff --git a/plugins/flake/artistictextshape/ArtisticTextToolSelection.cpp b/plugins/flake/artistictextshape/ArtisticTextToolSelection.cpp
index cead25f68f..d64a39c962 100644
--- a/plugins/flake/artistictextshape/ArtisticTextToolSelection.cpp
+++ b/plugins/flake/artistictextshape/ArtisticTextToolSelection.cpp
@@ -1,193 +1,190 @@
/* This file is part of the KDE project
* Copyright (C) 2011 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 "ArtisticTextToolSelection.h"
#include "ArtisticTextShape.h"
#include <KoCanvasBase.h>
-#include <KoViewConverter.h>
#include <QDebug>
#include <QPainter>
#include <QFontMetrics>
ArtisticTextToolSelection::ArtisticTextToolSelection(KoCanvasBase *canvas, QObject *parent)
: KoToolSelection(parent)
, m_canvas(canvas)
, m_currentShape(0)
, m_selectionStart(-1)
, m_selectionCount(0)
{
Q_ASSERT(m_canvas);
}
ArtisticTextToolSelection::~ArtisticTextToolSelection()
{
}
bool ArtisticTextToolSelection::hasSelection()
{
return m_currentShape && m_selectionCount > 0;
}
void ArtisticTextToolSelection::setSelectedShape(ArtisticTextShape *textShape)
{
if (textShape == m_currentShape) {
return;
}
clear();
m_currentShape = textShape;
}
ArtisticTextShape *ArtisticTextToolSelection::selectedShape() const
{
return m_currentShape;
}
void ArtisticTextToolSelection::selectText(int from, int to)
{
if (!m_currentShape) {
return;
}
repaintDecoration();
const int textCount = m_currentShape->plainText().count();
m_selectionStart = qBound(0, from, textCount - 1);
m_selectionCount = qBound(from, to, textCount) - m_selectionStart;
repaintDecoration();
}
int ArtisticTextToolSelection::selectionStart() const
{
return m_selectionStart;
}
int ArtisticTextToolSelection::selectionCount() const
{
return m_selectionCount;
}
void ArtisticTextToolSelection::clear()
{
repaintDecoration();
m_selectionStart = -1;
m_selectionCount = 0;
}
-void ArtisticTextToolSelection::paint(QPainter &painter, const KoViewConverter &converter)
+void ArtisticTextToolSelection::paint(QPainter &painter)
{
if (!hasSelection()) {
return;
}
-
- m_currentShape->applyConversion(painter, converter);
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0, 0, 255, 127));
painter.drawPath(outline());
}
QPainterPath ArtisticTextToolSelection::outline()
{
if (!hasSelection()) {
return QPainterPath();
}
CharIndex charPos = m_currentShape->indexOfChar(m_selectionStart);
if (charPos.first < 0) {
return QPainterPath();
}
QPainterPath outline;
QPolygonF polygon;
QList<ArtisticTextRange> ranges = m_currentShape->text();
if (ranges.size() == 0) {
return outline;
}
int globalCharIndex = m_selectionStart;
int remainingChars = m_selectionCount;
while (remainingChars && ranges.size() > charPos.first) {
const ArtisticTextRange &currentRange = ranges[charPos.first];
int currentTextLength = currentRange.text().length();
while (charPos.second < currentTextLength && remainingChars > 0) {
const QPointF pos = m_currentShape->charPositionAt(globalCharIndex);
const qreal angle = m_currentShape->charAngleAt(globalCharIndex);
QTransform charTransform;
charTransform.translate(pos.x() - 1, pos.y());
charTransform.rotate(360. - angle);
QFontMetricsF metrics(currentRange.font());
polygon.prepend(charTransform.map(QPointF(0.0, -metrics.ascent())));
polygon.append(charTransform.map(QPointF(0.0, metrics.descent())));
// advance to next character
charPos.second++;
globalCharIndex++;
remainingChars--;
// next character has y-offset or we are at the end of this text range
const bool hasYOffset = currentRange.hasYOffset(charPos.second);
const bool atRangeEnd = charPos.second == currentTextLength;
const bool atSelectionEnd = remainingChars == 0;
if (hasYOffset || atRangeEnd || atSelectionEnd) {
if (hasYOffset || atRangeEnd) {
const QChar c = currentRange.text().at(charPos.second - 1);
const qreal w = metrics.width(c);
polygon.prepend(charTransform.map(QPointF(w, -metrics.ascent())));
polygon.append(charTransform.map(QPointF(w, metrics.descent())));
} else {
const QPointF pos = m_currentShape->charPositionAt(globalCharIndex);
const qreal angle = m_currentShape->charAngleAt(globalCharIndex);
charTransform.reset();
charTransform.translate(pos.x() - 1, pos.y());
charTransform.rotate(360. - angle);
polygon.prepend(charTransform.map(QPointF(0.0, -metrics.ascent())));
polygon.append(charTransform.map(QPointF(0.0, metrics.descent())));
}
QPainterPath p;
p.addPolygon(polygon);
outline = outline.united(p);
polygon.clear();
}
}
// go to first character of next text range
charPos.first++;
charPos.second = 0;
}
// transform to document coordinates
- return m_currentShape->absoluteTransformation(0).map(outline);
+ return m_currentShape->absoluteTransformation().map(outline);
}
void ArtisticTextToolSelection::repaintDecoration()
{
if (hasSelection()) {
m_canvas->updateCanvas(outline().boundingRect());
}
}
diff --git a/plugins/flake/artistictextshape/ArtisticTextToolSelection.h b/plugins/flake/artistictextshape/ArtisticTextToolSelection.h
index 179af6bf46..89599f0213 100644
--- a/plugins/flake/artistictextshape/ArtisticTextToolSelection.h
+++ b/plugins/flake/artistictextshape/ArtisticTextToolSelection.h
@@ -1,76 +1,75 @@
/* This file is part of the KDE project
* Copyright (C) 2011 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.
*/
#ifndef ARTISTICTEXTTOOLSELECTION_H
#define ARTISTICTEXTTOOLSELECTION_H
#include <KoToolSelection.h>
#include <QPainterPath>
#include <QPointer>
#include <KoCanvasBase.h>
class ArtisticTextShape;
-class KoViewConverter;
class QPainter;
class ArtisticTextToolSelection : public KoToolSelection
{
public:
explicit ArtisticTextToolSelection(KoCanvasBase *canvas, QObject *parent = 0);
~ArtisticTextToolSelection() override;
// reimplemented from KoToolSelection
bool hasSelection() override;
/// Sets the currently selected text shape
void setSelectedShape(ArtisticTextShape *textShape);
/// Returns the currently selected text shape
ArtisticTextShape *selectedShape() const;
/// Selects specified range of characters
void selectText(int from, int to);
/// Returns the start character index of the selection
int selectionStart() const;
/// Returns number of selected characters
int selectionCount() const;
/// Clears the selection
void clear();
/// Paints the selection
- void paint(QPainter &painter, const KoViewConverter &converter);
+ void paint(QPainter &painter);
/// Triggers a repaint of the selection
void repaintDecoration();
private:
/// Returns the outline of the selection in document coordinates
QPainterPath outline();
QPointer<KoCanvasBase> m_canvas;
ArtisticTextShape *m_currentShape; ///< the currently selected text shape
int m_selectionStart;
int m_selectionCount;
};
#endif // ARTISTICTEXTTOOLSELECTION_H
diff --git a/plugins/flake/imageshape/ImageShape.cpp b/plugins/flake/imageshape/ImageShape.cpp
index 111056a2a7..9a97c6341a 100644
--- a/plugins/flake/imageshape/ImageShape.cpp
+++ b/plugins/flake/imageshape/ImageShape.cpp
@@ -1,187 +1,186 @@
/*
* 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 <KoTosContainer_p.h>
#include "ImageShape.h"
#include "kis_debug.h"
#include <QPainter>
-#include <KoViewConverter.h>
#include <SvgLoadingContext.h>
#include <SvgSavingContext.h>
#include <SvgUtil.h>
#include <QFileInfo>
#include <QBuffer>
#include <KisMimeDatabase.h>
#include <KoXmlWriter.h>
#include "kis_dom_utils.h"
#include <QRegularExpression>
#include "KisQPainterStateSaver.h"
-struct Q_DECL_HIDDEN ImageShape::Private
+struct Q_DECL_HIDDEN ImageShape::Private : public QSharedData
{
Private() {}
Private(const Private &rhs)
- : image(rhs.image),
+ : QSharedData(),
+ image(rhs.image),
ratioParser(rhs.ratioParser ? new SvgUtil::PreserveAspectRatioParser(*rhs.ratioParser) : 0),
viewBoxTransform(rhs.viewBoxTransform)
{
}
QImage image;
QScopedPointer<SvgUtil::PreserveAspectRatioParser> ratioParser;
QTransform viewBoxTransform;
};
ImageShape::ImageShape()
: m_d(new Private)
{
}
ImageShape::ImageShape(const ImageShape &rhs)
: KoTosContainer(rhs),
- m_d(new Private(*rhs.m_d))
+ m_d(rhs.m_d)
{
}
ImageShape::~ImageShape()
{
}
KoShape *ImageShape::cloneShape() const
{
return new ImageShape(*this);
}
-void ImageShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
+void ImageShape::paint(QPainter &painter, KoShapePaintingContext &paintContext) const
{
Q_UNUSED(paintContext);
KisQPainterStateSaver saver(&painter);
const QRectF myrect(QPointF(), size());
- applyConversion(painter, converter);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.setClipRect(QRectF(QPointF(), size()), Qt::IntersectClip);
painter.setTransform(m_d->viewBoxTransform, true);
painter.drawImage(QPoint(), m_d->image);
}
void ImageShape::setSize(const QSizeF &size)
{
KoTosContainer::setSize(size);
}
void ImageShape::saveOdf(KoShapeSavingContext &context) const
{
Q_UNUSED(context);
}
bool ImageShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
Q_UNUSED(element);
Q_UNUSED(context);
return false;
}
bool ImageShape::saveSvg(SvgSavingContext &context)
{
const QString uid = context.createUID("image");
context.shapeWriter().startElement("image");
context.shapeWriter().addAttribute("id", uid);
SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter());
context.shapeWriter().addAttribute("width", QString("%1px").arg(KisDomUtils::toString(size().width())));
context.shapeWriter().addAttribute("height", QString("%1px").arg(KisDomUtils::toString(size().height())));
QString aspectString = m_d->ratioParser->toString();
if (!aspectString.isEmpty()) {
context.shapeWriter().addAttribute("preserveAspectRatio", aspectString);
}
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
if (m_d->image.save(&buffer, "PNG")) {
const QString mimeType = KisMimeDatabase::mimeTypeForSuffix("*.png");
context.shapeWriter().addAttribute("xlink:href", "data:"+ mimeType + ";base64," + ba.toBase64());
}
context.shapeWriter().endElement(); // image
return true;
}
bool ImageShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context)
{
const qreal x = SvgUtil::parseUnitX(context.currentGC(), element.attribute("x"));
const qreal y = SvgUtil::parseUnitY(context.currentGC(), element.attribute("y"));
const qreal w = SvgUtil::parseUnitX(context.currentGC(), element.attribute("width"));
const qreal h = SvgUtil::parseUnitY(context.currentGC(), element.attribute("height"));
setSize(QSizeF(w, h));
setPosition(QPointF(x, y));
if (w == 0.0 || h == 0.0) {
setVisible(false);
}
QString fileName = element.attribute("xlink:href");
QByteArray data;
if (fileName.startsWith("data:")) {
QRegularExpression re("data:(.+?);base64,(.+)");
QRegularExpressionMatch match = re.match(fileName);
data = match.captured(2).toLatin1();
data = QByteArray::fromBase64(data);
} else {
data = context.fetchExternalFile(fileName);
}
if (!data.isEmpty()) {
QBuffer buffer(&data);
m_d->image.load(&buffer, "");
}
const QString aspectString = element.attribute("preserveAspectRatio", "xMidYMid meet");
m_d->ratioParser.reset(new SvgUtil::PreserveAspectRatioParser(aspectString));
if (!m_d->image.isNull()) {
m_d->viewBoxTransform =
QTransform::fromScale(w / m_d->image.width(), h / m_d->image.height());
SvgUtil::parseAspectRatio(*m_d->ratioParser,
QRectF(QPointF(), size()),
QRect(QPoint(), m_d->image.size()),
&m_d->viewBoxTransform);
}
if (m_d->ratioParser->defer) {
// TODO:
}
return true;
}
diff --git a/plugins/flake/imageshape/ImageShape.h b/plugins/flake/imageshape/ImageShape.h
index 515146a404..108cc8c28f 100644
--- a/plugins/flake/imageshape/ImageShape.h
+++ b/plugins/flake/imageshape/ImageShape.h
@@ -1,56 +1,56 @@
/*
* 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 IMAGESHAPE_H
#define IMAGESHAPE_H
-#include <QScopedPointer>
+#include <QSharedDataPointer>
#include "KoTosContainer.h"
#include <SvgShape.h>
#define ImageShapeId "ImageShape"
class ImageShape : public KoTosContainer, public SvgShape
{
public:
ImageShape();
~ImageShape() override;
KoShape *cloneShape() const override;
- void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override;
+ void paint(QPainter &painter, KoShapePaintingContext &paintContext) const override;
void setSize(const QSizeF &size) override;
void saveOdf(KoShapeSavingContext &context) const override;
bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override;
bool saveSvg(SvgSavingContext &context) override;
bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context) override;
private:
ImageShape(const ImageShape &rhs);
private:
struct Private;
- const QScopedPointer<Private> m_d;
+ QSharedDataPointer<Private> m_d;
};
#endif // IMAGESHAPE_H
diff --git a/plugins/flake/textshape/FontFamilyAction.cpp b/plugins/flake/textshape/FontFamilyAction.cpp
index 09f424e073..58f267ceba 100644
--- a/plugins/flake/textshape/FontFamilyAction.cpp
+++ b/plugins/flake/textshape/FontFamilyAction.cpp
@@ -1,194 +1,194 @@
/* This file is part of the KDE libraries
Copyright (C) 1999 Reginald Stadlbauer <reggie@kde.org>
(C) 1999 Simon Hausmann <hausmann@kde.org>
(C) 2000 Nicolas Hadacek <haadcek@kde.org>
(C) 2000 Kurt Granroth <granroth@kde.org>
(C) 2000 Michael Koch <koch@kde.org>
(C) 2001 Holger Freyther <freyther@kde.org>
(C) 2002 Ellis Whitehead <ellis@kde.org>
(C) 2002 Joseph Wenninger <jowenn@kde.org>
(C) 2003 Andras Mantia <amantia@kde.org>
(C) 2005-2006 Hamish Rodda <rodda@kde.org>
(C) 2007 Clarence Dang <dang@kde.org>
(C) 2014 Dan Leinir Turthra Jensen <admin@leinir.dk>
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.
*/
// This is a minorly modified version of the KFontAction class. It exists
// entirely because there's a hang bug on windows at the moment.
#include "FontFamilyAction.h"
#include <QToolBar>
#include <QDebug>
#include <QIcon>
#include <klocalizedstring.h>
#include <kfontchooser.h>
-#include <KoFontComboBox.h>
+#include <QFontComboBox>
class KoFontFamilyAction::KoFontFamilyActionPrivate
{
public:
KoFontFamilyActionPrivate(KoFontFamilyAction *parent)
: q(parent)
, settingFont(0)
{
}
void _ko_slotFontChanged(const QFont &font)
{
- qDebug() << "KoFontComboBox - slotFontChanged("
+ qDebug() << "QFontComboBox - slotFontChanged("
<< font.family() << ") settingFont=" << settingFont;
if (settingFont) {
return;
}
q->setFont(font.family());
q->triggered(font.family());
qDebug() << "\tslotFontChanged done";
}
KoFontFamilyAction *q;
int settingFont;
};
KoFontFamilyAction::KoFontFamilyAction(uint fontListCriteria, QObject *parent)
: KSelectAction(parent)
, d(new KoFontFamilyActionPrivate(this))
{
QStringList list;
KFontChooser::getFontList(list, fontListCriteria);
KSelectAction::setItems(list);
setEditable(true);
}
KoFontFamilyAction::KoFontFamilyAction(QObject *parent)
: KSelectAction(parent)
, d(new KoFontFamilyActionPrivate(this))
{
QStringList list;
KFontChooser::getFontList(list, 0);
KSelectAction::setItems(list);
setEditable(true);
}
KoFontFamilyAction::KoFontFamilyAction(const QString &text, QObject *parent)
: KSelectAction(text, parent)
, d(new KoFontFamilyActionPrivate(this))
{
QStringList list;
KFontChooser::getFontList(list, 0);
KSelectAction::setItems(list);
setEditable(true);
}
KoFontFamilyAction::KoFontFamilyAction(const QIcon &icon, const QString &text, QObject *parent)
: KSelectAction(icon, text, parent)
, d(new KoFontFamilyActionPrivate(this))
{
QStringList list;
KFontChooser::getFontList(list, 0);
KSelectAction::setItems(list);
setEditable(true);
}
KoFontFamilyAction::~KoFontFamilyAction()
{
delete d;
}
QString KoFontFamilyAction::font() const
{
return currentText();
}
QWidget *KoFontFamilyAction::createWidget(QWidget *parent)
{
qDebug() << "KoFontFamilyAction::createWidget()";
// silence unclear warning from original kfontaction.acpp
// #ifdef __GNUC__
// #warning FIXME: items need to be converted
// #endif
// This is the visual element on the screen. This method overrides
// the KSelectAction one, preventing KSelectAction from creating its
// regular KComboBox.
- KoFontComboBox *cb = new KoFontComboBox(parent);
+ QFontComboBox *cb = new QFontComboBox(parent);
//cb->setFontList(items());
qDebug() << "\tset=" << font();
// Do this before connecting the signal so that nothing will fire.
cb->setCurrentFont(QFont(font().toLower()));
qDebug() << "\tspit back=" << cb->currentFont().family();
connect(cb, SIGNAL(currentFontChanged(QFont)), SLOT(_ko_slotFontChanged(QFont)));
cb->setMinimumWidth(cb->sizeHint().width());
return cb;
}
/*
* Maintenance note: Keep in sync with KFontComboBox::setCurrentFont()
*/
void KoFontFamilyAction::setFont(const QString &family)
{
qDebug() << "KoFontFamilyAction::setFont(" << family << ")";
// Suppress triggered(QString) signal and prevent recursive call to ourself.
d->settingFont++;
Q_FOREACH (QWidget *w, createdWidgets()) {
- KoFontComboBox *cb = qobject_cast<KoFontComboBox *>(w);
+ QFontComboBox *cb = qobject_cast<QFontComboBox *>(w);
qDebug() << "\tw=" << w << "cb=" << cb;
if (!cb) {
continue;
}
cb->setCurrentFont(QFont(family.toLower()));
qDebug() << "\t\tw spit back=" << cb->currentFont().family();
}
d->settingFont--;
qDebug() << "\tcalling setCurrentAction()";
QString lowerName = family.toLower();
if (setCurrentAction(lowerName, Qt::CaseInsensitive)) {
return;
}
int i = lowerName.indexOf(" [");
if (i > -1) {
lowerName = lowerName.left(i);
i = 0;
if (setCurrentAction(lowerName, Qt::CaseInsensitive)) {
return;
}
}
lowerName += " [";
if (setCurrentAction(lowerName, Qt::CaseInsensitive)) {
return;
}
// TODO: Inconsistent state if KFontComboBox::setCurrentFont() succeeded
// but setCurrentAction() did not and vice-versa.
qDebug() << "Font not found " << family.toLower();
}
// have to include this because of Q_PRIVATE_SLOT
#include "moc_FontFamilyAction.cpp"
diff --git a/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp b/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp
index a2b0c4a838..3148323b8d 100644
--- a/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp
+++ b/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp
@@ -1,191 +1,190 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 Sebastian Sauer <sebsauer@kdab.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 "ShrinkToFitShapeContainer.h"
#include <KoShapeSavingContext.h>
#include <KoTextLayoutRootArea.h>
ShrinkToFitShapeContainer::ShrinkToFitShapeContainer(KoShape *childShape, KoDocumentResourceManager *documentResources)
: KoShapeContainer()
, d(new Private(childShape))
{
Q_UNUSED(documentResources);
setPosition(childShape->position());
setSize(childShape->size());
setZIndex(childShape->zIndex());
setRunThrough(childShape->runThrough());
rotate(childShape->rotation());
//setTransformation(childShape->transformation());
if (childShape->parent()) {
childShape->parent()->addShape(this);
childShape->setParent(0);
}
childShape->setPosition(QPointF(0.0, 0.0)); // since its relative to my position, this won't move it
childShape->setSelectable(false); // our ShrinkToFitShapeContainer will handle that from now on
setModel(new ShrinkToFitShapeContainerModel(this));
addShape(childShape);
QSet<KoShape *> delegates;
delegates << childShape;
setToolDelegates(delegates);
KoTextShapeData *data = dynamic_cast<KoTextShapeData *>(childShape->userData());
Q_ASSERT(data);
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(data->document()->documentLayout());
Q_ASSERT(lay);
QObject::connect(lay, SIGNAL(finishedLayout()), static_cast<ShrinkToFitShapeContainerModel *>(model()), SLOT(finishedLayout()));
}
ShrinkToFitShapeContainer::~ShrinkToFitShapeContainer()
{
}
-void ShrinkToFitShapeContainer::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &)
+void ShrinkToFitShapeContainer::paintComponent(QPainter &painter, KoShapePaintingContext &) const
{
Q_UNUSED(painter);
- Q_UNUSED(converter);
//painter.fillRect(converter.documentToView(QRectF(QPointF(0,0),size())), QBrush(QColor("#ffcccc"))); // for testing
}
bool ShrinkToFitShapeContainer::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
Q_UNUSED(element);
Q_UNUSED(context);
return true;
}
void ShrinkToFitShapeContainer::saveOdf(KoShapeSavingContext &context) const
{
d->childShape->saveOdf(context);
}
ShrinkToFitShapeContainer *ShrinkToFitShapeContainer::wrapShape(KoShape *shape, KoDocumentResourceManager *documentResourceManager)
{
Q_ASSERT(dynamic_cast<KoTextShapeData *>(shape->userData()));
Q_ASSERT(qobject_cast<KoTextDocumentLayout *>(dynamic_cast<KoTextShapeData *>(shape->userData())->document()->documentLayout()));
return new ShrinkToFitShapeContainer(shape, documentResourceManager);
}
void ShrinkToFitShapeContainer::tryWrapShape(KoShape *shape, const KoXmlElement &element, KoShapeLoadingContext &context)
{
KoTextShapeData *data = dynamic_cast<KoTextShapeData *>(shape->userData());
if (!data || data->resizeMethod() != KoTextShapeData::ShrinkToFitResize) {
return;
}
KoShapeContainer *oldParent = shape->parent();
ShrinkToFitShapeContainer *tos = wrapShape(shape, context.documentResourceManager());
if (!tos->loadOdf(element, context)) {
shape->setParent(oldParent);
delete tos;
}
}
void ShrinkToFitShapeContainer::unwrapShape(KoShape *shape)
{
Q_ASSERT(shape->parent() == this);
removeShape(shape);
shape->setParent(parent());
QSet<KoShape *> delegates = toolDelegates();
delegates.remove(shape);
setToolDelegates(delegates);
shape->setPosition(position());
shape->setSize(size());
shape->rotate(rotation());
shape->setSelectable(true);
}
ShrinkToFitShapeContainerModel::ShrinkToFitShapeContainerModel(ShrinkToFitShapeContainer *q)
: q(q)
, m_scale(1.0)
, m_dirty(10)
, m_maybeUpdate(false)
{
}
void ShrinkToFitShapeContainerModel::finishedLayout()
{
m_maybeUpdate = true;
containerChanged(q, KoShape::SizeChanged);
m_maybeUpdate = false;
}
void ShrinkToFitShapeContainerModel::containerChanged(KoShapeContainer *container, KoShape::ChangeType type)
{
Q_ASSERT(container == q); Q_UNUSED(container);
if (type == KoShape::SizeChanged) {
KoTextShapeData *data = dynamic_cast<KoTextShapeData *>(q->d->childShape->userData());
Q_ASSERT(data);
KoTextLayoutRootArea *rootArea = data->rootArea();
Q_ASSERT(rootArea);
QSizeF shapeSize = q->size();
QSizeF documentSize = rootArea->boundingRect().size();
if (m_maybeUpdate && shapeSize == m_shapeSize && documentSize == m_documentSize) {
m_dirty = 0;
return; // nothing to update
}
m_shapeSize = shapeSize;
m_documentSize = documentSize;
if (documentSize.width() > 0.0 && documentSize.height() > 0.0) {
if (m_dirty || !m_maybeUpdate) {
qreal scaleX = qMin<qreal>(1.0, shapeSize.width() / documentSize.width());
qreal scaleY = qMin<qreal>(1.0, shapeSize.height() / documentSize.height());
m_scale = (scaleX + scaleY) / 2.0 * 0.95;
if (m_maybeUpdate && m_dirty) {
--m_dirty;
}
}
} else {
m_scale = 1.0;
m_dirty = 1;
}
QSizeF newSize(shapeSize.width() / m_scale, shapeSize.height() / m_scale);
q->d->childShape->setSize(newSize);
QTransform m;
m.scale(m_scale, m_scale);
q->d->childShape->setTransformation(m);
}
}
bool ShrinkToFitShapeContainerModel::inheritsTransform(const KoShape *child) const
{
Q_ASSERT(child == q->d->childShape); Q_UNUSED(child);
return true;
}
bool ShrinkToFitShapeContainerModel::isClipped(const KoShape *child) const
{
Q_ASSERT(child == q->d->childShape); Q_UNUSED(child);
return false;
}
diff --git a/plugins/flake/textshape/ShrinkToFitShapeContainer.h b/plugins/flake/textshape/ShrinkToFitShapeContainer.h
index 3ca8063f9a..a3f7771e21 100644
--- a/plugins/flake/textshape/ShrinkToFitShapeContainer.h
+++ b/plugins/flake/textshape/ShrinkToFitShapeContainer.h
@@ -1,116 +1,116 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 Sebastian Sauer <sebsauer@kdab.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 SHRINKTOFITSHAPECONTAINER_H
#define SHRINKTOFITSHAPECONTAINER_H
#include <KoShape.h>
#include <KoShapeContainer.h>
#include <SimpleShapeContainerModel.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoOdfLoadingContext.h>
#include <KoTextShapeData.h>
#include <QObject>
#include <QPainter>
#include <KoShapeContainer_p.h>
#include <KoTextDocumentLayout.h>
#include <KoShapeLoadingContext.h>
#include <KoDocumentResourceManager.h>
/**
* Container that is used to wrap a shape and shrink a text-shape to fit the content.
*/
class ShrinkToFitShapeContainer : public KoShapeContainer
{
public:
explicit ShrinkToFitShapeContainer(KoShape *childShape, KoDocumentResourceManager *documentResources = 0);
~ShrinkToFitShapeContainer() override;
// reimplemented
- void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
+ void paintComponent(QPainter &painter, KoShapePaintingContext &paintcontext) const override;
// reimplemented
bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override;
// reimplemented
void saveOdf(KoShapeSavingContext &context) const override;
/**
* Factory function to create and return a ShrinkToFitShapeContainer instance that wraps the \a shape with it.
*/
static ShrinkToFitShapeContainer *wrapShape(KoShape *shape, KoDocumentResourceManager *documentResourceManager = 0);
/**
* Try to load text-on-shape from \a element and wrap \a shape with it.
*/
static void tryWrapShape(KoShape *shape, const KoXmlElement &element, KoShapeLoadingContext &context);
/**
* Undo the wrapping done in the \a wrapShape method.
*/
void unwrapShape(KoShape *shape);
private:
friend class ShrinkToFitShapeContainerModel;
class Private;
QSharedDataPointer<Private> d;
};
/**
* \internal d-pointer class for the \a ShrinkToFitShapeContainer class.
*/
class ShrinkToFitShapeContainer::Private : public QSharedData
{
public:
explicit Private(KoShape *childShape) : childShape(childShape) {}
virtual ~Private() = default;
KoShape *childShape; // the original shape not owned by us
};
/**
* The container-model class implements \a KoShapeContainerModel for the \a ShrinkToFitShapeContainer to
* to stuff once our container changes.
*/
class ShrinkToFitShapeContainerModel : public QObject, public SimpleShapeContainerModel
{
Q_OBJECT
friend class ShrinkToFitShapeContainer;
public:
ShrinkToFitShapeContainerModel(ShrinkToFitShapeContainer *q);
// reimplemented
void containerChanged(KoShapeContainer *container, KoShape::ChangeType type) override;
// reimplemented
bool inheritsTransform(const KoShape *child) const override;
// reimplemented
bool isClipped(const KoShape *child) const override;
private Q_SLOTS:
void finishedLayout();
private:
ShrinkToFitShapeContainer *q;
qreal m_scale;
QSizeF m_shapeSize, m_documentSize;
int m_dirty;
bool m_maybeUpdate;
};
#endif
diff --git a/plugins/flake/textshape/TextShape.cpp b/plugins/flake/textshape/TextShape.cpp
index f31b10bf88..02d3cbeeeb 100644
--- a/plugins/flake/textshape/TextShape.cpp
+++ b/plugins/flake/textshape/TextShape.cpp
@@ -1,472 +1,472 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2008-2010 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org>
* Copyright (C) 2010 KO GmbH <cbo@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 "TextShape.h"
#include "ShrinkToFitShapeContainer.h"
#include <KoTextSharedLoadingData.h>
#include "SimpleRootAreaProvider.h"
#include <KoTextLayoutRootArea.h>
#include <KoTextEditor.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceProvider.h>
#include <KoChangeTracker.h>
#include <KoInlineTextObjectManager.h>
#include <KoTextRangeManager.h>
#include <KoOdfLoadingContext.h>
#include <KoOdfWorkaround.h>
#include <KoParagraphStyle.h>
#include <KoPostscriptPaintDevice.h>
#include <KoSelection.h>
#include <KoShapeLoadingContext.h>
#include <KoShapeBackground.h>
#include <KoShapePaintingContext.h>
#include <KoShapeSavingContext.h>
#include <KoText.h>
#include <KoTextDocument.h>
#include <KoTextDocumentLayout.h>
#include <KoTextPage.h>
#include <KoTextShapeContainerModel.h>
#include <KoPageProvider.h>
-#include <KoViewConverter.h>
#include <KoXmlWriter.h>
#include <KoXmlReader.h>
#include <KoXmlNS.h>
#include <KoStyleStack.h>
#include <QAbstractTextDocumentLayout>
#include <QApplication>
#include <QFont>
#include <QPainter>
#include <QPen>
#include <QTextLayout>
#include <QDebug>
#include <KoShapeContainer_p.h>
#include "kis_painting_tweaks.h"
TextShape::TextShape(KoInlineTextObjectManager *inlineTextObjectManager, KoTextRangeManager *textRangeManager)
: KoShapeContainer(new KoTextShapeContainerModel())
, KoFrameShape(KoXmlNS::draw, "text-box")
, m_pageProvider(0)
, m_imageCollection(0)
, m_clip(true)
{
setShapeId(TextShape_SHAPEID);
m_textShapeData = new KoTextShapeData();
setUserData(m_textShapeData);
SimpleRootAreaProvider *provider = new SimpleRootAreaProvider(m_textShapeData, this);
KoTextDocument(m_textShapeData->document()).setInlineTextObjectManager(inlineTextObjectManager);
KoTextDocument(m_textShapeData->document()).setTextRangeManager(textRangeManager);
m_layout = new KoTextDocumentLayout(m_textShapeData->document(), provider);
m_textShapeData->document()->setDocumentLayout(m_layout);
- setCollisionDetection(true);
+ /// FIXME: collision detection was dropped in Krita
+ /// due to performance reasons
+ // setCollisionDetection(true);
QObject::connect(m_layout, SIGNAL(layoutIsDirty()), m_layout, SLOT(scheduleLayout()));
}
TextShape::TextShape(const TextShape &rhs)
: KoShapeContainer(rhs)
, KoFrameShape(rhs)
, m_textShapeData(dynamic_cast<KoTextShapeData*>(rhs.m_textShapeData->clone()))
, m_pageProvider(0)
, m_imageCollection(0)
, m_clip(rhs.m_clip)
{
/// TODO: we need to clone the model
KoTextShapeContainerModel *origModel = dynamic_cast<KoTextShapeContainerModel *>(rhs.model());
if (origModel) {
setModel(new KoTextShapeContainerModel());
}
// XXX: ?
//reinterpret_cast<KoShapeContainerPrivate*>(rhs.d_ptr)->model = new KoTextShapeContainerModel();
setShapeId(TextShape_SHAPEID);
setUserData(m_textShapeData);
SimpleRootAreaProvider *provider = new SimpleRootAreaProvider(m_textShapeData, this);
m_layout = new KoTextDocumentLayout(m_textShapeData->document(), provider);
m_textShapeData->document()->setDocumentLayout(m_layout);
- setCollisionDetection(true);
+ /// FIXME: collision detection was dropped in Krita
+ /// due to performance reasons
+ // setCollisionDetection(true);
+
QObject::connect(m_layout, SIGNAL(layoutIsDirty()), m_layout, SLOT(scheduleLayout()));
updateDocumentData();
m_layout->scheduleLayout();
}
TextShape::~TextShape()
{
}
KoShape *TextShape::cloneShape() const
{
return new TextShape(*this);
}
-void TextShape::paintComponent(QPainter &painter, const KoViewConverter &converter,
- KoShapePaintingContext &paintContext)
+void TextShape::paintComponent(QPainter &painter,
+ KoShapePaintingContext &paintContext) const
{
painter.save();
- applyConversion(painter, converter);
KoBorder *border = this->border();
if (border) {
- paintBorder(painter, converter);
+ const QRectF borderRect = QRectF(QPointF(0, 0), size());
+ border->paint(painter, borderRect, KoBorder::PaintInsideLine);
} else if (paintContext.showTextShapeOutlines) {
// No need to paint the outlines if there is a real border.
if (qAbs(rotation()) > 1) {
painter.setRenderHint(QPainter::Antialiasing);
}
- QPen pen(QColor(210, 210, 210)); // use cosmetic pen
- QPointF onePixel = converter.viewToDocument(QPointF(1.0, 1.0));
- QRectF rect(QPointF(0.0, 0.0), size() - QSizeF(onePixel.x(), onePixel.y()));
- painter.setPen(pen);
- painter.drawRect(rect);
+ // disabled, due to refactoring out of \p converter
+// QPen pen(QColor(210, 210, 210)); // use cosmetic pen
+// QPointF onePixel = converter.viewToDocument(QPointF(1.0, 1.0));
+// QRectF rect(QPointF(0.0, 0.0), size() - QSizeF(onePixel.x(), onePixel.y()));
+// painter.setPen(pen);
+// painter.drawRect(rect);
}
painter.restore();
if (m_textShapeData->isDirty()) { // not layouted yet.
return;
}
QTextDocument *doc = m_textShapeData->document();
Q_ASSERT(doc);
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(doc->documentLayout());
Q_ASSERT(lay);
lay->showInlineObjectVisualization(paintContext.showInlineObjectVisualization);
- applyConversion(painter, converter);
-
if (background()) {
QPainterPath p;
p.addRect(QRectF(QPointF(), size()));
- background()->paint(painter, converter, paintContext, p);
+ background()->paint(painter, paintContext, p);
}
// this enables to use the same shapes on different pages showing different page numbers
if (m_pageProvider) {
KoTextPage *page = m_pageProvider->page(this);
if (page) {
// this is used to not trigger repaints if layout during the painting is done
- m_paintRegion = KisPaintingTweaks::safeClipRegion(painter);
if (!m_textShapeData->rootArea()->page() || page->pageNumber() != m_textShapeData->rootArea()->page()->pageNumber()) {
m_textShapeData->rootArea()->setPage(page); // takes over ownership of the page
} else {
delete page;
}
}
}
KoTextDocumentLayout::PaintContext pc;
QAbstractTextDocumentLayout::Selection selection;
KoTextEditor *textEditor = KoTextDocument(m_textShapeData->document()).textEditor();
selection.cursor = *(textEditor->cursor());
QPalette palette = pc.textContext.palette;
selection.format.setBackground(palette.brush(QPalette::Highlight));
selection.format.setForeground(palette.brush(QPalette::HighlightedText));
pc.textContext.selections.append(selection);
pc.textContext.selections += KoTextDocument(doc).selections();
- pc.viewConverter = &converter;
pc.imageCollection = m_imageCollection;
pc.showFormattingCharacters = paintContext.showFormattingCharacters;
pc.showTableBorders = paintContext.showTableBorders;
pc.showSectionBounds = paintContext.showSectionBounds;
pc.showSpellChecking = paintContext.showSpellChecking;
pc.showSelections = paintContext.showSelections;
// When clipping the painter we need to make sure not to cutoff cosmetic pens which
// may used to draw e.g. table-borders for user convenience when on screen (but not
// on e.g. printing). Such cosmetic pens are special cause they will always have the
// same pen-width (1 pixel) independent of zoom-factor or painter transformations and
// are not taken into account in any border-calculations.
QRectF clipRect = outlineRect();
qreal cosmeticPenX = 1 * 72. / painter.device()->logicalDpiX();
qreal cosmeticPenY = 1 * 72. / painter.device()->logicalDpiY();
painter.setClipRect(clipRect.adjusted(-cosmeticPenX, -cosmeticPenY, cosmeticPenX, cosmeticPenY), Qt::IntersectClip);
painter.save();
painter.translate(0, -m_textShapeData->documentOffset());
m_textShapeData->rootArea()->paint(&painter, pc); // only need to draw ourselves
painter.restore();
- m_paintRegion = QRegion();
}
QPointF TextShape::convertScreenPos(const QPointF &point) const
{
- QPointF p = absoluteTransformation(0).inverted().map(point);
+ QPointF p = absoluteTransformation().inverted().map(point);
return p + QPointF(0.0, m_textShapeData->documentOffset());
}
QPainterPath TextShape::outline() const
{
QPainterPath path;
path.addRect(QRectF(QPointF(0, 0), size()));
return path;
}
QRectF TextShape::outlineRect() const
{
if (m_textShapeData->rootArea()) {
QRectF rect = m_textShapeData->rootArea()->boundingRect();
rect.moveTop(rect.top() - m_textShapeData->rootArea()->top());
if (m_clip) {
rect.setHeight(size().height());
}
return rect | QRectF(QPointF(0, 0), size());
}
return QRectF(QPointF(0, 0), size());
}
void TextShape::shapeChanged(ChangeType type, KoShape *shape)
{
Q_UNUSED(shape);
KoShapeContainer::shapeChanged(type, shape);
- if (type == PositionChanged || type == SizeChanged || type == CollisionDetected) {
+ if (type == PositionChanged || type == SizeChanged /* || type == CollisionDetected*/) {
m_textShapeData->setDirty();
}
}
void TextShape::saveOdf(KoShapeSavingContext &context) const
{
KoXmlWriter &writer = context.xmlWriter();
QString textHeight = additionalAttribute("fo:min-height");
const_cast<TextShape *>(this)->removeAdditionalAttribute("fo:min-height");
writer.startElement("draw:frame");
// if the TextShape is wrapped in a shrink to fit container we need to save the geometry of the container as
// the geometry of the shape might have been changed.
if (ShrinkToFitShapeContainer *stf = dynamic_cast<ShrinkToFitShapeContainer *>(this->parent())) {
stf->saveOdfAttributes(context, OdfSize | OdfPosition | OdfTransformation);
saveOdfAttributes(context, OdfAdditionalAttributes | OdfMandatories | OdfCommonChildElements);
} else {
saveOdfAttributes(context, OdfAllAttributes);
}
writer.startElement("draw:text-box");
if (!textHeight.isEmpty()) {
writer.addAttribute("fo:min-height", textHeight);
}
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(m_textShapeData->document()->documentLayout());
int index = -1;
if (lay) {
int i = 0;
foreach (KoShape *shape, lay->shapes()) {
if (shape == this) {
index = i;
} else if (index >= 0) {
writer.addAttribute("draw:chain-next-name", shape->name());
break;
}
++i;
}
}
const bool saveMyText = index == 0; // only save the text once.
m_textShapeData->saveOdf(context, 0, 0, saveMyText ? -1 : 0);
writer.endElement(); // draw:text-box
saveOdfCommonChildElements(context);
writer.endElement(); // draw:frame
}
QString TextShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const
{
Qt::Alignment vAlign(m_textShapeData->verticalAlignment());
QString verticalAlign = "top";
if (vAlign == Qt::AlignBottom) {
verticalAlign = "bottom";
} else if (vAlign == Qt::AlignVCenter) {
verticalAlign = "middle";
}
style.addProperty("draw:textarea-vertical-align", verticalAlign);
KoTextShapeData::ResizeMethod resize = m_textShapeData->resizeMethod();
if (resize == KoTextShapeData::AutoGrowWidth || resize == KoTextShapeData::AutoGrowWidthAndHeight) {
style.addProperty("draw:auto-grow-width", "true");
}
if (resize != KoTextShapeData::AutoGrowHeight && resize != KoTextShapeData::AutoGrowWidthAndHeight) {
style.addProperty("draw:auto-grow-height", "false");
}
if (resize == KoTextShapeData::ShrinkToFitResize) {
style.addProperty("draw:fit-to-size", "true");
}
m_textShapeData->saveStyle(style, context);
return KoShape::saveStyle(style, context);
}
void TextShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context)
{
KoShape::loadStyle(element, context);
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.setTypeProperties("graphic");
QString verticalAlign(styleStack.property(KoXmlNS::draw, "textarea-vertical-align"));
Qt::Alignment alignment(Qt::AlignTop);
if (verticalAlign == "bottom") {
alignment = Qt::AlignBottom;
} else if (verticalAlign == "justify") {
// not yet supported
alignment = Qt::AlignVCenter;
} else if (verticalAlign == "middle") {
alignment = Qt::AlignVCenter;
}
m_textShapeData->setVerticalAlignment(alignment);
const QString fitToSize = styleStack.property(KoXmlNS::draw, "fit-to-size");
KoTextShapeData::ResizeMethod resize = KoTextShapeData::NoResize;
if (fitToSize == "true" || fitToSize == "shrink-to-fit") { // second is buggy value from impress
resize = KoTextShapeData::ShrinkToFitResize;
} else {
// An explicit svg:width or svg:height defined do change the default value (means those value
// used if not explicit defined otherwise) for auto-grow-height and auto-grow-height. So
// they are mutable exclusive.
// It is not clear (means we did not test and took care of it) what happens if both are
// defined and are in conflict with each other or how the fit-to-size is related to this.
QString autoGrowWidth = styleStack.property(KoXmlNS::draw, "auto-grow-width");
if (autoGrowWidth.isEmpty()) {
autoGrowWidth = element.hasAttributeNS(KoXmlNS::svg, "width") ? "false" : "true";
}
QString autoGrowHeight = styleStack.property(KoXmlNS::draw, "auto-grow-height");
if (autoGrowHeight.isEmpty()) {
autoGrowHeight = element.hasAttributeNS(KoXmlNS::svg, "height") ? "false" : "true";
}
if (autoGrowWidth == "true") {
resize = autoGrowHeight == "true" ? KoTextShapeData::AutoGrowWidthAndHeight : KoTextShapeData::AutoGrowWidth;
} else if (autoGrowHeight == "true") {
resize = KoTextShapeData::AutoGrowHeight;
}
}
m_textShapeData->setResizeMethod(resize);
}
bool TextShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
m_textShapeData->document()->setUndoRedoEnabled(false);
loadOdfAttributes(element, context, OdfAllAttributes);
// this cannot be done in loadStyle as that fills the style stack incorrectly and therefore
// it results in wrong data being loaded.
m_textShapeData->loadStyle(element, context);
#ifndef NWORKAROUND_ODF_BUGS
KoTextShapeData::ResizeMethod method = m_textShapeData->resizeMethod();
if (KoOdfWorkaround::fixAutoGrow(method, context)) {
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(m_textShapeData->document()->documentLayout());
Q_ASSERT(lay);
if (lay) {
SimpleRootAreaProvider *provider = dynamic_cast<SimpleRootAreaProvider *>(lay->provider());
if (provider) {
provider->m_fixAutogrow = true;
}
}
}
#endif
bool answer = loadOdfFrame(element, context);
m_textShapeData->document()->setUndoRedoEnabled(true);
return answer;
}
bool TextShape::loadOdfFrame(const KoXmlElement &element, KoShapeLoadingContext &context)
{
// If the loadOdfFrame from the base class for draw:text-box fails, check
// for table:table, because that is a legal child of draw:frame in ODF 1.2.
if (!KoFrameShape::loadOdfFrame(element, context)) {
const KoXmlElement &possibleTableElement(KoXml::namedItemNS(element, KoXmlNS::table, "table"));
if (possibleTableElement.isNull()) {
return false;
} else {
return loadOdfFrameElement(possibleTableElement, context);
}
}
return true;
}
bool TextShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context)
{
bool ok = m_textShapeData->loadOdf(element, context, 0, this);
if (ok) {
ShrinkToFitShapeContainer::tryWrapShape(this, element, context);
}
return ok;
}
void TextShape::update() const
{
KoShapeContainer::update();
}
void TextShape::updateAbsolute(const QRectF &shape) const
{
// this is done to avoid updates which are called during the paint event and not needed.
//if (!m_paintRegion.contains(shape.toRect())) {
//KoShape::update(shape);
//}
// FIXME: just a hack to remove outdated call from the deprecated shape
KoShape::updateAbsolute(shape);
}
-void TextShape::waitUntilReady(const KoViewConverter &, bool asynchronous) const
+void TextShape::waitUntilReady(bool asynchronous) const
{
Q_UNUSED(asynchronous);
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(m_textShapeData->document()->documentLayout());
Q_ASSERT(lay);
if (m_textShapeData->isDirty()) {
// Do a simple layout-call which will make sure to relayout till things are done. If more
// layouts are scheduled then we don't need to wait for them here but can just continue.
lay->layout();
}
}
KoImageCollection *TextShape::imageCollection()
{
return m_imageCollection;
}
void TextShape::updateDocumentData()
{
if (m_layout) {
KoTextDocument document(m_textShapeData->document());
m_layout->setStyleManager(document.styleManager());
m_layout->setInlineTextObjectManager(document.inlineTextObjectManager());
m_layout->setTextRangeManager(document.textRangeManager());
m_layout->setChangeTracker(document.changeTracker());
}
}
diff --git a/plugins/flake/textshape/TextShape.h b/plugins/flake/textshape/TextShape.h
index 411b91c8d5..7d850e8279 100644
--- a/plugins/flake/textshape/TextShape.h
+++ b/plugins/flake/textshape/TextShape.h
@@ -1,147 +1,146 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org>
* Copyright (C) 2010 KO GmbH <cbo@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 KOTEXTSHAPE_H
#define KOTEXTSHAPE_H
#include <KoShapeContainer.h>
#include <KoFrameShape.h>
#include <KoTextShapeData.h>
#include <KoTextDocument.h>
#include <QTextDocument>
#include <QPainter>
#define TextShape_SHAPEID "TextShapeID"
class KoInlineTextObjectManager;
class KoTextRangeManager;
class KoPageProvider;
class KoImageCollection;
class KoTextDocument;
class TextShape;
class KoTextDocumentLayout;
class KoParagraphStyle;
/**
* A text shape.
* The Text shape is capable of drawing structured text.
* @see KoTextShapeData
*/
class TextShape : public KoShapeContainer, public KoFrameShape
{
public:
TextShape(KoInlineTextObjectManager *inlineTextObjectManager, KoTextRangeManager *textRangeManager);
~TextShape() override;
KoShape* cloneShape() const override;
/// reimplemented
- void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) override;
+ void paintComponent(QPainter &painter, KoShapePaintingContext &paintcontext) const override;
/// reimplemented
- void waitUntilReady(const KoViewConverter &converter, bool asynchronous) const override;
+ void waitUntilReady(bool asynchronous) const override;
/// helper method.
QPointF convertScreenPos(const QPointF &point) const;
/// reimplemented
QPainterPath outline() const override;
/// reimplemented
QRectF outlineRect() const override;
///reimplemented
ChildZOrderPolicy childZOrderPolicy() override
{
return KoShape::ChildZPassThrough;
}
/// set the image collection which is needed to draw bullet from images
void setImageCollection(KoImageCollection *collection)
{
m_imageCollection = collection;
}
KoImageCollection *imageCollection();
/**
* From KoShape reimplemented method to load the TextShape from ODF.
*
* This method redirects the call to the KoTextShapeData::loadOdf() method which
* in turn will call the KoTextLoader::loadBody() method that reads the element
* into a QTextCursor.
*
* @param context the KoShapeLoadingContext used for loading.
* @param element element which represents the shape in odf.
* @return false if loading failed.
*/
bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) override;
/**
* From KoShape reimplemented method to store the TextShape data as ODF.
*
* @param context the KoShapeSavingContext used for saving.
*/
void saveOdf(KoShapeSavingContext &context) const override;
KoTextShapeData *textShapeData()
{
return m_textShapeData;
}
void updateDocumentData();
void update() const override;
void updateAbsolute(const QRectF &shape) const override;
// required for kpresenter hack
void setPageProvider(KoPageProvider *provider)
{
m_pageProvider = provider;
}
/// reimplemented
bool loadOdfFrame(const KoXmlElement &element, KoShapeLoadingContext &context) override;
protected:
bool loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) override;
/// reimplemented
void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) override;
/// reimplemented
QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const override;
TextShape(const TextShape &rhs);
private:
void shapeChanged(ChangeType type, KoShape *shape = 0) override;
KoTextShapeData *m_textShapeData;
KoPageProvider *m_pageProvider;
KoImageCollection *m_imageCollection;
- QRegion m_paintRegion;
bool m_clip;
KoTextDocumentLayout *m_layout;
};
#endif
diff --git a/plugins/flake/textshape/TextTool.cpp b/plugins/flake/textshape/TextTool.cpp
index 0b77056d1a..090ab74eda 100644
--- a/plugins/flake/textshape/TextTool.cpp
+++ b/plugins/flake/textshape/TextTool.cpp
@@ -1,3140 +1,3140 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
* Copyright (C) 2008, 2012 Pierre Stirnweiss <pstirnweiss@googlemail.org>
* Copyright (C) 2009 KO GmbH <cbo@kogmbh.com>
* Copyright (C) 2011 Mojtaba Shahi Senobari <mojtaba.shahi3000@gmail.com>
* Copyright (C) 2014 Denis Kuplyakov <dener.kup@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 "TextTool.h"
#include "TextEditingPluginContainer.h"
#include "dialogs/SimpleCharacterWidget.h"
#include "dialogs/SimpleParagraphWidget.h"
#include "dialogs/SimpleTableWidget.h"
#include "dialogs/SimpleInsertWidget.h"
#include "dialogs/ParagraphSettingsDialog.h"
#include "dialogs/StyleManagerDialog.h"
#include "dialogs/InsertCharacter.h"
#include "dialogs/FontDia.h"
#include "dialogs/TableDialog.h"
#include "dialogs/SectionFormatDialog.h"
#include "dialogs/SectionsSplitDialog.h"
#include "commands/AutoResizeCommand.h"
#include "commands/ChangeListLevelCommand.h"
#include "FontSizeAction.h"
#include "FontFamilyAction.h"
#include <KoOdf.h>
#include <KoCanvasBase.h>
#include <KoShapeController.h>
#include <KoCanvasController.h>
#include <KoCanvasResourceProvider.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxy.h>
#include <KoPointerEvent.h>
#include <KoColor.h>
#include <KoColorBackground.h>
#include <KoColorPopupAction.h>
#include <KoTextDocumentLayout.h>
#include <KoParagraphStyle.h>
#include <KoToolSelection.h>
#include <KoTextEditingPlugin.h>
#include <KoTextEditingRegistry.h>
#include <KoInlineTextObjectManager.h>
#include <KoTextRangeManager.h>
#include <KoStyleManager.h>
#include <KoTextOdfSaveHelper.h>
#include <KoTextDrag.h>
#include <KoTextDocument.h>
#include <KoTextEditor.h>
#include <KoChangeTracker.h>
#include <KoChangeTrackerElement.h>
#include <KoInlineNote.h>
#include <KoBookmark.h>
#include <KoBookmarkManager.h>
#include <KoListLevelProperties.h>
#include <KoTextLayoutRootArea.h>
//#include <ResizeTableCommand.h>
#include <KoIcon.h>
#include <KoViewConverter.h>
#include <KoShapeFactoryBase.h>
#include "kis_action_registry.h"
#include <QDebug>
#include <kstandardshortcut.h>
#include <kactionmenu.h>
#include <kstandardaction.h>
#include <ksharedconfig.h>
#include <QDesktopServices>
#include <QMenu>
#include <QMenuBar>
#include <QAction>
#include <QTextTable>
#include <QTextList>
#include <QTabWidget>
#include <QTextDocumentFragment>
#include <QToolTip>
#include <KisSignalMapper.h>
#include <QLinearGradient>
#include <QBitmap>
#include <QDrag>
#include <QDragLeaveEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMimeData>
#include "KoShapeControllerBase.h"
#include <KoAnnotation.h>
#include <KoShapeRegistry.h>
#include <kuser.h>
#include <KoDocumentRdfBase.h>
class TextToolSelection : public KoToolSelection
{
public:
TextToolSelection(QWeakPointer<KoTextEditor> editor)
: KoToolSelection(0)
, m_editor(editor)
{
}
bool hasSelection() override
{
if (!m_editor.isNull()) {
return m_editor.data()->hasSelection();
}
return false;
}
QWeakPointer<KoTextEditor> m_editor;
};
static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut)
{
foreach (const QKeySequence &ks, KStandardShortcut::shortcut(shortcut)) {
if (input == ks) {
return true;
}
}
return false;
}
TextTool::TextTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_textShape(0)
, m_textShapeData(0)
, m_changeTracker(0)
, m_allowActions(true)
, m_allowAddUndoCommand(true)
, m_allowResourceManagerUpdates(true)
, m_prevCursorPosition(-1)
, m_caretTimer(this)
, m_caretTimerState(true)
, m_currentCommand(0)
, m_currentCommandHasChildren(false)
, m_specialCharacterDocker(0)
, m_textTyping(false)
, m_textDeleting(false)
, m_editTipTimer(this)
, m_delayedEnsureVisible(false)
, m_toolSelection(0)
, m_tableDraggedOnce(false)
, m_tablePenMode(false)
, m_lastImMicroFocus(QRectF(0, 0, 0, 0))
, m_drag(0)
{
setTextMode(true);
createActions();
m_unit = canvas->resourceManager()->unitResource(KoCanvasResourceProvider::Unit);
foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) {
connect(plugin, SIGNAL(startMacro(QString)),
this, SLOT(startMacro(QString)));
connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro()));
QHash<QString, QAction *> actions = plugin->actions();
QHash<QString, QAction *>::iterator i = actions.begin();
while (i != actions.end()) {
// addAction(i.key(), i.value());
++i;
}
}
m_contextMenu.reset(new QMenu());
// setup the context list.
KisSignalMapper *signalMapper = new KisSignalMapper(this);
connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(startTextEditingPlugin(QString)));
m_contextMenu->addAction(this->action("format_font"));
foreach (const QString &key, KoTextEditingRegistry::instance()->keys()) {
KoTextEditingFactory *factory = KoTextEditingRegistry::instance()->value(key);
if (factory->showInMenu()) {
QAction *a = new QAction(factory->title(), this);
connect(a, SIGNAL(triggered()), signalMapper, SLOT(map()));
signalMapper->setMapping(a, factory->id());
m_contextMenu->addAction(a);
// addAction(QString("apply_%1").arg(factory->id()), a);
}
}
connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeAddedToCanvas()));
m_caretTimer.setInterval(500);
connect(&m_caretTimer, SIGNAL(timeout()), this, SLOT(blinkCaret()));
m_editTipTimer.setInterval(500);
m_editTipTimer.setSingleShot(true);
connect(&m_editTipTimer, SIGNAL(timeout()), this, SLOT(showEditTip()));
}
void TextTool::createActions()
{
bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceProvider::ApplicationSpeciality)
& KoCanvasResourceProvider::NoAdvancedText);
KisActionRegistry *actionRegistry = KisActionRegistry::instance();
// FIXME: find new icons for these
m_actionConfigureSection = actionRegistry->makeQAction("configure_section", this);
// addAction("configure_section", m_actionConfigureSection);
connect(m_actionConfigureSection, SIGNAL(triggered(bool)), this, SLOT(configureSection()));
m_actionInsertSection = actionRegistry->makeQAction("insert_section", this);
// addAction("insert_section", m_actionInsertSection);
connect(m_actionInsertSection, SIGNAL(triggered(bool)), this, SLOT(insertNewSection()));
m_actionSplitSections = actionRegistry->makeQAction("split_sections", this);
// addAction("split_sections", m_actionSplitSections);
connect(m_actionSplitSections, SIGNAL(triggered(bool)), this, SLOT(splitSections()));
m_actionPasteAsText = actionRegistry->makeQAction("edit_paste_text", this);
// addAction("edit_paste_text", m_actionPasteAsText);
connect(m_actionPasteAsText, SIGNAL(triggered(bool)), this, SLOT(pasteAsText()));
m_actionFormatBold = actionRegistry->makeQAction("format_bold", this);
// addAction("format_bold", m_actionFormatBold);
m_actionFormatBold->setCheckable(true);
connect(m_actionFormatBold, SIGNAL(triggered(bool)), this, SLOT(bold(bool)));
m_actionFormatItalic = actionRegistry->makeQAction("format_italic", this);
m_actionFormatItalic->setCheckable(true);
// addAction("format_italic", m_actionFormatItalic);
connect(m_actionFormatItalic, SIGNAL(triggered(bool)), this, SLOT(italic(bool)));
m_actionFormatUnderline = actionRegistry->makeQAction("format_underline", this);
m_actionFormatUnderline->setCheckable(true);
// addAction("format_underline", m_actionFormatUnderline);
connect(m_actionFormatUnderline, SIGNAL(triggered(bool)), this, SLOT(underline(bool)));
m_actionFormatStrikeOut = actionRegistry->makeQAction("format_strike", this);
m_actionFormatStrikeOut->setCheckable(true);
// addAction("format_strike", m_actionFormatStrikeOut);
connect(m_actionFormatStrikeOut, SIGNAL(triggered(bool)), this, SLOT(strikeOut(bool)));
QActionGroup *alignmentGroup = new QActionGroup(this);
m_actionAlignLeft = actionRegistry->makeQAction("format_alignleft", this);
m_actionAlignLeft->setCheckable(true);
alignmentGroup->addAction(m_actionAlignLeft);
// addAction("format_alignleft", m_actionAlignLeft);
connect(m_actionAlignLeft, SIGNAL(triggered(bool)), this, SLOT(alignLeft()));
m_actionAlignRight = actionRegistry->makeQAction("format_alignright", this);
m_actionAlignRight->setCheckable(true);
alignmentGroup->addAction(m_actionAlignRight);
// addAction("format_alignright", m_actionAlignRight);
connect(m_actionAlignRight, SIGNAL(triggered(bool)), this, SLOT(alignRight()));
m_actionAlignCenter = actionRegistry->makeQAction("format_aligncenter", this);
m_actionAlignCenter->setCheckable(true);
// addAction("format_aligncenter", m_actionAlignCenter);
alignmentGroup->addAction(m_actionAlignCenter);
connect(m_actionAlignCenter, SIGNAL(triggered(bool)), this, SLOT(alignCenter()));
m_actionAlignBlock = actionRegistry->makeQAction("format_alignblock", this);
m_actionAlignBlock->setCheckable(true);
alignmentGroup->addAction(m_actionAlignBlock);
// addAction("format_alignblock", m_actionAlignBlock);
connect(m_actionAlignBlock, SIGNAL(triggered(bool)), this, SLOT(alignBlock()));
m_actionChangeDirection = actionRegistry->makeQAction("change_text_direction", this);
m_actionChangeDirection->setCheckable(true);
// addAction("change_text_direction", m_actionChangeDirection);
connect(m_actionChangeDirection, SIGNAL(triggered()), this, SLOT(textDirectionChanged()));
m_actionFormatSuper = actionRegistry->makeQAction("format_super", this);
m_actionFormatSuper->setCheckable(true);
// addAction("format_super", m_actionFormatSuper);
connect(m_actionFormatSuper, SIGNAL(triggered(bool)), this, SLOT(superScript(bool)));
m_actionFormatSub = actionRegistry->makeQAction("format_sub", this);
m_actionFormatSub->setCheckable(true);
// addAction("format_sub", m_actionFormatSub);
connect(m_actionFormatSub, SIGNAL(triggered(bool)), this, SLOT(subScript(bool)));
// TODO: check these rtl-things work properly
m_actionFormatIncreaseIndent = actionRegistry->makeQAction("format_increaseindent", this);
// addAction("format_increaseindent", m_actionFormatIncreaseIndent);
connect(m_actionFormatIncreaseIndent, SIGNAL(triggered()), this, SLOT(increaseIndent()));
m_actionFormatDecreaseIndent = actionRegistry->makeQAction("format_decreaseindent", this);
// addAction("format_decreaseindent", m_actionFormatDecreaseIndent);
connect(m_actionFormatDecreaseIndent, SIGNAL(triggered()), this, SLOT(decreaseIndent()));
const char *const increaseIndentActionIconName =
QApplication::isRightToLeft() ? koIconNameCStr("format-indent-less") : koIconNameCStr("format-indent-more");
m_actionFormatIncreaseIndent->setIcon(koIcon(increaseIndentActionIconName));
const char *const decreaseIndentActionIconName =
QApplication::isRightToLeft() ? koIconNameCStr("format_decreaseindent") : koIconNameCStr("format-indent-less");
m_actionFormatIncreaseIndent->setIcon(koIcon(decreaseIndentActionIconName));
QAction *action = actionRegistry->makeQAction("format_bulletlist", this);
// addAction("format_bulletlist", action);
action = actionRegistry->makeQAction("format_numberlist", this);
// addAction("format_numberlist", action);
action = actionRegistry->makeQAction("fontsizeup", this);
// addAction("fontsizeup", action);
connect(action, SIGNAL(triggered()), this, SLOT(increaseFontSize()));
action = actionRegistry->makeQAction("fontsizedown", this);
// addAction("fontsizedown", action);
connect(action, SIGNAL(triggered()), this, SLOT(decreaseFontSize()));
m_actionFormatFontFamily = new KoFontFamilyAction(this);
m_actionFormatFontFamily->setText(i18n("Font Family"));
// addAction("format_fontfamily", m_actionFormatFontFamily);
connect(m_actionFormatFontFamily, SIGNAL(triggered(QString)),
this, SLOT(setFontFamily(QString)));
m_variableMenu = new KActionMenu(i18n("Variable"), this);
// addAction("insert_variable", m_variableMenu);
// ------------------- Actions with a key binding and no GUI item
action = actionRegistry->makeQAction("nonbreaking_space", this);
// addAction("nonbreaking_space", action);
connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingSpace()));
action = actionRegistry->makeQAction("nonbreaking_hyphen", this);
// addAction("nonbreaking_hyphen", action);
connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingHyphen()));
action = actionRegistry->makeQAction("insert_index", this);
// addAction("insert_index", action);
connect(action, SIGNAL(triggered()), this, SLOT(insertIndexMarker()));
action = actionRegistry->makeQAction("soft_hyphen", this);
// TODO: double check this one works, conflicts with "zoom out"
// addAction("soft_hyphen", action);
connect(action, SIGNAL(triggered()), this, SLOT(softHyphen()));
if (useAdvancedText) {
action = actionRegistry->makeQAction("line_break", this);
// addAction("line_break", action);
connect(action, SIGNAL(triggered()), this, SLOT(lineBreak()));
action = actionRegistry->makeQAction("insert_framebreak", this);
// addAction("insert_framebreak", action);
connect(action, SIGNAL(triggered()), this, SLOT(insertFrameBreak()));
}
action = actionRegistry->makeQAction("format_font", this);
// addAction("format_font", action);
connect(action, SIGNAL(triggered()), this, SLOT(selectFont()));
m_actionFormatFontSize = new FontSizeAction(i18n("Font Size"), this);
// addAction("format_fontsize", m_actionFormatFontSize);
connect(m_actionFormatFontSize, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal)));
m_actionFormatTextColor = new KoColorPopupAction(this);
// addAction("format_textcolor", m_actionFormatTextColor);
connect(m_actionFormatTextColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setTextColor(KoColor)));
m_actionFormatBackgroundColor = new KoColorPopupAction(this);
// addAction("format_backgroundcolor", m_actionFormatBackgroundColor);
connect(m_actionFormatBackgroundColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setBackgroundColor(KoColor)));
m_growWidthAction = actionRegistry->makeQAction("grow_to_fit_width", this);
// addAction("grow_to_fit_width", m_growWidthAction);
connect(m_growWidthAction, SIGNAL(triggered(bool)), this, SLOT(setGrowWidthToFit(bool)));
m_growHeightAction = actionRegistry->makeQAction("grow_to_fit_height", this);
// addAction("grow_to_fit_height", m_growHeightAction);
connect(m_growHeightAction, SIGNAL(triggered(bool)), this, SLOT(setGrowHeightToFit(bool)));
m_shrinkToFitAction = actionRegistry->makeQAction("shrink_to_fit", this);
// addAction("shrink_to_fit", m_shrinkToFitAction);
connect(m_shrinkToFitAction, SIGNAL(triggered(bool)), this, SLOT(setShrinkToFit(bool)));
if (useAdvancedText) {
action = actionRegistry->makeQAction("insert_table", this);
// addAction("insert_table", action);
connect(action, SIGNAL(triggered()), this, SLOT(insertTable()));
action = actionRegistry->makeQAction("insert_tablerow_above", this);
// addAction("insert_tablerow_above", action);
connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowAbove()));
action = actionRegistry->makeQAction("insert_tablerow_below", this);
// addAction("insert_tablerow_below", action);
connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowBelow()));
action = actionRegistry->makeQAction("insert_tablecolumn_left", this);
// addAction("insert_tablecolumn_left", action);
connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnLeft()));
action = actionRegistry->makeQAction("insert_tablecolumn_right", this);
// addAction("insert_tablecolumn_right", action);
connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnRight()));
action = actionRegistry->makeQAction("delete_tablecolumn", this);
// addAction("delete_tablecolumn", action);
connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableColumn()));
action = actionRegistry->makeQAction("delete_tablerow", this);
// addAction("delete_tablerow", action);
connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableRow()));
action = actionRegistry->makeQAction("merge_tablecells", this);
// addAction("merge_tablecells", action);
connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeTableCells()));
action = actionRegistry->makeQAction("split_tablecells", this);
// addAction("split_tablecells", action);
connect(action, SIGNAL(triggered(bool)), this, SLOT(splitTableCells()));
action = actionRegistry->makeQAction("activate_borderpainter", this);
// addAction("activate_borderpainter", action);
}
action = actionRegistry->makeQAction("format_paragraph", this);
// addAction("format_paragraph", action);
connect(action, SIGNAL(triggered()), this, SLOT(formatParagraph()));
action = actionRegistry->makeQAction("format_stylist", this);
// addAction("format_stylist", action);
connect(action, SIGNAL(triggered()), this, SLOT(showStyleManager()));
action = KStandardAction::selectAll(this, SLOT(selectAll()), this);
// addAction("edit_select_all", action);
action = actionRegistry->makeQAction("insert_specialchar", this);
// addAction("insert_specialchar", action);
connect(action, SIGNAL(triggered()), this, SLOT(insertSpecialCharacter()));
action = actionRegistry->makeQAction("repaint", this);
// addAction("repaint", action);
connect(action, SIGNAL(triggered()), this, SLOT(relayoutContent()));
action = actionRegistry->makeQAction("insert_annotation", this);
// addAction("insert_annotation", action);
connect(action, SIGNAL(triggered()), this, SLOT(insertAnnotation()));
#ifndef NDEBUG
action = actionRegistry->makeQAction("detailed_debug_paragraphs", this);
// addAction("detailed_debug_paragraphs", action);
connect(action, SIGNAL(triggered()), this, SLOT(debugTextDocument()));
action = actionRegistry->makeQAction("detailed_debug_styles", this);
// addAction("detailed_debug_styles", action);
connect(action, SIGNAL(triggered()), this, SLOT(debugTextStyles()));
#endif
}
#ifndef NDEBUG
#include "tests/MockShapes.h"
#include <kundo2stack.h>
#include <KisMimeDatabase.h>
TextTool::TextTool(MockCanvas *canvas) // constructor for our unit tests;
: KoToolBase(canvas),
m_textShape(0),
m_textShapeData(0),
m_changeTracker(0),
m_allowActions(true),
m_allowAddUndoCommand(true),
m_allowResourceManagerUpdates(true),
m_prevCursorPosition(-1),
m_caretTimer(this),
m_caretTimerState(true),
m_currentCommand(0),
m_currentCommandHasChildren(false),
m_specialCharacterDocker(0),
m_textEditingPlugins(0)
, m_editTipTimer(this)
, m_delayedEnsureVisible(false)
, m_tableDraggedOnce(false)
, m_tablePenMode(false)
{
// we could init some vars here, but we probably don't have to
QLocale::setDefault(QLocale("en"));
QTextDocument *document = new QTextDocument(); // this document is leaked
KoInlineTextObjectManager *inlineManager = new KoInlineTextObjectManager();
KoTextDocument(document).setInlineTextObjectManager(inlineManager);
KoTextRangeManager *locationManager = new KoTextRangeManager();
KoTextDocument(document).setTextRangeManager(locationManager);
m_textEditor = new KoTextEditor(document);
KoTextDocument(document).setTextEditor(m_textEditor.data());
m_toolSelection = new TextToolSelection(m_textEditor);
m_changeTracker = new KoChangeTracker();
KoTextDocument(document).setChangeTracker(m_changeTracker);
KoTextDocument(document).setUndoStack(new KUndo2Stack());
}
#endif
TextTool::~TextTool()
{
delete m_toolSelection;
KIS_SAFE_ASSERT_RECOVER (!m_currentCommand) {
delete m_currentCommand;
}
}
void TextTool::showEditTip()
{
if (!m_textShapeData || m_editTipPointedAt.position == -1) {
return;
}
QTextCursor c(m_textShapeData->document());
c.setPosition(m_editTipPointedAt.position);
QString text = "<p align=center style=\'white-space:pre\' >";
int toolTipWidth = 0;
if (m_changeTracker && m_changeTracker->containsInlineChanges(c.charFormat())
&& m_changeTracker->displayChanges()) {
KoChangeTrackerElement *element = m_changeTracker->elementById(c.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt());
if (element->isEnabled()) {
QString changeType;
if (element->getChangeType() == KoGenChange::InsertChange) {
changeType = i18n("Insertion");
} else if (element->getChangeType() == KoGenChange::DeleteChange) {
changeType = i18n("Deletion");
} else {
changeType = i18n("Formatting");
}
text += "<b>" + changeType + "</b><br/>";
QString date = element->getDate();
//Remove the T which separates the Data and Time.
date[10] = QLatin1Char(' ');
date = element->getCreator() + QLatin1Char(' ') + date;
text += date + "</p>";
toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(date).width();
}
}
if (m_editTipPointedAt.bookmark || !m_editTipPointedAt.externalHRef.isEmpty()) {
QString help = i18n("Ctrl+click to go to link ");
help += m_editTipPointedAt.externalHRef;
text += help + "</p>";
toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width();
}
if (m_editTipPointedAt.note) {
QString help = i18n("Ctrl+click to go to the note ");
text += help + "</p>";
toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width();
}
if (m_editTipPointedAt.noteReference > 0) {
QString help = i18n("Ctrl+click to go to the note reference");
text += help + "</p>";
toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width();
}
QToolTip::hideText();
if (toolTipWidth) {
QRect keepRect(m_editTipPos - QPoint(3, 3), QSize(6, 6));
QToolTip::showText(m_editTipPos - QPoint(toolTipWidth / 2, 0), text, canvas()->canvasWidget(), keepRect);
}
}
void TextTool::blinkCaret()
{
if (!(canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus())) {
m_caretTimer.stop();
m_caretTimerState = false; // not visible.
} else {
m_caretTimerState = !m_caretTimerState;
}
repaintCaret();
}
void TextTool::relayoutContent()
{
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(m_textShapeData->document()->documentLayout());
Q_ASSERT(lay);
foreach (KoTextLayoutRootArea *rootArea, lay->rootAreas()) {
rootArea->setDirty();
}
lay->emitLayoutIsDirty();
}
void TextTool::paint(QPainter &painter, const KoViewConverter &converter)
{
if (m_textEditor.isNull()) {
return;
}
if (canvas()
&& (canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus())
&& !m_caretTimer.isActive()) { // make sure we blink
m_caretTimer.start();
m_caretTimerState = true;
}
if (!m_caretTimerState) {
m_caretTimer.setInterval(500); // we set it lower during typing, so set it back to normal
}
if (!m_textShapeData) {
return;
}
if (m_textShapeData->isDirty()) {
return;
}
qreal zoomX, zoomY;
converter.zoom(&zoomX, &zoomY);
painter.save();
- QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter);
+ QTransform shapeMatrix = m_textShape->absoluteTransformation();
shapeMatrix.scale(zoomX, zoomY);
shapeMatrix.translate(0, -m_textShapeData->documentOffset());
// Possibly draw table dragging visual cues
const qreal boxHeight = 20;
if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) {
QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(m_dx, 0.0);
if (m_tableDragInfo.tableColumnDivider > 0) {
//let's draw left
qreal w = m_tableDragInfo.tableLeadSize - m_dx;
QRectF rect(anchorPos - QPointF(w, 0.0), QSizeF(w, 0.0));
QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight()));
drawRect.setHeight(boxHeight);
drawRect.moveTop(drawRect.top() - 1.5 * boxHeight);
QString label = m_unit.toUserStringValue(w);
int labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width();
painter.fillRect(drawRect, QColor(64, 255, 64, 196));
painter.setPen(QColor(0, 0, 0, 196));
if (labelWidth + 10 < drawRect.width()) {
QPointF centerLeft(drawRect.left(), drawRect.center().y());
QPointF centerRight(drawRect.right(), drawRect.center().y());
painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth / 2 + 5, 0.0));
painter.drawLine(centerLeft, centerLeft + QPointF(7, -5));
painter.drawLine(centerLeft, centerLeft + QPointF(7, 5));
painter.drawLine(drawRect.center() + QPointF(labelWidth / 2 + 5, 0.0), centerRight);
painter.drawLine(centerRight, centerRight + QPointF(-7, -5));
painter.drawLine(centerRight, centerRight + QPointF(-7, 5));
painter.drawText(drawRect, Qt::AlignCenter, label);
}
}
if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) {
//let's draw right
qreal w = m_tableDragInfo.tableTrailSize + m_dx;
QRectF rect(anchorPos, QSizeF(w, 0.0));
QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight()));
drawRect.setHeight(boxHeight);
drawRect.moveTop(drawRect.top() - 1.5 * boxHeight);
QString label;
int labelWidth;
if (m_tableDragWithShift) {
label = i18n("follows along");
labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width();
drawRect.setWidth(2 * labelWidth);
QLinearGradient g(drawRect.topLeft(), drawRect.topRight());
g.setColorAt(0.6, QColor(255, 64, 64, 196));
g.setColorAt(1.0, QColor(255, 64, 64, 0));
QBrush brush(g);
painter.fillRect(drawRect, brush);
} else {
label = m_unit.toUserStringValue(w);
labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width();
drawRect.setHeight(boxHeight);
painter.fillRect(drawRect, QColor(64, 255, 64, 196));
}
painter.setPen(QColor(0, 0, 0, 196));
if (labelWidth + 10 < drawRect.width()) {
QPointF centerLeft(drawRect.left(), drawRect.center().y());
QPointF centerRight(drawRect.right(), drawRect.center().y());
painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth / 2 + 5, 0.0));
painter.drawLine(centerLeft, centerLeft + QPointF(7, -5));
painter.drawLine(centerLeft, centerLeft + QPointF(7, 5));
if (!m_tableDragWithShift) {
painter.drawLine(drawRect.center() + QPointF(labelWidth / 2 + 5, 0.0), centerRight);
painter.drawLine(centerRight, centerRight + QPointF(-7, -5));
painter.drawLine(centerRight, centerRight + QPointF(-7, 5));
}
painter.drawText(drawRect, Qt::AlignCenter, label);
}
if (!m_tableDragWithShift) {
// let's draw a helper text too
label = i18n("Press shift to not resize this");
labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width();
labelWidth += 10;
//if (labelWidth < drawRect.width())
{
drawRect.moveTop(drawRect.top() + boxHeight);
drawRect.moveLeft(drawRect.left() + (drawRect.width() - labelWidth) / 2);
drawRect.setWidth(labelWidth);
painter.fillRect(drawRect, QColor(64, 255, 64, 196));
painter.drawText(drawRect, Qt::AlignCenter, label);
}
}
}
}
// Possibly draw table dragging visual cues
if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) {
QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(0.0, m_dy);
if (m_tableDragInfo.tableRowDivider > 0) {
qreal h = m_tableDragInfo.tableLeadSize - m_dy;
QRectF rect(anchorPos - QPointF(0.0, h), QSizeF(0.0, h));
QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight()));
drawRect.setWidth(boxHeight);
drawRect.moveLeft(drawRect.left() - 1.5 * boxHeight);
QString label = m_unit.toUserStringValue(h);
QRectF labelRect = QFontMetrics(QToolTip::font()).boundingRect(label);
labelRect.setHeight(boxHeight);
labelRect.setWidth(labelRect.width() + 10);
labelRect.moveTopLeft(drawRect.center() - QPointF(labelRect.width(), labelRect.height()) / 2);
painter.fillRect(drawRect, QColor(64, 255, 64, 196));
painter.fillRect(labelRect, QColor(64, 255, 64, 196));
painter.setPen(QColor(0, 0, 0, 196));
if (labelRect.height() + 10 < drawRect.height()) {
QPointF centerTop(drawRect.center().x(), drawRect.top());
QPointF centerBottom(drawRect.center().x(), drawRect.bottom());
painter.drawLine(centerTop, drawRect.center() - QPointF(0.0, labelRect.height() / 2 + 5));
painter.drawLine(centerTop, centerTop + QPointF(-5, 7));
painter.drawLine(centerTop, centerTop + QPointF(5, 7));
painter.drawLine(drawRect.center() + QPointF(0.0, labelRect.height() / 2 + 5), centerBottom);
painter.drawLine(centerBottom, centerBottom + QPointF(-5, -7));
painter.drawLine(centerBottom, centerBottom + QPointF(5, -7));
}
painter.drawText(labelRect, Qt::AlignCenter, label);
}
}
if (m_caretTimerState) {
// Lets draw the caret ourselves, as the Qt method doesn't take cursor
// charFormat into consideration.
QTextBlock block = m_textEditor.data()->block();
if (block.isValid()) {
int posInParag = m_textEditor.data()->position() - block.position();
if (posInParag <= block.layout()->preeditAreaPosition()) {
posInParag += block.layout()->preeditAreaText().length();
}
QTextLine tl = block.layout()->lineForTextPosition(m_textEditor.data()->position() - block.position());
if (tl.isValid()) {
painter.setRenderHint(QPainter::Antialiasing, false);
QRectF rect = caretRect(m_textEditor.data()->cursor());
QPointF baselinePoint;
if (tl.ascent() > 0) {
QFontMetricsF fm(m_textEditor.data()->charFormat().font(), painter.device());
rect.setY(rect.y() + tl.ascent() - qMin(tl.ascent(), fm.ascent()));
rect.setHeight(qMin(tl.ascent(), fm.ascent()) + qMin(tl.descent(), fm.descent()));
baselinePoint = QPoint(rect.x(), rect.y() + tl.ascent());
} else {
//line only filled with characters-without-size (eg anchors)
// layout will make sure line has height of block font
QFontMetricsF fm(block.charFormat().font(), painter.device());
rect.setHeight(fm.ascent() + fm.descent());
baselinePoint = QPoint(rect.x(), rect.y() + fm.ascent());
}
QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomLeft()));
drawRect.setWidth(2);
painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
if (m_textEditor.data()->isEditProtected(true)) {
QRectF circleRect(shapeMatrix.map(baselinePoint), QSizeF(14, 14));
circleRect.translate(-6.5, -6.5);
QPen pen(QColor(16, 255, 255));
pen.setWidthF(2.0);
painter.setPen(pen);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.drawEllipse(circleRect);
painter.drawLine(circleRect.topLeft() + QPointF(4.5, 4.5),
circleRect.bottomRight() - QPointF(4.5, 4.5));
} else {
painter.fillRect(drawRect, QColor(128, 255, 128));
}
}
}
}
painter.restore();
}
void TextTool::updateSelectedShape(const QPointF &point, bool noDocumentChange)
{
QRectF area(point, QSizeF(1, 1));
if (m_textEditor.data()->hasSelection()) {
repaintSelection();
} else {
repaintCaret();
}
QList<KoShape *> sortedShapes = canvas()->shapeManager()->shapesAt(area, true);
std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
for (int count = sortedShapes.count() - 1; count >= 0; count--) {
KoShape *shape = sortedShapes.at(count);
if (shape->isContentProtected()) {
continue;
}
TextShape *textShape = dynamic_cast<TextShape *>(shape);
if (textShape) {
if (textShape != m_textShape) {
if (static_cast<KoTextShapeData *>(textShape->userData())->document() != m_textShapeData->document()) {
//we should only change to another document if allowed
if (noDocumentChange) {
return;
}
// if we change to another textdocument we need to remove selection in old document
// or it would continue to be painted etc
m_textEditor.data()->setPosition(m_textEditor.data()->position());
}
m_textShape = textShape;
setShapeData(static_cast<KoTextShapeData *>(m_textShape->userData()));
// This is how we inform the rulers of the active range
// For now we will not consider table cells, but just give the shape dimensions
QVariant v;
QRectF rect(QPoint(), m_textShape->size());
- rect = m_textShape->absoluteTransformation(0).mapRect(rect);
+ rect = m_textShape->absoluteTransformation().mapRect(rect);
v.setValue(rect);
canvas()->resourceManager()->setResource(KoCanvasResourceProvider::ActiveRange, v);
}
return;
}
}
}
void TextTool::mousePressEvent(KoPointerEvent *event)
{
if (m_textEditor.isNull()) {
return;
}
// request the software keyboard, if any
if (event->button() == Qt::LeftButton && qApp->autoSipEnabled()) {
QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(qApp->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel));
// the two following bools just make it all a lot easier to read in the following if()
// basically, we require a widget for this to work (passing 0 to QApplication::sendEvent
// crashes) and there are three tests any one of which can be true to trigger the event
const bool hasWidget = canvas()->canvasWidget();
if ((behavior == QStyle::RSIP_OnMouseClick && hasWidget) ||
(hasWidget && canvas()->canvasWidget()->hasFocus())) {
QEvent event(QEvent::RequestSoftwareInputPanel);
if (hasWidget) {
QApplication::sendEvent(canvas()->canvasWidget(), &event);
}
}
}
bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
updateSelectedShape(event->point, shiftPressed);
KoSelection *selection = canvas()->selectedShapesProxy()->selection();
if (m_textShape && !selection->isSelected(m_textShape) && m_textShape->isSelectable()) {
selection->deselectAll();
selection->select(m_textShape);
}
KoPointedAt pointedAt = hitTest(event->point);
m_tableDraggedOnce = false;
m_clickWithinSelection = false;
if (pointedAt.position != -1) {
m_tablePenMode = false;
if ((event->button() == Qt::LeftButton) && !shiftPressed && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position)) {
m_clickWithinSelection = true;
m_draggingOrigin = event->pos(); //we store the pixel pos
} else if (!(event->button() == Qt::RightButton && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position))) {
m_textEditor.data()->setPosition(pointedAt.position, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
useCursor(Qt::IBeamCursor);
}
m_tableDragInfo.tableHit = KoPointedAt::None;
if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw)
m_caretTimer.stop();
m_caretTimer.setInterval(50);
m_caretTimer.start();
m_caretTimerState = true; // turn caret instantly on on click
}
} else {
if (event->button() == Qt::RightButton) {
m_tablePenMode = false;
KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck();
if (plugin) {
plugin->setCurrentCursorPosition(m_textShapeData->document(), -1);
}
event->ignore();
} else if (m_tablePenMode) {
m_textEditor.data()->beginEditBlock(kundo2_i18n("Change Border Formatting"));
if (pointedAt.tableHit == KoPointedAt::ColumnDivider) {
if (pointedAt.tableColumnDivider < pointedAt.table->columns()) {
m_textEditor.data()->setTableBorderData(pointedAt.table,
pointedAt.tableRowDivider, pointedAt.tableColumnDivider,
KoBorder::LeftBorder, m_tablePenBorderData);
}
if (pointedAt.tableColumnDivider > 0) {
m_textEditor.data()->setTableBorderData(pointedAt.table,
pointedAt.tableRowDivider, pointedAt.tableColumnDivider - 1,
KoBorder::RightBorder, m_tablePenBorderData);
}
} else if (pointedAt.tableHit == KoPointedAt::RowDivider) {
if (pointedAt.tableRowDivider < pointedAt.table->rows()) {
m_textEditor.data()->setTableBorderData(pointedAt.table,
pointedAt.tableRowDivider, pointedAt.tableColumnDivider,
KoBorder::TopBorder, m_tablePenBorderData);
}
if (pointedAt.tableRowDivider > 0) {
m_textEditor.data()->setTableBorderData(pointedAt.table,
pointedAt.tableRowDivider - 1, pointedAt.tableColumnDivider,
KoBorder::BottomBorder, m_tablePenBorderData);
}
}
m_textEditor.data()->endEditBlock();
} else {
m_tableDragInfo = pointedAt;
m_tablePenMode = false;
}
return;
}
if (shiftPressed) { // altered selection.
repaintSelection();
} else {
repaintCaret();
}
updateSelectionHandler();
updateStyleManager();
updateActions();
//activate context-menu for spelling-suggestions
if (event->button() == Qt::RightButton) {
KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck();
if (plugin) {
plugin->setCurrentCursorPosition(m_textShapeData->document(), m_textEditor.data()->position());
}
event->ignore();
}
if (event->button() == Qt::MidButton) { // Paste
const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Selection);
// on windows we do not have data if we try to paste this selection
if (data) {
m_prevCursorPosition = m_textEditor.data()->position();
m_textEditor.data()->paste(canvas(), data, canvas()->resourceManager());
editingPluginEvents();
}
}
}
void TextTool::setShapeData(KoTextShapeData *data)
{
bool docChanged = !data || !m_textShapeData || m_textShapeData->document() != data->document();
if (m_textShapeData) {
disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved()));
}
m_textShapeData = data;
if (!m_textShapeData) {
return;
}
connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved()));
if (docChanged) {
if (!m_textEditor.isNull()) {
disconnect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions()));
}
m_textEditor = KoTextDocument(m_textShapeData->document()).textEditor();
Q_ASSERT(m_textEditor.data());
if (!m_toolSelection) {
m_toolSelection = new TextToolSelection(m_textEditor.data());
} else {
m_toolSelection->m_editor = m_textEditor.data();
}
m_variableMenu->menu()->clear();
KoTextDocument document(m_textShapeData->document());
foreach (QAction *action, document.inlineTextObjectManager()->createInsertVariableActions(canvas())) {
m_variableMenu->addAction(action);
connect(action, SIGNAL(triggered()), this, SLOT(returnFocusToCanvas()));
}
connect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions()));
updateActions();
}
}
void TextTool::updateSelectionHandler()
{
if (m_textEditor) {
emit selectionChanged(m_textEditor.data()->hasSelection());
if (m_textEditor.data()->hasSelection()) {
QClipboard *clipboard = QApplication::clipboard();
if (clipboard->supportsSelection()) {
clipboard->setText(m_textEditor.data()->selectedText(), QClipboard::Selection);
}
}
}
KoCanvasResourceProvider *p = canvas()->resourceManager();
m_allowResourceManagerUpdates = false;
if (m_textEditor && m_textShapeData) {
p->setResource(KoText::CurrentTextPosition, m_textEditor.data()->position());
p->setResource(KoText::CurrentTextAnchor, m_textEditor.data()->anchor());
QVariant variant;
variant.setValue<void *>(m_textShapeData->document());
p->setResource(KoText::CurrentTextDocument, variant);
} else {
p->clearResource(KoText::CurrentTextPosition);
p->clearResource(KoText::CurrentTextAnchor);
p->clearResource(KoText::CurrentTextDocument);
}
m_allowResourceManagerUpdates = true;
}
QMimeData *TextTool::generateMimeData() const
{
if (!m_textShapeData || m_textEditor.isNull() || !m_textEditor.data()->hasSelection()) {
return 0;
}
int from = m_textEditor.data()->position();
int to = m_textEditor.data()->anchor();
KoTextOdfSaveHelper saveHelper(m_textShapeData->document(), from, to);
KoTextDrag drag;
#ifdef SHOULD_BUILD_RDF
KoDocumentResourceManager *rm = 0;
if (canvas()->shapeController()) {
rm = canvas()->shapeController()->resourceManager();
}
if (rm && rm->hasResource(KoText::DocumentRdf)) {
KoDocumentRdfBase *rdf = qobject_cast<KoDocumentRdfBase *>(rm->resource(KoText::DocumentRdf).value<QObject *>());
if (rdf) {
saveHelper.setRdfModel(rdf->model());
}
}
#endif
drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
QTextDocumentFragment fragment = m_textEditor.data()->selection();
drag.setData("text/html", fragment.toHtml("utf-8").toUtf8());
drag.setData("text/plain", fragment.toPlainText().toUtf8());
return drag.takeMimeData();
}
TextEditingPluginContainer *TextTool::textEditingPluginContainer()
{
m_textEditingPlugins = canvas()->resourceManager()->
resource(TextEditingPluginContainer::ResourceId).value<TextEditingPluginContainer *>();
if (m_textEditingPlugins == 0) {
m_textEditingPlugins = new TextEditingPluginContainer(canvas()->resourceManager());
QVariant variant;
variant.setValue(m_textEditingPlugins.data());
canvas()->resourceManager()->setResource(TextEditingPluginContainer::ResourceId, variant);
foreach (KoTextEditingPlugin *plugin, m_textEditingPlugins->values()) {
connect(plugin, SIGNAL(startMacro(QString)),
this, SLOT(startMacro(QString)));
connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro()));
QHash<QString, QAction *> actions = plugin->actions();
QHash<QString, QAction *>::iterator i = actions.begin();
while (i != actions.end()) {
// addAction(i.key(), i.value());
++i;
}
}
}
return m_textEditingPlugins;
}
void TextTool::copy() const
{
QMimeData *mimeData = generateMimeData();
if (mimeData) {
QApplication::clipboard()->setMimeData(mimeData);
}
}
void TextTool::deleteSelection()
{
m_textEditor.data()->deleteChar();
editingPluginEvents();
}
bool TextTool::paste()
{
const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard);
// on windows we do not have data if we try to paste the selection
if (!data) {
return false;
}
// since this is not paste-as-text we will not paste in urls, but instead let KoToolProxy solve it
if (data->hasUrls()) {
return false;
}
if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))
|| data->hasText()) {
m_prevCursorPosition = m_textEditor.data()->position();
m_textEditor.data()->paste(canvas(), data);
editingPluginEvents();
return true;
}
return false;
}
void TextTool::cut()
{
if (m_textEditor.data()->hasSelection()) {
copy();
KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Cut"));
m_textEditor.data()->deleteChar(false, topCmd);
m_textEditor.data()->endEditBlock();
}
}
void TextTool::dragMoveEvent(QDragMoveEvent *event, const QPointF &point)
{
if (event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::Text))
|| event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::OpenOfficeClipboard))
|| event->mimeData()->hasText()) {
if (m_drag) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else if (event->proposedAction() == Qt::CopyAction) {
event->acceptProposedAction();
} else {
event->ignore();
return;
}
KoPointedAt pointedAt = hitTest(point);
if (pointedAt.position == -1) {
event->ignore();
}
if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw)
m_caretTimer.stop();
m_caretTimer.setInterval(50);
m_caretTimer.start();
m_caretTimerState = true; // turn caret instantly on on click
}
if (m_preDragSelection.cursor.isNull()) {
repaintSelection();
m_preDragSelection.cursor = QTextCursor(*m_textEditor.data()->cursor());
if (m_drag) {
// Make a selection that looks like the current cursor selection
// so we can move the real carent around freely
QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections();
m_preDragSelection.format = QTextCharFormat();
m_preDragSelection.format.setBackground(qApp->palette().brush(QPalette::Highlight));
m_preDragSelection.format.setForeground(qApp->palette().brush(QPalette::HighlightedText));
sels.append(m_preDragSelection);
KoTextDocument(m_textShapeData->document()).setSelections(sels);
} // else we wantt the selection ot disappaear
}
repaintCaret(); // will erase caret
m_textEditor.data()->setPosition(pointedAt.position);
repaintCaret(); // will paint caret in new spot
// Selection has visually not appeared at a new spot so no need to repaint it
}
}
void TextTool::dragLeaveEvent(QDragLeaveEvent *event)
{
if (m_drag) {
// restore the old selections
QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections();
sels.pop_back();
KoTextDocument(m_textShapeData->document()).setSelections(sels);
}
repaintCaret(); // will erase caret in old spot
m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor());
m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor);
repaintCaret(); // will paint caret in new spot
if (!m_drag) {
repaintSelection(); // will paint selection again
}
// mark that we now are back to normal selection
m_preDragSelection.cursor = QTextCursor();
event->accept();
}
void TextTool::dropEvent(QDropEvent *event, const QPointF &)
{
if (m_drag) {
// restore the old selections
QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections();
sels.pop_back();
KoTextDocument(m_textShapeData->document()).setSelections(sels);
}
QTextCursor insertCursor(*m_textEditor.data()->cursor());
m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor());
m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor);
repaintSelection(); // will erase the selection in new spot
if (m_drag) {
m_textEditor.data()->deleteChar();
}
m_prevCursorPosition = insertCursor.position();
m_textEditor.data()->setPosition(m_prevCursorPosition);
m_textEditor.data()->paste(canvas(), event->mimeData());
m_textEditor.data()->setPosition(m_prevCursorPosition);
//since the paste made insertCursor we can now use that for the end position
m_textEditor.data()->setPosition(insertCursor.position(), QTextCursor::KeepAnchor);
// mark that we no are back to normal selection
m_preDragSelection.cursor = QTextCursor();
event->accept();
}
KoPointedAt TextTool::hitTest(const QPointF &point) const
{
if (!m_textShape || !m_textShapeData) {
return KoPointedAt();
}
QPointF p = m_textShape->convertScreenPos(point);
KoTextLayoutRootArea *rootArea = m_textShapeData->rootArea();
return rootArea ? rootArea->hitTest(p, Qt::FuzzyHit) : KoPointedAt();
}
void TextTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) {
event->ignore(); // allow the event to be used by another
return;
}
if (event->modifiers() & Qt::ShiftModifier) {
// When whift is pressed we behave as a single press
return mousePressEvent(event);
}
m_textEditor.data()->select(QTextCursor::WordUnderCursor);
m_clickWithinSelection = false;
repaintSelection();
updateSelectionHandler();
}
void TextTool::mouseTripleClickEvent(KoPointerEvent *event)
{
if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) {
event->ignore(); // allow the event to be used by another
return;
}
if (event->modifiers() & Qt::ShiftModifier) {
// When whift is pressed we behave as a single press
return mousePressEvent(event);
}
m_textEditor.data()->clearSelection();
m_textEditor.data()->movePosition(QTextCursor::StartOfBlock);
m_textEditor.data()->movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
m_clickWithinSelection = false;
repaintSelection();
updateSelectionHandler();
}
void TextTool::mouseMoveEvent(KoPointerEvent *event)
{
m_editTipPos = event->globalPos();
if (event->buttons()) {
updateSelectedShape(event->point, true);
}
m_editTipTimer.stop();
if (QToolTip::isVisible()) {
QToolTip::hideText();
}
KoPointedAt pointedAt = hitTest(event->point);
if (event->buttons() == Qt::NoButton) {
if (m_tablePenMode) {
if (pointedAt.tableHit == KoPointedAt::ColumnDivider || pointedAt.tableHit == KoPointedAt::RowDivider) {
useTableBorderCursor();
} else {
useCursor(Qt::IBeamCursor);
}
// do nothing else
return;
}
if (!m_textShapeData || pointedAt.position < 0) {
if (pointedAt.tableHit == KoPointedAt::ColumnDivider) {
useCursor(Qt::SplitHCursor);
m_draggingOrigin = event->point;
} else if (pointedAt.tableHit == KoPointedAt::RowDivider) {
if (pointedAt.tableRowDivider > 0) {
useCursor(Qt::SplitVCursor);
m_draggingOrigin = event->point;
} else {
useCursor(Qt::IBeamCursor);
}
} else {
useCursor(Qt::IBeamCursor);
}
return;
}
QTextCursor mouseOver(m_textShapeData->document());
mouseOver.setPosition(pointedAt.position);
if (m_changeTracker && m_changeTracker->containsInlineChanges(mouseOver.charFormat())) {
m_editTipPointedAt = pointedAt;
if (QToolTip::isVisible()) {
QTimer::singleShot(0, this, SLOT(showEditTip()));
} else {
m_editTipTimer.start();
}
}
if ((pointedAt.bookmark || !pointedAt.externalHRef.isEmpty()) || pointedAt.note || (pointedAt.noteReference > 0)) {
if (event->modifiers() & Qt::ControlModifier) {
useCursor(Qt::PointingHandCursor);
}
m_editTipPointedAt = pointedAt;
if (QToolTip::isVisible()) {
QTimer::singleShot(0, this, SLOT(showEditTip()));
} else {
m_editTipTimer.start();
}
return;
}
// check if mouse pointer is over shape with hyperlink
KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point);
if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) {
useCursor(Qt::PointingHandCursor);
return;
}
useCursor(Qt::IBeamCursor);
// Set Arrow Cursor when mouse is on top of annotation shape.
if (selectedShape) {
if (selectedShape->shapeId() == "AnnotationTextShapeID") {
QPointF point(event->point);
if (point.y() <= (selectedShape->position().y() + 25)) {
useCursor(Qt::ArrowCursor);
}
}
}
return;
} else {
if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) {
m_tableDragWithShift = event->modifiers() & Qt::ShiftModifier;
if (m_tableDraggedOnce) {
canvas()->shapeController()->resourceManager()->undoStack()->undo();
}
KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Column Width"));
m_dx = m_draggingOrigin.x() - event->point.x();
if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()
&& m_tableDragInfo.tableTrailSize + m_dx < 0) {
m_dx = -m_tableDragInfo.tableTrailSize;
}
if (m_tableDragInfo.tableColumnDivider > 0) {
if (m_tableDragInfo.tableLeadSize - m_dx < 0) {
m_dx = m_tableDragInfo.tableLeadSize;
}
m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table,
m_tableDragInfo.tableColumnDivider - 1,
m_tableDragInfo.tableLeadSize - m_dx, topCmd);
} else {
m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, -m_dx, 0.0);
}
if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) {
if (!m_tableDragWithShift) {
m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table,
m_tableDragInfo.tableColumnDivider,
m_tableDragInfo.tableTrailSize + m_dx, topCmd);
}
} else {
m_tableDragWithShift = true; // act like shift pressed
}
if (m_tableDragWithShift) {
m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, 0.0, m_dx);
}
m_textEditor.data()->endEditBlock();
m_tableDragInfo.tableDividerPos.setY(m_textShape->convertScreenPos(event->point).y());
if (m_tableDraggedOnce) {
//we need to redraw like this so we update outside the textshape too
if (canvas()->canvasWidget()) {
canvas()->canvasWidget()->update();
}
}
m_tableDraggedOnce = true;
} else if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) {
if (m_tableDraggedOnce) {
canvas()->shapeController()->resourceManager()->undoStack()->undo();
}
if (m_tableDragInfo.tableRowDivider > 0) {
KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Row Height"));
m_dy = m_draggingOrigin.y() - event->point.y();
if (m_tableDragInfo.tableLeadSize - m_dy < 0) {
m_dy = m_tableDragInfo.tableLeadSize;
}
m_textEditor.data()->adjustTableRowHeight(m_tableDragInfo.table,
m_tableDragInfo.tableRowDivider - 1,
m_tableDragInfo.tableLeadSize - m_dy, topCmd);
m_textEditor.data()->endEditBlock();
m_tableDragInfo.tableDividerPos.setX(m_textShape->convertScreenPos(event->point).x());
if (m_tableDraggedOnce) {
//we need to redraw like this so we update outside the textshape too
if (canvas()->canvasWidget()) {
canvas()->canvasWidget()->update();
}
}
m_tableDraggedOnce = true;
}
} else if (m_tablePenMode) {
// do nothing
} else if (m_clickWithinSelection) {
if (!m_drag && (event->pos() - m_draggingOrigin).manhattanLength()
>= QApplication::startDragDistance()) {
QMimeData *mimeData = generateMimeData();
if (mimeData) {
m_drag = new QDrag(canvas()->canvasWidget());
m_drag->setMimeData(mimeData);
m_drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction);
m_drag = 0;
}
}
} else {
useCursor(Qt::IBeamCursor);
if (pointedAt.position == m_textEditor.data()->position()) {
return;
}
if (pointedAt.position >= 0) {
if (m_textEditor.data()->hasSelection()) {
repaintSelection(); // will erase selection
} else {
repaintCaret();
}
m_textEditor.data()->setPosition(pointedAt.position, QTextCursor::KeepAnchor);
if (m_textEditor.data()->hasSelection()) {
repaintSelection();
} else {
repaintCaret();
}
}
}
updateSelectionHandler();
}
}
void TextTool::mouseReleaseEvent(KoPointerEvent *event)
{
event->ignore();
editingPluginEvents();
m_tableDragInfo.tableHit = KoPointedAt::None;
if (m_tableDraggedOnce) {
m_tableDraggedOnce = false;
//we need to redraw like this so we update outside the textshape too
if (canvas()->canvasWidget()) {
canvas()->canvasWidget()->update();
}
}
if (!m_textShapeData) {
return;
}
// check if mouse pointer is not over some shape with hyperlink
KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point);
if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) {
QString url = selectedShape->hyperLink();
runUrl(event, url);
return;
}
KoPointedAt pointedAt = hitTest(event->point);
if (m_clickWithinSelection && !m_drag) {
if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw)
m_caretTimer.stop();
m_caretTimer.setInterval(50);
m_caretTimer.start();
m_caretTimerState = true; // turn caret instantly on on click
}
repaintCaret(); // will erase caret
repaintSelection(); // will erase selection
m_textEditor.data()->setPosition(pointedAt.position);
repaintCaret(); // will paint caret in new spot
}
// Is there an anchor here ?
if ((event->modifiers() & Qt::ControlModifier) && !m_textEditor.data()->hasSelection()) {
if (pointedAt.bookmark) {
m_textEditor.data()->setPosition(pointedAt.bookmark->rangeStart());
ensureCursorVisible();
event->accept();
return;
}
if (pointedAt.note) {
m_textEditor.data()->setPosition(pointedAt.note->textFrame()->firstPosition());
ensureCursorVisible();
event->accept();
return;
}
if (pointedAt.noteReference > 0) {
m_textEditor.data()->setPosition(pointedAt.noteReference);
ensureCursorVisible();
event->accept();
return;
}
if (!pointedAt.externalHRef.isEmpty()) {
runUrl(event, pointedAt.externalHRef);
}
}
}
void TextTool::keyPressEvent(QKeyEvent *event)
{
int destinationPosition = -1; // for those cases where the moveOperation is not relevant;
QTextCursor::MoveOperation moveOperation = QTextCursor::NoMove;
KoTextEditor *textEditor = m_textEditor.data();
m_tablePenMode = false; // keypress always stops the table (border) pen mode
Q_ASSERT(textEditor);
if (event->key() == Qt::Key_Backspace) {
if (!textEditor->hasSelection() && textEditor->block().textList()
&& (textEditor->position() == textEditor->block().position())
&& !(m_changeTracker && m_changeTracker->recordChanges())) {
if (!textEditor->blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) {
// backspace at beginning of numbered list item, makes it unnumbered
textEditor->toggleListNumbering(false);
} else {
KoListLevelProperties llp;
llp.setStyle(KoListStyle::None);
llp.setLevel(0);
// backspace on numbered, empty parag, removes numbering.
textEditor->setListProperties(llp);
}
} else if (textEditor->position() > 0 || textEditor->hasSelection()) {
if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) { // delete prev word.
textEditor->movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
}
textEditor->deletePreviousChar();
editingPluginEvents();
}
} else if ((event->key() == Qt::Key_Tab)
&& ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList()) {
ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel;
ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1);
textEditor->addCommand(cll);
editingPluginEvents();
} else if ((event->key() == Qt::Key_Backtab)
&& ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList() && !(m_changeTracker && m_changeTracker->recordChanges())) {
ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel;
ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1);
textEditor->addCommand(cll);
editingPluginEvents();
} else if (event->key() == Qt::Key_Delete) {
if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) {// delete next word.
textEditor->movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
}
// the event only gets through when the Del is not used in the app
// if the app forwards Del then deleteSelection is used
textEditor->deleteChar();
editingPluginEvents();
} else if ((event->key() == Qt::Key_Left) && (event->modifiers() & Qt::ControlModifier) == 0) {
moveOperation = QTextCursor::Left;
} else if ((event->key() == Qt::Key_Right) && (event->modifiers() & Qt::ControlModifier) == 0) {
moveOperation = QTextCursor::Right;
} else if ((event->key() == Qt::Key_Up) && (event->modifiers() & Qt::ControlModifier) == 0) {
moveOperation = QTextCursor::Up;
} else if ((event->key() == Qt::Key_Down) && (event->modifiers() & Qt::ControlModifier) == 0) {
moveOperation = QTextCursor::Down;
} else {
// check for shortcuts.
QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers()));
if (hit(item, KStandardShortcut::Begin))
// Goto beginning of the document. Default: Ctrl-Home
{
destinationPosition = 0;
} else if (hit(item, KStandardShortcut::End)) {
// Goto end of the document. Default: Ctrl-End
if (m_textShapeData) {
QTextBlock last = m_textShapeData->document()->lastBlock();
destinationPosition = last.position() + last.length() - 1;
}
} else if (hit(item, KStandardShortcut::Prior)) { // page up
// Scroll up one page. Default: Prior
event->ignore(); // let app level actions handle it
return;
} else if (hit(item, KStandardShortcut::Next)) {
// Scroll down one page. Default: Next
event->ignore(); // let app level actions handle it
return;
} else if (hit(item, KStandardShortcut::BeginningOfLine))
// Goto beginning of current line. Default: Home
{
moveOperation = QTextCursor::StartOfLine;
} else if (hit(item, KStandardShortcut::EndOfLine))
// Goto end of current line. Default: End
{
moveOperation = QTextCursor::EndOfLine;
} else if (hit(item, KStandardShortcut::BackwardWord)) {
moveOperation = QTextCursor::WordLeft;
} else if (hit(item, KStandardShortcut::ForwardWord)) {
moveOperation = QTextCursor::WordRight;
}
#ifdef Q_OS_MACOS
// Don't reject "alt" key, it may be used for typing text on Mac OS
else if ((event->modifiers() & Qt::ControlModifier) || event->text().length() == 0) {
#else
else if ((event->modifiers() & (Qt::ControlModifier | Qt::AltModifier)) || event->text().length() == 0) {
#endif
event->ignore();
return;
} else if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) {
m_prevCursorPosition = textEditor->position();
textEditor->newLine();
updateActions();
editingPluginEvents();
} else if ((event->key() == Qt::Key_Tab || !(event->text().length() == 1 && !event->text().at(0).isPrint()))) { // insert the text
m_prevCursorPosition = textEditor->position();
startingSimpleEdit(); //signal editing plugins that this is a simple edit
textEditor->insertText(event->text());
editingPluginEvents();
}
}
if (moveOperation != QTextCursor::NoMove || destinationPosition != -1) {
useCursor(Qt::BlankCursor);
bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
if (textEditor->hasSelection()) {
repaintSelection(); // will erase selection
} else {
repaintCaret();
}
QTextBlockFormat format = textEditor->blockFormat();
KoText::Direction dir = static_cast<KoText::Direction>(format.intProperty(KoParagraphStyle::TextProgressionDirection));
bool isRtl;
if (dir == KoText::AutoDirection) {
isRtl = textEditor->block().text().isRightToLeft();
} else {
isRtl = dir == KoText::RightLeftTopBottom;
}
if (isRtl) { // if RTL toggle direction of cursor movement.
switch (moveOperation) {
case QTextCursor::Left: moveOperation = QTextCursor::Right; break;
case QTextCursor::Right: moveOperation = QTextCursor::Left; break;
case QTextCursor::WordRight: moveOperation = QTextCursor::WordLeft; break;
case QTextCursor::WordLeft: moveOperation = QTextCursor::WordRight; break;
default: break;
}
}
int prevPosition = textEditor->position();
if (moveOperation != QTextCursor::NoMove)
textEditor->movePosition(moveOperation,
shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
else
textEditor->setPosition(destinationPosition,
shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
if (moveOperation == QTextCursor::Down && prevPosition == textEditor->position()) {
// change behavior a little big from Qt; at the bottom of the doc we go to the end of the doc
textEditor->movePosition(QTextCursor::End,
shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
}
if (shiftPressed) { // altered selection.
repaintSelection();
} else {
repaintCaret();
}
updateActions();
editingPluginEvents();
}
if (m_caretTimer.isActive()) { // make the caret not blink but decide on the action if its visible or not.
m_caretTimer.stop();
m_caretTimer.setInterval(50);
m_caretTimer.start();
m_caretTimerState = true; // turn caret on while typing
}
if (moveOperation != QTextCursor::NoMove)
// this difference in handling is need to prevent leaving a trail of old cursors onscreen
{
ensureCursorVisible();
} else {
m_delayedEnsureVisible = true;
}
updateActions();
updateSelectionHandler();
}
QVariant TextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const
{
KoTextEditor *textEditor = m_textEditor.data();
if (!textEditor || !m_textShapeData) {
return QVariant();
}
switch (query) {
case Qt::ImMicroFocus: {
// The rectangle covering the area of the input cursor in widget coordinates.
QRectF rect = caretRect(textEditor->cursor());
rect.moveTop(rect.top() - m_textShapeData->documentOffset());
- QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter);
+ QTransform shapeMatrix = m_textShape->absoluteTransformation();
qreal zoomX, zoomY;
converter.zoom(&zoomX, &zoomY);
shapeMatrix.scale(zoomX, zoomY);
rect = shapeMatrix.mapRect(rect);
return rect.toRect();
}
case Qt::ImFont:
// The currently used font for text input.
return textEditor->charFormat().font();
case Qt::ImCursorPosition:
// The logical position of the cursor within the text surrounding the input area (see ImSurroundingText).
return textEditor->position() - textEditor->block().position();
case Qt::ImSurroundingText:
// The plain text around the input area, for example the current paragraph.
return textEditor->block().text();
case Qt::ImCurrentSelection:
// The currently selected text.
return textEditor->selectedText();
default:
; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition
}
return QVariant();
}
void TextTool::inputMethodEvent(QInputMethodEvent *event)
{
KoTextEditor *textEditor = m_textEditor.data();
if (textEditor == 0) {
return;
}
if (event->replacementLength() > 0) {
textEditor->setPosition(textEditor->position() + event->replacementStart());
for (int i = event->replacementLength(); i > 0; --i) {
textEditor->deleteChar();
}
}
if (!event->commitString().isEmpty()) {
QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString());
keyPressEvent(&ke);
// The cursor may reside in a different block before vs. after keyPressEvent.
QTextBlock block = textEditor->block();
QTextLayout *layout = block.layout();
Q_ASSERT(layout);
layout->setPreeditArea(-1, QString());
} else {
QTextBlock block = textEditor->block();
QTextLayout *layout = block.layout();
Q_ASSERT(layout);
layout->setPreeditArea(textEditor->position() - block.position(), event->preeditString());
const_cast<QTextDocument *>(textEditor->document())->markContentsDirty(textEditor->position(), event->preeditString().length());
}
event->accept();
}
void TextTool::ensureCursorVisible(bool moveView)
{
KoTextEditor *textEditor = m_textEditor.data();
if (!textEditor || !m_textShapeData) {
return;
}
bool upToDate;
QRectF cRect = caretRect(textEditor->cursor(), &upToDate);
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(m_textShapeData->document()->documentLayout());
Q_ASSERT(lay);
KoTextLayoutRootArea *rootArea = lay->rootAreaForPoint(cRect.center());
if (rootArea && rootArea->associatedShape() && m_textShapeData->rootArea() != rootArea) {
// If we have changed root area we need to update m_textShape and m_textShapeData
m_textShape = static_cast<TextShape *>(rootArea->associatedShape());
Q_ASSERT(m_textShape);
disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved()));
m_textShapeData = static_cast<KoTextShapeData *>(m_textShape->userData());
Q_ASSERT(m_textShapeData);
connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved()));
}
if (!moveView) {
return;
}
if (!upToDate) { // paragraph is not yet layouted.
// The number one usecase for this is when the user pressed enter.
// try to do it on next caret blink
m_delayedEnsureVisible = true;
return; // we shouldn't move to an obsolete position
}
cRect.moveTop(cRect.top() - m_textShapeData->documentOffset());
- canvas()->ensureVisible(m_textShape->absoluteTransformation(0).mapRect(cRect));
+ canvas()->ensureVisible(m_textShape->absoluteTransformation().mapRect(cRect));
}
void TextTool::keyReleaseEvent(QKeyEvent *event)
{
event->accept();
}
void TextTool::updateActions()
{
bool notInAnnotation = true; // no annotation shape anymore!
KoTextEditor *textEditor = m_textEditor.data();
if (textEditor == 0) {
return;
}
m_allowActions = false;
//Update the characterStyle related GUI elements
QTextCharFormat cf = textEditor->charFormat();
m_actionFormatBold->setChecked(cf.fontWeight() > QFont::Normal);
m_actionFormatItalic->setChecked(cf.fontItalic());
m_actionFormatUnderline->setChecked(cf.intProperty(KoCharacterStyle::UnderlineType) != KoCharacterStyle::NoLineType);
m_actionFormatStrikeOut->setChecked(cf.intProperty(KoCharacterStyle::StrikeOutType) != KoCharacterStyle::NoLineType);
bool super = false, sub = false;
switch (cf.verticalAlignment()) {
case QTextCharFormat::AlignSuperScript:
super = true;
break;
case QTextCharFormat::AlignSubScript:
sub = true;
break;
default:;
}
m_actionFormatSuper->setChecked(super);
m_actionFormatSub->setChecked(sub);
m_actionFormatFontSize->setFontSize(cf.font().pointSizeF());
m_actionFormatFontFamily->setFont(cf.font().family());
KoTextShapeData::ResizeMethod resizemethod = KoTextShapeData::AutoResize;
if (m_textShapeData) {
resizemethod = m_textShapeData->resizeMethod();
}
m_shrinkToFitAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation);
m_shrinkToFitAction->setChecked(resizemethod == KoTextShapeData::ShrinkToFitResize);
m_growWidthAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation);
m_growWidthAction->setChecked(resizemethod == KoTextShapeData::AutoGrowWidth || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight);
m_growHeightAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation);
m_growHeightAction->setChecked(resizemethod == KoTextShapeData::AutoGrowHeight || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight);
//update paragraphStyle GUI element
QTextBlockFormat bf = textEditor->blockFormat();
if (bf.hasProperty(KoParagraphStyle::TextProgressionDirection)) {
switch (bf.intProperty(KoParagraphStyle::TextProgressionDirection)) {
case KoText::RightLeftTopBottom:
m_actionChangeDirection->setChecked(true);
break;
case KoText::LeftRightTopBottom:
default:
m_actionChangeDirection->setChecked(false);
break;
}
} else {
m_actionChangeDirection->setChecked(textEditor->block().text().isRightToLeft());
}
if (bf.alignment() == Qt::AlignLeading || bf.alignment() == Qt::AlignTrailing) {
bool revert = (textEditor->block().layout()->textOption().textDirection() == Qt::RightToLeft);
if ((bf.alignment() == Qt::AlignLeading) ^ revert) {
m_actionAlignLeft->setChecked(true);
} else {
m_actionAlignRight->setChecked(true);
}
} else if (bf.alignment() == Qt::AlignHCenter) {
m_actionAlignCenter->setChecked(true);
}
if (bf.alignment() == Qt::AlignJustify) {
m_actionAlignBlock->setChecked(true);
} else if (bf.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute)) {
m_actionAlignLeft->setChecked(true);
} else if (bf.alignment() == (Qt::AlignRight | Qt::AlignAbsolute)) {
m_actionAlignRight->setChecked(true);
}
if (textEditor->block().textList()) {
QTextListFormat listFormat = textEditor->block().textList()->format();
if (listFormat.intProperty(KoListStyle::Level) > 1) {
m_actionFormatDecreaseIndent->setEnabled(true);
} else {
m_actionFormatDecreaseIndent->setEnabled(false);
}
if (listFormat.intProperty(KoListStyle::Level) < 10) {
m_actionFormatIncreaseIndent->setEnabled(true);
} else {
m_actionFormatIncreaseIndent->setEnabled(false);
}
} else {
m_actionFormatDecreaseIndent->setEnabled(textEditor->blockFormat().leftMargin() > 0.);
}
m_allowActions = true;
bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceProvider::ApplicationSpeciality)
& KoCanvasResourceProvider::NoAdvancedText);
if (useAdvancedText) {
action("insert_table")->setEnabled(notInAnnotation);
bool hasTable = textEditor->currentTable();
action("insert_tablerow_above")->setEnabled(hasTable && notInAnnotation);
action("insert_tablerow_below")->setEnabled(hasTable && notInAnnotation);
action("insert_tablecolumn_left")->setEnabled(hasTable && notInAnnotation);
action("insert_tablecolumn_right")->setEnabled(hasTable && notInAnnotation);
action("delete_tablerow")->setEnabled(hasTable && notInAnnotation);
action("delete_tablecolumn")->setEnabled(hasTable && notInAnnotation);
action("merge_tablecells")->setEnabled(hasTable && notInAnnotation);
action("split_tablecells")->setEnabled(hasTable && notInAnnotation);
action("activate_borderpainter")->setEnabled(hasTable && notInAnnotation);
}
action("insert_annotation")->setEnabled(notInAnnotation);
///TODO if selection contains several different format
emit blockChanged(textEditor->block());
emit charFormatChanged(cf, textEditor->blockCharFormat());
emit blockFormatChanged(bf);
}
QMenu *TextTool::popupActionsMenu()
{
return m_contextMenu.data();
}
void TextTool::updateStyleManager()
{
if (!m_textShapeData) {
return;
}
KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager();
emit styleManagerChanged(styleManager);
//TODO move this to its own method
m_changeTracker = KoTextDocument(m_textShapeData->document()).changeTracker();
}
void TextTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
KoToolBase::activate(activation, shapes);
m_caretTimer.start();
m_caretTimerState = true;
foreach (KoShape *shape, shapes) {
m_textShape = dynamic_cast<TextShape *>(shape);
if (m_textShape) {
break;
}
}
if (!m_textShape) { // none found
emit done();
// This is how we inform the rulers of the active range
// No shape means no active range
canvas()->resourceManager()->setResource(KoCanvasResourceProvider::ActiveRange, QVariant(QRectF()));
return;
}
// This is how we inform the rulers of the active range
// For now we will not consider table cells, but just give the shape dimensions
QVariant v;
QRectF rect(QPoint(), m_textShape->size());
- rect = m_textShape->absoluteTransformation(0).mapRect(rect);
+ rect = m_textShape->absoluteTransformation().mapRect(rect);
v.setValue(rect);
canvas()->resourceManager()->setResource(KoCanvasResourceProvider::ActiveRange, v);
if ((!m_oldTextEditor.isNull()) && m_oldTextEditor.data()->document() != static_cast<KoTextShapeData *>(m_textShape->userData())->document()) {
m_oldTextEditor.data()->setPosition(m_oldTextEditor.data()->position());
//we need to redraw like this so we update the old textshape wherever it may be
if (canvas()->canvasWidget()) {
canvas()->canvasWidget()->update();
}
}
setShapeData(static_cast<KoTextShapeData *>(m_textShape->userData()));
useCursor(Qt::IBeamCursor);
updateStyleManager();
repaintSelection();
updateSelectionHandler();
updateActions();
if (m_specialCharacterDocker) {
m_specialCharacterDocker->setEnabled(true);
}
}
void TextTool::deactivate()
{
m_caretTimer.stop();
m_caretTimerState = false;
repaintCaret();
m_textShape = 0;
// This is how we inform the rulers of the active range
// No shape means no active range
canvas()->resourceManager()->setResource(KoCanvasResourceProvider::ActiveRange, QVariant(QRectF()));
m_oldTextEditor = m_textEditor;
setShapeData(0);
updateSelectionHandler();
if (m_specialCharacterDocker) {
m_specialCharacterDocker->setEnabled(false);
m_specialCharacterDocker->setVisible(false);
}
KoToolBase::deactivate();
}
void TextTool::repaintDecorations()
{
if (m_textShapeData) {
repaintSelection();
}
}
void TextTool::repaintCaret()
{
KoTextEditor *textEditor = m_textEditor.data();
if (!textEditor || !m_textShapeData) {
return;
}
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(m_textShapeData->document()->documentLayout());
Q_ASSERT(lay); Q_UNUSED(lay);
// If we have changed root area we need to update m_textShape and m_textShapeData
if (m_delayedEnsureVisible) {
m_delayedEnsureVisible = false;
ensureCursorVisible();
return;
}
ensureCursorVisible(false); // ensures the various vars are updated
bool upToDate;
QRectF repaintRect = caretRect(textEditor->cursor(), &upToDate);
repaintRect.moveTop(repaintRect.top() - m_textShapeData->documentOffset());
if (repaintRect.isValid()) {
- repaintRect = m_textShape->absoluteTransformation(0).mapRect(repaintRect);
+ repaintRect = m_textShape->absoluteTransformation().mapRect(repaintRect);
// Make sure there is enough space to show an icon
QRectF iconSize = canvas()->viewConverter()->viewToDocument(QRect(0, 0, 18, 18));
repaintRect.setX(repaintRect.x() - iconSize.width() / 2);
repaintRect.setRight(repaintRect.right() + iconSize.width() / 2);
repaintRect.setTop(repaintRect.y() - iconSize.height() / 2);
repaintRect.setBottom(repaintRect.bottom() + iconSize.height() / 2);
canvas()->updateCanvas(repaintRect);
}
}
void TextTool::repaintSelection()
{
KoTextEditor *textEditor = m_textEditor.data();
if (textEditor == 0) {
return;
}
QTextCursor cursor = *textEditor->cursor();
QList<TextShape *> shapes;
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(textEditor->document()->documentLayout());
Q_ASSERT(lay);
foreach (KoShape *shape, lay->shapes()) {
TextShape *textShape = dynamic_cast<TextShape *>(shape);
if (textShape == 0) { // when the shape is being deleted its no longer a TextShape but a KoShape
continue;
}
//Q_ASSERT(!shapes.contains(textShape));
if (!shapes.contains(textShape)) {
shapes.append(textShape);
}
}
// loop over all shapes that contain the text and update per shape.
QRectF repaintRect = textRect(cursor);
foreach (TextShape *ts, shapes) {
QRectF rect = repaintRect;
rect.moveTop(rect.y() - ts->textShapeData()->documentOffset());
- rect = ts->absoluteTransformation(0).mapRect(rect);
+ rect = ts->absoluteTransformation().mapRect(rect);
QRectF r = ts->boundingRect().intersected(rect);
canvas()->updateCanvas(r);
}
}
QRectF TextTool::caretRect(QTextCursor *cursor, bool *upToDate) const
{
QTextCursor tmpCursor(*cursor);
tmpCursor.setPosition(cursor->position()); // looses the anchor
QRectF rect = textRect(tmpCursor);
if (rect.size() == QSizeF(0, 0)) {
if (upToDate) {
*upToDate = false;
}
rect = m_lastImMicroFocus; // prevent block changed but layout not done
} else {
if (upToDate) {
*upToDate = true;
}
m_lastImMicroFocus = rect;
}
return rect;
}
QRectF TextTool::textRect(QTextCursor &cursor) const
{
if (!m_textShapeData) {
return QRectF();
}
KoTextEditor *textEditor = m_textEditor.data();
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(textEditor->document()->documentLayout());
return lay->selectionBoundingBox(cursor);
}
KoToolSelection *TextTool::selection()
{
return m_toolSelection;
}
QList<QPointer<QWidget> > TextTool::createOptionWidgets()
{
QList<QPointer<QWidget> > widgets;
SimpleCharacterWidget *scw = new SimpleCharacterWidget(this, 0);
SimpleParagraphWidget *spw = new SimpleParagraphWidget(this, 0);
if (m_textEditor.data()) {
// connect(m_textEditor.data(), SIGNAL(paragraphStyleApplied(KoParagraphStyle*)), spw, SLOT(slotParagraphStyleApplied(KoParagraphStyle*)));
// connect(m_textEditor.data(), SIGNAL(characterStyleApplied(KoCharacterStyle*)), scw, SLOT(slotCharacterStyleApplied(KoCharacterStyle*)));
//initialise the char- and par- widgets with the current block and formats.
scw->setCurrentBlockFormat(m_textEditor.data()->blockFormat());
scw->setCurrentFormat(m_textEditor.data()->charFormat(), m_textEditor.data()-> blockCharFormat());
spw->setCurrentBlock(m_textEditor.data()->block());
spw->setCurrentFormat(m_textEditor.data()->blockFormat());
}
SimpleTableWidget *stw = new SimpleTableWidget(this, 0);
SimpleInsertWidget *siw = new SimpleInsertWidget(this, 0);
/* We do not use these for now. Let's see if they become useful at a certain point in time. If not, we can remove the whole chain (SimpleCharWidget, SimpleParWidget, DockerStyleComboModel)
if (m_textShapeData && KoTextDocument(m_textShapeData->document()).styleManager()) {
scw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedCharacterStyles());
spw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedParagraphStyles());
}
*/
// Connect to/with simple character widget (docker)
connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), scw, SLOT(setStyleManager(KoStyleManager*)));
connect(this, SIGNAL(charFormatChanged(QTextCharFormat,QTextCharFormat)), scw, SLOT(setCurrentFormat(QTextCharFormat,QTextCharFormat)));
connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), scw, SLOT(setCurrentBlockFormat(QTextBlockFormat)));
connect(scw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas()));
connect(scw, SIGNAL(characterStyleSelected(KoCharacterStyle*)), this, SLOT(setStyle(KoCharacterStyle*)));
connect(scw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentCharFormat(QString)));
connect(scw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int)));
// Connect to/with simple paragraph widget (docker)
connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), spw, SLOT(setStyleManager(KoStyleManager*)));
connect(this, SIGNAL(blockChanged(QTextBlock)), spw, SLOT(setCurrentBlock(QTextBlock)));
connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), spw, SLOT(setCurrentFormat(QTextBlockFormat)));
connect(spw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas()));
connect(spw, SIGNAL(paragraphStyleSelected(KoParagraphStyle*)), this, SLOT(setStyle(KoParagraphStyle*)));
connect(spw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentBlockFormat(QString)));
connect(spw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int)));
// Connect to/with simple table widget (docker)
connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), stw, SLOT(setStyleManager(KoStyleManager*)));
connect(stw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas()));
connect(stw, SIGNAL(tableBorderDataUpdated(KoBorder::BorderData)), this, SLOT(setTableBorderData(KoBorder::BorderData)));
// Connect to/with simple insert widget (docker)
connect(siw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas()));
connect(siw, SIGNAL(insertTableQuick(int,int)), this, SLOT(insertTableQuick(int,int)));
updateStyleManager();
if (m_textShape) {
updateActions();
}
scw->setWindowTitle(i18n("Character"));
widgets.append(scw);
spw->setWindowTitle(i18n("Paragraph"));
widgets.append(spw);
bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceProvider::ApplicationSpeciality)
& KoCanvasResourceProvider::NoAdvancedText);
if (useAdvancedText) {
stw->setWindowTitle(i18n("Table"));
widgets.append(stw);
siw->setWindowTitle(i18n("Insert"));
widgets.append(siw);
}
return widgets;
}
void TextTool::returnFocusToCanvas()
{
canvas()->canvasWidget()->setFocus();
}
void TextTool::startEditing(KUndo2Command *command)
{
m_currentCommand = command;
m_currentCommandHasChildren = true;
}
void TextTool::stopEditing()
{
m_currentCommand = 0;
m_currentCommandHasChildren = false;
}
void TextTool::insertNewSection()
{
KoTextEditor *textEditor = m_textEditor.data();
if (!textEditor) {
return;
}
textEditor->newSection();
}
void TextTool::configureSection()
{
KoTextEditor *textEditor = m_textEditor.data();
if (!textEditor) {
return;
}
SectionFormatDialog *dia = new SectionFormatDialog(0, m_textEditor.data());
dia->exec();
delete dia;
returnFocusToCanvas();
updateActions();
}
void TextTool::splitSections()
{
KoTextEditor *textEditor = m_textEditor.data();
if (!textEditor) {
return;
}
SectionsSplitDialog *dia = new SectionsSplitDialog(0, m_textEditor.data());
dia->exec();
delete dia;
returnFocusToCanvas();
updateActions();
}
void TextTool::pasteAsText()
{
KoTextEditor *textEditor = m_textEditor.data();
if (!textEditor) {
return;
}
const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard);
// on windows we do not have data if we try to paste this selection
if (!data) {
return;
}
if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))
|| data->hasText()) {
m_prevCursorPosition = m_textEditor.data()->position();
m_textEditor.data()->paste(canvas(), data, true);
editingPluginEvents();
}
}
void TextTool::bold(bool bold)
{
m_textEditor.data()->bold(bold);
}
void TextTool::italic(bool italic)
{
m_textEditor.data()->italic(italic);
}
void TextTool::underline(bool underline)
{
m_textEditor.data()->underline(underline);
}
void TextTool::strikeOut(bool strikeOut)
{
m_textEditor.data()->strikeOut(strikeOut);
}
void TextTool::nonbreakingSpace()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->insertText(QString(QChar(Qt::Key_nobreakspace)));
}
void TextTool::nonbreakingHyphen()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->insertText(QString(QChar(0x2013)));
}
void TextTool::softHyphen()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->insertText(QString(QChar(Qt::Key_hyphen)));
}
void TextTool::lineBreak()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->insertText(QString(QChar(0x2028)));
}
void TextTool::alignLeft()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignLeft | Qt::AlignAbsolute);
}
void TextTool::alignRight()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignRight | Qt::AlignAbsolute);
}
void TextTool::alignCenter()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignHCenter);
}
void TextTool::alignBlock()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignJustify);
}
void TextTool::superScript(bool on)
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
if (on) {
m_actionFormatSub->setChecked(false);
}
m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignTop : Qt::AlignVCenter);
}
void TextTool::subScript(bool on)
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
if (on) {
m_actionFormatSuper->setChecked(false);
}
m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignBottom : Qt::AlignVCenter);
}
void TextTool::increaseIndent()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
if (m_textEditor.data()->block().textList()) {
ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel;
ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1);
m_textEditor.data()->addCommand(cll);
editingPluginEvents();
} else {
m_textEditor.data()->increaseIndent();
}
updateActions();
}
void TextTool::decreaseIndent()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
if (m_textEditor.data()->block().textList()) {
ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel;
ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1);
m_textEditor.data()->addCommand(cll);
editingPluginEvents();
} else {
m_textEditor.data()->decreaseIndent();
}
updateActions();
}
void TextTool::decreaseFontSize()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->decreaseFontSize();
}
void TextTool::increaseFontSize()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->increaseFontSize();
}
void TextTool::setFontFamily(const QString &font)
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
m_textEditor.data()->setFontFamily(font);
}
void TextTool::setFontSize(qreal size)
{
if (!m_allowActions || !m_textEditor.data() || m_textEditor.isNull()) {
return;
}
m_textEditor.data()->setFontSize(size);
}
void TextTool::insertIndexMarker()
{
// TODO handle result when we figure out how to report errors from a tool.
m_textEditor.data()->insertIndexMarker();
}
void TextTool::insertFrameBreak()
{
m_textEditor.data()->insertFrameBreak();
ensureCursorVisible();
m_delayedEnsureVisible = true;
}
void TextTool::setStyle(KoCharacterStyle *style)
{
KoCharacterStyle *charStyle = style;
//if the given KoCharacterStyle is null, set the KoParagraphStyle character properties
if (!charStyle) {
charStyle = static_cast<KoCharacterStyle *>(KoTextDocument(m_textShapeData->document()).styleManager()->paragraphStyle(m_textEditor.data()->blockFormat().intProperty(KoParagraphStyle::StyleId)));
}
if (charStyle) {
m_textEditor.data()->setStyle(charStyle);
updateActions();
}
}
void TextTool::setStyle(KoParagraphStyle *style)
{
m_textEditor.data()->setStyle(style);
updateActions();
}
void TextTool::insertTable()
{
TableDialog *dia = new TableDialog(0);
if (dia->exec() == TableDialog::Accepted) {
m_textEditor.data()->insertTable(dia->rows(), dia->columns());
}
delete dia;
updateActions();
}
void TextTool::insertTableQuick(int rows, int columns)
{
m_textEditor.data()->insertTable(rows, columns);
updateActions();
}
void TextTool::insertTableRowAbove()
{
m_textEditor.data()->insertTableRowAbove();
}
void TextTool::insertTableRowBelow()
{
m_textEditor.data()->insertTableRowBelow();
}
void TextTool::insertTableColumnLeft()
{
m_textEditor.data()->insertTableColumnLeft();
}
void TextTool::insertTableColumnRight()
{
m_textEditor.data()->insertTableColumnRight();
}
void TextTool::deleteTableColumn()
{
m_textEditor.data()->deleteTableColumn();
}
void TextTool::deleteTableRow()
{
m_textEditor.data()->deleteTableRow();
}
void TextTool::mergeTableCells()
{
m_textEditor.data()->mergeTableCells();
}
void TextTool::splitTableCells()
{
m_textEditor.data()->splitTableCells();
}
void TextTool::useTableBorderCursor()
{
static const unsigned char data[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x68, 0x00,
0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfd, 0x00,
0x00, 0x80, 0x7e, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x00, 0xa0, 0x1f, 0x00,
0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x03, 0x00,
0x00, 0xe4, 0x01, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x80, 0x41, 0x00, 0x00,
0x40, 0x32, 0x00, 0x00, 0xa0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00,
0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00,
0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
QBitmap result(32, 32);
result.fill(Qt::color0);
QPainter painter(&result);
painter.drawPixmap(0, 0, QBitmap::fromData(QSize(25, 23), data));
QBitmap brushMask = result.createHeuristicMask(false);
useCursor(QCursor(result, brushMask, 1, 21));
}
void TextTool::setTableBorderData(const KoBorder::BorderData &data)
{
m_tablePenMode = true;
m_tablePenBorderData = data;
}
void TextTool::formatParagraph()
{
ParagraphSettingsDialog *dia = new ParagraphSettingsDialog(this, m_textEditor.data());
dia->setUnit(canvas()->unit());
dia->setImageCollection(m_textShape->imageCollection());
dia->exec();
delete dia;
returnFocusToCanvas();
}
void TextTool::testSlot(bool on)
{
qDebug() << "signal received. bool:" << on;
}
void TextTool::selectAll()
{
KoTextEditor *textEditor = m_textEditor.data();
if (!textEditor || !m_textShapeData) {
return;
}
const int selectionLength = qAbs(textEditor->position() - textEditor->anchor());
textEditor->movePosition(QTextCursor::End);
textEditor->setPosition(0, QTextCursor::KeepAnchor);
repaintSelection();
if (selectionLength != qAbs(textEditor->position() - textEditor->anchor())) { // it actually changed
emit selectionChanged(true);
}
}
void TextTool::startMacro(const QString &title)
{
if (title != i18n("Key Press") && title != i18n("Autocorrection")) { //dirty hack while waiting for refactor of text editing
m_textTyping = false;
} else {
m_textTyping = true;
}
if (title != i18n("Delete") && title != i18n("Autocorrection")) { //same dirty hack as above
m_textDeleting = false;
} else {
m_textDeleting = true;
}
if (m_currentCommand) {
return;
}
class MacroCommand : public KUndo2Command
{
public:
MacroCommand(const KUndo2MagicString &title) : KUndo2Command(title), m_first(true) {}
void redo() override
{
if (!m_first) {
KUndo2Command::redo();
}
m_first = false;
}
bool mergeWith(const KUndo2Command *) override
{
return false;
}
bool m_first;
};
/**
* FIXME: The messages generated by the Text Tool might not be
* properly translated, since we don't control it in
* type-safe way.
*
* The title is already translated string, we just don't
* have any type control over it.
*/
KUndo2MagicString title_workaround = kundo2_noi18n(title);
m_currentCommand = new MacroCommand(title_workaround);
m_currentCommandHasChildren = false;
}
void TextTool::stopMacro()
{
if (!m_currentCommand) {
return;
}
if (!m_currentCommandHasChildren) {
delete m_currentCommand;
}
m_currentCommand = 0;
}
void TextTool::showStyleManager(int styleId)
{
if (!m_textShapeData) {
return;
}
KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager();
Q_ASSERT(styleManager);
if (!styleManager) {
return; //don't crash
}
StyleManagerDialog *dia = new StyleManagerDialog(canvas()->canvasWidget());
dia->setStyleManager(styleManager);
dia->setUnit(canvas()->unit());
KoParagraphStyle *paragraphStyle = styleManager->paragraphStyle(styleId);
if (paragraphStyle) {
dia->setParagraphStyle(paragraphStyle);
}
KoCharacterStyle *characterStyle = styleManager->characterStyle(styleId);
if (characterStyle) {
dia->setCharacterStyle(characterStyle);
}
dia->show();
}
void TextTool::startTextEditingPlugin(const QString &pluginId)
{
KoTextEditingPlugin *plugin = textEditingPluginContainer()->plugin(pluginId);
if (plugin) {
if (m_textEditor.data()->hasSelection()) {
plugin->checkSection(m_textShapeData->document(), m_textEditor.data()->selectionStart(), m_textEditor.data()->selectionEnd());
} else {
plugin->finishedWord(m_textShapeData->document(), m_textEditor.data()->position());
}
}
}
void TextTool::canvasResourceChanged(int key, const QVariant &var)
{
if (m_textEditor.isNull()) {
return;
}
if (!m_textShapeData) {
return;
}
if (m_allowResourceManagerUpdates == false) {
return;
}
if (key == KoText::CurrentTextPosition) {
repaintSelection();
m_textEditor.data()->setPosition(var.toInt());
ensureCursorVisible();
} else if (key == KoText::CurrentTextAnchor) {
repaintSelection();
int pos = m_textEditor.data()->position();
m_textEditor.data()->setPosition(var.toInt());
m_textEditor.data()->setPosition(pos, QTextCursor::KeepAnchor);
} else if (key == KoCanvasResourceProvider::Unit) {
m_unit = var.value<KoUnit>();
} else {
return;
}
repaintSelection();
}
void TextTool::insertSpecialCharacter()
{
if (m_specialCharacterDocker == 0) {
m_specialCharacterDocker = new InsertCharacter(canvas()->canvasWidget());
connect(m_specialCharacterDocker, SIGNAL(insertCharacter(QString)),
this, SLOT(insertString(QString)));
}
m_specialCharacterDocker->show();
}
void TextTool::insertString(const QString &string)
{
m_textEditor.data()->insertText(string);
returnFocusToCanvas();
}
void TextTool::selectFont()
{
FontDia *fontDlg = new FontDia(m_textEditor.data());
fontDlg->exec();
delete fontDlg;
returnFocusToCanvas();
}
void TextTool::shapeAddedToCanvas()
{
qDebug();
if (m_textShape) {
KoSelection *selection = canvas()->selectedShapesProxy()->selection();
KoShape *shape = selection->firstSelectedShape();
if (shape != m_textShape && canvas()->shapeManager()->shapes().contains(m_textShape)) {
// this situation applies when someone, not us, changed the selection by selecting another
// text shape. Possibly by adding one.
// Deselect the new shape again, so we can keep editing what we were already editing
selection->select(m_textShape);
selection->deselect(shape);
}
}
}
void TextTool::shapeDataRemoved()
{
m_textShapeData = 0;
m_textShape = 0;
if (!m_textEditor.isNull() && !m_textEditor.data()->cursor()->isNull()) {
const QTextDocument *doc = m_textEditor.data()->document();
Q_ASSERT(doc);
KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(doc->documentLayout());
if (!lay || lay->shapes().isEmpty()) {
emit done();
return;
}
m_textShape = static_cast<TextShape *>(lay->shapes().first());
m_textShapeData = static_cast<KoTextShapeData *>(m_textShape->userData());
connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved()));
}
}
void TextTool::createStyleFromCurrentBlockFormat(const QString &name)
{
KoTextDocument document(m_textShapeData->document());
KoStyleManager *styleManager = document.styleManager();
KoParagraphStyle *paragraphStyle = new KoParagraphStyle(m_textEditor.data()->blockFormat(), m_textEditor.data()->charFormat());
paragraphStyle->setName(name);
styleManager->add(paragraphStyle);
m_textEditor.data()->setStyle(paragraphStyle);
emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat());
emit blockFormatChanged(m_textEditor.data()->blockFormat());
}
void TextTool::createStyleFromCurrentCharFormat(const QString &name)
{
KoCharacterStyle blankStyle;
KoTextDocument document(m_textShapeData->document());
KoStyleManager *styleManager = document.styleManager();
KoCharacterStyle *originalCharStyle = styleManager->characterStyle(m_textEditor.data()->charFormat().intProperty(KoCharacterStyle::StyleId));
KoCharacterStyle *autoStyle;
if (!originalCharStyle) {
originalCharStyle = &blankStyle;
autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat());
autoStyle->setParentStyle(0);
} else {
autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat());
}
autoStyle->setName(name);
styleManager->add(autoStyle);
m_textEditor.data()->setStyle(autoStyle);
emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat());
}
// ---------- editing plugins methods.
void TextTool::editingPluginEvents()
{
if (m_prevCursorPosition == -1 || m_prevCursorPosition == m_textEditor.data()->position()) {
qDebug() << "m_prevCursorPosition=" << m_prevCursorPosition << "m_textEditor.data()->position()=" << m_textEditor.data()->position();
return;
}
QTextBlock block = m_textEditor.data()->block();
if (!block.contains(m_prevCursorPosition)) {
qDebug() << "m_prevCursorPosition=" << m_prevCursorPosition;
finishedWord();
finishedParagraph();
m_prevCursorPosition = -1;
} else {
int from = m_prevCursorPosition;
int to = m_textEditor.data()->position();
if (from > to) {
std::swap(from, to);
}
QString section = block.text().mid(from - block.position(), to - from);
qDebug() << "from=" << from << "to=" << to;
if (section.contains(' ')) {
finishedWord();
m_prevCursorPosition = -1;
}
}
}
void TextTool::finishedWord()
{
if (m_textShapeData && textEditingPluginContainer()) {
foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) {
plugin->finishedWord(m_textShapeData->document(), m_prevCursorPosition);
}
}
}
void TextTool::finishedParagraph()
{
if (m_textShapeData && textEditingPluginContainer()) {
foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) {
plugin->finishedParagraph(m_textShapeData->document(), m_prevCursorPosition);
}
}
}
void TextTool::startingSimpleEdit()
{
if (m_textShapeData && textEditingPluginContainer()) {
foreach (KoTextEditingPlugin *plugin, textEditingPluginContainer()->values()) {
plugin->startingSimpleEdit(m_textShapeData->document(), m_prevCursorPosition);
}
}
}
void TextTool::setTextColor(const KoColor &color)
{
m_textEditor.data()->setTextColor(color.toQColor());
}
void TextTool::setBackgroundColor(const KoColor &color)
{
m_textEditor.data()->setTextBackgroundColor(color.toQColor());
}
void TextTool::setGrowWidthToFit(bool enabled)
{
m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowWidth, enabled));
updateActions();
}
void TextTool::setGrowHeightToFit(bool enabled)
{
m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowHeight, enabled));
updateActions();
}
void TextTool::setShrinkToFit(bool enabled)
{
m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::ShrinkToFitResize, enabled));
updateActions();
}
void TextTool::runUrl(KoPointerEvent *event, QString &url)
{
QUrl _url = QUrl::fromUserInput(url);
if (!_url.isLocalFile()) {
QDesktopServices::openUrl(_url);
}
event->accept();
}
void TextTool::debugTextDocument()
{
#ifndef NDEBUG
if (!m_textShapeData) {
return;
}
const int CHARSPERLINE = 80; // TODO Make configurable using ENV var?
const int CHARPOSITION = 278301935;
KoTextDocument document(m_textShapeData->document());
KoStyleManager *styleManager = document.styleManager();
KoInlineTextObjectManager *inlineManager = document.inlineTextObjectManager();
QTextBlock block = m_textShapeData->document()->begin();
for (; block.isValid(); block = block.next()) {
QVariant var = block.blockFormat().property(KoParagraphStyle::StyleId);
if (!var.isNull()) {
KoParagraphStyle *ps = styleManager->paragraphStyle(var.toInt());
qDebug() << "--- Paragraph Style:" << (ps ? ps->name() : QString()) << var.toInt();
}
var = block.charFormat().property(KoCharacterStyle::StyleId);
if (!var.isNull()) {
KoCharacterStyle *cs = styleManager->characterStyle(var.toInt());
qDebug() << "--- Character Style:" << (cs ? cs->name() : QString()) << var.toInt();
}
int lastPrintedChar = -1;
QTextBlock::iterator it;
QString fragmentText;
QList<QTextCharFormat> inlineCharacters;
for (it = block.begin(); !it.atEnd(); ++it) {
QTextFragment fragment = it.fragment();
if (!fragment.isValid()) {
continue;
}
QTextCharFormat fmt = fragment.charFormat();
qDebug() << "changeId: " << fmt.property(KoCharacterStyle::ChangeTrackerId);
const int fragmentStart = fragment.position() - block.position();
for (int i = fragmentStart; i < fragmentStart + fragment.length(); i += CHARSPERLINE) {
if (lastPrintedChar == fragmentStart - 1) {
fragmentText += '|';
}
if (lastPrintedChar < fragmentStart || i > fragmentStart) {
QString debug = block.text().mid(lastPrintedChar, CHARSPERLINE);
lastPrintedChar += CHARSPERLINE;
if (lastPrintedChar > block.length()) {
debug += "\\n";
}
qDebug() << debug;
}
var = fmt.property(KoCharacterStyle::StyleId);
QString charStyleLong, charStyleShort;
if (!var.isNull()) { // named style
charStyleShort = QString::number(var.toInt());
KoCharacterStyle *cs = styleManager->characterStyle(var.toInt());
if (cs) {
charStyleLong = cs->name();
}
}
if (inlineManager && fmt.hasProperty(KoCharacterStyle::InlineInstanceId)) {
QTextCharFormat inlineFmt = fmt;
inlineFmt.setProperty(CHARPOSITION, fragmentStart);
inlineCharacters << inlineFmt;
}
if (fragment.length() > charStyleLong.length()) {
fragmentText += charStyleLong;
} else if (fragment.length() > charStyleShort.length()) {
fragmentText += charStyleShort;
} else if (fragment.length() >= 2) {
fragmentText += QChar(8230); // ellipses
}
int rest = fragmentStart - (lastPrintedChar - CHARSPERLINE) + fragment.length() - fragmentText.length();
rest = qMin(rest, CHARSPERLINE - fragmentText.length());
if (rest >= 2) {
fragmentText = QString("%1%2").arg(fragmentText).arg(' ', rest);
}
if (rest >= 0) {
fragmentText += '|';
}
if (fragmentText.length() >= CHARSPERLINE) {
qDebug() << fragmentText;
fragmentText.clear();
}
}
}
if (!fragmentText.isEmpty()) {
qDebug() << fragmentText;
} else if (block.length() == 1) { // no actual tet
qDebug() << "\\n";
}
foreach (const QTextCharFormat &cf, inlineCharacters) {
KoInlineObject *object = inlineManager->inlineTextObject(cf);
qDebug() << "At pos:" << cf.intProperty(CHARPOSITION) << object;
// qDebug() << "-> id:" << cf.intProperty(577297549);
}
QTextList *list = block.textList();
if (list) {
if (list->format().hasProperty(KoListStyle::StyleId)) {
KoListStyle *ls = styleManager->listStyle(list->format().intProperty(KoListStyle::StyleId));
qDebug() << " List style applied:" << ls->styleId() << ls->name();
} else {
qDebug() << " +- is a list..." << list;
}
}
}
#endif
}
void TextTool::debugTextStyles()
{
#ifndef NDEBUG
if (!m_textShapeData) {
return;
}
KoTextDocument document(m_textShapeData->document());
KoStyleManager *styleManager = document.styleManager();
QSet<int> seenStyles;
foreach (KoParagraphStyle *style, styleManager->paragraphStyles()) {
qDebug() << style->styleId() << style->name() << (styleManager->defaultParagraphStyle() == style ? "[Default]" : "");
KoListStyle *ls = style->listStyle();
if (ls) { // optional ;)
qDebug() << " +- ListStyle: " << ls->styleId() << ls->name()
<< (ls == styleManager->defaultListStyle() ? "[Default]" : "");
foreach (int level, ls->listLevels()) {
KoListLevelProperties llp = ls->levelProperties(level);
qDebug() << " | level" << llp.level() << " style (enum):" << llp.style();
if (llp.bulletCharacter().unicode() != 0) {
qDebug() << " | bullet" << llp.bulletCharacter();
}
}
seenStyles << ls->styleId();
}
}
bool first = true;
foreach (KoCharacterStyle *style, styleManager->characterStyles()) {
if (seenStyles.contains(style->styleId())) {
continue;
}
if (first) {
qDebug() << "--- Character styles ---";
first = false;
}
qDebug() << style->styleId() << style->name();
qDebug() << style->font();
}
first = true;
foreach (KoListStyle *style, styleManager->listStyles()) {
if (seenStyles.contains(style->styleId())) {
continue;
}
if (first) {
qDebug() << "--- List styles ---";
first = false;
}
qDebug() << style->styleId() << style->name()
<< (style == styleManager->defaultListStyle() ? "[Default]" : "");
}
#endif
}
void TextTool::textDirectionChanged()
{
if (!m_allowActions || !m_textEditor.data()) {
return;
}
QTextBlockFormat blockFormat;
if (m_actionChangeDirection->isChecked()) {
blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::RightLeftTopBottom);
} else {
blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::LeftRightTopBottom);
}
m_textEditor.data()->mergeBlockFormat(blockFormat);
}
void TextTool::setListLevel(int level)
{
if (level < 1 || level > 10) {
return;
}
KoTextEditor *textEditor = m_textEditor.data();
if (textEditor->block().textList()) {
ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::SetLevel;
ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, level);
textEditor->addCommand(cll);
editingPluginEvents();
}
}
void TextTool::insertAnnotation()
{
// no annotations anymore, sorry :(
}
diff --git a/plugins/flake/textshape/dialogs/BibliographyPreview.cpp b/plugins/flake/textshape/dialogs/BibliographyPreview.cpp
index ba946324e1..a209877d26 100644
--- a/plugins/flake/textshape/dialogs/BibliographyPreview.cpp
+++ b/plugins/flake/textshape/dialogs/BibliographyPreview.cpp
@@ -1,178 +1,178 @@
/* This file is part of the KDE project
* Copyright (C) 2011 Smit Patel <smitpatel24@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 "BibliographyPreview.h"
#include "KoBibliographyInfo.h"
#include "KoZoomHandler.h"
#include "KoTextDocumentLayout.h"
#include "TextTool.h"
#include <KoInlineTextObjectManager.h>
#include <KoParagraphStyle.h>
#include <KoPageProvider.h>
#include <KoShapePaintingContext.h>
BibliographyPreview::BibliographyPreview(QWidget *parent)
: QFrame(parent)
, m_textShape(0)
, m_pm(0)
, m_styleManager(0)
, m_previewPixSize(QSize(0, 0))
{
}
BibliographyPreview::~BibliographyPreview()
{
deleteTextShape();
if (m_pm) {
delete m_pm;
m_pm = 0;
}
}
void BibliographyPreview::setStyleManager(KoStyleManager *styleManager)
{
m_styleManager = styleManager;
}
void BibliographyPreview::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter *p = new QPainter(this);
p->save();
p->translate(5.5, 1.5);
p->setRenderHint(QPainter::Antialiasing);
QRect rectang = rect();
rectang.adjust(-4, -4, -4, -4);
if (m_pm) {
p->drawPixmap(rectang, *m_pm, m_pm->rect());
} else {
p->fillRect(rectang, QBrush(QColor(Qt::white)));
}
p->restore();
delete p;
}
void BibliographyPreview::updatePreview(KoBibliographyInfo *newbibInfo)
{
QTextBlockFormat bibFormat;
QTextDocument *bibDocument = new QTextDocument(this);
KoTextDocument(bibDocument).setStyleManager(m_styleManager);
KoBibliographyInfo *info = newbibInfo->clone();
bibFormat.setProperty(KoParagraphStyle::BibliographyData, QVariant::fromValue<KoBibliographyInfo *>(info));
bibFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue<QTextDocument *>(bibDocument));
deleteTextShape();
m_textShape = new TextShape(&m_itom, &m_tlm);
if (m_previewPixSize.isEmpty()) {
m_textShape->setSize(size());
} else {
m_textShape->setSize(m_previewPixSize);
}
QTextCursor cursor(m_textShape->textShapeData()->document());
QTextCharFormat textCharFormat = cursor.blockCharFormat();
textCharFormat.setFontPointSize(16);
textCharFormat.setFontWeight(QFont::Bold);
textCharFormat.setProperty(QTextCharFormat::ForegroundBrush, QBrush(Qt::black));
cursor.setCharFormat(textCharFormat);
cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
QTextBlockFormat titleBlockFormat;
cursor.insertBlock(titleBlockFormat, textCharFormat);
cursor.insertText(info->m_indexTitleTemplate.text);
textCharFormat.setFontPointSize(12);
textCharFormat.setFontWeight(QFont::Normal);
QTextBlockFormat blockFormat;
cursor.insertBlock(blockFormat, textCharFormat);
cursor.insertBlock(blockFormat, textCharFormat);
cursor.insertText("CIT01: Title, Author, Organisation, URL");
KoTextDocument(m_textShape->textShapeData()->document()).setStyleManager(m_styleManager);
KoTextDocumentLayout *lay = dynamic_cast<KoTextDocumentLayout *>(m_textShape->textShapeData()->document()->documentLayout());
connect(lay, SIGNAL(finishedLayout()), this, SLOT(finishedPreviewLayout()));
if (lay) {
lay->layout();
}
}
void BibliographyPreview::finishedPreviewLayout()
{
if (m_pm) {
delete m_pm;
m_pm = 0;
}
if (m_previewPixSize.isEmpty()) {
m_pm = new QPixmap(size());
} else {
m_pm = new QPixmap(m_previewPixSize);
}
m_pm->fill(Qt::white);
m_zoomHandler.setZoom(0.9);
m_zoomHandler.setDpi(72, 72);
QPainter p(m_pm);
if (m_textShape) {
if (m_previewPixSize.isEmpty()) {
m_textShape->setSize(size());
} else {
m_textShape->setSize(m_previewPixSize);
}
KoShapePaintingContext paintContext; //FIXME
- m_textShape->paintComponent(p, m_zoomHandler, paintContext);
+ m_textShape->paintComponent(p, paintContext);
}
emit pixmapGenerated();
update();
}
QPixmap BibliographyPreview::previewPixmap()
{
return QPixmap(*m_pm);
}
void BibliographyPreview::deleteTextShape()
{
if (m_textShape) {
KoTextDocumentLayout *lay = dynamic_cast<KoTextDocumentLayout *>(m_textShape->textShapeData()->document()->documentLayout());
if (lay) {
lay->setContinuousLayout(false);
lay->setBlockLayout(true);
}
delete m_textShape;
m_textShape = 0;
}
}
void BibliographyPreview::setPreviewSize(const QSize &size)
{
m_previewPixSize = size;
}
diff --git a/plugins/flake/textshape/dialogs/SimpleParagraphWidget.cpp b/plugins/flake/textshape/dialogs/SimpleParagraphWidget.cpp
index 6a26bd8730..ab0b881037 100644
--- a/plugins/flake/textshape/dialogs/SimpleParagraphWidget.cpp
+++ b/plugins/flake/textshape/dialogs/SimpleParagraphWidget.cpp
@@ -1,365 +1,365 @@
/* This file is part of the KDE project
* Copyright (C) 2007, 2008, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2009-2010 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2011 Mojtaba Shahi Senobari <mojtaba.shahi3000@gmail.com>
* Copyright (C) 2011-2012 Pierre Stirnweiss <pstirnweiss@googlemail.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 "SimpleParagraphWidget.h"
#include "TextTool.h"
#include <ListItemsHelper.h>
#include "FormattingButton.h"
#include <KoStyleThumbnailer.h>
#include "StylesCombo.h"
#include "StylesModel.h"
#include "DockerStylesComboModel.h"
#include "StylesDelegate.h"
#include "ListLevelChooser.h"
#include "commands/ChangeListLevelCommand.h"
#include <KoTextEditor.h>
#include <KoTextBlockData.h>
#include <KoParagraphStyle.h>
#include <KoInlineTextObjectManager.h>
#include <KoTextRangeManager.h>
#include <KoTextDocumentLayout.h>
#include <KoZoomHandler.h>
#include <KoStyleManager.h>
#include <KoListLevelProperties.h>
#include <KoShapePaintingContext.h>
#include <QAction>
#include <QTextLayout>
#include <QFlags>
#include <QMenu>
#include <QWidgetAction>
#include <KisSignalMapper.h>
#include <QDebug>
SimpleParagraphWidget::SimpleParagraphWidget(TextTool *tool, QWidget *parent)
: QWidget(parent)
, m_styleManager(0)
, m_blockSignals(false)
, m_tool(tool)
, m_directionButtonState(Auto)
, m_thumbnailer(new KoStyleThumbnailer())
, m_mapper(new KisSignalMapper(this))
, m_stylesModel(new StylesModel(0, StylesModel::ParagraphStyle))
, m_sortedStylesModel(new DockerStylesComboModel())
, m_stylesDelegate(0)
{
widget.setupUi(this);
widget.alignCenter->setDefaultAction(tool->action("format_aligncenter"));
widget.alignBlock->setDefaultAction(tool->action("format_alignblock"));
// RTL layout will reverse the button order, but the align left/right then get mixed up.
// this makes sure that whatever happens the 'align left' is to the left of the 'align right'
if (QApplication::isRightToLeft()) {
widget.alignLeft->setDefaultAction(tool->action("format_alignright"));
widget.alignRight->setDefaultAction(tool->action("format_alignleft"));
} else {
widget.alignLeft->setDefaultAction(tool->action("format_alignleft"));
widget.alignRight->setDefaultAction(tool->action("format_alignright"));
}
widget.decreaseIndent->setDefaultAction(tool->action("format_decreaseindent"));
widget.increaseIndent->setDefaultAction(tool->action("format_increaseindent"));
widget.changeTextDirection->setDefaultAction(tool->action("change_text_direction"));
widget.moreOptions->setText("...");
widget.moreOptions->setToolTip(i18n("Change paragraph format"));
connect(widget.moreOptions, SIGNAL(clicked(bool)), tool->action("format_paragraph"), SLOT(trigger()));
connect(widget.changeTextDirection, SIGNAL(clicked()), this, SIGNAL(doneWithFocus()));
connect(widget.alignCenter, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
connect(widget.alignBlock, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
connect(widget.alignLeft, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
connect(widget.alignRight, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
connect(widget.decreaseIndent, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
connect(widget.increaseIndent, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
widget.bulletListButton->setDefaultAction(tool->action("format_bulletlist"));
widget.bulletListButton->setNumColumns(3);
fillListButtons();
widget.bulletListButton->addSeparator();
connect(widget.bulletListButton, SIGNAL(itemTriggered(int)), this, SLOT(listStyleChanged(int)));
m_stylesModel->setStyleThumbnailer(m_thumbnailer);
widget.paragraphStyleCombo->setStylesModel(m_sortedStylesModel);
connect(widget.paragraphStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
connect(widget.paragraphStyleCombo, SIGNAL(newStyleRequested(QString)), this, SIGNAL(newStyleRequested(QString)));
connect(widget.paragraphStyleCombo, SIGNAL(newStyleRequested(QString)), this, SIGNAL(doneWithFocus()));
connect(widget.paragraphStyleCombo, SIGNAL(showStyleManager(int)), this, SLOT(slotShowStyleManager(int)));
connect(m_mapper, SIGNAL(mapped(int)), this, SLOT(changeListLevel(int)));
m_sortedStylesModel->setStylesModel(m_stylesModel);
}
SimpleParagraphWidget::~SimpleParagraphWidget()
{
//the style model is set on the comboBox who takes over ownership
delete m_thumbnailer;
}
void SimpleParagraphWidget::fillListButtons()
{
KoZoomHandler zoomHandler;
zoomHandler.setZoom(1.2);
zoomHandler.setDpi(72, 72);
KoInlineTextObjectManager itom;
KoTextRangeManager tlm;
TextShape textShape(&itom, &tlm);
textShape.setSize(QSizeF(300, 100));
QTextCursor cursor(textShape.textShapeData()->document());
Q_FOREACH (const Lists::ListStyleItem &item, Lists::genericListStyleItems()) {
QPixmap pm(48, 48);
pm.fill(Qt::transparent);
QPainter p(&pm);
p.translate(0, -1.5);
p.setRenderHint(QPainter::Antialiasing);
if (item.style != KoListStyle::None) {
KoListStyle listStyle;
KoListLevelProperties llp = listStyle.levelProperties(1);
llp.setStyle(item.style);
if (KoListStyle::isNumberingStyle(item.style)) {
llp.setStartValue(1);
llp.setListItemSuffix(".");
}
listStyle.setLevelProperties(llp);
cursor.select(QTextCursor::Document);
QTextCharFormat textCharFormat = cursor.blockCharFormat();
textCharFormat.setFontPointSize(11);
textCharFormat.setFontWeight(QFont::Normal);
cursor.setCharFormat(textCharFormat);
QTextBlock cursorBlock = cursor.block();
KoTextBlockData data(cursorBlock);
cursor.insertText("----");
listStyle.applyStyle(cursor.block(), 1);
cursorBlock = cursor.block();
KoTextBlockData data1(cursorBlock);
cursor.insertText("\n----");
cursorBlock = cursor.block();
KoTextBlockData data2(cursorBlock);
cursor.insertText("\n----");
cursorBlock = cursor.block();
KoTextBlockData data3(cursorBlock);
KoTextDocumentLayout *lay = dynamic_cast<KoTextDocumentLayout *>(textShape.textShapeData()->document()->documentLayout());
if (lay) {
lay->layout();
}
KoShapePaintingContext paintContext; //FIXME
- textShape.paintComponent(p, zoomHandler, paintContext);
+ textShape.paintComponent(p, paintContext);
widget.bulletListButton->addItem(pm, static_cast<int>(item.style));
}
}
widget.bulletListButton->addSeparator();
QAction *action = new QAction(i18n("Change List Level"), this);
action->setToolTip(i18n("Change the level the list is at"));
QMenu *listLevelMenu = new QMenu();
const int levelIndent = 13;
for (int level = 0; level < 10; ++level) {
QWidgetAction *wa = new QWidgetAction(listLevelMenu);
ListLevelChooser *chooserWidget = new ListLevelChooser((levelIndent * level) + 5);
wa->setDefaultWidget(chooserWidget);
listLevelMenu->addAction(wa);
m_mapper->setMapping(wa, level + 1);
connect(chooserWidget, SIGNAL(clicked()), wa, SLOT(trigger()));
connect(wa, SIGNAL(triggered()), m_mapper, SLOT(map()));
}
action->setMenu(listLevelMenu);
widget.bulletListButton->addAction(action);
}
void SimpleParagraphWidget::setCurrentBlock(const QTextBlock &block)
{
if (block == m_currentBlock) {
return;
}
m_currentBlock = block;
m_blockSignals = true;
struct Finally {
Finally(SimpleParagraphWidget *p)
{
parent = p;
}
~Finally()
{
parent->m_blockSignals = false;
}
SimpleParagraphWidget *parent;
};
Finally finally(this);
setCurrentFormat(m_currentBlock.blockFormat());
}
void SimpleParagraphWidget::setCurrentFormat(const QTextBlockFormat &format)
{
if (!m_styleManager || format == m_currentBlockFormat) {
return;
}
m_currentBlockFormat = format;
int id = m_currentBlockFormat.intProperty(KoParagraphStyle::StyleId);
KoParagraphStyle *style(m_styleManager->paragraphStyle(id));
if (style) {
bool unchanged = true;
Q_FOREACH (int property, m_currentBlockFormat.properties().keys()) {
switch (property) {
case QTextFormat::ObjectIndex:
case KoParagraphStyle::ListStyleId:
case KoParagraphStyle::OutlineLevel:
case KoParagraphStyle::ListStartValue:
case KoParagraphStyle::IsListHeader:
case KoParagraphStyle::UnnumberedListItem:
continue;
// These can be both content and style properties so let's ignore
case KoParagraphStyle::BreakBefore:
case KoParagraphStyle::MasterPageName:
continue;
default:
break;
}
if (property == QTextBlockFormat::BlockAlignment) { //the default alignment can be retrieved in the defaultTextOption. However, calligra sets the Qt::AlignAbsolute flag, so we need to or this flag with the default alignment before comparing.
if ((m_currentBlockFormat.property(property) != style->value(property))
&& !(style->value(property).isNull()
&& ((m_currentBlockFormat.intProperty(property)) == int(m_currentBlock.document()->defaultTextOption().alignment() | Qt::AlignAbsolute)))) {
unchanged = false;
break;
} else {
continue;
}
}
if (property == KoParagraphStyle::TextProgressionDirection) {
if (style->value(property).isNull() && m_currentBlockFormat.intProperty(property) == KoText::LeftRightTopBottom) {
//LTR seems to be Qt default when unset
continue;
}
}
if ((m_currentBlockFormat.property(property) != style->value(property)) && !(style->value(property).isNull() && !m_currentBlockFormat.property(property).toBool())) {
//the last check seems to work. might be cause of a bug. The problem is when comparing an unset property in the style with a set to {0, false, ...) property in the format (eg. set then unset bold)
unchanged = false;
break;
}
}
//we are updating the combo's selected item to what is the current format. we do not want this to apply the style as it would mess up the undo stack, the change tracking,...
disconnect(widget.paragraphStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
m_sortedStylesModel->styleApplied(style);
widget.paragraphStyleCombo->setCurrentIndex(m_sortedStylesModel->indexOf(style).row());
widget.paragraphStyleCombo->setStyleIsOriginal(unchanged);
m_stylesModel->setCurrentParagraphStyle(id);
widget.paragraphStyleCombo->slotUpdatePreview();
connect(widget.paragraphStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
}
}
void SimpleParagraphWidget::setStyleManager(KoStyleManager *sm)
{
Q_ASSERT(sm);
if (!sm || m_styleManager == sm) {
return;
}
if (m_styleManager) {
disconnect(m_styleManager, SIGNAL(styleApplied(const KoParagraphStyle*)), this, SLOT(slotParagraphStyleApplied(const KoParagraphStyle*)));
}
m_styleManager = sm;
//we want to disconnect this before setting the stylemanager. Populating the model apparently selects the first inserted item. We don't want this to actually set a new style.
disconnect(widget.paragraphStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
m_stylesModel->setStyleManager(sm);
m_sortedStylesModel->setStyleManager(sm);
connect(widget.paragraphStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
connect(m_styleManager, SIGNAL(styleApplied(const KoParagraphStyle*)), this, SLOT(slotParagraphStyleApplied(const KoParagraphStyle*)));
}
void SimpleParagraphWidget::setInitialUsedStyles(QVector<int> list)
{
m_sortedStylesModel->setInitialUsedStyles(list);
}
void SimpleParagraphWidget::listStyleChanged(int id)
{
emit doneWithFocus();
if (m_blockSignals) {
return;
}
KoListLevelProperties llp;
llp.setStyle(static_cast<KoListStyle::Style>(id));
llp.setLevel(1);
KoTextEditor::ChangeListFlags flags(KoTextEditor::AutoListStyle);
m_tool->textEditor()->setListProperties(llp, flags);
}
void SimpleParagraphWidget::styleSelected(int index)
{
KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(m_sortedStylesModel->index(index, 0, QModelIndex()).internalId());
if (paragStyle) {
emit paragraphStyleSelected(paragStyle);
}
emit doneWithFocus();
}
void SimpleParagraphWidget::styleSelected(const QModelIndex &index)
{
if (!index.isValid()) {
return;
}
KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(index.internalId());
if (paragStyle) {
emit paragraphStyleSelected(paragStyle);
}
emit doneWithFocus();
}
void SimpleParagraphWidget::slotShowStyleManager(int index)
{
int styleId = m_sortedStylesModel->index(index, 0, QModelIndex()).internalId();
emit showStyleManager(styleId);
emit doneWithFocus();
}
void SimpleParagraphWidget::slotParagraphStyleApplied(const KoParagraphStyle *style)
{
m_sortedStylesModel->styleApplied(style);
}
void SimpleParagraphWidget::changeListLevel(int level)
{
emit doneWithFocus();
if (m_blockSignals) {
return;
}
m_tool->setListLevel(level);
}
diff --git a/plugins/flake/textshape/dialogs/TableOfContentsPreview.cpp b/plugins/flake/textshape/dialogs/TableOfContentsPreview.cpp
index f5a605b379..2af5662e4b 100644
--- a/plugins/flake/textshape/dialogs/TableOfContentsPreview.cpp
+++ b/plugins/flake/textshape/dialogs/TableOfContentsPreview.cpp
@@ -1,192 +1,192 @@
/* This file is part of the KDE project
* Copyright (C) 2011 Gopalakrishna Bhat A <gopalakbhat@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 "TableOfContentsPreview.h"
#include "KoTableOfContentsGeneratorInfo.h"
#include "KoZoomHandler.h"
#include "KoTextDocumentLayout.h"
#include "TextTool.h"
#include <KoInlineTextObjectManager.h>
#include <KoParagraphStyle.h>
#include <KoPageProvider.h>
#include <KoShapePaintingContext.h>
TableOfContentsPreview::TableOfContentsPreview(QWidget *parent)
: QFrame(parent)
, m_textShape(0)
, m_pm(0)
, m_styleManager(0)
, m_previewPixSize(QSize(0, 0))
{
}
TableOfContentsPreview::~TableOfContentsPreview()
{
deleteTextShape();
if (m_pm) {
delete m_pm;
m_pm = 0;
}
}
void TableOfContentsPreview::setStyleManager(KoStyleManager *styleManager)
{
m_styleManager = styleManager;
}
void TableOfContentsPreview::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter *p = new QPainter(this);
p->save();
p->translate(5.5, 1.5);
p->setRenderHint(QPainter::Antialiasing);
QRect rectang = rect();
rectang.adjust(-4, -4, -4, -4);
if (m_pm) {
p->drawPixmap(rectang, *m_pm, m_pm->rect());
} else {
p->fillRect(rectang, QBrush(QColor(Qt::white)));
}
p->restore();
delete p;
}
void TableOfContentsPreview::updatePreview(KoTableOfContentsGeneratorInfo *newToCInfo)
{
QTextBlockFormat tocFormat;
QTextDocument *tocDocument = new QTextDocument(this);
KoTextDocument(tocDocument).setStyleManager(m_styleManager);
KoTableOfContentsGeneratorInfo *info = newToCInfo->clone();
// info->m_indexTitleTemplate.text = newToCInfo->m_indexTitleTemplate.text;
// info->m_useOutlineLevel = newToCInfo->m_useOutlineLevel;
tocFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue<KoTableOfContentsGeneratorInfo *>(info));
tocFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue<QTextDocument *>(tocDocument));
deleteTextShape();
m_textShape = new TextShape(&m_itom, &m_tlm);
if (m_previewPixSize.isEmpty()) {
m_textShape->setSize(size());
} else {
m_textShape->setSize(m_previewPixSize);
}
QTextCursor cursor(m_textShape->textShapeData()->document());
QTextCharFormat textCharFormat = cursor.blockCharFormat();
textCharFormat.setFontPointSize(11);
textCharFormat.setFontWeight(QFont::Normal);
//the brush is set to the background colour so that the actual text block(Heading 1,Heading 1.1 etc.) does not appear in the preview
textCharFormat.setProperty(QTextCharFormat::ForegroundBrush, QBrush(Qt::white));
cursor.setCharFormat(textCharFormat);
cursor.insertBlock(tocFormat);
cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
//insert text for different heading styles
QTextBlockFormat blockFormat;
blockFormat.setProperty(KoParagraphStyle::OutlineLevel, 1);
cursor.insertBlock(blockFormat, textCharFormat);
cursor.insertText("Header 1");
QTextBlockFormat blockFormat1;
blockFormat1.setProperty(KoParagraphStyle::OutlineLevel, 2);
cursor.insertBlock(blockFormat1, textCharFormat);
cursor.insertText("Header 1.1");
QTextBlockFormat blockFormat2;
blockFormat2.setProperty(KoParagraphStyle::OutlineLevel, 2);
cursor.insertBlock(blockFormat2, textCharFormat);
cursor.insertText("Header 1.2");
QTextBlockFormat blockFormat3;
blockFormat3.setProperty(KoParagraphStyle::OutlineLevel, 1);
cursor.insertBlock(blockFormat3, textCharFormat);
cursor.insertText("Header 2");
KoTextDocument(m_textShape->textShapeData()->document()).setStyleManager(m_styleManager);
KoTextDocumentLayout *lay = dynamic_cast<KoTextDocumentLayout *>(m_textShape->textShapeData()->document()->documentLayout());
connect(lay, SIGNAL(finishedLayout()), this, SLOT(finishedPreviewLayout()));
if (lay) {
lay->layout();
}
}
void TableOfContentsPreview::finishedPreviewLayout()
{
if (m_pm) {
delete m_pm;
m_pm = 0;
}
if (m_previewPixSize.isEmpty()) {
m_pm = new QPixmap(size());
} else {
m_pm = new QPixmap(m_previewPixSize);
}
m_pm->fill(Qt::white);
m_zoomHandler.setZoom(0.9);
m_zoomHandler.setDpi(72, 72);
QPainter p(m_pm);
if (m_textShape) {
if (m_previewPixSize.isEmpty()) {
m_textShape->setSize(size());
} else {
m_textShape->setSize(m_previewPixSize);
}
KoShapePaintingContext paintContext; //FIXME
- m_textShape->paintComponent(p, m_zoomHandler, paintContext);
+ m_textShape->paintComponent(p, paintContext);
}
emit pixmapGenerated();
update();
}
QPixmap TableOfContentsPreview::previewPixmap()
{
return QPixmap(*m_pm);
}
void TableOfContentsPreview::deleteTextShape()
{
if (m_textShape) {
KoTextDocumentLayout *lay = dynamic_cast<KoTextDocumentLayout *>(m_textShape->textShapeData()->document()->documentLayout());
if (lay) {
lay->setContinuousLayout(false);
lay->setBlockLayout(true);
}
delete m_textShape;
m_textShape = 0;
}
}
void TableOfContentsPreview::setPreviewSize(const QSize &size)
{
m_previewPixSize = size;
}
diff --git a/plugins/flake/textshape/kotext/KoPageProvider.h b/plugins/flake/textshape/kotext/KoPageProvider.h
index 497bbef216..bad158fdc7 100644
--- a/plugins/flake/textshape/kotext/KoPageProvider.h
+++ b/plugins/flake/textshape/kotext/KoPageProvider.h
@@ -1,41 +1,41 @@
/* This file is part of the KDE project
Copyright (C) 2009 Thorsten Zachmann <zachmann@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 KOPAGEPROVIDER_H
#define KOPAGEPROVIDER_H
#include "kritatext_export.h"
class KoShape;
class KoTextPage;
/// \internal this is a hack for kpresenter
class KRITATEXT_EXPORT KoPageProvider
{
public:
KoPageProvider();
virtual ~KoPageProvider();
/**
* Get the page number for the given shape
*/
- virtual KoTextPage *page(KoShape *shape) = 0;
+ virtual KoTextPage *page(const KoShape *shape) const = 0;
};
#endif // KOPAGEPROVIDER_H
diff --git a/plugins/flake/textshape/kotext/KoTextBlockData.cpp b/plugins/flake/textshape/kotext/KoTextBlockData.cpp
index 608322aac3..1fbdbf8a81 100644
--- a/plugins/flake/textshape/kotext/KoTextBlockData.cpp
+++ b/plugins/flake/textshape/kotext/KoTextBlockData.cpp
@@ -1,302 +1,302 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 C. Boemann <cbo@boemann.dk>
* 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.
*/
#include "KoTextBlockData.h"
#include "KoTextBlockBorderData.h"
#include "KoTextBlockPaintStrategyBase.h"
class Q_DECL_HIDDEN KoTextBlockData::Private : public QTextBlockUserData
{
public:
Private()
: counterWidth(-1.0)
, counterSpacing(0)
, counterIsImage(false)
, counterIndex(1)
, border(0)
, paintStrategy(0)
{
layoutedMarkupRanges[KoTextBlockData::Misspell] = false;
layoutedMarkupRanges[KoTextBlockData::Grammar] = false;
}
~Private() override {
if (border && !border->deref())
delete border;
delete paintStrategy;
}
qreal counterWidth;
qreal counterSpacing;
QString counterPrefix;
QString counterPlainText;
QString counterSuffix;
QString partialCounterText;
bool counterIsImage;
int counterIndex;
QPointF counterPos;
QTextCharFormat labelFormat;
KoTextBlockBorderData *border;
KoTextBlockPaintStrategyBase *paintStrategy;
QMap<KoTextBlockData::MarkupType, QList<MarkupRange> > markupRangesMap;
QMap<KoTextBlockData::MarkupType, bool> layoutedMarkupRanges;
};
KoTextBlockData::KoTextBlockData(QTextBlock &block)
: d(block.userData() ? dynamic_cast<KoTextBlockData::Private *>(block.userData())
: new Private())
{
block.setUserData(d);
}
KoTextBlockData::KoTextBlockData(QTextBlockUserData *userData)
: d(dynamic_cast<KoTextBlockData::Private *>(userData))
{
Q_ASSERT(d);
}
KoTextBlockData::~KoTextBlockData()
{
// explicitly do not delete the d-pointer here
}
void KoTextBlockData::appendMarkup(MarkupType type, int firstChar, int lastChar)
{
Q_ASSERT(d->markupRangesMap[type].isEmpty() || d->markupRangesMap[type].last().lastChar < firstChar);
MarkupRange range;
range.firstChar = firstChar;
range.lastChar = lastChar;
d->layoutedMarkupRanges[type] = false;
d->markupRangesMap[type].append(range);
}
void KoTextBlockData::clearMarkups(MarkupType type)
{
d->markupRangesMap[type].clear();
d->layoutedMarkupRanges[type] = false;
}
KoTextBlockData::MarkupRange KoTextBlockData::findMarkup(MarkupType type, int positionWithin) const
{
foreach (const MarkupRange &range, d->markupRangesMap[type]) {
if (positionWithin <= range.lastChar) {
// possible hit
if (positionWithin >= range.firstChar) {
return range;
} else {
return MarkupRange(); // we have passed it without finding
}
}
}
return MarkupRange(); // either no ranges or not in last either
}
void KoTextBlockData::rebaseMarkups(MarkupType type, int fromPosition, int delta)
{
QList<MarkupRange>::Iterator markIt = markupsBegin(type);
QList<MarkupRange>::Iterator markEnd = markupsEnd(type);
while (markIt != markEnd) {
if (fromPosition <= markIt->lastChar) {
// we need to modify the end of this
markIt->lastChar += delta;
}
if (fromPosition < markIt->firstChar) {
// we need to modify the end of this
markIt->firstChar += delta;
}
++markIt;
}
}
void KoTextBlockData::setMarkupsLayoutValidity(MarkupType type, bool valid)
{
d->layoutedMarkupRanges[type] = valid;
}
bool KoTextBlockData::isMarkupsLayoutValid(MarkupType type) const
{
return d->layoutedMarkupRanges[type];
}
QList<KoTextBlockData::MarkupRange>::Iterator KoTextBlockData::markupsBegin(MarkupType type)
{
return d->markupRangesMap[type].begin();
}
QList<KoTextBlockData::MarkupRange>::Iterator KoTextBlockData::markupsEnd(MarkupType type)
{
return d->markupRangesMap[type].end();
}
bool KoTextBlockData::hasCounterData() const
{
return d->counterWidth >= 0 && (!d->counterPlainText.isNull() || d->counterIsImage);
}
qreal KoTextBlockData::counterWidth() const
{
return qMax(qreal(0), d->counterWidth);
}
void KoTextBlockData::setBorder(KoTextBlockBorderData *border)
{
if (d->border && !d->border->deref())
delete d->border;
d->border = border;
if (d->border)
d->border->ref();
}
void KoTextBlockData::setCounterWidth(qreal width)
{
d->counterWidth = width;
}
qreal KoTextBlockData::counterSpacing() const
{
return d->counterSpacing;
}
void KoTextBlockData::setCounterSpacing(qreal spacing)
{
d->counterSpacing = spacing;
}
QString KoTextBlockData::counterText() const
{
return d->counterPrefix + d->counterPlainText + d->counterSuffix;
}
void KoTextBlockData::clearCounter()
{
d->partialCounterText.clear();
d->counterPlainText.clear();
d->counterPrefix.clear();
d->counterSuffix.clear();
d->counterSpacing = 0.0;
d->counterWidth = 0.0;
d->counterIsImage = false;
}
void KoTextBlockData::setPartialCounterText(const QString &text)
{
d->partialCounterText = text;
}
QString KoTextBlockData::partialCounterText() const
{
return d->partialCounterText;
}
void KoTextBlockData::setCounterPlainText(const QString &text)
{
d->counterPlainText = text;
}
QString KoTextBlockData::counterPlainText() const
{
return d->counterPlainText;
}
void KoTextBlockData::setCounterPrefix(const QString &text)
{
d->counterPrefix = text;
}
QString KoTextBlockData::counterPrefix() const
{
return d->counterPrefix;
}
void KoTextBlockData::setCounterSuffix(const QString &text)
{
d->counterSuffix = text;
}
QString KoTextBlockData::counterSuffix() const
{
return d->counterSuffix;
}
void KoTextBlockData::setCounterIsImage(bool isImage)
{
d->counterIsImage = isImage;
}
bool KoTextBlockData::counterIsImage() const
{
return d->counterIsImage;
}
void KoTextBlockData::setCounterIndex(int index)
{
d->counterIndex = index;
}
int KoTextBlockData::counterIndex() const
{
return d->counterIndex;
}
void KoTextBlockData::setCounterPosition(const QPointF &position)
{
d->counterPos = position;
}
QPointF KoTextBlockData::counterPosition() const
{
return d->counterPos;
}
void KoTextBlockData::setLabelFormat(const QTextCharFormat &format)
{
d->labelFormat = format;
}
QTextCharFormat KoTextBlockData::labelFormat() const
{
return d->labelFormat;
}
KoTextBlockBorderData *KoTextBlockData::border() const
{
return d->border;
}
void KoTextBlockData::setPaintStrategy(KoTextBlockPaintStrategyBase *paintStrategy)
{
delete d->paintStrategy;
d->paintStrategy = paintStrategy;
}
KoTextBlockPaintStrategyBase *KoTextBlockData::paintStrategy() const
{
return d->paintStrategy;
}
bool KoTextBlockData::saveXmlID() const
{
- // as suggested by boemann, http://lists.kde.org/?l=calligra-devel&m=132396354701553&w=2
+ // as suggested by boemann, https://marc.info/?l=calligra-devel&m=132396354701553&w=2
return d->paintStrategy != 0;
}
diff --git a/plugins/flake/textshape/kotext/KoVariable.cpp b/plugins/flake/textshape/kotext/KoVariable.cpp
index b878eeefb5..c8889797ac 100644
--- a/plugins/flake/textshape/kotext/KoVariable.cpp
+++ b/plugins/flake/textshape/kotext/KoVariable.cpp
@@ -1,168 +1,168 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2009 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 "KoVariable.h"
#include "KoInlineObject_p.h"
#include <KoShape.h>
#include <QPainter>
#include <QFontMetricsF>
#include <QTextDocument>
#include <QTextInlineObject>
#include "TextDebug.h"
class KoVariablePrivate : public KoInlineObjectPrivate
{
public:
KoVariablePrivate()
: modified(true),
document(0),
lastPositionInDocument(-1)
{
}
QDebug printDebug(QDebug dbg) const override
{
dbg.nospace() << "KoVariable value=" << value;
return dbg.space();
}
QString value;
bool modified;
const QTextDocument *document;
int lastPositionInDocument;
};
KoVariable::KoVariable(bool propertyChangeListener)
: KoInlineObject(*(new KoVariablePrivate()), propertyChangeListener)
{
}
KoVariable::~KoVariable()
{
}
void KoVariable::setValue(const QString &value)
{
Q_D(KoVariable);
if (d->value == value)
return;
d->value = value;
d->modified = true;
if (d->document) {
const_cast<QTextDocument *>(d->document)->markContentsDirty(d->lastPositionInDocument, 0);
}
}
void KoVariable::updatePosition(const QTextDocument *document, int posInDocument, const QTextCharFormat & format)
{
Q_D(KoVariable);
if (d->document) {
disconnect(d->document, SIGNAL(destroyed()), this, SLOT(documentDestroyed()));
}
d->document = document;
connect(d->document, SIGNAL(destroyed()), this, SLOT(documentDestroyed()));
d->lastPositionInDocument = posInDocument;
Q_UNUSED(format);
// Variables are always 'in place' so the position is 100% defined by the text layout.
variableMoved(d->document, posInDocument);
}
void KoVariable::resize(const QTextDocument *document, QTextInlineObject &object, int posInDocument, const QTextCharFormat &format, QPaintDevice *pd)
{
Q_D(KoVariable);
Q_UNUSED(document);
Q_UNUSED(posInDocument);
if (d->modified == false)
return;
if (object.isValid() == false)
return;
d->modified = true;
Q_ASSERT(format.isCharFormat());
QFontMetricsF fm(format.font(), pd);
qreal width = qMax(qreal(0.0), fm.width(d->value));
qreal ascent = fm.ascent();
qreal descent = fm.descent();
if (object.width() != width) {
object.setWidth(width);
}
if (object.ascent() != ascent) {
object.setAscent(ascent);
}
if (object.descent() != descent) {
object.setDescent(descent);
}
}
void KoVariable::paint(QPainter &painter, QPaintDevice *pd, const QTextDocument *document, const QRectF &rect, const QTextInlineObject &object, int posInDocument, const QTextCharFormat &format)
{
Q_D(KoVariable);
Q_UNUSED(document);
Q_UNUSED(posInDocument);
// TODO set all the font properties from the format (color etc)
QFont font(format.font(), pd);
QTextLayout layout(d->value, font, pd);
layout.setCacheEnabled(true);
QVector<QTextLayout::FormatRange> layouts;
QTextLayout::FormatRange range;
range.start = 0;
range.length = d->value.length();
range.format = format;
layouts.append(range);
layout.setFormats(layouts);
QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute);
if (object.isValid()) {
option.setTextDirection(object.textDirection());
}
layout.setTextOption(option);
layout.beginLayout();
layout.createLine();
layout.endLayout();
layout.draw(&painter, rect.topLeft());
}
void KoVariable::variableMoved(const QTextDocument *document, int posInDocument)
{
Q_UNUSED(document);
Q_UNUSED(posInDocument);
}
QString KoVariable::value() const
{
Q_D(const KoVariable);
return d->value;
}
int KoVariable::positionInDocument() const
{
Q_D(const KoVariable);
return d->lastPositionInDocument;
}
void KoVariable::documentDestroyed()
{
// deleteLater(); does not work when closing a document as the inline object manager is deleted before the control is given back to the event loop
// therefore commit suicide.
- // See http://www.parashift.com/c++-faq-lite/delete-this.html
+ // See https://isocpp.org/wiki/faq/freestore-mgmt#delete-this
delete(this);
}
diff --git a/plugins/flake/textshape/kotext/styles/KoCharacterStyle.cpp b/plugins/flake/textshape/kotext/styles/KoCharacterStyle.cpp
index 0c82bf4715..bac90fbecd 100644
--- a/plugins/flake/textshape/kotext/styles/KoCharacterStyle.cpp
+++ b/plugins/flake/textshape/kotext/styles/KoCharacterStyle.cpp
@@ -1,2268 +1,2268 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Sebastian Sauer <mail@dipe.org>
* Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
* Copyright (C) 2011 Stuart Dickson <stuart@furkinfantasic.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 "KoCharacterStyle.h"
#include "Styles_p.h"
#include <QTextBlock>
#include <QTextCursor>
#include <QFontDatabase>
#include <KoOdfLoadingContext.h>
#include <KoOdfStylesReader.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoUnit.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoGenStyle.h>
#include <KoShadowStyle.h>
#include <KoShapeLoadingContext.h>
#include <KoStyleStack.h>
#include "KoTextDocument.h"
#ifdef SHOULD_BUILD_FONT_CONVERSION
#include <string.h>
#include <fontconfig/fontconfig.h>
#include <fontconfig/fcfreetype.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_TYPES_H
#include FT_OUTLINE_H
#include FT_RENDER_H
#include FT_TRUETYPE_TABLES_H
#include FT_SFNT_NAMES_H
#endif
#include "TextDebug.h"
#include "KoTextDebug.h"
#ifdef SHOULD_BUILD_FONT_CONVERSION
QMap<QString,qreal> textScaleMap;
#endif //SHOULD_BUILD_FONT_CONVERSION
class Q_DECL_HIDDEN KoCharacterStyle::Private
{
public:
Private();
~Private() { }
void setProperty(int key, const QVariant &value) {
stylesPrivate.add(key, value);
}
qreal propertyDouble(int key) const {
QVariant variant = stylesPrivate.value(key);
if (variant.isNull()) {
if (parentStyle)
return parentStyle->d->propertyDouble(key);
else if (defaultStyle)
return defaultStyle->d->propertyDouble(key);
return 0.0;
}
return variant.toDouble();
}
int propertyInt(int key) const {
QVariant variant = stylesPrivate.value(key);
if (variant.isNull()) {
if (parentStyle)
return parentStyle->d->propertyInt(key);
else if (defaultStyle)
return defaultStyle->d->propertyInt(key);
return 0;
}
return variant.toInt();
}
QString propertyString(int key) const {
QVariant variant = stylesPrivate.value(key);
if (variant.isNull()) {
if (parentStyle)
return parentStyle->d->propertyString(key);
else if (defaultStyle)
return defaultStyle->d->propertyString(key);
return QString();
}
return qvariant_cast<QString>(variant);
}
bool propertyBoolean(int key) const {
QVariant variant = stylesPrivate.value(key);
if (variant.isNull()) {
if (parentStyle)
return parentStyle->d->propertyBoolean(key);
else if (defaultStyle)
return defaultStyle->d->propertyBoolean(key);
return false;
}
return variant.toBool();
}
QColor propertyColor(int key) const {
QVariant variant = stylesPrivate.value(key);
if (variant.isNull()) {
if (parentStyle)
return parentStyle->d->propertyColor(key);
else if (defaultStyle)
return defaultStyle->d->propertyColor(key);
return QColor();
}
return variant.value<QColor>();
}
// problem with fonts in linux and windows is that true type fonts have more than one metric
// they have normal metric placed in font header table
// microsoft metric placed in os2 table
// apple metric placed in os2 table
// ms-word is probably using CreateFontIndirect and GetOutlineTextMetric function to calculate line height
// and this functions are using windows gdi environment which is using microsoft font metric placed in os2 table
// qt on linux is using normal font metric
// this two metrics are different and change from font to font
// this font stretch is needed if we want to have exact line height as in ms-word and oo
//
// font_size * font_stretch = windows_font_height
qreal calculateFontYStretch(const QString &fontFamily);
StylePrivate hardCodedDefaultStyle;
QString name;
StylePrivate stylesPrivate;
KoCharacterStyle *parentStyle;
KoCharacterStyle *defaultStyle;
bool m_inUse;
};
KoCharacterStyle::Private::Private()
: parentStyle(0), defaultStyle(0), m_inUse(false)
{
//set the minimal default properties
hardCodedDefaultStyle.add(QTextFormat::FontFamily, QString("Sans Serif"));
hardCodedDefaultStyle.add(QTextFormat::FontPointSize, 12.0);
hardCodedDefaultStyle.add(QTextFormat::ForegroundBrush, QBrush(Qt::black));
hardCodedDefaultStyle.add(KoCharacterStyle::FontYStretch, 1);
hardCodedDefaultStyle.add(QTextFormat::FontHintingPreference, QFont::PreferNoHinting);
}
void KoCharacterStyle::ensureMinimalProperties(QTextCharFormat &format) const
{
if (d->defaultStyle) {
QMap<int, QVariant> props = d->defaultStyle->d->stylesPrivate.properties();
QMap<int, QVariant>::const_iterator it = props.constBegin();
while (it != props.constEnd()) {
// in case there is already a foreground color don't apply the use window font color as then the foreground color
// should be used.
if (it.key() == KoCharacterStyle::UseWindowFontColor && format.hasProperty(QTextFormat::ForegroundBrush)) {
++it;
continue;
}
// in case there is already a use window font color don't apply the foreground brush as this overwrite the foreground color
if (it.key() == QTextFormat::ForegroundBrush && format.hasProperty(KoCharacterStyle::UseWindowFontColor)) {
++it;
continue;
}
if (!it.value().isNull() && !format.hasProperty(it.key())) {
format.setProperty(it.key(), it.value());
}
++it;
}
}
QMap<int, QVariant> props = d->hardCodedDefaultStyle.properties();
QMap<int, QVariant>::const_iterator it = props.constBegin();
while (it != props.constEnd()) {
if (!it.value().isNull() && !format.hasProperty(it.key())) {
if (it.key() == QTextFormat::ForegroundBrush && format.hasProperty(KoCharacterStyle::UseWindowFontColor)) {
++it;
continue;
}
format.setProperty(it.key(), it.value());
}
++it;
}
}
qreal KoCharacterStyle::Private::calculateFontYStretch(const QString &fontFamily)
{
qreal stretch = 1;
#ifdef SHOULD_BUILD_FONT_CONVERSION
if (textScaleMap.contains(fontFamily)) {
return textScaleMap.value(fontFamily);
}
FcResult result = FcResultMatch;
FT_Library library;
FT_Face face;
int id = 0;
int error = 0;
QByteArray fontName = fontFamily.toLatin1();
- //TODO http://freedesktop.org/software/fontconfig/fontconfig-devel/x19.html
+ //TODO https://freedesktop.org/software/fontconfig/fontconfig-devel/x19.html
// we should specify slant and weight too
FcPattern *font = FcPatternBuild (0, FC_FAMILY, FcTypeString,fontName.data(), FC_SIZE, FcTypeDouble, (qreal)11, 0);
if (font == 0) {
return 1;
}
// find font
FcPattern *matched = 0;
matched = FcFontMatch (0, font, &result);
if (matched == 0) {
FcPatternDestroy (font);
return 1;
}
// get font family name
char * str = 0;
result = FcPatternGetString (matched, FC_FAMILY, 0,(FcChar8**) &str);
if (result != FcResultMatch || str == 0) {
FcPatternDestroy (font);
FcPatternDestroy (matched);
return 1;
}
// check if right font was found
QByteArray foundFontFamily = QByteArray::fromRawData(str, strlen(str));
if (foundFontFamily != fontName) {
FcPatternDestroy (font);
FcPatternDestroy (matched);
return 1;
}
// get path to font
str = 0;
result = FcPatternGetString (matched, FC_FILE, 0,(FcChar8**) &str);
if (result != FcResultMatch) {
FcPatternDestroy (font);
FcPatternDestroy (matched);
return 1;
}
// get index of font inside the font file
result = FcPatternGetInteger (matched, FC_INDEX, 0, &id);
if (result != FcResultMatch) {
FcPatternDestroy (font);
FcPatternDestroy (matched);
return 1;
}
// initialize freetype
error = FT_Init_FreeType( &library );
if (error) {
FcPatternDestroy (font);
FcPatternDestroy (matched);
return 1;
}
// get font metric
error = FT_New_Face (library,(char *) str, id, &face);
if (error) {
FT_Done_FreeType(library);
FcPatternDestroy (font);
FcPatternDestroy (matched);
return 1;
}
// get font metric os2 table
TT_OS2 *os2;
os2 = (TT_OS2 *) FT_Get_Sfnt_Table (face, ft_sfnt_os2);
if(os2 == 0) {
FT_Done_Face(face);
FT_Done_FreeType(library);
FcPatternDestroy (font);
FcPatternDestroy (matched);
return 1;
}
// get font metric header table
TT_Header *header;
header = (TT_Header *) FT_Get_Sfnt_Table (face, ft_sfnt_head);
if(header == 0) {
FT_Done_Face(face);
FT_Done_FreeType(library);
FcPatternDestroy (font);
FcPatternDestroy (matched);
return 1;
}
// check if the data is valid
if (header->Units_Per_EM == 0 || (os2->usWinAscent + os2->usWinDescent) == 0) {
FT_Done_Face(face);
FT_Done_FreeType(library);
FcPatternDestroy (font);
FcPatternDestroy (matched);
return 1;
}
// compute font height stretch
// font_size * font_stretch = windows_font_height
qreal height = os2->usWinAscent + os2->usWinDescent;
height = height * (2048 / header->Units_Per_EM);
stretch = (1.215 * height)/2500;
stretch = (1.15 * height)/2500; // seems a better guess but probably not right
FT_Done_Face(face);
FT_Done_FreeType(library);
FcPatternDestroy (font);
FcPatternDestroy (matched);
textScaleMap.insert(fontFamily, stretch);
#else
Q_UNUSED(fontFamily);
#endif //SHOULD_BUILD_FONT_CONVERSION
return stretch;
}
KoCharacterStyle::KoCharacterStyle(QObject *parent)
: QObject(parent), d(new Private())
{
}
KoCharacterStyle::KoCharacterStyle(const QTextCharFormat &format, QObject *parent)
: QObject(parent), d(new Private())
{
copyProperties(format);
}
KoCharacterStyle::Type KoCharacterStyle::styleType() const
{
return KoCharacterStyle::CharacterStyle;
}
void KoCharacterStyle::copyProperties(const KoCharacterStyle *style)
{
d->stylesPrivate = style->d->stylesPrivate;
setName(style->name()); // make sure we emit property change
d->parentStyle = style->d->parentStyle;
d->defaultStyle = style->d->defaultStyle;
}
void KoCharacterStyle::copyProperties(const QTextCharFormat &format)
{
d->stylesPrivate = format.properties();
}
KoCharacterStyle *KoCharacterStyle::clone(QObject *parent) const
{
KoCharacterStyle *newStyle = new KoCharacterStyle(parent);
newStyle->copyProperties(this);
return newStyle;
}
KoCharacterStyle::~KoCharacterStyle()
{
delete d;
}
void KoCharacterStyle::setDefaultStyle(KoCharacterStyle *defaultStyle)
{
d->defaultStyle = defaultStyle;
}
void KoCharacterStyle::setParentStyle(KoCharacterStyle *parent)
{
d->parentStyle = parent;
}
KoCharacterStyle *KoCharacterStyle::parentStyle() const
{
return d->parentStyle;
}
QPen KoCharacterStyle::textOutline() const
{
QVariant variant = value(QTextFormat::TextOutline);
if (variant.isNull()) {
return QPen(Qt::NoPen);
}
return qvariant_cast<QPen>(variant);
}
QBrush KoCharacterStyle::background() const
{
QVariant variant = value(QTextFormat::BackgroundBrush);
if (variant.isNull()) {
return QBrush();
}
return qvariant_cast<QBrush>(variant);
}
void KoCharacterStyle::clearBackground()
{
d->stylesPrivate.remove(QTextCharFormat::BackgroundBrush);
}
QBrush KoCharacterStyle::foreground() const
{
QVariant variant = value(QTextFormat::ForegroundBrush);
if (variant.isNull()) {
return QBrush();
}
return qvariant_cast<QBrush>(variant);
}
void KoCharacterStyle::clearForeground()
{
d->stylesPrivate.remove(QTextCharFormat::ForegroundBrush);
}
void KoCharacterStyle::applyStyle(QTextCharFormat &format, bool emitSignal) const
{
if (d->parentStyle) {
d->parentStyle->applyStyle(format);
}
bool fontSizeSet = false; // if this style has already set size don't apply the relatives
const QMap<int, QVariant> props = d->stylesPrivate.properties();
QMap<int, QVariant>::const_iterator it = props.begin();
QList<int> clearProperty;
while (it != props.end()) {
if (!it.value().isNull()) {
if (it.key() == KoCharacterStyle::PercentageFontSize && !fontSizeSet) {
qreal size = it.value().toDouble() / 100.0;
if (format.hasProperty(QTextFormat::FontPointSize)) {
size *= format.doubleProperty(QTextFormat::FontPointSize);
} else {
size *= 12.0;
}
format.setProperty(QTextFormat::FontPointSize, size);
}
else if (it.key() == KoCharacterStyle::AdditionalFontSize && !fontSizeSet) {
qreal size = it.value().toDouble() / 100.0;
if (format.hasProperty(QTextFormat::FontPointSize)) {
size += format.doubleProperty(QTextFormat::FontPointSize);
} else {
size += 12.0;
}
format.setProperty(QTextFormat::FontPointSize, size);
}
else if (it.key() == QTextFormat::FontFamily) {
if (!props.contains(QTextFormat::FontStyleHint)) {
clearProperty.append(QTextFormat::FontStyleHint);
}
if (!props.contains(QTextFormat::FontFixedPitch)) {
clearProperty.append(QTextFormat::FontFixedPitch);
}
if (!props.contains(KoCharacterStyle::FontCharset)) {
clearProperty.append(KoCharacterStyle::FontCharset);
}
format.setProperty(it.key(), it.value());
}
else {
debugText << "setProperty" << it.key() << it.value();
format.setProperty(it.key(), it.value());
}
if (it.key() == QTextFormat::FontPointSize) {
fontSizeSet = true;
}
if (it.key() == QTextFormat::ForegroundBrush) {
clearProperty.append(KoCharacterStyle::UseWindowFontColor);
}
else if (it.key() == KoCharacterStyle::UseWindowFontColor) {
clearProperty.append(QTextFormat::ForegroundBrush);
}
}
++it;
}
foreach (int property, clearProperty) {
debugText << "clearProperty" << property;
format.clearProperty(property);
}
if (emitSignal) {
emit styleApplied(this);
d->m_inUse = true;
}
}
KoCharacterStyle *KoCharacterStyle::autoStyle(const QTextCharFormat &format, QTextCharFormat blockCharFormat) const
{
KoCharacterStyle *autoStyle = new KoCharacterStyle(format);
applyStyle(blockCharFormat, false);
ensureMinimalProperties(blockCharFormat);
autoStyle->removeDuplicates(blockCharFormat);
autoStyle->setParentStyle(const_cast<KoCharacterStyle*>(this));
// remove StyleId if it is there as it is not a property of the style itself and will not be written out
// so it should not be part of the autostyle. As otherwise it can happen that the StyleId is the only
// property left and then we write out an empty style which is unneeded.
// we also need to remove the properties of links as they are saved differently
autoStyle->d->stylesPrivate.remove(StyleId);
autoStyle->d->stylesPrivate.remove(QTextFormat::IsAnchor);
autoStyle->d->stylesPrivate.remove(QTextFormat::AnchorHref);
autoStyle->d->stylesPrivate.remove(QTextFormat::AnchorName);
return autoStyle;
}
struct FragmentData
{
FragmentData(const QTextCharFormat &format, int position, int length)
: format(format)
, position(position)
, length(length)
{}
QTextCharFormat format;
int position;
int length;
};
void KoCharacterStyle::applyStyle(QTextBlock &block) const
{
QTextCursor cursor(block);
QTextCharFormat cf = block.charFormat();
if (!cf.isTableCellFormat()) {
cf = KoTextDocument(block.document()).frameCharFormat();
}
applyStyle(cf);
ensureMinimalProperties(cf);
cursor.setBlockCharFormat(cf);
// be sure that we keep the InlineInstanceId, anchor information and ChangeTrackerId when applying a style
QList<FragmentData> fragments;
for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) {
QTextFragment currentFragment = it.fragment();
if (currentFragment.isValid()) {
QTextCharFormat format(cf);
QVariant v = currentFragment.charFormat().property(InlineInstanceId);
if (!v.isNull()) {
format.setProperty(InlineInstanceId, v);
}
v = currentFragment.charFormat().property(ChangeTrackerId);
if (!v.isNull()) {
format.setProperty(ChangeTrackerId, v);
}
if (currentFragment.charFormat().isAnchor()) {
format.setAnchor(true);
format.setAnchorHref(currentFragment.charFormat().anchorHref());
}
fragments.append(FragmentData(format, currentFragment.position(), currentFragment.length()));
}
}
foreach (const FragmentData &fragment, fragments) {
cursor.setPosition(fragment.position);
cursor.setPosition(fragment.position + fragment.length, QTextCursor::KeepAnchor);
cursor.setCharFormat(fragment.format);
}
}
void KoCharacterStyle::applyStyle(QTextCursor *selection) const
{
// FIXME below should be done for each frament in the selection
QTextCharFormat cf = selection->charFormat();
applyStyle(cf);
ensureMinimalProperties(cf);
selection->setCharFormat(cf);
}
void KoCharacterStyle::unapplyStyle(QTextCharFormat &format) const
{
if (d->parentStyle)
d->parentStyle->unapplyStyle(format);
QMap<int, QVariant> props = d->stylesPrivate.properties();
QMap<int, QVariant>::const_iterator it = props.constBegin();
while (it != props.constEnd()) {
if (!it.value().isNull() && it.value() == format.property(it.key())) {
format.clearProperty(it.key());
}
++it;
}
props = d->hardCodedDefaultStyle.properties();
it = props.constBegin();
while (it != props.constEnd()) {
if (!it.value().isNull() && !format.hasProperty(it.key())) {
format.setProperty(it.key(), it.value());
}
++it;
}
}
bool KoCharacterStyle::isApplied() const
{
return d->m_inUse;
}
void KoCharacterStyle::unapplyStyle(QTextBlock &block) const
{
QTextCursor cursor(block);
QTextCharFormat cf = cursor.blockCharFormat();
unapplyStyle(cf);
cursor.setBlockCharFormat(cf);
if (block.length() == 1) // only the linefeed
return;
QTextBlock::iterator iter = block.end();
do {
--iter;
QTextFragment fragment = iter.fragment();
cursor.setPosition(fragment.position() + 1);
cf = cursor.charFormat();
unapplyStyle(cf);
cursor.setPosition(fragment.position());
cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);
cursor.setCharFormat(cf);
} while (iter != block.begin());
}
// OASIS 14.2.29
static void parseOdfLineWidth(const QString &width, KoCharacterStyle::LineWeight &lineWeight, qreal &lineWidth)
{
lineWidth = 0;
lineWeight = KoCharacterStyle::AutoLineWeight;
if (width.isEmpty() || width == "auto")
lineWeight = KoCharacterStyle::AutoLineWeight;
else if (width == "normal")
lineWeight = KoCharacterStyle::NormalLineWeight;
else if (width == "bold")
lineWeight = KoCharacterStyle::BoldLineWeight;
else if (width == "thin")
lineWeight = KoCharacterStyle::ThinLineWeight;
else if (width == "dash")
lineWeight = KoCharacterStyle::DashLineWeight;
else if (width == "medium")
lineWeight = KoCharacterStyle::MediumLineWeight;
else if (width == "thick")
lineWeight = KoCharacterStyle::ThickLineWeight;
else if (width.endsWith('%')) {
lineWeight = KoCharacterStyle::PercentLineWeight;
lineWidth = width.mid(0, width.length() - 1).toDouble();
} else if (width[width.length()-1].isNumber()) {
lineWeight = KoCharacterStyle::LengthLineWeight;
lineWidth = width.toDouble();
} else {
lineWeight = KoCharacterStyle::LengthLineWeight;
lineWidth = KoUnit::parseValue(width);
}
}
// OASIS 14.2.29
static void importOdfLine(const QString &type, const QString &style, KoCharacterStyle::LineStyle &lineStyle, KoCharacterStyle::LineType &lineType)
{
lineStyle = KoCharacterStyle::NoLineStyle;
lineType = KoCharacterStyle::NoLineType;
QString fixedType = type;
QString fixedStyle = style;
if (fixedStyle == "none")
fixedType.clear();
else if (fixedType.isEmpty() && !fixedStyle.isEmpty())
fixedType = "single";
else if (!fixedType.isEmpty() && fixedType != "none" && fixedStyle.isEmpty()) {
// don't set a style when the type is none
fixedStyle = "solid";
}
if (fixedType == "single")
lineType = KoCharacterStyle::SingleLine;
else if (fixedType == "double")
lineType = KoCharacterStyle::DoubleLine;
if (fixedStyle == "solid")
lineStyle = KoCharacterStyle::SolidLine;
else if (fixedStyle == "dotted")
lineStyle = KoCharacterStyle::DottedLine;
else if (fixedStyle == "dash")
lineStyle = KoCharacterStyle::DashLine;
else if (fixedStyle == "long-dash")
lineStyle = KoCharacterStyle::LongDashLine;
else if (fixedStyle == "dot-dash")
lineStyle = KoCharacterStyle::DotDashLine;
else if (fixedStyle == "dot-dot-dash")
lineStyle = KoCharacterStyle::DotDotDashLine;
else if (fixedStyle == "wave")
lineStyle = KoCharacterStyle::WaveLine;
}
static QString exportOdfLineType(KoCharacterStyle::LineType lineType)
{
switch (lineType) {
case KoCharacterStyle::NoLineType:
return "none";
case KoCharacterStyle::SingleLine:
return "single";
case KoCharacterStyle::DoubleLine:
return "double";
default:
return "";
}
}
static QString exportOdfLineStyle(KoCharacterStyle::LineStyle lineStyle)
{
switch (lineStyle) {
case KoCharacterStyle::NoLineStyle:
return "none";
case KoCharacterStyle::SolidLine:
return "solid";
case KoCharacterStyle::DottedLine:
return "dotted";
case KoCharacterStyle::DashLine:
return "dash";
case KoCharacterStyle::LongDashLine:
return "long-dash";
case KoCharacterStyle::DotDashLine:
return "dot-dash";
case KoCharacterStyle::DotDotDashLine:
return "dot-dot-dash";
case KoCharacterStyle::WaveLine:
return "wave";
default:
return "";
}
}
static QString exportOdfLineMode(KoCharacterStyle::LineMode lineMode)
{
switch (lineMode) {
case KoCharacterStyle::ContinuousLineMode:
return "continuous";
case KoCharacterStyle::SkipWhiteSpaceLineMode:
return "skip-white-space";
default:
return "";
}
}
static QString exportOdfLineWidth(KoCharacterStyle::LineWeight lineWeight, qreal lineWidth)
{
switch (lineWeight) {
case KoCharacterStyle::AutoLineWeight:
return "auto";
case KoCharacterStyle::NormalLineWeight:
return "normal";
case KoCharacterStyle::BoldLineWeight:
return "bold";
case KoCharacterStyle::ThinLineWeight:
return "thin";
case KoCharacterStyle::DashLineWeight:
return "dash";
case KoCharacterStyle::MediumLineWeight:
return "medium";
case KoCharacterStyle::ThickLineWeight:
return "thick";
case KoCharacterStyle::PercentLineWeight:
return QString("%1%").arg(lineWidth);
case KoCharacterStyle::LengthLineWeight:
return QString("%1pt").arg(lineWidth);
default:
return QString();
}
}
static QString exportOdfFontStyleHint(QFont::StyleHint hint)
{
switch (hint) {
case QFont::Serif:
return "roman";
case QFont::SansSerif:
return "swiss";
case QFont::TypeWriter:
return "modern";
case QFont::Decorative:
return "decorative";
case QFont::System:
return "system";
/*case QFont::Script */
default:
return "";
}
}
void KoCharacterStyle::setFontFamily(const QString &family)
{
d->setProperty(QTextFormat::FontFamily, family);
setFontYStretch(d->calculateFontYStretch(family));
}
QString KoCharacterStyle::fontFamily() const
{
return d->propertyString(QTextFormat::FontFamily);
}
void KoCharacterStyle::setFontPointSize(qreal size)
{
d->setProperty(QTextFormat::FontPointSize, size);
}
void KoCharacterStyle::clearFontPointSize() {
d->stylesPrivate.remove(QTextFormat::FontPointSize);
}
qreal KoCharacterStyle::fontPointSize() const
{
return d->propertyDouble(QTextFormat::FontPointSize);
}
void KoCharacterStyle::setFontWeight(int weight)
{
d->setProperty(QTextFormat::FontWeight, weight);
}
int KoCharacterStyle::fontWeight() const
{
return d->propertyInt(QTextFormat::FontWeight);
}
void KoCharacterStyle::setFontItalic(bool italic)
{
d->setProperty(QTextFormat::FontItalic, italic);
}
bool KoCharacterStyle::fontItalic() const
{
return d->propertyBoolean(QTextFormat::FontItalic);
}
///TODO Review legacy fontOverline functions and testing (consider removal)
/*
void KoCharacterStyle::setFontOverline(bool overline)
{
d->setProperty(QTextFormat::FontOverline, overline);
}
bool KoCharacterStyle::fontOverline() const
{
return d->propertyBoolean(QTextFormat::FontOverline);
}
*/
void KoCharacterStyle::setFontFixedPitch(bool fixedPitch)
{
d->setProperty(QTextFormat::FontFixedPitch, fixedPitch);
}
bool KoCharacterStyle::fontFixedPitch() const
{
return d->propertyBoolean(QTextFormat::FontFixedPitch);
}
void KoCharacterStyle::setFontStyleHint(QFont::StyleHint styleHint)
{
d->setProperty(QTextFormat::FontStyleHint, styleHint);
}
QFont::StyleHint KoCharacterStyle::fontStyleHint() const
{
return static_cast<QFont::StyleHint>(d->propertyInt(QTextFormat::FontStyleHint));
}
void KoCharacterStyle::setFontKerning(bool enable)
{
d->setProperty(QTextFormat::FontKerning, enable);
}
bool KoCharacterStyle::fontKerning() const
{
return d->propertyBoolean(QTextFormat::FontKerning);
}
void KoCharacterStyle::setVerticalAlignment(QTextCharFormat::VerticalAlignment alignment)
{
d->setProperty(QTextFormat::TextVerticalAlignment, alignment);
}
QTextCharFormat::VerticalAlignment KoCharacterStyle::verticalAlignment() const
{
return static_cast<QTextCharFormat::VerticalAlignment>(d->propertyInt(QTextFormat::TextVerticalAlignment));
}
void KoCharacterStyle::setTextOutline(const QPen &pen)
{
d->setProperty(QTextFormat::TextOutline, pen);
}
void KoCharacterStyle::setBackground(const QBrush &brush)
{
d->setProperty(QTextFormat::BackgroundBrush, brush);
}
void KoCharacterStyle::setForeground(const QBrush &brush)
{
d->setProperty(QTextFormat::ForegroundBrush, brush);
}
void KoCharacterStyle::setFontAutoColor(bool use)
{
d->setProperty(KoCharacterStyle::UseWindowFontColor, use);
}
QString KoCharacterStyle::name() const
{
return d->name;
}
void KoCharacterStyle::setName(const QString &name)
{
if (name == d->name)
return;
d->name = name;
emit nameChanged(name);
}
int KoCharacterStyle::styleId() const
{
return d->propertyInt(StyleId);
}
void KoCharacterStyle::setStyleId(int id)
{
d->setProperty(StyleId, id);
}
QFont KoCharacterStyle::font() const
{
QFont font;
if (d->stylesPrivate.contains(QTextFormat::FontFamily))
font.setFamily(fontFamily());
if (d->stylesPrivate.contains(QTextFormat::FontPointSize))
font.setPointSizeF(fontPointSize());
if (d->stylesPrivate.contains(QTextFormat::FontWeight))
font.setWeight(fontWeight());
if (d->stylesPrivate.contains(QTextFormat::FontItalic))
font.setItalic(fontItalic());
return font;
}
void KoCharacterStyle::setHasHyphenation(bool on)
{
d->setProperty(HasHyphenation, on);
}
bool KoCharacterStyle::hasHyphenation() const
{
return d->propertyBoolean(HasHyphenation);
}
void KoCharacterStyle::setHyphenationPushCharCount(int count)
{
if (count > 0)
d->setProperty(HyphenationPushCharCount, count);
else
d->stylesPrivate.remove(HyphenationPushCharCount);
}
int KoCharacterStyle::hyphenationPushCharCount() const
{
if (hasProperty(HyphenationPushCharCount))
return d->propertyInt(HyphenationPushCharCount);
return 0;
}
void KoCharacterStyle::setHyphenationRemainCharCount(int count)
{
if (count > 0)
d->setProperty(HyphenationRemainCharCount, count);
else
d->stylesPrivate.remove(HyphenationRemainCharCount);
}
int KoCharacterStyle::hyphenationRemainCharCount() const
{
if (hasProperty(HyphenationRemainCharCount))
return d->propertyInt(HyphenationRemainCharCount);
return 0;
}
void KoCharacterStyle::setStrikeOutStyle(KoCharacterStyle::LineStyle strikeOut)
{
d->setProperty(StrikeOutStyle, strikeOut);
}
KoCharacterStyle::LineStyle KoCharacterStyle::strikeOutStyle() const
{
return (KoCharacterStyle::LineStyle) d->propertyInt(StrikeOutStyle);
}
void KoCharacterStyle::setStrikeOutType(LineType lineType)
{
d->setProperty(StrikeOutType, lineType);
}
KoCharacterStyle::LineType KoCharacterStyle::strikeOutType() const
{
return (KoCharacterStyle::LineType) d->propertyInt(StrikeOutType);
}
void KoCharacterStyle::setStrikeOutColor(const QColor &color)
{
d->setProperty(StrikeOutColor, color);
}
QColor KoCharacterStyle::strikeOutColor() const
{
return d->propertyColor(StrikeOutColor);
}
void KoCharacterStyle::setStrikeOutWidth(LineWeight weight, qreal width)
{
d->setProperty(KoCharacterStyle::StrikeOutWeight, weight);
d->setProperty(KoCharacterStyle::StrikeOutWidth, width);
}
void KoCharacterStyle::strikeOutWidth(LineWeight &weight, qreal &width) const
{
weight = (KoCharacterStyle::LineWeight) d->propertyInt(KoCharacterStyle::StrikeOutWeight);
width = d->propertyDouble(KoCharacterStyle::StrikeOutWidth);
}
void KoCharacterStyle::setStrikeOutMode(LineMode lineMode)
{
d->setProperty(StrikeOutMode, lineMode);
}
void KoCharacterStyle::setStrikeOutText(const QString &text)
{
d->setProperty(StrikeOutText, text);
}
QString KoCharacterStyle::strikeOutText() const
{
return d->propertyString(StrikeOutText);
}
KoCharacterStyle::LineMode KoCharacterStyle::strikeOutMode() const
{
return (KoCharacterStyle::LineMode) d->propertyInt(StrikeOutMode);
}
void KoCharacterStyle::setOverlineStyle(KoCharacterStyle::LineStyle overline)
{
d->setProperty(OverlineStyle, overline);
}
KoCharacterStyle::LineStyle KoCharacterStyle::overlineStyle() const
{
return (KoCharacterStyle::LineStyle) d->propertyInt(OverlineStyle);
}
void KoCharacterStyle::setOverlineType(LineType lineType)
{
d->setProperty(OverlineType, lineType);
}
KoCharacterStyle::LineType KoCharacterStyle::overlineType() const
{
return (KoCharacterStyle::LineType) d->propertyInt(OverlineType);
}
void KoCharacterStyle::setOverlineColor(const QColor &color)
{
d->setProperty(KoCharacterStyle::OverlineColor, color);
}
QColor KoCharacterStyle::overlineColor() const
{
return d->propertyColor(KoCharacterStyle::OverlineColor);
}
void KoCharacterStyle::setOverlineWidth(LineWeight weight, qreal width)
{
d->setProperty(KoCharacterStyle::OverlineWeight, weight);
d->setProperty(KoCharacterStyle::OverlineWidth, width);
}
void KoCharacterStyle::overlineWidth(LineWeight &weight, qreal &width) const
{
weight = (KoCharacterStyle::LineWeight) d->propertyInt(KoCharacterStyle::OverlineWeight);
width = d->propertyDouble(KoCharacterStyle::OverlineWidth);
}
void KoCharacterStyle::setOverlineMode(LineMode mode)
{
d->setProperty(KoCharacterStyle::OverlineMode, mode);
}
KoCharacterStyle::LineMode KoCharacterStyle::overlineMode() const
{
return static_cast<KoCharacterStyle::LineMode>(d->propertyInt(KoCharacterStyle::OverlineMode));
}
void KoCharacterStyle::setUnderlineStyle(KoCharacterStyle::LineStyle underline)
{
d->setProperty(UnderlineStyle, underline);
}
KoCharacterStyle::LineStyle KoCharacterStyle::underlineStyle() const
{
return (KoCharacterStyle::LineStyle) d->propertyInt(UnderlineStyle);
}
void KoCharacterStyle::setUnderlineType(LineType lineType)
{
d->setProperty(UnderlineType, lineType);
}
KoCharacterStyle::LineType KoCharacterStyle::underlineType() const
{
return (KoCharacterStyle::LineType) d->propertyInt(UnderlineType);
}
void KoCharacterStyle::setUnderlineColor(const QColor &color)
{
d->setProperty(QTextFormat::TextUnderlineColor, color);
}
QColor KoCharacterStyle::underlineColor() const
{
return d->propertyColor(QTextFormat::TextUnderlineColor);
}
void KoCharacterStyle::setUnderlineWidth(LineWeight weight, qreal width)
{
d->setProperty(KoCharacterStyle::UnderlineWeight, weight);
d->setProperty(KoCharacterStyle::UnderlineWidth, width);
}
void KoCharacterStyle::underlineWidth(LineWeight &weight, qreal &width) const
{
weight = (KoCharacterStyle::LineWeight) d->propertyInt(KoCharacterStyle::UnderlineWeight);
width = d->propertyDouble(KoCharacterStyle::UnderlineWidth);
}
void KoCharacterStyle::setUnderlineMode(LineMode mode)
{
d->setProperty(KoCharacterStyle::UnderlineMode, mode);
}
KoCharacterStyle::LineMode KoCharacterStyle::underlineMode() const
{
return static_cast<KoCharacterStyle::LineMode>(d->propertyInt(KoCharacterStyle::UnderlineMode));
}
void KoCharacterStyle::setFontLetterSpacing(qreal spacing)
{
d->setProperty(KoCharacterStyle::FontLetterSpacing, spacing);
}
qreal KoCharacterStyle::fontLetterSpacing() const
{
return d->propertyDouble(KoCharacterStyle::FontLetterSpacing);
}
void KoCharacterStyle::setFontWordSpacing(qreal spacing)
{
d->setProperty(QTextCharFormat::FontWordSpacing, spacing);
}
qreal KoCharacterStyle::fontWordSpacing() const
{
return d->propertyDouble(QTextCharFormat::FontWordSpacing);
}
void KoCharacterStyle::setFontCapitalization(QFont::Capitalization capitalization)
{
d->setProperty(QTextFormat::FontCapitalization, capitalization);
}
QFont::Capitalization KoCharacterStyle::fontCapitalization() const
{
return (QFont::Capitalization) d->propertyInt(QTextFormat::FontCapitalization);
}
void KoCharacterStyle::setFontYStretch(qreal stretch)
{
d->setProperty(KoCharacterStyle::FontYStretch, stretch);
}
qreal KoCharacterStyle::fontYStretch() const
{
return d->propertyDouble(KoCharacterStyle::FontYStretch);
}
void KoCharacterStyle::setCountry(const QString &country)
{
if (country.isEmpty())
d->stylesPrivate.remove(KoCharacterStyle::Country);
else
d->setProperty(KoCharacterStyle::Country, country);
}
void KoCharacterStyle::setLanguage(const QString &language)
{
if (language.isEmpty())
d->stylesPrivate.remove(KoCharacterStyle::Language);
else
d->setProperty(KoCharacterStyle::Language, language);
}
QString KoCharacterStyle::country() const
{
return value(KoCharacterStyle::Country).toString();
}
QString KoCharacterStyle::language() const
{
return d->propertyString(KoCharacterStyle::Language);
}
bool KoCharacterStyle::blinking() const
{
return d->propertyBoolean(Blink);
}
void KoCharacterStyle::setBlinking(bool blink)
{
d->setProperty(KoCharacterStyle::Blink, blink);
}
bool KoCharacterStyle::hasProperty(int key) const
{
return d->stylesPrivate.contains(key);
}
static QString rotationScaleToString(KoCharacterStyle::RotationScale rotationScale)
{
QString scale = "line-height";
if (rotationScale == KoCharacterStyle::Fixed) {
scale = "fixed";
}
return scale;
}
static KoCharacterStyle::RotationScale stringToRotationScale(const QString &scale)
{
KoCharacterStyle::RotationScale rotationScale = KoCharacterStyle::LineHeight;
if (scale == "fixed") {
rotationScale = KoCharacterStyle::Fixed;
}
return rotationScale;
}
void KoCharacterStyle::setTextRotationAngle(qreal angle)
{
d->setProperty(TextRotationAngle, angle);
}
qreal KoCharacterStyle::textRotationAngle() const
{
return d->propertyDouble(TextRotationAngle);
}
void KoCharacterStyle::setTextRotationScale(RotationScale scale)
{
d->setProperty(TextRotationScale, rotationScaleToString(scale));
}
KoCharacterStyle::RotationScale KoCharacterStyle::textRotationScale() const
{
return stringToRotationScale(d->propertyString(TextRotationScale));
}
void KoCharacterStyle::setTextScale(int scale)
{
d->setProperty(TextScale, scale);
}
int KoCharacterStyle::textScale() const
{
return d->propertyInt(TextScale);
}
void KoCharacterStyle::setTextShadow(const KoShadowStyle& shadow)
{
d->setProperty(TextShadow, qVariantFromValue<KoShadowStyle>(shadow));
}
KoShadowStyle KoCharacterStyle::textShadow() const
{
if (hasProperty(TextShadow)) {
QVariant shadow = value(TextShadow);
if (shadow.canConvert<KoShadowStyle>())
return shadow.value<KoShadowStyle>();
}
return KoShadowStyle();
}
void KoCharacterStyle::setTextCombine(KoCharacterStyle::TextCombineType type)
{
d->setProperty(TextCombine, type);
}
KoCharacterStyle::TextCombineType KoCharacterStyle::textCombine() const
{
if (hasProperty(TextCombine)) {
return (KoCharacterStyle::TextCombineType) d->propertyInt(TextCombine);
}
return NoTextCombine;
}
QChar KoCharacterStyle::textCombineEndChar() const
{
if (hasProperty(TextCombineEndChar)) {
QString val = d->propertyString(TextCombineEndChar);
if (val.length() > 0)
return val.at(0);
}
return QChar();
}
void KoCharacterStyle::setTextCombineEndChar(const QChar& character)
{
d->setProperty(TextCombineEndChar, character);
}
QChar KoCharacterStyle::textCombineStartChar() const
{
if (hasProperty(TextCombineStartChar)) {
QString val = d->propertyString(TextCombineStartChar);
if (val.length() > 0)
return val.at(0);
}
return QChar();
}
void KoCharacterStyle::setTextCombineStartChar(const QChar& character)
{
d->setProperty(TextCombineStartChar, character);
}
void KoCharacterStyle::setFontRelief(KoCharacterStyle::ReliefType relief)
{
d->setProperty(FontRelief, relief);
}
KoCharacterStyle::ReliefType KoCharacterStyle::fontRelief() const
{
if (hasProperty(FontRelief))
return (KoCharacterStyle::ReliefType) d->propertyInt(FontRelief);
return KoCharacterStyle::NoRelief;
}
KoCharacterStyle::EmphasisPosition KoCharacterStyle::textEmphasizePosition() const
{
if (hasProperty(TextEmphasizePosition))
return (KoCharacterStyle::EmphasisPosition) d->propertyInt(TextEmphasizePosition);
return KoCharacterStyle::EmphasisAbove;
}
void KoCharacterStyle::setTextEmphasizePosition(KoCharacterStyle::EmphasisPosition position)
{
d->setProperty(TextEmphasizePosition, position);
}
KoCharacterStyle::EmphasisStyle KoCharacterStyle::textEmphasizeStyle() const
{
if (hasProperty(TextEmphasizeStyle))
return (KoCharacterStyle::EmphasisStyle) d->propertyInt(TextEmphasizeStyle);
return KoCharacterStyle::NoEmphasis;
}
void KoCharacterStyle::setTextEmphasizeStyle(KoCharacterStyle::EmphasisStyle emphasis)
{
d->setProperty(TextEmphasizeStyle, emphasis);
}
void KoCharacterStyle::setPercentageFontSize(qreal percent)
{
d->setProperty(KoCharacterStyle::PercentageFontSize, percent);
}
qreal KoCharacterStyle::percentageFontSize() const
{
return d->propertyDouble(KoCharacterStyle::PercentageFontSize);
}
void KoCharacterStyle::setAdditionalFontSize(qreal percent)
{
d->setProperty(KoCharacterStyle::AdditionalFontSize, percent);
}
qreal KoCharacterStyle::additionalFontSize() const
{
return d->propertyDouble(KoCharacterStyle::AdditionalFontSize);
}
void KoCharacterStyle::loadOdf(const KoXmlElement *element, KoShapeLoadingContext &scontext,
bool loadParents)
{
KoOdfLoadingContext &context = scontext.odfLoadingContext();
const QString name(element->attributeNS(KoXmlNS::style, "display-name", QString()));
if (!name.isEmpty()) {
d->name = name;
}
else {
d->name = element->attributeNS(KoXmlNS::style, "name", QString());
}
QString family = element->attributeNS(KoXmlNS::style, "family", "text");
context.styleStack().save();
if (loadParents) {
context.addStyles(element, family.toLocal8Bit().constData()); // Load all parent
} else {
context.styleStack().push(*element);
}
context.styleStack().setTypeProperties("text"); // load the style:text-properties
loadOdfProperties(scontext);
context.styleStack().restore();
}
void KoCharacterStyle::loadOdfProperties(KoShapeLoadingContext &scontext)
{
KoStyleStack &styleStack = scontext.odfLoadingContext().styleStack();
d->stylesPrivate = StylePrivate();
// The fo:color attribute specifies the foreground color of text.
const QString color(styleStack.property(KoXmlNS::fo, "color"));
if (!color.isEmpty()) {
QColor c(color);
if (c.isValid()) { // 3.10.3
setForeground(QBrush(c));
}
}
QString fontName(styleStack.property(KoXmlNS::fo, "font-family"));
if (!fontName.isEmpty()) {
// Specify whether a font has a fixed or variable width.
// These attributes are ignored if there is no corresponding fo:font-family attribute attached to the same formatting properties element.
const QString fontPitch(styleStack.property(KoXmlNS::style, "font-pitch"));
if (!fontPitch.isEmpty()) {
setFontFixedPitch(fontPitch == "fixed");
}
const QString genericFamily(styleStack.property(KoXmlNS::style, "font-family-generic"));
if (!genericFamily.isEmpty()) {
if (genericFamily == "roman")
setFontStyleHint(QFont::Serif);
else if (genericFamily == "swiss")
setFontStyleHint(QFont::SansSerif);
else if (genericFamily == "modern")
setFontStyleHint(QFont::TypeWriter);
else if (genericFamily == "decorative")
setFontStyleHint(QFont::Decorative);
else if (genericFamily == "system")
setFontStyleHint(QFont::System);
else if (genericFamily == "script") {
; // TODO: no hint available in Qt yet, we should at least store it as a property internally!
}
}
const QString fontCharset(styleStack.property(KoXmlNS::style, "font-charset"));
if (!fontCharset.isEmpty()) {
// this property is not required by Qt, since Qt auto selects the right font based on the text
// The only charset of interest to us is x-symbol - this should disable spell checking
d->setProperty(KoCharacterStyle::FontCharset, fontCharset);
}
}
const QString fontFamily(styleStack.property(KoXmlNS::style, "font-family"));
if (!fontFamily.isEmpty())
fontName = fontFamily;
if (styleStack.hasProperty(KoXmlNS::style, "font-name")) {
// This font name is a reference to a font face declaration.
KoOdfStylesReader &stylesReader = scontext.odfLoadingContext().stylesReader();
const KoXmlElement *fontFace = stylesReader.findStyle(styleStack.property(KoXmlNS::style, "font-name"));
if (fontFace != 0) {
fontName = fontFace->attributeNS(KoXmlNS::svg, "font-family", "");
KoXmlElement fontFaceElem;
forEachElement(fontFaceElem, (*fontFace)) {
if (fontFaceElem.tagName() == "font-face-src") {
KoXmlElement fontUriElem;
forEachElement(fontUriElem, fontFaceElem) {
if (fontUriElem.tagName() == "font-face-uri") {
QString filename = fontUriElem.attributeNS(KoXmlNS::xlink, "href");
KoStore *store = scontext.odfLoadingContext().store();
if (store->open(filename)) {
KoStoreDevice device(store);
QByteArray data = device.readAll();
if (device.open(QIODevice::ReadOnly)) {
QFontDatabase::addApplicationFontFromData(data);
}
}
}
}
}
}
}
}
if (!fontName.isEmpty()) {
// Hmm, the remove "'" could break it's in the middle of the fontname...
fontName = fontName.remove('\'');
// 'Thorndale' is not known outside OpenOffice so we substitute it
// with 'Times New Roman' that looks nearly the same.
if (fontName == "Thorndale")
fontName = "Times New Roman";
// 'StarSymbol' is written by OpenOffice but they actually mean
// 'OpenSymbol'.
if (fontName == "StarSymbol")
fontName = "OpenSymbol";
fontName.remove(QRegExp("\\sCE$")); // Arial CE -> Arial
setFontFamily(fontName);
}
// Specify the size of a font. The value of these attribute is either an absolute length or a percentage
if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) {
const QString fontSize(styleStack.property(KoXmlNS::fo, "font-size"));
if (!fontSize.isEmpty()) {
if (fontSize.endsWith('%')) {
setPercentageFontSize(fontSize.left(fontSize.length() - 1).toDouble());
} else {
setFontPointSize(KoUnit::parseValue(fontSize));
}
}
}
else {
const QString fontSizeRel(styleStack.property(KoXmlNS::style, "font-size-rel"));
if (!fontSizeRel.isEmpty()) {
setAdditionalFontSize(KoUnit::parseValue(fontSizeRel));
}
}
// Specify the weight of a font. The permitted values are normal, bold, and numeric values 100-900, in steps of 100. Unsupported numerical values are rounded off to the next supported value.
const QString fontWeight(styleStack.property(KoXmlNS::fo, "font-weight"));
if (!fontWeight.isEmpty()) { // 3.10.24
int boldness;
if (fontWeight == "normal")
boldness = 50;
else if (fontWeight == "bold")
boldness = 75;
else
// XSL/CSS has 100,200,300...900. Not the same scale as Qt!
// See http://www.w3.org/TR/2001/REC-xsl-20011015/slice7.html#font-weight
boldness = fontWeight.toInt() / 10;
setFontWeight(boldness);
}
// Specify whether to use normal or italic font face.
const QString fontStyle(styleStack.property(KoXmlNS::fo, "font-style" ));
if (!fontStyle.isEmpty()) { // 3.10.19
if (fontStyle == "italic" || fontStyle == "oblique") { // no difference in kotext
setFontItalic(true);
} else {
setFontItalic(false);
}
}
//TODO
#if 0
d->m_bWordByWord = styleStack.property(KoXmlNS::style, "text-underline-mode") == "skip-white-space";
// TODO style:text-line-through-mode
/*
// OO compat code, to move to OO import filter
d->m_bWordByWord = (styleStack.hasProperty( KoXmlNS::fo, "score-spaces")) // 3.10.25
&& (styleStack.property( KoXmlNS::fo, "score-spaces") == "false");
if( styleStack.hasProperty( KoXmlNS::style, "text-crossing-out" )) { // 3.10.6
QString strikeOutType = styleStack.property( KoXmlNS::style, "text-crossing-out" );
if( strikeOutType =="double-line")
m_strikeOutType = S_DOUBLE;
else if( strikeOutType =="single-line")
m_strikeOutType = S_SIMPLE;
else if( strikeOutType =="thick-line")
m_strikeOutType = S_SIMPLE_BOLD;
// not supported by Words: "slash" and "X"
// not supported by OO: stylelines (solid, dash, dot, dashdot, dashdotdot)
}
*/
#endif
// overline modes
const QString textOverlineMode(styleStack.property( KoXmlNS::style, "text-overline-mode"));
if (!textOverlineMode.isEmpty()) {
if (textOverlineMode == "skip-white-space") {
setOverlineMode(SkipWhiteSpaceLineMode);
} else if (textOverlineMode == "continuous") {
setOverlineMode(ContinuousLineMode);
}
}
// Specifies whether text is overlined, and if so, whether a single or qreal line will be used for overlining.
const QString textOverlineType(styleStack.property(KoXmlNS::style, "text-overline-type"));
const QString textOverlineStyle(styleStack.property(KoXmlNS::style, "text-overline-style"));
if (!textOverlineType.isEmpty() || !textOverlineStyle.isEmpty()) { // OASIS 14.4.28
LineStyle overlineStyle;
LineType overlineType;
importOdfLine(textOverlineType, textOverlineStyle,
overlineStyle, overlineType);
setOverlineStyle(overlineStyle);
setOverlineType(overlineType);
}
const QString textOverlineWidth(styleStack.property(KoXmlNS::style, "text-overline-width"));
if (!textOverlineWidth.isEmpty()) {
qreal overlineWidth;
LineWeight overlineWeight;
parseOdfLineWidth(textOverlineWidth, overlineWeight, overlineWidth);
setOverlineWidth(overlineWeight, overlineWidth);
}
// Specifies the color that is used to overline text. The value of this attribute is either font-color or a color. If the value is font-color, the current text color is used for overlining.
QString overLineColor = styleStack.property(KoXmlNS::style, "text-overline-color"); // OO 3.10.23, OASIS 14.4.31
if (!overLineColor.isEmpty() && overLineColor != "font-color") {
setOverlineColor(QColor(overLineColor));
} else if (overLineColor == "font-color") {
setOverlineColor(QColor());
}
// underline modes
const QString textUndelineMode(styleStack.property( KoXmlNS::style, "text-underline-mode"));
if (!textUndelineMode.isEmpty()) {
if (textUndelineMode == "skip-white-space") {
setUnderlineMode(SkipWhiteSpaceLineMode);
} else if (textUndelineMode == "continuous") {
setUnderlineMode(ContinuousLineMode);
}
}
// Specifies whether text is underlined, and if so, whether a single or qreal line will be used for underlining.
const QString textUnderlineType(styleStack.property(KoXmlNS::style, "text-underline-type"));
const QString textUnderlineStyle(styleStack.property(KoXmlNS::style, "text-underline-style"));
if (!textUnderlineType.isEmpty() || !textUnderlineStyle.isEmpty()) { // OASIS 14.4.28
LineStyle underlineStyle;
LineType underlineType;
importOdfLine(textUnderlineType, textUnderlineStyle,
underlineStyle, underlineType);
setUnderlineStyle(underlineStyle);
setUnderlineType(underlineType);
}
const QString textUnderlineWidth(styleStack.property(KoXmlNS::style, "text-underline-width"));
if (!textUnderlineWidth.isEmpty()) {
qreal underlineWidth;
LineWeight underlineWeight;
parseOdfLineWidth(textUnderlineWidth, underlineWeight, underlineWidth);
setUnderlineWidth(underlineWeight, underlineWidth);
}
// Specifies the color that is used to underline text. The value of this attribute is either font-color or a color. If the value is font-color, the current text color is used for underlining.
QString underLineColor = styleStack.property(KoXmlNS::style, "text-underline-color"); // OO 3.10.23, OASIS 14.4.31
if (!underLineColor.isEmpty() && underLineColor != "font-color") {
setUnderlineColor(QColor(underLineColor));
} else if (underLineColor == "font-color") {
setUnderlineColor(QColor());
}
const QString textLineThroughType(styleStack.property(KoXmlNS::style, "text-line-through-type"));
const QString textLineThroughStyle(styleStack.property(KoXmlNS::style, "text-line-through-style"));
if (!textLineThroughType.isEmpty() || !textLineThroughStyle.isEmpty()) { // OASIS 14.4.7
KoCharacterStyle::LineStyle throughStyle;
LineType throughType;
importOdfLine(textLineThroughType,textLineThroughStyle,
throughStyle, throughType);
setStrikeOutStyle(throughStyle);
setStrikeOutType(throughType);
const QString textLineThroughText(styleStack.property(KoXmlNS::style, "text-line-through-text"));
if (!textLineThroughText.isEmpty()) {
setStrikeOutText(textLineThroughText);
}
}
const QString textLineThroughWidth(styleStack.property(KoXmlNS::style, "text-line-through-width"));
if (!textLineThroughWidth.isEmpty()) {
qreal throughWidth;
LineWeight throughWeight;
parseOdfLineWidth(textLineThroughWidth, throughWeight, throughWidth);
setStrikeOutWidth(throughWeight, throughWidth);
}
const QString lineThroughColor(styleStack.property(KoXmlNS::style, "text-line-through-color")); // OO 3.10.23, OASIS 14.4.31
if (!lineThroughColor.isEmpty() && lineThroughColor != "font-color") {
setStrikeOutColor(QColor(lineThroughColor));
}
const QString lineThroughMode(styleStack.property(KoXmlNS::style, "text-line-through-mode"));
if (lineThroughMode == "continuous") {
setStrikeOutMode(ContinuousLineMode);
}
else if (lineThroughMode == "skip-white-space") {
setStrikeOutMode(SkipWhiteSpaceLineMode);
}
const QString textPosition(styleStack.property(KoXmlNS::style, "text-position"));
if (!textPosition.isEmpty()) { // OO 3.10.7
if (textPosition.startsWith("super"))
setVerticalAlignment(QTextCharFormat::AlignSuperScript);
else if (textPosition.startsWith("sub"))
setVerticalAlignment(QTextCharFormat::AlignSubScript);
else {
QRegExp re("(-?[\\d.]+)%.*");
if (re.exactMatch(textPosition)) {
int percent = re.capturedTexts()[1].toInt();
if (percent > 0)
setVerticalAlignment(QTextCharFormat::AlignSuperScript);
else if (percent < 0)
setVerticalAlignment(QTextCharFormat::AlignSubScript);
else // set explicit to overwrite inherited text-position's
setVerticalAlignment(QTextCharFormat::AlignNormal);
}
}
}
// The fo:font-variant attribute provides the option to display text as small capitalized letters.
const QString textVariant(styleStack.property(KoXmlNS::fo, "font-variant"));
if (!textVariant.isEmpty()) {
if (textVariant == "small-caps")
setFontCapitalization(QFont::SmallCaps);
else if (textVariant == "normal")
setFontCapitalization(QFont::MixedCase);
}
// The fo:text-transform attribute specifies text transformations to uppercase, lowercase, and capitalization.
else {
const QString textTransform(styleStack.property(KoXmlNS::fo, "text-transform"));
if (!textTransform.isEmpty()) {
if (textTransform == "uppercase")
setFontCapitalization(QFont::AllUppercase);
else if (textTransform == "lowercase")
setFontCapitalization(QFont::AllLowercase);
else if (textTransform == "capitalize")
setFontCapitalization(QFont::Capitalize);
else if (textTransform == "none")
setFontCapitalization(QFont::MixedCase);
}
}
const QString foLanguage(styleStack.property(KoXmlNS::fo, "language"));
if (!foLanguage.isEmpty()) {
setLanguage(foLanguage);
}
const QString foCountry(styleStack.property(KoXmlNS::fo, "country"));
if (!foCountry.isEmpty()) {
setCountry(foCountry);
}
// The fo:background-color attribute specifies the background color of a paragraph.
const QString bgcolor(styleStack.property(KoXmlNS::fo, "background-color"));
if (!bgcolor.isEmpty()) {
QBrush brush = background();
if (bgcolor == "transparent")
brush.setStyle(Qt::NoBrush);
else {
if (brush.style() == Qt::NoBrush)
brush.setStyle(Qt::SolidPattern);
brush.setColor(bgcolor); // #rrggbb format
}
setBackground(brush);
}
// The style:use-window-font-color attribute specifies whether or not the window foreground color should be as used as the foreground color for a light background color and white for a dark background color.
const QString useWindowFont(styleStack.property(KoXmlNS::style, "use-window-font-color"));
if (!useWindowFont.isEmpty()) {
setFontAutoColor(useWindowFont == "true");
}
const QString letterKerning(styleStack.property( KoXmlNS::style, "letter-kerning"));
if (!letterKerning.isEmpty()) {
setFontKerning(letterKerning == "true");
}
const QString letterSpacing(styleStack.property(KoXmlNS::fo, "letter-spacing"));
if ((!letterSpacing.isEmpty()) && (letterSpacing != "normal")) {
qreal space = KoUnit::parseValue(letterSpacing);
setFontLetterSpacing(space);
}
const QString textOutline(styleStack.property(KoXmlNS::style, "text-outline"));
if (!textOutline.isEmpty()) {
if (textOutline == "true") {
setTextOutline(QPen((foreground().style() != Qt::NoBrush)?foreground():QBrush(Qt::black) , 0));
setForeground(Qt::transparent);
} else {
setTextOutline(QPen(Qt::NoPen));
}
}
const QString textRotationAngle(styleStack.property(KoXmlNS::style, "text-rotation-angle"));
if (!textRotationAngle.isEmpty()) {
setTextRotationAngle(KoUnit::parseAngle(textRotationAngle));
}
const QString textRotationScale(styleStack.property(KoXmlNS::style, "text-rotation-scale"));
if (!textRotationScale.isEmpty()) {
setTextRotationScale(stringToRotationScale(textRotationScale));
}
const QString textScale(styleStack.property(KoXmlNS::style, "text-scale"));
if (!textScale.isEmpty()) {
const int scale = (textScale.endsWith('%') ? textScale.left(textScale.length()-1) : textScale).toInt();
setTextScale(scale);
}
const QString textShadow(styleStack.property(KoXmlNS::fo, "text-shadow"));
if (!textShadow.isEmpty()) {
KoShadowStyle shadow;
if (shadow.loadOdf(textShadow))
setTextShadow(shadow);
}
const QString textCombine(styleStack.property(KoXmlNS::style, "text-combine"));
if (!textCombine.isEmpty()) {
if (textCombine == "letters")
setTextCombine(TextCombineLetters);
else if (textCombine == "lines")
setTextCombine(TextCombineLines);
else if (textCombine == "none")
setTextCombine(NoTextCombine);
}
const QString textCombineEndChar(styleStack.property(KoXmlNS::style, "text-combine-end-char"));
if (!textCombineEndChar.isEmpty()) {
setTextCombineEndChar(textCombineEndChar.at(0));
}
const QString textCombineStartChar(styleStack.property(KoXmlNS::style, "text-combine-start-char"));
if (!textCombineStartChar.isEmpty()) {
setTextCombineStartChar(textCombineStartChar.at(0));
}
const QString fontRelief(styleStack.property(KoXmlNS::style, "font-relief"));
if (!fontRelief.isEmpty()) {
if (fontRelief == "none")
setFontRelief(KoCharacterStyle::NoRelief);
else if (fontRelief == "embossed")
setFontRelief(KoCharacterStyle::Embossed);
else if (fontRelief == "engraved")
setFontRelief(KoCharacterStyle::Engraved);
}
const QString fontEmphasize(styleStack.property(KoXmlNS::style, "text-emphasize"));
if (!fontEmphasize.isEmpty()) {
QString style, position;
QStringList parts = fontEmphasize.split(' ');
style = parts[0];
if (parts.length() > 1)
position = parts[1];
if (style == "none") {
setTextEmphasizeStyle(NoEmphasis);
} else if (style == "accent") {
setTextEmphasizeStyle(AccentEmphasis);
} else if (style == "circle") {
setTextEmphasizeStyle(CircleEmphasis);
} else if (style == "disc") {
setTextEmphasizeStyle(DiscEmphasis);
} else if (style == "dot") {
setTextEmphasizeStyle(DotEmphasis);
}
if (position == "below") {
setTextEmphasizePosition(EmphasisBelow);
} else if (position == "above") {
setTextEmphasizePosition(EmphasisAbove);
}
}
if (styleStack.hasProperty(KoXmlNS::fo, "hyphenate"))
setHasHyphenation(styleStack.property(KoXmlNS::fo, "hyphenate") == "true");
if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-remain-char-count")) {
bool ok = false;
int count = styleStack.property(KoXmlNS::fo, "hyphenation-remain-char-count").toInt(&ok);
if (ok)
setHyphenationRemainCharCount(count);
}
if (styleStack.hasProperty(KoXmlNS::fo, "hyphenation-push-char-count")) {
bool ok = false;
int count = styleStack.property(KoXmlNS::fo, "hyphenation-push-char-count").toInt(&ok);
if (ok)
setHyphenationPushCharCount(count);
}
if (styleStack.hasProperty(KoXmlNS::style, "text-blinking")) {
setBlinking(styleStack.property(KoXmlNS::style, "text-blinking") == "true");
}
//TODO
#if 0
/*
Missing properties:
style:font-style-name, 3.10.11 - can be ignored, says DV, the other ways to specify a font are more precise
fo:letter-spacing, 3.10.16 - not implemented in kotext
style:text-relief, 3.10.20 - not implemented in kotext
style:text-blinking, 3.10.27 - not implemented in kotext IIRC
style:text-combine, 3.10.29/30 - not implemented, see http://www.w3.org/TR/WD-i18n-format/
style:text-emphasis, 3.10.31 - not implemented in kotext
style:text-scale, 3.10.33 - not implemented in kotext
style:text-rotation-angle, 3.10.34 - not implemented in kotext (kpr rotates whole objects)
style:text-rotation-scale, 3.10.35 - not implemented in kotext (kpr rotates whole objects)
style:punctuation-wrap, 3.10.36 - not implemented in kotext
*/
d->m_underLineWidth = 1.0;
generateKey();
addRef();
#endif
}
bool KoCharacterStyle::operator==(const KoCharacterStyle &other) const
{
return compareCharacterProperties(other);
}
bool KoCharacterStyle::operator!=(const KoCharacterStyle &other) const
{
return !compareCharacterProperties(other);
}
bool KoCharacterStyle::compareCharacterProperties(const KoCharacterStyle &other) const
{
return other.d->stylesPrivate == d->stylesPrivate;
}
void KoCharacterStyle::removeDuplicates(const KoCharacterStyle &other)
{
// In case the current style doesn't have the flag UseWindowFontColor set but the other has it set and they use the same color
// remove duplicates will remove the color. However to make it work correctly we need to store the color with the style so it
// will be loaded again. We don't store a use-window-font-color="false" as that is not compatible to the way OO/LO does work.
// So save the color and restore it after the remove duplicates
QBrush brush;
if (other.d->propertyBoolean(KoCharacterStyle::UseWindowFontColor) && !d->propertyBoolean(KoCharacterStyle::UseWindowFontColor)) {
brush = foreground();
}
// this properties should need to be kept if there is a font family defined as these are only evaluated if there is also a font family
int keepProperties[] = { QTextFormat::FontStyleHint, QTextFormat::FontFixedPitch, KoCharacterStyle::FontCharset };
QMap<int, QVariant> keep;
for (unsigned int i = 0; i < sizeof(keepProperties)/sizeof(*keepProperties); ++i) {
if (hasProperty(keepProperties[i])) {
keep.insert(keepProperties[i], value(keepProperties[i]));
}
}
this->d->stylesPrivate.removeDuplicates(other.d->stylesPrivate);
if (brush.style() != Qt::NoBrush) {
setForeground(brush);
}
// in case the char style has any of the following properties it also needs to have the fontFamily as otherwise
// these values will be ignored when loading according to the odf spec
if (!hasProperty(QTextFormat::FontFamily)) {
if (hasProperty(QTextFormat::FontStyleHint) || hasProperty(QTextFormat::FontFixedPitch) || hasProperty(KoCharacterStyle::FontCharset)) {
QString fontFamily = other.fontFamily();
if (!fontFamily.isEmpty()) {
setFontFamily(fontFamily);
}
}
}
else {
for (QMap<int, QVariant>::const_iterator it(keep.constBegin()); it != keep.constEnd(); ++it) {
this->d->stylesPrivate.add(it.key(), it.value());
}
}
}
void KoCharacterStyle::removeDuplicates(const QTextCharFormat &otherFormat)
{
KoCharacterStyle other(otherFormat);
removeDuplicates(other);
}
void KoCharacterStyle::remove(int key)
{
d->stylesPrivate.remove(key);
}
bool KoCharacterStyle::isEmpty() const
{
return d->stylesPrivate.isEmpty();
}
void KoCharacterStyle::saveOdf(KoGenStyle &style) const
{
if (!d->name.isEmpty() && !style.isDefaultStyle()) {
style.addAttribute("style:display-name", d->name);
}
QList<int> keys = d->stylesPrivate.keys();
Q_FOREACH (int key, keys) {
if (key == QTextFormat::FontWeight) {
bool ok = false;
int boldness = d->stylesPrivate.value(key).toInt(&ok);
if (ok) {
if (boldness == QFont::Normal) {
style.addProperty("fo:font-weight", "normal", KoGenStyle::TextType);
} else if (boldness == QFont::Bold) {
style.addProperty("fo:font-weight", "bold", KoGenStyle::TextType);
} else {
// Remember : Qt and CSS/XSL doesn't have the same scale. Its 100-900 instead of Qts 0-100
style.addProperty("fo:font-weight", qBound(10, boldness, 90) * 10, KoGenStyle::TextType);
}
}
} else if (key == QTextFormat::FontItalic) {
if (d->stylesPrivate.value(key).toBool()) {
style.addProperty("fo:font-style", "italic", KoGenStyle::TextType);
} else {
style.addProperty("fo:font-style", "normal", KoGenStyle::TextType);
}
} else if (key == QTextFormat::FontFamily) {
QString fontFamily = d->stylesPrivate.value(key).toString();
style.addProperty("fo:font-family", fontFamily, KoGenStyle::TextType);
} else if (key == QTextFormat::FontFixedPitch) {
bool fixedPitch = d->stylesPrivate.value(key).toBool();
style.addProperty("style:font-pitch", fixedPitch ? "fixed" : "variable", KoGenStyle::TextType);
// if this property is saved we also need to save the fo:font-family attribute as otherwise it will be ignored on loading as defined in the spec
style.addProperty("fo:font-family", fontFamily(), KoGenStyle::TextType);
} else if (key == QTextFormat::FontStyleHint) {
bool ok = false;
int styleHint = d->stylesPrivate.value(key).toInt(&ok);
if (ok) {
QString generic = exportOdfFontStyleHint((QFont::StyleHint) styleHint);
if (!generic.isEmpty()) {
style.addProperty("style:font-family-generic", generic, KoGenStyle::TextType);
}
// if this property is saved we also need to save the fo:font-family attribute as otherwise it will be ignored on loading as defined in the spec
style.addProperty("fo:font-family", fontFamily(), KoGenStyle::TextType);
}
} else if (key == QTextFormat::FontKerning) {
style.addProperty("style:letter-kerning", fontKerning() ? "true" : "false", KoGenStyle::TextType);
} else if (key == QTextFormat::FontCapitalization) {
switch (fontCapitalization()) {
case QFont::SmallCaps:
style.addProperty("fo:font-variant", "small-caps", KoGenStyle::TextType);
break;
case QFont::MixedCase:
style.addProperty("fo:font-variant", "normal", KoGenStyle::TextType);
style.addProperty("fo:text-transform", "none", KoGenStyle::TextType);
break;
case QFont::AllUppercase:
style.addProperty("fo:text-transform", "uppercase", KoGenStyle::TextType);
break;
case QFont::AllLowercase:
style.addProperty("fo:text-transform", "lowercase", KoGenStyle::TextType);
break;
case QFont::Capitalize:
style.addProperty("fo:text-transform", "capitalize", KoGenStyle::TextType);
break;
}
} else if (key == OverlineStyle) {
bool ok = false;
int styleId = d->stylesPrivate.value(key).toInt(&ok);
if (ok) {
style.addProperty("style:text-overline-style", exportOdfLineStyle((KoCharacterStyle::LineStyle) styleId), KoGenStyle::TextType);
}
} else if (key == OverlineType) {
bool ok = false;
int type = d->stylesPrivate.value(key).toInt(&ok);
if (ok) {
style.addProperty("style:text-overline-type", exportOdfLineType((KoCharacterStyle::LineType) type), KoGenStyle::TextType);
}
} else if (key == OverlineColor) {
QColor color = d->stylesPrivate.value(key).value<QColor>();
if (color.isValid())
style.addProperty("style:text-overline-color", color.name(), KoGenStyle::TextType);
else
style.addProperty("style:text-overline-color", "font-color", KoGenStyle::TextType);
} else if (key == OverlineMode) {
bool ok = false;
int mode = d->stylesPrivate.value(key).toInt(&ok);
if (ok) {
style.addProperty("style:text-overline-mode", exportOdfLineMode((KoCharacterStyle::LineMode) mode), KoGenStyle::TextType);
}
} else if (key == OverlineWidth) {
KoCharacterStyle::LineWeight weight;
qreal width;
overlineWidth(weight, width);
style.addProperty("style:text-overline-width", exportOdfLineWidth(weight, width), KoGenStyle::TextType);
} else if (key == UnderlineStyle) {
bool ok = false;
int styleId = d->stylesPrivate.value(key).toInt(&ok);
if (ok)
style.addProperty("style:text-underline-style", exportOdfLineStyle((KoCharacterStyle::LineStyle) styleId), KoGenStyle::TextType);
} else if (key == UnderlineType) {
bool ok = false;
int type = d->stylesPrivate.value(key).toInt(&ok);
if (ok)
style.addProperty("style:text-underline-type", exportOdfLineType((KoCharacterStyle::LineType) type), KoGenStyle::TextType);
} else if (key == QTextFormat::TextUnderlineColor) {
QColor color = d->stylesPrivate.value(key).value<QColor>();
if (color.isValid())
style.addProperty("style:text-underline-color", color.name(), KoGenStyle::TextType);
else
style.addProperty("style:text-underline-color", "font-color", KoGenStyle::TextType);
} else if (key == UnderlineMode) {
bool ok = false;
int mode = d->stylesPrivate.value(key).toInt(&ok);
if (ok)
style.addProperty("style:text-underline-mode", exportOdfLineMode((KoCharacterStyle::LineMode) mode), KoGenStyle::TextType);
} else if (key == UnderlineWidth) {
KoCharacterStyle::LineWeight weight;
qreal width;
underlineWidth(weight, width);
style.addProperty("style:text-underline-width", exportOdfLineWidth(weight, width), KoGenStyle::TextType);
} else if (key == StrikeOutStyle) {
bool ok = false;
int styleId = d->stylesPrivate.value(key).toInt(&ok);
if (ok)
style.addProperty("style:text-line-through-style", exportOdfLineStyle((KoCharacterStyle::LineStyle) styleId), KoGenStyle::TextType);
} else if (key == StrikeOutType) {
bool ok = false;
int type = d->stylesPrivate.value(key).toInt(&ok);
if (ok)
style.addProperty("style:text-line-through-type", exportOdfLineType((KoCharacterStyle::LineType) type), KoGenStyle::TextType);
} else if (key == StrikeOutText) {
style.addProperty("style:text-line-through-text", d->stylesPrivate.value(key).toString(), KoGenStyle::TextType);
} else if (key == StrikeOutColor) {
QColor color = d->stylesPrivate.value(key).value<QColor>();
if (color.isValid())
style.addProperty("style:text-line-through-color", color.name(), KoGenStyle::TextType);
} else if (key == StrikeOutMode) {
bool ok = false;
int mode = d->stylesPrivate.value(key).toInt(&ok);
if (ok)
style.addProperty("style:text-line-through-mode", exportOdfLineMode((KoCharacterStyle::LineMode) mode), KoGenStyle::TextType);
} else if (key == StrikeOutWidth) {
KoCharacterStyle::LineWeight weight;
qreal width;
strikeOutWidth(weight, width);
style.addProperty("style:text-line-through-width", exportOdfLineWidth(weight, width), KoGenStyle::TextType);
} else if (key == QTextFormat::BackgroundBrush) {
QBrush brush = d->stylesPrivate.value(key).value<QBrush>();
if (brush.style() == Qt::NoBrush)
style.addProperty("fo:background-color", "transparent", KoGenStyle::TextType);
else
style.addProperty("fo:background-color", brush.color().name(), KoGenStyle::TextType);
} else if (key == QTextFormat::ForegroundBrush) {
QBrush brush = d->stylesPrivate.value(key).value<QBrush>();
if (brush.style() != Qt::NoBrush) {
style.addProperty("fo:color", brush.color().name(), KoGenStyle::TextType);
}
} else if (key == KoCharacterStyle::UseWindowFontColor) {
bool use = d->stylesPrivate.value(key).toBool();
style.addProperty("style:use-window-font-color", use ? "true" : "false", KoGenStyle::TextType);
} else if (key == QTextFormat::TextVerticalAlignment) {
if (verticalAlignment() == QTextCharFormat::AlignSuperScript)
style.addProperty("style:text-position", "super", KoGenStyle::TextType);
else if (verticalAlignment() == QTextCharFormat::AlignSubScript)
style.addProperty("style:text-position", "sub", KoGenStyle::TextType);
else if (d->stylesPrivate.contains(QTextFormat::TextVerticalAlignment)) // no superscript or subscript
style.addProperty("style:text-position", "0% 100%", KoGenStyle::TextType);
} else if (key == QTextFormat::FontPointSize) {
// when there is percentageFontSize!=100% property ignore the fontSize property and store the percentage property
if ( (!hasProperty(KoCharacterStyle::PercentageFontSize)) || (percentageFontSize()==100))
style.addPropertyPt("fo:font-size", fontPointSize(), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::PercentageFontSize) {
if(percentageFontSize()!=100) {
style.addProperty("fo:font-size", QString::number(percentageFontSize()) + '%', KoGenStyle::TextType);
}
} else if (key == KoCharacterStyle::Country) {
style.addProperty("fo:country", d->stylesPrivate.value(KoCharacterStyle::Country).toString(), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::Language) {
style.addProperty("fo:language", d->stylesPrivate.value(KoCharacterStyle::Language).toString(), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::FontLetterSpacing) {
qreal space = fontLetterSpacing();
style.addPropertyPt("fo:letter-spacing", space, KoGenStyle::TextType);
} else if (key == QTextFormat::TextOutline) {
QPen outline = textOutline();
style.addProperty("style:text-outline", outline.style() == Qt::NoPen ? "false" : "true", KoGenStyle::TextType);
} else if (key == KoCharacterStyle::FontCharset) {
style.addProperty("style:font-charset", d->stylesPrivate.value(KoCharacterStyle::FontCharset).toString(), KoGenStyle::TextType);
// if this property is saved we also need to save the fo:font-family attribute as otherwise it will be ignored on loading as defined in the spec
style.addProperty("fo:font-family", fontFamily(), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::TextRotationAngle) {
style.addProperty("style:text-rotation-angle", QString::number(textRotationAngle()), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::TextRotationScale) {
RotationScale scale = textRotationScale();
style.addProperty("style:text-rotation-scale", rotationScaleToString(scale), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::TextScale) {
int scale = textScale();
style.addProperty("style:text-scale", QString::number(scale) + '%', KoGenStyle::TextType);
} else if (key == KoCharacterStyle::TextShadow) {
KoShadowStyle shadow = textShadow();
style.addProperty("fo:text-shadow", shadow.saveOdf(), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::TextCombine) {
KoCharacterStyle::TextCombineType textCombineType = textCombine();
switch (textCombineType)
{
case KoCharacterStyle::NoTextCombine:
style.addProperty("style:text-combine", "none", KoGenStyle::TextType);
break;
case KoCharacterStyle::TextCombineLetters:
style.addProperty("style:text-combine", "letters", KoGenStyle::TextType);
break;
case KoCharacterStyle::TextCombineLines:
style.addProperty("style:text-combine", "lines", KoGenStyle::TextType);
break;
}
} else if (key == KoCharacterStyle::TextCombineEndChar) {
style.addProperty("style:text-combine-end-char", textCombineEndChar(), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::TextCombineStartChar) {
style.addProperty("style:text-combine-start-char", textCombineStartChar(), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::FontRelief) {
KoCharacterStyle::ReliefType relief = fontRelief();
switch (relief)
{
case KoCharacterStyle::NoRelief:
style.addProperty("style:font-relief", "none", KoGenStyle::TextType);
break;
case KoCharacterStyle::Embossed:
style.addProperty("style:font-relief", "embossed", KoGenStyle::TextType);
break;
case KoCharacterStyle::Engraved:
style.addProperty("style:font-relief", "engraved", KoGenStyle::TextType);
break;
}
} else if (key == KoCharacterStyle::TextEmphasizeStyle) {
KoCharacterStyle::EmphasisStyle emphasisStyle = textEmphasizeStyle();
KoCharacterStyle::EmphasisPosition position = textEmphasizePosition();
QString odfEmphasis;
switch (emphasisStyle)
{
case KoCharacterStyle::NoEmphasis:
odfEmphasis = "none";
break;
case KoCharacterStyle::AccentEmphasis:
odfEmphasis = "accent";
break;
case KoCharacterStyle::CircleEmphasis:
odfEmphasis = "circle";
break;
case KoCharacterStyle::DiscEmphasis:
odfEmphasis = "disc";
break;
case KoCharacterStyle::DotEmphasis:
odfEmphasis = "dot";
break;
}
if (hasProperty(KoCharacterStyle::TextEmphasizePosition)) {
if (position == KoCharacterStyle::EmphasisAbove)
odfEmphasis += " above";
else
odfEmphasis += " below";
}
style.addProperty("style:text-emphasize", odfEmphasis, KoGenStyle::TextType);
} else if (key == KoCharacterStyle::HasHyphenation) {
if (hasHyphenation())
style.addProperty("fo:hyphenate", "true", KoGenStyle::TextType);
else
style.addProperty("fo:hyphenate", "false", KoGenStyle::TextType);
} else if (key == KoCharacterStyle::HyphenationPushCharCount) {
style.addProperty("fo:hyphenation-push-char-count", hyphenationPushCharCount(), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::HyphenationRemainCharCount) {
style.addProperty("fo:hyphenation-remain-char-count", hyphenationRemainCharCount(), KoGenStyle::TextType);
} else if (key == KoCharacterStyle::Blink) {
style.addProperty("style:text-blinking", blinking(), KoGenStyle::TextType);
}
}
//TODO: font name and family
}
QVariant KoCharacterStyle::value(int key) const
{
QVariant variant = d->stylesPrivate.value(key);
if (variant.isNull()) {
if (d->parentStyle)
variant = d->parentStyle->value(key);
else if (d->defaultStyle)
variant = d->defaultStyle->value(key);
}
return variant;
}
void KoCharacterStyle::removeHardCodedDefaults()
{
d->hardCodedDefaultStyle.clearAll();
}
diff --git a/plugins/flake/textshape/textlayout/FloatingAnchorStrategy.cpp b/plugins/flake/textshape/textlayout/FloatingAnchorStrategy.cpp
index d75ba231e1..7fcda80d02 100644
--- a/plugins/flake/textshape/textlayout/FloatingAnchorStrategy.cpp
+++ b/plugins/flake/textshape/textlayout/FloatingAnchorStrategy.cpp
@@ -1,468 +1,468 @@
/* This file is part of the KDE project
* Copyright (C) 2007, 2009, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2011 Matus Hanzes <matus.hanzes@ixonos.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 "FloatingAnchorStrategy.h"
#include "KoTextDocumentLayout.h"
#include "KoTextLayoutObstruction.h"
#include <KoShapeContainer.h>
#include <KoTextShapeData.h>
#include <KoTextLayoutRootArea.h>
#include <KoAnchorTextRange.h>
#include <TextLayoutDebug.h>
#include <QTextLayout>
#include <QTextBlock>
FloatingAnchorStrategy::FloatingAnchorStrategy(KoAnchorTextRange *anchorRange, KoTextLayoutRootArea *rootArea)
: AnchorStrategy(anchorRange->anchor(), rootArea)
, m_obstruction(new KoTextLayoutObstruction(anchorRange->anchor()->shape(), QTransform()))
, m_anchorRange(anchorRange)
{
}
FloatingAnchorStrategy::~FloatingAnchorStrategy()
{
}
void FloatingAnchorStrategy::updateObstruction(qreal documentOffset)
{
KoTextDocumentLayout *layout = dynamic_cast<KoTextDocumentLayout *>(m_anchorRange->document()->documentLayout());
- QTransform matrix = m_anchor->shape()->absoluteTransformation(0);
- matrix = matrix * m_anchor->shape()->parent()->absoluteTransformation(0).inverted();
+ QTransform matrix = m_anchor->shape()->absoluteTransformation();
+ matrix = matrix * m_anchor->shape()->parent()->absoluteTransformation().inverted();
matrix.translate(0, documentOffset);
m_obstruction->changeMatrix(matrix);
layout->registerAnchoredObstruction(m_obstruction);
}
//should return true while we are still moving around
bool FloatingAnchorStrategy::moveSubject()
{
if (!m_anchor->shape()->parent()) {
return false; // let's fake we moved to force another relayout
}
// get the page data
KoTextShapeData *data = qobject_cast<KoTextShapeData*>(m_anchor->shape()->parent()->userData());
if (!data) {
return false; // let's fake we moved to force another relayout
}
QTextBlock block = m_anchorRange->document()->findBlock(m_anchorRange->position());
QTextLayout *layout = block.layout();
// there should be always at least one line
if (layout->lineCount() == 0) {
return false; // let's fake we moved to force another relayout
}
// The bounding rect of the textshape in document coords
QRectF containerBoundingRect = m_anchor->shape()->parent()->boundingRect();
// The (to be calculated) reference rect for anchoring in document coords
QRectF anchorBoundingRect;
// This is in coords relative to texshape
QPointF newPosition;
QPointF offset;
if (m_anchor->horizontalPos() == KoShapeAnchor::HFromLeft
|| m_anchor->horizontalPos() == KoShapeAnchor::HFromInside) {
offset.setX(m_anchor->offset().x());
}
if (m_anchor->verticalPos() == KoShapeAnchor::VFromTop) {
offset.setY(m_anchor->offset().y());
}
// set anchor bounding rectangle horizontal position and size
if (!countHorizontalRel(anchorBoundingRect, containerBoundingRect, block, layout)) {
return false; // let's fake we moved to force another relayout
}
// set anchor bounding rectangle vertical position
if (!countVerticalRel(anchorBoundingRect, containerBoundingRect, data, block, layout)) {
return false; // let's fake we moved to force another relayout
}
// Set shape horizontal alignment inside anchor bounding rectangle
countHorizontalPos(newPosition, anchorBoundingRect);
// Set shape vertical alignment inside anchor bounding rectangle
countVerticalPos(newPosition, anchorBoundingRect);
newPosition += offset;
//check the border of page and move the shape back to have it visible
checkPageBorder(newPosition);
newPosition -= containerBoundingRect.topLeft();
//check the border of layout environment and move the shape back to have it within
if (m_anchor->flowWithText()) {
checkLayoutEnvironment(newPosition, data);
}
checkStacking(newPosition);
if (newPosition == m_anchor->shape()->position()) {
if (m_anchor->shape()->textRunAroundSide() != KoShape::RunThrough) {
updateObstruction(data->documentOffset());
}
return true;
}
// set the shape to the proper position based on the data
m_anchor->shape()->update();
m_anchor->shape()->setPosition(newPosition);
m_anchor->shape()->update();
if (m_anchor->shape()->textRunAroundSide() != KoShape::RunThrough) {
updateObstruction(data->documentOffset());
}
return true;
}
bool FloatingAnchorStrategy::countHorizontalRel(QRectF &anchorBoundingRect, const QRectF &containerBoundingRect, QTextBlock &block, QTextLayout *layout)
{
switch (m_anchor->horizontalRel()) {
case KoShapeAnchor::HPage:
anchorBoundingRect.setX(pageRect().x());
anchorBoundingRect.setWidth(pageRect().width());
break;
case KoShapeAnchor::HFrameContent:
case KoShapeAnchor::HFrame:
anchorBoundingRect.setX(containerBoundingRect.x());
anchorBoundingRect.setWidth(containerBoundingRect.width());
break;
case KoShapeAnchor::HPageContent:
anchorBoundingRect.setX(pageContentRect().x());
anchorBoundingRect.setWidth(pageContentRect().width());
break;
case KoShapeAnchor::HParagraph:
anchorBoundingRect.setX(paragraphRect().x() + containerBoundingRect.x());
anchorBoundingRect.setWidth(paragraphRect().width());
break;
case KoShapeAnchor::HParagraphContent:
anchorBoundingRect.setX(paragraphContentRect().x() + containerBoundingRect.x());
anchorBoundingRect.setWidth(paragraphContentRect().width());
break;
case KoShapeAnchor::HChar: {
QTextLine tl = layout->lineForTextPosition(m_anchorRange->position() - block.position());
if (!tl.isValid())
return false; // lets go for a second round.
anchorBoundingRect.setX(tl.cursorToX(m_anchorRange->position() - block.position()) + containerBoundingRect.x());
anchorBoundingRect.setWidth(0.1); // just some small value
break;
}
case KoShapeAnchor::HPageStartMargin: {
int horizontalPos = m_anchor->horizontalPos();
// if verticalRel is HFromInside or HInside or HOutside and the page number is even,
// than set anchorBoundingRect to HPageEndMargin area
if ((pageNumber()%2 == 0) && (horizontalPos == KoShapeAnchor::HFromInside ||
horizontalPos == KoShapeAnchor::HInside || horizontalPos == KoShapeAnchor::HOutside)) {
anchorBoundingRect.setX(containerBoundingRect.x() + containerBoundingRect.width());
anchorBoundingRect.setWidth(pageRect().width() - anchorBoundingRect.x());
} else {
anchorBoundingRect.setX(pageRect().x());
anchorBoundingRect.setWidth(containerBoundingRect.x());
}
break;
}
case KoShapeAnchor::HPageEndMargin:
{
int horizontalPos = m_anchor->horizontalPos();
// if verticalRel is HFromInside or HInside or HOutside and the page number is even,
// than set anchorBoundingRect to HPageStartMargin area
if ((pageNumber()%2 == 0) && (horizontalPos == KoShapeAnchor::HFromInside ||
horizontalPos == KoShapeAnchor::HInside || horizontalPos == KoShapeAnchor::HOutside)) {
anchorBoundingRect.setX(pageRect().x());
anchorBoundingRect.setWidth(containerBoundingRect.x());
} else {
anchorBoundingRect.setX(containerBoundingRect.x() + containerBoundingRect.width());
anchorBoundingRect.setWidth(pageRect().width() - anchorBoundingRect.x());
}
break;
}
case KoShapeAnchor::HParagraphStartMargin:
{
int horizontalPos = m_anchor->horizontalPos();
// if verticalRel is HFromInside or HInside or HOutside and the page number is even,
// than set anchorBoundingRect to HParagraphEndMargin area
if ((pageNumber()%2 == 0) && (horizontalPos == KoShapeAnchor::HFromInside ||
horizontalPos == KoShapeAnchor::HInside || horizontalPos == KoShapeAnchor::HOutside)) {
//FIXME anchorBoundingRect.setX(state->x() + containerBoundingRect.x() + state->width());
anchorBoundingRect.setWidth(containerBoundingRect.x() + containerBoundingRect.width() - anchorBoundingRect.x());
} else {
anchorBoundingRect.setX(containerBoundingRect.x());
//FIXME anchorBoundingRect.setWidth(state->x());
}
break;
}
case KoShapeAnchor::HParagraphEndMargin:
{
int horizontalPos = m_anchor->horizontalPos();
// if verticalRel is HFromInside or HInside or HOutside and the page number is even,
// than set anchorBoundingRect to HParagraphStartMargin area
if ((pageNumber()%2 == 0) && (horizontalPos == KoShapeAnchor::HFromInside ||
horizontalPos == KoShapeAnchor::HInside || horizontalPos == KoShapeAnchor::HOutside)) {
anchorBoundingRect.setX(containerBoundingRect.x());
//FIXME anchorBoundingRect.setWidth(state->x());
} else {
//FIXME anchorBoundingRect.setX(state->x() + containerBoundingRect.x() + state->width());
anchorBoundingRect.setWidth(containerBoundingRect.x() + containerBoundingRect.width() - anchorBoundingRect.x());
}
break;
}
default :
warnTextLayout << "horizontal-rel not handled";
}
return true;
}
void FloatingAnchorStrategy::countHorizontalPos(QPointF &newPosition, const QRectF &anchorBoundingRect)
{
switch (m_anchor->horizontalPos()) {
case KoShapeAnchor::HCenter:
newPosition.setX(anchorBoundingRect.x() + anchorBoundingRect.width()/2
- m_anchor->shape()->size().width()/2);
break;
case KoShapeAnchor::HFromInside:
case KoShapeAnchor::HInside:
{
if (pageNumber()%2 == 1) {
newPosition.setX(anchorBoundingRect.x());
} else {
newPosition.setX(anchorBoundingRect.right() -
m_anchor->shape()->size().width() - 2*m_anchor->offset().x() );
}
break;
}
case KoShapeAnchor::HLeft:
case KoShapeAnchor::HFromLeft:
newPosition.setX(anchorBoundingRect.x());
break;
case KoShapeAnchor::HOutside:
{
if (pageNumber()%2 == 1) {
newPosition.setX(anchorBoundingRect.right());
} else {
QSizeF size = m_anchor->shape()->boundingRect().size();
newPosition.setX(anchorBoundingRect.x() - size.width() - m_anchor->offset().x());
}
break;
}
case KoShapeAnchor::HRight: {
QSizeF size = m_anchor->shape()->boundingRect().size();
newPosition.setX(anchorBoundingRect.right() - size.width());
break;
}
default :
warnTextLayout << "horizontal-pos not handled";
}
}
bool FloatingAnchorStrategy::countVerticalRel(QRectF &anchorBoundingRect, const QRectF &containerBoundingRect,
KoTextShapeData *data, QTextBlock &block, QTextLayout *layout)
{
//FIXME proper handle VFrame and VFrameContent but fallback to VPage/VPageContent for now to produce better results
switch (m_anchor->verticalRel()) {
case KoShapeAnchor::VPage:
anchorBoundingRect.setY(pageRect().y());
anchorBoundingRect.setHeight(pageRect().height());
break;
case KoShapeAnchor::VFrame:
case KoShapeAnchor::VFrameContent:
anchorBoundingRect.setY(containerBoundingRect.y());
anchorBoundingRect.setHeight(containerBoundingRect.height());
break;
case KoShapeAnchor::VPageContent:
anchorBoundingRect.setY(pageContentRect().y());
anchorBoundingRect.setHeight(pageContentRect().height());
break;
case KoShapeAnchor::VParagraph:
anchorBoundingRect.setY(paragraphRect().y() + containerBoundingRect.y() - data->documentOffset());
anchorBoundingRect.setHeight(paragraphRect().height());
break;
case KoShapeAnchor::VParagraphContent: {
anchorBoundingRect.setY(paragraphContentRect().y() + containerBoundingRect.y() - data->documentOffset());
anchorBoundingRect.setHeight(paragraphContentRect().height());
}
break;
case KoShapeAnchor::VLine: {
QTextLine tl = layout->lineForTextPosition(m_anchorRange->position() - block.position());
if (!tl.isValid())
return false; // lets go for a second round.
QSizeF size = m_anchor->shape()->boundingRect().size();
anchorBoundingRect.setY(tl.y() - size.height()
+ containerBoundingRect.y() - data->documentOffset());
anchorBoundingRect.setHeight(2*size.height());
}
break;
case KoShapeAnchor::VText: // same as char apparently only used when as-char
case KoShapeAnchor::VChar: {
QTextLine tl = layout->lineForTextPosition(m_anchorRange->position() - block.position());
if (!tl.isValid())
return false; // lets go for a second round.
anchorBoundingRect.setY(tl.y() + containerBoundingRect.y() - data->documentOffset());
anchorBoundingRect.setHeight(tl.height());
}
break;
case KoShapeAnchor::VBaseline: {
QTextLine tl = layout->lineForTextPosition(m_anchorRange->position() - block.position());
if (!tl.isValid())
return false; // lets go for a second round.
QSizeF size = m_anchor->shape()->boundingRect().size();
anchorBoundingRect.setY(tl.y() + tl.ascent() - size.height()
+ containerBoundingRect.y() - data->documentOffset());
anchorBoundingRect.setHeight(2*size.height());
}
break;
default :
warnTextLayout << "vertical-rel not handled";
}
return true;
}
void FloatingAnchorStrategy::countVerticalPos(QPointF &newPosition, const QRectF &anchorBoundingRect)
{
switch (m_anchor->verticalPos()) {
case KoShapeAnchor::VBottom:
newPosition.setY(anchorBoundingRect.bottom() - m_anchor->shape()->size().height());
break;
case KoShapeAnchor::VBelow:
newPosition.setY(anchorBoundingRect.bottom());
break;
case KoShapeAnchor::VMiddle:
newPosition.setY(anchorBoundingRect.y() + anchorBoundingRect.height()/2 - m_anchor->shape()->size().height()/2);
break;
case KoShapeAnchor::VFromTop:
case KoShapeAnchor::VTop:
newPosition.setY(anchorBoundingRect.y());
break;
default :
warnTextLayout << "vertical-pos not handled";
}
}
void FloatingAnchorStrategy::checkLayoutEnvironment(QPointF &newPosition, KoTextShapeData *data)
{
QSizeF size = m_anchor->shape()->boundingRect().size();
//check left border and move the shape back to have the whole shape within
if (newPosition.x() < layoutEnvironmentRect().x()) {
newPosition.setX(layoutEnvironmentRect().x());
}
//check right border and move the shape back to have the whole shape within
if (newPosition.x() + size.width() > layoutEnvironmentRect().right()) {
newPosition.setX(layoutEnvironmentRect().right() - size.width());
}
//check top border and move the shape back to have the whole shape within
if (newPosition.y() < layoutEnvironmentRect().y() - data->documentOffset()) {
newPosition.setY(layoutEnvironmentRect().y() - data->documentOffset());
}
//check bottom border and move the shape back to have the whole shape within
if (newPosition.y() + size.height() > layoutEnvironmentRect().bottom() - data->documentOffset()) {
newPosition.setY(layoutEnvironmentRect().bottom() - size.height() - data->documentOffset());
}
}
void FloatingAnchorStrategy::checkPageBorder(QPointF &newPosition)
{
QSizeF size = m_anchor->shape()->boundingRect().size();
//check left border and move the shape back to have the whole shape visible
if (newPosition.x() < pageRect().x()) {
newPosition.setX(pageRect().x());
}
//check right border and move the shape back to have the whole shape visible
if (newPosition.x() + size.width() > pageRect().x() + pageRect().width()) {
newPosition.setX(pageRect().x() + pageRect().width() - size.width());
}
//check top border and move the shape back to have the whole shape visible
if (newPosition.y() < pageRect().y()) {
newPosition.setY(pageRect().y());
}
//check bottom border and move the shape back to have the whole shape visible
if (newPosition.y() + size.height() > pageRect().y() + pageRect().height()) {
newPosition.setY(pageRect().y() + pageRect().height() - size.height());
}
}
// If the horizontal-pos is Left or Right then we need to check if there are other
// objects anchored with horizontal-pos left or right. If there are then we need
// to "stack" our object on them what means that rather then floating this object
// over the other it is needed to adjust the position to be sure they are not
// floating over each other.
void FloatingAnchorStrategy::checkStacking(QPointF &newPosition)
{
if (m_anchor->anchorType() != KoShapeAnchor::AnchorParagraph || (m_anchor->horizontalPos() != KoShapeAnchor::HLeft && m_anchor->horizontalPos() != KoShapeAnchor::HRight))
return;
int idx = m_rootArea->documentLayout()->textAnchors().indexOf(m_anchor);
Q_ASSERT_X(idx >= 0, __FUNCTION__, QString("WTF? How can our anchor not be in the anchor-list but still be called?").toLocal8Bit());
QSizeF size = m_anchor->shape()->boundingRect().size();
for(int i = 0; i < idx; ++i) {
KoShapeAnchor *a = m_rootArea->documentLayout()->textAnchors()[i];
if (m_anchor->anchorType() != a->anchorType() || m_anchor->horizontalPos() != a->horizontalPos())
continue;
QRectF thisRect(newPosition, size);
QRectF r(a->shape()->boundingRect());
if (thisRect.intersects(r)) {
if (m_anchor->horizontalPos() == KoShapeAnchor::HLeft)
newPosition.setX(a->shape()->position().x() + r.width());
else // KoShapeAnchor::HRight
newPosition.setX(a->shape()->position().x() - size.width());
}
}
}
diff --git a/plugins/flake/textshape/textlayout/KoTextDocumentLayout.cpp b/plugins/flake/textshape/textlayout/KoTextDocumentLayout.cpp
index de30a44dd9..465fa31162 100644
--- a/plugins/flake/textshape/textlayout/KoTextDocumentLayout.cpp
+++ b/plugins/flake/textshape/textlayout/KoTextDocumentLayout.cpp
@@ -1,1025 +1,1025 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007, 2009-2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 Johannes Simon <johannes.simon@gmail.com>
* Copyright (C) 2011-2013 KO GmbH <cbo@kogmbh.com>
* Copyright (C) 2011-2013 C.Boemann <cbo@boemann.dk>
*
* 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 "KoTextDocumentLayout.h"
#include "styles/KoStyleManager.h"
#include "KoTextBlockData.h"
#include "KoInlineTextObjectManager.h"
#include "KoTextLayoutRootArea.h"
#include "KoTextLayoutRootAreaProvider.h"
#include "KoTextLayoutObstruction.h"
#include "FrameIterator.h"
#include "InlineAnchorStrategy.h"
#include "FloatingAnchorStrategy.h"
#include "AnchorStrategy.h"
#include "IndexGeneratorManager.h"
#include <KoShapeAnchor.h>
#include <KoAnchorInlineObject.h>
#include <KoAnchorTextRange.h>
#include <KoTextRangeManager.h>
#include <KoTextPage.h>
#include <KoPostscriptPaintDevice.h>
#include <KoShape.h>
#include <KoShapeContainer.h>
#include <KoAnnotation.h>
#include <KoTextDocument.h>
#include <KoUnit.h>
#include <KoParagraphStyle.h>
#include <KoTableStyle.h>
#include <TextLayoutDebug.h>
#include <QTextBlock>
#include <QTextTable>
#include <QTimer>
#include <QList>
extern int qt_defaultDpiY();
KoInlineObjectExtent::KoInlineObjectExtent(qreal ascent, qreal descent)
: m_ascent(ascent),
m_descent(descent)
{
}
class Q_DECL_HIDDEN KoTextDocumentLayout::Private
{
public:
Private(KoTextDocumentLayout *)
: styleManager(0)
, changeTracker(0)
, inlineTextObjectManager(0)
, textRangeManager(0)
, provider(0)
, layoutPosition(0)
, anchoringRootArea(0)
, anchoringIndex(0)
, anAnchorIsPlaced(false)
, anchoringSoftBreak(INT_MAX)
, allowPositionInlineObject(true)
, continuationObstruction(0)
, referencedLayout(0)
, defaultTabSizing(0)
, y(0)
, isLayouting(false)
, layoutScheduled(false)
, continuousLayout(true)
, layoutBlocked(false)
, changesBlocked(false)
, restartLayout(false)
, wordprocessingMode(false)
, showInlineObjectVisualization(false)
{
}
KoStyleManager *styleManager;
KoChangeTracker *changeTracker;
KoInlineTextObjectManager *inlineTextObjectManager;
KoTextRangeManager *textRangeManager;
KoTextLayoutRootAreaProvider *provider;
KoPostscriptPaintDevice *paintDevice;
QList<KoTextLayoutRootArea *> rootAreaList;
FrameIterator *layoutPosition;
QHash<int, KoInlineObjectExtent> inlineObjectExtents; // maps text-position to whole-line-height of an inline object
int inlineObjectOffset;
QList<KoShapeAnchor *> textAnchors; // list of all inserted inline objects
QList<KoShapeAnchor *> foundAnchors; // anchors found in an iteration run
KoTextLayoutRootArea *anchoringRootArea;
int anchoringIndex; // index of last not positioned inline object inside textAnchors
bool anAnchorIsPlaced;
int anchoringSoftBreak;
QRectF anchoringParagraphRect;
QRectF anchoringParagraphContentRect;
QRectF anchoringLayoutEnvironmentRect;
bool allowPositionInlineObject;
QHash<KoShape*,KoTextLayoutObstruction*> anchoredObstructions; // all obstructions created because KoShapeAnchor from m_textAnchors is in text
QList<KoTextLayoutObstruction*> freeObstructions; // obstructions affecting the current rootArea, and not anchored to text
KoTextLayoutObstruction *continuationObstruction;
KoTextDocumentLayout *referencedLayout;
QHash<KoInlineObject *, KoTextLayoutRootArea *> rootAreaForInlineObject;
qreal defaultTabSizing;
qreal y;
bool isLayouting;
bool layoutScheduled;
bool continuousLayout;
bool layoutBlocked;
bool changesBlocked;
bool restartLayout;
bool wordprocessingMode;
bool showInlineObjectVisualization;
};
// ------------------- KoTextDocumentLayout --------------------
KoTextDocumentLayout::KoTextDocumentLayout(QTextDocument *doc, KoTextLayoutRootAreaProvider *provider)
: QAbstractTextDocumentLayout(doc),
d(new Private(this))
{
d->paintDevice = new KoPostscriptPaintDevice();
d->provider = provider;
setPaintDevice(d->paintDevice);
d->styleManager = KoTextDocument(document()).styleManager();
d->changeTracker = KoTextDocument(document()).changeTracker();
d->inlineTextObjectManager = KoTextDocument(document()).inlineTextObjectManager();
d->textRangeManager = KoTextDocument(document()).textRangeManager();
setTabSpacing(MM_TO_POINT(23)); // use same default as open office
d->layoutPosition = new FrameIterator(doc->rootFrame());
}
KoTextDocumentLayout::~KoTextDocumentLayout()
{
delete d->paintDevice;
delete d->layoutPosition;
qDeleteAll(d->freeObstructions);
qDeleteAll(d->anchoredObstructions);
qDeleteAll(d->textAnchors);
delete d;
}
KoTextLayoutRootAreaProvider *KoTextDocumentLayout::provider() const
{
return d->provider;
}
void KoTextDocumentLayout::setWordprocessingMode()
{
d->wordprocessingMode = true;
}
bool KoTextDocumentLayout::wordprocessingMode() const
{
return d->wordprocessingMode;
}
bool KoTextDocumentLayout::relativeTabs(const QTextBlock &block) const
{
return KoTextDocument(document()).relativeTabs()
&& KoTextDocument(block.document()).relativeTabs();
}
KoInlineTextObjectManager *KoTextDocumentLayout::inlineTextObjectManager() const
{
return d->inlineTextObjectManager;
}
void KoTextDocumentLayout::setInlineTextObjectManager(KoInlineTextObjectManager *manager)
{
d->inlineTextObjectManager = manager;
}
KoTextRangeManager *KoTextDocumentLayout::textRangeManager() const
{
return d->textRangeManager;
}
void KoTextDocumentLayout::setTextRangeManager(KoTextRangeManager *manager)
{
d->textRangeManager = manager;
}
KoChangeTracker *KoTextDocumentLayout::changeTracker() const
{
return d->changeTracker;
}
void KoTextDocumentLayout::setChangeTracker(KoChangeTracker *tracker)
{
d->changeTracker = tracker;
}
KoStyleManager *KoTextDocumentLayout::styleManager() const
{
return d->styleManager;
}
void KoTextDocumentLayout::setStyleManager(KoStyleManager *manager)
{
d->styleManager = manager;
}
QRectF KoTextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
{
QTextLayout *layout = block.layout();
return layout->boundingRect();
}
QSizeF KoTextDocumentLayout::documentSize() const
{
return QSizeF();
}
QRectF KoTextDocumentLayout::selectionBoundingBox(QTextCursor &cursor) const
{
QRectF retval;
Q_FOREACH (const KoTextLayoutRootArea *rootArea, d->rootAreaList) {
if (!rootArea->isDirty()) {
QRectF areaBB = rootArea->selectionBoundingBox(cursor);
if (areaBB.isValid()) {
retval |= areaBB;
}
}
}
return retval;
}
void KoTextDocumentLayout::draw(QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context)
{
// WARNING Text shapes ask their root area directly to paint.
// It saves a lot of extra traversal, that is quite costly for big
// documents
Q_UNUSED(painter);
Q_UNUSED(context);
}
int KoTextDocumentLayout::hitTest(const QPointF &point, Qt::HitTestAccuracy accuracy) const
{
Q_UNUSED(point);
Q_UNUSED(accuracy);
Q_ASSERT(false); //we should no longer call this method.
// There is no need and is just slower than needed
// call rootArea->hitTest() directly
// root area is available through KoTextShapeData
return -1;
}
int KoTextDocumentLayout::pageCount() const
{
return 1;
}
void KoTextDocumentLayout::setTabSpacing(qreal spacing)
{
d->defaultTabSizing = spacing;
}
qreal KoTextDocumentLayout::defaultTabSpacing() const
{
return d->defaultTabSizing;
}
// this method is called on every char inserted or deleted, on format changes, setting/moving of variables or objects.
void KoTextDocumentLayout::documentChanged(int position, int charsRemoved, int charsAdded)
{
if (d->changesBlocked) {
return;
}
int from = position;
const int to = from + charsAdded;
while (from < to) { // find blocks that have been added
QTextBlock block = document()->findBlock(from);
if (! block.isValid())
break;
if (from == block.position() && block.textList()) {
KoTextBlockData data(block);
data.setCounterWidth(-1);
}
from = block.position() + block.length();
}
// Mark the to the position corresponding root-areas as dirty. If there is no root-area for the position then we
// don't need to mark anything dirty but still need to go on to force a scheduled relayout.
if (!d->rootAreaList.isEmpty()) {
KoTextLayoutRootArea *fromArea;
if (position) {
fromArea = rootAreaForPosition(position-1);
} else {
fromArea = d->rootAreaList.at(0);
}
int startIndex = fromArea ? qMax(0, d->rootAreaList.indexOf(fromArea)) : 0;
int endIndex = startIndex;
if (charsRemoved != 0 || charsAdded != 0) {
// If any characters got removed or added make sure to also catch other root-areas that may be
// affected by this change. Note that adding, removing or formatting text will always charsRemoved>0
// and charsAdded>0 cause they are changing a range of characters. One case where both is zero is if
// the content of a variable changed (see KoVariable::setValue which calls publicDocumentChanged). In
// those cases we only need to relayout the root-area dirty where the variable is on.
KoTextLayoutRootArea *toArea = fromArea ? rootAreaForPosition(position + qMax(charsRemoved, charsAdded) + 1) : 0;
if (toArea) {
if (toArea != fromArea) {
endIndex = qMax(startIndex, d->rootAreaList.indexOf(toArea));
} else {
endIndex = startIndex;
}
} else {
endIndex = d->rootAreaList.count() - 1;
}
// The previous and following root-area of that range are selected too cause they can also be affect by
// changes done to the range of root-areas.
if (startIndex >= 1)
--startIndex;
if (endIndex + 1 < d->rootAreaList.count())
++endIndex;
}
// Mark all selected root-areas as dirty so they are relayouted.
for(int i = startIndex; i <= endIndex; ++i) {
if (d->rootAreaList.size() > i && d->rootAreaList[i])
d->rootAreaList[i]->setDirty();
}
}
// Once done we emit the layoutIsDirty signal. The consumer (e.g. the TextShape) will then layout dirty
// root-areas and if needed following ones which got dirty cause content moved to them. Also this will
// created new root-areas using KoTextLayoutRootAreaProvider::provide if needed.
emitLayoutIsDirty();
}
KoTextLayoutRootArea *KoTextDocumentLayout::rootAreaForPosition(int position) const
{
QTextBlock block = document()->findBlock(position);
if (!block.isValid())
return 0;
QTextLine line = block.layout()->lineForTextPosition(position - block.position());
if (!line.isValid())
return 0;
foreach (KoTextLayoutRootArea *rootArea, d->rootAreaList) {
QRectF rect = rootArea->boundingRect(); // should already be normalized()
if (rect.width() <= 0.0 && rect.height() <= 0.0) // ignore the rootArea if it has a size of QSizeF(0,0)
continue;
QPointF pos = line.position();
qreal x = pos.x();
qreal y = pos.y();
//0.125 needed since Qt Scribe works with fixed point
if (x + 0.125 >= rect.x() && x<= rect.right() && y + line.height() + 0.125 >= rect.y() && y <= rect.bottom()) {
return rootArea;
}
}
return 0;
}
KoTextLayoutRootArea *KoTextDocumentLayout::rootAreaForPoint(const QPointF &point) const
{
Q_FOREACH (KoTextLayoutRootArea *rootArea, d->rootAreaList) {
if (!rootArea->isDirty()) {
if (rootArea->boundingRect().contains(point)) {
return rootArea;
}
}
}
return 0;
}
void KoTextDocumentLayout::showInlineObjectVisualization(bool show)
{
d->showInlineObjectVisualization = show;
}
void KoTextDocumentLayout::drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int position, const QTextFormat &format)
{
Q_ASSERT(format.isCharFormat());
if (d->inlineTextObjectManager == 0)
return;
QTextCharFormat cf = format.toCharFormat();
if (d->showInlineObjectVisualization) {
QColor color = cf.foreground().color();
// initial idea was to use Qt::gray (#A0A0A4)
// for non-black text on non-white background it was derived to use
// the text color with a transparency of 0x5F, so white-gray (0xFF-0xA0)
color.setAlpha(0x5F);
cf.setBackground(QBrush(color));
}
KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf);
if (obj)
obj->paint(*painter, paintDevice(), document(), rect, object, position, cf);
}
QList<KoShapeAnchor *> KoTextDocumentLayout::textAnchors() const
{
return d->textAnchors;
}
void KoTextDocumentLayout::registerAnchoredObstruction(KoTextLayoutObstruction *obstruction)
{
d->anchoredObstructions.insert(obstruction->shape(), obstruction);
}
qreal KoTextDocumentLayout::maxYOfAnchoredObstructions(int firstCursorPosition, int lastCursorPosition) const
{
qreal y = 0.0;
int index = 0;
while (index < d->anchoringIndex) {
Q_ASSERT(index < d->textAnchors.count());
KoShapeAnchor *anchor = d->textAnchors[index];
if (anchor->flowWithText()) {
if (anchor->textLocation()->position() >= firstCursorPosition
&& anchor->textLocation()->position() <= lastCursorPosition) {
y = qMax(y, anchor->shape()->boundingRect().bottom() - anchor->shape()->parent()->boundingRect().y());
}
}
++index;
}
return y;
}
int KoTextDocumentLayout::anchoringSoftBreak() const
{
return d->anchoringSoftBreak;
}
void KoTextDocumentLayout::positionAnchoredObstructions()
{
if (!d->anchoringRootArea)
return;
KoTextPage *page = d->anchoringRootArea->page();
if (!page)
return;
if (d->anAnchorIsPlaced)
return;
// The specs define 3 different anchor modes using the
// draw:wrap-influence-on-position. We only implement the
// once-successive and decided against supporting the other
// two modes cause;
// 1. The first mode, once-concurrently, is only for backward-compatibility
// with pre OpenOffice.org 1.1. No other application supports that. It
// should never have been added to the specs.
// 2. The iterative mode is undocumented and it's absolute unclear how to
// implement it in a way that we would earn 100% the same results OO.org
// produces. In fact by looking at the OO.org source-code there seem to
// be lot of extra-conditions, assumptions and OO.org related things going
// on to handle that mode. We tried to support that mode once and it did
// hit us bad, our source-code become way more worse, layouting slower and
// the result was still different from OO.org. So, we decided it's not
// worth it.
- // 3. The explanation provided at http://lists.oasis-open.org/archives/office/200409/msg00018.html
+ // 3. The explanation provided at https://lists.oasis-open.org/archives/office/200409/msg00018.html
// why the specs support those 3 anchor modes is, well, poor. It just doesn't
// make sense. The specs should be fixed.
// 4. The only support mode, the once-successive, is the one (only) support by
// MSOffice. It's clear, logical, easy and needs to be supported by all
// major office-suites that like to be compatible with MSOffice and OO.org.
if (d->anchoringIndex < d->textAnchors.count()) {
KoShapeAnchor *textAnchor = d->textAnchors[d->anchoringIndex];
AnchorStrategy *strategy = static_cast<AnchorStrategy *>(textAnchor->placementStrategy());
strategy->setPageRect(page->rect());
strategy->setPageContentRect(page->contentRect());
strategy->setPageNumber(page->pageNumber());
if (strategy->moveSubject()) {
++d->anchoringIndex;
d->anAnchorIsPlaced = true;
}
}
}
void KoTextDocumentLayout::setAnchoringParagraphRect(const QRectF &paragraphRect)
{
d->anchoringParagraphRect = paragraphRect;
}
void KoTextDocumentLayout::setAnchoringParagraphContentRect(const QRectF &paragraphContentRect)
{
d->anchoringParagraphContentRect = paragraphContentRect;
}
void KoTextDocumentLayout::setAnchoringLayoutEnvironmentRect(const QRectF &layoutEnvironmentRect)
{
d->anchoringLayoutEnvironmentRect = layoutEnvironmentRect;
}
void KoTextDocumentLayout::allowPositionInlineObject(bool allow)
{
d->allowPositionInlineObject = allow;
}
// This method is called by qt every time QTextLine.setWidth()/setNumColumns() is called
void KoTextDocumentLayout::positionInlineObject(QTextInlineObject item, int position, const QTextFormat &format)
{
// Note: "item" used to be what was positioned. We don't actually use qtextinlineobjects anymore
// for our inline objects, but get the id from the format.
Q_UNUSED(item);
//We are called before layout so that we can position objects
Q_ASSERT(format.isCharFormat());
if (d->inlineTextObjectManager == 0)
return;
if (!d->allowPositionInlineObject)
return;
QTextCharFormat cf = format.toCharFormat();
KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf);
// We need some special treatment for anchors as they need to position their object during
// layout and not this early
KoAnchorInlineObject *anchorObject = dynamic_cast<KoAnchorInlineObject *>(obj);
if (anchorObject && d->anchoringRootArea->associatedShape()) {
// The type can only be KoShapeAnchor::AnchorAsCharacter since it's inline
KoShapeAnchor *anchor = anchorObject->anchor();
d->foundAnchors.append(anchor);
// if there is no anchor strategy set then create one
if (!anchor->placementStrategy()) {
anchor->setPlacementStrategy(new InlineAnchorStrategy(anchorObject, d->anchoringRootArea));
d->textAnchors.append(anchor);
anchorObject->updatePosition(document(), position, cf); // by extension calls updateContainerModel
}
static_cast<AnchorStrategy *>(anchor->placementStrategy())->setParagraphRect(d->anchoringParagraphRect);
static_cast<AnchorStrategy *>(anchor->placementStrategy())->setParagraphContentRect(d->anchoringParagraphContentRect);
static_cast<AnchorStrategy *>(anchor->placementStrategy())->setLayoutEnvironmentRect(d->anchoringLayoutEnvironmentRect);
}
else if (obj) {
obj->updatePosition(document(), position, cf);
}
}
// This method is called by KoTextLauoutArea every time it encounters a KoAnchorTextRange
void KoTextDocumentLayout::positionAnchorTextRanges(int pos, int length, const QTextDocument *effectiveDocument)
{
if (!d->allowPositionInlineObject)
return;
if (!textRangeManager()) {
return;
}
QHash<int, KoTextRange *> ranges = textRangeManager()->textRangesChangingWithin(effectiveDocument, pos, pos+length, pos, pos+length);
Q_FOREACH (KoTextRange *range, ranges.values()) {
KoAnchorTextRange *anchorRange = dynamic_cast<KoAnchorTextRange *>(range);
if (anchorRange) {
// We need some special treatment for anchors as they need to position their object during
// layout and not this early
KoShapeAnchor *anchor = anchorRange->anchor();
d->foundAnchors.append(anchor);
// At the beginAnchorCollecting the strategy is cleared, so this if will be entered
// every time we layout a page (though not every time for the inner repeats due to anchors)
if (!anchor->placementStrategy()) {
int index = d->textAnchors.count();
anchor->setPlacementStrategy(new FloatingAnchorStrategy(anchorRange, d->anchoringRootArea));
// The purpose of following code-block is to be sure that our paragraph-anchors are
// properly sorted by their z-index so the FloatingAnchorStrategy::checkStacking
// logic stack in the proper order. Bug 274512 has a testdoc for this attached.
if (index > 0 &&
anchor->anchorType() == KoShapeAnchor::AnchorParagraph &&
(anchor->horizontalRel() == KoShapeAnchor::HParagraph || anchor->horizontalRel() == KoShapeAnchor::HParagraphContent) &&
(anchor->horizontalPos() == KoShapeAnchor::HLeft || anchor->horizontalPos() == KoShapeAnchor::HRight)) {
QTextBlock anchorBlock = document()->findBlock(anchorRange->position());
for(int i = index - 1; i >= 0; --i) {
KoShapeAnchor *a = d->textAnchors[i];
if (a->anchorType() != anchor->anchorType())
break;
if (a->horizontalPos() != anchor->horizontalPos())
break;
if (document()->findBlock(a->textLocation()->position()) != anchorBlock)
break;
if (a->shape()->zIndex() < anchor->shape()->zIndex())
break;
--index;
}
}
d->textAnchors.insert(index, anchor);
anchorRange->updateContainerModel();
}
static_cast<AnchorStrategy *>(anchor->placementStrategy())->setParagraphRect(d->anchoringParagraphRect);
static_cast<AnchorStrategy *>(anchor->placementStrategy())->setParagraphContentRect(d->anchoringParagraphContentRect);
static_cast<AnchorStrategy *>(anchor->placementStrategy())->setLayoutEnvironmentRect(d->anchoringLayoutEnvironmentRect);
}
KoAnnotation *annotation = dynamic_cast<KoAnnotation *>(range);
if (annotation) {
int position = range->rangeStart();
QTextBlock block = range->document()->findBlock(position);
QTextLine line = block.layout()->lineForTextPosition(position - block.position());
QPointF refPos(line.cursorToX(position - block.position()), line.y());
KoShape *refShape = d->anchoringRootArea->associatedShape();
//KoTextShapeData *refTextShapeData;
//refPos += QPointF(refTextShapeData->leftPadding(), -refTextShapeData->documentOffset() + refTextShapeData->topPadding());
refPos += QPointF(0, -d->anchoringRootArea->top());
- refPos = refShape->absoluteTransformation(0).map(refPos);
+ refPos = refShape->absoluteTransformation().map(refPos);
//FIXME we need a more precise position than anchorParagraph Rect
emit foundAnnotation(annotation->annotationShape(), refPos);
}
}
}
void KoTextDocumentLayout::beginAnchorCollecting(KoTextLayoutRootArea *rootArea)
{
for(int i = 0; i<d->textAnchors.size(); i++ ) {
d->textAnchors[i]->setPlacementStrategy(0);
}
qDeleteAll(d->anchoredObstructions);
d->anchoredObstructions.clear();
d->textAnchors.clear();
d->anchoringIndex = 0;
d->anAnchorIsPlaced = false;
d->anchoringRootArea = rootArea;
d->allowPositionInlineObject = true;
d->anchoringSoftBreak = INT_MAX;
}
void KoTextDocumentLayout::resizeInlineObject(QTextInlineObject item, int position, const QTextFormat &format)
{
// Note: This method is called by qt during layout AND during paint
Q_ASSERT(format.isCharFormat());
if (d->inlineTextObjectManager == 0)
return;
QTextCharFormat cf = format.toCharFormat();
KoInlineObject *obj = d->inlineTextObjectManager->inlineTextObject(cf);
if (!obj) {
return;
}
if (d->isLayouting) {
d->rootAreaForInlineObject[obj] = d->anchoringRootArea;
}
KoTextLayoutRootArea *rootArea = d->rootAreaForInlineObject.value(obj);
if (rootArea == 0 || rootArea->associatedShape() == 0)
return;
QTextDocument *doc = document();
QVariant v;
v.setValue(rootArea->page());
doc->addResource(KoTextDocument::LayoutTextPage, KoTextDocument::LayoutTextPageUrl, v);
obj->resize(doc, item, position, cf, paintDevice());
registerInlineObject(item);
}
void KoTextDocumentLayout::emitLayoutIsDirty()
{
emit layoutIsDirty();
}
void KoTextDocumentLayout::layout()
{
if (d->layoutBlocked) {
return;
}
if (IndexGeneratorManager::instance(document())->generate()) {
return;
}
Q_ASSERT(!d->isLayouting);
d->isLayouting = true;
bool finished;
do {
// Try to layout as long as d->restartLayout==true. This can happen for example if
// a schedule layout call interrupts the layouting and asks for a new layout run.
finished = doLayout();
} while (d->restartLayout);
Q_ASSERT(d->isLayouting);
d->isLayouting = false;
if (finished) {
// We are only finished with layouting if continuousLayout()==true.
emit finishedLayout();
}
}
RootAreaConstraint constraintsForPosition(QTextFrame::iterator it, bool previousIsValid)
{
RootAreaConstraint constraints;
constraints.masterPageName.clear();
constraints.visiblePageNumber = -1;
constraints.newPageForced = false;
QTextBlock block = it.currentBlock();
QTextTable *table = qobject_cast<QTextTable*>(it.currentFrame());
if (block.isValid()) {
constraints.masterPageName = block.blockFormat().stringProperty(KoParagraphStyle::MasterPageName);
if (block.blockFormat().hasProperty(KoParagraphStyle::PageNumber)) {
constraints.visiblePageNumber = block.blockFormat().intProperty(KoParagraphStyle::PageNumber);
}
constraints.newPageForced = block.blockFormat().intProperty(KoParagraphStyle::BreakBefore) == KoText::PageBreak;
}
if (table) {
constraints.masterPageName = table->frameFormat().stringProperty(KoTableStyle::MasterPageName);
if (table->frameFormat().hasProperty(KoTableStyle::PageNumber)) {
constraints.visiblePageNumber = table->frameFormat().intProperty(KoTableStyle::PageNumber);
}
constraints.newPageForced = table->frameFormat().intProperty(KoTableStyle::BreakBefore) == KoText::PageBreak;
}
if (!constraints.masterPageName.isEmpty()) {
constraints.newPageForced = true;
}
if (previousIsValid && !constraints.newPageForced) {
it--;
block = it.currentBlock();
table = qobject_cast<QTextTable*>(it.currentFrame());
if (block.isValid()) {
constraints.newPageForced = block.blockFormat().intProperty(KoParagraphStyle::BreakAfter) == KoText::PageBreak;
}
if (table) {
constraints.newPageForced = table->frameFormat().intProperty(KoTableStyle::BreakAfter) == KoText::PageBreak;
}
}
return constraints;
}
bool KoTextDocumentLayout::doLayout()
{
delete d->layoutPosition;
d->layoutPosition = new FrameIterator(document()->rootFrame());
d->y = 0;
d->layoutScheduled = false;
d->restartLayout = false;
FrameIterator *transferedFootNoteCursor = 0;
KoInlineNote *transferedContinuedNote = 0;
int footNoteAutoCount = 0;
KoTextLayoutRootArea *rootArea = 0;
d->rootAreaList.clear();
int currentAreaNumber = 0;
do {
if (d->restartLayout) {
return false; // Abort layouting to restart from the beginning.
}
// Build our request for our rootArea provider
RootAreaConstraint constraints = constraintsForPosition(d->layoutPosition->it, currentAreaNumber > 0);
// Request a new root-area. If 0 is returned then layouting is finished.
bool newRootArea = false;
rootArea = d->provider->provide(this, constraints, currentAreaNumber, &newRootArea);
if (!rootArea) {
// Out of space ? Nothing more to do
break;
}
d->rootAreaList.append(rootArea);
bool shouldLayout = false;
if (rootArea->top() != d->y) {
shouldLayout = true;
}
else if (rootArea->isDirty()) {
shouldLayout = true;
}
else if (!rootArea->isStartingAt(d->layoutPosition)) {
shouldLayout = true;
}
else if (newRootArea) {
shouldLayout = true;
}
if (shouldLayout) {
QRectF rect = d->provider->suggestRect(rootArea);
d->freeObstructions = d->provider->relevantObstructions(rootArea);
rootArea->setReferenceRect(rect.left(), rect.right(), d->y + rect.top(), d->y + rect.bottom());
beginAnchorCollecting(rootArea);
// Layout all that can fit into that root area
bool finished;
FrameIterator *tmpPosition = 0;
do {
rootArea->setFootNoteCountInDoc(footNoteAutoCount);
rootArea->setFootNoteFromPrevious(transferedFootNoteCursor, transferedContinuedNote);
d->foundAnchors.clear();
delete tmpPosition;
tmpPosition = new FrameIterator(d->layoutPosition);
finished = rootArea->layoutRoot(tmpPosition);
if (d->anAnchorIsPlaced) {
d->anAnchorIsPlaced = false;
} else {
++d->anchoringIndex;
}
} while (d->anchoringIndex < d->textAnchors.count());
foreach (KoShapeAnchor *anchor, d->textAnchors) {
if (!d->foundAnchors.contains(anchor)) {
d->anchoredObstructions.remove(anchor->shape());
d->anchoringSoftBreak = qMin(d->anchoringSoftBreak, anchor->textLocation()->position());
}
}
if (d->textAnchors.count() > 0) {
delete tmpPosition;
tmpPosition = new FrameIterator(d->layoutPosition);
finished = rootArea->layoutRoot(tmpPosition);
}
delete d->layoutPosition;
d->layoutPosition = tmpPosition;
d->provider->doPostLayout(rootArea, newRootArea);
updateProgress(d->layoutPosition->it);
if (finished && !rootArea->footNoteCursorToNext()) {
d->provider->releaseAllAfter(rootArea);
// We must also delete them from our own list too
int newsize = d->rootAreaList.indexOf(rootArea) + 1;
while (d->rootAreaList.size() > newsize) {
d->rootAreaList.removeLast();
}
return true; // Finished layouting
}
if (d->layoutPosition->it == document()->rootFrame()->end()) {
return true; // Finished layouting
}
if (!continuousLayout()) {
return false; // Let's take a break. We are not finished layouting yet.
}
} else {
// Drop following rootAreas
delete d->layoutPosition;
d->layoutPosition = new FrameIterator(rootArea->nextStartOfArea());
if (d->layoutPosition->it == document()->rootFrame()->end() && !rootArea->footNoteCursorToNext()) {
d->provider->releaseAllAfter(rootArea);
// We must also delete them from our own list too
int newsize = d->rootAreaList.indexOf(rootArea) + 1;
while (d->rootAreaList.size() > newsize) {
d->rootAreaList.removeLast();
}
return true; // Finished layouting
}
}
transferedFootNoteCursor = rootArea->footNoteCursorToNext();
transferedContinuedNote = rootArea->continuedNoteToNext();
footNoteAutoCount += rootArea->footNoteAutoCount();
d->y = rootArea->bottom() + qreal(50); // (post)Layout method(s) just set this
// 50 just to separate pages
currentAreaNumber++;
} while (transferedFootNoteCursor || d->layoutPosition->it != document()->rootFrame()->end());
return true; // Finished layouting
}
void KoTextDocumentLayout::scheduleLayout()
{
// Compress multiple scheduleLayout calls into one executeScheduledLayout.
if (d->layoutScheduled) {
return;
}
d->layoutScheduled = true;
QTimer::singleShot(0, this, SLOT(executeScheduledLayout()));
}
void KoTextDocumentLayout::executeScheduledLayout()
{
// Only do the actual layout if it wasn't done meanwhile by someone else.
if (!d->layoutScheduled) {
return;
}
d->layoutScheduled = false;
if (d->isLayouting) {
// Since we are already layouting ask for a restart to be sure to also include
// root-areas that got dirty and are before the currently processed root-area.
d->restartLayout = true;
} else {
layout();
}
}
bool KoTextDocumentLayout::continuousLayout() const
{
return d->continuousLayout;
}
void KoTextDocumentLayout::setContinuousLayout(bool continuous)
{
d->continuousLayout = continuous;
}
void KoTextDocumentLayout::setBlockLayout(bool block)
{
d->layoutBlocked = block;
}
bool KoTextDocumentLayout::layoutBlocked() const
{
return d->layoutBlocked;
}
void KoTextDocumentLayout::setBlockChanges(bool block)
{
d->changesBlocked = block;
}
bool KoTextDocumentLayout::changesBlocked() const
{
return d->changesBlocked;
}
KoTextDocumentLayout* KoTextDocumentLayout::referencedLayout() const
{
return d->referencedLayout;
}
void KoTextDocumentLayout::setReferencedLayout(KoTextDocumentLayout *layout)
{
d->referencedLayout = layout;
}
QRectF KoTextDocumentLayout::frameBoundingRect(QTextFrame*) const
{
return QRectF();
}
void KoTextDocumentLayout::clearInlineObjectRegistry(const QTextBlock &block)
{
d->inlineObjectExtents.clear();
d->inlineObjectOffset = block.position();
}
void KoTextDocumentLayout::registerInlineObject(const QTextInlineObject &inlineObject)
{
KoInlineObjectExtent pos(inlineObject.ascent(),inlineObject.descent());
d->inlineObjectExtents.insert(d->inlineObjectOffset + inlineObject.textPosition(), pos);
}
KoInlineObjectExtent KoTextDocumentLayout::inlineObjectExtent(const QTextFragment &fragment)
{
if (d->inlineObjectExtents.contains(fragment.position()))
return d->inlineObjectExtents[fragment.position()];
return KoInlineObjectExtent();
}
void KoTextDocumentLayout::setContinuationObstruction(KoTextLayoutObstruction *continuationObstruction)
{
if (d->continuationObstruction) {
delete d->continuationObstruction;
}
d->continuationObstruction = continuationObstruction;
}
QList<KoTextLayoutObstruction *> KoTextDocumentLayout::currentObstructions()
{
if (d->continuationObstruction) {
// () is needed so we append to a local list and not anchoredObstructions
return (d->freeObstructions + d->anchoredObstructions.values()) << d->continuationObstruction;
} else {
return d->freeObstructions + d->anchoredObstructions.values();
}
}
QList<KoTextLayoutRootArea *> KoTextDocumentLayout::rootAreas() const
{
return d->rootAreaList;
}
void KoTextDocumentLayout::removeRootArea(KoTextLayoutRootArea *rootArea)
{
int indexOf = rootArea ? qMax(0, d->rootAreaList.indexOf(rootArea)) : 0;
for(int i = d->rootAreaList.count() - 1; i >= indexOf; --i)
d->rootAreaList.removeAt(i);
}
QList<KoShape*> KoTextDocumentLayout::shapes() const
{
QList<KoShape*> listOfShapes;
foreach (KoTextLayoutRootArea *rootArea, d->rootAreaList) {
if (rootArea->associatedShape())
listOfShapes.append(rootArea->associatedShape());
}
return listOfShapes;
}
void KoTextDocumentLayout::updateProgress(const QTextFrame::iterator &it)
{
QTextBlock block = it.currentBlock();
if (block.isValid()) {
int percent = block.position() / qreal(document()->rootFrame()->lastPosition()) * 100.0;
emit layoutProgressChanged(percent);
} else if (it.currentFrame()) {
int percent = it.currentFrame()->firstPosition() / qreal(document()->rootFrame()->lastPosition()) * 100.0;
emit layoutProgressChanged(percent);
}
}
diff --git a/plugins/flake/textshape/textlayout/KoTextDocumentLayout.h b/plugins/flake/textshape/textlayout/KoTextDocumentLayout.h
index d5bba00a11..3ed0095e56 100644
--- a/plugins/flake/textshape/textlayout/KoTextDocumentLayout.h
+++ b/plugins/flake/textshape/textlayout/KoTextDocumentLayout.h
@@ -1,329 +1,327 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007, 2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2006, 2011 Sebastian Sauer <mail@dipe.org>
* Copyright (C) 2011 C. Boemann <cbo@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 KOTEXTDOCUMENTLAYOUT_H
#define KOTEXTDOCUMENTLAYOUT_H
#include "kritatextlayout_export.h"
#include <QAbstractTextDocumentLayout>
#include <QList>
#include <QTextFrame>
class KoShape;
class KoStyleManager;
class KoChangeTracker;
class KoTextRangeManager;
class KoInlineTextObjectManager;
class KoViewConverter;
class KoImageCollection;
class KoShapeAnchor;
class KoTextLayoutRootArea;
class KoTextLayoutRootAreaProvider;
class KoTextLayoutObstruction;
class QRectF;
class QSizeF;
class KRITATEXTLAYOUT_EXPORT KoInlineObjectExtent
{
public:
explicit KoInlineObjectExtent(qreal ascent = 0, qreal descent = 0);
qreal m_ascent;
qreal m_descent;
};
/**
* Text layouter that allows text to flow in multiple root area and around
* obstructions.
*/
class KRITATEXTLAYOUT_EXPORT KoTextDocumentLayout : public QAbstractTextDocumentLayout
{
Q_OBJECT
public:
/// This struct is a helper for painting of kotext texts.
struct PaintContext {
PaintContext()
- : viewConverter(0)
- , imageCollection(0)
+ : imageCollection(0)
, showFormattingCharacters(false)
, showSectionBounds(false)
, showSpellChecking(false)
, showSelections(true)
, background(Qt::white)
{
}
/// the QText context
QAbstractTextDocumentLayout::PaintContext textContext;
/// A view converter, when set, is used to find out when the zoom is so low that painting of text is unneeded
- const KoViewConverter *viewConverter;
KoImageCollection *imageCollection;
bool showFormattingCharacters;
bool showTableBorders;
bool showSectionBounds;
bool showSpellChecking;
bool showSelections;
QColor background;
};
/// constructor
explicit KoTextDocumentLayout(QTextDocument *doc, KoTextLayoutRootAreaProvider *provider = 0);
~KoTextDocumentLayout() override;
/// return the rootAreaProvider.
KoTextLayoutRootAreaProvider *provider() const;
/// return the currently set manager, or 0 if none is set.
KoInlineTextObjectManager *inlineTextObjectManager() const;
void setInlineTextObjectManager(KoInlineTextObjectManager *manager);
/// return the currently set manager, or 0 if none is set.
KoTextRangeManager *textRangeManager() const;
void setTextRangeManager(KoTextRangeManager *manager);
/// return the currently set changeTracker, or 0 if none is set.
KoChangeTracker *changeTracker() const;
void setChangeTracker(KoChangeTracker *tracker);
/// return the currently set styleManager, or 0 if none is set.
KoStyleManager *styleManager() const;
void setStyleManager(KoStyleManager *manager);
/// Returns the bounding rectangle of block.
QRectF blockBoundingRect(const QTextBlock &block) const override;
/**
* Returns the total size of the document. This is useful to display
* widgets since they can use to information to update their scroll bars
* correctly
*/
QSizeF documentSize() const override;
QRectF frameBoundingRect(QTextFrame*) const override;
/// the default tab size for this document
qreal defaultTabSpacing() const;
/// set default tab size for this document
void setTabSpacing(qreal spacing);
/// set if this is for a word processor (slight changes in layout may occur)
void setWordprocessingMode();
/// is it for a word processor (slight changes in layout may occur)
bool wordprocessingMode() const;
/// are the tabs relative to indent or not
bool relativeTabs(const QTextBlock &block) const;
/// visualize inline objects during paint
void showInlineObjectVisualization(bool show);
/// Calc a bounding box rect of the selection
QRectF selectionBoundingBox(QTextCursor &cursor) const;
/// Draws the layout on the given painter with the given context.
void draw(QPainter * painter, const QAbstractTextDocumentLayout::PaintContext & context) override;
/// reimplemented DO NOT CALL - USE HITTEST IN THE ROOTAREAS INSTEAD
int hitTest(const QPointF & point, Qt::HitTestAccuracy accuracy) const override;
/// reimplemented to always return 1
int pageCount() const override;
QList<KoShapeAnchor *> textAnchors() const;
/**
* Register the anchored obstruction for run around
*
* We have the concept of Obstructions which text has to run around in various ways.
* We maintain two collections of obstructions. The free which are tied to just a position
* (tied to pages), and the anchored obstructions which are each anchored to a KoShapeAnchor
*
* The free obstructions are collected from the KoTextLayoutRootAreaProvider during layout
*
* The anchored obstructions are created in the FloatingAnchorStrategy and registered using
* this method.
*/
void registerAnchoredObstruction(KoTextLayoutObstruction *obstruction);
/**
* Anchors are special InlineObjects that we detect in positionInlineObject()
* We save those for later so we can position them during layout instead.
* During KoTextLayoutArea::layout() we call positionAnchoredObstructions()
*/
/// remove all anchors and associated obstructions and set up for collecting new ones
void beginAnchorCollecting(KoTextLayoutRootArea *rootArea);
/// allow positionInlineObject() to do anything (incl saving anchors)
void allowPositionInlineObject(bool allow);
/// Sets the paragraph rect that will be applied to anchorStrategies being created in
/// positionInlineObject()
void setAnchoringParagraphRect(const QRectF &paragraphRect);
/// Sets the paragraph content rect that will be applied to anchorStrategies being created in
/// positionInlineObject()
void setAnchoringParagraphContentRect(const QRectF &paragraphContentRect);
/// Sets the layoutEnvironment rect that will be applied to anchorStrategies being created in
/// positionInlineObject()
void setAnchoringLayoutEnvironmentRect(const QRectF &layoutEnvironmentRect);
/// Calculates the maximum y of anchored obstructions
qreal maxYOfAnchoredObstructions(int firstCursorPosition, int lastCursorPosition) const;
int anchoringSoftBreak() const;
/// Positions all anchored obstructions
/// the paragraphRect should be in textDocument coords and not global/document coords
void positionAnchoredObstructions();
/// remove inline object
void removeInlineObject(KoShapeAnchor *textAnchor);
void clearInlineObjectRegistry(const QTextBlock& block);
KoInlineObjectExtent inlineObjectExtent(const QTextFragment&);
/**
* We allow a text document to be distributed onto a sequence of KoTextLayoutRootArea;
* which brings up the need to figure out which KoTextLayoutRootArea is used for a certain
* text.
* @param position the position of the character in the text document we want to locate.
* @return the KoTextLayoutRootArea the text is laid-out in. Or 0 if there is no shape for that text character.
*/
KoTextLayoutRootArea *rootAreaForPosition(int position) const;
KoTextLayoutRootArea *rootAreaForPoint(const QPointF &point) const;
/**
* Remove the root-areas \p rootArea from the list of \a rootAreas() .
* \param rootArea root-area to remove. If 0 then all root-areas are removed.
*/
void removeRootArea(KoTextLayoutRootArea *rootArea = 0);
/// reimplemented from QAbstractTextDocumentLayout
void documentChanged(int position, int charsRemoved, int charsAdded) override;
void setContinuationObstruction(KoTextLayoutObstruction *continuationObstruction);
/// Return a list of obstructions intersecting current root area (during layout)
QList<KoTextLayoutObstruction *> currentObstructions();
QList<KoTextLayoutRootArea *> rootAreas() const;
QList<KoShape*> shapes() const;
/// Set should layout be continued when done with current root area
void setContinuousLayout(bool continuous);
/// Set \a layout() to be blocked (no layouting will happen)
void setBlockLayout(bool block);
bool layoutBlocked() const;
/// Set \a documentChanged() to be blocked (changes will not result in root-areas being marked dirty)
void setBlockChanges(bool block);
bool changesBlocked() const;
KoTextDocumentLayout* referencedLayout() const;
void setReferencedLayout(KoTextDocumentLayout *layout);
/**
* To be called during layout by KoTextLayoutArea - similar to how qt calls positionInlineObject
*
* It searches for anchor text ranges in the given span
*/
void positionAnchorTextRanges(int pos, int length, const QTextDocument *effectiveDocument);
Q_SIGNALS:
/**
* Signal that is emitted during layouting to inform about the progress done so far.
*/
void layoutProgressChanged(int percent);
/**
* Signal is emitted every time a layout run has completely finished (all text is positioned).
*/
void finishedLayout();
/**
* Signal is emitted when emitLayoutIsDirty() is called which happens at
* least when a root area is marked as dirty.
* @see emitLayoutIsDirty
*/
void layoutIsDirty();
void foundAnnotation(KoShape *annotationShape, const QPointF &refPosition);
public Q_SLOTS:
/**
* Does the layout of the text.
* This method will layout the text into sections, tables and textlines,
* chunk by chunk.
* It may interrupt itself, @see contiuousLayout
* calling this method when the layout is not dirty, doesn't take that much
* time as it doesn't do much, although it does check every root area
*/
virtual void layout();
/**
* Schedules a \a layout call for later using a QTimer::singleShot. Multiple calls
* to this slot will be compressed into one layout-call to prevent calling layouting
* to much. Also if meanwhile \a layout was called then the scheduled layout won't
* be executed.
*/
virtual void scheduleLayout();
/**
* Emits the \a layoutIsDirty signal.
*/
void emitLayoutIsDirty();
private Q_SLOTS:
/// Called by \a scheduleLayout to start a \a layout run if not done already meanwhile.
void executeScheduledLayout();
protected:
/// reimplemented
void drawInlineObject(QPainter *painter, const QRectF &rect, QTextInlineObject object, int position, const QTextFormat &format) override;
/// reimplemented
void positionInlineObject(QTextInlineObject item, int position, const QTextFormat &format) override;
/// reimplemented
void resizeInlineObject(QTextInlineObject item, int position, const QTextFormat &format) override;
/// should we continue layout when done with current root area
bool continuousLayout() const;
void registerInlineObject(const QTextInlineObject &inlineObject);
private:
class Private;
Private * const d;
bool doLayout();
void updateProgress(const QTextFrame::iterator &it);
};
#endif
diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp
index ff90af68da..e569fc12da 100644
--- a/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp
+++ b/plugins/flake/textshape/textlayout/KoTextLayoutArea_paint.cpp
@@ -1,1200 +1,1200 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
* Copyright (C) 2008 Roopesh Chander <roop@forwardbias.in>
* Copyright (C) 2007-2008 Pierre Ducroquet <pinaraf@pinaraf.info>
* Copyright (C) 2009-2011 KO GmbH <cbo@kogmbh.com>
* Copyright (C) 2009-2012 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2010 Nandita Suri <suri.nandita@gmail.com>
* Copyright (C) 2010 Ajay Pundhir <ajay.pratap@iiitb.net>
* Copyright (C) 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.com>
* Copyright (C) 2011 Gopalakrishna Bhat A <gopalakbhat@gmail.com>
* Copyright (C) 2011 Stuart Dickson <stuart@furkinfantasic.net>
* Copyright (C) 2014 Denis Kuplyakov <dener.kup@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 "KoTextLayoutArea.h"
#include "KoTextLayoutEndNotesArea.h"
#include "KoTextLayoutTableArea.h"
#include "KoTextLayoutNoteArea.h"
#include "TableIterator.h"
#include "ListItemsHelper.h"
#include "RunAroundHelper.h"
#include "KoTextDocumentLayout.h"
#include "FrameIterator.h"
#include <KoParagraphStyle.h>
#include <KoCharacterStyle.h>
#include <KoListStyle.h>
#include <KoStyleManager.h>
#include <KoTextBlockData.h>
#include <KoTextBlockBorderData.h>
#include <KoTextBlockPaintStrategyBase.h>
#include <KoText.h>
#include <KoChangeTracker.h>
#include <KoChangeTrackerElement.h>
#include <KoImageData.h>
#include <TextLayoutDebug.h>
#include <KoSection.h>
#include <KoSectionEnd.h>
#include <KoSectionUtils.h>
#include <QPainter>
#include <QTextTable>
#include <QTextList>
#include <QFontMetrics>
#include <QTextFragment>
#include <QTextLayout>
#include <QTextCursor>
#include <QTime>
#include "kis_painting_tweaks.h"
extern int qt_defaultDpiY();
Q_DECLARE_METATYPE(QTextDocument *)
#define DropCapsAdditionalFormattingId 25602902
#include "KoTextLayoutArea_p.h"
void KoTextLayoutArea::paint(QPainter *painter, const KoTextDocumentLayout::PaintContext &context)
{
if (d->startOfArea == 0 || d->endOfArea == 0) // We have not been layouted yet
return;
/*
struct Timer {
QTime d->time;
Timer() { d->time.start(); }
~Timer() { warnTextLayout << "elapsed=" << d->time.elapsed(); }
};
Timer timer;
*/
painter->save();
painter->translate(0, d->verticalAlignOffset);
painter->setPen(context.textContext.palette.color(QPalette::Text)); // for text that has no color.
const QRegion clipRegion = KisPaintingTweaks::safeClipRegion(*painter); // fetch after painter->translate so the clipRegion is correct
KoTextBlockBorderData *lastBorder = 0;
QRectF lastBorderRect;
QTextFrame::iterator it = d->startOfArea->it;
QTextFrame::iterator stop = d->endOfArea->it;
if (!stop.atEnd()) {
if (!stop.currentBlock().isValid() || d->endOfArea->lineTextStart >= 0) {
// Last thing we show is a frame (table) or first part of a paragraph split in two
// The stop point should be the object after that
// However if stop is already atEnd we shouldn't increment further
++stop;
}
}
int tableAreaIndex = 0;
int blockIndex = 0;
int tocIndex = 0;
for (; it != stop && !it.atEnd(); ++it) {
QTextBlock block = it.currentBlock();
QTextTable *table = qobject_cast<QTextTable*>(it.currentFrame());
QTextFrame *subFrame = it.currentFrame();
QTextBlockFormat format = block.blockFormat();
if (!block.isValid()) {
if (lastBorder) { // draw previous block's border
lastBorder->paint(*painter, lastBorderRect);
lastBorder = 0;
}
}
if (table) {
if (tableAreaIndex >= d->tableAreas.size()) {
continue;
}
d->tableAreas[tableAreaIndex]->paint(painter, context);
++tableAreaIndex;
continue;
} else if (subFrame) {
if (subFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) {
d->endNotesArea->paint(painter, context);
}
continue;
} else {
if (!block.isValid()) {
continue;
}
}
if (block.blockFormat().hasProperty(KoParagraphStyle::GeneratedDocument)) {
// Possibly paint the selection of the entire Table of Contents
// but since it's a secondary document we need to create a fake selection
QVariant data = block.blockFormat().property(KoParagraphStyle::GeneratedDocument);
QTextDocument *generatedDocument = data.value<QTextDocument *>();
KoTextDocumentLayout::PaintContext tocContext = context;
tocContext.textContext.selections = QVector<QAbstractTextDocumentLayout::Selection>();
bool pure = true;
Q_FOREACH (const QAbstractTextDocumentLayout::Selection &selection, context.textContext.selections) {
if (selection.cursor.selectionStart() <= block.position()
&& selection.cursor.selectionEnd() >= block.position()) {
painter->fillRect(d->generatedDocAreas[tocIndex]->boundingRect(), selection.format.background());
if (pure) {
tocContext.textContext.selections.append(QAbstractTextDocumentLayout::Selection());
tocContext.textContext.selections[0].cursor = QTextCursor(generatedDocument);
tocContext.textContext.selections[0].cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
tocContext.textContext.selections[0].format = selection.format;
pure = false;
}
}
}
d->generatedDocAreas[tocIndex]->paint(painter, tocContext);
++tocIndex;
continue;
}
QTextLayout *layout = block.layout();
KoTextBlockBorderData *border = 0;
if (blockIndex >= d->blockRects.count())
break;
QRectF br = d->blockRects[blockIndex];
++blockIndex;
if (!painter->hasClipping() || clipRegion.intersects(br.toRect())) {
KoTextBlockData blockData(block);
border = blockData.border();
KoTextBlockPaintStrategyBase *paintStrategy = blockData.paintStrategy();
KoTextBlockPaintStrategyBase dummyPaintStrategy;
if (paintStrategy == 0) {
paintStrategy = &dummyPaintStrategy;
}
if (!paintStrategy->isVisible()) {
if (lastBorder) { // draw previous block's border
lastBorder->paint(*painter, lastBorderRect);
lastBorder = 0;
}
continue; // this paragraph shouldn't be shown so just skip it
}
// Check and update border drawing code
if (lastBorder == 0) {
lastBorderRect = br;
} else if (lastBorder != border
|| lastBorderRect.width() != br.width()
|| lastBorderRect.x() != br.x()) {
lastBorder->paint(*painter, lastBorderRect);
lastBorderRect = br;
} else {
lastBorderRect = lastBorderRect.united(br);
}
lastBorder = border;
painter->save();
QBrush bg = paintStrategy->background(block.blockFormat().background());
if (bg != Qt::NoBrush ) {
painter->fillRect(br, bg);
} else {
bg = context.background;
}
paintStrategy->applyStrategy(painter);
painter->save();
drawListItem(painter, block);
painter->restore();
QVector<QTextLayout::FormatRange> selections;
if (context.showSelections) {
Q_FOREACH (const QAbstractTextDocumentLayout::Selection & selection, context.textContext.selections) {
QTextCursor cursor = selection.cursor;
int begin = cursor.position();
int end = cursor.anchor();
if (begin > end)
std::swap(begin, end);
if (end < block.position() || begin > block.position() + block.length())
continue; // selection does not intersect this block.
if (selection.cursor.hasComplexSelection()) {
continue; // selections of several table cells are covered by the within drawBorders above.
}
if (d->documentLayout->changeTracker()
&& !d->documentLayout->changeTracker()->displayChanges()
&& d->documentLayout->changeTracker()->containsInlineChanges(selection.format)
&& d->documentLayout->changeTracker()->elementById(selection.format.property(KoCharacterStyle::ChangeTrackerId).toInt())->isEnabled()
&& d->documentLayout->changeTracker()->elementById(selection.format.property(KoCharacterStyle::ChangeTrackerId).toInt())->getChangeType() == KoGenChange::DeleteChange) {
continue; // Deletions should not be shown.
}
QTextLayout::FormatRange fr;
fr.start = begin - block.position();
fr.length = end - begin;
fr.format = selection.format;
selections.append(fr);
}
}
// this is a workaround to fix text getting cut of when format ranges are used. There
// is a bug in Qt that can hit when text lines overlap each other. In case a format range
// is used for formatting it can clip the lines above/below as Qt creates a clip rect for
// the places it already painted for the format range which results in clippling. So use
// the format range always to paint the text.
QVector<QTextLayout::FormatRange> workaroundFormatRanges;
for (QTextBlock::iterator it = block.begin(); !(it.atEnd()); ++it) {
QTextFragment currentFragment = it.fragment();
if (currentFragment.isValid()) {
bool formatChanged = false;
QTextCharFormat format = currentFragment.charFormat();
int changeId = format.intProperty(KoCharacterStyle::ChangeTrackerId);
if (changeId && d->documentLayout->changeTracker() && d->documentLayout->changeTracker()->displayChanges()) {
KoChangeTrackerElement *changeElement = d->documentLayout->changeTracker()->elementById(changeId);
switch(changeElement->getChangeType()) {
case (KoGenChange::InsertChange):
format.setBackground(QBrush(d->documentLayout->changeTracker()->getInsertionBgColor()));
break;
case (KoGenChange::FormatChange):
format.setBackground(QBrush(d->documentLayout->changeTracker()->getFormatChangeBgColor()));
break;
case (KoGenChange::DeleteChange):
format.setBackground(QBrush(d->documentLayout->changeTracker()->getDeletionBgColor()));
break;
case (KoGenChange::UNKNOWN):
break;
}
formatChanged = true;
}
if (format.isAnchor()) {
if (!format.hasProperty(KoCharacterStyle::UnderlineStyle))
format.setFontUnderline(true);
if (!format.hasProperty(QTextFormat::ForegroundBrush))
format.setForeground(Qt::blue);
formatChanged = true;
}
if (format.boolProperty(KoCharacterStyle::UseWindowFontColor)) {
QBrush backbrush = bg;
if (format.background() != Qt::NoBrush) {
backbrush = format.background();
}
QBrush frontBrush;
frontBrush.setStyle(Qt::SolidPattern);
// use the same luma calculation and threshold as msoffice
- // see http://social.msdn.microsoft.com/Forums/en-US/os_binaryfile/thread/a02a9a24-efb6-4ba0-a187-0e3d2704882b
+ // see https://social.msdn.microsoft.com/Forums/en-US/os_binaryfile/thread/a02a9a24-efb6-4ba0-a187-0e3d2704882b
int luma = ((5036060/2) * backbrush.color().red()
+ (9886846/2) * backbrush.color().green()
+ (1920103/2) * backbrush.color().blue()) >> 23;
if (luma > 60) {
frontBrush.setColor(QColor(Qt::black));
} else {
frontBrush.setColor(QColor(Qt::white));
}
format.setForeground(frontBrush);
formatChanged = true;
}
if (formatChanged) {
QTextLayout::FormatRange fr;
fr.start = currentFragment.position() - block.position();
fr.length = currentFragment.length();
if (!format.hasProperty(KoCharacterStyle::InlineInstanceId)) {
if (format.background().style() == Qt::NoBrush) {
format.setBackground(QBrush(QColor(0, 0, 0, 0)));
}
if (format.foreground().style() == Qt::NoBrush) {
format.setForeground(QBrush(QColor(0, 0, 0)));
}
}
fr.format = format;
// the prepend is done so the selections are at the end.
selections.prepend(fr);
}
else {
if (!format.hasProperty(KoCharacterStyle::InlineInstanceId)) {
QTextLayout::FormatRange fr;
fr.start = currentFragment.position() - block.position();
fr.length = currentFragment.length();
QTextCharFormat f;
if (format.background().style() == Qt::NoBrush) {
f.setBackground(QBrush(QColor(0, 0, 0, 0)));
}
else {
f.setBackground(format.background());
}
if (format.foreground().style() == Qt::NoBrush) {
f.setForeground(QBrush(QColor(0, 0, 0)));
}
else {
f.setForeground(format.foreground());
}
fr.format = f;
workaroundFormatRanges.append(fr);
}
}
}
}
if (!selections.isEmpty()) {
selections = workaroundFormatRanges + selections;
}
//We set clip because layout-draw doesn't clip text to it correctly after all
//and adjust to make sure we don't clip edges of glyphs. The clipping is
//important for paragraph split across two pages.
//20pt enlargement seems safe as pages is split by 50pt and this helps unwanted
//glyph cutting
painter->setClipRect(br.adjusted(-20,-20,20,20), Qt::IntersectClip);
layout->draw(painter, QPointF(0, 0), selections);
if (context.showSectionBounds) {
decorateParagraphSections(painter, block);
}
decorateParagraph(painter, block, context.showFormattingCharacters, context.showSpellChecking);
painter->restore();
} else {
if (lastBorder) {
lastBorder->paint(*painter, lastBorderRect);
lastBorder = 0;
}
}
}
if (lastBorder) {
lastBorder->paint(*painter, lastBorderRect);
}
painter->translate(0, -d->verticalAlignOffset);
painter->translate(0, bottom() - d->footNotesHeight);
Q_FOREACH (KoTextLayoutNoteArea *footerArea, d->footNoteAreas) {
footerArea->paint(painter, context);
painter->translate(0, footerArea->bottom() - footerArea->top());
}
painter->restore();
}
void KoTextLayoutArea::drawListItem(QPainter *painter, QTextBlock &block)
{
KoTextBlockData blockData(block);
QTextList *list = block.textList();
if (list && blockData.hasCounterData()) {
QTextListFormat listFormat = list->format();
if (! blockData.counterText().isEmpty()) {
QFont font(blockData.labelFormat().font(), d->documentLayout->paintDevice());
KoListStyle::Style listStyle = static_cast<KoListStyle::Style>(listFormat.style());
QString result = blockData.counterText();
QTextLayout layout(result, font, d->documentLayout->paintDevice());
QList<QTextLayout::FormatRange> layouts;
QTextLayout::FormatRange format;
format.start = 0;
format.length = blockData.counterText().length();
format.format = blockData.labelFormat();
layouts.append(format);
layout.setAdditionalFormats(layouts);
Qt::Alignment alignment = static_cast<Qt::Alignment>(listFormat.intProperty(KoListStyle::Alignment));
if (alignment == 0) {
alignment = Qt::AlignLeft | Qt::AlignAbsolute;
}
if (d->isRtl && (alignment & Qt::AlignAbsolute) == 0) {
if (alignment & Qt::AlignLeft) {
alignment = Qt::AlignRight;
} else if (alignment & Qt::AlignRight) {
alignment = Qt::AlignLeft;
}
}
alignment |= Qt::AlignAbsolute;
QTextOption option(alignment);
option.setTextDirection(block.layout()->textOption().textDirection());
/*
if (option.textDirection() == Qt::RightToLeft || blockData.counterText().isRightToLeft()) {
option.setAlignment(Qt::AlignRight);
}
*/
layout.setTextOption(option);
layout.beginLayout();
QTextLine line = layout.createLine();
line.setLineWidth(blockData.counterWidth());
layout.endLayout();
QPointF counterPosition = blockData.counterPosition();
if (block.layout()->lineCount() > 0) {
// if there is text, then baseline align the counter.
QTextLine firstParagLine = block.layout()->lineAt(0);
if (KoListStyle::isNumberingStyle(listStyle)) {
//if numbered list baseline align
counterPosition += QPointF(0, firstParagLine.ascent() - layout.lineAt(0).ascent());
} else {
//for unnumbered list center align
counterPosition += QPointF(0, (firstParagLine.height() - layout.lineAt(0).height())/2.0);
}
}
layout.draw(painter, counterPosition);
//decorate the list label iff it is a numbered list
if (KoListStyle::isNumberingStyle(listStyle)) {
painter->save();
decorateListLabel(painter, blockData, layout.lineAt(0), block);
painter->restore();
}
}
KoListStyle::Style listStyle = static_cast<KoListStyle::Style>(listFormat.style());
if (listStyle == KoListStyle::ImageItem) {
QFontMetricsF fm(blockData.labelFormat().font(), d->documentLayout->paintDevice());
qreal x = qMax(qreal(1), blockData.counterPosition().x());
qreal width = qMax(listFormat.doubleProperty(KoListStyle::Width), (qreal)1.0);
qreal height = qMax(listFormat.doubleProperty(KoListStyle::Height), (qreal)1.0);
qreal y = blockData.counterPosition().y() + fm.ascent() - fm.xHeight()/2 - height/2; // centered
KoImageData *idata = listFormat.property(KoListStyle::BulletImage).value<KoImageData *>();
if (idata) {
painter->drawPixmap(x, y, width, height, idata->pixmap());
}
}
}
}
void KoTextLayoutArea::decorateListLabel(QPainter *painter, const KoTextBlockData &blockData, const QTextLine &listLabelLine, const QTextBlock &listItem)
{
const QTextCharFormat listLabelCharFormat = blockData.labelFormat();
painter->setFont(listLabelCharFormat.font());
int startOfFragmentInBlock = 0;
Q_ASSERT_X(listLabelLine.isValid(), __FUNCTION__, QString("Invalid list label").toLocal8Bit());
if (!listLabelLine.isValid()) {
return;
}
int fragmentToLineOffset = 0;
qreal x1 = blockData.counterPosition().x();
qreal x2 = listItem.layout()->lineAt(0).x();
if (x2 != x1) {
drawStrikeOuts(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset);
drawOverlines(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset);
drawUnderlines(painter, listLabelCharFormat, blockData.counterText(), listItem.layout()->lineAt(0), x1, x2, startOfFragmentInBlock, fragmentToLineOffset);
}
}
/**
* Draw a line. Typically meant to underline text or similar.
* @param painter the painter to paint on.
* @param color the pen color to for the decoration line
* @param type The type
* @param style the type of line to draw.
* @param width The thickness of the line, in pixels (the painter will be prescaled to points coordinate system).
* @param x1 we are always drawing horizontal lines, this is the start point.
* @param x2 we are always drawing horizontal lines, this is the end point.
* @param y the y-offset to paint on.
*/
static void drawDecorationLine(QPainter *painter, const QColor &color, KoCharacterStyle::LineType type, KoCharacterStyle::LineStyle style, qreal width, const qreal x1, const qreal x2, const qreal y)
{
QPen penBackup = painter->pen();
QPen pen = painter->pen();
pen.setColor(color);
pen.setWidthF(width);
if (style == KoCharacterStyle::WaveLine) {
// Ok, try the waves :)
pen.setStyle(Qt::SolidLine);
painter->setPen(pen);
qreal x = x1;
const qreal halfWaveWidth = 0.5 * width;
const qreal halfWaveLength = 2 * width;
const int startAngle = 0 * 16;
const int middleAngle = 180 * 16;
const int endAngle = 180 * 16;
while (x < x2) {
QRectF rectangle1(x, y, halfWaveLength, 2*halfWaveWidth);
if (type == KoCharacterStyle::DoubleLine) {
painter->translate(0, -pen.width());
painter->drawArc(rectangle1, startAngle, middleAngle);
painter->translate(0, 2*pen.width());
painter->drawArc(rectangle1, startAngle, middleAngle);
painter->translate(0, -pen.width());
} else {
painter->drawArc(rectangle1, startAngle, middleAngle);
}
if (x + halfWaveLength > x2)
break;
QRectF rectangle2(x + halfWaveLength, y, halfWaveLength, 2*halfWaveWidth);
if (type == KoCharacterStyle::DoubleLine) {
painter->translate(0, -pen.width());
painter->drawArc(rectangle2, middleAngle, endAngle);
painter->translate(0, 2*pen.width());
painter->drawArc(rectangle2, middleAngle, endAngle);
painter->translate(0, -pen.width());
} else {
painter->drawArc(rectangle2, middleAngle, endAngle);
}
x = x + 2 * halfWaveLength;
}
} else {
if (style == KoCharacterStyle::LongDashLine) {
QVector<qreal> dashes;
dashes << 12 << 2;
pen.setDashPattern(dashes);
} else {
pen.setStyle((Qt::PenStyle)style);
}
painter->setPen(pen);
if (type == KoCharacterStyle::DoubleLine) {
painter->translate(0, -pen.width());
painter->drawLine(QPointF(x1, y), QPointF(x2, y));
painter->translate(0, 2*pen.width());
painter->drawLine(QPointF(x1, y), QPointF(x2, y));
painter->translate(0, -pen.width());
} else {
painter->drawLine(QPointF(x1, y), QPointF(x2, y));
}
}
painter->setPen(penBackup);
}
static void drawDecorationText(QPainter *painter, const QTextLine &line, const QColor &color, const QString& decorText, qreal x1, qreal x2)
{
qreal y = line.position().y();
QPen oldPen = painter->pen();
painter->setPen(QPen(color));
do {
QRectF br;
painter->drawText(QRectF(QPointF(x1, y), QPointF(x2, y + line.height())), Qt::AlignLeft | Qt::AlignVCenter, decorText, &br);
x1 = br.right();
} while (x1 <= x2);
painter->setPen(oldPen);
}
static void drawDecorationWords(QPainter *painter, const QTextLine &line, const QString &text, const QColor &color, KoCharacterStyle::LineType type, KoCharacterStyle::LineStyle style, const QString& decorText, qreal width, const qreal y, const int fragmentToLineOffset, const int startOfFragmentInBlock)
{
qreal wordBeginX = -1;
int j = line.textStart()+fragmentToLineOffset;
while (j < line.textLength() + line.textStart() && j-startOfFragmentInBlock<text.size()) {
if (text[j-startOfFragmentInBlock].isSpace()) {
if (wordBeginX != -1) {
if (decorText.isEmpty())
drawDecorationLine(painter, color, type, style, width, wordBeginX, line.cursorToX(j), y);
else
drawDecorationText(painter, line, color, decorText, wordBeginX, line.cursorToX(j));
}
wordBeginX = -1;
} else if (wordBeginX == -1) {
wordBeginX = line.cursorToX(j);
}
++j;
}
if (wordBeginX != -1) {
if (decorText.isEmpty())
drawDecorationLine(painter, color, type, style, width, wordBeginX, line.cursorToX(j), y);
else
drawDecorationText(painter, line, color, decorText, wordBeginX, line.cursorToX(j));
}
}
static qreal computeWidth(KoCharacterStyle::LineWeight weight, qreal width, const QFont& font)
{
switch (weight) {
case KoCharacterStyle::AutoLineWeight:
case KoCharacterStyle::NormalLineWeight:
case KoCharacterStyle::MediumLineWeight:
case KoCharacterStyle::DashLineWeight:
return QFontMetricsF(font).lineWidth();
case KoCharacterStyle::BoldLineWeight:
case KoCharacterStyle::ThickLineWeight:
return QFontMetricsF(font).lineWidth() * 1.5;
case KoCharacterStyle::ThinLineWeight:
return QFontMetricsF(font).lineWidth() * 0.7;
case KoCharacterStyle::PercentLineWeight:
return QFontInfo(font).pointSizeF() * width / 100;
case KoCharacterStyle::LengthLineWeight:
return width;
}
Q_ASSERT(0); // illegal weight passed
return 0;
}
void KoTextLayoutArea::decorateParagraphSections(QPainter *painter, QTextBlock &block)
{
QTextLayout *layout = block.layout();
QTextBlockFormat bf = block.blockFormat();
QPen penBackup = painter->pen();
QPen pen = painter->pen();
pen.setWidth(1);
pen.setColor(Qt::gray);
painter->setPen(pen);
qreal xl = layout->boundingRect().left();
qreal xr = qMax(layout->boundingRect().right(), layout->boundingRect().left() + width());
qreal yu = layout->boundingRect().top();
qreal yd = layout->boundingRect().bottom();
qreal bracketSize = painter->fontMetrics().height() / 2;
const qreal levelShift = 3;
QList<KoSection *> openList = KoSectionUtils::sectionStartings(bf);
for (int i = 0; i < openList.size(); i++) {
int sectionLevel = openList[i]->level();
if (i == 0) {
painter->drawLine(xl + sectionLevel * levelShift, yu,
xr - sectionLevel * levelShift, yu);
}
painter->drawLine(xl + sectionLevel * levelShift, yu,
xl + sectionLevel * levelShift, yu + bracketSize);
painter->drawLine(xr - sectionLevel * levelShift, yu,
xr - sectionLevel * levelShift, yu + bracketSize);
}
QList<KoSectionEnd *> closeList = KoSectionUtils::sectionEndings(bf);
for (int i = 0; i < closeList.size(); i++) {
int sectionLevel = closeList[i]->correspondingSection()->level();
if (i == closeList.count() - 1) {
painter->drawLine(xl + sectionLevel * levelShift, yd,
xr - sectionLevel * levelShift, yd);
}
painter->drawLine(xl + sectionLevel * levelShift, yd,
xl + sectionLevel * levelShift, yd - bracketSize);
painter->drawLine(xr - sectionLevel * levelShift, yd,
xr - sectionLevel * levelShift, yd - bracketSize);
}
painter->setPen(penBackup);
}
void KoTextLayoutArea::decorateParagraph(QPainter *painter, QTextBlock &block, bool showFormattingCharacters, bool showSpellChecking)
{
QTextLayout *layout = block.layout();
QTextBlockFormat bf = block.blockFormat();
QVariantList tabList = bf.property(KoParagraphStyle::TabPositions).toList();
QFont oldFont = painter->font();
QTextBlock::iterator it;
int startOfBlock = -1;
int currentTabStop = 0;
// qDebug() << "\n-------------------"
// << "\nGoing to decorate block\n"
// << block.text()
// << "\n-------------------";
// loop over text fragments in this paragraph and draw the underline and line through.
for (it = block.begin(); !it.atEnd(); ++it) {
QTextFragment currentFragment = it.fragment();
if (currentFragment.isValid()) {
// qDebug() << "\tGoing to layout fragment:" << currentFragment.text();
QTextCharFormat fmt = currentFragment.charFormat();
painter->setFont(fmt.font());
// a block doesn't have a real start position, so use our own counter. Initialize
// it with the position of the first text fragment in the block.
if (startOfBlock == -1) {
startOfBlock = currentFragment.position(); // start of this block w.r.t. the document
}
// the start of our fragment in the block is the absolute position of the fragment
// in the document minus the start of the block in the document.
int startOfFragmentInBlock = currentFragment.position() - startOfBlock;
// a fragment can span multiple lines, but we paint the decorations per line.
int firstLine = layout->lineForTextPosition(currentFragment.position() - startOfBlock).lineNumber();
int lastLine = layout->lineForTextPosition(currentFragment.position() + currentFragment.length()
- startOfBlock).lineNumber();
// qDebug() << "\tfirst line:" << firstLine << "last line:" << lastLine;
for (int i = firstLine ; i <= lastLine ; ++i) {
QTextLine line = layout->lineAt(i);
// qDebug() << "\n\t\tcurrent line:" << i
// << "\n\t\tline length:" << line.textLength() << "width:"<< line.width() << "natural width" << line.naturalTextWidth()
// << "\n\t\tvalid:" << layout->isValidCursorPosition(currentFragment.position() - startOfBlock)
// << "\n\t\tcurrentFragment.position:" << currentFragment.position()
// << "\n\t\tstartOfBlock:" << startOfBlock
// << "\n\t\tstartOfFragmentInBlock:" << startOfFragmentInBlock;
if (layout->isValidCursorPosition(currentFragment.position() - startOfBlock)) {
// the start position for painting the decoration is the position of the fragment
// inside, but after the first line, the decoration always starts at the beginning
// of the line. See bug: 264471
int p1 = startOfFragmentInBlock;
if (i > firstLine) {
p1 = line.textStart();
}
// qDebug() << "\n\t\tblock.text.length:" << block.text().length() << "p1" << p1;
if (block.text().length() > p1 && block.text().at(p1) != QChar::ObjectReplacementCharacter) {
Q_ASSERT_X(line.isValid(), __FUNCTION__, QString("Invalid line=%1 first=%2 last=%3").arg(i).arg(firstLine).arg(lastLine).toLocal8Bit()); // see bug 278682
if (!line.isValid())
continue;
// end position: note that x2 can be smaller than x1 when we are handling RTL
int p2 = startOfFragmentInBlock + currentFragment.length();
int lineEndWithoutPreedit = line.textStart() + line.textLength();
if (block.layout()->preeditAreaPosition() >= block.position() + line.textStart() &&
block.layout()->preeditAreaPosition() <= block.position() + line.textStart() + line.textLength()) {
lineEndWithoutPreedit -= block.layout()->preeditAreaText().length();
}
while (lineEndWithoutPreedit > line.textStart() && block.text().at(lineEndWithoutPreedit - 1) == ' ') {
--lineEndWithoutPreedit;
}
if (lineEndWithoutPreedit < p2) { //line caps
p2 = lineEndWithoutPreedit;
}
int fragmentToLineOffset = qMax(startOfFragmentInBlock - line.textStart(), 0);
qreal x1 = line.cursorToX(p1);
qreal x2 = line.cursorToX(p2);
//qDebug() << "\n\t\t\tp1:" << p1 << "x1:" << x1
// << "\n\t\t\tp2:" << p2 << "x2:" << x2
// << "\n\t\t\tlineEndWithoutPreedit" << lineEndWithoutPreedit;
if (x1 != x2) {
drawStrikeOuts(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset);
drawOverlines(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset);
drawUnderlines(painter, fmt, currentFragment.text(), line, x1, x2, startOfFragmentInBlock, fragmentToLineOffset);
}
decorateTabsAndFormatting(painter, currentFragment, line, startOfFragmentInBlock, tabList, currentTabStop, showFormattingCharacters);
// underline preedit strings
if (layout->preeditAreaPosition() > -1) {
int start = block.layout()->preeditAreaPosition();
int end = block.layout()->preeditAreaPosition() + block.layout()->preeditAreaText().length();
QTextCharFormat underline;
underline.setFontUnderline(true);
underline.setUnderlineStyle(QTextCharFormat::DashUnderline);
//qDebug() << "underline style" << underline.underlineStyle();
//qDebug() << "line start: " << block.position() << line.textStart();
qreal z1 = 0;
qreal z2 = 0;
// preedit start in this line, end in this line
if ( start >= block.position() + line.textStart() &&
end <= block.position() + line.textStart() + line.textLength() ) {
z1 = line.cursorToX(start);
z2 = line.cursorToX(end);
}
// preedit start in this line, end after this line
if ( start >= block.position() + line.textStart() &&
end > block.position() + line.textStart() + line.textLength() ) {
z1 = line.cursorToX(start);
z2 = line.cursorToX(block.position() + line.textStart() + line.textLength());
}
// preedit start before this line, end in this line
if ( start < block.position() + line.textStart() &&
end <= block.position() + line.textStart() + line.textLength() ) {
z1 = line.cursorToX(block.position() + line.textStart());
z2 = line.cursorToX(end);
}
// preedit start before this line, end after this line
if ( start < block.position() + line.textStart() &&
end > block.position() + line.textStart() + line.textLength() ) {
z1 = line.cursorToX(block.position() + line.textStart());
z2 = line.cursorToX(block.position() + line.textStart() + line.textLength());
}
if (z2 > z1) {
//qDebug() << "z1: " << z1 << "z2: " << z2;
KoCharacterStyle::LineStyle fontUnderLineStyle = KoCharacterStyle::DashLine;
KoCharacterStyle::LineType fontUnderLineType = KoCharacterStyle::SingleLine;
QTextCharFormat::VerticalAlignment valign = fmt.verticalAlignment();
QFont font(fmt.font());
if (valign == QTextCharFormat::AlignSubScript
|| valign == QTextCharFormat::AlignSuperScript)
font.setPointSize(font.pointSize() * 2 / 3);
QFontMetricsF metrics(font, d->documentLayout->paintDevice());
qreal y = line.position().y();
if (valign == QTextCharFormat::AlignSubScript)
y += line.height() - metrics.descent() + metrics.underlinePos();
else if (valign == QTextCharFormat::AlignSuperScript)
y += metrics.ascent() + metrics.underlinePos();
else
y += line.ascent() + metrics.underlinePos();
QColor color = fmt.foreground().color();
qreal width = computeWidth( // line thickness
(KoCharacterStyle::LineWeight) underline.intProperty(KoCharacterStyle::UnderlineWeight),
underline.doubleProperty(KoCharacterStyle::UnderlineWidth),
font);
if (valign == QTextCharFormat::AlignSubScript
|| valign == QTextCharFormat::AlignSuperScript) // adjust size.
width = width * 2 / 3;
drawDecorationLine(painter, color, fontUnderLineType, fontUnderLineStyle, width, z1, z2, y);
}
}
}
}
}
}
}
if (showFormattingCharacters) {
QTextLine line = layout->lineForTextPosition(block.length()-1);
qreal y = line.position().y() + line.ascent();
qreal x = line.cursorToX(block.length()-1);
painter->drawText(QPointF(x, y), QChar((ushort)0x00B6));
}
if (showSpellChecking) {
// Finally let's paint our own spelling markings
// TODO Should we make this optional at this point (right on/off handled by the plugin)
// also we might want to provide alternative ways of drawing it
KoTextBlockData blockData(block);
QPen penBackup = painter->pen();
QPen pen;
pen.setColor(QColor(Qt::red));
pen.setWidthF(1.5);
QVector<qreal> pattern;
pattern << 1 << 2;
pen.setDashPattern(pattern);
painter->setPen(pen);
QList<KoTextBlockData::MarkupRange>::Iterator markIt = blockData.markupsBegin(KoTextBlockData::Misspell);
QList<KoTextBlockData::MarkupRange>::Iterator markEnd = blockData.markupsEnd(KoTextBlockData::Misspell);
for (int i = 0 ; i < layout->lineCount(); ++i) {
if (markIt == markEnd) {
break;
}
QTextLine line = layout->lineAt(i);
// the y position is placed half way between baseline and descent of the line
// this is fast and sufficient
qreal y = line.position().y() + line.ascent() + 0.5 * line.descent();
// first handle all those marking ranges that end on this line
while (markIt != markEnd && markIt->lastChar < line.textStart() + line.textLength()
&& line.textStart() + line.textLength() <= block.length()) {
if (!blockData.isMarkupsLayoutValid(KoTextBlockData::Misspell)) {
if (markIt->firstChar > line.textStart()) {
markIt->startX = line.cursorToX(markIt->firstChar);
}
markIt->endX = line.cursorToX(qMin(markIt->lastChar, block.length()));
}
qreal x1 = (markIt->firstChar > line.textStart()) ? markIt->startX : line.cursorToX(0);
painter->drawLine(QPointF(x1, y), QPointF(markIt->endX, y));
++markIt;
}
// there may be a markup range on this line that extends to the next line
if (markIt != markEnd && markIt->firstChar < line.textStart() + line.textLength()
&& line.textStart() + line.textLength() <= block.length()) {
if (!blockData.isMarkupsLayoutValid(KoTextBlockData::Misspell)) {
if (markIt->firstChar > line.textStart()) {
markIt->startX = line.cursorToX(markIt->firstChar);
}
}
qreal x1 = (markIt->firstChar > line.textStart()) ? markIt->startX : line.cursorToX(0);
painter->drawLine(QPointF(x1, y), QPointF(line.cursorToX(line.textStart() + line.textLength()), y));
// since it extends to next line we don't increment the iterator
}
}
blockData.setMarkupsLayoutValidity(KoTextBlockData::Misspell, true);
painter->setPen(penBackup);
}
painter->setFont(oldFont);
}
void KoTextLayoutArea::drawStrikeOuts(QPainter *painter, const QTextCharFormat &currentCharFormat, const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const
{
KoCharacterStyle::LineStyle strikeOutStyle = (KoCharacterStyle::LineStyle)
currentCharFormat.intProperty(KoCharacterStyle::StrikeOutStyle);
KoCharacterStyle::LineType strikeOutType = (KoCharacterStyle::LineType)
currentCharFormat.intProperty(KoCharacterStyle::StrikeOutType);
if ((strikeOutStyle != KoCharacterStyle::NoLineStyle) &&
(strikeOutType != KoCharacterStyle::NoLineType)) {
QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment();
QFont font(currentCharFormat.font());
if (valign == QTextCharFormat::AlignSubScript
|| valign == QTextCharFormat::AlignSuperScript)
font.setPointSize(qRound(font.pointSize() * 2 / 3.));
QFontMetricsF metrics(font, d->documentLayout->paintDevice());
qreal y = line.position().y();
if (valign == QTextCharFormat::AlignSubScript)
y += line.height() - metrics.descent() - metrics.strikeOutPos();
else if (valign == QTextCharFormat::AlignSuperScript)
y += metrics.ascent() - metrics.strikeOutPos();
else
y += line.ascent() - metrics.strikeOutPos();
QColor color = currentCharFormat.colorProperty(KoCharacterStyle::StrikeOutColor);
if (!color.isValid())
color = currentCharFormat.foreground().color();
KoCharacterStyle::LineMode strikeOutMode =
(KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutMode);
QString strikeOutText = currentCharFormat.stringProperty(KoCharacterStyle::StrikeOutText);
qreal width = 0; // line thickness
if (strikeOutText.isEmpty()) {
width = computeWidth(
(KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::StrikeOutWeight),
currentCharFormat.doubleProperty(KoCharacterStyle::StrikeOutWidth),
font);
}
if (valign == QTextCharFormat::AlignSubScript
|| valign == QTextCharFormat::AlignSuperScript) // adjust size.
width = width * 2 / 3;
if (strikeOutMode == KoCharacterStyle::SkipWhiteSpaceLineMode) {
drawDecorationWords(painter, line, text, color, strikeOutType,
strikeOutStyle, strikeOutText, width, y, fragmentToLineOffset,
startOfFragmentInBlock);
} else {
if (strikeOutText.isEmpty())
drawDecorationLine(painter, color, strikeOutType, strikeOutStyle, width, x1, x2, y);
else
drawDecorationText(painter, line, color, strikeOutText, x1, x2);
}
}
}
void KoTextLayoutArea::drawOverlines(QPainter *painter, const QTextCharFormat &currentCharFormat, const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const
{
KoCharacterStyle::LineStyle fontOverLineStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::OverlineStyle);
KoCharacterStyle::LineType fontOverLineType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::OverlineType);
if ((fontOverLineStyle != KoCharacterStyle::NoLineStyle) &&
(fontOverLineType != KoCharacterStyle::NoLineType)) {
QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment();
QFont font(currentCharFormat.font());
if (valign == QTextCharFormat::AlignSubScript
|| valign == QTextCharFormat::AlignSuperScript)
font.setPointSize(font.pointSize() * 2 / 3);
QFontMetricsF metrics(font, d->documentLayout->paintDevice());
qreal y = line.position().y();
if (valign == QTextCharFormat::AlignSubScript)
y += line.height() - metrics.descent() - metrics.overlinePos();
else if (valign == QTextCharFormat::AlignSuperScript)
y += metrics.ascent() - metrics.overlinePos();
else
y += line.ascent() - metrics.overlinePos();
QColor color = currentCharFormat.colorProperty(KoCharacterStyle::OverlineColor);
if (!color.isValid())
color = currentCharFormat.foreground().color();
KoCharacterStyle::LineMode overlineMode =
(KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::OverlineMode);
qreal width = computeWidth( // line thickness
(KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::OverlineWeight),
currentCharFormat.doubleProperty(KoCharacterStyle::OverlineWidth),
font);
if (valign == QTextCharFormat::AlignSubScript
|| valign == QTextCharFormat::AlignSuperScript) // adjust size.
width = width * 2 / 3;
if (overlineMode == KoCharacterStyle::SkipWhiteSpaceLineMode) {
drawDecorationWords(painter, line, text, color, fontOverLineType,
fontOverLineStyle, QString(), width, y, fragmentToLineOffset, startOfFragmentInBlock);
} else {
drawDecorationLine(painter, color, fontOverLineType, fontOverLineStyle, width, x1, x2, y);
}
}
}
void KoTextLayoutArea::drawUnderlines(QPainter *painter, const QTextCharFormat &currentCharFormat,const QString &text, const QTextLine &line, qreal x1, qreal x2, const int startOfFragmentInBlock, const int fragmentToLineOffset) const
{
KoCharacterStyle::LineStyle fontUnderLineStyle = (KoCharacterStyle::LineStyle) currentCharFormat.intProperty(KoCharacterStyle::UnderlineStyle);
KoCharacterStyle::LineType fontUnderLineType = (KoCharacterStyle::LineType) currentCharFormat.intProperty(KoCharacterStyle::UnderlineType);
if ((fontUnderLineStyle != KoCharacterStyle::NoLineStyle) &&
(fontUnderLineType != KoCharacterStyle::NoLineType)) {
QTextCharFormat::VerticalAlignment valign = currentCharFormat.verticalAlignment();
QFont font(currentCharFormat.font());
if (valign == QTextCharFormat::AlignSubScript
|| valign == QTextCharFormat::AlignSuperScript)
font.setPointSize(font.pointSize() * 2 / 3);
QFontMetricsF metrics(font, d->documentLayout->paintDevice());
qreal y = line.position().y();
if (valign == QTextCharFormat::AlignSubScript)
y += line.height() - metrics.descent() + metrics.underlinePos();
else if (valign == QTextCharFormat::AlignSuperScript)
y += metrics.ascent() + metrics.underlinePos();
else
y += line.ascent() + metrics.underlinePos();
QColor color = currentCharFormat.underlineColor();
if (!color.isValid())
color = currentCharFormat.foreground().color();
KoCharacterStyle::LineMode underlineMode =
(KoCharacterStyle::LineMode) currentCharFormat.intProperty(KoCharacterStyle::UnderlineMode);
qreal width = computeWidth( // line thickness
(KoCharacterStyle::LineWeight) currentCharFormat.intProperty(KoCharacterStyle::UnderlineWeight),
currentCharFormat.doubleProperty(KoCharacterStyle::UnderlineWidth),
font);
if (valign == QTextCharFormat::AlignSubScript
|| valign == QTextCharFormat::AlignSuperScript) // adjust size.
width = width * 2 / 3;
if (underlineMode == KoCharacterStyle::SkipWhiteSpaceLineMode) {
drawDecorationWords(painter, line, text, color, fontUnderLineType,
fontUnderLineStyle, QString(), width, y, fragmentToLineOffset, startOfFragmentInBlock);
} else {
drawDecorationLine(painter, color, fontUnderLineType, fontUnderLineStyle, width, x1, x2, y);
}
}
}
// Decorate any tabs ('\t's) in 'currentFragment' and laid out in 'line'.
int KoTextLayoutArea::decorateTabsAndFormatting(QPainter *painter, const QTextFragment& currentFragment, const QTextLine &line, const int startOfFragmentInBlock, const QVariantList& tabList, int currentTabStop, bool showFormattingCharacters)
{
// If a line in the layout represent multiple text fragments, this function will
// be called multiple times on the same line, with different fragments.
// Likewise, if a fragment spans two lines, then this function will be called twice
// on the same fragment, once for each line.
QString fragText = currentFragment.text();
QFontMetricsF fm(currentFragment.charFormat().font(), d->documentLayout->paintDevice());
qreal tabStyleLineMargin = fm.averageCharWidth() / 4; // leave some margin for the tab decoration line
// currentFragment.position() : start of this fragment w.r.t. the document
// startOfFragmentInBlock : start of this fragment w.r.t. the block
// line.textStart() : start of this line w.r.t. the block
int searchForCharFrom; // search for \t from this point onwards in fragText
int searchForCharTill; // search for \t till this point in fragText
if (line.textStart() >= startOfFragmentInBlock) { // fragment starts at or before the start of line
// we are concerned with only that part of the fragment displayed in this line
searchForCharFrom = line.textStart() - startOfFragmentInBlock;
// It's a new line. So we should look at the first tab-stop properties for the next \t.
currentTabStop = 0;
} else { // fragment starts in the middle of the line
searchForCharFrom = 0;
}
if (line.textStart() + line.textLength() > startOfFragmentInBlock + currentFragment.length()) {
// fragment ends before the end of line. need to see only till the end of the fragment.
searchForCharTill = currentFragment.length();
} else {
// line ends before the fragment ends. need to see only till the end of this line.
// but then, we need to convert the end of line to an index into fragText
searchForCharTill = line.textLength() + line.textStart() - startOfFragmentInBlock;
}
for (int i = searchForCharFrom ; i < searchForCharTill; i++) {
if (currentTabStop >= tabList.size() && !showFormattingCharacters) // no more decorations
break;
if (fragText[i] == '\t') {
qreal x1(0.0);
qreal x2(0.0);
if (showFormattingCharacters) {
x1 = line.cursorToX(startOfFragmentInBlock + i);
x2 = line.cursorToX(startOfFragmentInBlock + i + 1);
qreal y = line.position().y() + line.ascent() - fm.xHeight()/2.0;
qreal arrowDim = fm.xHeight()/2.0;
QPen penBackup = painter->pen();
QPen pen = painter->pen();
pen.setWidthF(fm.ascent()/10.0);
pen.setStyle(Qt::SolidLine);
painter->setPen(pen);
painter->drawLine(QPointF(x1, y), QPointF(x2, y));
painter->drawLine(QPointF(x2 - arrowDim, y - arrowDim), QPointF(x2, y));
painter->drawLine(QPointF(x2 - arrowDim, y + arrowDim), QPointF(x2, y));
painter->setPen(penBackup);
}
if (currentTabStop < tabList.size()) { // still tabsstops worth examining
if (!showFormattingCharacters) {
// only then was it not calculated
x1 = line.cursorToX(startOfFragmentInBlock + i);
}
// find a tab-stop decoration for this tab position
// for eg., if there's a tab-stop at 1in, but the text before \t already spans 1.2in,
// we should look at the next tab-stop
KoText::Tab tab;
do {
tab = qvariant_cast<KoText::Tab>(tabList[currentTabStop]);
currentTabStop++;
// comparing with x1 should work for all of left/right/center/char tabs
} while (tab.position <= x1 && currentTabStop < tabList.size());
if (tab.position > x1) {
if (!showFormattingCharacters) {
// only then was it not calculated
x2 = line.cursorToX(startOfFragmentInBlock + i + 1);
}
qreal tabStyleLeftLineMargin = tabStyleLineMargin;
qreal tabStyleRightLineMargin = tabStyleLineMargin;
// no margin if its adjacent char is also a tab
if (i > searchForCharFrom && fragText[i-1] == '\t')
tabStyleLeftLineMargin = 0;
if (i < (searchForCharTill - 1) && fragText[i+1] == '\t')
tabStyleRightLineMargin = 0;
qreal y = line.position().y() + line.ascent() - 1;
x1 += tabStyleLeftLineMargin;
x2 -= tabStyleRightLineMargin;
QColor tabDecorColor = currentFragment.charFormat().foreground().color();
if (tab.leaderColor.isValid())
tabDecorColor = tab.leaderColor;
qreal width = computeWidth(tab.leaderWeight, tab.leaderWidth, painter->font());
if (x1 < x2) {
if (tab.leaderText.isEmpty()) {
drawDecorationLine(painter, tabDecorColor, tab.leaderType, tab.leaderStyle, width, x1, x2, y);
} else {
drawDecorationText(painter, line, tabDecorColor, tab.leaderText, x1, x2);
}
}
}
}
} else if (showFormattingCharacters) {
if (fragText[i] == ' ' || fragText[i] == QChar::Nbsp) {
qreal x = line.cursorToX(startOfFragmentInBlock + i);
qreal y = line.position().y() + line.ascent();
painter->drawText(QPointF(x, y), QChar((ushort)0xb7));
} else if (fragText[i] == QChar::LineSeparator){
qreal x = line.cursorToX(startOfFragmentInBlock + i);
qreal y = line.position().y() + line.ascent();
painter->drawText(QPointF(x, y), QChar((ushort)0x21B5));
}
}
}
return currentTabStop;
}
diff --git a/plugins/flake/textshape/textlayout/KoTextLayoutObstruction.cpp b/plugins/flake/textshape/textlayout/KoTextLayoutObstruction.cpp
index 3e0c838d6e..6860673ad1 100644
--- a/plugins/flake/textshape/textlayout/KoTextLayoutObstruction.cpp
+++ b/plugins/flake/textshape/textlayout/KoTextLayoutObstruction.cpp
@@ -1,321 +1,321 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 Ko Gmbh <cbo@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 "KoTextLayoutObstruction.h"
#include <KoShapeStrokeModel.h>
#include <KoShapeShadow.h>
#include <KoShapeGroup.h>
#include <KoClipPath.h>
#include <KoInsets.h>
#include <QTransform>
#include <QPainterPath>
KoTextLayoutObstruction::KoTextLayoutObstruction(KoShape *shape, const QTransform &matrix)
: m_side(None),
m_polygon(QPolygonF()),
m_line(QRectF()),
m_shape(shape),
m_runAroundThreshold(0)
{
qreal borderHalfWidth;
QPainterPath path = decoratedOutline(m_shape, borderHalfWidth);
//TODO check if path is convex. otherwise do triangulation and create more convex obstructions
init(matrix, path, shape->textRunAroundDistanceLeft(), shape->textRunAroundDistanceTop(), shape->textRunAroundDistanceRight(), shape->textRunAroundDistanceBottom(), borderHalfWidth);
if (shape->textRunAroundSide() == KoShape::NoRunAround) {
// make the shape take the full width of the text area
m_side = Empty;
} else if (shape->textRunAroundSide() == KoShape::RunThrough) {
m_distanceLeft = 0;
m_distanceTop = 0;
m_distanceRight = 0;
m_distanceBottom = 0;
// We don't exist.
return;
} else if (shape->textRunAroundSide() == KoShape::LeftRunAroundSide) {
m_side = Left;
} else if (shape->textRunAroundSide() == KoShape::RightRunAroundSide) {
m_side = Right;
} else if (shape->textRunAroundSide() == KoShape::BothRunAroundSide) {
m_side = Both;
} else if (shape->textRunAroundSide() == KoShape::BiggestRunAroundSide) {
m_side = Bigger;
} else if (shape->textRunAroundSide() == KoShape::EnoughRunAroundSide) {
m_side = Enough;
m_runAroundThreshold = shape->textRunAroundThreshold();
}
}
KoTextLayoutObstruction::KoTextLayoutObstruction(const QRectF &rect, bool rtl)
: m_side(None),
m_polygon(QPolygonF()),
m_line(QRectF()),
m_shape(0),
m_runAroundThreshold(0)
{
qreal borderHalfWidth = 0;
qreal textRunAroundDistance = 1;
QPainterPath path;
path.addRect(rect);
init(QTransform(), path, textRunAroundDistance, 0.0, textRunAroundDistance, 0.0, borderHalfWidth);
if (rtl) {
m_side = Right;
} else {
m_side = Left;
}
}
QPainterPath KoTextLayoutObstruction::decoratedOutline(const KoShape *shape, qreal &borderHalfWidth) const
{
const KoShapeGroup *shapeGroup = dynamic_cast<const KoShapeGroup *>(shape);
if (shapeGroup) {
QPainterPath groupPath;
foreach (const KoShape *child, shapeGroup->shapes()) {
groupPath += decoratedOutline(child, borderHalfWidth);
}
return groupPath;
}
QPainterPath path;
if (shape->textRunAroundContour() != KoShape::ContourBox) {
KoClipPath *clipPath = shape->clipPath();
if (clipPath) {
path = clipPath->pathForSize(shape->size());
} else {
path = shape->outline();
}
} else {
path.addRect(shape->outlineRect());
}
QRectF bb = shape->outlineRect();
borderHalfWidth = 0;
if (shape->stroke()) {
KoInsets insets;
shape->stroke()->strokeInsets(shape, insets);
/*
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
path = QPainterPath();
path.addRect(bb);
*/
borderHalfWidth = qMax(qMax(insets.left, insets.top),qMax(insets.right, insets.bottom));
}
if (shape->shadow() && shape->shadow()->isVisible()) {
- QTransform transform = shape->absoluteTransformation(0);
+ QTransform transform = shape->absoluteTransformation();
bb = transform.mapRect(bb);
KoInsets insets;
shape->shadow()->insets(insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
path = QPainterPath();
path.addRect(bb);
path = transform.inverted().map(path);
}
return path;
}
void KoTextLayoutObstruction::init(const QTransform &matrix, const QPainterPath &obstruction, qreal distanceLeft, qreal distanceTop, qreal distanceRight, qreal distanceBottom, qreal borderHalfWidth)
{
m_distanceLeft = distanceLeft;
m_distanceTop = distanceTop;
m_distanceRight = distanceRight;
m_distanceBottom = distanceBottom;
QPainterPath path = matrix.map(obstruction);
distanceLeft += borderHalfWidth;
distanceTop += borderHalfWidth;
distanceRight += borderHalfWidth;
distanceBottom += borderHalfWidth;
qreal extraWidth = distanceLeft + distanceRight;
qreal extraHeight = distanceTop + distanceBottom;
if (extraWidth != 0.0 || extraHeight != 0.0) {
// Let's extend the outline with at least the border half width in all directions.
// However since the distance can be express in 4 directions and QPainterPathStroker only
// handles a penWidth we do some tricks to get it working.
//
// Explanation in one dimension only: we sum the distances top and below and use that as the
// penWidth. Afterwards we translate the result so it is distributed correctly by top and bottom.
// Now by doing that we would also implicitly set the left+right size of the pen which is no good,
// so in order to set that to a minimal value (we choose 1, as 0 would give division by 0) we do
// the following:. We scale the original path by sumX, stroke the path with penwidth=sumY, then
// scale it back. Effectively we have now stroked with a pen sized 1 x sumY.
//
// The math to do both x an y in one go becomes a little more complex, but only a little.
extraWidth = qMax(qreal(0.1), extraWidth);
extraHeight = qMax(qreal(0.1), extraHeight);
QPainterPathStroker stroker;
stroker.setWidth(extraWidth);
stroker.setJoinStyle(Qt::MiterJoin);
stroker.setCapStyle(Qt::SquareCap);
QPainterPath bigPath = stroker.createStroke(QTransform().scale(1.0, extraWidth / extraHeight).map(path));
bigPath = QTransform().scale(1.0, extraHeight / extraWidth). map(bigPath);
path += bigPath.translated(extraWidth / 2 - distanceLeft, extraHeight / 2 - distanceTop);
}
m_bounds = path.boundingRect();
// Now we need to change the path into a polygon for easier handling later on
m_polygon = path.toFillPolygon();
QPointF prev = *(m_polygon.begin());
foreach (const QPointF &vtx, m_polygon) { //initialized edges
if (vtx.x() == prev.x() && vtx.y() == prev.y())
continue;
QLineF line;
if (prev.y() < vtx.y()) // Make sure the vector lines all point downwards.
line = QLineF(prev, vtx);
else
line = QLineF(vtx, prev);
m_edges.insert(line.y1(), line);
prev = vtx;
}
}
qreal KoTextLayoutObstruction::xAtY(const QLineF &line, qreal y)
{
if (line.dx() == 0)
return line.x1();
return line.x1() + (y - line.y1()) / line.dy() * line.dx();
}
void KoTextLayoutObstruction::changeMatrix(const QTransform &matrix)
{
m_edges.clear();
qreal borderHalfWidth;
QPainterPath path = decoratedOutline(m_shape, borderHalfWidth);
init(matrix, path, m_distanceLeft, m_distanceTop, m_distanceRight, m_distanceBottom, borderHalfWidth);
}
QRectF KoTextLayoutObstruction::cropToLine(const QRectF &lineRect)
{
if (m_bounds.intersects(lineRect)) {
m_line = lineRect;
bool untilFirst = true;
//check inner points
foreach (const QPointF &point, m_polygon) {
if (lineRect.contains(point)) {
if (untilFirst) {
m_line.setLeft(point.x());
m_line.setRight(point.x());
untilFirst = false;
} else {
if (point.x() < m_line.left()) {
m_line.setLeft(point.x());
} else if (point.x() > m_line.right()) {
m_line.setRight(point.x());
}
}
}
}
//check edges
qreal points[2] = { lineRect.top(), lineRect.bottom() };
for (int i = 0; i < 2; i++) {
const qreal y = points[i];
QMap<qreal, QLineF>::const_iterator iter = m_edges.constBegin();
for (;iter != m_edges.constEnd(); ++iter) {
QLineF line = iter.value();
if (line.y2() < y) // not a section that will intersect with ou Y yet
continue;
if (line.y1() > y) // section is below our Y, so abort loop
//break;
continue;
if (qAbs(line.dy()) < 1E-10) // horizontal lines don't concern us.
continue;
qreal intersect = xAtY(iter.value(), y);
if (untilFirst) {
m_line.setLeft(intersect);
m_line.setRight(intersect);
untilFirst = false;
} else {
if (intersect < m_line.left()) {
m_line.setLeft(intersect);
} else if (intersect > m_line.right()) {
m_line.setRight(intersect);
}
}
}
}
} else {
m_line = QRectF();
}
return m_line;
}
QRectF KoTextLayoutObstruction::getLeftLinePart(const QRectF &lineRect) const
{
QRectF leftLinePart = lineRect;
leftLinePart.setRight(m_line.left());
return leftLinePart;
}
QRectF KoTextLayoutObstruction::getRightLinePart(const QRectF &lineRect) const
{
QRectF rightLinePart = lineRect;
if (m_line.right() > rightLinePart.left()) {
rightLinePart.setLeft(m_line.right());
}
return rightLinePart;
}
bool KoTextLayoutObstruction::textOnLeft() const
{
return m_side == Left;
}
bool KoTextLayoutObstruction::textOnRight() const
{
return m_side == Right;
}
bool KoTextLayoutObstruction::textOnBiggerSide() const
{
return m_side == Bigger;
}
bool KoTextLayoutObstruction::textOnEnoughSides() const
{
return m_side == Enough;
}
qreal KoTextLayoutObstruction::runAroundThreshold() const
{
return m_runAroundThreshold;
}
bool KoTextLayoutObstruction::noTextAround() const
{
return m_side == Empty;
}
bool KoTextLayoutObstruction::compareRectLeft(KoTextLayoutObstruction *o1, KoTextLayoutObstruction *o2)
{
return o1->m_line.left() < o2->m_line.left();
}
diff --git a/plugins/flake/textshape/textlayout/KoTextShapeContainerModel.cpp b/plugins/flake/textshape/textlayout/KoTextShapeContainerModel.cpp
index 74eb69438e..43fc529794 100644
--- a/plugins/flake/textshape/textlayout/KoTextShapeContainerModel.cpp
+++ b/plugins/flake/textshape/textlayout/KoTextShapeContainerModel.cpp
@@ -1,239 +1,240 @@
/* This file is part of the KDE project
* Copyright (C) 2007,2009,2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 C. Boemann <cbo@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 "KoTextShapeContainerModel.h"
#include "KoAnchorInlineObject.h"
#include "KoTextShapeData.h"
#include "KoShapeContainer.h"
#include <QTextBlock>
#include <QTextLayout>
#include <QTextLine>
#include <QTextDocument>
#include <TextLayoutDebug.h>
struct Relation
{
Relation(KoShape *shape = 0)
: child(shape),
anchor(0),
nested(false),
inheritsTransform(false)
{
}
KoShape *child;
KoShapeAnchor *anchor;
uint nested : 1;
uint inheritsTransform :1;
};
class Q_DECL_HIDDEN KoTextShapeContainerModel::Private
{
public:
QHash<const KoShape*, Relation> children;
QList<KoShapeAnchor *> shapeRemovedAnchors;
};
KoTextShapeContainerModel::KoTextShapeContainerModel()
: d(new Private())
{
}
KoTextShapeContainerModel::~KoTextShapeContainerModel()
{
delete d;
}
void KoTextShapeContainerModel::add(KoShape *child)
{
if (d->children.contains(child))
return;
Relation relation(child);
d->children.insert(child, relation);
KoShapeAnchor *toBeAddedAnchor = 0;
foreach (KoShapeAnchor *anchor, d->shapeRemovedAnchors) {
if (child == anchor->shape()) {
toBeAddedAnchor = anchor;
break;
}
}
if (toBeAddedAnchor) {
addAnchor(toBeAddedAnchor);
d->shapeRemovedAnchors.removeAll(toBeAddedAnchor);
}
}
void KoTextShapeContainerModel::remove(KoShape *child)
{
Relation relation = d->children.value(child);
d->children.remove(child);
if (relation.anchor) {
relation.anchor->placementStrategy()->detachFromModel();
d->shapeRemovedAnchors.append(relation.anchor);
}
}
void KoTextShapeContainerModel::setClipped(const KoShape *child, bool clipping)
{
Q_ASSERT(d->children.contains(child));
d->children[child].nested = clipping;
}
bool KoTextShapeContainerModel::isClipped(const KoShape *child) const
{
Q_ASSERT(d->children.contains(child));
return d->children[child].nested;
}
void KoTextShapeContainerModel::setInheritsTransform(const KoShape *shape, bool inherit)
{
Q_ASSERT(d->children.contains(shape));
d->children[shape].inheritsTransform = inherit;
}
bool KoTextShapeContainerModel::inheritsTransform(const KoShape *shape) const
{
Q_ASSERT(d->children.contains(shape));
return d->children[shape].inheritsTransform;
}
int KoTextShapeContainerModel::count() const
{
return d->children.count();
}
QList<KoShape*> KoTextShapeContainerModel::shapes() const
{
QList<KoShape*> answer;
answer.reserve(d->children.count());
foreach (const Relation &relation, d->children) {
answer << relation.child;
}
return answer;
}
void KoTextShapeContainerModel::containerChanged(KoShapeContainer *container, KoShape::ChangeType type)
{
Q_UNUSED(container);
Q_UNUSED(type);
}
void KoTextShapeContainerModel::childChanged(KoShape *child, KoShape::ChangeType type)
{
if (((type == KoShape::RotationChanged ||
type == KoShape::ScaleChanged ||
type == KoShape::ShearChanged ||
type == KoShape::ClipPathChanged ||
+ type == KoShape::ClipMaskChanged ||
type == KoShape::PositionChanged ||
type == KoShape::SizeChanged) && child->textRunAroundSide() != KoShape::RunThrough) ||
type == KoShape::TextRunAroundChanged) {
relayoutInlineObject(child);
}
KoShapeContainerModel::childChanged( child, type );
}
void KoTextShapeContainerModel::addAnchor(KoShapeAnchor *anchor)
{
Q_ASSERT(anchor);
Q_ASSERT(anchor->shape());
Q_ASSERT(d->children.contains(anchor->shape()));
d->children[anchor->shape()].anchor = anchor;
}
void KoTextShapeContainerModel::removeAnchor(KoShapeAnchor *anchor)
{
if (d->children.contains(anchor->shape())) {
d->children[anchor->shape()].anchor = 0;
d->shapeRemovedAnchors.removeAll(anchor);
}
}
void KoTextShapeContainerModel::proposeMove(KoShape *child, QPointF &move)
{
if (!d->children.contains(child))
return;
Relation relation = d->children.value(child);
if (relation.anchor == 0)
return;
QPointF newPosition = child->position() + move/* + relation.anchor->offset()*/;
const QRectF parentShapeRect(QPointF(0, 0), child->parent()->size());
//warnTextLayout <<"proposeMove:" /*<< move <<" |"*/ << newPosition <<" |" << parentShapeRect;
QTextLayout *layout = 0;
int anchorPosInParag = -1;
if (relation.anchor->anchorType() == KoShapeAnchor::AnchorAsCharacter) {
int posInDocument = relation.anchor->textLocation()->position();
const QTextDocument *document = relation.anchor->textLocation()->document();
QTextBlock block = document->findBlock(posInDocument);
layout = block.layout();
anchorPosInParag = posInDocument - block.position();
if (layout) {
QTextLine tl = layout->lineForTextPosition(anchorPosInParag);
Q_ASSERT(tl.isValid());
relation.anchor->setOffset(QPointF(newPosition.x() - tl.cursorToX(anchorPosInParag)
+ tl.x(), 0));
relayoutInlineObject(child);
}
// the rest of the code uses the shape baseline, at this time the bottom. So adjust
newPosition.setY(newPosition.y() + child->size().height());
if (layout == 0) {
QTextBlock block = document->findBlock(posInDocument);
layout = block.layout();
anchorPosInParag = posInDocument - block.position();
}
if (layout->lineCount() > 0) {
KoTextShapeData *data = qobject_cast<KoTextShapeData*>(child->parent()->userData());
Q_ASSERT(data);
QTextLine tl = layout->lineForTextPosition(anchorPosInParag);
Q_ASSERT(tl.isValid());
qreal y = tl.y() - data->documentOffset() - newPosition.y() + child->size().height();
relation.anchor->setOffset(QPointF(relation.anchor->offset().x(), -y));
relayoutInlineObject(child);
}
} else {
//TODO pavolk: handle position type change: absolute to relative, etc ..
child->setPosition(newPosition);
relation.anchor->setOffset(relation.anchor->offset() + move);
relayoutInlineObject(child);
}
move.setX(0); // let the text layout move it.
move.setY(0);
}
void KoTextShapeContainerModel::relayoutInlineObject(KoShape *child)
{
if (child == 0) {
return;
}
KoTextShapeData *data = qobject_cast<KoTextShapeData*>(child->parent()->userData());
Q_ASSERT(data);
data->setDirty();
}
diff --git a/plugins/flake/textshape/textlayout/ListItemsHelper.cpp b/plugins/flake/textshape/textlayout/ListItemsHelper.cpp
index aa30260f90..d3b2709dd3 100644
--- a/plugins/flake/textshape/textlayout/ListItemsHelper.cpp
+++ b/plugins/flake/textshape/textlayout/ListItemsHelper.cpp
@@ -1,503 +1,503 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
*
* 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 "ListItemsHelper.h"
#include <KoTextBlockData.h>
#include <KoParagraphStyle.h>
#include <KoTextDocument.h>
#include <KoList.h>
#include <TextLayoutDebug.h>
#include <klocalizedstring.h>
#include <QTextList>
using namespace Lists;
QString Lists::intToRoman(int n)
{
static const QString RNUnits[] = {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"};
static const QString RNTens[] = {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"};
static const QString RNHundreds[] = {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"};
static const QString RNThousands[] = {"", "m", "mm", "mmm", "mmmm", "mmmmm", "mmmmmm", "mmmmmmm", "mmmmmmmm", "mmmmmmmmm"};
if (n <= 0) {
warnTextLayout << "intToRoman called with negative number: n=" << n;
return QString::number(n);
}
return RNThousands[(n / 1000)] +
RNHundreds[(n / 100) % 10 ] +
RNTens[(n / 10) % 10 ] +
RNUnits[(n) % 10 ];
}
QString Lists::intToAlpha(int n, Capitalisation caps, bool letterSynchronization)
{
const char offset = caps == Uppercase ? 'A' : 'a';
QString answer;
if (letterSynchronization) {
int digits = 1;
for (; n > 26; n -= 26)
digits += 1;
for (int i = 0; i < digits; i++)
answer.prepend(QChar(offset + n - 1));
return answer;
} else {
char bottomDigit;
while (n > 26) {
bottomDigit = (n - 1) % 26;
n = (n - 1) / 26;
answer.prepend(QChar(offset + bottomDigit));
}
}
answer.prepend(QChar(offset + n - 1));
return answer;
}
QString Lists::intToScript(int n, KoListStyle::Style type)
{
// 10-base
static const int bengali = 0x9e6;
static const int gujarati = 0xae6;
static const int gurumukhi = 0xa66;
static const int kannada = 0xce6;
static const int malayalam = 0xd66;
static const int oriya = 0xb66;
static const int tamil = 0x0be6;
static const int telugu = 0xc66;
static const int tibetan = 0xf20;
static const int thai = 0xe50;
int offset;
switch (type) {
case KoListStyle::Bengali:
offset = bengali;
break;
case KoListStyle::Gujarati:
offset = gujarati;
break;
case KoListStyle::Gurumukhi:
offset = gurumukhi;
break;
case KoListStyle::Kannada:
offset = kannada;
break;
case KoListStyle::Malayalam:
offset = malayalam;
break;
case KoListStyle::Oriya:
offset = oriya;
break;
case KoListStyle::Tamil:
offset = tamil;
break;
case KoListStyle::Telugu:
offset = telugu;
break;
case KoListStyle::Tibetan:
offset = tibetan;
break;
case KoListStyle::Thai:
offset = thai;
break;
default:
return QString::number(n);
}
QString answer;
while (n > 0) {
answer.prepend(QChar(offset + n % 10));
n = n / 10;
}
return answer;
}
QString Lists::intToScriptList(int n, KoListStyle::Style type)
{
// 1 time Sequences
// note; the leading X is to make these 1 based.
static const char* const Abjad[] = { "أ", "ب", "ج", "د", "ﻫ", "و", "ز", "ح", "ط", "ي", "ك", "ل", "م",
"ن", "س", "ع", "ف", "ص", "ق", "ر", "ش", "ت", "ث", "خ", "ذ", "ض", "ظ", "غ"
};
static const char* const Abjad2[] = { "ﺃ", "ﺏ", "ﺝ", "ﺩ", "ﻫ", "ﻭ", "ﺯ", "ﺡ", "ﻁ", "ﻱ", "ﻙ", "ﻝ", "ﻡ",
"ﻥ", "ﺹ", "ﻉ", "ﻑ", "ﺽ", "ﻕ", "ﺭ", "ﺱ", "ﺕ", "ﺙ", "ﺥ", "ﺫ", "ﻅ", "ﻍ", "ﺵ"
};
static const char* const ArabicAlphabet[] = {"ا", "ب", "ت", "ث", "ج", "ح", "خ", "د", "ذ", "ر", "ز",
"س", "ش", "ص", "ض", "ط", "ظ", "ع", "غ", "ف", "ق", "ك", "ل", "م", "ن", "ه", "و", "ي"
};
/*
// see this page for the 10, 100, 1000 etc http://en.wikipedia.org/wiki/Chinese_numerals
static const char* const chinese1[] = { '零','壹','貳','叄','肆','伍','陸','柒','捌','玖' };
static const char* const chinese2[] = { '〇','一','二','三','四','五','六','七','八','九' };
- TODO: http://en.wikipedia.org/wiki/Korean_numerals
- http://en.wikipedia.org/wiki/Japanese_numerals
- 'http://en.wikipedia.org/wiki/Hebrew_numerals'
- 'http://en.wikipedia.org/wiki/Armenian_numerals'
- 'http://en.wikipedia.org/wiki/Greek_numerals'
- 'http://en.wikipedia.org/wiki/Cyrillic_numerals'
- 'http://en.wikipedia.org/wiki/Sanskrit_numerals'
- 'http://en.wikipedia.org/wiki/Ge%27ez_alphabet#Numerals'
- 'http://en.wikipedia.org/wiki/Abjad_numerals'
+ TODO: https://en.wikipedia.org/wiki/Korean_numerals
+ https://en.wikipedia.org/wiki/Japanese_numerals
+ 'https://en.wikipedia.org/wiki/Hebrew_numerals'
+ 'https://en.wikipedia.org/wiki/Armenian_numerals'
+ 'https://en.wikipedia.org/wiki/Greek_numerals'
+ 'https://en.wikipedia.org/wiki/Cyrillic_numerals'
+ 'https://en.wikipedia.org/wiki/Sanskrit_numerals'
+ 'https://en.wikipedia.org/wiki/Ge%27ez_alphabet#Numerals'
+ 'https://en.wikipedia.org/wiki/Abjad_numerals'
*/
switch (type) {
case KoListStyle::Abjad:
if (n > 22) return "*";
return QString::fromUtf8(Abjad[n-1]);
case KoListStyle::AbjadMinor:
if (n > 22) return "*";
return QString::fromUtf8(Abjad2[n-1]);
case KoListStyle::ArabicAlphabet:
if (n > 28) return "*";
return QString::fromUtf8(ArabicAlphabet[n-1]);
default:
return QString::number(n);
}
}
QString Lists::intToNumberingStyle(int index, KoListStyle::Style listStyle, bool letterSynchronization)
{
QString counterText;
switch(listStyle) {
case KoListStyle::DecimalItem:
counterText = QString::number(index);
break;
case KoListStyle::AlphaLowerItem:
counterText = intToAlpha(index, Lowercase, letterSynchronization);
break;
case KoListStyle::UpperAlphaItem:
counterText = intToAlpha(index, Uppercase, letterSynchronization);
break;
case KoListStyle::RomanLowerItem:
counterText = intToRoman(index);
break;
case KoListStyle::UpperRomanItem:
counterText = intToRoman(index).toUpper();
break;
case KoListStyle::Bengali:
case KoListStyle::Gujarati:
case KoListStyle::Gurumukhi:
case KoListStyle::Kannada:
case KoListStyle::Malayalam:
case KoListStyle::Oriya:
case KoListStyle::Tamil:
case KoListStyle::Telugu:
case KoListStyle::Tibetan:
case KoListStyle::Thai:
counterText = intToScript(index, listStyle);
break;
case KoListStyle::Abjad:
case KoListStyle::ArabicAlphabet:
case KoListStyle::AbjadMinor:
counterText = intToScriptList(index, listStyle);
break;
default:
counterText = QString::number(index);
}
return counterText;
}
QList<ListStyleItem> Lists::genericListStyleItems()
{
QList<ListStyleItem> answer;
answer.append(ListStyleItem(i18nc("Text list-style", "None"), KoListStyle::None));
answer.append(ListStyleItem(i18n("Small Bullet"), KoListStyle::Bullet));
answer.append(ListStyleItem(i18n("Circle Bullet"), KoListStyle::CircleItem));
answer.append(ListStyleItem(i18n("Square Bullet"), KoListStyle::SquareItem));
answer.append(ListStyleItem(i18n("Rhombus Bullet"), KoListStyle::RhombusItem));
answer.append(ListStyleItem(i18n("Check Mark Bullet"), KoListStyle::HeavyCheckMarkItem));
answer.append(ListStyleItem(i18n("Rightwards Arrow Bullet"), KoListStyle::RightArrowItem));
answer.append(ListStyleItem(i18n("Arabic"), KoListStyle::DecimalItem));
answer.append(ListStyleItem(i18n("Lower Alphabetical"), KoListStyle::AlphaLowerItem));
answer.append(ListStyleItem(i18n("Upper Alphabetical"), KoListStyle::UpperAlphaItem));
answer.append(ListStyleItem(i18n("Lower Roman"), KoListStyle::RomanLowerItem));
answer.append(ListStyleItem(i18n("Upper Roman"), KoListStyle::UpperRomanItem));
return answer;
}
QList<ListStyleItem> Lists::otherListStyleItems()
{
QList<ListStyleItem> answer;
answer.append(ListStyleItem(i18n("Large Bullet"), KoListStyle::BlackCircle));
answer.append(ListStyleItem(i18n("Ballot X Bullet"), KoListStyle::BallotXItem));
answer.append(ListStyleItem(i18n("Rightwards Arrow Head Bullet"), KoListStyle::RightArrowHeadItem));
answer.append(ListStyleItem(i18n("Bengali"), KoListStyle::Bengali));
answer.append(ListStyleItem(i18n("Gujarati"), KoListStyle::Gujarati));
answer.append(ListStyleItem(i18n("Gurumukhi"), KoListStyle::Gurumukhi));
answer.append(ListStyleItem(i18n("Kannada"), KoListStyle::Kannada));
answer.append(ListStyleItem(i18n("Malayalam"), KoListStyle::Malayalam));
answer.append(ListStyleItem(i18n("Oriya"), KoListStyle::Oriya));
answer.append(ListStyleItem(i18n("Tamil"), KoListStyle::Tamil));
answer.append(ListStyleItem(i18n("Telugu"), KoListStyle::Telugu));
answer.append(ListStyleItem(i18n("Tibetan"), KoListStyle::Tibetan));
answer.append(ListStyleItem(i18n("Thai"), KoListStyle::Thai));
answer.append(ListStyleItem(i18n("Abjad"), KoListStyle::Abjad));
answer.append(ListStyleItem(i18n("AbjadMinor"), KoListStyle::AbjadMinor));
answer.append(ListStyleItem(i18n("ArabicAlphabet"), KoListStyle::ArabicAlphabet));
answer.append(ListStyleItem(i18n("Image"), KoListStyle::ImageItem));
return answer;
}
// ------------------- ListItemsHelper ------------
/// \internal helper class for calculating text-lists prefixes and indents
ListItemsHelper::ListItemsHelper(QTextList *textList, const QFont &font)
: m_textList(textList)
, m_fm(font, textList->document()->documentLayout()->paintDevice())
{
}
void ListItemsHelper::recalculateBlock(QTextBlock &block)
{
//warnTextLayout;
const QTextListFormat format = m_textList->format();
const KoListStyle::Style listStyle = static_cast<KoListStyle::Style>(format.style());
const QString prefix = format.stringProperty(KoListStyle::ListItemPrefix);
const QString suffix = format.stringProperty(KoListStyle::ListItemSuffix);
const int level = format.intProperty(KoListStyle::Level);
int dp = format.intProperty(KoListStyle::DisplayLevel);
if (dp > level)
dp = level;
const int displayLevel = dp ? dp : 1;
QTextBlockFormat blockFormat = block.blockFormat();
// Look if we have a block that is inside a header. We need to special case them cause header-lists are
// different from any other kind of list and they do build up there own global list (table of content).
bool isOutline = blockFormat.intProperty(KoParagraphStyle::OutlineLevel) > 0;
int startValue = 1;
if (format.hasProperty(KoListStyle::StartValue))
startValue = format.intProperty(KoListStyle::StartValue);
int index = startValue;
bool fixed = false;
if (blockFormat.boolProperty(KoParagraphStyle::RestartListNumbering)) {
index = format.intProperty(KoListStyle::StartValue);
fixed = true;
}
const int paragIndex = blockFormat.intProperty(KoParagraphStyle::ListStartValue);
if (paragIndex > 0) {
index = paragIndex;
fixed = true;
}
if (!fixed) {
//if this is the first item then find if the list has to be continued from any other list
KoList *listContinued = 0;
if (m_textList->itemNumber(block) == 0 && KoTextDocument(m_textList->document()).list(m_textList) && (listContinued = KoTextDocument(m_textList->document()).list(m_textList)->listContinuedFrom())) {
//find the previous list of the same level
QTextList *previousTextList = listContinued->textLists().at(level - 1).data();
if (previousTextList) {
QTextBlock textBlock = previousTextList->item(previousTextList->count() - 1);
if (textBlock.isValid()) {
index = KoTextBlockData(textBlock).counterIndex() + 1; //resume the previous list count
}
}
} else if (m_textList->itemNumber(block) > 0) {
QTextBlock textBlock = m_textList->item(m_textList->itemNumber(block) - 1);
if (textBlock.isValid()) {
index = KoTextBlockData(textBlock).counterIndex() + 1; //resume the previous list count
}
}
}
qreal width = 0.0;
KoTextBlockData blockData(block);
if (blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem)
|| blockFormat.boolProperty(KoParagraphStyle::IsListHeader)) {
blockData.setCounterPlainText(QString());
blockData.setCounterPrefix(QString());
blockData.setCounterSuffix(QString());
blockData.setPartialCounterText(QString());
// set the counter for the current un-numbered list to the counter index of the previous list item.
// index-1 because the list counter would have already incremented by one
blockData.setCounterIndex(index - 1);
if (blockFormat.boolProperty(KoParagraphStyle::IsListHeader)) {
blockData.setCounterWidth(format.doubleProperty(KoListStyle::MinimumWidth));
blockData.setCounterSpacing(0);
}
return;
}
QString item;
if (displayLevel > 1) {
int checkLevel = level;
int tmpDisplayLevel = displayLevel;
bool counterResetRequired = true;
for (QTextBlock b = block.previous(); tmpDisplayLevel > 1 && b.isValid(); b = b.previous()) {
if (b.textList() == 0)
continue;
QTextListFormat lf = b.textList()->format();
if (lf.property(KoListStyle::StyleId) != format.property(KoListStyle::StyleId))
continue; // uninteresting for us
if (isOutline != bool(b.blockFormat().intProperty(KoParagraphStyle::OutlineLevel)))
continue; // also uninteresting cause the one is an outline-listitem while the other is not
if (! KoListStyle::isNumberingStyle(static_cast<KoListStyle::Style>(lf.style()))) {
continue;
}
if (b.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) {
continue; //unnumbered listItems are irrelevant
}
const int otherLevel = lf.intProperty(KoListStyle::Level);
if (isOutline && checkLevel == otherLevel) {
counterResetRequired = false;
}
if (checkLevel <= otherLevel)
continue;
KoTextBlockData otherData(b);
if (!otherData.hasCounterData()) {
continue;
}
if (tmpDisplayLevel - 1 < otherLevel) { // can't just copy it fully since we are
// displaying less then the full counter
item += otherData.partialCounterText();
tmpDisplayLevel--;
checkLevel--;
for (int i = otherLevel + 1; i < level; i++) {
tmpDisplayLevel--;
item += "." + intToNumberingStyle(index, listStyle,
m_textList->format().boolProperty(KoListStyle::LetterSynchronization)); // add missing counters.
}
} else { // just copy previous counter as prefix
QString otherPrefix = lf.stringProperty(KoListStyle::ListItemPrefix);
QString otherSuffix = lf.stringProperty(KoListStyle::ListItemSuffix);
QString pureCounter = otherData.counterText().mid(otherPrefix.size());
pureCounter = pureCounter.left(pureCounter.size() - otherSuffix.size());
item += pureCounter;
for (int i = otherLevel + 1; i < level; i++)
item += "." + intToNumberingStyle(index, listStyle,
m_textList->format().boolProperty(KoListStyle::LetterSynchronization)); // add missing counters.
tmpDisplayLevel = 0;
if (isOutline && counterResetRequired) {
index = 1;
}
break;
}
}
for (int i = 1; i < tmpDisplayLevel; i++)
item = intToNumberingStyle(index, listStyle,
m_textList->format().boolProperty(KoListStyle::LetterSynchronization))
+ "." + item; // add missing counters.
}
if ((listStyle == KoListStyle::DecimalItem || listStyle == KoListStyle::AlphaLowerItem ||
listStyle == KoListStyle::UpperAlphaItem ||
listStyle == KoListStyle::RomanLowerItem ||
listStyle == KoListStyle::UpperRomanItem) &&
!(item.isEmpty() || item.endsWith('.') || item.endsWith(' '))) {
item += '.';
}
bool calcWidth = true;
QString partialCounterText;
if (KoListStyle::isNumberingStyle(listStyle)) {
partialCounterText = intToNumberingStyle(index, listStyle,
m_textList->format().boolProperty(KoListStyle::LetterSynchronization));
} else {
switch (listStyle) {
case KoListStyle::SquareItem:
case KoListStyle::Bullet:
case KoListStyle::BlackCircle:
case KoListStyle::DiscItem:
case KoListStyle::CircleItem:
case KoListStyle::HeavyCheckMarkItem:
case KoListStyle::BallotXItem:
case KoListStyle::RightArrowItem:
case KoListStyle::RightArrowHeadItem:
case KoListStyle::RhombusItem:
case KoListStyle::BoxItem: {
calcWidth = false;
if (format.intProperty(KoListStyle::BulletCharacter))
item = QString(QChar(format.intProperty(KoListStyle::BulletCharacter)));
width = m_fm.width(item);
int percent = format.intProperty(KoListStyle::RelativeBulletSize);
if (percent > 0)
width = width * (percent / 100.0);
break;
}
case KoListStyle::CustomCharItem:
calcWidth = false;
if (format.intProperty(KoListStyle::BulletCharacter))
item = QString(QChar(format.intProperty(KoListStyle::BulletCharacter)));
width = m_fm.width(item);
break;
case KoListStyle::None:
calcWidth = false;
width = 0.0;
break;
case KoListStyle::ImageItem:
calcWidth = false;
width = qMax(format.doubleProperty(KoListStyle::Width), (qreal)1.0);
break;
default: // others we ignore.
calcWidth = false;
}
}
blockData.setCounterIsImage(listStyle == KoListStyle::ImageItem);
blockData.setPartialCounterText(partialCounterText);
blockData.setCounterIndex(index);
item += partialCounterText;
blockData.setCounterPlainText(item);
blockData.setCounterPrefix(prefix);
blockData.setCounterSuffix(suffix);
if (calcWidth)
width = m_fm.width(item);
index++;
width += m_fm.width(prefix + suffix);
qreal counterSpacing = 0;
if (format.boolProperty(KoListStyle::AlignmentMode)) {
// for AlignmentMode spacing should be 0
counterSpacing = 0;
} else {
if (listStyle != KoListStyle::None) {
// see ODF spec 1.2 item 20.422
counterSpacing = format.doubleProperty(KoListStyle::MinimumDistance);
if (width < format.doubleProperty(KoListStyle::MinimumWidth)) {
counterSpacing -= format.doubleProperty(KoListStyle::MinimumWidth) - width;
}
counterSpacing = qMax(counterSpacing, qreal(0.0));
}
width = qMax(width, format.doubleProperty(KoListStyle::MinimumWidth));
}
blockData.setCounterWidth(width);
blockData.setCounterSpacing(counterSpacing);
//warnTextLayout;
}
// static
bool ListItemsHelper::needsRecalc(QTextList *textList)
{
Q_ASSERT(textList);
QTextBlock tb = textList->item(0);
KoTextBlockData blockData(tb);
return !blockData.hasCounterData();
}
diff --git a/plugins/impex/csv/csv_layer_record.cpp b/plugins/impex/csv/csv_layer_record.cpp
index 7143aee9f0..522a422ac1 100644
--- a/plugins/impex/csv/csv_layer_record.cpp
+++ b/plugins/impex/csv/csv_layer_record.cpp
@@ -1,31 +1,28 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "csv_layer_record.h"
CSVLayerRecord::CSVLayerRecord()
- : layer(0)
- , channel(0)
- , frame(0)
{
}
CSVLayerRecord::~CSVLayerRecord()
{
}
diff --git a/plugins/impex/csv/csv_layer_record.h b/plugins/impex/csv/csv_layer_record.h
index 24a47838ec..5be3c4024d 100644
--- a/plugins/impex/csv/csv_layer_record.h
+++ b/plugins/impex/csv/csv_layer_record.h
@@ -1,45 +1,45 @@
/*
* Copyright (c) 2016 Laszlo Fazekas <mneko@freemail.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public 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 CSV_LAYER_RECORD_H_
#define CSV_LAYER_RECORD_H_
#include <QString>
#include "kis_raster_keyframe_channel.h"
class CSVLayerRecord
{
public:
CSVLayerRecord();
virtual ~CSVLayerRecord();
QString name;
QString blending;
- float density;
- int visible;
+ float density {0.0};
+ int visible {0};
- KisLayer* layer;
- KisRasterKeyframeChannel *channel;
+ KisLayer* layer {0};
+ KisRasterKeyframeChannel *channel {0};
QString last;
QString path;
- int frame;
+ int frame {0};
};
#endif
diff --git a/plugins/impex/csv/tests/CMakeLists.txt b/plugins/impex/csv/tests/CMakeLists.txt
index dd7a4193ff..68efd0974a 100644
--- a/plugins/impex/csv/tests/CMakeLists.txt
+++ b/plugins/impex/csv/tests/CMakeLists.txt
@@ -1,11 +1,12 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests )
include(KritaAddBrokenUnitTest)
macro_add_unittest_definitions()
+include(KritaAddBrokenUnitTest)
-ecm_add_test(kis_csv_test.cpp
+krita_add_broken_unit_test(kis_csv_test.cpp
TEST_NAME kis_csv_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/jp2/tests/CMakeLists.txt b/plugins/impex/jp2/tests/CMakeLists.txt
index 596045d947..9ebdaae8d6 100644
--- a/plugins/impex/jp2/tests/CMakeLists.txt
+++ b/plugins/impex/jp2/tests/CMakeLists.txt
@@ -1,11 +1,11 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests )
include(KritaAddBrokenUnitTest)
macro_add_unittest_definitions()
-ecm_add_test(KisJP2Test.cpp
+krita_add_broken_unit_test(KisJP2Test.cpp
TEST_NAME KisJP2Test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp
index 55e20d7b40..8f9618f226 100644
--- a/plugins/impex/kra/kra_converter.cpp
+++ b/plugins/impex/kra/kra_converter.cpp
@@ -1,407 +1,437 @@
/*
* 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>
+#include <kis_clone_layer.h>
static const char CURRENT_DTD_VERSION[] = "2.0";
KraConverter::KraConverter(KisDocument *doc)
: m_doc(doc)
, m_image(doc->savingImage())
{
}
KraConverter::KraConverter(KisDocument *doc, QPointer<KoUpdater> updater)
: m_doc(doc)
, m_image(doc->savingImage())
, m_updater(updater)
{
}
KraConverter::~KraConverter()
{
delete m_store;
delete m_kraSaver;
delete m_kraLoader;
}
+void fixCloneLayers(KisImageSP image, KisNodeSP root)
+{
+ KisNodeSP first = root->firstChild();
+ KisNodeSP node = first;
+ while (!node.isNull()) {
+ if (node->inherits("KisCloneLayer")) {
+ KisCloneLayer* layer = dynamic_cast<KisCloneLayer*>(node.data());
+ if (layer && layer->copyFrom().isNull()) {
+ KisLayerSP reincarnation = layer->reincarnateAsPaintLayer();
+ image->addNode(reincarnation, node->parent(), node->prevSibling());
+ image->removeNode(node);
+ node = reincarnation;
+ }
+ } else if (node->childCount() > 0) {
+ fixCloneLayers(image, node);
+ }
+ node = node->nextSibling();
+ }
+}
+
KisImportExportErrorCode 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 ImportExportCodes::FileFormatIncorrect;
}
- bool success;
+ bool success = false;
{
if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml)
KoXmlDocument doc;
KisImportExportErrorCode res = oldLoadAndParse(m_store, "root", doc);
if (res.isOk())
res = loadXML(doc, m_store);
if (!res.isOk()) {
return res;
}
} else {
errUI << "ERROR: No maindoc.xml" << endl;
m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'."));
return ImportExportCodes::FileFormatIncorrect;
}
if (m_store->hasFile("documentinfo.xml")) {
KoXmlDocument doc;
- if (oldLoadAndParse(m_store, "documentinfo.xml", doc).isOk()) {
+ KisImportExportErrorCode resultHere = oldLoadAndParse(m_store, "documentinfo.xml", doc);
+ if (resultHere.isOk()) {
m_doc->documentInfo()->load(doc);
}
}
success = completeLoading(m_store);
}
+ fixCloneLayers(m_image, m_image->root());
+
return success ? ImportExportCodes::OK : ImportExportCodes::Failure;
}
KisImageSP KraConverter::image()
{
return m_image;
}
vKisNodeSP KraConverter::activeNodes()
{
return m_activeNodes;
}
QList<KisPaintingAssistantSP> KraConverter::assistants()
{
return m_assistants;
}
KisImportExportErrorCode KraConverter::buildFile(QIODevice *io, const QString &filename)
{
setProgress(5);
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 ImportExportCodes::CannotCreateFile;
}
setProgress(20);
m_kraSaver = new KisKraSaver(m_doc, filename);
KisImportExportErrorCode resultCode = saveRootDocuments(m_store);
if (!resultCode.isOk()) {
return resultCode;
}
setProgress(40);
bool result;
result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true);
if (!result) {
qWarning() << "saving key frames failed";
}
setProgress(60);
result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving());
if (!result) {
qWarning() << "saving binary data failed";
}
setProgress(70);
result = m_kraSaver->savePalettes(m_store, m_image, m_doc->url().toLocalFile());
if (!result) {
qWarning() << "saving palettes data failed";
}
setProgress(80);
if (!m_store->finalize()) {
return ImportExportCodes::Failure;
}
if (!m_kraSaver->errorMessages().isEmpty()) {
m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n"));
return ImportExportCodes::Failure;
}
setProgress(90);
return ImportExportCodes::OK;
}
KisImportExportErrorCode KraConverter::saveRootDocuments(KoStore *store)
{
dbgFile << "Saving root";
if (store->open("root")) {
KoStoreDevice dev(store);
if (!saveToStream(&dev) || !store->close()) {
dbgUI << "saveToStream failed";
return ImportExportCodes::NoAccessToWrite;
}
} else {
m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml")));
return ImportExportCodes::ErrorWhileWriting;
}
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!
bool success = dev.write(s.data(), s.size());
if (!success) {
return ImportExportCodes::ErrorWhileWriting;
}
store->close();
} else {
return ImportExportCodes::Failure;
}
if (store->open("preview.png")) {
// ### TODO: missing error checking (The partition could be full!)
KisImportExportErrorCode result = savePreview(store);
(void)store->close();
if (!result.isOk()) {
return result;
}
} else {
return ImportExportCodes::Failure;
}
dbgUI << "Saving done of url:" << m_doc->url().toLocalFile();
return ImportExportCodes::OK;
}
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;
}
KisImportExportErrorCode 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 ImportExportCodes::NoAccessToWrite;
}
bool ret = preview.save(&io, "PNG");
io.close();
return ret ? ImportExportCodes::OK : ImportExportCodes::ErrorWhileWriting;
}
KisImportExportErrorCode 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 ImportExportCodes::FileNotExist;
}
// 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)));
return ImportExportCodes::FileFormatIncorrect;
}
dbgUI << "File" << filename << " loaded and parsed";
return ImportExportCodes::OK;
}
KisImportExportErrorCode KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store)
{
Q_UNUSED(store);
KoXmlElement root;
KoXmlNode node;
if (doc.doctype().name() != "DOC") {
errUI << "The format is not supported or the file is corrupted";
m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted"));
return ImportExportCodes::FileFormatIncorrect;
}
root = doc.documentElement();
int syntaxVersion = root.attribute("syntaxVersion", "3").toInt();
if (syntaxVersion > 2) {
errUI << "The file is too new for this version of Krita:" << syntaxVersion;
m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion));
return ImportExportCodes::FormatFeaturesUnsupported;
}
if (!root.hasChildNodes()) {
errUI << "The file has no layers.";
m_doc->setErrorMessage(i18n("The file has no layers."));
return ImportExportCodes::FileFormatIncorrect;
}
m_kraLoader = new KisKraLoader(m_doc, syntaxVersion);
// reset the old image before loading the next one
m_doc->setCurrentImage(0, false);
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()) {
errUI << "Unknown error while opening the .kra file.";
m_doc->setErrorMessage(i18n("Unknown error."));
}
else {
m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n"));
errUI << m_kraLoader->errorMessages().join("\n");
}
return ImportExportCodes::Failure;
}
// HACK ALERT!
m_doc->hackPreliminarySetImage(m_image);
return ImportExportCodes::OK;
}
else {
if (m_kraLoader->errorMessages().isEmpty()) {
m_doc->setErrorMessage(i18n("The file does not contain an image."));
}
return ImportExportCodes::FileFormatIncorrect;
}
}
}
return ImportExportCodes::Failure;
}
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();
QString layerPathName = m_kraLoader->imageName();
if (!m_store->hasDirectory(layerPathName)) {
// We might be hitting an encoding problem. Get the only folder in the toplevel
Q_FOREACH (const QString &entry, m_store->directoryList()) {
if (entry.contains("/layers/")) {
layerPathName = entry.split("/layers/").first();
m_store->setSubstitution(m_kraLoader->imageName(), layerPathName);
break;
}
}
}
m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true);
m_kraLoader->loadPalettes(store, m_doc);
+ if (!m_kraLoader->errorMessages().isEmpty()) {
+ m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n"));
+ return false;
+ }
+
m_image->unblockUpdates();
if (!m_kraLoader->warningMessages().isEmpty()) {
// warnings do not interrupt loading process, so we do not return here
m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n"));
}
m_activeNodes = m_kraLoader->selectedNodes();
m_assistants = m_kraLoader->assistants();
return true;
+ return m_kraLoader->errorMessages().isEmpty();
}
void KraConverter::cancel()
{
m_stop = true;
}
void KraConverter::setProgress(int progress)
{
if (m_updater) {
m_updater->setProgress(progress);
}
}
diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp
index c11dc50054..d495abd1ff 100644
--- a/plugins/impex/libkra/kis_kra_load_visitor.cpp
+++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp
@@ -1,784 +1,789 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* 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_kra_load_visitor.h"
#include "kis_kra_tags.h"
#include "flake/kis_shape_layer.h"
#include "flake/KisReferenceImagesLayer.h"
#include "KisReferenceImage.h"
#include <KisImportExportManager.h>
#include <QRect>
#include <QBuffer>
#include <QByteArray>
#include <QMessageBox>
#include <KoHashGenerator.h>
#include <KoHashGeneratorProvider.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KoFileDialog.h>
#include <KoStore.h>
#include <KoColorSpace.h>
#include <KoShapeControllerBase.h>
// kritaimage
#include <kis_meta_data_io_backend.h>
#include <kis_meta_data_store.h>
#include <kis_types.h>
#include <kis_node_visitor.h>
#include <kis_image.h>
#include <kis_selection.h>
#include <kis_layer.h>
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
#include <kis_adjustment_layer.h>
#include <filter/kis_filter_configuration.h>
#include <kis_datamanager.h>
#include <generator/kis_generator_layer.h>
#include <kis_pixel_selection.h>
#include <kis_clone_layer.h>
#include <kis_filter_mask.h>
#include <kis_transform_mask.h>
#include <kis_transform_mask_params_interface.h>
#include "kis_transform_mask_params_factory_registry.h"
#include <kis_transparency_mask.h>
#include <kis_selection_mask.h>
#include <lazybrush/kis_colorize_mask.h>
#include <lazybrush/kis_lazy_fill_tools.h>
#include "kis_shape_selection.h"
#include "kis_colorize_dom_utils.h"
#include "kis_dom_utils.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_paint_device_frames_interface.h"
#include "kis_filter_registry.h"
using namespace KRA;
QString expandEncodedDirectory(const QString& _intern)
{
QString intern = _intern;
QString result;
int pos;
while ((pos = intern.indexOf('/')) != -1) {
if (QChar(intern.at(0)).isDigit())
result += "part";
result += intern.left(pos + 1); // copy numbers (or "pictures") + "/"
intern = intern.mid(pos + 1); // remove the dir we just processed
}
if (!intern.isEmpty() && QChar(intern.at(0)).isDigit())
result += "part";
result += intern;
return result;
}
KisKraLoadVisitor::KisKraLoadVisitor(KisImageSP image,
KoStore *store,
KoShapeControllerBase *shapeController,
QMap<KisNode *, QString> &layerFilenames,
QMap<KisNode *, QString> &keyframeFilenames,
const QString & name,
int syntaxVersion)
: KisNodeVisitor()
, m_image(image)
, m_store(store)
, m_external(false)
, m_layerFilenames(layerFilenames)
, m_keyframeFilenames(keyframeFilenames)
, m_name(name)
, m_shapeController(shapeController)
{
m_store->pushDirectory();
if (!m_store->enterDirectory(m_name)) {
QStringList directories = m_store->directoryList();
dbgKrita << directories;
if (directories.size() > 0) {
dbgFile << "Could not locate the directory, maybe some encoding issue? Grab the first directory, that'll be the image one." << m_name << directories;
m_name = directories.first();
}
else {
dbgFile << "Could not enter directory" << m_name << ", probably an old-style file with 'part' added.";
m_name = expandEncodedDirectory(m_name);
}
}
else {
m_store->popDirectory();
}
m_syntaxVersion = syntaxVersion;
}
void KisKraLoadVisitor::setExternalUri(const QString &uri)
{
m_external = true;
m_uri = uri;
}
bool KisKraLoadVisitor::visit(KisExternalLayer * layer)
{
bool result = false;
if (auto *referencesLayer = dynamic_cast<KisReferenceImagesLayer*>(layer)) {
Q_FOREACH(KoShape *shape, referencesLayer->shapes()) {
auto *reference = dynamic_cast<KisReferenceImage*>(shape);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, false);
while (!reference->loadImage(m_store)) {
if (reference->embed()) {
m_errorMessages << i18n("Could not load embedded reference image %1 ", reference->internalFile());
break;
} else {
QString msg = i18nc(
"@info",
"A reference image linked to an external file could not be loaded.\n\n"
"Path: %1\n\n"
"Do you want to select another location?", reference->filename());
int locateManually = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
QString url;
if (locateManually == QMessageBox::Yes) {
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import));
url = dialog.filename();
}
if (url.isEmpty()) {
break;
} else {
reference->setFilename(url);
}
}
}
}
} else if (KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(layer)) {
if (!loadMetaData(layer)) {
return false;
}
m_store->pushDirectory();
m_store->enterDirectory(getLocation(layer, DOT_SHAPE_LAYER)) ;
result = shapeLayer->loadLayer(m_store);
m_store->popDirectory();
}
result = visitAll(layer) && result;
return result;
}
bool KisKraLoadVisitor::visit(KisPaintLayer *layer)
{
loadNodeKeyframes(layer);
if (!loadPaintDevice(layer->paintDevice(), getLocation(layer))) {
return false;
}
if (!loadProfile(layer->paintDevice(), getLocation(layer, DOT_ICC))) {
return false;
}
if (!loadMetaData(layer)) {
return false;
}
if (m_syntaxVersion == 1) {
// Check whether there is a file with a .mask extension in the
// layer directory, if so, it's an old-style transparency mask
// that should be converted.
QString location = getLocation(layer, ".mask");
if (m_store->open(location)) {
KisSelectionSP selection = KisSelectionSP(new KisSelection());
KisPixelSelectionSP pixelSelection = selection->pixelSelection();
if (!pixelSelection->read(m_store->device())) {
pixelSelection->disconnect();
} else {
KisTransparencyMask* mask = new KisTransparencyMask();
mask->setSelection(selection);
m_image->addNode(mask, layer, layer->firstChild());
}
m_store->close();
}
}
bool result = visitAll(layer);
return result;
}
bool KisKraLoadVisitor::visit(KisGroupLayer *layer)
{
if (*layer->colorSpace() != *m_image->colorSpace()) {
layer->resetCache(m_image->colorSpace());
}
if (!loadMetaData(layer)) {
return false;
}
bool result = visitAll(layer);
return result;
}
bool KisKraLoadVisitor::visit(KisAdjustmentLayer* layer)
{
loadNodeKeyframes(layer);
// Adjustmentlayers are tricky: there's the 1.x style and the 2.x
// style, which has selections with selection components
bool result = true;
if (m_syntaxVersion == 1) {
KisSelectionSP selection = new KisSelection();
KisPixelSelectionSP pixelSelection = selection->pixelSelection();
result = loadPaintDevice(pixelSelection, getLocation(layer, ".selection"));
layer->setInternalSelection(selection);
} else if (m_syntaxVersion == 2) {
result = loadSelection(getLocation(layer), layer->internalSelection());
} else {
// We use the default, empty selection
}
if (!loadMetaData(layer)) {
return false;
}
loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG));
fixOldFilterConfigurations(layer->filter());
result = visitAll(layer);
return result;
}
bool KisKraLoadVisitor::visit(KisGeneratorLayer *layer)
{
if (!loadMetaData(layer)) {
return false;
}
bool result = true;
loadNodeKeyframes(layer);
result = loadSelection(getLocation(layer), layer->internalSelection());
// HACK ALERT: we set the same filter again to ensure the layer
// is correctly updated
result = loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG));
layer->setFilter(layer->filter());
result = visitAll(layer);
return result;
}
bool KisKraLoadVisitor::visit(KisCloneLayer *layer)
{
if (!loadMetaData(layer)) {
return false;
}
// the layer might have already been lazily initialized
// from the mask loading code
if (layer->copyFrom()) {
return true;
}
KisNodeSP srcNode = layer->copyFromInfo().findNode(m_image->rootLayer());
- KisLayerSP srcLayer = qobject_cast<KisLayer*>(srcNode.data());
- Q_ASSERT(srcLayer);
+ if (!srcNode.isNull()) {
+ KisLayerSP srcLayer = qobject_cast<KisLayer*>(srcNode.data());
+ Q_ASSERT(srcLayer);
- layer->setCopyFrom(srcLayer);
+ layer->setCopyFrom(srcLayer);
+ } else {
+ m_warningMessages.append(i18nc("Loading a .kra file", "The file contains a clone layer that has an incorrect source node id. "
+ "This layer will be converted into a paint layer."));
+ }
// Clone layers have no data except for their masks
bool result = visitAll(layer);
return result;
}
void KisKraLoadVisitor::initSelectionForMask(KisMask *mask)
{
KisLayer *cloneLayer = dynamic_cast<KisCloneLayer*>(mask->parent().data());
if (cloneLayer) {
// the clone layers should be initialized out of order
// and lazily, because their original() is still not
// initialized
cloneLayer->accept(*this);
}
KisLayer *parentLayer = qobject_cast<KisLayer*>(mask->parent().data());
// the KisKraLoader must have already set the parent for us
Q_ASSERT(parentLayer);
mask->initSelection(parentLayer);
}
bool KisKraLoadVisitor::visit(KisFilterMask *mask)
{
initSelectionForMask(mask);
loadNodeKeyframes(mask);
bool result = true;
result = loadSelection(getLocation(mask), mask->selection());
result = loadFilterConfiguration(mask, getLocation(mask, DOT_FILTERCONFIG));
fixOldFilterConfigurations(mask->filter());
return result;
}
bool KisKraLoadVisitor::visit(KisTransformMask *mask)
{
QString location = getLocation(mask, DOT_TRANSFORMCONFIG);
if (m_store->hasFile(location)) {
QByteArray data;
m_store->open(location);
data = m_store->read(m_store->size());
m_store->close();
if (!data.isEmpty()) {
QDomDocument doc;
doc.setContent(data);
QDomElement rootElement = doc.documentElement();
QDomElement main;
if (!KisDomUtils::findOnlyElement(rootElement, "main", &main/*, &m_errorMessages*/)) {
return false;
}
QString id = main.attribute("id", "not-valid");
if (id == "not-valid") {
m_errorMessages << i18n("Could not load \"id\" of the transform mask");
return false;
}
QDomElement data;
if (!KisDomUtils::findOnlyElement(rootElement, "data", &data, &m_errorMessages)) {
return false;
}
KisTransformMaskParamsInterfaceSP params =
KisTransformMaskParamsFactoryRegistry::instance()->createParams(id, data);
if (!params) {
m_errorMessages << i18n("Could not create transform mask params");
return false;
}
mask->setTransformParams(params);
loadNodeKeyframes(mask);
params->clearChangedFlag();
return true;
}
}
return false;
}
bool KisKraLoadVisitor::visit(KisTransparencyMask *mask)
{
initSelectionForMask(mask);
loadNodeKeyframes(mask);
return loadSelection(getLocation(mask), mask->selection());
}
bool KisKraLoadVisitor::visit(KisSelectionMask *mask)
{
initSelectionForMask(mask);
return loadSelection(getLocation(mask), mask->selection());
}
bool KisKraLoadVisitor::visit(KisColorizeMask *mask)
{
m_store->pushDirectory();
QString location = getLocation(mask, DOT_COLORIZE_MASK);
m_store->enterDirectory(location) ;
QByteArray data;
if (!m_store->extractFile("content.xml", data))
return false;
QDomDocument doc;
if (!doc.setContent(data))
return false;
QVector<KisLazyFillTools::KeyStroke> strokes;
if (!KisDomUtils::loadValue(doc.documentElement(), COLORIZE_KEYSTROKES_SECTION, &strokes, mask->colorSpace()))
return false;
int i = 0;
Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, strokes) {
const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++);
loadPaintDevice(stroke.dev, fileName);
}
mask->setKeyStrokesDirect(QList<KisLazyFillTools::KeyStroke>::fromVector(strokes));
loadPaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE);
mask->resetCache();
m_store->popDirectory();
return true;
}
QStringList KisKraLoadVisitor::errorMessages() const
{
return m_errorMessages;
}
QStringList KisKraLoadVisitor::warningMessages() const
{
return m_warningMessages;
}
struct SimpleDevicePolicy
{
bool read(KisPaintDeviceSP dev, QIODevice *stream) {
return dev->read(stream);
}
void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const {
return dev->setDefaultPixel(defaultPixel);
}
};
struct FramedDevicePolicy
{
FramedDevicePolicy(int frameId)
: m_frameId(frameId) {}
bool read(KisPaintDeviceSP dev, QIODevice *stream) {
return dev->framesInterface()->readFrame(stream, m_frameId);
}
void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const {
return dev->framesInterface()->setFrameDefaultPixel(defaultPixel, m_frameId);
}
int m_frameId;
};
bool KisKraLoadVisitor::loadPaintDevice(KisPaintDeviceSP device, const QString& location)
{
// Layer data
KisPaintDeviceFramesInterface *frameInterface = device->framesInterface();
QList<int> frames;
if (frameInterface) {
frames = device->framesInterface()->frames();
}
if (!frameInterface || frames.count() <= 1) {
return loadPaintDeviceFrame(device, location, SimpleDevicePolicy());
} else {
KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel();
for (int i = 0; i < frames.count(); i++) {
int id = frames[i];
if (keyframeChannel->frameFilename(id).isEmpty()) {
m_warningMessages << i18n("Could not find keyframe pixel data for frame %1 in %2.", id, location);
}
else {
Q_ASSERT(!keyframeChannel->frameFilename(id).isEmpty());
QString frameFilename = getLocation(keyframeChannel->frameFilename(id));
Q_ASSERT(!frameFilename.isEmpty());
if (!loadPaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) {
m_warningMessages << i18n("Could not load keyframe pixel data for frame %1 in %2.", id, location);
}
}
}
}
return true;
}
template<class DevicePolicy>
bool KisKraLoadVisitor::loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy)
{
{
const int pixelSize = device->colorSpace()->pixelSize();
KoColor color(Qt::transparent, device->colorSpace());
if (m_store->open(location + ".defaultpixel")) {
if (m_store->size() == pixelSize) {
m_store->read((char*)color.data(), pixelSize);
}
m_store->close();
}
policy.setDefaultPixel(device, color);
}
if (m_store->open(location)) {
if (!policy.read(device, m_store->device())) {
m_warningMessages << i18n("Could not read pixel data: %1.", location);
device->disconnect();
m_store->close();
return true;
}
m_store->close();
} else {
m_warningMessages << i18n("Could not load pixel data: %1.", location);
return true;
}
return true;
}
bool KisKraLoadVisitor::loadProfile(KisPaintDeviceSP device, const QString& location)
{
if (m_store->hasFile(location)) {
m_store->open(location);
QByteArray data;
data.resize(m_store->size());
dbgFile << "Data to load: " << m_store->size() << " from " << location << " with color space " << device->colorSpace()->id();
int read = m_store->read(data.data(), m_store->size());
dbgFile << "Profile size: " << data.size() << " " << m_store->atEnd() << " " << m_store->device()->bytesAvailable() << " " << read;
m_store->close();
KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5");
QByteArray hash = hashGenerator->generateHash(data);
if (m_profileCache.contains(hash)) {
if (device->setProfile(m_profileCache[hash], 0)) {
return true;
}
}
else {
// Create a colorspace with the embedded profile
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(device->colorSpace()->colorModelId().id(), device->colorSpace()->colorDepthId().id(), data);
m_profileCache[hash] = profile;
if (device->setProfile(profile, 0)) {
return true;
}
}
}
m_warningMessages << i18n("Could not load profile: %1.", location);
return true;
}
bool KisKraLoadVisitor::loadFilterConfiguration(KisNodeFilterInterface *nodeInterface, const QString& location)
{
KisFilterConfigurationSP kfc = nodeInterface->filter();
if (m_store->hasFile(location)) {
QByteArray data;
m_store->open(location);
data = m_store->read(m_store->size());
m_store->close();
if (!data.isEmpty()) {
QDomDocument doc;
doc.setContent(data);
QDomElement e = doc.documentElement();
if (e.tagName() == "filterconfig") {
kfc->fromLegacyXML(e);
} else {
kfc->fromXML(e);
}
loadDeprecatedFilter(kfc);
return true;
}
}
m_warningMessages << i18n("Could not filter configuration %1.", location);
return true;
}
void KisKraLoadVisitor::fixOldFilterConfigurations(KisFilterConfigurationSP kfc)
{
KisFilterSP filter = KisFilterRegistry::instance()->value(kfc->name());
KIS_SAFE_ASSERT_RECOVER_RETURN(filter);
if (!filter->configurationAllowedForMask(kfc)) {
filter->fixLoadedFilterConfigurationForMasks(kfc);
}
KIS_SAFE_ASSERT_RECOVER_NOOP(filter->configurationAllowedForMask(kfc));
}
bool KisKraLoadVisitor::loadMetaData(KisNode* node)
{
KisLayer* layer = qobject_cast<KisLayer*>(node);
if (!layer) return true;
KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp");
if (!backend || !backend->supportLoading()) {
if (backend)
dbgFile << "Backend " << backend->id() << " does not support loading.";
else
dbgFile << "Could not load the XMP backend at all";
return true;
}
QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA);
dbgFile << "going to load " << backend->id() << ", " << backend->name() << " from " << location;
if (m_store->hasFile(location)) {
QByteArray data;
m_store->open(location);
data = m_store->read(m_store->size());
m_store->close();
QBuffer buffer(&data);
if (!backend->loadFrom(layer->metaData(), &buffer)) {
m_warningMessages << i18n("Could not load metadata for layer %1.", layer->name());
}
}
return true;
}
bool KisKraLoadVisitor::loadSelection(const QString& location, KisSelectionSP dstSelection)
{
// by default the selection is expected to be fully transparent
{
KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection();
KoColor transparent(Qt::transparent, pixelSelection->colorSpace());
pixelSelection->setDefaultPixel(transparent);
}
// Pixel selection
bool result = true;
QString pixelSelectionLocation = location + DOT_PIXEL_SELECTION;
if (m_store->hasFile(pixelSelectionLocation)) {
KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection();
result = loadPaintDevice(pixelSelection, pixelSelectionLocation);
if (!result) {
m_warningMessages << i18n("Could not load raster selection %1.", location);
}
pixelSelection->invalidateOutlineCache();
}
// Shape selection
QString shapeSelectionLocation = location + DOT_SHAPE_SELECTION;
if (m_store->hasFile(shapeSelectionLocation + "/content.svg") ||
m_store->hasFile(shapeSelectionLocation + "/content.xml")) {
m_store->pushDirectory();
m_store->enterDirectory(shapeSelectionLocation) ;
KisShapeSelection* shapeSelection = new KisShapeSelection(m_shapeController, m_image, dstSelection);
dstSelection->setShapeSelection(shapeSelection);
result = shapeSelection->loadSelection(m_store);
m_store->popDirectory();
if (!result) {
m_warningMessages << i18n("Could not load vector selection %1.", location);
}
}
return true;
}
QString KisKraLoadVisitor::getLocation(KisNode* node, const QString& suffix)
{
return getLocation(m_layerFilenames[node], suffix);
}
QString KisKraLoadVisitor::getLocation(const QString &filename, const QString& suffix)
{
QString location = m_external ? QString() : m_uri;
location += m_name + LAYER_PATH + filename + suffix;
return location;
}
void KisKraLoadVisitor::loadNodeKeyframes(KisNode *node)
{
if (!m_keyframeFilenames.contains(node)) return;
node->enableAnimation();
const QString &location = getLocation(m_keyframeFilenames[node]);
if (!m_store->open(location)) {
m_errorMessages << i18n("Could not load keyframes from %1.", location);
return;
}
QString errorMsg;
int errorLine;
int errorColumn;
QDomDocument dom;
bool ok = dom.setContent(m_store->device(), &errorMsg, &errorLine, &errorColumn);
m_store->close();
if (!ok) {
m_errorMessages << i18n("parsing error in the keyframe file %1 at line %2, column %3\nError message: %4", location, errorLine, errorColumn, i18n(errorMsg.toUtf8()));
return;
}
QDomElement root = dom.firstChildElement();
for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) {
if (child.nodeName().toUpper() == "CHANNEL") {
QString id = child.attribute("name");
KisKeyframeChannel *channel = node->getKeyframeChannel(id, true);
if (!channel) {
m_warningMessages << i18n("unknown keyframe channel type: %1 in %2", id, location);
continue;
}
channel->loadXML(child);
}
}
}
void KisKraLoadVisitor::loadDeprecatedFilter(KisFilterConfigurationSP cfg)
{
if (cfg->getString("legacy") == "left edge detections") {
cfg->setProperty("horizRadius", 1);
cfg->setProperty("vertRadius", 1);
cfg->setProperty("type", "prewitt");
cfg->setProperty("output", "yFall");
cfg->setProperty("lockAspect", true);
cfg->setProperty("transparency", false);
} else if (cfg->getString("legacy") == "right edge detections") {
cfg->setProperty("horizRadius", 1);
cfg->setProperty("vertRadius", 1);
cfg->setProperty("type", "prewitt");
cfg->setProperty("output", "yGrowth");
cfg->setProperty("lockAspect", true);
cfg->setProperty("transparency", false);
} else if (cfg->getString("legacy") == "top edge detections") {
cfg->setProperty("horizRadius", 1);
cfg->setProperty("vertRadius", 1);
cfg->setProperty("type", "prewitt");
cfg->setProperty("output", "xGrowth");
cfg->setProperty("lockAspect", true);
cfg->setProperty("transparency", false);
} else if (cfg->getString("legacy") == "bottom edge detections") {
cfg->setProperty("horizRadius", 1);
cfg->setProperty("vertRadius", 1);
cfg->setProperty("type", "prewitt");
cfg->setProperty("output", "xFall");
cfg->setProperty("lockAspect", true);
cfg->setProperty("transparency", false);
}
}
diff --git a/plugins/impex/libkra/tests/CMakeLists.txt b/plugins/impex/libkra/tests/CMakeLists.txt
index 096295b95c..99cf94589e 100644
--- a/plugins/impex/libkra/tests/CMakeLists.txt
+++ b/plugins/impex/libkra/tests/CMakeLists.txt
@@ -1,12 +1,18 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests )
+macro_add_unittest_definitions()
macro_add_unittest_definitions()
ecm_add_tests(
kis_kra_loader_test.cpp
- kis_kra_saver_test.cpp
-
LINK_LIBRARIES kritaui kritalibkra Qt5::Test
NAME_PREFIX "plugins-impex-")
+
+
+krita_add_broken_unit_test(kis_kra_saver_test.cpp
+ TEST_NAME kis_kra_saver_test.cpp
+ LINK_LIBRARIES kritaui Qt5::Test
+ NAME_PREFIX "plugins-impex-")
+
diff --git a/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp b/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp
index 9fd6fc0d07..c7b3b6ada0 100644
--- a/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp
+++ b/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp
@@ -1,172 +1,182 @@
/*
* Copyright (c) 2006-2007,2009 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_open_raster_stack_save_visitor.h"
#include <math.h>
#include <QDomElement>
#include <QImage>
#include <KoCompositeOpRegistry.h>
#include "kis_adjustment_layer.h"
#include "filter/kis_filter.h"
#include "filter/kis_filter_configuration.h"
#include "kis_group_layer.h"
#include "kis_paint_layer.h"
#include <generator/kis_generator_layer.h>
#include "kis_open_raster_save_context.h"
#include <kis_clone_layer.h>
#include <kis_external_layer_iface.h>
struct KisOpenRasterStackSaveVisitor::Private {
Private() {}
KisOpenRasterSaveContext* saveContext;
QDomDocument layerStack;
QDomElement currentElement;
vKisNodeSP activeNodes;
};
KisOpenRasterStackSaveVisitor::KisOpenRasterStackSaveVisitor(KisOpenRasterSaveContext* saveContext, vKisNodeSP activeNodes)
: d(new Private)
{
d->saveContext = saveContext;
d->activeNodes = activeNodes;
}
KisOpenRasterStackSaveVisitor::~KisOpenRasterStackSaveVisitor()
{
delete d;
}
void KisOpenRasterStackSaveVisitor::saveLayerInfo(QDomElement& elt, KisLayer* layer)
{
elt.setAttribute("name", layer->name());
elt.setAttribute("opacity", QString().setNum(layer->opacity() / 255.0));
elt.setAttribute("visibility", layer->visible() ? "visible" : "hidden");
+ elt.setAttribute("x", QString().setNum(layer->x()));
+ elt.setAttribute("y", QString().setNum(layer->y()));
if (layer->userLocked()) {
elt.setAttribute("edit-locked", "true");
}
if (d->activeNodes.contains(layer)) {
elt.setAttribute("selected", "true");
}
QString compop = layer->compositeOpId();
if (layer->compositeOpId() == COMPOSITE_CLEAR) compop = "svg:clear";
- else if (layer->compositeOpId() == COMPOSITE_OVER) compop = "svg:src-over";
else if (layer->compositeOpId() == COMPOSITE_ERASE) compop = "svg:dst-out";
- else if (layer->alphaChannelDisabled()) compop = "svg:src-atop";
else if (layer->compositeOpId() == COMPOSITE_DESTINATION_ATOP) compop = "svg:dst-atop";
else if (layer->compositeOpId() == COMPOSITE_DESTINATION_IN) compop = "svg:dst-in";
else if (layer->compositeOpId() == COMPOSITE_ADD) compop = "svg:plus";
else if (layer->compositeOpId() == COMPOSITE_MULT) compop = "svg:multiply";
else if (layer->compositeOpId() == COMPOSITE_SCREEN) compop = "svg:screen";
else if (layer->compositeOpId() == COMPOSITE_OVERLAY) compop = "svg:overlay";
else if (layer->compositeOpId() == COMPOSITE_DARKEN) compop = "svg:darken";
else if (layer->compositeOpId() == COMPOSITE_LIGHTEN) compop = "svg:lighten";
else if (layer->compositeOpId() == COMPOSITE_DODGE) compop = "svg:color-dodge";
else if (layer->compositeOpId() == COMPOSITE_BURN) compop = "svg:color-burn";
else if (layer->compositeOpId() == COMPOSITE_HARD_LIGHT) compop = "svg:hard-light";
else if (layer->compositeOpId() == COMPOSITE_SOFT_LIGHT_SVG) compop = "svg:soft-light";
else if (layer->compositeOpId() == COMPOSITE_DIFF) compop = "svg:difference";
else if (layer->compositeOpId() == COMPOSITE_COLOR) compop = "svg:color";
else if (layer->compositeOpId() == COMPOSITE_LUMINIZE) compop = "svg:luminosity";
else if (layer->compositeOpId() == COMPOSITE_HUE) compop = "svg:hue";
else if (layer->compositeOpId() == COMPOSITE_SATURATION) compop = "svg:saturation";
+
+ // it is important that the check for alphaChannelDisabled (and other non compositeOpId checks)
+ // come before the check for COMPOSITE_OVER, otherwise they will be logically ignored.
+ else if (layer->alphaChannelDisabled()) compop = "svg:src-atop";
+ else if (layer->compositeOpId() == COMPOSITE_OVER) compop = "svg:src-over";
+
//else if (layer->compositeOpId() == COMPOSITE_EXCLUSION) compop = "svg:exclusion";
else compop = "krita:" + layer->compositeOpId();
elt.setAttribute("composite-op", compop);
}
bool KisOpenRasterStackSaveVisitor::visit(KisPaintLayer *layer)
{
return saveLayer(layer);
}
bool KisOpenRasterStackSaveVisitor::visit(KisGeneratorLayer* layer)
{
return saveLayer(layer);
}
bool KisOpenRasterStackSaveVisitor::visit(KisGroupLayer *layer)
{
QDomElement previousElt = d->currentElement;
QDomElement elt = d->layerStack.createElement("stack");
d->currentElement = elt;
saveLayerInfo(elt, layer);
QString isolate = "isolate";
if (layer->passThroughMode()) {
isolate = "auto";
}
elt.setAttribute("isolation", isolate);
visitAll(layer);
if (!previousElt.isNull()) {
previousElt.insertBefore(elt, QDomNode());
d->currentElement = previousElt;
} else {
QDomElement imageElt = d->layerStack.createElement("image");
int width = layer->image()->width();
int height = layer->image()->height();
int xRes = (int)(qRound(layer->image()->xRes() * 72));
int yRes = (int)(qRound(layer->image()->yRes() * 72));
imageElt.setAttribute("version", "0.0.1");
imageElt.setAttribute("w", width);
imageElt.setAttribute("h", height);
imageElt.setAttribute("xres", xRes);
imageElt.setAttribute("yres", yRes);
imageElt.appendChild(elt);
d->layerStack.insertBefore(imageElt, QDomNode());
d->currentElement = QDomElement();
d->saveContext->saveStack(d->layerStack);
}
return true;
}
bool KisOpenRasterStackSaveVisitor::visit(KisAdjustmentLayer *layer)
{
QDomElement elt = d->layerStack.createElement("filter");
saveLayerInfo(elt, layer);
elt.setAttribute("type", "applications:krita:" + layer->filter()->name());
return true;
}
bool KisOpenRasterStackSaveVisitor::visit(KisCloneLayer *layer)
{
return saveLayer(layer);
}
bool KisOpenRasterStackSaveVisitor::visit(KisExternalLayer * layer)
{
return saveLayer(layer);
}
bool KisOpenRasterStackSaveVisitor::saveLayer(KisLayer *layer)
{
- QString filename = d->saveContext->saveDeviceData(layer->projection(), layer->metaData(), layer->image()->bounds(), layer->image()->xRes(), layer->image()->yRes());
+
+ // here we adjust the bounds to encompass the entire area of the layer with color data by adding the current offsets
+ QRect adjustedBounds = layer->image()->bounds();
+ adjustedBounds.adjust(layer->x(), layer->y(), layer->x(), layer->y());
+ QString filename = d->saveContext->saveDeviceData(layer->projection(), layer->metaData(), adjustedBounds, layer->image()->xRes(), layer->image()->yRes());
QDomElement elt = d->layerStack.createElement("layer");
saveLayerInfo(elt, layer);
elt.setAttribute("src", filename);
d->currentElement.insertBefore(elt, QDomNode());
return true;
}
diff --git a/plugins/impex/ora/ora_converter.cpp b/plugins/impex/ora/ora_converter.cpp
index 49667f9058..2f2f394eb0 100644
--- a/plugins/impex/ora/ora_converter.cpp
+++ b/plugins/impex/ora/ora_converter.cpp
@@ -1,118 +1,126 @@
/*
* 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, 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 "ora_converter.h"
#include <QApplication>
#include <QFileInfo>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoColorSpaceRegistry.h>
#include <KisDocument.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_open_raster_stack_load_visitor.h>
#include <kis_open_raster_stack_save_visitor.h>
#include <kis_paint_layer.h>
#include "kis_png_converter.h"
#include "kis_open_raster_load_context.h"
#include "kis_open_raster_save_context.h"
OraConverter::OraConverter(KisDocument *doc)
: m_doc(doc)
, m_stop(false)
{
}
OraConverter::~OraConverter()
{
}
KisImportExportErrorCode OraConverter::buildImage(QIODevice *io)
{
KoStore* store = KoStore::createStore(io, KoStore::Read, "image/openraster", KoStore::Zip);
if (!store) {
delete store;
- return ImportExportCodes::Failure;
+ return ImportExportCodes::FileFormatIncorrect;
}
KisOpenRasterLoadContext olc(store);
KisOpenRasterStackLoadVisitor orslv(m_doc->createUndoStore(), &olc);
orslv.loadImage();
m_image = orslv.image();
+
+ qDebug() << "m_image" << m_image;
+
+ if (!m_image) {
+ delete store;
+ return ImportExportCodes::ErrorWhileReading;
+ }
+
m_activeNodes = orslv.activeNodes();
delete store;
return ImportExportCodes::OK;
}
KisImageSP OraConverter::image()
{
return m_image;
}
vKisNodeSP OraConverter::activeNodes()
{
return m_activeNodes;
}
KisImportExportErrorCode OraConverter::buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes)
{
// Open file for writing
KoStore* store = KoStore::createStore(io, KoStore::Write, "image/openraster", KoStore::Zip);
if (!store) {
delete store;
return ImportExportCodes::Failure;
}
KisOpenRasterSaveContext osc(store);
KisOpenRasterStackSaveVisitor orssv(&osc, activeNodes);
image->rootLayer()->accept(orssv);
if (store->open("Thumbnails/thumbnail.png")) {
QSize previewSize = image->bounds().size();
previewSize.scale(QSize(256,256), Qt::KeepAspectRatio);
QImage preview = image->convertToQImage(previewSize, 0);
KoStoreDevice io(store);
if (io.open(QIODevice::WriteOnly)) {
preview.save(&io, "PNG");
}
io.close();
store->close();
}
KisPaintDeviceSP dev = image->projection();
KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store);
delete store;
return ImportExportCodes::OK;
}
void OraConverter::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/ora/ora_import.cc b/plugins/impex/ora/ora_import.cc
index d558a1d3de..7be26247c2 100644
--- a/plugins/impex/ora/ora_import.cc
+++ b/plugins/impex/ora/ora_import.cc
@@ -1,53 +1,56 @@
/*
* 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, 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 "ora_import.h"
#include <kpluginfactory.h>
#include <QFileInfo>
#include <KisDocument.h>
#include <kis_image.h>
#include "ora_converter.h"
K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_ora_import.json", registerPlugin<OraImport>();)
OraImport::OraImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
OraImport::~OraImport()
{
}
KisImportExportErrorCode OraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/)
{
OraConverter oraConverter(document);
KisImportExportErrorCode result = oraConverter.buildImage(io);
if (result.isOk()) {
document->setCurrentImage(oraConverter.image());
if (oraConverter.activeNodes().size() > 0) {
document->setPreActivatedNode(oraConverter.activeNodes()[0]);
}
}
+
+ qDebug() << ">>>>>>>>>" << result;
+
return result;
}
#include <ora_import.moc>
diff --git a/plugins/impex/ora/tests/CMakeLists.txt b/plugins/impex/ora/tests/CMakeLists.txt
index 40a1fd500f..9dfb9bc1fc 100644
--- a/plugins/impex/ora/tests/CMakeLists.txt
+++ b/plugins/impex/ora/tests/CMakeLists.txt
@@ -1,11 +1,12 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests )
include(KritaAddBrokenUnitTest)
+macro_add_unittest_definitions()
macro_add_unittest_definitions()
-ecm_add_test(KisOraTest.cpp
+krita_add_broken_unit_test( KisOraTest.cpp
TEST_NAME KisOraTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/png/tests/CMakeLists.txt b/plugins/impex/png/tests/CMakeLists.txt
index 8d78edc511..69c1557c77 100644
--- a/plugins/impex/png/tests/CMakeLists.txt
+++ b/plugins/impex/png/tests/CMakeLists.txt
@@ -1,11 +1,12 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests )
include(KritaAddBrokenUnitTest)
macro_add_unittest_definitions()
+include(KritaAddBrokenUnitTest)
-ecm_add_test(kis_png_test.cpp
+krita_add_broken_unit_test(kis_png_test.cpp
TEST_NAME kis_png_test
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "plugins-impex-")
diff --git a/plugins/impex/psd/psd_additional_layer_info_block.h b/plugins/impex/psd/psd_additional_layer_info_block.h
index 4dc020ac26..24cab8e122 100644
--- a/plugins/impex/psd/psd_additional_layer_info_block.h
+++ b/plugins/impex/psd/psd_additional_layer_info_block.h
@@ -1,295 +1,295 @@
/*
* 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.
*/
#ifndef PSD_ADDITIONAL_LAYER_INFO_BLOCK_H
#define PSD_ADDITIONAL_LAYER_INFO_BLOCK_H
#include <QString>
#include <QVector>
#include <QByteArray>
#include <QBitArray>
#include <QIODevice>
#include <QDomDocument>
#include <kis_types.h>
#include <kis_paint_device.h>
#include <kis_node.h>
#include <boost/function.hpp>
#include "psd.h"
#include "psd_header.h"
// additional layer information
// LEVELS
// Level record
struct psd_layer_level_record {
quint16 input_floor; // (0...253)
quint16 input_ceiling; // (2...255)
quint16 output_floor; // 255). Matched to input floor.
quint16 output_ceiling; // (0...255)
float gamma; // Short integer from 10...999 representing 0.1...9.99. Applied to all image data.
};
// Levels settings files are loaded and saved in the Levels dialog.
struct psd_layer_levels
{
psd_layer_level_record record[29]; // 29 sets of level records, each level containing 5 qint8 integers
// Photoshop CS (8.0) Additional information
// At the end of the Version 2 file is the following information:
quint16 extra_level_count; // Count of total level record structures. Subtract the legacy number of level record structures, 29, to determine how many are remaining in the file for reading.
psd_layer_level_record *extra_record; // Additianol level records according to count
quint8 lookup_table[3][256];
};
// CURVES
// The following is the data for each curve specified by count above
struct psd_layer_curves_data
{
quint16 channel_index; // Before each curve is a channel index.
quint16 point_count; // Count of points in the curve (qint8 integer from 2...19)
quint16 output_value[19]; // All coordinates have range 0 to 255
quint16 input_value[19];
};
// Curves file format
struct psd_layer_curves
{
quint16 curve_count; // Count of curves in the file.
psd_layer_curves_data * curve;
quint8 lookup_table[3][256];
};
// BRIGHTNESS AND CONTRAST
struct psd_layer_brightness_contrast
{
qint8 brightness;
qint8 contrast;
qint8 mean_value; // for brightness and contrast
qint8 Lab_color;
quint8 lookup_table[256];
};
// COLOR BALANCE
struct psd_layer_color_balance
{
qint8 cyan_red[3]; // (-100...100). shadows, midtones, highlights
qint8 magenta_green[3];
qint8 yellow_blue[3];
bool preserve_luminosity;
quint8 lookup_table[3][256];
};
// HUE/SATURATION
// Hue/Saturation settings files are loaded and saved in Photoshop¡¯s Hue/Saturation dialog
struct psd_layer_hue_saturation
{
quint8 hue_or_colorization; // 0 = Use settings for hue-adjustment; 1 = Use settings for colorization.
qint8 colorization_hue; // Photoshop 5.0: The actual values are stored for the new version. Hue is - 180...180, Saturation is 0...100, and Lightness is -100...100.
qint8 colorization_saturation;// Photoshop 4.0: Three qint8 integers Hue, Saturation, and Lightness from ¨C100...100.
qint8 colorization_lightness; // The user interface represents hue as ¨C180...180, saturation as 0...100, and Lightness as -100...1000, as the traditional HSB color wheel, with red = 0.
qint8 master_hue; // Master hue, saturation and lightness values.
qint8 master_saturation;
qint8 master_lightness;
qint8 range_values[6][4]; // For RGB and CMYK, those values apply to each of the six hextants in the HSB color wheel: those image pixels nearest to red, yellow, green, cyan, blue, or magenta. These numbers appear in the user interface from ¨C60...60, however the slider will reflect each of the possible 201 values from ¨C100...100.
qint8 setting_values[6][3]; // For Lab, the first four of the six values are applied to image pixels in the four Lab color quadrants, yellow, green, blue, and magenta. The other two values are ignored ( = 0). The values appear in the user interface from ¨C90 to 90.
quint8 lookup_table[6][360];
};
// SELECTIVE COLOR
// Selective Color settings files are loaded and saved in Photoshop¡¯s Selective Color dialog.
struct psd_layer_selective_color
{
quint16 correction_method; // 0 = Apply color correction in relative mode; 1 = Apply color correction in absolute mode.
qint8 cyan_correction[10]; // Amount of cyan correction. Short integer from ¨C100...100.
qint8 magenta_correction[10]; // Amount of magenta correction. Short integer from ¨C100...100.
qint8 yellow_correction[10]; // Amount of yellow correction. Short integer from ¨C100...100.
qint8 black_correction[10]; // Amount of black correction. Short integer from ¨C100...100.
};
// THRESHOLD
struct psd_layer_threshold
{
quint16 level; // (1...255)
} ;
// INVERT
// no parameter
// POSTERIZE
struct psd_layer_posterize
{
quint16 levels; // (2...255)
quint8 lookup_table[256];
};
// CHANNEL MIXER
struct psd_layer_channel_mixer
{
bool monochrome;
qint8 red_cyan[4]; // RGB or CMYK color plus constant for the mixer settings. 4 * 2 bytes of color with 2 bytes of constant.
qint8 green_magenta[4]; // (-200...200)
qint8 blue_yellow[4];
qint8 black[4];
qint8 constant[4];
};
// PHOTO FILTER
struct psd_layer_photo_filter
{
qint32 x_color; // 4 bytes each for XYZ color
qint32 y_color;
qint32 z_color;
qint32 density; // (1...100)
bool preserve_luminosity;
};
#include <kis_psd_layer_style.h>
struct psd_layer_solid_color {
quint32 id;
QColor fill_color;
};
struct psd_layer_gradient_fill {
quint32 id;
double angle;
psd_gradient_style style;
qint32 scale;
bool reverse; // Is gradient reverse
bool dithered; // Is gradient dithered
bool align_with_layer;
psd_gradient_color gradient_color;
};
struct psd_layer_pattern_fill {
quint32 id;
psd_pattern_info pattern_info;
qint32 scale;
};
struct psd_layer_type_face {
qint8 mark; // Mark value
qint32 font_type; // Font type data
qint8 font_name[256]; // Pascal string of font name
qint8 font_family_name[256]; // Pascal string of font family name
qint8 font_style_name[256]; // Pascal string of font style name
qint8 script; // Script value
qint32 number_axes_vector; // Number of design axes vector to follow
qint32 * vector; // Design vector value
};
struct psd_layer_type_style {
qint8 mark; // Mark value
qint8 face_mark; // Face mark value
qint32 size; // Size value
qint32 tracking; // Tracking value
qint32 kerning; // Kerning value
qint32 leading; // Leading value
qint32 base_shift; // Base shift value
bool auto_kern; // Auto kern on/off
bool rotate; // Rotate up/down
};
struct psd_layer_type_line {
qint32 char_count; // Character count value
qint8 orientation; // Orientation value
qint8 alignment; // Alignment value
qint8 actual_char; // Actual character as a double byte character
qint8 style; // Style value
};
struct psd_layer_type_tool {
double transform_info[6]; // 6 * 8 double precision numbers for the transform information
qint8 faces_count; // Count of faces
psd_layer_type_face * face;
qint8 styles_count; // Count of styles
psd_layer_type_style * style;
qint8 type; // Type value
qint32 scaling_factor; // Scaling factor value
qint32 character_count; // Character count value
qint32 horz_place; // Horizontal placement
qint32 vert_place; // Vertical placement
qint32 select_start; // Select start value
qint32 select_end; // Select end value
qint8 lines_count; // Line count
psd_layer_type_line * line;
QColor color;
bool anti_alias; // Anti alias on/off
};
/**
* @brief The PsdAdditionalLayerInfoBlock class implements the Additional Layer Information block
*
- * See: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_71546
+ * See: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_71546
*/
class PsdAdditionalLayerInfoBlock
{
public:
PsdAdditionalLayerInfoBlock(const PSDHeader& header);
typedef boost::function<bool(QIODevice*)> ExtraLayerInfoBlockHandler;
void setExtraLayerInfoBlockHandler(ExtraLayerInfoBlockHandler handler);
bool read(QIODevice* io);
bool write(QIODevice* io, KisNodeSP node);
void writeLuniBlockEx(QIODevice* io, const QString &layerName);
void writeLsctBlockEx(QIODevice* io, psd_section_type sectionType, bool isPassThrough, const QString &blendModeKey);
void writeLfx2BlockEx(QIODevice* io, const QDomDocument &stylesXmlDoc, bool useLfxsLayerStyleFormat);
void writePattBlockEx(QIODevice* io, const QDomDocument &patternsXmlDoc);
bool valid();
const PSDHeader &m_header;
QString error;
QStringList keys; // List of all the keys that we've seen
QString unicodeLayerName;
QDomDocument layerStyleXml;
QVector<QDomDocument> embeddedPatterns;
psd_section_type sectionDividerType;
QString sectionDividerBlendMode;
private:
void readImpl(QIODevice* io);
private:
ExtraLayerInfoBlockHandler m_layerInfoBlockHandler;
};
#endif // PSD_ADDITIONAL_LAYER_INFO_BLOCK_H
diff --git a/plugins/impex/psd/psd_layer_section.cpp b/plugins/impex/psd/psd_layer_section.cpp
index fd73f37997..ac15eaa1e8 100644
--- a/plugins/impex/psd/psd_layer_section.cpp
+++ b/plugins/impex/psd/psd_layer_section.cpp
@@ -1,585 +1,582 @@
/*
* 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.
*/
#include "psd_layer_section.h"
#include <QIODevice>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <kis_debug.h>
#include <kis_node.h>
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
#include <kis_effect_mask.h>
#include <kis_image.h>
#include "kis_dom_utils.h"
#include "psd_header.h"
#include "psd_utils.h"
#include "compression.h"
#include <asl/kis_offset_on_exit_verifier.h>
#include <asl/kis_asl_reader_utils.h>
#include <kis_asl_layer_style_serializer.h>
#include <asl/kis_asl_writer_utils.h>
PSDLayerMaskSection::PSDLayerMaskSection(const PSDHeader& header)
- : globalInfoSection(header),
- m_header(header)
+ : globalInfoSection(header)
+ , m_header(header)
{
- hasTransparency = false;
- layerMaskBlockSize = 0;
- nLayers = 0;
}
PSDLayerMaskSection::~PSDLayerMaskSection()
{
qDeleteAll(layers);
}
bool PSDLayerMaskSection::read(QIODevice* io)
{
bool retval = true; // be optimistic! <:-)
try {
retval = readImpl(io);
} catch (KisAslReaderUtils::ASLParseException &e) {
warnKrita << "WARNING: PSD (emb. pattern):" << e.what();
retval = false;
}
return retval;
}
bool PSDLayerMaskSection::readLayerInfoImpl(QIODevice* io)
{
quint32 layerInfoSectionSize = 0;
SAFE_READ_EX(io, layerInfoSectionSize);
if (layerInfoSectionSize & 0x1) {
warnKrita << "WARNING: layerInfoSectionSize is NOT even! Fixing...";
layerInfoSectionSize++;
}
{
SETUP_OFFSET_VERIFIER(layerInfoSectionTag, io, layerInfoSectionSize, 0);
dbgFile << "Layer info block size" << layerInfoSectionSize;
if (layerInfoSectionSize > 0 ) {
if (!psdread(io, &nLayers) || nLayers == 0) {
error = QString("Could not read read number of layers or no layers in image. %1").arg(nLayers);
return false;
}
hasTransparency = nLayers < 0; // first alpha channel is the alpha channel of the projection.
nLayers = qAbs(nLayers);
dbgFile << "Number of layers:" << nLayers;
dbgFile << "Has separate projection transparency:" << hasTransparency;
for (int i = 0; i < nLayers; ++i) {
dbgFile << "Going to read layer" << i << "pos" << io->pos();
dbgFile << "== Enter PSDLayerRecord";
QScopedPointer<PSDLayerRecord> layerRecord(new PSDLayerRecord(m_header));
if (!layerRecord->read(io)) {
error = QString("Could not load layer %1: %2").arg(i).arg(layerRecord->error);
return false;
}
dbgFile << "== Leave PSDLayerRecord";
dbgFile << "Finished reading layer" << i << layerRecord->layerName << "blending mode"
<< layerRecord->blendModeKey << io->pos()
<< "Number of channels:" << layerRecord->channelInfoRecords.size();
layers << layerRecord.take();
}
}
// get the positions for the channels belonging to each layer
for (int i = 0; i < nLayers; ++i) {
dbgFile << "Going to seek channel positions for layer" << i << "pos" << io->pos();
if (i > layers.size()) {
error = QString("Expected layer %1, but only have %2 layers").arg(i).arg(layers.size());
return false;
}
PSDLayerRecord *layerRecord = layers.at(i);
for (int j = 0; j < layerRecord->nChannels; ++j) {
// save the current location so we can jump beyond this block later on.
quint64 channelStartPos = io->pos();
dbgFile << "\tReading channel image data for channel" << j << "from pos" << io->pos();
KIS_ASSERT_RECOVER(j < layerRecord->channelInfoRecords.size()) { return false; }
ChannelInfo* channelInfo = layerRecord->channelInfoRecords.at(j);
quint16 compressionType;
if (!psdread(io, &compressionType)) {
error = "Could not read compression type for channel";
return false;
}
channelInfo->compressionType = (Compression::CompressionType)compressionType;
dbgFile << "\t\tChannel" << j << "has compression type" << compressionType;
QRect channelRect = layerRecord->channelRect(channelInfo);
// read the rle row lengths;
if (channelInfo->compressionType == Compression::RLE) {
for(qint64 row = 0; row < channelRect.height(); ++row) {
//dbgFile << "Reading the RLE bytecount position of row" << row << "at pos" << io->pos();
quint32 byteCount;
if (m_header.version == 1) {
quint16 _byteCount;
if (!psdread(io, &_byteCount)) {
error = QString("Could not read byteCount for rle-encoded channel");
return 0;
}
byteCount = _byteCount;
}
else {
if (!psdread(io, &byteCount)) {
error = QString("Could not read byteCount for rle-encoded channel");
return 0;
}
}
////dbgFile << "rle byte count" << byteCount;
channelInfo->rleRowLengths << byteCount;
}
}
// we're beyond all the length bytes, rle bytes and whatever, this is the
// location of the real pixel data
channelInfo->channelDataStart = io->pos();
dbgFile << "\t\tstart" << channelStartPos
<< "data start" << channelInfo->channelDataStart
<< "data length" << channelInfo->channelDataLength
<< "pos" << io->pos();
// make sure we are at the start of the next channel data block
io->seek(channelStartPos + channelInfo->channelDataLength);
// this is the length of the actual channel data bytes
channelInfo->channelDataLength = channelInfo->channelDataLength - (channelInfo->channelDataStart - channelStartPos);
dbgFile << "\t\tchannel record" << j << "for layer" << i << "with id" << channelInfo->channelId
<< "starting position" << channelInfo->channelDataStart
<< "with length" << channelInfo->channelDataLength
<< "and has compression type" << channelInfo->compressionType;
}
}
}
return true;
}
bool PSDLayerMaskSection::readImpl(QIODevice* io)
{
dbgFile << "reading layer section. Pos:" << io->pos() << "bytes left:" << io->bytesAvailable();
layerMaskBlockSize = 0;
if (m_header.version == 1) {
quint32 _layerMaskBlockSize = 0;
if (!psdread(io, &_layerMaskBlockSize) || _layerMaskBlockSize > (quint64)io->bytesAvailable()) {
error = QString("Could not read layer + mask block size. Got %1. Bytes left %2")
.arg(_layerMaskBlockSize).arg(io->bytesAvailable());
return false;
}
layerMaskBlockSize = _layerMaskBlockSize;
}
else if (m_header.version == 2) {
if (!psdread(io, &layerMaskBlockSize) || layerMaskBlockSize > (quint64)io->bytesAvailable()) {
error = QString("Could not read layer + mask block size. Got %1. Bytes left %2")
.arg(layerMaskBlockSize).arg(io->bytesAvailable());
return false;
}
}
quint64 start = io->pos();
dbgFile << "layer + mask section size" << layerMaskBlockSize;
if (layerMaskBlockSize == 0) {
dbgFile << "No layer + mask info, so no layers, only a background layer";
return true;
}
if (!readLayerInfoImpl(io)) {
return false;
}
quint32 globalMaskBlockLength;
if (!psdread(io, &globalMaskBlockLength)) {
error = "Could not read global mask info block";
return false;
}
if (globalMaskBlockLength > 0) {
if (!psdread(io, &globalLayerMaskInfo.overlayColorSpace)) {
error = "Could not read global mask info overlay colorspace";
return false;
}
for (int i = 0; i < 4; ++i) {
if (!psdread(io, &globalLayerMaskInfo.colorComponents[i])) {
error = QString("Could not read mask info visualizaion color component %1").arg(i);
return false;
}
}
if (!psdread(io, &globalLayerMaskInfo.opacity)) {
error = "Could not read global mask info visualization opacity";
return false;
}
if (!psdread(io, &globalLayerMaskInfo.kind)) {
error = "Could not read global mask info visualization type";
return false;
}
}
// global additional sections
/**
* Newer versions of PSD have layers info block wrapped into
* 'Lr16' or 'Lr32' additional section, while the main block is
* absent.
*
* Here we pass the callback which should be used when such
* additional section is recognized.
*/
globalInfoSection.setExtraLayerInfoBlockHandler(std::bind(&PSDLayerMaskSection::readLayerInfoImpl, this, std::placeholders::_1));
globalInfoSection.read(io);
/* put us after this section so reading the next section will work even if we mess up */
io->seek(start + layerMaskBlockSize);
return true;
}
struct FlattenedNode {
FlattenedNode() : type(RASTER_LAYER) {}
KisNodeSP node;
enum Type {
RASTER_LAYER,
FOLDER_OPEN,
FOLDER_CLOSED,
SECTION_DIVIDER
};
Type type;
};
void addBackgroundIfNeeded(KisNodeSP root, QList<FlattenedNode> &nodes)
{
KisGroupLayer *group = dynamic_cast<KisGroupLayer*>(root.data());
if (!group) return;
KoColor projectionColor = group->defaultProjectionColor();
if (projectionColor.opacityU8() == OPACITY_TRANSPARENT_U8) return;
KisPaintLayerSP layer =
new KisPaintLayer(group->image(),
i18nc("Automatically created layer name when saving into PSD", "Background"),
OPACITY_OPAQUE_U8);
layer->paintDevice()->setDefaultPixel(projectionColor);
{
FlattenedNode item;
item.node = layer;
item.type = FlattenedNode::RASTER_LAYER;
nodes << item;
}
}
void flattenNodes(KisNodeSP node, QList<FlattenedNode> &nodes)
{
KisNodeSP child = node->firstChild();
while (child) {
const bool isLayer = child->inherits("KisLayer");
const bool isGroupLayer = child->inherits("KisGroupLayer");
if (isGroupLayer) {
{
FlattenedNode item;
item.node = child;
item.type = FlattenedNode::SECTION_DIVIDER;
nodes << item;
}
flattenNodes(child, nodes);
{
FlattenedNode item;
item.node = child;
item.type = FlattenedNode::FOLDER_OPEN;
nodes << item;
}
} else if (isLayer) {
FlattenedNode item;
item.node = child;
item.type = FlattenedNode::RASTER_LAYER;
nodes << item;
}
child = child->nextSibling();
}
}
KisNodeSP findOnlyTransparencyMask(KisNodeSP node, FlattenedNode::Type type)
{
if (type != FlattenedNode::FOLDER_OPEN &&
type != FlattenedNode::FOLDER_CLOSED &&
type != FlattenedNode::RASTER_LAYER) {
return 0;
}
KisLayer *layer = qobject_cast<KisLayer*>(node.data());
QList<KisEffectMaskSP> masks = layer->effectMasks();
if (masks.size() != 1) return 0;
KisEffectMaskSP onlyMask = masks.first();
return onlyMask->inherits("KisTransparencyMask") ? onlyMask : 0;
}
QDomDocument fetchLayerStyleXmlData(KisNodeSP node)
{
const KisLayer *layer = qobject_cast<KisLayer*>(node.data());
KisPSDLayerStyleSP layerStyle = layer->layerStyle();
if (!layerStyle) return QDomDocument();
KisAslLayerStyleSerializer serializer;
serializer.setStyles(QVector<KisPSDLayerStyleSP>() << layerStyle);
return serializer.formPsdXmlDocument();
}
inline QDomNode findNodeByKey(const QString &key, QDomNode parent) {
return KisDomUtils::findElementByAttibute(parent, "node", "key", key);
}
void mergePatternsXMLSection(const QDomDocument &src, QDomDocument &dst)
{
QDomNode srcPatternsNode = findNodeByKey("Patterns", src.documentElement());
QDomNode dstPatternsNode = findNodeByKey("Patterns", dst.documentElement());
if (srcPatternsNode.isNull()) return;
if (dstPatternsNode.isNull()) {
dst = src;
return;
}
KIS_ASSERT_RECOVER_RETURN(!srcPatternsNode.isNull());
KIS_ASSERT_RECOVER_RETURN(!dstPatternsNode.isNull());
QDomNode node = srcPatternsNode.firstChild();
while(!node.isNull()) {
QDomNode importedNode = dst.importNode(node, true);
KIS_ASSERT_RECOVER_RETURN(!importedNode.isNull());
dstPatternsNode.appendChild(importedNode);
node = node.nextSibling();
}
}
bool PSDLayerMaskSection::write(QIODevice* io, KisNodeSP rootLayer)
{
bool retval = true;
try {
writeImpl(io, rootLayer);
} catch (KisAslWriterUtils::ASLWriteException &e) {
error = PREPEND_METHOD(e.what());
retval = false;
}
return retval;
}
void PSDLayerMaskSection::writeImpl(QIODevice* io, KisNodeSP rootLayer)
{
dbgFile << "Writing layer layer section";
// Build the whole layer structure
QList<FlattenedNode> nodes;
addBackgroundIfNeeded(rootLayer, nodes);
flattenNodes(rootLayer, nodes);
if (nodes.isEmpty()) {
throw KisAslWriterUtils::ASLWriteException("Could not find paint layers to save");
}
{
KisAslWriterUtils::OffsetStreamPusher<quint32> layerAndMaskSectionSizeTag(io, 2);
QDomDocument mergedPatternsXmlDoc;
{
KisAslWriterUtils::OffsetStreamPusher<quint32> layerInfoSizeTag(io, 4);
{
// number of layers (negative, because krita always has alpha)
const qint16 layersSize = -nodes.size();
SAFE_WRITE_EX(io, layersSize);
dbgFile << "Number of layers" << layersSize << "at" << io->pos();
}
// Layer records section
Q_FOREACH (const FlattenedNode &item, nodes) {
KisNodeSP node = item.node;
PSDLayerRecord *layerRecord = new PSDLayerRecord(m_header);
layers.append(layerRecord);
KisNodeSP onlyTransparencyMask = findOnlyTransparencyMask(node, item.type);
const QRect maskRect = onlyTransparencyMask ? onlyTransparencyMask->paintDevice()->exactBounds() : QRect();
const bool nodeVisible = node->visible();
const KoColorSpace *colorSpace = node->colorSpace();
const quint8 nodeOpacity = node->opacity();
const quint8 nodeClipping = 0;
const KisPaintLayer *paintLayer = qobject_cast<KisPaintLayer*>(node.data());
const bool alphaLocked = (paintLayer && paintLayer->alphaLocked());
const QString nodeCompositeOp = node->compositeOpId();
const KisGroupLayer *groupLayer = qobject_cast<KisGroupLayer*>(node.data());
const bool nodeIsPassThrough = groupLayer && groupLayer->passThroughMode();
QDomDocument stylesXmlDoc = fetchLayerStyleXmlData(node);
if (mergedPatternsXmlDoc.isNull() && !stylesXmlDoc.isNull()) {
mergedPatternsXmlDoc = stylesXmlDoc;
} else if (!mergedPatternsXmlDoc.isNull() && !stylesXmlDoc.isNull()) {
mergePatternsXMLSection(stylesXmlDoc, mergedPatternsXmlDoc);
}
bool nodeIrrelevant = false;
QString nodeName;
KisPaintDeviceSP layerContentDevice;
psd_section_type sectionType;
if (item.type == FlattenedNode::RASTER_LAYER) {
nodeIrrelevant = false;
nodeName = node->name();
layerContentDevice = onlyTransparencyMask ? node->original() : node->projection();
sectionType = psd_other;
} else {
nodeIrrelevant = true;
nodeName = item.type == FlattenedNode::SECTION_DIVIDER ?
QString("</Layer group>") :
node->name();
layerContentDevice = 0;
sectionType =
item.type == FlattenedNode::SECTION_DIVIDER ? psd_bounding_divider :
item.type == FlattenedNode::FOLDER_OPEN ? psd_open_folder :
psd_closed_folder;
}
// === no access to node anymore
QRect layerRect;
if (layerContentDevice) {
QRect rc = layerContentDevice->exactBounds();
rc = rc.normalized();
// keep to the max of photoshop's capabilities
if (rc.width() > 30000) rc.setWidth(30000);
if (rc.height() > 30000) rc.setHeight(30000);
layerRect = rc;
}
layerRecord->top = layerRect.y();
layerRecord->left = layerRect.x();
layerRecord->bottom = layerRect.y() + layerRect.height();
layerRecord->right = layerRect.x() + layerRect.width();
// colors + alpha channel
// note: transparency mask not included
layerRecord->nChannels = colorSpace->colorChannelCount() + 1;
ChannelInfo *info = new ChannelInfo;
info->channelId = -1; // For the alpha channel, which we always have in Krita, and should be saved first in
layerRecord->channelInfoRecords << info;
// the rest is in display order: rgb, cmyk, lab...
for (int i = 0; i < (int)colorSpace->colorChannelCount(); ++i) {
info = new ChannelInfo;
info->channelId = i; // 0 for red, 1 = green, etc
layerRecord->channelInfoRecords << info;
}
layerRecord->blendModeKey = composite_op_to_psd_blendmode(nodeCompositeOp);
layerRecord->isPassThrough = nodeIsPassThrough;
layerRecord->opacity = nodeOpacity;
layerRecord->clipping = nodeClipping;
layerRecord->transparencyProtected = alphaLocked;
layerRecord->visible = nodeVisible;
layerRecord->irrelevant = nodeIrrelevant;
layerRecord->layerName = nodeName.isEmpty() ? i18n("Unnamed Layer") : nodeName;
layerRecord->write(io,
layerContentDevice,
onlyTransparencyMask,
maskRect,
sectionType,
stylesXmlDoc,
node->inherits("KisGroupLayer"));
}
dbgFile << "start writing layer pixel data" << io->pos();
// Now save the pixel data
Q_FOREACH (PSDLayerRecord *layerRecord, layers) {
layerRecord->writePixelData(io);
}
}
{
// write the global layer mask info -- which is empty
const quint32 globalMaskSize = 0;
SAFE_WRITE_EX(io, globalMaskSize);
}
globalInfoSection.writePattBlockEx(io, mergedPatternsXmlDoc);
}
}
diff --git a/plugins/impex/psd/psd_layer_section.h b/plugins/impex/psd/psd_layer_section.h
index 55db74f209..bcdb430fec 100644
--- a/plugins/impex/psd/psd_layer_section.h
+++ b/plugins/impex/psd/psd_layer_section.h
@@ -1,72 +1,72 @@
/*
* 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 PSD_LAYER_SECTION_H
#define PSD_LAYER_SECTION_H
#include <QString>
class QIODevice;
#include <kis_types.h>
#include "psd_header.h"
#include "psd_layer_record.h"
class PSDLayerMaskSection
{
public:
PSDLayerMaskSection(const PSDHeader& header);
~PSDLayerMaskSection();
bool read(QIODevice* io);
bool write(QIODevice* io, KisNodeSP rootLayer);
QString error;
- // http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_21849
- quint64 layerMaskBlockSize; // Length of the layer and mask information section
+ // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_21849
+ quint64 layerMaskBlockSize {0}; // Length of the layer and mask information section
- // layer info: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000
- bool hasTransparency;
+ // layer info: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000
+ bool hasTransparency {false};
- qint16 nLayers; // If layer count is a negative number, its absolute value is the number of layers and the first alpha channel contains the transparency data for the merged result.
+ qint16 nLayers {0}; // If layer count is a negative number, its absolute value is the number of layers and the first alpha channel contains the transparency data for the merged result.
QVector<PSDLayerRecord*> layers;
- // mask info: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17115
+ // mask info: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17115
struct GlobalLayerMaskInfo {
- quint16 overlayColorSpace; // Overlay color space (undocumented).
- quint16 colorComponents[4]; // 4 * 2 byte color components
- quint16 opacity; // Opacity. 0 = transparent, 100 = opaque.
- quint8 kind; // Kind. 0 = Color selected--i.e. inverted; 1 = Color protected;128 = use value stored per layer. This value is preferred. The others are for backward compatibility with beta versions.
+ quint16 overlayColorSpace {0}; // Overlay color space (undocumented).
+ quint16 colorComponents[4] {0, 0, 0, 0}; // 4 * 2 byte color components
+ quint16 opacity {0}; // Opacity. 0 = transparent, 100 = opaque.
+ quint8 kind {0}; // Kind. 0 = Color selected--i.e. inverted; 1 = Color protected;128 = use value stored per layer. This value is preferred. The others are for backward compatibility with beta versions.
};
GlobalLayerMaskInfo globalLayerMaskInfo;
PsdAdditionalLayerInfoBlock globalInfoSection;
private:
bool readLayerInfoImpl(QIODevice* io);
bool readImpl(QIODevice* io);
void writeImpl(QIODevice* io, KisNodeSP rootLayer);
private:
const PSDHeader m_header;
};
#endif // PSD_LAYER_SECTION_H
diff --git a/plugins/impex/psd/psd_loader.cpp b/plugins/impex/psd/psd_loader.cpp
index f68e47a503..d538d215c7 100644
--- a/plugins/impex/psd/psd_loader.cpp
+++ b/plugins/impex/psd/psd_loader.cpp
@@ -1,380 +1,380 @@
/*
* 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.
*/
#include "psd_loader.h"
#include <QApplication>
#include <QFileInfo>
#include <QStack>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KoColorProfile.h>
#include <KoCompositeOp.h>
#include <KoUnit.h>
#include <kis_annotation.h>
#include <kis_types.h>
#include <kis_paint_layer.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_paint_device.h>
#include <kis_transaction.h>
#include <kis_transparency_mask.h>
#include <kis_asl_layer_style_serializer.h>
#include <kis_psd_layer_style_resource.h>
#include "KisResourceServerProvider.h"
#include "psd.h"
#include "psd_header.h"
#include "psd_colormode_block.h"
#include "psd_utils.h"
#include "psd_resource_section.h"
#include "psd_layer_section.h"
#include "psd_resource_block.h"
#include "psd_image_data.h"
PSDLoader::PSDLoader(KisDocument *doc)
: m_image(0)
, m_doc(doc)
, m_stop(false)
{
}
PSDLoader::~PSDLoader()
{
}
KisImportExportErrorCode PSDLoader::decode(QIODevice *io)
{
// open the file
dbgFile << "pos:" << io->pos();
PSDHeader header;
if (!header.read(io)) {
dbgFile << "failed reading header: " << header.error;
return ImportExportCodes::FileFormatIncorrect;
}
dbgFile << header;
dbgFile << "Read header. pos:" << io->pos();
PSDColorModeBlock colorModeBlock(header.colormode);
if (!colorModeBlock.read(io)) {
dbgFile << "failed reading colormode block: " << colorModeBlock.error;
return ImportExportCodes::FileFormatIncorrect;
}
dbgFile << "Read color mode block. pos:" << io->pos();
PSDImageResourceSection resourceSection;
if (!resourceSection.read(io)) {
dbgFile << "failed image reading resource section: " << resourceSection.error;
return ImportExportCodes::FileFormatIncorrect;
}
dbgFile << "Read image resource section. pos:" << io->pos();
PSDLayerMaskSection layerSection(header);
if (!layerSection.read(io)) {
dbgFile << "failed reading layer/mask section: " << layerSection.error;
return ImportExportCodes::FileFormatIncorrect;
}
dbgFile << "Read layer/mask section. " << layerSection.nLayers << "layers. pos:" << io->pos();
// Done reading, except possibly for the image data block, which is only relevant if there
// are no layers.
// Get the right colorspace
QPair<QString, QString> colorSpaceId = psd_colormode_to_colormodelid(header.colormode,
header.channelDepth);
if (colorSpaceId.first.isNull()) {
dbgFile << "Unsupported colorspace" << header.colormode << header.channelDepth;
return ImportExportCodes::FormatColorSpaceUnsupported;
}
// Get the icc profile from the image resource section
const KoColorProfile* profile = 0;
if (resourceSection.resources.contains(PSDImageResourceSection::ICC_PROFILE)) {
ICC_PROFILE_1039 *iccProfileData = dynamic_cast<ICC_PROFILE_1039*>(resourceSection.resources[PSDImageResourceSection::ICC_PROFILE]->resource);
if (iccProfileData ) {
profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first,
colorSpaceId.second,
iccProfileData->icc);
dbgFile << "Loaded ICC profile" << profile->name();
delete resourceSection.resources.take(PSDImageResourceSection::ICC_PROFILE);
}
}
// Create the colorspace
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile);
if (!cs) {
return ImportExportCodes::FormatColorSpaceUnsupported;
}
// Creating the KisImage
QFile *file = dynamic_cast<QFile*>(io);
QString name = file ? file->fileName() : "Imported";
m_image = new KisImage(m_doc->createUndoStore(), header.width, header.height, cs, name);
Q_CHECK_PTR(m_image);
m_image->lock();
// set the correct resolution
if (resourceSection.resources.contains(PSDImageResourceSection::RESN_INFO)) {
RESN_INFO_1005 *resInfo = dynamic_cast<RESN_INFO_1005*>(resourceSection.resources[PSDImageResourceSection::RESN_INFO]->resource);
if (resInfo) {
// check resolution size is not zero
if (resInfo->hRes * resInfo->vRes > 0)
m_image->setResolution(POINT_TO_INCH(resInfo->hRes), POINT_TO_INCH(resInfo->vRes));
// let's skip the unit for now; we can only set that on the KisDocument, and krita doesn't use it.
delete resourceSection.resources.take(PSDImageResourceSection::RESN_INFO);
}
}
// Preserve all the annotations
Q_FOREACH (PSDResourceBlock *resourceBlock, resourceSection.resources.values()) {
m_image->addAnnotation(resourceBlock);
}
// Preserve the duotone colormode block for saving back to psd
if (header.colormode == DuoTone) {
KisAnnotationSP annotation = new KisAnnotation("DuotoneColormodeBlock",
i18n("Duotone Colormode Block"),
colorModeBlock.data);
m_image->addAnnotation(annotation);
}
// Read the projection into our single layer. Since we only read the projection when
// we have just one layer, we don't need to later on apply the alpha channel of the
// first layer to the projection if the number of layers is negative/
- // See http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000.
+ // See https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000.
if (layerSection.nLayers == 0) {
dbgFile << "Position" << io->pos() << "Going to read the projection into the first layer, which Photoshop calls 'Background'";
KisPaintLayerSP layer = new KisPaintLayer(m_image, i18n("Background"), OPACITY_OPAQUE_U8);
PSDImageData imageData(&header);
imageData.read(io, layer->paintDevice());
m_image->addNode(layer, m_image->rootLayer());
// Only one layer, the background layer, so we're done.
m_image->unlock();
return ImportExportCodes::OK;
}
// More than one layer, so now construct the Krita image from the info we read.
QStack<KisGroupLayerSP> groupStack;
groupStack.push(m_image->rootLayer());
/**
* PSD has a weird "optimization": if a group layer has only one
* child layer, it omits it's 'psd_bounding_divider' section. So
* fi you ever see an unbalanced layers group in PSD, most
* probably, it is just a single layered group.
*/
KisNodeSP lastAddedLayer;
typedef QPair<QDomDocument, KisLayerSP> LayerStyleMapping;
QVector<LayerStyleMapping> allStylesXml;
// read the channels for the various layers
for(int i = 0; i < layerSection.nLayers; ++i) {
PSDLayerRecord* layerRecord = layerSection.layers.at(i);
dbgFile << "Going to read channels for layer" << i << layerRecord->layerName;
KisLayerSP newLayer;
if (layerRecord->infoBlocks.keys.contains("lsct") &&
layerRecord->infoBlocks.sectionDividerType != psd_other) {
if (layerRecord->infoBlocks.sectionDividerType == psd_bounding_divider && !groupStack.isEmpty()) {
KisGroupLayerSP groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8);
m_image->addNode(groupLayer, groupStack.top());
groupStack.push(groupLayer);
newLayer = groupLayer;
}
else if ((layerRecord->infoBlocks.sectionDividerType == psd_open_folder ||
layerRecord->infoBlocks.sectionDividerType == psd_closed_folder) &&
(groupStack.size() > 1 || (lastAddedLayer && !groupStack.isEmpty()))) {
KisGroupLayerSP groupLayer;
if (groupStack.size() <= 1) {
groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8);
m_image->addNode(groupLayer, groupStack.top());
m_image->moveNode(lastAddedLayer, groupLayer, KisNodeSP());
} else {
groupLayer = groupStack.pop();
}
const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml;
if (!styleXml.isNull()) {
allStylesXml << LayerStyleMapping(styleXml, groupLayer);
}
groupLayer->setName(layerRecord->layerName);
groupLayer->setVisible(layerRecord->visible);
QString compositeOp = psd_blendmode_to_composite_op(layerRecord->infoBlocks.sectionDividerBlendMode);
// Krita doesn't support pass-through blend
// mode. Instead it is just a property of a group
// layer, so flip it
if (compositeOp == COMPOSITE_PASS_THROUGH) {
compositeOp = COMPOSITE_OVER;
groupLayer->setPassThroughMode(true);
}
groupLayer->setCompositeOpId(compositeOp);
newLayer = groupLayer;
} else {
/**
* In some files saved by PS CS6 the group layer sections seem
* to be unbalanced. I don't know why it happens because the
* reporter didn't provide us an example file. So here we just
* check if the new layer was created, and if not, skip the
* initialization of masks.
*
* See bug: 357559
*/
warnKrita << "WARNING: Provided PSD has unbalanced group "
<< "layer markers. Some masks and/or layers can "
<< "be lost while loading this file. Please "
<< "report a bug to Krita developers and attach "
<< "this file to the bugreport\n"
<< " " << ppVar(layerRecord->layerName) << "\n"
<< " " << ppVar(layerRecord->infoBlocks.sectionDividerType) << "\n"
<< " " << ppVar(groupStack.size());
continue;
}
}
else {
KisPaintLayerSP layer = new KisPaintLayer(m_image, layerRecord->layerName, layerRecord->opacity);
layer->setCompositeOpId(psd_blendmode_to_composite_op(layerRecord->blendModeKey));
const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml;
if (!styleXml.isNull()) {
allStylesXml << LayerStyleMapping(styleXml, layer);
}
if (!layerRecord->readPixelData(io, layer->paintDevice())) {
dbgFile << "failed reading channels for layer: " << layerRecord->layerName << layerRecord->error;
return ImportExportCodes::FileFormatIncorrect;
}
if (!groupStack.isEmpty()) {
m_image->addNode(layer, groupStack.top());
}
else {
m_image->addNode(layer, m_image->root());
}
layer->setVisible(layerRecord->visible);
newLayer = layer;
}
Q_FOREACH (ChannelInfo *channelInfo, layerRecord->channelInfoRecords) {
if (channelInfo->channelId < -1) {
KisTransparencyMaskSP mask = new KisTransparencyMask();
mask->setName(i18n("Transparency Mask"));
mask->initSelection(newLayer);
if (!layerRecord->readMask(io, mask->paintDevice(), channelInfo)) {
dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error;
}
m_image->addNode(mask, newLayer);
}
}
lastAddedLayer = newLayer;
}
const QVector<QDomDocument> &embeddedPatterns =
layerSection.globalInfoSection.embeddedPatterns;
KisAslLayerStyleSerializer serializer;
if (!embeddedPatterns.isEmpty()) {
Q_FOREACH (const QDomDocument &doc, embeddedPatterns) {
serializer.registerPSDPattern(doc);
}
}
QVector<KisPSDLayerStyleSP> allStylesForServer;
if (!allStylesXml.isEmpty()) {
Q_FOREACH (const LayerStyleMapping &mapping, allStylesXml) {
serializer.readFromPSDXML(mapping.first);
if (serializer.styles().size() == 1) {
KisPSDLayerStyleSP layerStyle = serializer.styles().first();
KisLayerSP layer = mapping.second;
layerStyle->setName(layer->name());
allStylesForServer << layerStyle;
layer->setLayerStyle(layerStyle->clone());
} else {
warnKrita << "WARNING: Couldn't read layer style!" << ppVar(serializer.styles());
}
}
}
if (!allStylesForServer.isEmpty()) {
KisPSDLayerStyleCollectionResource *collection =
new KisPSDLayerStyleCollectionResource("Embedded PSD Styles.asl");
collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_image->objectName()));
KIS_SAFE_ASSERT_RECOVER_NOOP(!collection->valid());
collection->setLayerStyles(allStylesForServer);
KIS_SAFE_ASSERT_RECOVER_NOOP(collection->valid());
KoResourceServer<KisPSDLayerStyleCollectionResource> *server = KisResourceServerProvider::instance()->layerStyleCollectionServer();
server->addResource(collection, false);
}
m_image->unlock();
return ImportExportCodes::OK;
}
KisImportExportErrorCode PSDLoader::buildImage(QIODevice *io)
{
return decode(io);
}
KisImageSP PSDLoader::image()
{
return m_image;
}
void PSDLoader::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/AUTHORS b/plugins/impex/raw/3rdparty/libkdcraw/AUTHORS
index 4ece2f5b77..0b6b91fa4a 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/AUTHORS
+++ b/plugins/impex/raw/3rdparty/libkdcraw/AUTHORS
@@ -1,15 +1,15 @@
AUTHORS AND MAINTAINERS :
Caulier Gilles <caulier dot gilles at gmail dot com>
Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
CONTRIBUTORS:
Angelo Naselli <anaselli at linux dot it>
Gerhard Kulzer <gerhard at kulzer dot net>
Achim Bohnet <ach at mpe dot mpg dot de>
Guillaume Castagnino <casta at xwing dot info>
THANKS:
-Alex Tutubalin <lexa at lexa dot ru> from LibRaw project (http://www.libraw.org)
\ No newline at end of file
+Alex Tutubalin <lexa at lexa dot ru> from LibRaw project (https://www.libraw.org)
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/README b/plugins/impex/raw/3rdparty/libkdcraw/README
index d85be7c116..47462fd8e6 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/README
+++ b/plugins/impex/raw/3rdparty/libkdcraw/README
@@ -1,61 +1,61 @@
LibRaw C++ interface for KDE
-This library is a part of digiKam project (http://www.digikam.org)
+This library is a part of digiKam project (https://www.digikam.org)
-- AUTHORS -----------------------------------------------------------
See AUTHORS file for details.
-- ABOUT -------------------------------------------------------------
Libkdcraw is a C++ interface around LibRaw library used to decode RAW
-picture files. More information about LibRaw can be found at http://www.libraw.org.
+picture files. More information about LibRaw can be found at https://www.libraw.org.
This library is used by kipi-plugins, digiKam and others kipi host programs.
The library documentation is available on header files.
-- DEPENDENCIES -------------------------------------------------------
-CMake >= 2.8.12 http://www.cmake.org
+CMake >= 2.8.12 https://www.cmake.org
ECM >= 1.1.0 https://projects.kde.org/projects/kdesupport/extra-cmake-modules
-libqt >= 5.2.0 http://qt-project.org
-libkde >= 5.0.0 http://www.kde.org
-libraw >= 0.16.x http://www.libraw.org
+libqt >= 5.2.0 https://qt.io
+libkde >= 5.0.0 https://www.kde.org
+libraw >= 0.16.x https://www.libraw.org
Note: all library dependencies require development and binary packages installed on your
computer to compile digiKam.
-- INSTALL ------------------------------------------------------------
Usual CMake options:
-DCMAKE_INSTALL_PREFIX : decide where the program will be install on your computer.
-DCMAKE_BUILD_TYPE : decide which type of build you want. You can chose between "debug", "profile", "relwithdebinfo" and "release". The default is "relwithdebinfo" (-O2 -g).
Note: To know KDE install path on your computer, use 'kf5-config --prefix' command line like this (with debug object enabled):
"cmake . -DCMAKE_BUILD_TYPE=debug -DCMAKE_INSTALL_PREFIX=`kf5-config --prefix`"
-- CONTACT ------------------------------------------------------------
If you have questions, comments, suggestions to make do email at :
digikam-devel@kde.org
IRC channel from freenode.net server:
#digikam
-- BUGS ---------------------------------------------------------------
IMPORTANT : the bugreports and the wishlist are hosted by the KDE bugs report
system who can be contacted by the standard Kde help menu of the plugins dialog.
A mail will be automatically sent to the digiKam devel mailing list.
There is no need to contact directly the digiKam mailing list for a bug report
or a devel wish.
The current digiKam bugs and devel wish reported to the Kde bugs report can be seen
at this url:
-http://bugs.kde.org/buglist.cgi?product=digikam&component=libkdcraw&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED
+https://bugs.kde.org/buglist.cgi?product=digikam&component=libkdcraw&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/cmake/modules/FindLibRaw.cmake b/plugins/impex/raw/3rdparty/libkdcraw/cmake/modules/FindLibRaw.cmake
index d6966dbc04..0baddc03cd 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/cmake/modules/FindLibRaw.cmake
+++ b/plugins/impex/raw/3rdparty/libkdcraw/cmake/modules/FindLibRaw.cmake
@@ -1,79 +1,79 @@
# - Find LibRaw
-# Find the LibRaw library <http://www.libraw.org>
+# Find the LibRaw library <https://www.libraw.org>
# This module defines
# LibRaw_VERSION_STRING, the version string of LibRaw
# LibRaw_INCLUDE_DIR, where to find libraw.h
# LibRaw_LIBRARIES, the libraries needed to use LibRaw (non-thread-safe)
# LibRaw_r_LIBRARIES, the libraries needed to use LibRaw (thread-safe)
# LibRaw_DEFINITIONS, the definitions needed to use LibRaw (non-thread-safe)
# LibRaw_r_DEFINITIONS, the definitions needed to use LibRaw (thread-safe)
#
# Copyright (c) 2013, Pino Toscano <pino at kde dot org>
# Copyright (c) 2013, Gilles Caulier <caulier dot gilles at gmail dot com>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
FIND_PACKAGE(PkgConfig)
IF(PKG_CONFIG_FOUND)
PKG_CHECK_MODULES(PC_LIBRAW libraw)
SET(LibRaw_DEFINITIONS ${PC_LIBRAW_CFLAGS_OTHER})
PKG_CHECK_MODULES(PC_LIBRAW_R libraw_r)
SET(LibRaw_r_DEFINITIONS ${PC_LIBRAW_R_CFLAGS_OTHER})
ENDIF()
FIND_PATH(LibRaw_INCLUDE_DIR libraw.h
HINTS
${PC_LIBRAW_INCLUDEDIR}
${PC_LibRaw_INCLUDE_DIRS}
PATH_SUFFIXES libraw
)
FIND_LIBRARY(LibRaw_LIBRARIES NAMES raw
HINTS
${PC_LIBRAW_LIBDIR}
${PC_LIBRAW_LIBRARY_DIRS}
)
FIND_LIBRARY(LibRaw_r_LIBRARIES NAMES raw_r
HINTS
${PC_LIBRAW_R_LIBDIR}
${PC_LIBRAW_R_LIBRARY_DIRS}
)
IF(LibRaw_INCLUDE_DIR)
FILE(READ ${LibRaw_INCLUDE_DIR}/libraw_version.h _libraw_version_content)
STRING(REGEX MATCH "#define LIBRAW_MAJOR_VERSION[ \t]*([0-9]*)\n" _version_major_match ${_libraw_version_content})
SET(_libraw_version_major "${CMAKE_MATCH_1}")
STRING(REGEX MATCH "#define LIBRAW_MINOR_VERSION[ \t]*([0-9]*)\n" _version_minor_match ${_libraw_version_content})
SET(_libraw_version_minor "${CMAKE_MATCH_1}")
STRING(REGEX MATCH "#define LIBRAW_PATCH_VERSION[ \t]*([0-9]*)\n" _version_patch_match ${_libraw_version_content})
SET(_libraw_version_patch "${CMAKE_MATCH_1}")
IF(_version_major_match AND _version_minor_match AND _version_patch_match)
SET(LibRaw_VERSION_STRING "${_libraw_version_major}.${_libraw_version_minor}.${_libraw_version_patch}")
ELSE()
IF(NOT LibRaw_FIND_QUIETLY)
MESSAGE(STATUS "Failed to get version information from ${LibRaw_INCLUDE_DIR}/libraw_version.h")
ENDIF()
ENDIF()
ENDIF()
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibRaw
REQUIRED_VARS LibRaw_LIBRARIES LibRaw_INCLUDE_DIR
VERSION_VAR LibRaw_VERSION_STRING
)
MARK_AS_ADVANCED(LibRaw_VERSION_STRING
LibRaw_INCLUDE_DIR
LibRaw_LIBRARIES
LibRaw_r_LIBRARIES
LibRaw_DEFINITIONS
LibRaw_r_DEFINITIONS
)
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/cmake/templates/libkdcraw.pc.cmake b/plugins/impex/raw/3rdparty/libkdcraw/cmake/templates/libkdcraw.pc.cmake
index 3e4cb29116..30e2e85cec 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/cmake/templates/libkdcraw.pc.cmake
+++ b/plugins/impex/raw/3rdparty/libkdcraw/cmake/templates/libkdcraw.pc.cmake
@@ -1,12 +1,12 @@
prefix=${CMAKE_INSTALL_PREFIX}
exec_prefix=${BIN_INSTALL_DIR}
libdir=${LIB_INSTALL_DIR}
includedir=${INCLUDE_INSTALL_DIR}
Name: ${PROJECT_NAME}
Description: A C++ wrapper around LibRaw library to decode RAW pictures. This library is used by digiKam and kipi-plugins.
-URL: http://www.digikam.org/sharedlibs
+URL: https://commits.kde.org/libkdcraw
Requires:
Version: ${DCRAW_LIB_VERSION_STRING}
Libs: -L${LIB_INSTALL_DIR} -lkdcraw
Cflags: -I${INCLUDE_INSTALL_DIR}
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/Mainpage.dox b/plugins/impex/raw/3rdparty/libkdcraw/src/Mainpage.dox
index 7b96e9e556..a02a96f450 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/Mainpage.dox
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/Mainpage.dox
@@ -1,7 +1,7 @@
/** @mainpage libKDcraw
-libKDcraw is a thread-safe wrapper around <a href="http://www.libraw.org">libraw</a>. Have a look at KDcrawIface::KDcraw to get started.
+libKDcraw is a thread-safe wrapper around <a href="https://www.libraw.org">libraw</a>. Have a look at KDcrawIface::KDcraw to get started.
@see KDcrawIface::KDcraw
*/
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.cpp
index b82ac4c653..65655df64f 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.cpp
@@ -1,173 +1,173 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2007-05-02
* @brief RAW file identification information container
*
* @author Copyright (C) 2007-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.
*
* ============================================================ */
// Local includes
#include "dcrawinfocontainer.h"
namespace KDcrawIface
{
DcrawInfoContainer::DcrawInfoContainer()
{
sensitivity = -1.0;
exposureTime = -1.0;
aperture = -1.0;
focalLength = -1.0;
pixelAspectRatio = 1.0; // Default value. This can be unavailable (depending of camera model).
rawColors = -1;
rawImages = -1;
hasIccProfile = false;
isDecodable = false;
daylightMult[0] = 0.0;
daylightMult[1] = 0.0;
daylightMult[2] = 0.0;
cameraMult[0] = 0.0;
cameraMult[1] = 0.0;
cameraMult[2] = 0.0;
cameraMult[3] = 0.0;
blackPoint = 0;
for (int ch=0; ch<4; ch++)
{
blackPointCh[ch] = 0;
}
whitePoint = 0;
topMargin = 0;
leftMargin = 0;
orientation = ORIENTATION_NONE;
for (int x=0 ; x<3 ; x++)
{
for (int y=0 ; y<4 ; y++)
{
cameraColorMatrix1[x][y] = 0.0;
cameraColorMatrix2[x][y] = 0.0;
cameraXYZMatrix[y][x] = 0.0; // NOTE: see B.K.O # 253911 : [y][x] not [x][y]
}
}
}
DcrawInfoContainer::~DcrawInfoContainer()
{
}
bool DcrawInfoContainer::isEmpty()
{
if (make.isEmpty() &&
model.isEmpty() &&
filterPattern.isEmpty() &&
colorKeys.isEmpty() &&
DNGVersion.isEmpty() &&
exposureTime == -1.0 &&
aperture == -1.0 &&
focalLength == -1.0 &&
pixelAspectRatio == 1.0 &&
sensitivity == -1.0 &&
rawColors == -1 &&
rawImages == -1 &&
blackPoint == 0 &&
blackPointCh[0] == 0 &&
blackPointCh[1] == 0 &&
blackPointCh[2] == 0 &&
blackPointCh[3] == 0 &&
whitePoint == 0 &&
topMargin == 0 &&
leftMargin == 0 &&
!dateTime.isValid() &&
!imageSize.isValid() &&
!fullSize.isValid() &&
!outputSize.isValid() &&
!thumbSize.isValid() &&
cameraColorMatrix1[0][0] == 0.0 &&
cameraColorMatrix1[0][1] == 0.0 &&
cameraColorMatrix1[0][2] == 0.0 &&
cameraColorMatrix1[0][3] == 0.0 &&
cameraColorMatrix1[1][0] == 0.0 &&
cameraColorMatrix1[1][1] == 0.0 &&
cameraColorMatrix1[1][2] == 0.0 &&
cameraColorMatrix1[1][3] == 0.0 &&
cameraColorMatrix1[2][0] == 0.0 &&
cameraColorMatrix1[2][1] == 0.0 &&
cameraColorMatrix1[2][2] == 0.0 &&
cameraColorMatrix1[2][3] == 0.0 &&
cameraColorMatrix2[0][0] == 0.0 &&
cameraColorMatrix2[0][1] == 0.0 &&
cameraColorMatrix2[0][2] == 0.0 &&
cameraColorMatrix2[0][3] == 0.0 &&
cameraColorMatrix2[1][0] == 0.0 &&
cameraColorMatrix2[1][1] == 0.0 &&
cameraColorMatrix2[1][2] == 0.0 &&
cameraColorMatrix2[1][3] == 0.0 &&
cameraColorMatrix2[2][0] == 0.0 &&
cameraColorMatrix2[2][1] == 0.0 &&
cameraColorMatrix2[2][2] == 0.0 &&
cameraColorMatrix2[2][3] == 0.0 &&
cameraXYZMatrix[0][0] == 0.0 &&
cameraXYZMatrix[0][1] == 0.0 &&
cameraXYZMatrix[0][2] == 0.0 &&
cameraXYZMatrix[1][0] == 0.0 &&
cameraXYZMatrix[1][1] == 0.0 &&
cameraXYZMatrix[1][2] == 0.0 &&
cameraXYZMatrix[2][0] == 0.0 &&
cameraXYZMatrix[2][1] == 0.0 &&
cameraXYZMatrix[2][2] == 0.0 &&
cameraXYZMatrix[3][0] == 0.0 &&
cameraXYZMatrix[3][1] == 0.0 &&
cameraXYZMatrix[3][2] == 0.0 &&
orientation == ORIENTATION_NONE
)
{
return true;
}
else
{
return false;
}
}
QDebug operator<<(QDebug dbg, const DcrawInfoContainer& c)
{
dbg.nospace() << "DcrawInfoContainer::sensitivity: " << c.sensitivity << ", ";
dbg.nospace() << "DcrawInfoContainer::exposureTime: " << c.exposureTime << ", ";
dbg.nospace() << "DcrawInfoContainer::aperture: " << c.aperture << ", ";
dbg.nospace() << "DcrawInfoContainer::focalLength: " << c.focalLength << ", ";
dbg.nospace() << "DcrawInfoContainer::pixelAspectRatio: " << c.pixelAspectRatio << ", ";
dbg.nospace() << "DcrawInfoContainer::rawColors: " << c.rawColors << ", ";
dbg.nospace() << "DcrawInfoContainer::rawImages: " << c.rawImages << ", ";
dbg.nospace() << "DcrawInfoContainer::hasIccProfile: " << c.hasIccProfile << ", ";
dbg.nospace() << "DcrawInfoContainer::isDecodable: " << c.isDecodable << ", ";
dbg.nospace() << "DcrawInfoContainer::daylightMult: " << c.daylightMult << ", ";
dbg.nospace() << "DcrawInfoContainer::cameraMult: " << c.cameraMult << ", ";
dbg.nospace() << "DcrawInfoContainer::blackPoint: " << c.blackPoint << ", ";
dbg.nospace() << "DcrawInfoContainer::whitePoint: " << c.whitePoint << ", ";
dbg.nospace() << "DcrawInfoContainer::topMargin: " << c.topMargin << ", ";
dbg.nospace() << "DcrawInfoContainer::leftMargin: " << c.leftMargin << ", ";
dbg.nospace() << "DcrawInfoContainer::orientation: " << c.orientation;
return dbg.space();
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.h b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.h
index bf0773a78b..739b9ef4c5 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawinfocontainer.h
@@ -1,158 +1,158 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2007-05-02
* @brief RAW file identification information container
*
* @author Copyright (C) 2007-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.
*
* ============================================================ */
#ifndef DCRAW_INFO_CONTAINER_H
#define DCRAW_INFO_CONTAINER_H
// Qt includes
#include <QtCore/QString>
#include <QtCore/QDateTime>
#include <QtCore/QSize>
#include <QtCore/QDebug>
// Local includes
namespace KDcrawIface
{
class DcrawInfoContainer
{
public:
/** The RAW image orientation values
*/
enum ImageOrientation
{
ORIENTATION_NONE = 0,
ORIENTATION_180 = 3,
ORIENTATION_Mirror90CCW = 4,
ORIENTATION_90CCW = 5,
ORIENTATION_90CW = 6
};
public:
/** Standard constructor */
DcrawInfoContainer();
/** Standard destructor */
virtual ~DcrawInfoContainer();
/** Return 'true' if container is empty, else 'false' */
bool isEmpty();
public:
/** True if RAW file include an ICC color profile. */
bool hasIccProfile;
/** True is RAW file is decodable by dcraw. */
bool isDecodable;
/** The number of RAW colors. */
int rawColors;
/** The number of RAW images. */
int rawImages;
/** Black level from Raw histogram. */
unsigned int blackPoint;
/** Channel black levels from Raw histogram. */
unsigned int blackPointCh[4];
/** White level from Raw histogram. */
unsigned int whitePoint;
/** Top margin of raw image. */
unsigned int topMargin;
/** Left margin of raw image. */
unsigned int leftMargin;
/** The raw image orientation */
ImageOrientation orientation;
/** The sensitivity in ISO used by camera to take the picture. */
float sensitivity;
/** ==> 1/exposureTime = exposure time in seconds. */
float exposureTime;
/** ==> Aperture value in APEX. */
float aperture;
/** ==> Focal Length value in mm. */
float focalLength;
/** The pixel Aspect Ratio if != 1.0. NOTE: if == 1.0, dcraw do not show this value. */
float pixelAspectRatio;
/** White color balance settings. */
double daylightMult[3];
/** Camera multipliers used for White Balance adjustments */
double cameraMult[4];
/** Camera Color Matrix */
float cameraColorMatrix1[3][4];
float cameraColorMatrix2[3][4];
float cameraXYZMatrix[4][3];
/** The used Color Keys */
QString colorKeys;
/** The camera maker. */
QString make;
/** The camera model. */
QString model;
/** The artist name who have picture owner. */
QString owner;
/** The demosaising filter pattern. */
QString filterPattern;
/** The DNG version. NOTE: it is only shown with DNG RAW files. */
QString DNGVersion;
/** Date & time when the picture has been taken. */
QDateTime dateTime;
/** The image dimensions in pixels. */
QSize imageSize;
/** The thumb dimensions in pixels. */
QSize thumbSize;
/** The full RAW image dimensions in pixels. */
QSize fullSize;
/** The output dimensions in pixels. */
QSize outputSize;
};
//! qDebug() stream operator. Writes container @a c to the debug output in a nicely formatted way.
QDebug operator<<(QDebug dbg, const DcrawInfoContainer& c);
} // namespace KDcrawIface
#endif /* DCRAW_INFO_CONTAINER_H */
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp
index a7d64ce10b..7b242d2b10 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>
+ * <a href="https://www.digikam.org">https://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 <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(QLatin1String("https://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 (no profile)</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/dcrawsettingswidget.h b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.h
index 1370cae405..9bca08791a 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.h
@@ -1,126 +1,126 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://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.
*
* ============================================================ */
#ifndef DCRAW_SETTINGS_WIDGET_H
#define DCRAW_SETTINGS_WIDGET_H
// Qt includes
#include <QtCore/QString>
// KDE includes
#include <kconfiggroup.h>
// Local includes
#include "rawdecodingsettings.h"
#include "rexpanderbox.h"
#include "rwidgetutils.h"
namespace KDcrawIface
{
class DcrawSettingsWidget : public RExpanderBox
{
Q_OBJECT
public:
enum AdvancedSettingsOptions
{
SIXTEENBITS = 0x00000001,
COLORSPACE = 0x00000002,
POSTPROCESSING = 0x00000004,
BLACKWHITEPOINTS = 0x00000008
};
enum SettingsTabs
{
DEMOSAICING = 0,
WHITEBALANCE,
CORRECTIONS,
COLORMANAGEMENT
};
public:
/**
* @param advSettings the default value is COLORSPACE
*/
explicit DcrawSettingsWidget(QWidget* const parent, int advSettings = COLORSPACE);
~DcrawSettingsWidget() override;
RFileSelector* inputProfileUrlEdit() const;
RFileSelector* outputProfileUrlEdit() const;
void setup(int advSettings);
void setEnabledBrightnessSettings(bool b);
bool brightnessSettingsIsEnabled() const;
void updateMinimumWidth();
void resetToDefault();
void setSettings(const RawDecodingSettings& settings);
RawDecodingSettings settings() const;
void readSettings(KConfigGroup& group) override;
void writeSettings(KConfigGroup& group) override;
Q_SIGNALS:
void signalSixteenBitsImageToggled(bool);
void signalSettingsChanged();
private Q_SLOTS:
void slotWhiteBalanceToggled(int);
void slotsixteenBitsImageToggled(bool);
void slotUnclipColorActivated(int);
void slotNoiseReductionChanged(int);
void slotCACorrectionToggled(bool);
void slotExposureCorrectionToggled(bool);
void slotAutoCAToggled(bool);
void slotInputColorSpaceChanged(int);
void slotOutputColorSpaceChanged(int);
void slotRAWQualityChanged(int);
void slotExpoCorrectionShiftChanged(double);
private:
class Private;
Private* const d;
};
} // NameSpace KDcrawIface
#endif /* DCRAW_SETTINGS_WIDGET_H */
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp
index 3ca5011b3f..dcaecc6fa7 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp
@@ -1,583 +1,583 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2006-12-09
* @brief a tread-safe libraw C++ program interface
*
* @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-2013 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 "kdcraw.h"
#include "kdcraw_p.h"
// Qt includes
#include <QFile>
#include <QFileInfo>
#include <QStringList>
// KDE includes
//#include <klibloader.h>
// LibRaw includes
#include <libraw_version.h>
#ifdef LIBRAW_HAS_CONFIG
#include <libraw_config.h>
#endif
// Local includes
#include "libkdcraw_debug.h"
#include "libkdcraw_version.h"
#include "rawfiles.h"
namespace KDcrawIface
{
KDcraw::KDcraw()
: d(new Private(this))
{
m_cancel = false;
}
KDcraw::~KDcraw()
{
cancel();
delete d;
}
QString KDcraw::version()
{
return QString(KDCRAW_VERSION_STRING);
}
void KDcraw::cancel()
{
m_cancel = true;
}
bool KDcraw::loadRawPreview(QImage& image, const QString& path)
{
// In first, try to extract the embedded JPEG preview. Very fast.
bool ret = loadEmbeddedPreview(image, path);
if (ret)
return true;
// In second, decode and half size of RAW picture. More slow.
return (loadHalfPreview(image, path));
}
bool KDcraw::loadEmbeddedPreview(QImage& image, const QString& path)
{
QByteArray imgData;
if ( loadEmbeddedPreview(imgData, path) )
{
qCDebug(LIBKDCRAW_LOG) << "Preview data size:" << imgData.size();
if (image.loadFromData( imgData ))
{
qCDebug(LIBKDCRAW_LOG) << "Using embedded RAW preview extraction";
return true;
}
}
qCDebug(LIBKDCRAW_LOG) << "Failed to load embedded RAW preview";
return false;
}
bool KDcraw::loadEmbeddedPreview(QByteArray& imgData, const QString& path)
{
QFileInfo fileInfo(path);
QString rawFilesExt(rawFiles());
QString ext = fileInfo.suffix().toUpper();
if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext))
return false;
LibRaw raw;
int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData());
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret);
raw.recycle();
return false;
}
return (Private::loadEmbeddedPreview(imgData, raw));
}
bool KDcraw::loadEmbeddedPreview(QByteArray& imgData, const QBuffer& buffer)
{
QString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
LibRaw raw;
QByteArray inData = buffer.data();
int ret = raw.open_buffer((void*) inData.data(), (size_t) inData.size());
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_buffer: " << libraw_strerror(ret);
raw.recycle();
return false;
}
return (Private::loadEmbeddedPreview(imgData, raw));
}
bool KDcraw::loadHalfPreview(QImage& image, const QString& path)
{
QFileInfo fileInfo(path);
QString rawFilesExt(rawFiles());
QString ext = fileInfo.suffix().toUpper();
if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext))
return false;
qCDebug(LIBKDCRAW_LOG) << "Try to use reduced RAW picture extraction";
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).
int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData());
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret);
raw.recycle();
return false;
}
if(!Private::loadHalfPreview(image, raw))
{
qCDebug(LIBKDCRAW_LOG) << "Failed to get half preview from LibRaw!";
return false;
}
qCDebug(LIBKDCRAW_LOG) << "Using reduced RAW picture extraction";
return true;
}
bool KDcraw::loadHalfPreview(QByteArray& imgData, const QString& path)
{
QFileInfo fileInfo(path);
QString rawFilesExt(rawFiles());
QString ext = fileInfo.suffix().toUpper();
if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext))
return false;
qCDebug(LIBKDCRAW_LOG) << "Try to use reduced RAW picture extraction";
LibRaw raw;
int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData());
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_process: " << libraw_strerror(ret);
raw.recycle();
return false;
}
QImage image;
if (!Private::loadHalfPreview(image, raw))
{
qCDebug(LIBKDCRAW_LOG) << "KDcraw: failed to get half preview: " << libraw_strerror(ret);
return false;
}
QBuffer buffer(&imgData);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "JPEG");
return true;
}
bool KDcraw::loadHalfPreview(QByteArray& imgData, const QBuffer& inBuffer)
{
QString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
LibRaw raw;
QByteArray inData = inBuffer.data();
int ret = raw.open_buffer((void*) inData.data(), (size_t) inData.size());
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_make_mem_image: " << libraw_strerror(ret);
raw.recycle();
return false;
}
QImage image;
if (!Private::loadHalfPreview(image, raw))
{
qCDebug(LIBKDCRAW_LOG) << "KDcraw: failed to get half preview: " << libraw_strerror(ret);
return false;
}
QBuffer buffer(&imgData);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "JPG");
return true;
}
bool KDcraw::loadFullImage(QImage& image, const QString& path, const RawDecodingSettings& settings)
{
QFileInfo fileInfo(path);
QString rawFilesExt(rawFiles());
QString ext = fileInfo.suffix().toUpper();
if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext))
return false;
qCDebug(LIBKDCRAW_LOG) << "Try to load full RAW picture...";
RawDecodingSettings prm = settings;
prm.sixteenBitsImage = false;
QByteArray imgData;
int width, height, rgbmax;
KDcraw decoder;
bool ret = decoder.decodeRAWImage(path, prm, imgData, width, height, rgbmax);
if (!ret)
{
qCDebug(LIBKDCRAW_LOG) << "Failled to load full RAW picture";
return false;
}
uchar* sptr = (uchar*)imgData.data();
uchar tmp8[2];
// Set RGB color components.
for (int i = 0 ; i < width * height ; ++i)
{
// Swap Red and Blue
tmp8[0] = sptr[2];
tmp8[1] = sptr[0];
sptr[0] = tmp8[0];
sptr[2] = tmp8[1];
sptr += 3;
}
image = QImage(width, height, QImage::Format_ARGB32);
uint* dptr = reinterpret_cast<uint*>(image.bits());
sptr = (uchar*)imgData.data();
for (int i = 0 ; i < width * height ; ++i)
{
*dptr++ = qRgba(sptr[2], sptr[1], sptr[0], 0xFF);
sptr += 3;
}
qCDebug(LIBKDCRAW_LOG) << "Load full RAW picture done";
return true;
}
bool KDcraw::rawFileIdentify(DcrawInfoContainer& identify, const QString& path)
{
QFileInfo fileInfo(path);
QString rawFilesExt(rawFiles());
QString ext = fileInfo.suffix().toUpper();
identify.isDecodable = false;
if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext))
return false;
LibRaw raw;
int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData());
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret);
raw.recycle();
return false;
}
ret = raw.adjust_sizes_info_only();
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run adjust_sizes_info_only: " << libraw_strerror(ret);
raw.recycle();
return false;
}
Private::fillIndentifyInfo(&raw, identify);
raw.recycle();
return true;
}
// ----------------------------------------------------------------------------------
bool KDcraw::extractRAWData(const QString& filePath, QByteArray& rawData, DcrawInfoContainer& identify, unsigned int shotSelect)
{
QFileInfo fileInfo(filePath);
QString rawFilesExt(rawFiles());
QString ext = fileInfo.suffix().toUpper();
identify.isDecodable = false;
if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext))
return false;
if (m_cancel)
return false;
d->setProgress(0.1);
LibRaw raw;
// Set progress call back function.
raw.set_progress_handler(callbackForLibRaw, d);
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_cancel)
{
raw.recycle();
return false;
}
d->setProgress(0.3);
raw.imgdata.params.output_bps = 16;
raw.imgdata.params.shot_select = shotSelect;
ret = raw.unpack();
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run unpack: " << libraw_strerror(ret);
raw.recycle();
return false;
}
if (m_cancel)
{
raw.recycle();
return false;
}
d->setProgress(0.4);
ret = raw.raw2image();
if (ret != LIBRAW_SUCCESS)
{
qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run raw2image: " << libraw_strerror(ret);
raw.recycle();
return false;
}
if (m_cancel)
{
raw.recycle();
return false;
}
d->setProgress(0.6);
Private::fillIndentifyInfo(&raw, identify);
if (m_cancel)
{
raw.recycle();
return false;
}
d->setProgress(0.8);
rawData = QByteArray();
if (raw.imgdata.idata.filters == 0)
{
rawData.resize((int)(raw.imgdata.sizes.iwidth * raw.imgdata.sizes.iheight * raw.imgdata.idata.colors * sizeof(unsigned short)));
unsigned short* output = reinterpret_cast<unsigned short*>(rawData.data());
for (unsigned int row = 0; row < raw.imgdata.sizes.iheight; row++)
{
for (unsigned int col = 0; col < raw.imgdata.sizes.iwidth; col++)
{
for (int color = 0; color < raw.imgdata.idata.colors; color++)
{
*output = raw.imgdata.image[raw.imgdata.sizes.iwidth*row + col][color];
output++;
}
}
}
}
else
{
int w = raw.imgdata.sizes.iwidth;
int h = raw.imgdata.sizes.iheight;
rawData.resize(w * h * sizeof(unsigned short));
unsigned short* output = reinterpret_cast<unsigned short*>(rawData.data());
for (uint row = 0; row < raw.imgdata.sizes.iheight; row++)
{
for (uint col = 0; col < raw.imgdata.sizes.iwidth; col++)
{
*output = raw.imgdata.image[raw.imgdata.sizes.iwidth*row + col][raw.COLOR(row, col)];
output++;
}
}
}
raw.recycle();
d->setProgress(1.0);
return true;
}
bool KDcraw::decodeHalfRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings,
QByteArray& imageData, int& width, int& height, int& rgbmax)
{
m_rawDecodingSettings = rawDecodingSettings;
m_rawDecodingSettings.halfSizeColorImage = true;
return (d->loadFromLibraw(filePath, imageData, width, height, rgbmax));
}
bool KDcraw::decodeRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings,
QByteArray& imageData, int& width, int& height, int& rgbmax)
{
m_rawDecodingSettings = rawDecodingSettings;
return (d->loadFromLibraw(filePath, imageData, width, height, rgbmax));
}
bool KDcraw::checkToCancelWaitingData()
{
return m_cancel;
}
void KDcraw::setWaitingDataProgress(double)
{
}
const char* KDcraw::rawFiles()
{
return raw_file_extentions;
}
QStringList KDcraw::rawFilesList()
{
QString string = QString::fromLatin1(rawFiles());
return string.remove("*.").split(' ');
}
int KDcraw::rawFilesVersion()
{
return raw_file_extensions_version;
}
QStringList KDcraw::supportedCamera()
{
QStringList camera;
const char** const list = LibRaw::cameraList();
for (int i = 0; i < LibRaw::cameraCount(); i++)
camera.append(list[i]);
return camera;
}
QString KDcraw::librawVersion()
{
return QString(LIBRAW_VERSION_STR).remove("-Release");
}
int KDcraw::librawUseGomp()
{
#ifdef LIBRAW_HAS_CONFIG
# ifdef LIBRAW_USE_OPENMP
return true;
# else
return false;
# endif
#else
return -1;
#endif
}
int KDcraw::librawUseRawSpeed()
{
#ifdef LIBRAW_HAS_CONFIG
# ifdef LIBRAW_USE_RAWSPEED
return true;
# else
return false;
# endif
#else
return -1;
#endif
}
int KDcraw::librawUseGPL2DemosaicPack()
{
#ifdef LIBRAW_HAS_CONFIG
# ifdef LIBRAW_USE_DEMOSAIC_PACK_GPL2
return true;
# else
return false;
# endif
#else
return -1;
#endif
}
int KDcraw::librawUseGPL3DemosaicPack()
{
#ifdef LIBRAW_HAS_CONFIG
# ifdef LIBRAW_USE_DEMOSAIC_PACK_GPL3
return true;
# else
return false;
# endif
#else
return -1;
#endif
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.h b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.h
index 61f7471f5b..549ad9158a 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.h
@@ -1,267 +1,267 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2006-12-09
* @brief a tread-safe libraw C++ program interface
*
* @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-2013 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.
*
* ============================================================ */
#ifndef KDCRAW_H
#define KDCRAW_H
// C++ includes
#include <cmath>
// Qt includes
#include <QtCore/QBuffer>
#include <QtCore/QString>
#include <QtCore/QObject>
#include <QtGui/QImage>
// Local includes
#include "rawdecodingsettings.h"
#include "dcrawinfocontainer.h"
/** @brief Main namespace of libKDcraw
*/
namespace KDcrawIface
{
class KDcraw : public QObject
{
Q_OBJECT
public:
/** Standard constructor.
*/
KDcraw();
/** Standard destructor.
*/
~KDcraw() override;
public:
/** Return a string version of libkdcraw release
*/
static QString version();
/** Get the preview of RAW picture as a QImage.
It tries loadEmbeddedPreview() first and if it fails, calls loadHalfPreview().
*/
static bool loadRawPreview(QImage& image, const QString& path);
/** Get the preview of RAW picture as a QByteArray holding JPEG data.
It tries loadEmbeddedPreview() first and if it fails, calls loadHalfPreview().
*/
static bool loadRawPreview(QByteArray& imgData, const QString& path);
/** Get the preview of RAW picture passed in QBuffer as a QByteArray holding JPEG data.
It tries loadEmbeddedPreview() first and if it fails, calls loadHalfPreview().
*/
static bool loadRawPreview(QByteArray& imgData, const QBuffer& inBuffer);
/** Get the embedded JPEG preview image from RAW picture as a QByteArray which will include Exif Data.
This is fast and non cancelable. This method does not require a class instance to run.
*/
static bool loadEmbeddedPreview(QByteArray& imgData, const QString& path);
/** Get the embedded JPEG preview image from RAW picture as a QImage. This is fast and non cancelable
This method does not require a class instance to run.
*/
static bool loadEmbeddedPreview(QImage& image, const QString& path);
/** Get the embedded JPEG preview image from RAW image passed in QBuffer as a QByteArray which will include Exif Data.
This is fast and non cancelable. This method does not require a class instance to run.
*/
static bool loadEmbeddedPreview(QByteArray& imgData, const QBuffer& inBuffer);
/** Get the half decoded RAW picture. This is slower than loadEmbeddedPreview() method
and non cancelable. This method does not require a class instance to run.
*/
static bool loadHalfPreview(QImage& image, const QString& path);
/** Get the half decoded RAW picture as JPEG data in QByteArray. This is slower than loadEmbeddedPreview()
method and non cancelable. This method does not require a class instance to run.
*/
static bool loadHalfPreview(QByteArray& imgData, const QString& path);
/** Get the half decoded RAW picture passed in QBuffer as JPEG data in QByteArray. This is slower than loadEmbeddedPreview()
method and non cancelable. This method does not require a class instance to run.
*/
static bool loadHalfPreview(QByteArray& imgData, const QBuffer& inBuffer);
/** Get the full decoded RAW picture. This is a more slower than loadHalfPreview() method
and non cancelable. This method does not require a class instance to run.
*/
static bool loadFullImage(QImage& image, const QString& path, const RawDecodingSettings& settings = RawDecodingSettings());
/** Get the camera settings witch have taken RAW file. Look into dcrawinfocontainer.h
for more details. This is a fast and non cancelable method witch do not require
a class instance to run.
*/
static bool rawFileIdentify(DcrawInfoContainer& identify, const QString& path);
/** Return the string of all RAW file type mime supported.
*/
static const char* rawFiles();
/** Return the list of all RAW file type mime supported,
as a QStringList, without wildcard and suffix dot.
*/
static QStringList rawFilesList();
/** Returns a version number for the list of supported RAW file types.
This version is incremented if the list of supported formats has changed
between library releases.
*/
static int rawFilesVersion();
/** Provide a list of supported RAW Camera name.
*/
static QStringList supportedCamera();
/** Return LibRaw version string.
*/
static QString librawVersion();
/** Return true or false if LibRaw use parallel demosaicing or not (libgomp support).
* Return -1 if undefined.
*/
static int librawUseGomp();
/** Return true or false if LibRaw use RawSpeed codec or not.
* Return -1 if undefined.
*/
static int librawUseRawSpeed();
/** Return true or false if LibRaw use Demosaic Pack GPL2 or not.
* Return -1 if undefined.
*/
static int librawUseGPL2DemosaicPack();
/** Return true or false if LibRaw use Demosaic Pack GPL3 or not.
* Return -1 if undefined.
*/
static int librawUseGPL3DemosaicPack();
public:
/** Extract Raw image data undemosaiced and without post processing from 'filePath' picture file.
This is a cancelable method which require a class instance to run because RAW pictures loading
can take a while.
This method return:
- A byte array container 'rawData' with raw data.
- All info about Raw image into 'identify' container.
- 'false' is returned if loadding failed, else 'true'.
*/
bool extractRAWData(const QString& filePath, QByteArray& rawData, DcrawInfoContainer& identify, unsigned int shotSelect=0);
/** Extract a small size of decode RAW data from 'filePath' picture file using
'rawDecodingSettings' settings. This is a cancelable method which require
a class instance to run because RAW pictures decoding can take a while.
This method return:
- A byte array container 'imageData' with picture data. Pixels order is RGB.
Color depth can be 8 or 16. In 8 bits you can access to color component
using (uchar*), in 16 bits using (ushort*).
- Size size of image in number of pixels ('width' and 'height').
- The max average of RGB components from decoded picture.
- 'false' is returned if decoding failed, else 'true'.
*/
bool decodeHalfRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings,
QByteArray& imageData, int& width, int& height, int& rgbmax);
/** Extract a full size of RAW data from 'filePath' picture file using
'rawDecodingSettings' settings. This is a cancelable method which require
a class instance to run because RAW pictures decoding can take a while.
This method return:
- A byte array container 'imageData' with picture data. Pixels order is RGB.
Color depth can be 8 or 16. In 8 bits you can access to color component
using (uchar*), in 16 bits using (ushort*).
- Size size of image in number of pixels ('width' and 'height').
- The max average of RGB components from decoded picture.
- 'false' is returned if decoding failed, else 'true'.
*/
bool decodeRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings,
QByteArray& imageData, int& width, int& height, int& rgbmax);
/** To cancel 'decodeHalfRAWImage' and 'decodeRAWImage' methods running
in a separate thread.
*/
void cancel();
protected:
/** Used internally to cancel RAW decoding operation. Normally, you don't need to use it
directly, excepted if you derivated this class. Usual way is to use cancel() method
*/
bool m_cancel;
/** The settings container used to perform RAW pictures decoding. See 'rawdecodingsetting.h'
for details.
*/
RawDecodingSettings m_rawDecodingSettings;
protected:
/** Re-implement this method to control the cancelisation of loop witch wait data
from RAW decoding process with your propers envirronement.
By default, this method check if m_cancel is true.
*/
virtual bool checkToCancelWaitingData();
/** Re-implement this method to control the pseudo progress value during RAW decoding (when dcraw run with an
internal loop without feedback) with your proper environment. By default, this method does nothing.
Progress value average for this stage is 0%-n%, with 'n' == 40% max (see setWaitingDataProgress() method).
*/
virtual void setWaitingDataProgress(double value);
public:
// Declared public to be called externally by callbackForLibRaw() static method.
class Private;
private:
Private* const d;
friend class Private;
};
} // namespace KDcrawIface
#endif /* KDCRAW_H */
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp
index 29553fce35..6be94077f0 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.cpp
@@ -1,698 +1,698 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://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)
{
identify.dateTime.setSecsSinceEpoch(raw->imgdata.other.timestamp);
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;
}
#if !LIBRAW_COMPILE_CHECK_VERSION_NOTLESS(0, 19)
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;
}
#endif
default: // No Noise Reduction
{
raw.imgdata.params.threshold = 0;
raw.imgdata.params.fbdd_noiserd = 0;
#if !LIBRAW_COMPILE_CHECK_VERSION_NOTLESS(0, 19)
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;
#endif
break;
}
}
#if !LIBRAW_COMPILE_CHECK_VERSION_NOTLESS(0, 19)
// 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];
#endif
// 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;
#if !LIBRAW_COMPILE_CHECK_VERSION_NOTLESS(0, 19)
raw.imgdata.params.eeci_refine = m_parent->m_rawDecodingSettings.eeciRefine;
raw.imgdata.params.es_med_passes = m_parent->m_rawDecodingSettings.esMedPasses;
#endif
//-------------------------------------------------------------------------------------------
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/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.h b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.h
index 71b36b3e30..f233a18372 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw_p.h
@@ -1,109 +1,109 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://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.
*
* ============================================================ */
#ifndef KDCRAWPRIVATE_H
#define KDCRAWPRIVATE_H
// Qt includes
#include <QByteArray>
// Pragma directives to reduce warnings from LibRaw header files.
#if !defined(__APPLE__) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#if defined(__APPLE__) && defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
// LibRaw includes
#include <libraw.h>
// Restore warnings
#if !defined(__APPLE__) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#if defined(__APPLE__) && defined(__clang__)
#pragma clang diagnostic pop
#endif
// Local includes
#include "dcrawinfocontainer.h"
#include "kdcraw.h"
namespace KDcrawIface
{
extern "C"
{
int callbackForLibRaw(void* data, enum LibRaw_progress p, int iteration, int expected);
}
class Q_DECL_HIDDEN KDcraw::Private
{
public:
Private(KDcraw* const p);
~Private();
public:
int progressCallback(enum LibRaw_progress p, int iteration, int expected);
void setProgress(double value);
double progressValue() const;
bool loadFromLibraw(const QString& filePath, QByteArray& imageData,
int& width, int& height, int& rgbmax);
public:
static void createPPMHeader(QByteArray& imgData, libraw_processed_image_t* const img);
static void fillIndentifyInfo(LibRaw* const raw, DcrawInfoContainer& identify);
static bool loadEmbeddedPreview(QByteArray&, LibRaw&);
static bool loadHalfPreview(QImage&, LibRaw&);
private:
double m_progress;
KDcraw* m_parent;
friend class KDcraw;
};
} // namespace KDcrawIface
#endif /* KDCRAWPRIVATE_H */
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.cpp
index 11f2d7033d..c8e88b2556 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.cpp
@@ -1,51 +1,51 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2014-15-11
* @brief QRunnable job extended with QObject features
*
* @author Copyright (C) 2011-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) 2014 by Veaceslav Munteanu
* <a href="mailto:veaceslav dot munteanu90 at gmail dot com">veaceslav dot munteanu90 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 "ractionjob.h"
namespace KDcrawIface
{
RActionJob::RActionJob()
: QObject(),
QRunnable(),
m_cancel(false)
{
setAutoDelete(false);
}
RActionJob::~RActionJob()
{
cancel();
}
void RActionJob::cancel()
{
m_cancel = true;
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.h b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.h
index 7dd60915c5..12a42e9629 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionjob.h
@@ -1,93 +1,93 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2014-15-11
* @brief QRunnable job extended with QObject features
*
* @author Copyright (C) 2011-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) 2014 by Veaceslav Munteanu
* <a href="mailto:veaceslav dot munteanu90 at gmail dot com">veaceslav dot munteanu90 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.
*
* ============================================================ */
#ifndef RACTIONJOB_H
#define RACTIONJOB_H
// Qt includes
#include <QObject>
#include <QRunnable>
// Local includes
namespace KDcrawIface
{
class RActionJob : public QObject,
public QRunnable
{
Q_OBJECT
public:
/** Constructor which delegate deletion of QRunnable instance to RActionThreadBase, not QThreadPool.
*/
RActionJob();
/** Re-implement destructor in you implementation. Don't forget to cancel job.
*/
~RActionJob() override;
Q_SIGNALS:
/** Use this signal in your implementation to inform RActionThreadBase manager that job is started
*/
void signalStarted();
/** Use this signal in your implementation to inform RActionThreadBase manager the job progress
*/
void signalProgress(int);
/** Use this signal in your implementation to inform RActionThreadBase manager the job is done.
*/
void signalDone();
public Q_SLOTS:
/** Call this method to cancel job.
*/
void cancel();
protected:
/** You can use this boolean in your implementation to know if job must be canceled.
*/
bool m_cancel;
};
/** Define a map of job/priority to process by RActionThreadBase manager.
* Priority value can be used to control the run queue's order of execution.
* Zero priority want mean to process job with higher priority.
*/
typedef QMap<RActionJob*, int> RJobCollection;
} // namespace KDcrawIface
#endif // RACTIONJOB_H
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.cpp
index 4fad77e4bf..db8d610894 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.cpp
@@ -1,202 +1,202 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2011-12-28
* @brief re-implementation of action thread using threadweaver
*
* @author Copyright (C) 2011-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) 2014 by Veaceslav Munteanu
* <a href="mailto:veaceslav dot munteanu90 at gmail dot com">veaceslav dot munteanu90 at gmail dot com</a>
* @author Copyright (C) 2011-2012 by A Janardhan Reddy
* <a href="annapareddyjanardhanreddy at gmail dot com">annapareddyjanardhanreddy 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 "ractionthreadbase.h"
// Qt includes
#include <QMutexLocker>
#include <QObject>
#include <QWaitCondition>
#include <QMutex>
#include <QList>
#include <QThreadPool>
// Local includes
#include "libkdcraw_debug.h"
namespace KDcrawIface
{
class Q_DECL_HIDDEN RActionThreadBase::Private
{
public:
Private()
{
running = false;
pool = QThreadPool::globalInstance();
}
volatile bool running;
QWaitCondition condVarJobs;
QMutex mutex;
RJobCollection todo;
RJobCollection pending;
RJobCollection processed;
QThreadPool* pool;
};
RActionThreadBase::RActionThreadBase(QObject* const parent)
: QThread(parent),
d(new Private)
{
defaultMaximumNumberOfThreads();
}
RActionThreadBase::~RActionThreadBase()
{
// cancel the thread
cancel();
// wait for the thread to finish
wait();
// Cleanup all jobs from memory
Q_FOREACH (RActionJob* const job, d->todo.keys())
delete(job);
Q_FOREACH (RActionJob* const job, d->pending.keys())
delete(job);
Q_FOREACH (RActionJob* const job, d->processed.keys())
delete(job);
delete d;
}
void RActionThreadBase::setMaximumNumberOfThreads(int n)
{
d->pool->setMaxThreadCount(n);
qCDebug(LIBKDCRAW_LOG) << "Using " << n << " CPU core to run threads";
}
int RActionThreadBase::maximumNumberOfThreads() const
{
return d->pool->maxThreadCount();
}
void RActionThreadBase::defaultMaximumNumberOfThreads()
{
const int maximumNumberOfThreads = qMax(QThreadPool::globalInstance()->maxThreadCount(), 1);
setMaximumNumberOfThreads(maximumNumberOfThreads);
}
void RActionThreadBase::slotJobFinished()
{
RActionJob* const job = dynamic_cast<RActionJob*>(sender());
if (!job) return;
qCDebug(LIBKDCRAW_LOG) << "One job is done";
QMutexLocker lock(&d->mutex);
d->processed.insert(job, 0);
d->pending.remove(job);
if (isEmpty())
{
d->running = false;
}
d->condVarJobs.wakeAll();
}
void RActionThreadBase::cancel()
{
qCDebug(LIBKDCRAW_LOG) << "Cancel Main Thread";
QMutexLocker lock(&d->mutex);
d->todo.clear();
Q_FOREACH (RActionJob* const job, d->pending.keys())
{
job->cancel();
d->processed.insert(job, 0);
}
d->pending.clear();
d->condVarJobs.wakeAll();
d->running = false;
}
bool RActionThreadBase::isEmpty() const
{
return d->pending.isEmpty();
}
void RActionThreadBase::appendJobs(const RJobCollection& jobs)
{
QMutexLocker lock(&d->mutex);
for (RJobCollection::const_iterator it = jobs.begin() ; it != jobs.end(); ++it)
{
d->todo.insert(it.key(), it.value());
}
d->condVarJobs.wakeAll();
}
void RActionThreadBase::run()
{
d->running = true;
while (d->running)
{
QMutexLocker lock(&d->mutex);
if (!d->todo.isEmpty())
{
qCDebug(LIBKDCRAW_LOG) << "Action Thread run " << d->todo.count() << " new jobs";
for (RJobCollection::iterator it = d->todo.begin() ; it != d->todo.end(); ++it)
{
RActionJob* const job = it.key();
int priority = it.value();
connect(job, SIGNAL(signalDone()),
this, SLOT(slotJobFinished()));
d->pool->start(job, priority);
d->pending.insert(job, priority);
}
d->todo.clear();
}
else
{
d->condVarJobs.wait(&d->mutex);
}
}
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.h b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.h
index 8f1f653615..ed96335359 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/ractionthreadbase.h
@@ -1,98 +1,98 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2011-12-28
* @brief re-implementation of action thread using threadweaver
*
* @author Copyright (C) 2011-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) 2014 by Veaceslav Munteanu
* <a href="mailto:veaceslav dot munteanu90 at gmail dot com">veaceslav dot munteanu90 at gmail dot com</a>
* @author Copyright (C) 2011-2012 by A Janardhan Reddy
* <a href="annapareddyjanardhanreddy at gmail dot com">annapareddyjanardhanreddy 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.
*
* ============================================================ */
#ifndef RACTION_THREAD_BASE_H
#define RACTION_THREAD_BASE_H
// Qt includes
#include <QtCore/QThread>
// Local includes
#include "ractionjob.h"
namespace KDcrawIface
{
class RActionThreadBase : public QThread
{
Q_OBJECT
public:
RActionThreadBase(QObject* const parent=0);
~RActionThreadBase() override;
/** Adjust maximum number of threads used to parallelize collection of job processing.
*/
void setMaximumNumberOfThreads(int n);
/** Return the maximum number of threads used to parallelize collection of job processing.
*/
int maximumNumberOfThreads() const;
/** Reset maximum number of threads used to parallelize collection of job processing to max core detected on computer.
* This method is called in contructor.
*/
void defaultMaximumNumberOfThreads();
/** Cancel processing of current jobs under progress.
*/
void cancel();
protected:
/** Main thread loop used to process jobs in todo list.
*/
void run() override;
/** Append a collection of jobs to process into QThreadPool.
* Jobs are add to pending lists and will be deleted by RActionThreadBase, not QThreadPool.
*/
void appendJobs(const RJobCollection& jobs);
/** Return true if list of pending jobs to process is empty.
*/
bool isEmpty() const;
protected Q_SLOTS:
void slotJobFinished();
private:
class Private;
Private* const d;
};
} // namespace KDcrawIface
#endif // RACTION_THREAD_BASE_H
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.cpp
index 0736f12301..5a265d7908 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.cpp
@@ -1,396 +1,396 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2006-12-09
* @brief Raw decoding settings
*
* @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-2013 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.
*
* ============================================================ */
#define OPTIONFIXCOLORSHIGHLIGHTSENTRY "FixColorsHighlights"
#define OPTIONDECODESIXTEENBITENTRY "SixteenBitsImage"
#define OPTIONWHITEBALANCEENTRY "White Balance"
#define OPTIONCUSTOMWHITEBALANCEENTRY "Custom White Balance"
#define OPTIONCUSTOMWBGREENENTRY "Custom White Balance Green"
#define OPTIONFOURCOLORRGBENTRY "Four Color RGB"
#define OPTIONUNCLIPCOLORSENTRY "Unclip Color"
// Wrong spelling, but do not fix it since it is a configuration key
// krazy:cond=spelling
#define OPTIONDONTSTRETCHPIXELSENTRY "Dont Stretch Pixels"
// krazy:endcond=spelling
#define OPTIONMEDIANFILTERPASSESENTRY "Median Filter Passes"
#define OPTIONNOISEREDUCTIONTYPEENTRY "Noise Reduction Type"
#define OPTIONNOISEREDUCTIONTHRESHOLDENTRY "Noise Reduction Threshold"
#define OPTIONUSECACORRECTIONENTRY "EnableCACorrection"
#define OPTIONCAREDMULTIPLIERENTRY "caRedMultiplier"
#define OPTIONCABLUEMULTIPLIERENTRY "caBlueMultiplier"
#define OPTIONAUTOBRIGHTNESSENTRY "AutoBrightness"
#define OPTIONDECODINGQUALITYENTRY "Decoding Quality"
#define OPTIONINPUTCOLORSPACEENTRY "Input Color Space"
#define OPTIONOUTPUTCOLORSPACEENTRY "Output Color Space"
#define OPTIONINPUTCOLORPROFILEENTRY "Input Color Profile"
#define OPTIONOUTPUTCOLORPROFILEENTRY "Output Color Profile"
#define OPTIONBRIGHTNESSMULTIPLIERENTRY "Brightness Multiplier"
#define OPTIONUSEBLACKPOINTENTRY "Use Black Point"
#define OPTIONBLACKPOINTENTRY "Black Point"
#define OPTIONUSEWHITEPOINTENTRY "Use White Point"
#define OPTIONWHITEPOINTENTRY "White Point"
//-- Extended demosaicing settings ----------------------------------------------------------
#define OPTIONDCBITERATIONSENTRY "Dcb Iterations"
#define OPTIONDCBENHANCEFLENTRY "Dcb Enhance Filter"
#define OPTIONEECIREFINEENTRY "Eeci Refine"
#define OPTIONESMEDPASSESENTRY "Es Median Filter Passes"
#define OPTIONNRCHROMINANCETHRESHOLDENTRY "Noise Reduction Chrominance Threshold"
#define OPTIONEXPOCORRECTIONENTRY "Expo Correction"
#define OPTIONEXPOCORRECTIONSHIFTENTRY "Expo Correction Shift"
#define OPTIONEXPOCORRECTIONHIGHLIGHTENTRY "Expo Correction Highlight"
#include "rawdecodingsettings.h"
namespace KDcrawIface
{
RawDecodingSettings::RawDecodingSettings()
{
fixColorsHighlights = false;
autoBrightness = true;
sixteenBitsImage = false;
brightness = 1.0;
RAWQuality = BILINEAR;
inputColorSpace = NOINPUTCS;
outputColorSpace = SRGB;
RGBInterpolate4Colors = false;
DontStretchPixels = false;
unclipColors = 0;
whiteBalance = CAMERA;
customWhiteBalance = 6500;
customWhiteBalanceGreen = 1.0;
medianFilterPasses = 0;
halfSizeColorImage = false;
enableBlackPoint = false;
blackPoint = 0;
enableWhitePoint = false;
whitePoint = 0;
NRType = NONR;
NRThreshold = 0;
enableCACorrection = false;
caMultiplier[0] = 0.0;
caMultiplier[1] = 0.0;
inputProfile = QString();
outputProfile = QString();
deadPixelMap = QString();
whiteBalanceArea = QRect();
//-- Extended demosaicing settings ----------------------------------------------------------
dcbIterations = -1;
dcbEnhanceFl = false;
eeciRefine = false;
esMedPasses = 0;
NRChroThreshold = 0;
expoCorrection = false;
expoCorrectionShift = 1.0;
expoCorrectionHighlight = 0.0;
}
RawDecodingSettings::~RawDecodingSettings()
{
}
RawDecodingSettings& RawDecodingSettings::operator=(const RawDecodingSettings& o)
{
fixColorsHighlights = o.fixColorsHighlights;
autoBrightness = o.autoBrightness;
sixteenBitsImage = o.sixteenBitsImage;
brightness = o.brightness;
RAWQuality = o.RAWQuality;
inputColorSpace = o.inputColorSpace;
outputColorSpace = o.outputColorSpace;
RGBInterpolate4Colors = o.RGBInterpolate4Colors;
DontStretchPixels = o.DontStretchPixels;
unclipColors = o.unclipColors;
whiteBalance = o.whiteBalance;
customWhiteBalance = o.customWhiteBalance;
customWhiteBalanceGreen = o.customWhiteBalanceGreen;
halfSizeColorImage = o.halfSizeColorImage;
enableBlackPoint = o.enableBlackPoint;
blackPoint = o.blackPoint;
enableWhitePoint = o.enableWhitePoint;
whitePoint = o.whitePoint;
NRType = o.NRType;
NRThreshold = o.NRThreshold;
enableCACorrection = o.enableCACorrection;
caMultiplier[0] = o.caMultiplier[0];
caMultiplier[1] = o.caMultiplier[1];
medianFilterPasses = o.medianFilterPasses;
inputProfile = o.inputProfile;
outputProfile = o.outputProfile;
deadPixelMap = o.deadPixelMap;
whiteBalanceArea = o.whiteBalanceArea;
//-- Extended demosaicing settings ----------------------------------------------------------
dcbIterations = o.dcbIterations;
dcbEnhanceFl = o.dcbEnhanceFl;
eeciRefine = o.eeciRefine;
esMedPasses = o.esMedPasses;
NRChroThreshold = o.NRChroThreshold;
expoCorrection = o.expoCorrection;
expoCorrectionShift = o.expoCorrectionShift;
expoCorrectionHighlight = o.expoCorrectionHighlight;
return *this;
}
bool RawDecodingSettings::operator==(const RawDecodingSettings& o) const
{
return fixColorsHighlights == o.fixColorsHighlights
&& autoBrightness == o.autoBrightness
&& sixteenBitsImage == o.sixteenBitsImage
&& brightness == o.brightness
&& RAWQuality == o.RAWQuality
&& inputColorSpace == o.inputColorSpace
&& outputColorSpace == o.outputColorSpace
&& RGBInterpolate4Colors == o.RGBInterpolate4Colors
&& DontStretchPixels == o.DontStretchPixels
&& unclipColors == o.unclipColors
&& whiteBalance == o.whiteBalance
&& customWhiteBalance == o.customWhiteBalance
&& customWhiteBalanceGreen == o.customWhiteBalanceGreen
&& halfSizeColorImage == o.halfSizeColorImage
&& enableBlackPoint == o.enableBlackPoint
&& blackPoint == o.blackPoint
&& enableWhitePoint == o.enableWhitePoint
&& whitePoint == o.whitePoint
&& NRType == o.NRType
&& NRThreshold == o.NRThreshold
&& enableCACorrection == o.enableCACorrection
&& caMultiplier[0] == o.caMultiplier[0]
&& caMultiplier[1] == o.caMultiplier[1]
&& medianFilterPasses == o.medianFilterPasses
&& inputProfile == o.inputProfile
&& outputProfile == o.outputProfile
&& deadPixelMap == o.deadPixelMap
&& whiteBalanceArea == o.whiteBalanceArea
//-- Extended demosaicing settings ----------------------------------------------------------
&& dcbIterations == o.dcbIterations
&& dcbEnhanceFl == o.dcbEnhanceFl
&& eeciRefine == o.eeciRefine
&& esMedPasses == o.esMedPasses
&& NRChroThreshold == o.NRChroThreshold
&& expoCorrection == o.expoCorrection
&& expoCorrectionShift == o.expoCorrectionShift
&& expoCorrectionHighlight == o.expoCorrectionHighlight
;
}
void RawDecodingSettings::optimizeTimeLoading()
{
fixColorsHighlights = false;
autoBrightness = true;
sixteenBitsImage = true;
brightness = 1.0;
RAWQuality = BILINEAR;
inputColorSpace = NOINPUTCS;
outputColorSpace = SRGB;
RGBInterpolate4Colors = false;
DontStretchPixels = false;
unclipColors = 0;
whiteBalance = CAMERA;
customWhiteBalance = 6500;
customWhiteBalanceGreen = 1.0;
halfSizeColorImage = true;
medianFilterPasses = 0;
enableBlackPoint = false;
blackPoint = 0;
enableWhitePoint = false;
whitePoint = 0;
NRType = NONR;
NRThreshold = 0;
enableCACorrection = false;
caMultiplier[0] = 0.0;
caMultiplier[1] = 0.0;
inputProfile = QString();
outputProfile = QString();
deadPixelMap = QString();
whiteBalanceArea = QRect();
//-- Extended demosaicing settings ----------------------------------------------------------
dcbIterations = -1;
dcbEnhanceFl = false;
eeciRefine = false;
esMedPasses = 0;
NRChroThreshold = 0;
expoCorrection = false;
expoCorrectionShift = 1.0;
expoCorrectionHighlight = 0.0;
}
void RawDecodingSettings::readSettings(KConfigGroup& group)
{
RawDecodingSettings defaultPrm;
fixColorsHighlights = group.readEntry(OPTIONFIXCOLORSHIGHLIGHTSENTRY, defaultPrm.fixColorsHighlights);
sixteenBitsImage = group.readEntry(OPTIONDECODESIXTEENBITENTRY, defaultPrm.sixteenBitsImage);
whiteBalance = (WhiteBalance)
group.readEntry(OPTIONWHITEBALANCEENTRY, (int)defaultPrm.whiteBalance);
customWhiteBalance = group.readEntry(OPTIONCUSTOMWHITEBALANCEENTRY, defaultPrm.customWhiteBalance);
customWhiteBalanceGreen = group.readEntry(OPTIONCUSTOMWBGREENENTRY, defaultPrm.customWhiteBalanceGreen);
RGBInterpolate4Colors = group.readEntry(OPTIONFOURCOLORRGBENTRY, defaultPrm.RGBInterpolate4Colors);
unclipColors = group.readEntry(OPTIONUNCLIPCOLORSENTRY, defaultPrm.unclipColors);
DontStretchPixels = group.readEntry(OPTIONDONTSTRETCHPIXELSENTRY, defaultPrm.DontStretchPixels);
NRType = (NoiseReduction)
group.readEntry(OPTIONNOISEREDUCTIONTYPEENTRY, (int)defaultPrm.NRType);
brightness = group.readEntry(OPTIONBRIGHTNESSMULTIPLIERENTRY, defaultPrm.brightness);
enableBlackPoint = group.readEntry(OPTIONUSEBLACKPOINTENTRY, defaultPrm.enableBlackPoint);
blackPoint = group.readEntry(OPTIONBLACKPOINTENTRY, defaultPrm.blackPoint);
enableWhitePoint = group.readEntry(OPTIONUSEWHITEPOINTENTRY, defaultPrm.enableWhitePoint);
whitePoint = group.readEntry(OPTIONWHITEPOINTENTRY, defaultPrm.whitePoint);
medianFilterPasses = group.readEntry(OPTIONMEDIANFILTERPASSESENTRY, defaultPrm.medianFilterPasses);
NRThreshold = group.readEntry(OPTIONNOISEREDUCTIONTHRESHOLDENTRY, defaultPrm.NRThreshold);
enableCACorrection = group.readEntry(OPTIONUSECACORRECTIONENTRY, defaultPrm.enableCACorrection);
caMultiplier[0] = group.readEntry(OPTIONCAREDMULTIPLIERENTRY, defaultPrm.caMultiplier[0]);
caMultiplier[1] = group.readEntry(OPTIONCABLUEMULTIPLIERENTRY, defaultPrm.caMultiplier[1]);
RAWQuality = (DecodingQuality)
group.readEntry(OPTIONDECODINGQUALITYENTRY, (int)defaultPrm.RAWQuality);
outputColorSpace = (OutputColorSpace)
group.readEntry(OPTIONOUTPUTCOLORSPACEENTRY, (int)defaultPrm.outputColorSpace);
autoBrightness = group.readEntry(OPTIONAUTOBRIGHTNESSENTRY, defaultPrm.autoBrightness);
//-- Extended demosaicing settings ----------------------------------------------------------
dcbIterations = group.readEntry(OPTIONDCBITERATIONSENTRY, defaultPrm.dcbIterations);
dcbEnhanceFl = group.readEntry(OPTIONDCBENHANCEFLENTRY, defaultPrm.dcbEnhanceFl);
eeciRefine = group.readEntry(OPTIONEECIREFINEENTRY, defaultPrm.eeciRefine);
esMedPasses = group.readEntry(OPTIONESMEDPASSESENTRY, defaultPrm.esMedPasses);
NRChroThreshold = group.readEntry(OPTIONNRCHROMINANCETHRESHOLDENTRY, defaultPrm.NRChroThreshold);
expoCorrection = group.readEntry(OPTIONEXPOCORRECTIONENTRY, defaultPrm.expoCorrection);
expoCorrectionShift = group.readEntry(OPTIONEXPOCORRECTIONSHIFTENTRY, defaultPrm.expoCorrectionShift);
expoCorrectionHighlight = group.readEntry(OPTIONEXPOCORRECTIONHIGHLIGHTENTRY, defaultPrm.expoCorrectionHighlight);
}
void RawDecodingSettings::writeSettings(KConfigGroup& group)
{
group.writeEntry(OPTIONFIXCOLORSHIGHLIGHTSENTRY, fixColorsHighlights);
group.writeEntry(OPTIONDECODESIXTEENBITENTRY, sixteenBitsImage);
group.writeEntry(OPTIONWHITEBALANCEENTRY, (int)whiteBalance);
group.writeEntry(OPTIONCUSTOMWHITEBALANCEENTRY, customWhiteBalance);
group.writeEntry(OPTIONCUSTOMWBGREENENTRY, customWhiteBalanceGreen);
group.writeEntry(OPTIONFOURCOLORRGBENTRY, RGBInterpolate4Colors);
group.writeEntry(OPTIONUNCLIPCOLORSENTRY, unclipColors);
group.writeEntry(OPTIONDONTSTRETCHPIXELSENTRY, DontStretchPixels);
group.writeEntry(OPTIONNOISEREDUCTIONTYPEENTRY, (int)NRType);
group.writeEntry(OPTIONBRIGHTNESSMULTIPLIERENTRY, brightness);
group.writeEntry(OPTIONUSEBLACKPOINTENTRY, enableBlackPoint);
group.writeEntry(OPTIONBLACKPOINTENTRY, blackPoint);
group.writeEntry(OPTIONUSEWHITEPOINTENTRY, enableWhitePoint);
group.writeEntry(OPTIONWHITEPOINTENTRY, whitePoint);
group.writeEntry(OPTIONMEDIANFILTERPASSESENTRY, medianFilterPasses);
group.writeEntry(OPTIONNOISEREDUCTIONTHRESHOLDENTRY, NRThreshold);
group.writeEntry(OPTIONUSECACORRECTIONENTRY, enableCACorrection);
group.writeEntry(OPTIONCAREDMULTIPLIERENTRY, caMultiplier[0]);
group.writeEntry(OPTIONCABLUEMULTIPLIERENTRY, caMultiplier[1]);
group.writeEntry(OPTIONDECODINGQUALITYENTRY, (int)RAWQuality);
group.writeEntry(OPTIONOUTPUTCOLORSPACEENTRY, (int)outputColorSpace);
group.writeEntry(OPTIONAUTOBRIGHTNESSENTRY, autoBrightness);
//-- Extended demosaicing settings ----------------------------------------------------------
group.writeEntry(OPTIONDCBITERATIONSENTRY, dcbIterations);
group.writeEntry(OPTIONDCBENHANCEFLENTRY, dcbEnhanceFl);
group.writeEntry(OPTIONEECIREFINEENTRY, eeciRefine);
group.writeEntry(OPTIONESMEDPASSESENTRY, esMedPasses);
group.writeEntry(OPTIONNRCHROMINANCETHRESHOLDENTRY, NRChroThreshold);
group.writeEntry(OPTIONEXPOCORRECTIONENTRY, expoCorrection);
group.writeEntry(OPTIONEXPOCORRECTIONSHIFTENTRY, expoCorrectionShift);
group.writeEntry(OPTIONEXPOCORRECTIONHIGHLIGHTENTRY, expoCorrectionHighlight);
}
QDebug operator<<(QDebug dbg, const RawDecodingSettings& s)
{
dbg.nospace() << endl;
dbg.nospace() << "-- RAW DECODING SETTINGS --------------------------------" << endl;
dbg.nospace() << "-- autoBrightness: " << s.autoBrightness << endl;
dbg.nospace() << "-- sixteenBitsImage: " << s.sixteenBitsImage << endl;
dbg.nospace() << "-- brightness: " << s.brightness << endl;
dbg.nospace() << "-- RAWQuality: " << s.RAWQuality << endl;
dbg.nospace() << "-- inputColorSpace: " << s.inputColorSpace << endl;
dbg.nospace() << "-- outputColorSpace: " << s.outputColorSpace << endl;
dbg.nospace() << "-- RGBInterpolate4Colors: " << s.RGBInterpolate4Colors << endl;
dbg.nospace() << "-- DontStretchPixels: " << s.DontStretchPixels << endl;
dbg.nospace() << "-- unclipColors: " << s.unclipColors << endl;
dbg.nospace() << "-- whiteBalance: " << s.whiteBalance << endl;
dbg.nospace() << "-- customWhiteBalance: " << s.customWhiteBalance << endl;
dbg.nospace() << "-- customWhiteBalanceGreen: " << s.customWhiteBalanceGreen << endl;
dbg.nospace() << "-- halfSizeColorImage: " << s.halfSizeColorImage << endl;
dbg.nospace() << "-- enableBlackPoint: " << s.enableBlackPoint << endl;
dbg.nospace() << "-- blackPoint: " << s.blackPoint << endl;
dbg.nospace() << "-- enableWhitePoint: " << s.enableWhitePoint << endl;
dbg.nospace() << "-- whitePoint: " << s.whitePoint << endl;
dbg.nospace() << "-- NoiseReductionType: " << s.NRType << endl;
dbg.nospace() << "-- NoiseReductionThreshold: " << s.NRThreshold << endl;
dbg.nospace() << "-- enableCACorrection: " << s.enableCACorrection << endl;
dbg.nospace() << "-- caMultiplier: " << s.caMultiplier[0]
<< ", " << s.caMultiplier[1] << endl;
dbg.nospace() << "-- medianFilterPasses: " << s.medianFilterPasses << endl;
dbg.nospace() << "-- inputProfile: " << s.inputProfile << endl;
dbg.nospace() << "-- outputProfile: " << s.outputProfile << endl;
dbg.nospace() << "-- deadPixelMap: " << s.deadPixelMap << endl;
dbg.nospace() << "-- whiteBalanceArea: " << s.whiteBalanceArea << endl;
//-- Extended demosaicing settings ----------------------------------------------------------
dbg.nospace() << "-- dcbIterations: " << s.dcbIterations << endl;
dbg.nospace() << "-- dcbEnhanceFl: " << s.dcbEnhanceFl << endl;
dbg.nospace() << "-- eeciRefine: " << s.eeciRefine << endl;
dbg.nospace() << "-- esMedPasses: " << s.esMedPasses << endl;
dbg.nospace() << "-- NRChrominanceThreshold: " << s.NRChroThreshold << endl;
dbg.nospace() << "-- expoCorrection: " << s.expoCorrection << endl;
dbg.nospace() << "-- expoCorrectionShift: " << s.expoCorrectionShift << endl;
dbg.nospace() << "-- expoCorrectionHighlight: " << s.expoCorrectionHighlight << endl;
dbg.nospace() << "---------------------------------------------------------" << endl;
return dbg.space();
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.h
index 5916fb2260..5927f4dd6b 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rawdecodingsettings.h
@@ -1,376 +1,376 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2006-12-09
* @brief Raw decoding settings
*
* @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-2013 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.
*
* ============================================================ */
#ifndef RAW_DECODING_SETTINGS_H
#define RAW_DECODING_SETTINGS_H
// Qt includes
#include <QtCore/QRect>
#include <QtCore/QString>
#include <QtCore/QDebug>
// KDE includes
#include <kconfiggroup.h>
// Local includes
namespace KDcrawIface
{
class RawDecodingSettings
{
public:
/** RAW decoding Interpolation methods
*
* Bilinear: use high-speed but low-quality bilinear
* interpolation (default - for slow computer). In this method,
* the red value of a non-red pixel is computed as the average of
* the adjacent red pixels, and similar for blue and green.
* VNG: 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.
* PPG: 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.
* AHD: use Adaptive Homogeneity-Directed interpolation.
* This method selects the direction of interpolation so as to
* maximize a homogeneity metric, thus typically minimizing color artifacts.
* DCB: DCB interpolation (see http://www.linuxphoto.org/html/dcb.html for details)
*
* NOTE: from GPL2/GPL3 demosaic packs - will not work with libraw>=0.19
*
- * PL_AHD: modified AHD interpolation (see http://sites.google.com/site/demosaicalgorithms/modified-dcraw
+ * PL_AHD: modified AHD interpolation (see https://sites.google.com/site/demosaicalgorithms/modified-dcraw
* for details).
* AFD: demosaicing through 5 pass median filter from PerfectRaw project.
* VCD: VCD interpolation.
* VCD_AHD: mixed demosaicing between VCD and AHD.
* LMMSE: LMMSE interpolation from PerfectRaw.
* AMAZE: AMaZE interpolation and color aberration removal from RawTherapee project.
*
* NOTE: for libraw>=0.19 only
*
* DHT: DHT interpolation.
* AAHD: Enhanced Adaptative AHD interpolation.
*/
enum DecodingQuality
{
BILINEAR = 0,
VNG = 1,
PPG = 2,
AHD = 3,
DCB = 4,
PL_AHD = 5,
AFD = 6,
VCD = 7,
VCD_AHD = 8,
LMMSE = 9,
AMAZE = 10,
DHT = 11,
AAHD = 12
};
/** White balances alternatives
* NONE: no white balance used : reverts to standard daylight D65 WB.
* CAMERA: Use the camera embedded WB if available. Reverts to NONE if not.
* AUTO: Averages an auto WB on the entire image.
* CUSTOM: Let use set it's own temperature and green factor (later converted to RGBG factors).
* AERA: Let use an aera from image to average white balance (see whiteBalanceArea for details).
*/
enum WhiteBalance
{
NONE = 0,
CAMERA = 1,
AUTO = 2,
CUSTOM = 3,
AERA = 4
};
/** Noise Reduction method to apply before demosaicing
* NONR: No noise reduction.
* WAVELETSNR: wavelets correction to erase noise while preserving real detail. It's applied after interpolation.
* FBDDNR: Fake Before Demosaicing Denoising noise reduction. It's applied before interpolation.
* LINENR: CFA Line Denoise. It's applied after interpolation.
* IMPULSENR: Impulse Denoise. It's applied after interpolation.
*/
enum NoiseReduction
{
NONR = 0,
WAVELETSNR,
FBDDNR,
LINENR,
IMPULSENR
};
/** Input color profile used to decoded image
* NOINPUTCS: No input color profile.
* EMBEDDED: Use the camera profile embedded in RAW file if exist.
* CUSTOMINPUTCS: Use a custom input color space profile.
*/
enum InputColorSpace
{
NOINPUTCS = 0,
EMBEDDED,
CUSTOMINPUTCS
};
/** Output RGB color space used to decoded image
* RAWCOLOR: No output color profile (Linear RAW).
* SRGB: Use standard sRGB color space.
* ADOBERGB: Use standard Adobe RGB color space.
* WIDEGAMMUT: Use standard RGB Wide Gamut color space.
* PROPHOTO: Use standard RGB Pro Photo color space.
* CUSTOMOUTPUTCS: Use a custom workspace color profile.
*/
enum OutputColorSpace
{
RAWCOLOR = 0,
SRGB,
ADOBERGB,
WIDEGAMMUT,
PROPHOTO,
CUSTOMOUTPUTCS
};
/** Standard constructor with default settings
*/
RawDecodingSettings();
/** Equivalent to the copy constructor
*/
RawDecodingSettings& operator=(const RawDecodingSettings& prm);
/** Compare for equality
*/
bool operator==(const RawDecodingSettings& o) const;
/** Standard destructor
*/
virtual ~RawDecodingSettings();
/** Method to use a settings to optimize time loading, for example to compute image histogram
*/
void optimizeTimeLoading();
/** Methods to read/write settings from/to a config file
*/
void readSettings(KConfigGroup& group);
void writeSettings(KConfigGroup& group);
public:
/** If true, images with overblown channels are processed much more accurate,
* without 'pink clouds' (and blue highlights under tungsteen lamps).
*/
bool fixColorsHighlights;
/** If false, use a fixed white level, ignoring the image histogram.
*/
bool autoBrightness;
/** Turn on RAW file decoding in 16 bits per color per pixel instead 8 bits.
*/
bool sixteenBitsImage;
/** Half-size color image decoding (twice as fast as "enableRAWQuality").
* Turn on this option to reduce time loading to render histogram for example,
* no to render an image to screen.
*/
bool halfSizeColorImage;
/** White balance type to use. See WhiteBalance values for detail
*/
WhiteBalance whiteBalance;
/** The temperature and the green multiplier of the custom white balance
*/
int customWhiteBalance;
double customWhiteBalanceGreen;
/** Turn on RAW file decoding using RGB interpolation as four colors.
*/
bool RGBInterpolate4Colors;
/** 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.
*/
bool DontStretchPixels;
/** Unclip Highlight color level:
* 0 = Clip all highlights to solid white.
* 1 = Leave highlights unclipped in various shades of pink.
* 2 = Blend clipped and unclipped values together for a gradual
* fade to white.
* 3-9 = Reconstruct highlights. Low numbers favor whites; high numbers
* favor colors.
*/
int unclipColors;
/** RAW quality decoding factor value. See DecodingQuality values
* for details.
*/
DecodingQuality RAWQuality;
/** After interpolation, clean up color artifacts by repeatedly applying
* a 3x3 median filter to the R-G and B-G channels.
*/
int medianFilterPasses;
/** Noise reduction method to apply before demosaicing.
*/
NoiseReduction NRType;
/** Noise reduction threshold value. Null value disable NR. Range is between 100 and 1000.
* For IMPULSENR : set the amount of Luminance impulse denoise.
*/
int NRThreshold;
/** Turn on chromatic aberrations correction
* @deprecated does not work with libraw>=0.19
*/
bool enableCACorrection;
/** Magnification factor for Red and Blue layers
* - caMultiplier[0] = amount of correction on red-green axis.
* - caMultiplier[1] = amount of correction on blue-yellow axis.
* - Both values set to 0.0 = automatic CA correction.
* @deprecated does not work with libraw>=0.19
*/
double caMultiplier[2];
/** Brightness of output image.
*/
double brightness;
/** Turn on the black point setting to decode RAW image.
*/
bool enableBlackPoint;
/** Black Point value of output image.
*/
int blackPoint;
/** Turn on the white point setting to decode RAW image.
*/
bool enableWhitePoint;
/** White Point value of output image.
*/
int whitePoint;
/** The input color profile used to decoded RAW data. See OutputColorProfile
* values for details.
*/
InputColorSpace inputColorSpace;
/** Path to custom input ICC profile to define the camera's raw colorspace.
*/
QString inputProfile;
/** The output color profile used to decoded RAW data. See OutputColorProfile
* values for details.
*/
OutputColorSpace outputColorSpace;
/** Path to custom output ICC profile to define the color workspace.
*/
QString outputProfile;
/** Path to text file including dead pixel list.
*/
QString deadPixelMap;
/** Rectangle used to calculate the white balance by averaging the region of image.
*/
QRect whiteBalanceArea;
//-- Extended demosaicing settings ----------------------------------------------------------
/// For DCB interpolation.
/** Number of DCB median filtering correction passes.
* -1 : disable (default)
* 1-10 : DCB correction passes
*/
int dcbIterations;
/** Turn on the DCB interpolation with enhance interpolated colors.
*/
bool dcbEnhanceFl;
/// For VCD_AHD interpolation.
/** Turn on the EECI refine for VCD Demosaicing.
* @deprecated does not work with libraw>=0.19
*/
bool eeciRefine;
/** Use edge-sensitive median filtering for artifact supression after VCD demosaicing.
* 0 : disable (default)
* 1-10 : median filter passes.
* @deprecated does not work with libraw>=0.19
*/
int esMedPasses;
/** For IMPULSENR Noise reduction. Set the amount of Chrominance impulse denoise.
* Null value disable NR. Range is between 100 and 1000.
* @deprecated does not work with libraw>=0.19
*/
int NRChroThreshold;
/** Turn on the Exposure Correction before interpolation.
*/
bool expoCorrection;
/** Shift of Exposure Correction before interpolation in linear scale.
* Usable range is from 0.25 (darken image 1 stop : -2EV) to 8.0 (lighten ~1.5 photographic stops : +3EV).
*/
double expoCorrectionShift;
/** Amount of highlight preservation for exposure correction before interpolation in E.V.
* Usable range is from 0.0 (linear exposure shift, highlights may blow) to 1.0 (maximum highlights preservation)
* This settings can only take effect if expoCorrectionShift > 1.0.
*/
double expoCorrectionHighlight;
};
//! qDebug() stream operator. Writes settings @a s to the debug output in a nicely formatted way.
QDebug operator<<(QDebug dbg, const RawDecodingSettings& s);
} // namespace KDcrawIface
#endif /* RAW_DECODING_SETTINGS_H */
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rawfiles.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rawfiles.h
index 34783c43bf..c4b43e8e25 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rawfiles.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rawfiles.h
@@ -1,99 +1,99 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2005-11-06
* @brief list of RAW file extensions supported by libraw
*
* @author Copyright (C) 2005-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.
*
* ============================================================ */
#ifndef RAW_FILES_H
#define RAW_FILES_H
-// NOTE: extension list Version 1 and 2 are taken from http://www.cybercom.net/~dcoffin/dcraw/rawphoto.c
+// NOTE: extension list Version 1 and 2 are taken from https://www.dechifro.org/dcraw/rawphoto.c
// Ext Descriptions From
// www.file-extensions.org
// en.wikipedia.org/wiki/RAW_file_format
// filext.com
static const char raw_file_extentions[] =
// NOTE: VERSION 1
"*.bay " // Casio Digital Camera Raw File Format.
"*.bmq " // NuCore Raw Image File.
"*.cr2 " // Canon Digital Camera RAW Image Format version 2.0. These images are based on the TIFF image standard.
"*.crw " // Canon Digital Camera RAW Image Format version 1.0.
"*.cs1 " // Capture Shop Raw Image File.
"*.dc2 " // Kodak DC25 Digital Camera File.
"*.dcr " // Kodak Digital Camera Raw Image Format for these models: Kodak DSC Pro SLR/c, Kodak DSC Pro SLR/n, Kodak DSC Pro 14N, Kodak DSC PRO 14nx.
"*.dng " // Adobe Digital Negative: DNG is publicly available archival format for the raw files generated by digital cameras. By addressing the lack of an open standard for the raw files created by individual camera models, DNG helps ensure that photographers will be able to access their files in the future.
"*.erf " // Epson Digital Camera Raw Image Format.
"*.fff " // Imacon Digital Camera Raw Image Format.
"*.hdr " // Leaf Raw Image File.
"*.k25 " // Kodak DC25 Digital Camera Raw Image Format.
"*.kdc " // Kodak Digital Camera Raw Image Format.
"*.mdc " // Minolta RD175 Digital Camera Raw Image Format.
"*.mos " // Mamiya Digital Camera Raw Image Format.
"*.mrw " // Minolta Dimage Digital Camera Raw Image Format.
"*.nef " // Nikon Digital Camera Raw Image Format.
"*.orf " // Olympus Digital Camera Raw Image Format.
"*.pef " // Pentax Digital Camera Raw Image Format.
"*.pxn " // Logitech Digital Camera Raw Image Format.
"*.raf " // Fuji Digital Camera Raw Image Format.
"*.raw " // Panasonic Digital Camera Image Format.
"*.rdc " // Digital Foto Maker Raw Image File.
"*.sr2 " // Sony Digital Camera Raw Image Format.
"*.srf " // Sony Digital Camera Raw Image Format for DSC-F828 8 megapixel digital camera or Sony DSC-R1
"*.x3f " // Sigma Digital Camera Raw Image Format for devices based on Foveon X3 direct image sensor.
"*.arw " // Sony Digital Camera Raw Image Format for Alpha devices.
// NOTE: VERSION 2
"*.3fr " // Hasselblad Digital Camera Raw Image Format.
"*.cine " // Phantom Software Raw Image File.
"*.ia " // Sinar Raw Image File.
"*.kc2 " // Kodak DCS200 Digital Camera Raw Image Format.
"*.mef " // Mamiya Digital Camera Raw Image Format.
"*.nrw " // Nikon Digital Camera Raw Image Format.
"*.qtk " // Apple Quicktake 100/150 Digital Camera Raw Image Format.
"*.rw2 " // Panasonic LX3 Digital Camera Raw Image Format.
"*.sti " // Sinar Capture Shop Raw Image File.
// NOTE: VERSION 3
"*.rwl " // Leica Digital Camera Raw Image Format.
// NOTE: VERSION 4
"*.srw "; // Samnsung Raw Image Format.
/* TODO: check if these format are supported
"*.drf " // Kodak Digital Camera Raw Image Format.
"*.dsc " // Kodak Digital Camera Raw Image Format.
"*.ptx " // Pentax Digital Camera Raw Image Format.
"*.cap " // Phase One Digital Camera Raw Image Format.
"*.iiq " // Phase One Digital Camera Raw Image Format.
"*.rwz " // Rawzor Digital Camera Raw Image Format.
*/
// increment this number whenever you change the above string
static const int raw_file_extensions_version = 4;
#endif // RAW_FILES_H
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.cpp
index c7e1d34d03..f41d89848f 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.cpp
@@ -1,156 +1,156 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2008-08-16
* @brief a combo box widget re-implemented with a
* reset button to switch to a default item
*
* @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 "rcombobox.h"
// Qt includes
#include <QApplication>
#include <QStyle>
#include <QToolButton>
#include <QHBoxLayout>
#include <QIcon>
// KDE includes
#include <klocalizedstring.h>
#include <kis_icon_utils.h>
namespace KDcrawIface
{
class Q_DECL_HIDDEN RComboBox::Private
{
public:
Private()
{
defaultIndex = 0;
resetButton = 0;
combo = 0;
}
int defaultIndex;
QToolButton* resetButton;
QComboBox* combo;
};
RComboBox::RComboBox(QWidget* const parent)
: QWidget(parent), d(new Private)
{
QHBoxLayout* const hlay = new QHBoxLayout(this);
d->combo = new QComboBox(this);
d->resetButton = new QToolButton(this);
d->resetButton->setAutoRaise(true);
d->resetButton->setFocusPolicy(Qt::NoFocus);
d->resetButton->setIcon(KisIconUtils::loadIcon("document-revert").pixmap(16, 16));
d->resetButton->setToolTip(i18nc("@info:tooltip", "Reset to default value"));
hlay->addWidget(d->combo);
hlay->addWidget(d->resetButton);
hlay->setStretchFactor(d->combo, 10);
hlay->setMargin(0);
hlay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
// -------------------------------------------------------------
connect(d->resetButton, &QToolButton::clicked,
this, &RComboBox::slotReset);
connect(d->combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated),
this, &RComboBox::slotItemActivated);
connect(d->combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &RComboBox::slotCurrentIndexChanged);
}
RComboBox::~RComboBox()
{
delete d;
}
QComboBox* RComboBox::combo() const
{
return d->combo;
}
void RComboBox::addItem(const QString& t, int index)
{
d->combo->addItem(t, index);
}
void RComboBox::insertItem(int index, const QString& t)
{
d->combo->insertItem(index, t);
}
int RComboBox::currentIndex() const
{
return d->combo->currentIndex();
}
void RComboBox::setCurrentIndex(int v)
{
d->combo->setCurrentIndex(v);
}
int RComboBox::defaultIndex() const
{
return d->defaultIndex;
}
void RComboBox::setDefaultIndex(int v)
{
d->defaultIndex = v;
d->combo->setCurrentIndex(d->defaultIndex);
slotItemActivated(v);
}
void RComboBox::slotReset()
{
d->combo->setCurrentIndex(d->defaultIndex);
d->resetButton->setEnabled(false);
slotItemActivated(d->defaultIndex);
emit reset();
}
void RComboBox::slotItemActivated(int v)
{
d->resetButton->setEnabled(v != d->defaultIndex);
emit activated(v);
}
void RComboBox::slotCurrentIndexChanged(int v)
{
d->resetButton->setEnabled(v != d->defaultIndex);
emit currentIndexChanged(v);
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.h
index 0dd29d2e3c..8b6c40c4b1 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rcombobox.h
@@ -1,86 +1,86 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2008-08-16
* @brief a combo box widget re-implemented with a
* reset button to switch to a default item
*
* @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.
*
* ============================================================ */
#ifndef RCOMBOBOX_H
#define RCOMBOBOX_H
// Qt includes
#include <QWidget>
#include <QComboBox>
// Local includes
namespace KDcrawIface
{
class RComboBox : public QWidget
{
Q_OBJECT
public:
RComboBox(QWidget* const parent=0);
~RComboBox() override;
void setCurrentIndex(int d);
int currentIndex() const;
void setDefaultIndex(int d);
int defaultIndex() const;
QComboBox* combo() const;
void addItem(const QString& t, int index = -1);
void insertItem(int index, const QString& t);
Q_SIGNALS:
void reset();
void activated(int);
void currentIndexChanged(int);
public Q_SLOTS:
void slotReset();
private Q_SLOTS:
void slotItemActivated(int);
void slotCurrentIndexChanged(int);
private:
class Private;
Private* const d;
};
} // namespace KDcrawIface
#endif /* RCOMBOBOX_H */
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.cpp
index 2d188a152d..2ed1176281 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.cpp
@@ -1,826 +1,826 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2008-03-14
* @brief A widget to host settings as expander box
*
* @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>
* @author Copyright (C) 2008-2013 by Marcel Wiesweg
* <a href="mailto:marcel dot wiesweg at gmx dot de">marcel dot wiesweg at gmx dot de</a>
* @author Copyright (C) 2010 by Manuel Viet
* <a href="mailto:contact at 13zenrv dot fr">contact at 13zenrv dot fr</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 "rexpanderbox.h"
// Qt includes
#include <QApplication>
#include <QMouseEvent>
#include <QPainter>
#include <QStyle>
#include <QStyleOption>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QCheckBox>
// KDE includes
#include <kconfiggroup.h>
#include <klocalizedstring.h>
namespace KDcrawIface
{
RClickLabel::RClickLabel(QWidget* const parent)
: QLabel(parent)
{
setCursor(Qt::PointingHandCursor);
}
RClickLabel::RClickLabel(const QString& text, QWidget* const parent)
: QLabel(text, parent)
{
setCursor(Qt::PointingHandCursor);
}
RClickLabel::~RClickLabel()
{
}
void RClickLabel::mousePressEvent(QMouseEvent* event)
{
QLabel::mousePressEvent(event);
/*
* In some contexts, like QGraphicsView, there will be no
* release event if the press event was not accepted.
*/
if (event->button() == Qt::LeftButton)
{
event->accept();
}
}
void RClickLabel::mouseReleaseEvent(QMouseEvent* event)
{
QLabel::mouseReleaseEvent(event);
if (event->button() == Qt::LeftButton)
{
emit leftClicked();
emit activated();
event->accept();
}
}
void RClickLabel::keyPressEvent(QKeyEvent* e)
{
switch (e->key())
{
case Qt::Key_Down:
case Qt::Key_Right:
case Qt::Key_Space:
emit activated();
return;
default:
break;
}
QLabel::keyPressEvent(e);
}
// ------------------------------------------------------------------------
RSqueezedClickLabel::RSqueezedClickLabel(QWidget* const parent)
: RAdjustableLabel(parent)
{
setCursor(Qt::PointingHandCursor);
}
RSqueezedClickLabel::RSqueezedClickLabel(const QString& text, QWidget* const parent)
: RAdjustableLabel(parent)
{
setAdjustedText(text);
setCursor(Qt::PointingHandCursor);
}
RSqueezedClickLabel::~RSqueezedClickLabel()
{
}
void RSqueezedClickLabel::mouseReleaseEvent(QMouseEvent* event)
{
QLabel::mouseReleaseEvent(event);
if (event->button() == Qt::LeftButton)
{
emit leftClicked();
emit activated();
event->accept();
}
}
void RSqueezedClickLabel::mousePressEvent(QMouseEvent* event)
{
QLabel::mousePressEvent(event);
/*
* In some contexts, like QGraphicsView, there will be no
* release event if the press event was not accepted.
*/
if (event->button() == Qt::LeftButton)
{
event->accept();
}
}
void RSqueezedClickLabel::keyPressEvent(QKeyEvent* e)
{
switch (e->key())
{
case Qt::Key_Down:
case Qt::Key_Right:
case Qt::Key_Space:
emit activated();
return;
default:
break;
}
QLabel::keyPressEvent(e);
}
// ------------------------------------------------------------------------
RArrowClickLabel::RArrowClickLabel(QWidget* const parent)
: QWidget(parent), m_arrowType(Qt::DownArrow)
{
setCursor(Qt::PointingHandCursor);
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_size = 8;
m_margin = 2;
}
void RArrowClickLabel::setArrowType(Qt::ArrowType type)
{
m_arrowType = type;
update();
}
RArrowClickLabel::~RArrowClickLabel()
{
}
Qt::ArrowType RArrowClickLabel::arrowType() const
{
return m_arrowType;
}
void RArrowClickLabel::mousePressEvent(QMouseEvent* event)
{
/*
* In some contexts, like QGraphicsView, there will be no
* release event if the press event was not accepted.
*/
if (event->button() == Qt::LeftButton)
{
event->accept();
}
}
void RArrowClickLabel::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
emit leftClicked();
}
}
void RArrowClickLabel::paintEvent(QPaintEvent*)
{
// Inspired by karrowbutton.cpp,
// Copyright (C) 2001 Frerich Raabe <raabe@kde.org>
QPainter p(this);
QStyleOptionFrame opt;
opt.init(this);
opt.lineWidth = 2;
opt.midLineWidth = 0;
/*
p.fillRect( rect(), palette().brush( QPalette::Background ) );
style()->drawPrimitive( QStyle::PE_Frame, &opt, &p, this);
*/
if (m_arrowType == Qt::NoArrow)
return;
if (width() < m_size + m_margin || height() < m_size + m_margin)
return; // don't draw arrows if we are too small
unsigned int x = 0, y = 0;
if (m_arrowType == Qt::DownArrow)
{
x = (width() - m_size) / 2;
y = height() - (m_size + m_margin);
}
else if (m_arrowType == Qt::UpArrow)
{
x = (width() - m_size) / 2;
y = m_margin;
}
else if (m_arrowType == Qt::RightArrow)
{
x = width() - (m_size + m_margin);
y = (height() - m_size) / 2;
}
else // arrowType == LeftArrow
{
x = m_margin;
y = (height() - m_size) / 2;
}
/*
if (isDown())
{
++x;
++y;
}
*/
QStyle::PrimitiveElement e = QStyle::PE_IndicatorArrowLeft;
switch (m_arrowType)
{
case Qt::LeftArrow:
e = QStyle::PE_IndicatorArrowLeft;
break;
case Qt::RightArrow:
e = QStyle::PE_IndicatorArrowRight;
break;
case Qt::UpArrow:
e = QStyle::PE_IndicatorArrowUp;
break;
case Qt::DownArrow:
e = QStyle::PE_IndicatorArrowDown;
break;
case Qt::NoArrow:
break;
}
opt.state |= QStyle::State_Enabled;
opt.rect = QRect( x, y, m_size, m_size);
style()->drawPrimitive( e, &opt, &p, this );
}
QSize RArrowClickLabel::sizeHint() const
{
return QSize(m_size + 2*m_margin, m_size + 2*m_margin);
}
// ------------------------------------------------------------------------
class Q_DECL_HIDDEN RLabelExpander::Private
{
public:
Private()
{
clickLabel = 0;
containerWidget = 0;
pixmapLabel = 0;
grid = 0;
arrow = 0;
line = 0;
hbox = 0;
checkBox = 0;
expandByDefault = true;
}
bool expandByDefault;
QCheckBox* checkBox;
QLabel* pixmapLabel;
QWidget* containerWidget;
QGridLayout* grid;
RLineWidget* line;
QWidget* hbox;
RArrowClickLabel* arrow;
RClickLabel* clickLabel;
};
RLabelExpander::RLabelExpander(QWidget* const parent)
: QWidget(parent), d(new Private)
{
d->grid = new QGridLayout(this);
d->line = new RLineWidget(Qt::Horizontal, this);
d->hbox = new QWidget(this);
d->arrow = new RArrowClickLabel(d->hbox);
d->checkBox = new QCheckBox(d->hbox);
d->pixmapLabel = new QLabel(d->hbox);
d->clickLabel = new RClickLabel(d->hbox);
QHBoxLayout* const hlay = new QHBoxLayout(d->hbox);
hlay->addWidget(d->arrow);
hlay->addWidget(d->checkBox);
hlay->addWidget(d->pixmapLabel);
hlay->addWidget(d->clickLabel, 10);
hlay->setMargin(0);
hlay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
d->pixmapLabel->installEventFilter(this);
d->pixmapLabel->setCursor(Qt::PointingHandCursor);
d->hbox->setCursor(Qt::PointingHandCursor);
setCheckBoxVisible(false);
d->grid->addWidget(d->line, 0, 0, 1, 3);
d->grid->addWidget(d->hbox, 1, 0, 1, 3);
d->grid->setColumnStretch(2, 10);
d->grid->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
d->grid->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
connect(d->arrow, &RArrowClickLabel::leftClicked,
this, &RLabelExpander::slotToggleContainer);
connect(d->clickLabel, &RClickLabel::activated,
this, &RLabelExpander::slotToggleContainer);
connect(d->checkBox, &QCheckBox::toggled,
this, &RLabelExpander::signalToggled);
}
RLabelExpander::~RLabelExpander()
{
delete d;
}
void RLabelExpander::setCheckBoxVisible(bool b)
{
d->checkBox->setVisible(b);
}
bool RLabelExpander::checkBoxIsVisible() const
{
return d->checkBox->isVisible();
}
void RLabelExpander::setChecked(bool b)
{
d->checkBox->setChecked(b);
}
bool RLabelExpander::isChecked() const
{
return d->checkBox->isChecked();
}
void RLabelExpander::setLineVisible(bool b)
{
d->line->setVisible(b);
}
bool RLabelExpander::lineIsVisible() const
{
return d->line->isVisible();
}
void RLabelExpander::setText(const QString& txt)
{
d->clickLabel->setText(QString("<qt><b>%1</b></qt>").arg(txt));
}
QString RLabelExpander::text() const
{
return d->clickLabel->text();
}
void RLabelExpander::setIcon(const QIcon& icon)
{
d->pixmapLabel->setPixmap(icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)));
}
QIcon RLabelExpander::icon() const
{
return QIcon(*d->pixmapLabel->pixmap());
}
void RLabelExpander::setWidget(QWidget* const widget)
{
if (widget)
{
d->containerWidget = widget;
d->containerWidget->setParent(this);
d->grid->addWidget(d->containerWidget, 2, 0, 1, 3);
}
}
QWidget* RLabelExpander::widget() const
{
return d->containerWidget;
}
void RLabelExpander::setExpandByDefault(bool b)
{
d->expandByDefault = b;
}
bool RLabelExpander::isExpandByDefault() const
{
return d->expandByDefault;
}
void RLabelExpander::setExpanded(bool b)
{
if (d->containerWidget)
{
d->containerWidget->setVisible(b);
if (b)
d->arrow->setArrowType(Qt::DownArrow);
else
d->arrow->setArrowType(Qt::RightArrow);
}
emit signalExpanded(b);
}
bool RLabelExpander::isExpanded() const
{
return (d->arrow->arrowType() == Qt::DownArrow);
}
void RLabelExpander::slotToggleContainer()
{
if (d->containerWidget)
setExpanded(!d->containerWidget->isVisible());
}
bool RLabelExpander::eventFilter(QObject* obj, QEvent* ev)
{
if ( obj == d->pixmapLabel)
{
if ( ev->type() == QEvent::MouseButtonRelease)
{
slotToggleContainer();
return false;
}
else
{
return false;
}
}
else
{
// pass the event on to the parent class
return QWidget::eventFilter(obj, ev);
}
}
// ------------------------------------------------------------------------
class Q_DECL_HIDDEN RExpanderBox::Private
{
public:
Private(RExpanderBox* const box)
{
parent = box;
vbox = 0;
}
void createItem(int index, QWidget* const w, const QIcon& icon, const QString& txt,
const QString& objName, bool expandBydefault)
{
RLabelExpander* const exp = new RLabelExpander(parent->viewport());
exp->setText(txt);
exp->setIcon(icon.pixmap(QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize)));
exp->setWidget(w);
exp->setLineVisible(!wList.isEmpty());
exp->setObjectName(objName);
exp->setExpandByDefault(expandBydefault);
if (index >= 0)
{
vbox->insertWidget(index, exp);
wList.insert(index, exp);
}
else
{
vbox->addWidget(exp);
wList.append(exp);
}
parent->connect(exp, SIGNAL(signalExpanded(bool)),
parent, SLOT(slotItemExpanded(bool)));
parent->connect(exp, SIGNAL(signalToggled(bool)),
parent, SLOT(slotItemToggled(bool)));
}
public:
QList<RLabelExpander*> wList;
QVBoxLayout* vbox;
RExpanderBox* parent;
};
RExpanderBox::RExpanderBox(QWidget* const parent)
: QScrollArea(parent), d(new Private(this))
{
setFrameStyle(QFrame::NoFrame);
setWidgetResizable(true);
QWidget* const main = new QWidget(viewport());
d->vbox = new QVBoxLayout(main);
d->vbox->setMargin(0);
d->vbox->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
setWidget(main);
setAutoFillBackground(false);
viewport()->setAutoFillBackground(false);
main->setAutoFillBackground(false);
}
RExpanderBox::~RExpanderBox()
{
d->wList.clear();
delete d;
}
void RExpanderBox::setCheckBoxVisible(int index, bool b)
{
if (index > d->wList.count() || index < 0) return;
d->wList[index]->setCheckBoxVisible(b);
}
bool RExpanderBox::checkBoxIsVisible(int index) const
{
if (index > d->wList.count() || index < 0) return false;
return d->wList[index]->checkBoxIsVisible();
}
void RExpanderBox::setChecked(int index, bool b)
{
if (index > d->wList.count() || index < 0) return;
d->wList[index]->setChecked(b);
}
bool RExpanderBox::isChecked(int index) const
{
if (index > d->wList.count() || index < 0) return false;
return d->wList[index]->isChecked();
}
void RExpanderBox::addItem(QWidget* const w, const QIcon& icon, const QString& txt,
const QString& objName, bool expandBydefault)
{
d->createItem(-1, w, icon, txt, objName, expandBydefault);
}
void RExpanderBox::addItem(QWidget* const w, const QString& txt,
const QString& objName, bool expandBydefault)
{
addItem(w, QIcon(), txt, objName, expandBydefault);
}
void RExpanderBox::addStretch()
{
d->vbox->addStretch(10);
}
void RExpanderBox::insertItem(int index, QWidget* const w, const QIcon& icon, const QString& txt,
const QString& objName, bool expandBydefault)
{
d->createItem(index, w, icon, txt, objName, expandBydefault);
}
void RExpanderBox::slotItemExpanded(bool b)
{
RLabelExpander* const exp = dynamic_cast<RLabelExpander*>(sender());
if (exp)
{
int index = indexOf(exp);
emit signalItemExpanded(index, b);
}
}
void RExpanderBox::slotItemToggled(bool b)
{
RLabelExpander* const exp = dynamic_cast<RLabelExpander*>(sender());
if (exp)
{
int index = indexOf(exp);
emit signalItemToggled(index, b);
}
}
void RExpanderBox::insertItem(int index, QWidget* const w, const QString& txt,
const QString& objName, bool expandBydefault)
{
insertItem(index, w, QIcon(), txt, objName, expandBydefault);
}
void RExpanderBox::insertStretch(int index)
{
d->vbox->insertStretch(index, 10);
}
void RExpanderBox::removeItem(int index)
{
if (index > d->wList.count() || index < 0) return;
d->wList[index]->hide();
d->wList.removeAt(index);
}
void RExpanderBox::setItemText(int index, const QString& txt)
{
if (index > d->wList.count() || index < 0) return;
d->wList[index]->setText(txt);
}
QString RExpanderBox::itemText(int index) const
{
if (index > d->wList.count() || index < 0) return QString();
return d->wList[index]->text();
}
void RExpanderBox::setItemIcon(int index, const QIcon& icon)
{
if (index > d->wList.count() || index < 0) return;
d->wList[index]->setIcon(icon.pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)));
}
QIcon RExpanderBox::itemIcon(int index) const
{
if (index > d->wList.count() || index < 0) return QIcon();
return d->wList[index]->icon();
}
int RExpanderBox::count() const
{
return d->wList.count();
}
void RExpanderBox::setItemToolTip(int index, const QString& tip)
{
if (index > d->wList.count() || index < 0) return;
d->wList[index]->setToolTip(tip);
}
QString RExpanderBox::itemToolTip(int index) const
{
if (index > d->wList.count() || index < 0) return QString();
return d->wList[index]->toolTip();
}
void RExpanderBox::setItemEnabled(int index, bool enabled)
{
if (index > d->wList.count() || index < 0) return;
d->wList[index]->setEnabled(enabled);
}
bool RExpanderBox::isItemEnabled(int index) const
{
if (index > d->wList.count() || index < 0) return false;
return d->wList[index]->isEnabled();
}
RLabelExpander* RExpanderBox::widget(int index) const
{
if (index > d->wList.count() || index < 0) return 0;
return d->wList[index];
}
int RExpanderBox::indexOf(RLabelExpander* const widget) const
{
for (int i = 0 ; i < count(); ++i)
{
RLabelExpander* const exp = d->wList[i];
if (widget == exp)
return i;
}
return -1;
}
void RExpanderBox::setItemExpanded(int index, bool b)
{
if (index > d->wList.count() || index < 0) return;
RLabelExpander* const exp = d->wList[index];
if (!exp) return;
exp->setExpanded(b);
}
bool RExpanderBox::isItemExpanded(int index) const
{
if (index > d->wList.count() || index < 0) return false;
RLabelExpander* const exp = d->wList[index];
if (!exp) return false;
return (exp->isExpanded());
}
void RExpanderBox::readSettings(KConfigGroup& group)
{
for (int i = 0 ; i < count(); ++i)
{
RLabelExpander* const exp = d->wList[i];
if (exp)
{
exp->setExpanded(group.readEntry(QString("%1 Expanded").arg(exp->objectName()),
exp->isExpandByDefault()));
}
}
}
void RExpanderBox::writeSettings(KConfigGroup& group)
{
for (int i = 0 ; i < count(); ++i)
{
RLabelExpander* const exp = d->wList[i];
if (exp)
{
group.writeEntry(QString("%1 Expanded").arg(exp->objectName()),
exp->isExpanded());
}
}
}
// ------------------------------------------------------------------------
RExpanderBoxExclusive::RExpanderBoxExclusive(QWidget* const parent)
: RExpanderBox(parent)
{
setIsToolBox(true);
}
RExpanderBoxExclusive::~RExpanderBoxExclusive()
{
}
void RExpanderBoxExclusive::slotItemExpanded(bool b)
{
RLabelExpander* const exp = dynamic_cast<RLabelExpander*>(sender());
if (!exp) return;
if (isToolBox() && b)
{
int item = 0;
while (item < count())
{
if (isItemExpanded(item) && item != indexOf(exp))
{
setItemExpanded(item, false);
}
item++;
}
}
emit signalItemExpanded(indexOf(exp), b);
}
void RExpanderBoxExclusive::setIsToolBox(bool b)
{
m_toolbox = b;
}
bool RExpanderBoxExclusive::isToolBox() const
{
return (m_toolbox);
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.h
index 8cd0675b57..13d36b5d00 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rexpanderbox.h
@@ -1,299 +1,299 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2008-03-14
* @brief A widget to host settings as expander box
*
* @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>
* @author Copyright (C) 2008-2013 by Marcel Wiesweg
* <a href="mailto:marcel dot wiesweg at gmx dot de">marcel dot wiesweg at gmx dot de</a>
* @author Copyright (C) 2010 by Manuel Viet
* <a href="mailto:contact at 13zenrv dot fr">contact at 13zenrv dot fr</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.
*
* ============================================================ */
#ifndef REXPANDERBOX_H
#define REXPANDERBOX_H
// Qt includes
#include <QObject>
#include <QPixmap>
#include <QLabel>
#include <QWidget>
#include <QScrollArea>
// KDE includes
#include <kconfiggroup.h>
// Local includes
#include "rwidgetutils.h"
namespace KDcrawIface
{
class RClickLabel : public QLabel
{
Q_OBJECT
public:
RClickLabel(QWidget* const parent = 0);
explicit RClickLabel(const QString& text, QWidget* const parent = 0);
~RClickLabel() override;
Q_SIGNALS:
/// Emitted when activated by left mouse click
void leftClicked();
/// Emitted when activated, by mouse or key press
void activated();
protected:
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
};
// -------------------------------------------------------------------------
class RSqueezedClickLabel : public RAdjustableLabel
{
Q_OBJECT
public:
RSqueezedClickLabel(QWidget* const parent = 0);
explicit RSqueezedClickLabel(const QString& text, QWidget* const parent = 0);
~RSqueezedClickLabel() override;
Q_SIGNALS:
void leftClicked();
void activated();
protected:
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
};
// -------------------------------------------------------------------------
class RArrowClickLabel : public QWidget
{
Q_OBJECT
public:
RArrowClickLabel(QWidget* const parent = 0);
~RArrowClickLabel() override;
void setArrowType(Qt::ArrowType arrowType);
Qt::ArrowType arrowType() const;
QSize sizeHint () const override;
Q_SIGNALS:
void leftClicked();
protected:
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void paintEvent(QPaintEvent* event) override;
protected:
Qt::ArrowType m_arrowType;
int m_size;
int m_margin;
};
// -------------------------------------------------------------------------
class RLabelExpander : public QWidget
{
Q_OBJECT
public:
RLabelExpander(QWidget* const parent = 0);
~RLabelExpander() override;
void setCheckBoxVisible(bool b);
bool checkBoxIsVisible() const;
void setChecked(bool b);
bool isChecked() const;
void setLineVisible(bool b);
bool lineIsVisible() const;
void setText(const QString& txt);
QString text() const;
void setIcon(const QIcon &icon);
QIcon icon() const;
void setWidget(QWidget* const widget);
QWidget* widget() const;
void setExpanded(bool b);
bool isExpanded() const;
void setExpandByDefault(bool b);
bool isExpandByDefault() const;
Q_SIGNALS:
void signalExpanded(bool);
void signalToggled(bool);
private Q_SLOTS:
void slotToggleContainer();
private:
bool eventFilter(QObject* obj, QEvent* ev) override;
private:
class Private;
Private* const d;
};
// -------------------------------------------------------------------------
class RExpanderBox : public QScrollArea
{
Q_OBJECT
public:
RExpanderBox(QWidget* const parent = 0);
~RExpanderBox() override;
/** Add RLabelExpander item at end of box layout with these settings :
'w' : the widget hosted by RLabelExpander.
'pix' : pixmap used as icon to item title.
'txt' : text used as item title.
'objName' : item object name used to read/save expanded settings to rc file.
'expandBydefault' : item state by default (expanded or not).
*/
void addItem(QWidget* const w, const QIcon &icon, const QString& txt,
const QString& objName, bool expandBydefault);
void addItem(QWidget* const w, const QString& txt,
const QString& objName, bool expandBydefault);
/** Insert RLabelExpander item at box layout index with these settings :
'w' : the widget hosted by RLabelExpander.
'pix' : pixmap used as icon to item title.
'txt' : text used as item title.
'objName' : item object name used to read/save expanded settings to rc file.
'expandBydefault' : item state by default (expanded or not).
*/
void insertItem(int index, QWidget* const w, const QIcon &icon, const QString& txt,
const QString& objName, bool expandBydefault);
void insertItem(int index, QWidget* const w, const QString& txt,
const QString& objName, bool expandBydefault);
void removeItem(int index);
void setCheckBoxVisible(int index, bool b);
bool checkBoxIsVisible(int index) const;
void setChecked(int index, bool b);
bool isChecked(int index) const;
void setItemText(int index, const QString& txt);
QString itemText (int index) const;
void setItemIcon(int index, const QIcon &icon);
QIcon itemIcon(int index) const;
void setItemToolTip(int index, const QString& tip);
QString itemToolTip(int index) const;
void setItemEnabled(int index, bool enabled);
bool isItemEnabled(int index) const;
void addStretch();
void insertStretch(int index);
void setItemExpanded(int index, bool b);
bool isItemExpanded(int index) const;
int count() const;
RLabelExpander* widget(int index) const;
int indexOf(RLabelExpander* const widget) const;
virtual void readSettings(KConfigGroup& group);
virtual void writeSettings(KConfigGroup& group);
Q_SIGNALS:
void signalItemExpanded(int index, bool b);
void signalItemToggled(int index, bool b);
private Q_SLOTS:
void slotItemExpanded(bool b);
void slotItemToggled(bool b);
private:
class Private;
Private* const d;
};
// -------------------------------------------------------------------------
class RExpanderBoxExclusive : public RExpanderBox
{
Q_OBJECT
public:
RExpanderBoxExclusive(QWidget* const parent = 0);
~RExpanderBoxExclusive() override;
/** Show one expander open at most */
void setIsToolBox(bool b);
bool isToolBox() const;
private Q_SLOTS:
void slotItemExpanded(bool b);
private:
bool m_toolbox;
};
} // namespace KDcrawIface
#endif // REXPANDERBOX_H
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.cpp
index adcb5f66ea..63425d2fb0 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.cpp
@@ -1,257 +1,257 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2008-08-16
* @brief Integer and double num input widget
* re-implemented with a reset button to switch to
* a default value
*
* @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 "rnuminput.h"
// C++ includes
#include <cmath>
// Qt includes
#include <QToolButton>
#include <QApplication>
#include <QStyle>
#include <QHBoxLayout>
#include <QIcon>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "rsliderspinbox.h"
#include <kis_icon_utils.h>
namespace KDcrawIface
{
class Q_DECL_HIDDEN RIntNumInput::Private
{
public:
Private()
{
defaultValue = 0;
resetButton = 0;
input = 0;
}
int defaultValue;
QToolButton* resetButton;
RSliderSpinBox* input;
};
RIntNumInput::RIntNumInput(QWidget* const parent)
: QWidget(parent),
d(new Private)
{
QHBoxLayout* const hlay = new QHBoxLayout(this);
d->input = new RSliderSpinBox(this);
d->resetButton = new QToolButton(this);
d->resetButton->setAutoRaise(true);
d->resetButton->setFocusPolicy(Qt::NoFocus);
d->resetButton->setIcon(KisIconUtils::loadIcon("document-revert").pixmap(16, 16));
d->resetButton->setToolTip(i18nc("@info:tooltip", "Reset to default value"));
hlay->addWidget(d->input);
hlay->addWidget(d->resetButton);
hlay->setContentsMargins(QMargins());
hlay->setStretchFactor(d->input, 10);
hlay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
// -------------------------------------------------------------
connect(d->resetButton, &QToolButton::clicked,
this, &RIntNumInput::slotReset);
connect(d->input, &RSliderSpinBox::valueChanged,
this, &RIntNumInput::slotValueChanged);
}
RIntNumInput::~RIntNumInput()
{
delete d;
}
void RIntNumInput::setRange(int min, int max, int step)
{
d->input->setRange(min, max);
d->input->setSingleStep(step);
}
int RIntNumInput::value() const
{
return d->input->value();
}
void RIntNumInput::setValue(int v)
{
d->input->setValue(v);
}
int RIntNumInput::defaultValue() const
{
return d->defaultValue;
}
void RIntNumInput::setDefaultValue(int v)
{
d->defaultValue = v;
d->input->setValue(d->defaultValue);
slotValueChanged(v);
}
void RIntNumInput::setSuffix(const QString& suffix)
{
d->input->setSuffix(suffix);
}
void RIntNumInput::slotReset()
{
d->input->setValue(d->defaultValue);
d->resetButton->setEnabled(false);
emit reset();
}
void RIntNumInput::slotValueChanged(int v)
{
d->resetButton->setEnabled(v != d->defaultValue);
emit valueChanged(v);
}
// ----------------------------------------------------
class Q_DECL_HIDDEN RDoubleNumInput::Private
{
public:
Private()
{
defaultValue = 0.0;
resetButton = 0;
input = 0;
}
double defaultValue;
QToolButton* resetButton;
RDoubleSliderSpinBox* input;
};
RDoubleNumInput::RDoubleNumInput(QWidget* const parent)
: QWidget(parent),
d(new Private)
{
QHBoxLayout* const hlay = new QHBoxLayout(this);
d->input = new RDoubleSliderSpinBox(this);
d->resetButton = new QToolButton(this);
d->resetButton->setAutoRaise(true);
d->resetButton->setFocusPolicy(Qt::NoFocus);
d->resetButton->setIcon(KisIconUtils::loadIcon("document-revert").pixmap(16, 16));
d->resetButton->setToolTip(i18nc("@info:tooltip", "Reset to default value"));
hlay->addWidget(d->input);
hlay->addWidget(d->resetButton);
hlay->setContentsMargins(QMargins());
hlay->setStretchFactor(d->input, 10);
hlay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
// -------------------------------------------------------------
connect(d->resetButton, &QToolButton::clicked,
this, &RDoubleNumInput::slotReset);
connect(d->input, &RDoubleSliderSpinBox::valueChanged,
this, &RDoubleNumInput::slotValueChanged);
}
RDoubleNumInput::~RDoubleNumInput()
{
delete d;
}
void RDoubleNumInput::setDecimals(int p)
{
d->input->setRange(d->input->minimum(), d->input->maximum(), p);
}
void RDoubleNumInput::setRange(double min, double max, double step)
{
d->input->setRange(min, max, (int) -floor(log10(step)));
d->input->setFastSliderStep(5 * step);
d->input->setSingleStep(step);
}
double RDoubleNumInput::value() const
{
return d->input->value();
}
void RDoubleNumInput::setValue(double v)
{
d->input->setValue(v);
}
double RDoubleNumInput::defaultValue() const
{
return d->defaultValue;
}
void RDoubleNumInput::setDefaultValue(double v)
{
d->defaultValue = v;
d->input->setValue(d->defaultValue);
slotValueChanged(v);
}
void RDoubleNumInput::setSuffix(const QString& suffix)
{
d->input->setSuffix(suffix);
}
void RDoubleNumInput::slotReset()
{
d->input->setValue(d->defaultValue);
d->resetButton->setEnabled(false);
emit reset();
}
void RDoubleNumInput::slotValueChanged(double v)
{
d->resetButton->setEnabled(v != d->defaultValue);
emit valueChanged(v);
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.h
index cb18e58339..2c8ff38607 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rnuminput.h
@@ -1,121 +1,121 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2008-08-16
* @brief Integer and double num input widget
* re-implemented with a reset button to switch to
* a default value
*
* @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.
*
* ============================================================ */
#ifndef RNUMINPUT_H
#define RNUMINPUT_H
// Qt includes
#include <QWidget>
// Local includes
namespace KDcrawIface
{
class RIntNumInput : public QWidget
{
Q_OBJECT
public:
RIntNumInput(QWidget* const parent=0);
~RIntNumInput() override;
void setRange(int min, int max, int step);
void setDefaultValue(int d);
int defaultValue() const;
int value() const;
void setSuffix(const QString& suffix);
Q_SIGNALS:
void reset();
void valueChanged(int);
public Q_SLOTS:
void setValue(int d);
void slotReset();
private Q_SLOTS:
void slotValueChanged(int);
private:
class Private;
Private* const d;
};
// ---------------------------------------------------------
class RDoubleNumInput : public QWidget
{
Q_OBJECT
public:
RDoubleNumInput(QWidget* const parent=0);
~RDoubleNumInput() override;
void setDecimals(int p);
void setRange(double min, double max, double step);
void setDefaultValue(double d);
double defaultValue() const;
double value() const;
void setSuffix(const QString& suffix);
Q_SIGNALS:
void reset();
void valueChanged(double);
public Q_SLOTS:
void setValue(double d);
void slotReset();
private Q_SLOTS:
void slotValueChanged(double);
private:
class Private;
Private* const d;
};
} // namespace KDcrawIface
#endif /* RNUMINPUT_H */
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp
index 0efcf9dbcb..b89263c04d 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp
@@ -1,773 +1,772 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2014-11-30
* @brief Save space slider widget
*
* @author Copyright (C) 2014 by Gilles Caulier
* <a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
* @author Copyright (C) 2010 by Justin Noel
* <a href="mailto:justin at ics dot com">justin at ics dot com</a>
* @author Copyright (C) 2010 by Cyrille Berger
* <a href="mailto:cberger at cberger dot net">cberger at cberger dot net</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 "rsliderspinbox.h"
// C++ includes
#include <cmath>
// Qt includes
#include <QPainter>
#include <QStyle>
#include <QLineEdit>
#include <QApplication>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QIntValidator>
#include <QTimer>
#include <QtDebug>
#include <QDoubleSpinBox>
namespace KDcrawIface
{
class RAbstractSliderSpinBoxPrivate
{
public:
RAbstractSliderSpinBoxPrivate()
{
edit = 0;
validator = 0;
dummySpinBox = 0;
upButtonDown = false;
downButtonDown = false;
shiftMode = false;
factor = 1.0;
fastSliderStep = 5;
slowFactor = 0.1;
shiftPercent = 0.0;
exponentRatio = 0.0;
value = 0;
maximum = 100;
minimum = 0;
singleStep = 1;
}
QLineEdit* edit;
QDoubleValidator* validator;
bool upButtonDown;
bool downButtonDown;
int factor;
int fastSliderStep;
double slowFactor;
double shiftPercent;
bool shiftMode;
QString suffix;
double exponentRatio;
int value;
int maximum;
int minimum;
int singleStep;
QSpinBox* dummySpinBox;
};
RAbstractSliderSpinBox::RAbstractSliderSpinBox(QWidget* const parent, RAbstractSliderSpinBoxPrivate* const q)
: QWidget(parent),
d_ptr(q)
{
Q_D(RAbstractSliderSpinBox);
d->edit = new QLineEdit(this);
d->edit->setFrame(false);
d->edit->setAlignment(Qt::AlignCenter);
d->edit->hide();
d->edit->installEventFilter(this);
// Make edit transparent
d->edit->setAutoFillBackground(false);
QPalette pal = d->edit->palette();
pal.setColor(QPalette::Base, Qt::transparent);
d->edit->setPalette(pal);
connect(d->edit, SIGNAL(editingFinished()),
this, SLOT(editLostFocus()));
d->validator = new QDoubleValidator(d->edit);
d->edit->setValidator(d->validator);
setExponentRatio(1.0);
// Set sane defaults
setFocusPolicy(Qt::StrongFocus);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
// dummy needed to fix a bug in the polyester theme
d->dummySpinBox = new QSpinBox(this);
d->dummySpinBox->hide();
}
RAbstractSliderSpinBox::~RAbstractSliderSpinBox()
{
Q_D(RAbstractSliderSpinBox);
delete d;
}
void RAbstractSliderSpinBox::showEdit()
{
Q_D(RAbstractSliderSpinBox);
if (d->edit->isVisible()) return;
d->edit->setGeometry(editRect(spinBoxOptions()));
d->edit->setText(valueString());
d->edit->selectAll();
d->edit->show();
d->edit->setFocus(Qt::OtherFocusReason);
update();
}
void RAbstractSliderSpinBox::hideEdit()
{
Q_D(RAbstractSliderSpinBox);
d->edit->hide();
update();
}
void RAbstractSliderSpinBox::paintEvent(QPaintEvent* e)
{
Q_D(RAbstractSliderSpinBox);
Q_UNUSED(e)
QPainter painter(this);
// Create options to draw spin box parts
QStyleOptionSpinBox spinOpts = spinBoxOptions();
// Draw "SpinBox".Clip off the area of the lineEdit to avoid double borders being drawn
painter.save();
painter.setClipping(true);
QRect eraseRect(QPoint(rect().x(), rect().y()),
QPoint(editRect(spinOpts).right(), rect().bottom()));
painter.setClipRegion(QRegion(rect()).subtracted(eraseRect));
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
painter.setClipping(false);
painter.restore();
// Create options to draw progress bar parts
QStyleOptionProgressBar progressOpts = progressBarOptions();
// Draw "ProgressBar" in SpinBox
style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0);
// Draw focus if necessary
if (hasFocus() && d->edit->hasFocus())
{
QStyleOptionFocusRect focusOpts;
focusOpts.initFrom(this);
focusOpts.rect = progressOpts.rect;
focusOpts.backgroundColor = palette().color(QPalette::Window);
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this);
}
}
void RAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e)
{
Q_D(RAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
// Depress buttons or highlight slider. Also used to emulate mouse grab.
if (e->buttons() & Qt::LeftButton)
{
if (upButtonRect(spinOpts).contains(e->pos()))
{
d->upButtonDown = true;
}
else if (downButtonRect(spinOpts).contains(e->pos()))
{
d->downButtonDown = true;
}
}
else if (e->buttons() & Qt::RightButton)
{
showEdit();
}
update();
}
void RAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e)
{
Q_D(RAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
// Step up/down for buttons. Emulating mouse grab too.
if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown)
{
setInternalValue(d->value + d->singleStep);
}
else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown)
{
setInternalValue(d->value - d->singleStep);
}
else if (editRect(spinOpts).contains(e->pos()) &&
!(d->edit->isVisible()) &&
!(d->upButtonDown || d->downButtonDown))
{
// Snap to percentage for progress area
setInternalValue(valueForX(e->pos().x(),e->modifiers()));
}
d->upButtonDown = false;
d->downButtonDown = false;
update();
}
void RAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e)
{
Q_D(RAbstractSliderSpinBox);
if( e->modifiers() & Qt::ShiftModifier )
{
if( !d->shiftMode )
{
d->shiftPercent = pow(double(d->value - d->minimum)/double(d->maximum - d->minimum),
1/double(d->exponentRatio));
d->shiftMode = true;
}
}
else
{
d->shiftMode = false;
}
// Respect emulated mouse grab.
if (e->buttons() & Qt::LeftButton && !(d->downButtonDown || d->upButtonDown))
{
setInternalValue(valueForX(e->pos().x(),e->modifiers()));
update();
}
}
void RAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e)
{
Q_D(RAbstractSliderSpinBox);
switch (e->key())
{
case Qt::Key_Up:
case Qt::Key_Right:
setInternalValue(d->value + d->singleStep);
break;
case Qt::Key_Down:
case Qt::Key_Left:
setInternalValue(d->value - d->singleStep);
break;
case Qt::Key_Shift:
d->shiftPercent = pow( double(d->value - d->minimum)/double(d->maximum - d->minimum), 1/double(d->exponentRatio) );
d->shiftMode = true;
break;
case Qt::Key_Enter: // Line edit isn't "accepting" key strokes...
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Control:
case Qt::Key_Alt:
case Qt::Key_AltGr:
case Qt::Key_Super_L:
case Qt::Key_Super_R:
break;
default:
showEdit();
d->edit->event(e);
break;
}
}
void RAbstractSliderSpinBox::wheelEvent(QWheelEvent *e)
{
Q_D(RAbstractSliderSpinBox);
int step = d->fastSliderStep;
if( e->modifiers() & Qt::ShiftModifier )
{
step = d->singleStep;
}
if ( e->delta() > 0)
{
setInternalValue(d->value + step);
}
else
{
setInternalValue(d->value - step);
}
update();
e->accept();
}
bool RAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e)
{
Q_D(RAbstractSliderSpinBox);
if (recv == static_cast<QObject*>(d->edit) && e->type() == QEvent::KeyRelease)
{
QKeyEvent* const keyEvent = static_cast<QKeyEvent*>(e);
switch (keyEvent->key())
{
case Qt::Key_Enter:
case Qt::Key_Return:
setInternalValue(QLocale::system().toDouble(d->edit->text()) * d->factor);
hideEdit();
return true;
case Qt::Key_Escape:
hideEdit();
return true;
default:
break;
}
}
return false;
}
QSize RAbstractSliderSpinBox::sizeHint() const
{
const Q_D(RAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QFontMetrics fm(font());
// We need at least 50 pixels or things start to look bad
int w = qMax(fm.width(QString::number(d->maximum)), 50);
QSize hint(w, d->edit->sizeHint().height() + 3);
// Getting the size of the buttons is a pain as the calcs require a rect
// that is "big enough". We run the calc twice to get the "smallest" buttons
// This code was inspired by QAbstractSpinBox.
QSize extra(35, 6);
spinOpts.rect.setSize(hint + extra);
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts,
QStyle::SC_SpinBoxEditField, this).size();
spinOpts.rect.setSize(hint + extra);
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts,
QStyle::SC_SpinBoxEditField, this).size();
hint += extra;
spinOpts.rect = rect();
- return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint, 0)
- .expandedTo(QApplication::globalStrut());
+ return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint, 0);
}
QSize RAbstractSliderSpinBox::minimumSizeHint() const
{
return sizeHint();
}
QStyleOptionSpinBox RAbstractSliderSpinBox::spinBoxOptions() const
{
const Q_D(RAbstractSliderSpinBox);
QStyleOptionSpinBox opts;
opts.initFrom(this);
opts.frame = false;
opts.buttonSymbols = QAbstractSpinBox::UpDownArrows;
opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown;
// Disable non-logical buttons
if (d->value == d->minimum)
{
opts.stepEnabled = QAbstractSpinBox::StepUpEnabled;
}
else if (d->value == d->maximum)
{
opts.stepEnabled = QAbstractSpinBox::StepDownEnabled;
}
else
{
opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled;
}
// Deal with depressed buttons
if (d->upButtonDown)
{
opts.activeSubControls = QStyle::SC_SpinBoxUp;
}
else if (d->downButtonDown)
{
opts.activeSubControls = QStyle::SC_SpinBoxDown;
}
else
{
opts.activeSubControls = 0;
}
return opts;
}
QStyleOptionProgressBar RAbstractSliderSpinBox::progressBarOptions() const
{
const Q_D(RAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
// Create opts for drawing the progress portion
QStyleOptionProgressBar progressOpts;
progressOpts.initFrom(this);
progressOpts.maximum = d->maximum;
progressOpts.minimum = d->minimum;
double minDbl = d->minimum;
double dValues = (d->maximum - minDbl);
progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl;
progressOpts.text = valueString() + d->suffix;
progressOpts.textAlignment = Qt::AlignCenter;
progressOpts.textVisible = !(d->edit->isVisible());
// Change opts rect to be only the ComboBox's text area
progressOpts.rect = editRect(spinOpts);
return progressOpts;
}
QRect RAbstractSliderSpinBox::editRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxEditField);
}
QRect RAbstractSliderSpinBox::progressRect(const QStyleOptionProgressBar& progressBarOptions) const
{
return style()->subElementRect(QStyle::SE_ProgressBarGroove, &progressBarOptions);
}
QRect RAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxUp);
}
QRect RAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxDown);
}
int RAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const
{
const Q_D(RAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QStyleOptionProgressBar progressOpts = progressBarOptions();
// Adjust for magic number in style code (margins)
QRect correctedProgRect = progressRect(progressOpts).adjusted(2, 2, -2, -2);
// Compute the distance of the progress bar, in pixel
double leftDbl = correctedProgRect.left();
double xDbl = x - leftDbl;
// Compute the ration of the progress bar used, linearly (ignoring the exponent)
double rightDbl = correctedProgRect.right();
double minDbl = d->minimum;
double maxDbl = d->maximum;
double dValues = (maxDbl - minDbl);
double percent = (xDbl / (rightDbl - leftDbl));
// If SHIFT is pressed, movement should be slowed.
if ( modifiers & Qt::ShiftModifier )
{
percent = d->shiftPercent + (percent - d->shiftPercent) * d->slowFactor;
}
// Final value
double realvalue = ((dValues * pow(percent, d->exponentRatio)) + minDbl);
// If key CTRL is pressed, round to the closest step.
if ( modifiers & Qt::ControlModifier )
{
double fstep = d->fastSliderStep;
if( modifiers & Qt::ShiftModifier )
{
fstep *= d->slowFactor;
}
realvalue = floor((realvalue + fstep / 2) / fstep) * fstep;
}
// Return the value
return int(realvalue);
}
void RAbstractSliderSpinBox::setSuffix(const QString& suffix)
{
Q_D(RAbstractSliderSpinBox);
d->suffix = suffix;
}
void RAbstractSliderSpinBox::setExponentRatio(double dbl)
{
Q_D(RAbstractSliderSpinBox);
Q_ASSERT(dbl > 0);
d->exponentRatio = dbl;
}
void RAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event)
{
event->accept();
}
void RAbstractSliderSpinBox::editLostFocus()
{
// only hide on focus lost, if editing is finished that will be handled in eventFilter
Q_D(RAbstractSliderSpinBox);
if (!d->edit->hasFocus())
{
hideEdit();
}
}
// ---------------------------------------------------------------------------------------------
class RSliderSpinBoxPrivate : public RAbstractSliderSpinBoxPrivate
{
};
RSliderSpinBox::RSliderSpinBox(QWidget* const parent)
: RAbstractSliderSpinBox(parent, new RSliderSpinBoxPrivate)
{
setRange(0,99);
}
RSliderSpinBox::~RSliderSpinBox()
{
}
void RSliderSpinBox::setRange(int minimum, int maximum)
{
Q_D(RSliderSpinBox);
d->minimum = minimum;
d->maximum = maximum;
d->fastSliderStep = (maximum-minimum+1)/20;
d->validator->setRange(minimum, maximum, 0);
update();
}
int RSliderSpinBox::minimum() const
{
const Q_D(RSliderSpinBox);
return d->minimum;
}
void RSliderSpinBox::setMinimum(int minimum)
{
Q_D(RSliderSpinBox);
setRange(minimum, d->maximum);
}
int RSliderSpinBox::maximum() const
{
const Q_D(RSliderSpinBox);
return d->maximum;
}
void RSliderSpinBox::setMaximum(int maximum)
{
Q_D(RSliderSpinBox);
setRange(d->minimum, maximum);
}
int RSliderSpinBox::fastSliderStep() const
{
const Q_D(RSliderSpinBox);
return d->fastSliderStep;
}
void RSliderSpinBox::setFastSliderStep(int step)
{
Q_D(RSliderSpinBox);
d->fastSliderStep = step;
}
int RSliderSpinBox::value() const
{
const Q_D(RSliderSpinBox);
return d->value;
}
void RSliderSpinBox::setValue(int value)
{
setInternalValue(value);
update();
}
QString RSliderSpinBox::valueString() const
{
const Q_D(RSliderSpinBox);
return QLocale::system().toString(d->value);
}
void RSliderSpinBox::setSingleStep(int value)
{
Q_D(RSliderSpinBox);
d->singleStep = value;
}
void RSliderSpinBox::setPageStep(int value)
{
Q_UNUSED(value);
}
void RSliderSpinBox::setInternalValue(int _value)
{
Q_D(RAbstractSliderSpinBox);
d->value = qBound(d->minimum, _value, d->maximum);
emit(valueChanged(value()));
}
// ---------------------------------------------------------------------------------------------
class RDoubleSliderSpinBoxPrivate : public RAbstractSliderSpinBoxPrivate
{
};
RDoubleSliderSpinBox::RDoubleSliderSpinBox(QWidget* const parent)
: RAbstractSliderSpinBox(parent, new RDoubleSliderSpinBoxPrivate)
{
}
RDoubleSliderSpinBox::~RDoubleSliderSpinBox()
{
}
void RDoubleSliderSpinBox::setRange(double minimum, double maximum, int decimals)
{
Q_D(RDoubleSliderSpinBox);
d->factor = pow(10.0, decimals);
d->minimum = minimum * d->factor;
d->maximum = maximum * d->factor;
// This code auto-compute a new step when pressing control.
// A flag defaulting to "do not change the fast step" should be added, but it implies changing every call
if (maximum - minimum >= 2.0 || decimals <= 0)
{
//Quick step on integers
d->fastSliderStep = int(pow(10.0, decimals));
}
else if(decimals == 1)
{
d->fastSliderStep = (maximum-minimum)*d->factor/10;
}
else
{
d->fastSliderStep = (maximum-minimum)*d->factor/20;
}
d->validator->setRange(minimum, maximum, decimals);
update();
setValue(value());
}
double RDoubleSliderSpinBox::minimum() const
{
const Q_D(RAbstractSliderSpinBox);
return d->minimum / d->factor;
}
void RDoubleSliderSpinBox::setMinimum(double minimum)
{
Q_D(RAbstractSliderSpinBox);
setRange(minimum, d->maximum);
}
double RDoubleSliderSpinBox::maximum() const
{
const Q_D(RAbstractSliderSpinBox);
return d->maximum / d->factor;
}
void RDoubleSliderSpinBox::setMaximum(double maximum)
{
Q_D(RAbstractSliderSpinBox);
setRange(d->minimum, maximum);
}
double RDoubleSliderSpinBox::fastSliderStep() const
{
const Q_D(RAbstractSliderSpinBox);
return d->fastSliderStep;
}
void RDoubleSliderSpinBox::setFastSliderStep(double step)
{
Q_D(RAbstractSliderSpinBox);
d->fastSliderStep = step * d->factor;
}
double RDoubleSliderSpinBox::value() const
{
const Q_D(RAbstractSliderSpinBox);
return (double)d->value / d->factor;
}
void RDoubleSliderSpinBox::setValue(double value)
{
Q_D(RAbstractSliderSpinBox);
setInternalValue(d->value = qRound(value * d->factor));
update();
}
void RDoubleSliderSpinBox::setSingleStep(double value)
{
Q_D(RAbstractSliderSpinBox);
d->singleStep = value * d->factor;
}
QString RDoubleSliderSpinBox::valueString() const
{
const Q_D(RAbstractSliderSpinBox);
return QLocale::system().toString((double)d->value / d->factor, 'f', d->validator->decimals());
}
void RDoubleSliderSpinBox::setInternalValue(int val)
{
Q_D(RAbstractSliderSpinBox);
d->value = qBound(d->minimum, val, d->maximum);
emit(valueChanged(value()));
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.h
index d1a1377647..0e79f3fce9 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.h
@@ -1,183 +1,183 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2014-11-30
* @brief Save space slider widget
*
* @author Copyright (C) 2014 by Gilles Caulier
* <a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
* @author Copyright (C) 2010 by Justin Noel
* <a href="mailto:justin at ics dot com">justin at ics 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.
*
* ============================================================ */
#ifndef RSLIDERSPINBOX_H
#define RSLIDERSPINBOX_H
// Qt includes
#include <QWidget>
#include <QString>
#include <QRect>
#include <QStyleOptionSpinBox>
#include <QStyleOptionProgressBar>
namespace KDcrawIface
{
class RAbstractSliderSpinBoxPrivate;
class RSliderSpinBoxPrivate;
class RDoubleSliderSpinBoxPrivate;
/**
* TODO: when inactive, also show the progress bar part as inactive!
*/
class RAbstractSliderSpinBox : public QWidget
{
Q_OBJECT
Q_DISABLE_COPY(RAbstractSliderSpinBox)
Q_DECLARE_PRIVATE(RAbstractSliderSpinBox)
protected:
explicit RAbstractSliderSpinBox(QWidget* const parent, RAbstractSliderSpinBoxPrivate* const q);
public:
~RAbstractSliderSpinBox() override;
void showEdit();
void hideEdit();
void setSuffix(const QString& suffix);
void setExponentRatio(double dbl);
protected:
void paintEvent(QPaintEvent* e) override;
void mousePressEvent(QMouseEvent* e) override;
void mouseReleaseEvent(QMouseEvent* e) override;
void mouseMoveEvent(QMouseEvent* e) override;
void keyPressEvent(QKeyEvent* e) override;
void wheelEvent(QWheelEvent *) override;
bool eventFilter(QObject* recv, QEvent* e) override;
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
QStyleOptionSpinBox spinBoxOptions() const;
QStyleOptionProgressBar progressBarOptions() const;
QRect editRect(const QStyleOptionSpinBox& spinBoxOptions) const;
QRect progressRect(const QStyleOptionProgressBar& progressBarOptions) const;
QRect upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const;
QRect downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const;
int valueForX(int x, Qt::KeyboardModifiers modifiers = Qt::NoModifier) const;
virtual QString valueString() const = 0;
virtual void setInternalValue(int value) = 0;
protected Q_SLOTS:
void contextMenuEvent(QContextMenuEvent* event) override;
void editLostFocus();
protected:
RAbstractSliderSpinBoxPrivate* const d_ptr;
};
// ---------------------------------------------------------------------------------
class RSliderSpinBox : public RAbstractSliderSpinBox
{
Q_OBJECT
Q_DECLARE_PRIVATE(RSliderSpinBox)
Q_PROPERTY( int minimum READ minimum WRITE setMinimum )
Q_PROPERTY( int maximum READ maximum WRITE setMaximum )
public:
RSliderSpinBox(QWidget* const parent = 0);
~RSliderSpinBox() override;
void setRange(int minimum, int maximum);
int minimum() const;
void setMinimum(int minimum);
int maximum() const;
void setMaximum(int maximum);
int fastSliderStep() const;
void setFastSliderStep(int step);
int value() const;
void setValue(int value);
void setSingleStep(int value);
void setPageStep(int value);
Q_SIGNALS:
void valueChanged(int value);
protected:
QString valueString() const override;
void setInternalValue(int value) override;
};
// ---------------------------------------------------------------------------------
class RDoubleSliderSpinBox : public RAbstractSliderSpinBox
{
Q_OBJECT
Q_DECLARE_PRIVATE(RDoubleSliderSpinBox)
public:
RDoubleSliderSpinBox(QWidget* const parent = 0);
~RDoubleSliderSpinBox() override;
void setRange(double minimum, double maximum, int decimals = 0);
double minimum() const;
void setMinimum(double minimum);
double maximum() const;
void setMaximum(double maximum);
double fastSliderStep() const;
void setFastSliderStep(double step);
double value() const;
void setValue(double value);
void setSingleStep(double value);
Q_SIGNALS:
void valueChanged(double value);
protected:
QString valueString() const override;
void setInternalValue(int val) override;
};
} // namespace KDcrawIface
#endif // RSLIDERSPINBOX_H
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp
index 35e1cc1266..67c2daf946 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.cpp
@@ -1,618 +1,618 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2014-09-12
* @brief Simple helper widgets collection
*
* @author Copyright (C) 2014-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 "rwidgetutils.h"
// Qt includes
#include <QWidget>
#include <QByteArray>
#include <QBuffer>
#include <QImage>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QApplication>
#include <QDesktopWidget>
#include <QPushButton>
#include <QFileInfo>
#include <QPainter>
#include <QStandardPaths>
#include <QVector>
#include <QColorDialog>
#include <QStyleOptionButton>
#include <qdrawutil.h>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "libkdcraw_debug.h"
namespace KDcrawIface
{
RActiveLabel::RActiveLabel(const QUrl& url, const QString& imgPath, QWidget* const parent)
: QLabel(parent)
{
setMargin(0);
setScaledContents(false);
setOpenExternalLinks(true);
setTextFormat(Qt::RichText);
setFocusPolicy(Qt::NoFocus);
setTextInteractionFlags(Qt::LinksAccessibleByMouse);
setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
QImage img = QImage(imgPath);
updateData(url, img);
}
RActiveLabel::~RActiveLabel()
{
}
void RActiveLabel::updateData(const QUrl& url, const QImage& img)
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
img.save(&buffer, "PNG");
setText(QString::fromLatin1("<a href=\"%1\">%2</a>")
.arg(url.url())
.arg(QString::fromLatin1("<img src=\"data:image/png;base64,%1\">")
.arg(QString::fromLatin1(byteArray.toBase64().data()))));
}
// ------------------------------------------------------------------------------------
RLineWidget::RLineWidget(Qt::Orientation orientation, QWidget* const parent)
: QFrame(parent)
{
setLineWidth(1);
setMidLineWidth(0);
if (orientation == Qt::Vertical)
{
setFrameShape(QFrame::VLine);
setFrameShadow(QFrame::Sunken);
setMinimumSize(2, 0);
}
else
{
setFrameShape(QFrame::HLine);
setFrameShadow(QFrame::Sunken);
setMinimumSize(0, 2);
}
updateGeometry();
}
RLineWidget::~RLineWidget()
{
}
// ------------------------------------------------------------------------------------
RHBox::RHBox(QWidget* const parent)
: QFrame(parent)
{
QHBoxLayout* const layout = new QHBoxLayout(this);
layout->setSpacing(0);
layout->setMargin(0);
setLayout(layout);
}
RHBox::RHBox(bool /*vertical*/, QWidget* const parent)
: QFrame(parent)
{
QVBoxLayout* const layout = new QVBoxLayout(this);
layout->setSpacing(0);
layout->setMargin(0);
setLayout(layout);
}
RHBox::~RHBox()
{
}
void RHBox::childEvent(QChildEvent* e)
{
switch (e->type())
{
case QEvent::ChildAdded:
{
QChildEvent* const ce = static_cast<QChildEvent*>(e);
if (ce->child()->isWidgetType())
{
QWidget* const w = static_cast<QWidget*>(ce->child());
static_cast<QBoxLayout*>(layout())->addWidget(w);
}
break;
}
case QEvent::ChildRemoved:
{
QChildEvent* const ce = static_cast<QChildEvent*>(e);
if (ce->child()->isWidgetType())
{
QWidget* const w = static_cast<QWidget*>(ce->child());
static_cast<QBoxLayout*>(layout())->removeWidget(w);
}
break;
}
default:
break;
}
QFrame::childEvent(e);
}
QSize RHBox::sizeHint() const
{
RHBox* const b = const_cast<RHBox*>(this);
QApplication::sendPostedEvents(b, QEvent::ChildAdded);
return QFrame::sizeHint();
}
QSize RHBox::minimumSizeHint() const
{
RHBox* const b = const_cast<RHBox*>(this);
QApplication::sendPostedEvents(b, QEvent::ChildAdded );
return QFrame::minimumSizeHint();
}
void RHBox::setSpacing(int spacing)
{
layout()->setSpacing(spacing);
}
void RHBox::setMargin(int margin)
{
layout()->setMargin(margin);
}
void RHBox::setStretchFactor(QWidget* const widget, int stretch)
{
static_cast<QBoxLayout*>(layout())->setStretchFactor(widget, stretch);
}
// ------------------------------------------------------------------------------------
RVBox::RVBox(QWidget* const parent)
: RHBox(true, parent)
{
}
RVBox::~RVBox()
{
}
// ------------------------------------------------------------------------------------
class Q_DECL_HIDDEN RAdjustableLabel::Private
{
public:
Private()
{
emode = Qt::ElideMiddle;
}
QString ajdText;
Qt::TextElideMode emode;
};
RAdjustableLabel::RAdjustableLabel(QWidget* const parent)
: QLabel(parent),
d(new Private)
{
setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
}
RAdjustableLabel::~RAdjustableLabel()
{
delete d;
}
void RAdjustableLabel::resizeEvent(QResizeEvent*)
{
adjustTextToLabel();
}
QSize RAdjustableLabel::minimumSizeHint() const
{
QSize sh = QLabel::minimumSizeHint();
sh.setWidth(-1);
return sh;
}
QSize RAdjustableLabel::sizeHint() const
{
QFontMetrics fm(fontMetrics());
int maxW = QApplication::desktop()->screenGeometry(this).width() * 3 / 4;
int currentW = fm.width(d->ajdText);
return (QSize(currentW > maxW ? maxW : currentW, QLabel::sizeHint().height()));
}
void RAdjustableLabel::setAdjustedText(const QString& text)
{
d->ajdText = text;
if (d->ajdText.isNull())
QLabel::clear();
adjustTextToLabel();
}
QString RAdjustableLabel::adjustedText() const
{
return d->ajdText;
}
void RAdjustableLabel::setAlignment(Qt::Alignment alignment)
{
QString tmp(d->ajdText);
QLabel::setAlignment(alignment);
d->ajdText = tmp;
}
void RAdjustableLabel::setElideMode(Qt::TextElideMode mode)
{
d->emode = mode;
adjustTextToLabel();
}
void RAdjustableLabel::adjustTextToLabel()
{
QFontMetrics fm(fontMetrics());
QStringList adjustedLines;
int lblW = size().width();
bool adjusted = false;
Q_FOREACH(const QString& line, d->ajdText.split(QLatin1Char('\n')))
{
int lineW = fm.width(line);
if (lineW > lblW)
{
adjusted = true;
adjustedLines << fm.elidedText(line, d->emode, lblW);
}
else
{
adjustedLines << line;
}
}
if (adjusted)
{
QLabel::setText(adjustedLines.join(QStringLiteral("\n")));
setToolTip(d->ajdText);
}
else
{
QLabel::setText(d->ajdText);
setToolTip(QString());
}
}
// ------------------------------------------------------------------------------------
class Q_DECL_HIDDEN RFileSelector::Private
{
public:
Private()
{
edit = 0;
btn = 0;
fdMode = QFileDialog::ExistingFile;
fdOptions = QFileDialog::DontUseNativeDialog;
}
QLineEdit* edit;
QPushButton* btn;
QFileDialog::FileMode fdMode;
QString fdFilter;
QString fdTitle;
QFileDialog::Options fdOptions;
};
RFileSelector::RFileSelector(QWidget* const parent)
: RHBox(parent),
d(new Private)
{
d->edit = new QLineEdit(this);
d->btn = new QPushButton(i18n("Browse..."), this);
setStretchFactor(d->edit, 10);
connect(d->btn, SIGNAL(clicked()),
this, SLOT(slotBtnClicked()));
}
RFileSelector::~RFileSelector()
{
delete d;
}
QLineEdit* RFileSelector::lineEdit() const
{
return d->edit;
}
void RFileSelector::setFileDlgMode(QFileDialog::FileMode mode)
{
d->fdMode = mode;
}
void RFileSelector::setFileDlgFilter(const QString& filter)
{
d->fdFilter = filter;
}
void RFileSelector::setFileDlgTitle(const QString& title)
{
d->fdTitle = title;
}
void RFileSelector::setFileDlgOptions(QFileDialog::Options opts)
{
d->fdOptions = opts;
}
void RFileSelector::slotBtnClicked()
{
if (d->fdMode == QFileDialog::ExistingFiles)
{
qCDebug(LIBKDCRAW_LOG) << "Multiple selection is not supported";
return;
}
QFileDialog* const fileDlg = new QFileDialog(this);
fileDlg->setOptions(d->fdOptions);
fileDlg->setDirectory(QFileInfo(d->edit->text()).dir());
fileDlg->setFileMode(d->fdMode);
if (!d->fdFilter.isNull())
fileDlg->setNameFilter(d->fdFilter);
if (!d->fdTitle.isNull())
fileDlg->setWindowTitle(d->fdTitle);
connect(fileDlg, SIGNAL(urlSelected(QUrl)),
this, SIGNAL(signalUrlSelected(QUrl)));
emit signalOpenFileDialog();
if (fileDlg->exec() == QDialog::Accepted)
{
QStringList sel = fileDlg->selectedFiles();
if (!sel.isEmpty())
{
d->edit->setText(sel.first());
}
}
delete fileDlg;
}
// ---------------------------------------------------------------------------------------
WorkingPixmap::WorkingPixmap()
{
QPixmap pix(QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("libkdcraw/pics/process-working.png")));
QSize size(22, 22);
if (pix.isNull())
{
qCWarning(LIBKDCRAW_LOG) << "Invalid pixmap specified.";
return;
}
if (!size.isValid())
{
size = QSize(pix.width(), pix.width());
}
if (pix.width() % size.width() || pix.height() % size.height())
{
qCWarning(LIBKDCRAW_LOG) << "Invalid framesize.";
return;
}
const int rowCount = pix.height() / size.height();
const int colCount = pix.width() / size.width();
m_frames.resize(rowCount * colCount);
int pos = 0;
for (int row = 0; row < rowCount; ++row)
{
for (int col = 0; col < colCount; ++col)
{
QPixmap frm = pix.copy(col * size.width(), row * size.height(), size.width(), size.height());
m_frames[pos++] = frm;
}
}
}
WorkingPixmap::~WorkingPixmap()
{
}
bool WorkingPixmap::isEmpty() const
{
return m_frames.isEmpty();
}
QSize WorkingPixmap::frameSize() const
{
if (isEmpty())
{
qCWarning(LIBKDCRAW_LOG) << "No frame loaded.";
return QSize();
}
return m_frames[0].size();
}
int WorkingPixmap::frameCount() const
{
return m_frames.size();
}
QPixmap WorkingPixmap::frameAt(int index) const
{
if (isEmpty())
{
qCWarning(LIBKDCRAW_LOG) << "No frame loaded.";
return QPixmap();
}
return m_frames.at(index);
}
// ------------------------------------------------------------------------------------
class Q_DECL_HIDDEN RColorSelector::Private
{
public:
Private()
{
}
QColor color;
};
RColorSelector::RColorSelector(QWidget* const parent)
: QPushButton(parent),
d(new Private)
{
connect(this, SIGNAL(clicked()),
this, SLOT(slotBtnClicked()));
}
RColorSelector::~RColorSelector()
{
delete d;
}
void RColorSelector::setColor(const QColor& color)
{
if (color.isValid())
{
d->color = color;
update();
}
}
QColor RColorSelector::color() const
{
return d->color;
}
void RColorSelector::slotBtnClicked()
{
QColor color = QColorDialog::getColor(d->color);
if (color.isValid())
{
setColor(color);
emit signalColorSelected(color);
}
}
void RColorSelector::paintEvent(QPaintEvent*)
{
QPainter painter(this);
QStyle* const style = QWidget::style();
QStyleOptionButton opt;
opt.initFrom(this);
opt.state |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised;
opt.features = QStyleOptionButton::None;
opt.icon = QIcon();
opt.text.clear();
style->drawControl(QStyle::CE_PushButtonBevel, &opt, &painter, this);
QRect labelRect = style->subElementRect(QStyle::SE_PushButtonContents, &opt, this);
int shift = style->pixelMetric(QStyle::PM_ButtonMargin, &opt, this) / 2;
labelRect.adjust(shift, shift, -shift, -shift);
int x, y, w, h;
labelRect.getRect(&x, &y, &w, &h);
if (isChecked() || isDown())
{
x += style->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &opt, this);
y += style->pixelMetric(QStyle::PM_ButtonShiftVertical, &opt, this);
}
QColor fillCol = isEnabled() ? d->color : palette().color(backgroundRole());
qDrawShadePanel(&painter, x, y, w, h, palette(), true, 1, 0);
if (fillCol.isValid())
{
const QRect rect(x + 1, y + 1, w - 2, h - 2);
if (fillCol.alpha() < 255)
{
QPixmap chessboardPattern(16, 16);
QPainter patternPainter(&chessboardPattern);
patternPainter.fillRect(0, 0, 8, 8, Qt::black);
patternPainter.fillRect(8, 8, 8, 8, Qt::black);
patternPainter.fillRect(0, 8, 8, 8, Qt::white);
patternPainter.fillRect(8, 0, 8, 8, Qt::white);
patternPainter.end();
painter.fillRect(rect, QBrush(chessboardPattern));
}
painter.fillRect(rect, fillCol);
}
if (hasFocus())
{
QRect focusRect = style->subElementRect(QStyle::SE_PushButtonFocusRect, &opt, this);
QStyleOptionFocusRect focusOpt;
focusOpt.init(this);
focusOpt.rect = focusRect;
focusOpt.backgroundColor = palette().background().color();
style->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter, this);
}
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.h b/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.h
index d17b0690e5..14f8861350 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rwidgetutils.h
@@ -1,256 +1,256 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2014-09-12
* @brief Simple helpher widgets collection
*
* @author Copyright (C) 2014-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.
*
* ============================================================ */
#ifndef RWIDGETUTILS_H
#define RWIDGETUTILS_H
// Qt includes
#include <QLabel>
#include <QFileInfo>
#include <QString>
#include <QFrame>
#include <QLineEdit>
#include <QSize>
#include <QPixmap>
#include <QFileDialog>
#include <QColor>
#include <QPushButton>
// Local includes
namespace KDcrawIface
{
/** A widget to host an image into a label with an active url which can be
* open to default web browser using simple mouse click.
*/
class RActiveLabel : public QLabel
{
Q_OBJECT
public:
explicit RActiveLabel(const QUrl& url=QUrl(), const QString& imgPath=QString(), QWidget* const parent=0);
~RActiveLabel() override;
void updateData(const QUrl& url, const QImage& img);
};
// ------------------------------------------------------------------------------------
/**
* A widget to show an horizontal or vertical line separator
**/
class RLineWidget : public QFrame
{
Q_OBJECT
public:
explicit RLineWidget(Qt::Orientation orientation, QWidget* const parent=0);
~RLineWidget() override;
};
// ------------------------------------------------------------------------------------
/** An Horizontal widget to host children widgets
*/
class RHBox : public QFrame
{
Q_OBJECT
Q_DISABLE_COPY(RHBox)
public:
explicit RHBox(QWidget* const parent=0);
~RHBox() override;
void setMargin(int margin);
void setSpacing(int space);
void setStretchFactor(QWidget* const widget, int stretch);
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
protected:
RHBox(bool vertical, QWidget* const parent);
void childEvent(QChildEvent* e) override;
};
// ------------------------------------------------------------------------------------
/** A Vertical widget to host children widgets
*/
class RVBox : public RHBox
{
Q_OBJECT
Q_DISABLE_COPY(RVBox)
public:
explicit RVBox(QWidget* const parent=0);
~RVBox() override;
};
// ------------------------------------------------------------------------------------
/** A label to show text adjusted to widget size
*/
class RAdjustableLabel : public QLabel
{
Q_OBJECT
public:
explicit RAdjustableLabel(QWidget* const parent=0);
~RAdjustableLabel() override;
QSize minimumSizeHint() const override;
QSize sizeHint() const override;
void setAlignment(Qt::Alignment align);
void setElideMode(Qt::TextElideMode mode);
QString adjustedText() const;
public Q_SLOTS:
void setAdjustedText(const QString& text=QString());
private:
void resizeEvent(QResizeEvent*) override;
void adjustTextToLabel();
// Disabled methods from QLabel
QString text() const { return QString(); }; // Use adjustedText() instead.
void setText(const QString&) {}; // Use setAdjustedText(text) instead.
void clear() {}; // Use setdjustedText(QString()) instead.
private:
class Private;
Private* const d;
};
// ------------------------------------------------------------------------------------
/** A widget to chosse a single local file or path.
* Use line edit and file dialog properties to customize operation modes.
*/
class RFileSelector : public RHBox
{
Q_OBJECT
public:
explicit RFileSelector(QWidget* const parent=0);
~RFileSelector() override;
QLineEdit* lineEdit() const;
void setFileDlgMode(QFileDialog::FileMode mode);
void setFileDlgFilter(const QString& filter);
void setFileDlgTitle(const QString& title);
void setFileDlgOptions(QFileDialog::Options opts);
Q_SIGNALS:
void signalOpenFileDialog();
void signalUrlSelected(const QUrl&);
private Q_SLOTS:
void slotBtnClicked();
private:
class Private;
Private* const d;
};
// --------------------------------------------------------------------------------------
/** A widget to draw progress wheel indicator over thumbnails.
*/
class WorkingPixmap
{
public:
explicit WorkingPixmap();
~WorkingPixmap();
bool isEmpty() const;
QSize frameSize() const;
int frameCount() const;
QPixmap frameAt(int index) const;
private:
QVector<QPixmap> m_frames;
};
// ------------------------------------------------------------------------------------
/** A widget to chosse a color from a palette.
*/
class RColorSelector : public QPushButton
{
Q_OBJECT
public:
explicit RColorSelector(QWidget* const parent=0);
~RColorSelector() override;
void setColor(const QColor& color);
QColor color() const;
Q_SIGNALS:
void signalColorSelected(const QColor&);
private Q_SLOTS:
void slotBtnClicked();
private:
void paintEvent(QPaintEvent*) override;
private:
class Private;
Private* const d;
};
} // namespace KDcrawIface
#endif // RWIDGETUTILS_H
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp
index 2ff49314fc..ffa40dee49 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp
@@ -1,196 +1,195 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2008-08-21
* @brief a combo box with a width not depending of text
* content size
*
* @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) 2008 by Andi Clemens
* <a href="mailto:andi dot clemens at googlemail dot com">andi dot clemens at googlemail dot com</a>
* @author Copyright (C) 2005 by Tom Albers
* <a href="mailto:tomalbers at kde dot nl">tomalbers at kde dot nl</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 "squeezedcombobox.h"
// Qt includes
#include <QComboBox>
#include <QTimer>
#include <QStyle>
#include <QApplication>
#include <QResizeEvent>
namespace KDcrawIface
{
class Q_DECL_HIDDEN SqueezedComboBox::Private
{
public:
Private()
{
timer = 0;
}
QMap<int, QString> originalItems;
QTimer* timer;
};
SqueezedComboBox::SqueezedComboBox(QWidget* const parent, const char* name)
: QComboBox(parent), d(new Private)
{
setObjectName(name);
setMinimumWidth(100);
d->timer = new QTimer(this);
d->timer->setSingleShot(true);
connect(d->timer, &QTimer::timeout, this, &SqueezedComboBox::slotTimeOut);
connect(this, static_cast<void (SqueezedComboBox::*)(int)>(&SqueezedComboBox::activated), this, &SqueezedComboBox::slotUpdateToolTip);
}
SqueezedComboBox::~SqueezedComboBox()
{
d->originalItems.clear();
delete d->timer;
delete d;
}
bool SqueezedComboBox::contains(const QString& text) const
{
if (text.isEmpty())
return false;
for (QMap<int, QString>::const_iterator it = d->originalItems.constBegin() ; it != d->originalItems.constEnd(); ++it)
{
if (it.value() == text)
return true;
}
return false;
}
QSize SqueezedComboBox::sizeHint() const
{
ensurePolished();
QFontMetrics fm = fontMetrics();
int maxW = count() ? 18 : 7 * fm.width(QChar('x')) + 18;
int maxH = qMax( fm.lineSpacing(), 14 ) + 2;
QStyleOptionComboBox options;
options.initFrom(this);
- return style()->sizeFromContents(QStyle::CT_ComboBox, &options,
- QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut());
+ return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this);
}
void SqueezedComboBox::insertSqueezedItem(const QString& newItem, int index,
const QVariant& userData)
{
d->originalItems[index] = newItem;
QComboBox::insertItem(index, squeezeText(newItem), userData);
// if this is the first item, set the tooltip.
if (index == 0)
slotUpdateToolTip(0);
}
void SqueezedComboBox::insertSqueezedList(const QStringList& newItems, int index)
{
for(QStringList::const_iterator it = newItems.constBegin() ; it != newItems.constEnd() ; ++it)
{
insertSqueezedItem(*it, index);
index++;
}
}
void SqueezedComboBox::addSqueezedItem(const QString& newItem,
const QVariant& userData)
{
insertSqueezedItem(newItem, count(), userData);
}
void SqueezedComboBox::setCurrent(const QString& itemText)
{
QString squeezedText = squeezeText(itemText);
qint32 itemIndex = findText(squeezedText);
if (itemIndex >= 0)
setCurrentIndex(itemIndex);
}
void SqueezedComboBox::resizeEvent(QResizeEvent *)
{
d->timer->start(200);
}
void SqueezedComboBox::slotTimeOut()
{
for (QMap<int, QString>::iterator it = d->originalItems.begin() ; it != d->originalItems.end(); ++it)
{
setItemText( it.key(), squeezeText( it.value() ) );
}
}
QString SqueezedComboBox::squeezeText(const QString& original) const
{
// not the complete widgetSize is usable. Need to compensate for that.
int widgetSize = width()-30;
QFontMetrics fm( fontMetrics() );
// If we can fit the full text, return that.
if (fm.width(original) < widgetSize)
return(original);
// We need to squeeze.
QString sqItem = original; // prevent empty return value;
widgetSize = widgetSize-fm.width("...");
for (int i = 0 ; i != original.length(); ++i)
{
if ((int)fm.width(original.right(i)) > widgetSize)
{
sqItem = QString(original.left(i) + "...");
break;
}
}
return sqItem;
}
void SqueezedComboBox::slotUpdateToolTip(int index)
{
setToolTip(d->originalItems[index]);
}
QString SqueezedComboBox::currentUnsqueezedText() const
{
int curItem = currentIndex();
return d->originalItems[curItem];
}
QString SqueezedComboBox::item(int index) const
{
return d->originalItems[index];
}
} // namespace KDcrawIface
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h
index 7a482e52c2..906b32b664 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.h
@@ -1,165 +1,165 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2008-08-21
* @brief a combo box with a width not depending of text
* content size
*
* @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) 2008 by Andi Clemens
* <a href="mailto:andi dot clemens at googlemail dot com">andi dot clemens at googlemail dot com</a>
* @author Copyright (C) 2005 by Tom Albers
* <a href="mailto:tomalbers at kde dot nl">tomalbers at kde dot nl</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.
*
* ============================================================ */
#ifndef SQUEEZEDCOMBOBOX_H
#define SQUEEZEDCOMBOBOX_H
// Qt includes
#include <QComboBox>
// Local includes
namespace KDcrawIface
{
/** @class SqueezedComboBox
*
* This widget is a QComboBox, but then a little bit
* different. It only shows the right part of the items
* depending on de size of the widget. When it is not
* possible to show the complete item, it will be shortened
* and "..." will be prepended.
*/
class SqueezedComboBox : public QComboBox
{
Q_OBJECT
public:
/**
* Constructor
* @param parent parent widget
* @param name name to give to the widget
*/
explicit SqueezedComboBox(QWidget* const parent = 0, const char* name = 0 );
/**
* destructor
*/
~SqueezedComboBox() override;
/**
*
* Returns true if the combobox contains the original (not-squeezed)
* version of text.
* @param text the original (not-squeezed) text to check for
*/
bool contains(const QString& text) const;
/**
* This inserts a item to the list. See QComboBox::insertItem()
* for details. Please do not use QComboBox::insertItem() to this
* widget, as that will fail.
* @param newItem the original (long version) of the item which needs
* to be added to the combobox
* @param index the position in the widget.
* @param userData custom meta-data assigned to new item.
*/
void insertSqueezedItem(const QString& newItem, int index,
const QVariant& userData=QVariant());
/**
* This inserts items to the list. See QComboBox::insertItems()
* for details. Please do not use QComboBox:: insertItems() to this
* widget, as that will fail.
* @param newItems the originals (long version) of the items which needs
* to be added to the combobox
* @param index the position in the widget.
*/
void insertSqueezedList(const QStringList& newItems, int index);
/**
* Append an item.
* @param newItem the original (long version) of the item which needs
* to be added to the combobox
* @param userData custom meta-data assigned to new item.
*/
void addSqueezedItem(const QString& newItem,
const QVariant& userData=QVariant());
/**
* Set the current item to the one matching the given text.
*
* @param itemText the original (long version) of the item text
*/
void setCurrent(const QString& itemText);
/**
* This method returns the full text (not squeezed) of the currently
* highlighted item.
* @return full text of the highlighted item
*/
QString currentUnsqueezedText() const;
/**
* This method returns the full text (not squeezed) for the index.
* @param index the position in the widget.
* @return full text of the item
*/
QString item(int index) const;
/**
* Sets the sizeHint() of this widget.
*/
QSize sizeHint() const override;
private Q_SLOTS:
void slotTimeOut();
void slotUpdateToolTip(int index);
private:
void resizeEvent(QResizeEvent*) override;
QString squeezeText(const QString& original) const;
// Prevent these from being used.
QString currentText() const;
void setCurrentText(const QString& itemText);
void insertItem(const QString& text);
void insertItem(qint32 index, const QString& text);
void insertItem(int index, const QIcon& icon, const QString& text, const QVariant& userData=QVariant());
void insertItems(int index, const QStringList& list);
void addItem(const QString& text);
void addItem(const QIcon& icon, const QString& text, const QVariant& userData=QVariant());
void addItems(const QStringList& texts);
QString itemText(int index) const;
private:
class Private;
Private* const d;
};
} // namespace KDcrawIface
#endif // SQUEEZEDCOMBOBOX_H
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/libinfo.cpp b/plugins/impex/raw/3rdparty/libkdcraw/tests/libinfo.cpp
index 84c194143a..65d4267c19 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/tests/libinfo.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/libinfo.cpp
@@ -1,49 +1,49 @@
/** ===========================================================
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2013-09-07
* @brief a command line tool to show libkdcraw info
*
* @author Copyright (C) 2013 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.
*
* ============================================================ */
// Qt includes
#include <QString>
#include <QDebug>
// Local includes
#include <KDCRAW/KDcraw>
using namespace KDcrawIface;
int main(int /*argc*/, char** /*argv*/)
{
qDebug() << "Libkdcraw version : " << KDcraw::version(),
qDebug() << "Libraw version : " << KDcraw::librawVersion();
qDebug() << "Use OpenMP : " << KDcraw::librawUseGomp();
qDebug() << "Use RawSpeed : " << KDcraw::librawUseRawSpeed();
qDebug() << "Use GPL2 Pack : " << KDcraw::librawUseGPL2DemosaicPack();
qDebug() << "Use GPL3 Pack : " << KDcraw::librawUseGPL3DemosaicPack();
qDebug() << "Raw files list : " << KDcraw::rawFilesList();
qDebug() << "Raw files version : " << KDcraw::rawFilesVersion();
qDebug() << "Supported camera : " << KDcraw::supportedCamera();
return 0;
}
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.cpp b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.cpp
index 48fd250504..6a6e1327f9 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.cpp
@@ -1,171 +1,171 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date : 2014-10-17
* @brief : a class to manage Raw to Png conversion using threads
*
* @author Copyright (C) 2011-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) 2014 by Veaceslav Munteanu
* <a href="mailto:veaceslav dot munteanu90 at gmail dot com">veaceslav dot munteanu90 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 "actionthread.h"
// Qt includes
#include <QFileInfo>
#include <QImage>
#include <QDebug>
// Local includes
#include "kdcraw.h"
#include "ractionjob.h"
class Task : public RActionJob
{
public:
Task()
: RActionJob()
{
}
RawDecodingSettings settings;
QString errString;
QUrl fileUrl;
protected:
void run()
{
emit signalStarted();
QImage image;
if (m_cancel) return;
emit signalProgress(20);
KDcraw rawProcessor;
if (m_cancel) return;
emit signalProgress(30);
QFileInfo input(fileUrl.toLocalFile());
QString fullFilePath(input.baseName() + QString::fromLatin1(".full.png"));
QFileInfo fullOutput(fullFilePath);
if (m_cancel) return;
emit signalProgress(40);
if (!rawProcessor.loadFullImage(image, fileUrl.toLocalFile(), settings))
{
errString = QString::fromLatin1("raw2png: Loading full RAW image failed. Aborted...");
return;
}
if (m_cancel) return;
emit signalProgress(60);
qDebug() << "raw2png: Saving full RAW image to "
<< fullOutput.fileName() << " size ("
<< image.width() << "x" << image.height()
<< ")";
if (m_cancel) return;
emit signalProgress(80);
image.save(fullFilePath, "PNG");
emit signalDone();
}
};
// ----------------------------------------------------------------------------------------------------
ActionThread::ActionThread(QObject* const parent)
: RActionThreadBase(parent)
{
}
ActionThread::~ActionThread()
{
}
void ActionThread::convertRAWtoPNG(const QList<QUrl>& list, const RawDecodingSettings& settings, int priority)
{
RJobCollection collection;
Q_FOREACH (const QUrl& url, list)
{
Task* const job = new Task();
job->fileUrl = url;
job->settings = settings;
connect(job, SIGNAL(signalStarted()),
this, SLOT(slotJobStarted()));
connect(job, SIGNAL(signalProgress(int)),
this, SLOT(slotJobProgress(int)));
connect(job, SIGNAL(signalDone()),
this, SLOT(slotJobDone()));
collection.insert(job, priority);
qDebug() << "Appending file to process " << url;
}
appendJobs(collection);
}
void ActionThread::slotJobDone()
{
Task* const task = dynamic_cast<Task*>(sender());
if (!task) return;
if (task->errString.isEmpty())
{
emit finished(task->fileUrl);
}
else
{
emit failed(task->fileUrl, task->errString);
}
}
void ActionThread::slotJobProgress(int p)
{
Task* const task = dynamic_cast<Task*>(sender());
if (!task) return;
emit progress(task->fileUrl, p);
}
void ActionThread::slotJobStarted()
{
Task* const task = dynamic_cast<Task*>(sender());
if (!task) return;
emit starting(task->fileUrl);
-}
\ No newline at end of file
+}
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.h b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.h
index 526815c2e9..f0a7de9654 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/actionthread.h
@@ -1,66 +1,66 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date : 2014-10-17
* @brief : a class to manage Raw to Png conversion using threads
*
* @author Copyright (C) 2011-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) 2014 by Veaceslav Munteanu
* <a href="mailto:veaceslav dot munteanu90 at gmail dot com">veaceslav dot munteanu90 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.
*
* ============================================================ */
#ifndef ACTIONTHREAD_H
#define ACTIONTHREAD_H
// Qt includes
#include <QFileInfo>
// Libkdcraw includes
#include "ractionthreadbase.h"
#include "rawdecodingsettings.h"
using namespace KDcrawIface;
class ActionThread : public RActionThreadBase
{
Q_OBJECT
public:
ActionThread(QObject* const parent);
~ActionThread();
void convertRAWtoPNG(const QList<QUrl>& list, const RawDecodingSettings& settings, int priority=0);
Q_SIGNALS:
void starting(const QUrl& url);
void finished(const QUrl& url);
void failed(const QUrl& url, const QString& err);
void progress(const QUrl& url, int percent);
private Q_SLOTS:
void slotJobDone();
void slotJobProgress(int);
void slotJobStarted();
};
#endif /* ACTIONTHREAD_H */
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/main.cpp b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/main.cpp
index 38d777a0e1..2b44cef00f 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/main.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/main.cpp
@@ -1,78 +1,78 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date : 2011-12-28
* @brief : test for implementation of threadWeaver api
*
* @author Copyright (C) 2011-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) 2014 by Veaceslav Munteanu
* <a href="mailto:veaceslav dot munteanu90 at gmail dot com">veaceslav dot munteanu90 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.
*
* ============================================================ */
// Qt includes
#include <QString>
#include <QStringList>
#include <QApplication>
#include <QStandardPaths>
#include <QFileInfo>
#include <QDebug>
#include <QFileDialog>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "rawfiles.h"
#include "processordlg.h"
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QList<QUrl> list;
if (argc <= 1)
{
QString filter = i18n("Raw Files") + QString::fromLatin1(" (%1)").arg(QString::fromLatin1(raw_file_extentions));
qDebug() << filter;
QStringList files = QFileDialog::getOpenFileNames(0, i18n("Select RAW files to process"),
QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).first(),
filter);
Q_FOREACH (QString f, files)
list.append(QUrl::fromLocalFile(f));
}
else
{
for (int i = 1 ; i < argc ; i++)
list.append(QUrl::fromLocalFile(QString::fromLocal8Bit(argv[i])));
}
if (!list.isEmpty())
{
ProcessorDlg* const dlg = new ProcessorDlg(list);
dlg->show();
app.exec();
}
return 0;
}
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.cpp b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.cpp
index dcbb7fbb79..57fa8471e9 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.cpp
@@ -1,280 +1,280 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date : 2014-10-17
* @brief : test for implementation of threadWeaver api
*
* @author Copyright (C) 2011-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) 2014 by Veaceslav Munteanu
* <a href="mailto:veaceslav dot munteanu90 at gmail dot com">veaceslav dot munteanu90 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 "processordlg.h"
// Qt includes
#include <QList>
#include <QCoreApplication>
#include <QVBoxLayout>
#include <QLabel>
#include <QProgressBar>
#include <QDialog>
#include <QThreadPool>
#include <QFileInfo>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QDebug>
#include <QHBoxLayout>
#include <QScrollArea>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "actionthread.h"
#include "dcrawsettingswidget.h"
#include "rnuminput.h"
class ProcessorDlg::Private
{
public:
Private()
{
count = 0;
page = 0;
items = 0;
buttons = 0;
progressView = 0;
usedCore = 0;
thread = 0;
settings = 0;
}
int count;
QWidget* page;
QLabel* items;
QDialogButtonBox* buttons;
QScrollArea* progressView;
QList<QUrl> list;
DcrawSettingsWidget* settings;
RIntNumInput* usedCore;
ActionThread* thread;
};
ProcessorDlg::ProcessorDlg(const QList<QUrl>& list)
: QDialog(0), d(new Private)
{
setModal(false);
setWindowTitle(i18n("Convert RAW files To PNG"));
d->buttons = new QDialogButtonBox(QDialogButtonBox::Apply | QDialogButtonBox::Close, this);
d->thread = new ActionThread(this);
d->list = list;
d->count = d->list.count();
qDebug() << d->list;
d->page = new QWidget(this);
QVBoxLayout* const vbx = new QVBoxLayout(this);
vbx->addWidget(d->page);
vbx->addWidget(d->buttons);
setLayout(vbx);
int cpu = d->thread->maximumNumberOfThreads();
QGridLayout* const grid = new QGridLayout(d->page);
QLabel* const pid = new QLabel(i18n("PID: %1", QCoreApplication::applicationPid()), this);
QLabel* const core = new QLabel(i18n("CPU Cores: %1", cpu), this);
QWidget* const hbox = new QWidget(this);
d->items = new QLabel(this);
QHBoxLayout* const hlay = new QHBoxLayout(hbox);
QLabel* const coresLabel = new QLabel(i18n("Cores to use: "), this);
d->usedCore = new RIntNumInput(this);
d->usedCore->setRange(1, cpu, 1);
d->usedCore->setDefaultValue(cpu);
hlay->addWidget(coresLabel);
hlay->addWidget(d->usedCore);
hlay->setContentsMargins(QMargins());
d->progressView = new QScrollArea(this);
QWidget* const progressbox = new QWidget(d->progressView->viewport());
QVBoxLayout* const progressLay = new QVBoxLayout(progressbox);
d->progressView->setWidget(progressbox);
d->progressView->setWidgetResizable(true);
d->settings = new DcrawSettingsWidget(this);
grid->addWidget(pid, 0, 0, 1, 1);
grid->addWidget(core, 1, 0, 1, 1);
grid->addWidget(hbox, 2, 0, 1, 1);
grid->addWidget(d->items, 3, 0, 1, 1);
grid->addWidget(d->progressView, 4, 0, 1, 1);
grid->addWidget(d->settings, 0, 1, 5, 1);
Q_FOREACH (const QUrl& url, d->list)
{
QProgressBar* const bar = new QProgressBar(progressbox);
QString file = url.toLocalFile();
QFileInfo fi(file);
bar->setMaximum(100);
bar->setMinimum(0);
bar->setValue(100);
bar->setObjectName(file);
bar->setFormat(fi.fileName());
progressLay->addWidget(bar);
}
progressLay->addStretch();
QPushButton* const applyBtn = d->buttons->button(QDialogButtonBox::Apply);
QPushButton* const cancelBtn = d->buttons->button(QDialogButtonBox::Close);
connect(applyBtn, SIGNAL(clicked()),
this, SLOT(slotStart()));
connect(cancelBtn, SIGNAL(clicked()),
this, SLOT(slotStop()));
connect(d->thread, SIGNAL(starting(QUrl)),
this, SLOT(slotStarting(QUrl)));
connect(d->thread, SIGNAL(finished(QUrl)),
this, SLOT(slotFinished(QUrl)));
connect(d->thread, SIGNAL(failed(QUrl,QString)),
this, SLOT(slotFailed(QUrl,QString)));
connect(d->thread, SIGNAL(progress(QUrl,int)),
this, SLOT(slotProgress(QUrl,int)));
updateCount();
resize(500, 400);
}
ProcessorDlg::~ProcessorDlg()
{
delete d;
}
void ProcessorDlg::updateCount()
{
d->items->setText(i18n("Files to process : %1", d->count));
}
void ProcessorDlg::slotStart()
{
if (d->list.isEmpty()) return;
d->buttons->button(QDialogButtonBox::Apply)->setDisabled(true);
d->usedCore->setDisabled(true);
d->settings->setDisabled(true);
d->thread->setMaximumNumberOfThreads(d->usedCore->value());
d->thread->convertRAWtoPNG(d->list, d->settings->settings());
d->thread->start();
}
void ProcessorDlg::slotStop()
{
d->thread->cancel();
reject();
}
QProgressBar* ProcessorDlg::findProgressBar(const QUrl& url) const
{
QList<QProgressBar*> bars = findChildren<QProgressBar*>();
Q_FOREACH (QProgressBar* const b, bars)
{
if (b->objectName() == url.toLocalFile())
{
return b;
}
}
qWarning() << "Cannot found relevant progress bar for " << url.toLocalFile();
return 0;
}
void ProcessorDlg::slotStarting(const QUrl& url)
{
qDebug() << "Start to process item " << url.toLocalFile();
QProgressBar* const b = findProgressBar(url);
if (b)
{
d->progressView->ensureWidgetVisible(b);
b->setMinimum(0);
b->setMaximum(100);
b->setValue(0);
}
}
void ProcessorDlg::slotProgress(const QUrl& url,int p)
{
qDebug() << "Processing item " << url.toLocalFile() << " : " << p << " %";;
QProgressBar* const b = findProgressBar(url);
if (b)
{
b->setMinimum(0);
b->setMaximum(100);
b->setValue(p);
}
}
void ProcessorDlg::slotFinished(const QUrl& url)
{
qDebug() << "Completed item " << url.toLocalFile();
QProgressBar* const b = findProgressBar(url);
if (b)
{
b->setMinimum(0);
b->setMaximum(100);
b->setValue(100);
b->setFormat(i18n("Done"));
d->count--;
updateCount();
}
}
void ProcessorDlg::slotFailed(const QUrl& url, const QString& err)
{
qDebug() << "Failed to complete item " << url.toLocalFile();
QProgressBar* const b = findProgressBar(url);
if (b)
{
b->setMinimum(0);
b->setMaximum(100);
b->setValue(100);
b->setFormat(err);
d->count--;
updateCount();
}
}
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.h b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.h
index eca8578134..ae37ef5049 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.h
+++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/multithreading/processordlg.h
@@ -1,67 +1,67 @@
/** ===========================================================
* @file
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date : 2014-10-17
* @brief : test for implementation of threadWeaver api
*
* @author Copyright (C) 2011-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) 2014 by Veaceslav Munteanu
* <a href="mailto:veaceslav dot munteanu90 at gmail dot com">veaceslav dot munteanu90 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.
*
* ============================================================ */
#ifndef PROCESSOR_DLG_H
#define PROCESSOR_DLG_H
// KDE includes
#include <QList>
#include <QFileInfo>
#include <QDialog>
class QProgressBar;
class ProcessorDlg : public QDialog
{
Q_OBJECT
public:
ProcessorDlg(const QList<QUrl> &list);
~ProcessorDlg();
private :
QProgressBar* findProgressBar(const QUrl& url) const;
void updateCount();
private Q_SLOTS:
void slotStart();
void slotStop();
void slotStarting(const QUrl&);
void slotProgress(const QUrl&, int);
void slotFinished(const QUrl&);
void slotFailed(const QUrl&, const QString&);
private:
class Private;
Private* const d;
};
#endif // PROCESSOR_DLG_H
diff --git a/plugins/impex/raw/3rdparty/libkdcraw/tests/raw2png.cpp b/plugins/impex/raw/3rdparty/libkdcraw/tests/raw2png.cpp
index a182950681..827f43d35a 100644
--- a/plugins/impex/raw/3rdparty/libkdcraw/tests/raw2png.cpp
+++ b/plugins/impex/raw/3rdparty/libkdcraw/tests/raw2png.cpp
@@ -1,138 +1,138 @@
/** ===========================================================
*
* This file is a part of digiKam project
- * <a href="http://www.digikam.org">http://www.digikam.org</a>
+ * <a href="https://www.digikam.org">https://www.digikam.org</a>
*
* @date 2008-15-09
* @brief a command line tool to convert RAW file to PNG
*
* @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.
*
* ============================================================ */
// Qt includes
#include <QString>
#include <QFile>
#include <QFileInfo>
#include <QDebug>
// Local includes
#include <KDCRAW/KDcraw>
#include <KDCRAW/RawDecodingSettings>
using namespace KDcrawIface;
int main(int argc, char** argv)
{
if(argc != 2)
{
qDebug() << "raw2png - RAW Camera Image to PNG Converter";
qDebug() << "Usage: <rawfile>";
return -1;
}
QString filePath = QString::fromLatin1(argv[1]);
QFileInfo input(filePath);
QString previewFilePath(input.baseName() + QString::QString::fromLatin1(".preview.png"));
QFileInfo previewOutput(previewFilePath);
QString halfFilePath(input.baseName() + QString::fromLatin1(".half.png"));
QFileInfo halfOutput(halfFilePath);
QString fullFilePath(input.baseName() + QString::fromLatin1(".full.png"));
QFileInfo fullOutput(fullFilePath);
QImage image;
DcrawInfoContainer identify;
// -----------------------------------------------------------
qDebug() << "raw2png: Identify RAW image from " << input.fileName();
KDcraw rawProcessor;
if (!rawProcessor.rawFileIdentify(identify, filePath))
{
qDebug() << "raw2png: Idendify RAW image failed. Aborted...";
return -1;
}
int width = identify.imageSize.width();
int height = identify.imageSize.height();
qDebug() << "raw2png: Raw image info:";
qDebug() << "--- Date: " << identify.dateTime.toString(Qt::ISODate);
qDebug() << "--- Make: " << identify.make;
qDebug() << "--- Model: " << identify.model;
qDebug() << "--- Size: " << width << "x" << height;
qDebug() << "--- Filter: " << identify.filterPattern;
qDebug() << "--- Colors: " << identify.rawColors;
// -----------------------------------------------------------
qDebug() << "raw2png: Loading RAW image preview";
if (!rawProcessor.loadRawPreview(image, filePath))
{
qDebug() << "raw2png: Loading RAW image preview failed. Aborted...";
return -1;
}
qDebug() << "raw2png: Saving preview image to "
<< previewOutput.fileName() << " size ("
<< image.width() << "x" << image.height()
<< ")";
image.save(previewFilePath, "PNG");
// -----------------------------------------------------------
qDebug() << "raw2png: Loading half RAW image";
image = QImage();
if (!rawProcessor.loadHalfPreview(image, filePath))
{
qDebug() << "raw2png: Loading half RAW image failed. Aborted...";
return -1;
}
qDebug() << "raw2png: Saving half image to "
<< halfOutput.fileName() << " size ("
<< image.width() << "x" << image.height()
<< ")";
image.save(halfFilePath, "PNG");
// -----------------------------------------------------------
qDebug() << "raw2png: Loading full RAW image";
image = QImage();
RawDecodingSettings settings;
settings.halfSizeColorImage = false;
settings.sixteenBitsImage = false;
settings.RGBInterpolate4Colors = false;
settings.RAWQuality = RawDecodingSettings::BILINEAR;
if (!rawProcessor.loadFullImage(image, filePath, settings))
{
qDebug() << "raw2png: Loading full RAW image failed. Aborted...";
return -1;
}
qDebug() << "raw2png: Saving full RAW image to "
<< fullOutput.fileName() << " size ("
<< image.width() << "x" << image.height()
<< ")";
image.save(fullFilePath, "PNG");
return 0;
}
diff --git a/plugins/impex/svg/kis_svg_import.cc b/plugins/impex/svg/kis_svg_import.cc
index bf41af28ca..218d96efd6 100644
--- a/plugins/impex/svg/kis_svg_import.cc
+++ b/plugins/impex/svg/kis_svg_import.cc
@@ -1,103 +1,103 @@
/*
* 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_svg_import.h"
#include <kpluginfactory.h>
#include <QFileInfo>
#include "kis_config.h"
#include <QInputDialog>
#include <KisDocument.h>
#include <kis_image.h>
#include <SvgParser.h>
#include <KoColorSpaceRegistry.h>
#include "kis_shape_layer.h"
#include <KoShapeControllerBase.h>
K_PLUGIN_FACTORY_WITH_JSON(SVGImportFactory, "krita_svg_import.json", registerPlugin<KisSVGImport>();)
KisSVGImport::KisSVGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisSVGImport::~KisSVGImport()
{
}
KisImportExportErrorCode KisSVGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
Q_UNUSED(configuration);
KisDocument * doc = document;
const QString baseXmlDir = QFileInfo(filename()).canonicalPath();
KisConfig cfg(false);
qreal resolutionPPI = cfg.preferredVectorImportResolutionPPI(true);
if (!batchMode()) {
bool okay = false;
const QString name = QFileInfo(filename()).fileName();
resolutionPPI = QInputDialog::getInt(0,
i18n("Import SVG"),
i18n("Enter preferred resolution (PPI) for \"%1\"", name),
cfg.preferredVectorImportResolutionPPI(),
0, 100000, 1, &okay);
if (!okay) {
return ImportExportCodes::Cancelled;
}
cfg.setPreferredVectorImportResolutionPPI(resolutionPPI);
}
- const qreal resolution = resolutionPPI / 72.0;
+ const qreal resolution = resolutionPPI;
QSizeF fragmentSize;
QList<KoShape*> shapes =
KisShapeLayer::createShapesFromSvg(io, baseXmlDir,
QRectF(0,0,1200,800), resolutionPPI,
doc->shapeController()->resourceManager(),
&fragmentSize);
QRectF rawImageRect(QPointF(), fragmentSize);
QRect imageRect(rawImageRect.toAlignedRect());
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(doc->createUndoStore(), imageRect.width(), imageRect.height(), cs, "svg image");
- image->setResolution(resolution, resolution);
+ image->setResolution(resolution / 72.0, resolution / 72.0);
doc->setCurrentImage(image);
KisShapeLayerSP shapeLayer =
new KisShapeLayer(doc->shapeController(), image,
i18n("Vector Layer"),
OPACITY_OPAQUE_U8);
Q_FOREACH (KoShape *shape, shapes) {
shapeLayer->addShape(shape);
}
image->addNode(shapeLayer);
return ImportExportCodes::OK;
}
#include <kis_svg_import.moc>
diff --git a/plugins/impex/svg/tests/CMakeLists.txt b/plugins/impex/svg/tests/CMakeLists.txt
index 94fcb20c21..10e9d1a1bc 100644
--- a/plugins/impex/svg/tests/CMakeLists.txt
+++ b/plugins/impex/svg/tests/CMakeLists.txt
@@ -1,11 +1,11 @@
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_SOURCE_DIR}/sdk/tests)
include(ECMAddTests)
macro_add_unittest_definitions()
+include(KritaAddBrokenUnitTest)
-ecm_add_test(
- kis_svg_test.cpp
+krita_add_broken_unit_test(kis_svg_test.cpp
TEST_NAME KisSvgTest
LINK_LIBRARIES kritaui Qt5::Test
NAME_PREFIX "plugins-impex-"
)
diff --git a/plugins/impex/xcf/3rdparty/xcftools/utils.c b/plugins/impex/xcf/3rdparty/xcftools/utils.c
index aa8ba263f9..a905b5bbb1 100644
--- a/plugins/impex/xcf/3rdparty/xcftools/utils.c
+++ b/plugins/impex/xcf/3rdparty/xcftools/utils.c
@@ -1,178 +1,178 @@
/* Generic support functions for Xcftools
*
* This file was written by Henning Makholm <henning@makholm.net>
* It is hereby in the public domain.
*
* In jurisdictions that do not recognise grants of copyright to the
* public domain: I, the author and (presumably, in those jurisdictions)
* copyright holder, hereby permit anyone to distribute and use this code,
* in source code or binary form, with or without modifications. This
* permission is world-wide and irrevocable.
*
* Of course, I will not be liable for any errors or shortcomings in the
* code, since I give it away without asking any compenstations.
*
* If you use or distribute this code, I would appreciate receiving
* credit for writing it, in whichever way you find proper and customary.
*/
#include "xcftools.h"
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <errno.h>
const char *progname = "$0" ;
int verboseFlag = 0 ;
void
vFatalGeneric(int status,const char *format, va_list args)
{
(void) status; /* mark as unused */
if( format ) {
if( *format == '!' ) {
vfprintf(stderr,format+1,args);
fprintf(stderr,": %s\n",strerror(errno));
} else {
vfprintf(stderr,format,args);
fputc('\n',stderr);
}
}
/* don't exit here - Krita can't handle errors otherwise */
/* exit(status); */
}
void
FatalGeneric(int status,const char* format,...)
{
va_list v;
va_start(v,format);
if( format ) fprintf(stderr,"%s: ",progname);
vFatalGeneric(status,format,v);
va_end(v);
}
void
FatalUnexpected(const char* format,...)
{
va_list v;
va_start(v, format);
fprintf(stderr,"%s: ",progname);
vFatalGeneric(127, format, v);
va_end(v);
}
void
FatalBadXCF(const char* format,...)
{
va_list v; va_start(v,format);
fprintf(stderr,"%s: %s:\n ",progname,_("Corrupted or malformed XCF file"));
vFatalGeneric(125,format,v) ;
va_end(v);
}
int
xcfCheckspace(uint32_t addr,int spaceafter,const char *format,...)
{
if( xcf_length < spaceafter || addr > xcf_length - spaceafter ) {
va_list v;
va_start(v,format);
fprintf(stderr,"%s: %s\n ",progname,_("Corrupted or truncated XCF file"));
fprintf(stderr,"(0x%" PRIXPTR " bytes): ",(uintptr_t)xcf_length);
vFatalGeneric(125,format,v) ;
va_end(v);
return XCF_ERROR;
}
return XCF_OK;
}
void
FatalUnsupportedXCF(const char* format,...)
{
va_list v;
va_start(v,format);
fprintf(stderr,"%s: %s\n ",progname,
_("The image contains features not understood by this program:"));
vFatalGeneric(123,format,v) ;
va_end(v);
}
void
gpl_blurb(void)
{
fprintf(stderr,PACKAGE_STRING "\n");
fprintf(stderr,
_("Type \"%s -h\" to get an option summary.\n"),progname);
/* don't exit here - Krita will close otherwise */
/* exit(1) ; */
}
/* ******************************************************* */
void *
xcfmalloc(size_t size)
{
void *ptr = malloc(size);
if( !ptr ) {
FatalUnexpected(_("Out of memory"));
return XCF_PTR_EMPTY;
}
return ptr ;
}
void
xcffree(void *block)
{
if( xcf_file &&
(uint8_t*)block >= xcf_file &&
(uint8_t*)block < xcf_file + xcf_length )
;
else
free(block);
}
/* ******************************************************* */
FILE *
openout(const char *name)
{
FILE *newfile ;
if( strcmp(name,"-") == 0 )
return stdout ;
newfile = fopen(name,"wb") ;
if( newfile == NULL ) {
FatalUnexpected(_("!Cannot create file %s"),name);
return XCF_PTR_EMPTY;
}
return newfile ;
}
int
closeout(FILE *f,const char *name)
{
if( f == NULL )
return XCF_OK;
if( fflush(f) == 0 ) {
errno = 0 ;
if( !ferror(f) ) {
if( fclose(f) == 0 )
return XCF_OK;
} else if( errno == 0 ) {
/* Attempt to coax a valid errno out of the standard library,
* following an idea by Bruno Haible
- * http://lists.gnu.org/archive/html/bug-gnulib/2003-09/msg00157.html
+ * https://lists.gnu.org/archive/html/bug-gnulib/2003-09/msg00157.html
*/
if( fputc('\0', f) != EOF &&
fflush(f) == 0 )
errno = EIO ; /* Argh, everything succedes. Just call it an I/O error */
}
}
FatalUnexpected(_("!Error writing file %s"),name);
return XCF_ERROR;
}
diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h
index f7df52e583..cb0a182b75 100644
--- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h
+++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h
@@ -1,82 +1,82 @@
/*
* 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_DUPLICATEOP_H_
#define KIS_DUPLICATEOP_H_
#include "kis_brush_based_paintop.h"
#include <klocalizedstring.h>
#include <kis_types.h>
#include <brushengine/kis_paintop_factory.h>
#include <brushengine/kis_paintop_settings.h>
#include <kis_pressure_size_option.h>
#include <kis_pressure_rotation_option.h>
#include <kis_pressure_opacity_option.h>
#include "kis_duplicateop_settings.h"
class KisPaintInformation;
class QPointF;
class KisPainter;
class KisDuplicateOp : public KisBrushBasedPaintOp
{
public:
KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image);
~KisDuplicateOp() override;
protected:
KisSpacingInformation paintAt(const KisPaintInformation& info) override;
KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override;
private:
qreal minimizeEnergy(const qreal* m, qreal* sol, int w, int h);
private:
KisImageSP m_image;
KisNodeSP m_node;
KisDuplicateOpSettingsSP m_settings;
KisPaintDeviceSP m_srcdev;
KisPaintDeviceSP m_target;
- QPointF m_duplicateStart;
- bool m_duplicateStartIsSet;
+ QPointF m_duplicateStart {QPointF(0.0, 0.0)};
+ bool m_duplicateStartIsSet {false};
KisPressureSizeOption m_sizeOption;
KisPressureOpacityOption m_opacityOption;
KisPressureRotationOption m_rotationOption;
- bool m_healing;
- bool m_perspectiveCorrection;
- bool m_moveSourcePoint;
- bool m_cloneFromProjection;
+ bool m_healing {false};
+ bool m_perspectiveCorrection {false};
+ bool m_moveSourcePoint {false};
+ bool m_cloneFromProjection {false};
};
#endif // KIS_DUPLICATEOP_H_
diff --git a/plugins/paintops/hatching/kis_hatching_paintop_settings_widget.cpp b/plugins/paintops/hatching/kis_hatching_paintop_settings_widget.cpp
index 856ec17169..a1b67a70af 100644
--- a/plugins/paintops/hatching/kis_hatching_paintop_settings_widget.cpp
+++ b/plugins/paintops/hatching/kis_hatching_paintop_settings_widget.cpp
@@ -1,138 +1,138 @@
/*
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 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_hatching_paintop_settings_widget.h"
#include "kis_hatching_options.h"
#include "kis_hatching_preferences.h"
#include "kis_hatching_paintop_settings.h"
#include "kis_hatching_pressure_angle_option.h"
#include "kis_hatching_pressure_crosshatching_option.h"
#include "kis_hatching_pressure_separation_option.h"
#include "kis_hatching_pressure_thickness_option.h"
#include <kis_brush_option_widget.h>
#include <kis_curve_option_widget.h>
#include <kis_pressure_opacity_option.h>
#include <kis_pressure_size_option.h>
#include <kis_paintop_settings_widget.h>
#include <kis_paint_action_type_option.h>
#include <kis_compositeop_option.h>
#include "kis_texture_option.h"
#include <kis_pressure_mirror_option_widget.h>
#include "kis_pressure_texture_strength_option.h"
#include <QDomDocument>
#include <QDomElement>
KisHatchingPaintOpSettingsWidget:: KisHatchingPaintOpSettingsWidget(QWidget* parent)
: KisBrushBasedPaintopOptionWidget(parent)
{
setPrecisionEnabled(true);
//-------Adding widgets to the screen------------
addPaintOpOption(new KisHatchingOptions(), i18n("Hatching options"));
addPaintOpOption(new KisHatchingPreferences(), i18n("Hatching preferences"));
addPaintOpOption(new KisCompositeOpOption(true), i18n("Blending Mode"));
addPaintOpOption(new KisCurveOptionWidget(new KisHatchingPressureSeparationOption(), i18n("0.0"), i18n("1.0")), i18n("Separation"));
addPaintOpOption(new KisCurveOptionWidget(new KisHatchingPressureThicknessOption(), i18n("0.0"), i18n("1.0")), i18n("Thickness"));
addPaintOpOption(new KisCurveOptionWidget(new KisHatchingPressureAngleOption(), i18n("0.0"), i18n("1.0")), i18n("Angle"));
addPaintOpOption(new KisCurveOptionWidget(new KisHatchingPressureCrosshatchingOption(), i18n("0.0"), i18n("1.0")), i18n("Crosshatching"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureOpacityOption(), i18n("Transparent"), i18n("Opaque")), i18n("Opacity"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureSizeOption(), i18n("0%"), i18n("100%")), i18n("Size"));
addPaintOpOption(new KisPressureMirrorOptionWidget(), i18n("Mirror"));
addPaintOpOption(new KisPaintActionTypeOption(), i18n("Painting Mode"));
addPaintOpOption(new KisTextureOption(), i18n("Pattern"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureTextureStrengthOption(), i18n("Weak"), i18n("Strong")), i18n("Strength"));
//-----Useful to read first:------
/*
Below you will encounter a reasonably correct solution to the problem of changing
the default presets of the "BrushTip" popup configuration dialogue.
In my (Pentalis) opinion, the best solution is code refactoring (simpler ways
to change the defaults). On the meanwhile, copypasting this code
won't give your class a charisma penalty.
In kis_hatching_paintop_settings.cpp you will find a snippet of code to
discover the structure of your XML config tree if you need to edit it at build
time like here.
*/
//---------START ALTERING DEFAULT VALUES-----------
//As the name implies, reconfigurationCourier is the KisPropertiesConfigurationSP
//we'll use as an intermediary to edit the default settings
KisPropertiesConfigurationSP reconfigurationCourier = configuration();
/*xMLAnalyzer is an empty document we'll use to analyze and edit the config string part by part
I know the important string is "brush_definition" because I read the tree with the snippet
in kis_hatching_paintop_settings.cpp */
QDomDocument xMLAnalyzer;
xMLAnalyzer.setContent(reconfigurationCourier->getString("brush_definition"));
/*More things I know by reading the XML tree. At this point you can just read it with:
dbgKrita << xMLAnalyzer.toString() ;
those QDomElements are the way to navigate the XML tree, read
- http://doc.qt.nokia.com/latest/qdomdocument.html for more information */
+ https://doc.qt.io/qt-5/qdomdocument.html for more information */
QDomElement firstTag = xMLAnalyzer.documentElement();
QDomElement firstTagsChild = firstTag.elementsByTagName("MaskGenerator").item(0).toElement();
// SET THE DEFAULT VALUES
firstTag.attributeNode("spacing").setValue("0.4");
firstTagsChild.attributeNode("diameter").setValue("30");
//Write them into the intermediary config file
reconfigurationCourier->setProperty("brush_definition", xMLAnalyzer.toString());
KisCubicCurve CurveSize;
CurveSize.fromString("0,1;1,0.1;");
//dbgKrita << "\n\n\n" << CurveSize.toString() << "\n\n\n";
QVariant QVCurveSize = QVariant::fromValue(CurveSize);
reconfigurationCourier->setProperty("CurveSize", QVCurveSize);
setConfiguration(reconfigurationCourier); // Finished.
/* Debugging block
QMap<QString, QVariant> rofl = QMap<QString, QVariant>(reconfigurationCourier->getProperties());
QMap<QString, QVariant>::const_iterator i;
for (i = rofl.constBegin(); i != rofl.constEnd(); ++i)
dbgKrita << i.key() << ":" << i.value();
*/
}
KisHatchingPaintOpSettingsWidget::~ KisHatchingPaintOpSettingsWidget()
{
}
KisPropertiesConfigurationSP KisHatchingPaintOpSettingsWidget::configuration() const
{
KisHatchingPaintOpSettingsSP config = new KisHatchingPaintOpSettings();
config->setOptionsWidget(const_cast<KisHatchingPaintOpSettingsWidget*>(this));
config->setProperty("paintop", "hatchingbrush"); // XXX: make this a const id string
writeConfiguration(config);
return config;
}
diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp
index b7dc342b13..b5e87819f8 100644
--- a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp
+++ b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp
@@ -1,336 +1,339 @@
/*
* Copyright (c) 2010 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_brush_based_paintop_settings.h"
#include <kis_paint_action_type_option.h>
#include <kis_airbrush_option_widget.h>
#include "kis_brush_based_paintop_options_widget.h"
#include <kis_boundary.h>
#include "kis_brush_server.h"
#include <QLineF>
#include "kis_signals_blocker.h"
#include "kis_brush_option.h"
#include <KisPaintopSettingsIds.h>
+#include <brushengine/kis_paintop_preset.h>
struct BrushReader {
BrushReader(const KisBrushBasedPaintOpSettings *parent)
: m_parent(parent)
{
m_option.readOptionSetting(m_parent);
}
KisBrushSP brush() {
return m_option.brush();
}
const KisBrushBasedPaintOpSettings *m_parent;
KisBrushOptionProperties m_option;
};
struct BrushWriter {
BrushWriter(KisBrushBasedPaintOpSettings *parent)
: m_parent(parent)
{
m_option.readOptionSetting(m_parent);
}
~BrushWriter() {
m_option.writeOptionSetting(m_parent);
}
KisBrushSP brush() {
return m_option.brush();
}
KisBrushBasedPaintOpSettings *m_parent;
KisBrushOptionProperties m_option;
};
KisBrushBasedPaintOpSettings::KisBrushBasedPaintOpSettings()
: KisOutlineGenerationPolicy<KisPaintOpSettings>(KisCurrentOutlineFetcher::SIZE_OPTION |
KisCurrentOutlineFetcher::ROTATION_OPTION |
- KisCurrentOutlineFetcher::MIRROR_OPTION)
+ KisCurrentOutlineFetcher::MIRROR_OPTION |
+ KisCurrentOutlineFetcher::SHARPNESS_OPTION)
+
{
}
bool KisBrushBasedPaintOpSettings::paintIncremental()
{
if (hasProperty("PaintOpAction")) {
return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP;
}
return true;
}
KisPaintOpSettingsSP KisBrushBasedPaintOpSettings::clone() const
{
KisPaintOpSettingsSP _settings = KisOutlineGenerationPolicy<KisPaintOpSettings>::clone();
KisBrushBasedPaintOpSettingsSP settings = dynamic_cast<KisBrushBasedPaintOpSettings*>(_settings.data());
settings->m_savedBrush = 0;
return settings;
}
KisBrushSP KisBrushBasedPaintOpSettings::brush() const
{
KisBrushSP brush = m_savedBrush;
if (!brush) {
BrushReader w(this);
brush = w.brush();
m_savedBrush = brush;
}
return brush;
}
QPainterPath KisBrushBasedPaintOpSettings::brushOutlineImpl(const KisPaintInformation &info,
const OutlineMode &mode,
qreal additionalScale)
{
QPainterPath path;
if (mode.isVisible) {
KisBrushSP brush = this->brush();
if (!brush) return path;
qreal finalScale = brush->scale() * additionalScale;
QPainterPath realOutline = brush->outline();
if (mode.forceCircle) {
QPainterPath ellipse;
ellipse.addEllipse(realOutline.boundingRect());
realOutline = ellipse;
}
path = outlineFetcher()->fetchOutline(info, this, realOutline, mode, finalScale, brush->angle());
if (mode.showTiltDecoration) {
const QPainterPath tiltLine = makeTiltIndicator(info,
realOutline.boundingRect().center(),
realOutline.boundingRect().width() * 0.5,
3.0);
path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, mode, finalScale, 0.0, true, realOutline.boundingRect().center().x(), realOutline.boundingRect().center().y()));
}
}
return path;
}
QPainterPath KisBrushBasedPaintOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode)
{
return brushOutlineImpl(info, mode, 1.0);
}
bool KisBrushBasedPaintOpSettings::isValid() const
{
QStringList files = getStringList(KisPaintOpUtils::RequiredBrushFilesListTag);
files << getString(KisPaintOpUtils::RequiredBrushFileTag);
Q_FOREACH (const QString &file, files) {
if (!file.isEmpty()) {
KisBrushSP brush = KisBrushServer::instance()->brushServer()->resourceByFilename(file);
if (!brush) {
return false;
}
}
}
return true;
}
bool KisBrushBasedPaintOpSettings::isLoadable()
{
return (KisBrushServer::instance()->brushServer()->resources().count() > 0);
}
void KisBrushBasedPaintOpSettings::setAngle(qreal value)
{
BrushWriter w(this);
if (!w.brush()) return;
w.brush()->setAngle(value);
}
qreal KisBrushBasedPaintOpSettings::angle()
{
return this->brush()->angle();
}
void KisBrushBasedPaintOpSettings::setSpacing(qreal value)
{
BrushWriter w(this);
if (!w.brush()) return;
w.brush()->setSpacing(value);
}
qreal KisBrushBasedPaintOpSettings::spacing()
{
return this->brush()->spacing();
}
void KisBrushBasedPaintOpSettings::setAutoSpacing(bool active, qreal coeff)
{
BrushWriter w(this);
if (!w.brush()) return;
w.brush()->setAutoSpacing(active, coeff);
}
bool KisBrushBasedPaintOpSettings::autoSpacingActive()
{
return this->brush()->autoSpacingActive();
}
qreal KisBrushBasedPaintOpSettings::autoSpacingCoeff()
{
return this->brush()->autoSpacingCoeff();
}
void KisBrushBasedPaintOpSettings::setPaintOpSize(qreal value)
{
BrushWriter w(this);
if (!w.brush()) return;
w.brush()->setUserEffectiveSize(value);
}
qreal KisBrushBasedPaintOpSettings::paintOpSize() const
{
return this->brush()->userEffectiveSize();
}
#include <brushengine/kis_slider_based_paintop_property.h>
#include "kis_paintop_preset.h"
#include "kis_paintop_settings_update_proxy.h"
QList<KisUniformPaintOpPropertySP> KisBrushBasedPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings)
{
QList<KisUniformPaintOpPropertySP> props =
listWeakToStrong(m_uniformProperties);
if (props.isEmpty()) {
{
KisIntSliderBasedPaintOpPropertyCallback *prop =
new KisIntSliderBasedPaintOpPropertyCallback(
KisIntSliderBasedPaintOpPropertyCallback::Int,
"angle",
"Angle",
settings, 0);
prop->setRange(0, 360);
prop->setReadCallback(
[](KisUniformPaintOpProperty *prop) {
KisBrushBasedPaintOpSettings *s =
dynamic_cast<KisBrushBasedPaintOpSettings*>(prop->settings().data());
const qreal angleResult = kisRadiansToDegrees(s->angle());
prop->setValue(angleResult);
});
prop->setWriteCallback(
[](KisUniformPaintOpProperty *prop) {
KisBrushBasedPaintOpSettings *s =
dynamic_cast<KisBrushBasedPaintOpSettings*>(prop->settings().data());
s->setAngle(kisDegreesToRadians(prop->value().toReal()));
});
QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue()));
prop->requestReadValue();
props << toQShared(prop);
}
{
KisUniformPaintOpPropertyCallback *prop =
new KisUniformPaintOpPropertyCallback(
KisUniformPaintOpPropertyCallback::Bool,
"auto_spacing",
"Auto Spacing",
settings, 0);
prop->setReadCallback(
[](KisUniformPaintOpProperty *prop) {
KisBrushBasedPaintOpSettings *s =
dynamic_cast<KisBrushBasedPaintOpSettings*>(prop->settings().data());
prop->setValue(s->autoSpacingActive());
});
prop->setWriteCallback(
[](KisUniformPaintOpProperty *prop) {
KisBrushBasedPaintOpSettings *s =
dynamic_cast<KisBrushBasedPaintOpSettings*>(prop->settings().data());
s->setAutoSpacing(prop->value().toBool(), s->autoSpacingCoeff());
});
QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue()));
prop->requestReadValue();
props << toQShared(prop);
}
{
KisDoubleSliderBasedPaintOpPropertyCallback *prop =
new KisDoubleSliderBasedPaintOpPropertyCallback(
KisDoubleSliderBasedPaintOpPropertyCallback::Double,
"spacing",
"Spacing",
settings, 0);
prop->setRange(0.01, 10);
prop->setSingleStep(0.01);
prop->setExponentRatio(3.0);
prop->setReadCallback(
[](KisUniformPaintOpProperty *prop) {
KisBrushBasedPaintOpSettings *s =
dynamic_cast<KisBrushBasedPaintOpSettings*>(prop->settings().data());
if (s) {
const qreal value = s->autoSpacingActive() ?
s->autoSpacingCoeff() : s->spacing();
prop->setValue(value);
}
});
prop->setWriteCallback(
[](KisUniformPaintOpProperty *prop) {
KisBrushBasedPaintOpSettings *s =
dynamic_cast<KisBrushBasedPaintOpSettings*>(prop->settings().data());
if (s) {
if (s->autoSpacingActive()) {
s->setAutoSpacing(true, prop->value().toReal());
} else {
s->setSpacing(prop->value().toReal());
}
}
});
QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue()));
prop->requestReadValue();
props << toQShared(prop);
}
}
return KisPaintOpSettings::uniformProperties(settings) + props;
}
void KisBrushBasedPaintOpSettings::onPropertyChanged()
{
m_savedBrush.clear();
KisOutlineGenerationPolicy<KisPaintOpSettings>::onPropertyChanged();
}
diff --git a/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp b/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp
index fee3928f66..0bdbe9dc43 100644
--- a/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp
+++ b/plugins/paintops/libpaintop/kis_current_outline_fetcher.cpp
@@ -1,162 +1,181 @@
/*
* 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_current_outline_fetcher.h"
#include "kis_pressure_size_option.h"
#include "kis_pressure_rotation_option.h"
#include "kis_pressure_mirror_option.h"
+#include "kis_pressure_sharpness_option.h"
#include <brushengine/kis_paintop_settings.h>
#include <kis_properties_configuration.h>
#include "kis_paintop_settings.h"
#include <QElapsedTimer>
#define NOISY_UPDATE_SPEED 50 // Time in ms for outline updates to noisy brushes
struct KisCurrentOutlineFetcher::Private {
Private(Options optionsAvailable)
: isDirty(true) {
if (optionsAvailable & SIZE_OPTION) {
sizeOption.reset(new KisPressureSizeOption());
}
if (optionsAvailable & ROTATION_OPTION) {
rotationOption.reset(new KisPressureRotationOption());
}
if (optionsAvailable & MIRROR_OPTION) {
mirrorOption.reset(new KisPressureMirrorOption());
}
+
+ if (optionsAvailable & SHARPNESS_OPTION) {
+ sharpnessOption.reset(new KisPressureSharpnessOption());
+ }
}
QScopedPointer<KisPressureSizeOption> sizeOption;
QScopedPointer<KisPressureRotationOption> rotationOption;
QScopedPointer<KisPressureMirrorOption> mirrorOption;
+ QScopedPointer<KisPressureSharpnessOption> sharpnessOption;
bool isDirty;
QElapsedTimer lastUpdateTime;
qreal lastRotationApplied;
qreal lastSizeApplied;
MirrorProperties lastMirrorApplied;
};
KisCurrentOutlineFetcher::KisCurrentOutlineFetcher(Options optionsAvailable)
: d(new Private(optionsAvailable))
{
d->lastUpdateTime.start();
}
KisCurrentOutlineFetcher::~KisCurrentOutlineFetcher()
{
}
void KisCurrentOutlineFetcher::setDirty()
{
d->isDirty = true;
}
QPainterPath KisCurrentOutlineFetcher::fetchOutline(const KisPaintInformation &info,
const KisPaintOpSettingsSP settings,
const QPainterPath &originalOutline,
const KisPaintOpSettings::OutlineMode &mode,
qreal additionalScale,
qreal additionalRotation,
bool tilt, qreal tiltcenterx, qreal tiltcentery) const
{
if (d->isDirty) {
if (d->sizeOption) {
d->sizeOption->readOptionSetting(settings);
d->sizeOption->resetAllSensors();
}
if (d->rotationOption) {
d->rotationOption->readOptionSetting(settings);
d->rotationOption->resetAllSensors();
}
if (d->mirrorOption) {
d->mirrorOption->readOptionSetting(settings);
d->mirrorOption->resetAllSensors();
}
+ if (d->sharpnessOption) {
+ d->sharpnessOption->readOptionSetting(settings);
+ d->sharpnessOption->resetAllSensors();
+ }
+
d->isDirty = false;
}
qreal scale = additionalScale;
qreal rotation = additionalRotation;
bool needsUpdate = false;
// Randomized rotation at full speed looks noisy, so slow it down
if (d->lastUpdateTime.elapsed() > NOISY_UPDATE_SPEED) {
needsUpdate = true;
d->lastUpdateTime.restart();
}
if (d->sizeOption && !tilt && !mode.forceFullSize) {
if (!d->sizeOption->isRandom() || needsUpdate) {
d->lastSizeApplied = d->sizeOption->apply(info);
}
scale *= d->lastSizeApplied;
}
if (d->rotationOption && !tilt) {
if (!d->rotationOption->isRandom() || needsUpdate) {
d->lastRotationApplied = d->rotationOption->apply(info);
}
rotation += d->lastRotationApplied;
} else if (d->rotationOption && tilt) {
rotation += info.canvasRotation() * M_PI / 180.0;
}
qreal xFlip = 1.0;
qreal yFlip = 1.0;
if (d->mirrorOption) {
if (!d->mirrorOption->isRandom() || needsUpdate) {
d->lastMirrorApplied = d->mirrorOption->apply(info);
}
if (d->lastMirrorApplied.coordinateSystemFlipped) {
rotation = 2 * M_PI - rotation;
}
if (d->lastMirrorApplied.horizontalMirror) {
xFlip = -1.0;
}
if (d->lastMirrorApplied.verticalMirror) {
yFlip = -1.0;
}
}
-
QTransform rot;
rot.rotateRadians(-rotation);
QPointF hotSpot = originalOutline.boundingRect().center();
if (tilt) {
hotSpot.setX(tiltcenterx);
hotSpot.setY(tiltcentery);
}
+
+ QPointF pos = info.pos();
+ if (d->sharpnessOption) {
+ qint32 x = 0, y = 0;
+ qreal subPixelX = 0.0, subPixelY = 0.0;
+ d->sharpnessOption->apply(info, pos - hotSpot, x, y, subPixelX, subPixelY);
+ pos = QPointF(x + subPixelX, y + subPixelY) + hotSpot;
+ }
+
QTransform T1 = QTransform::fromTranslate(-hotSpot.x(), -hotSpot.y());
- QTransform T2 = QTransform::fromTranslate(info.pos().x(), info.pos().y());
+ QTransform T2 = QTransform::fromTranslate(pos.x(), pos.y());
QTransform S = QTransform::fromScale(xFlip * scale, yFlip * scale);
return (T1 * rot * S * T2).map(originalOutline);
}
diff --git a/plugins/paintops/libpaintop/kis_current_outline_fetcher.h b/plugins/paintops/libpaintop/kis_current_outline_fetcher.h
index 9877db6d46..412b65a328 100644
--- a/plugins/paintops/libpaintop/kis_current_outline_fetcher.h
+++ b/plugins/paintops/libpaintop/kis_current_outline_fetcher.h
@@ -1,69 +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_CURRENT_OUTLINE_FETCHER_H
#define __KIS_CURRENT_OUTLINE_FETCHER_H
#include <kritapaintop_export.h>
#include <QFlags>
#include <QScopedPointer>
#include <QPainterPath>
#include <QPointF>
#include <kis_paintop_settings.h>
class KisPaintInformation;
class PAINTOP_EXPORT KisCurrentOutlineFetcher
{
public:
enum Option {
NO_OPTION,
SIZE_OPTION,
ROTATION_OPTION,
- MIRROR_OPTION
+ MIRROR_OPTION,
+ SHARPNESS_OPTION
};
Q_DECLARE_FLAGS(Options, Option);
public:
KisCurrentOutlineFetcher(Options optionsAvailable);
~KisCurrentOutlineFetcher();
void setDirty();
QPainterPath fetchOutline(const KisPaintInformation &info,
const KisPaintOpSettingsSP settings,
const QPainterPath &originalOutline,
const KisPaintOpSettings::OutlineMode &mode,
qreal additionalScale = 1.0,
qreal additionalRotation = 0.0,
bool tilt = false, qreal tiltcenterx = 1.0, qreal tiltcentery = 1.0) const;
private:
Q_DISABLE_COPY(KisCurrentOutlineFetcher);
struct Private;
const QScopedPointer<Private> d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KisCurrentOutlineFetcher::Options);
#endif /* __KIS_CURRENT_OUTLINE_FETCHER_H */
diff --git a/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.cpp b/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.cpp
index 6597a8de95..0ac4a36e26 100644
--- a/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.cpp
+++ b/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.cpp
@@ -1,66 +1,32 @@
/*
* 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_pressure_texture_strength_option.h"
#include <klocalizedstring.h>
KisPressureTextureStrengthOption::KisPressureTextureStrengthOption()
: KisCurveOption("Texture/Strength/", KisPaintOpOption::TEXTURE, false)
{
}
double KisPressureTextureStrengthOption::apply(const KisPaintInformation & info) const
{
if (!isChecked()) return 1.0;
return computeSizeLikeValue(info);
}
-
-void KisPressureTextureStrengthOption::readOptionSetting(const KisPropertiesConfigurationSP setting)
-{
- KisCurveOption::readOptionSetting(setting);
-
- /**
- * Backward compatibility with Krita < 2.7.
- *
- * Process the presets created with the old UI, when the
- * strength was a part of Texture/Pattern option.
- */
- int strengthVersion = setting->getInt("Texture/Strength/StrengthVersion", 1);
- if (strengthVersion == 1) {
- double legacyStrength = setting->getDouble("Texture/Pattern/Strength", 1.0);
- setChecked(true);
- setValue(legacyStrength);
- }
-}
-
-void KisPressureTextureStrengthOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const
-{
- KisCurveOption::writeOptionSetting(setting);
-
- /**
- * Forward compatibility with the Krita < 2.7
- *
- * Duplicate the value of the maximum strength into the
- * property used by older versions of Krita.
- */
- setting->setProperty("Texture/Strength/StrengthVersion", 2);
- if (isChecked()) {
- setting->setProperty("Texture/Pattern/Strength", value());
- }
-}
diff --git a/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.h b/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.h
index 0f5862bc49..4f7d0b0819 100644
--- a/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.h
+++ b/plugins/paintops/libpaintop/kis_pressure_texture_strength_option.h
@@ -1,41 +1,38 @@
/*
* 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_PRESSURE_TEXTURE_STRENGTH_OPTION_H
#define __KIS_PRESSURE_TEXTURE_STRENGTH_OPTION_H
#include "kis_curve_option.h"
#include <brushengine/kis_paint_information.h>
#include <kritapaintop_export.h>
/**
* This curve defines how deep the ink (or a pointer) of a brush
* penetrates the surface of the canvas, that is how strong we
* press on the paper
*/
class PAINTOP_EXPORT KisPressureTextureStrengthOption : public KisCurveOption
{
public:
KisPressureTextureStrengthOption();
double apply(const KisPaintInformation & info) const;
-
- void readOptionSetting(const KisPropertiesConfigurationSP setting) override;
- void writeOptionSetting(KisPropertiesConfigurationSP setting) const override;
};
#endif /* __KIS_PRESSURE_TEXTURE_STRENGTH_OPTION_H */
diff --git a/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp b/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp
index c95b9855ac..fb423ab29a 100644
--- a/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp
+++ b/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp
@@ -1,130 +1,130 @@
/*
* 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_spacing_selection_widget.h"
#include <QCheckBox>
#include <QHBoxLayout>
#include "klocalizedstring.h"
#include "kis_signals_blocker.h"
#include "kis_slider_spin_box.h"
struct KisSpacingSelectionWidget::Private
{
Private(KisSpacingSelectionWidget *_q)
- : q(_q), oldSliderValue(0.1)
+ : q(_q)
{
}
KisSpacingSelectionWidget *q;
- KisDoubleSliderSpinBox *slider;
- QCheckBox *autoButton;
+ KisDoubleSliderSpinBox *slider {0};
+ QCheckBox *autoButton {0};
- qreal oldSliderValue;
+ qreal oldSliderValue {0.1};
void slotSpacingChanged(qreal value);
void slotAutoSpacing(bool value);
};
KisSpacingSelectionWidget::KisSpacingSelectionWidget(QWidget *parent)
: QWidget(parent),
m_d(new Private(this))
{
m_d->slider = new KisDoubleSliderSpinBox(this);
m_d->slider->setRange(0.02, 10.0, 2);
m_d->slider->setExponentRatio(3);
m_d->slider->setSingleStep(0.01);
m_d->slider->setValue(0.1);
m_d->slider->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
m_d->autoButton = new QCheckBox(this);
m_d->autoButton->setText(i18nc("@action:button", "Auto"));
m_d->autoButton->setToolTip(i18nc("@info:tooltip", "In auto mode the spacing of the brush will be calculated automatically depending on its size"));
m_d->autoButton->setCheckable(true);
m_d->autoButton->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
QHBoxLayout *layout = new QHBoxLayout(this);
layout->addWidget(m_d->autoButton);
layout->addWidget(m_d->slider);
layout->setMargin(0);
connect(m_d->slider, SIGNAL(valueChanged(qreal)), SLOT(slotSpacingChanged(qreal)));
connect(m_d->autoButton, SIGNAL(toggled(bool)), SLOT(slotAutoSpacing(bool)));
}
KisSpacingSelectionWidget::~KisSpacingSelectionWidget()
{
}
qreal KisSpacingSelectionWidget::spacing() const
{
return autoSpacingActive() ? 0.1 : m_d->slider->value();
}
bool KisSpacingSelectionWidget::autoSpacingActive() const
{
return m_d->autoButton->isChecked();
}
qreal KisSpacingSelectionWidget::autoSpacingCoeff() const
{
return autoSpacingActive() ? m_d->slider->value() : 1.0;
}
void KisSpacingSelectionWidget::setSpacing(bool isAuto, qreal spacing)
{
KisSignalsBlocker b1(m_d->autoButton);
KisSignalsBlocker b2(m_d->slider);
m_d->autoButton->setChecked(isAuto);
m_d->slider->setValue(spacing);
}
void KisSpacingSelectionWidget::Private::slotSpacingChanged(qreal value)
{
Q_UNUSED(value);
emit q->sigSpacingChanged();
}
void KisSpacingSelectionWidget::Private::slotAutoSpacing(bool value)
{
qreal newSliderValue = 0.0;
if (value) {
newSliderValue = 1.0;
oldSliderValue = slider->value();
} else {
newSliderValue = oldSliderValue;
}
{
KisSignalsBlocker b(slider);
slider->setValue(newSliderValue);
}
emit q->sigSpacingChanged();
}
#include "moc_kis_spacing_selection_widget.moc"
diff --git a/plugins/paintops/sketch/kis_sketch_paintop.cpp b/plugins/paintops/sketch/kis_sketch_paintop.cpp
index 1671edff80..fb20f0f335 100644
--- a/plugins/paintops/sketch/kis_sketch_paintop.cpp
+++ b/plugins/paintops/sketch/kis_sketch_paintop.cpp
@@ -1,325 +1,325 @@
/*
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 Ricardo Cabello <hello@mrdoob.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_sketch_paintop.h"
#include "kis_sketch_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 <kis_paintop_plugin_utils.h>
#include <brushengine/kis_paintop.h>
#include <brushengine/kis_paint_information.h>
#include <kis_fixed_paint_device.h>
#include <kis_pressure_opacity_option.h>
#include <kis_dab_cache.h>
#include "kis_lod_transform.h"
#include <QtGlobal>
/*
-* Based on Harmony project http://github.com/mrdoob/harmony/
+* Based on Harmony project https://github.com/mrdoob/harmony/
*/
// chrome : diff 0.2, sketchy : 0.3, fur: 0.5
// fur : distance / thresholdDistance
// shaded: opacity per line :/
// ((1 - (d / 1000)) * 0.1 * BRUSH_PRESSURE), offset == 0
// chrome: color per line :/
//this.context.strokeStyle = "rgba(" + Math.floor(Math.random() * COLOR[0]) + ", " + Math.floor(Math.random() * COLOR[1]) + ", " + Math.floor(Math.random() * COLOR[2]) + ", " + 0.1 * BRUSH_PRESSURE + " )";
// long fur
// from: count + offset * -random
// to: i point - (offset * -random) + random * 2
// probability distance / thresholdDistnace
// shaded: probability : paint always - 0.0 density
KisSketchPaintOp::KisSketchPaintOp(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_sizeOption.readOptionSetting(settings);
m_rotationOption.readOptionSetting(settings);
m_rateOption.readOptionSetting(settings);
m_sketchProperties.readOptionSetting(settings);
m_brushOption.readOptionSetting(settings);
m_densityOption.readOptionSetting(settings);
m_lineWidthOption.readOptionSetting(settings);
m_offsetScaleOption.readOptionSetting(settings);
m_brush = m_brushOption.brush();
m_dabCache = new KisDabCache(m_brush);
m_opacityOption.resetAllSensors();
m_sizeOption.resetAllSensors();
m_rotationOption.resetAllSensors();
m_rateOption.resetAllSensors();
m_painter = 0;
m_count = 0;
}
KisSketchPaintOp::~KisSketchPaintOp()
{
delete m_painter;
delete m_dabCache;
}
void KisSketchPaintOp::drawConnection(const QPointF& start, const QPointF& end, double lineWidth)
{
if (lineWidth == 1.0) {
m_painter->drawThickLine(start, end, lineWidth, lineWidth);
}
else {
m_painter->drawLine(start, end, lineWidth, true);
}
}
void KisSketchPaintOp::updateBrushMask(const KisPaintInformation& info, qreal scale, qreal rotation)
{
QRect dstRect;
m_maskDab = m_dabCache->fetchDab(m_dab->colorSpace(),
painter()->paintColor(),
info.pos(),
KisDabShape(scale, 1.0, rotation),
info, 1.0,
&dstRect);
m_brushBoundingBox = dstRect;
m_hotSpot = QPointF(0.5 * m_brushBoundingBox.width(),
0.5 * m_brushBoundingBox.height());
}
void KisSketchPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
// Use superclass behavior for lines of zero length. Otherwise, airbrushing can happen faster
// than it is supposed to.
if (pi1.pos() == pi2.pos()) {
KisPaintOp::paintLine(pi1, pi2, currentDistance);
}
else {
doPaintLine(pi1, pi2);
}
}
void KisSketchPaintOp::doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2)
{
if (!m_brush || !painter()) return;
if (!m_dab) {
m_dab = source()->createCompositionSourceDevice();
m_painter = new KisPainter(m_dab);
m_painter->setPaintColor(painter()->paintColor());
}
else {
m_dab->clear();
}
QPointF prevMouse = pi1.pos();
QPointF mousePosition = pi2.pos();
m_points.append(mousePosition);
const qreal lodAdditionalScale = KisLodTransform::lodToScale(painter()->device());
const qreal scale = lodAdditionalScale * m_sizeOption.apply(pi2);
if ((scale * m_brush->width()) <= 0.01 || (scale * m_brush->height()) <= 0.01) return;
const qreal currentLineWidth = qMax(0.9, lodAdditionalScale * m_lineWidthOption.apply(pi2, m_sketchProperties.lineWidth));
const qreal currentOffsetScale = m_offsetScaleOption.apply(pi2, m_sketchProperties.offset);
const double rotation = m_rotationOption.apply(pi2);
const double currentProbability = m_densityOption.apply(pi2, m_sketchProperties.probability);
// shaded: does not draw this line, chrome does, fur does
if (m_sketchProperties.makeConnection) {
drawConnection(prevMouse, mousePosition, currentLineWidth);
}
qreal thresholdDistance = 0.0;
// update the mask for simple mode only once
// determine the radius
if (m_count == 0 && m_sketchProperties.simpleMode) {
updateBrushMask(pi2, 1.0, 0.0);
//m_radius = qMax(m_maskDab->bounds().width(),m_maskDab->bounds().height()) * 0.5;
m_radius = 0.5 * qMax(m_brush->width(), m_brush->height());
}
if (!m_sketchProperties.simpleMode) {
updateBrushMask(pi2, scale, rotation);
m_radius = qMax(m_maskDab->bounds().width(), m_maskDab->bounds().height()) * 0.5;
thresholdDistance = pow(m_radius, 2);
}
if (m_sketchProperties.simpleMode) {
// update the radius according scale in simple mode
thresholdDistance = pow(m_radius * scale, 2);
}
// determine density
const qreal density = thresholdDistance * currentProbability;
// probability behaviour
qreal probability = 1.0 - currentProbability;
QColor painterColor = painter()->paintColor().toQColor();
QColor randomColor;
KoColor color(m_dab->colorSpace());
int w = m_maskDab->bounds().width();
quint8 opacityU8 = 0;
quint8 * pixel;
qreal distance;
QPoint positionInMask;
QPointF diff;
int size = m_points.size();
// MAIN LOOP
for (int i = 0; i < size; i++) {
diff = m_points.at(i) - mousePosition;
distance = diff.x() * diff.x() + diff.y() * diff.y();
// circle test
bool makeConnection = false;
if (m_sketchProperties.simpleMode) {
if (distance < thresholdDistance) {
makeConnection = true;
}
// mask test
}
else {
if (m_brushBoundingBox.contains(m_points.at(i))) {
positionInMask = (diff + m_hotSpot).toPoint();
uint pos = ((positionInMask.y() * w + positionInMask.x()) * m_maskDab->pixelSize());
if (pos < m_maskDab->allocatedPixels() * m_maskDab->pixelSize()) {
pixel = m_maskDab->data() + pos;
opacityU8 = m_maskDab->colorSpace()->opacityU8(pixel);
if (opacityU8 != 0) {
makeConnection = true;
}
}
}
}
if (!makeConnection) {
// check next point
continue;
}
if (m_sketchProperties.distanceDensity) {
probability = distance / density;
}
KisRandomSourceSP randomSource = pi2.randomSource();
// density check
if (randomSource->generateNormalized() >= probability) {
QPointF offsetPt = diff * currentOffsetScale;
if (m_sketchProperties.randomRGB) {
/**
* Since the order of calculation of function
* parameters is not defined by C++ standard, we
* should generate values in an external code snippet
* which has a definite order of execution.
*/
qreal r1 = randomSource->generateNormalized();
qreal r2 = randomSource->generateNormalized();
qreal r3 = randomSource->generateNormalized();
// some color transformation per line goes here
randomColor.setRgbF(r1 * painterColor.redF(),
r2 * painterColor.greenF(),
r3 * painterColor.blueF());
color.fromQColor(randomColor);
m_painter->setPaintColor(color);
}
// distance based opacity
quint8 opacity = OPACITY_OPAQUE_U8;
if (m_sketchProperties.distanceOpacity) {
opacity *= qRound((1.0 - (distance / thresholdDistance)));
}
if (m_sketchProperties.randomOpacity) {
opacity *= randomSource->generateNormalized();
}
m_painter->setOpacity(opacity);
if (m_sketchProperties.magnetify) {
drawConnection(mousePosition + offsetPt, m_points.at(i) - offsetPt, currentLineWidth);
}
else {
drawConnection(mousePosition + offsetPt, mousePosition - offsetPt, currentLineWidth);
}
}
}// end of MAIN LOOP
m_count++;
QRect rc = m_dab->extent();
quint8 origOpacity = m_opacityOption.apply(painter(), pi2);
painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height());
painter()->renderMirrorMask(rc, m_dab);
painter()->setOpacity(origOpacity);
}
KisSpacingInformation KisSketchPaintOp::paintAt(const KisPaintInformation& info)
{
doPaintLine(info, info);
return updateSpacingImpl(info);
}
KisSpacingInformation KisSketchPaintOp::updateSpacingImpl(const KisPaintInformation &info) const
{
return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0,
KisLodTransform::lodToScale(painter()->device()),
&m_airbrushOption, nullptr, info);
}
KisTimingInformation KisSketchPaintOp::updateTimingImpl(const KisPaintInformation &info) const
{
return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info);
}
diff --git a/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp b/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp
index 4e8f3ebbfb..7390fc598d 100644
--- a/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp
+++ b/plugins/paintops/tangentnormal/kis_tangent_tilt_option.cpp
@@ -1,258 +1,258 @@
/* This file is part of the KDE project
*
* Copyright (C) 2015 Wolthera van Hövell tot Westerflier <griffinvalley@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_tangent_tilt_option.h"
#include <cmath>
#include <QColor>
#include <QPoint>
#include "ui_wdgtangenttiltoption.h"
#include "kis_global.h"
class KisTangentTiltOptionWidget: public QWidget, public Ui::WdgTangentTiltOptions
{
public:
KisTangentTiltOptionWidget(QWidget *parent = 0)
: QWidget(parent) {
setupUi(this);
}
};
KisTangentTiltOption::KisTangentTiltOption()
: KisPaintOpOption(KisPaintOpOption::GENERAL, false)
{
m_checkable = false;
m_options = new KisTangentTiltOptionWidget();
//Setup tangent tilt.
m_options->comboRed->setCurrentIndex(0);
m_options->comboGreen->setCurrentIndex(2);
m_options->comboBlue->setCurrentIndex(4);
m_options->sliderElevationSensitivity->setRange(0, 100, 0);
m_options->sliderElevationSensitivity->setValue(100);
m_options->sliderElevationSensitivity->setSuffix(i18n("%"));
m_options->sliderMixValue->setRange(0, 100, 0);
m_options->sliderMixValue->setValue(50);
m_options->sliderMixValue->setSuffix(i18n("%"));
connect(m_options->comboRed, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged()));
connect(m_options->comboGreen, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged()));
connect(m_options->comboBlue, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged()));
connect(m_options->comboRed, SIGNAL(currentIndexChanged(int)), m_options->TangentTiltPreview, SLOT(setRedChannel(int)));
connect(m_options->comboGreen, SIGNAL(currentIndexChanged(int)), m_options->TangentTiltPreview, SLOT(setGreenChannel(int)));
connect(m_options->comboBlue, SIGNAL(currentIndexChanged(int)), m_options->TangentTiltPreview, SLOT(setBlueChannel(int)));
connect(m_options->optionTilt, SIGNAL(toggled(bool)), SLOT(emitSettingChanged()));
connect(m_options->optionDirection, SIGNAL(toggled(bool)), SLOT(emitSettingChanged()));
connect(m_options->optionRotation, SIGNAL(toggled(bool)), SLOT(emitSettingChanged()));
connect(m_options->optionMix, SIGNAL(toggled(bool)), SLOT(emitSettingChanged()));
connect(m_options->sliderElevationSensitivity, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged()));
connect(m_options->sliderMixValue, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged()));
m_options->sliderMixValue->setVisible(false);
setConfigurationPage(m_options);
}
KisTangentTiltOption::~KisTangentTiltOption()
{
delete m_options;
}
//options
int KisTangentTiltOption::redChannel() const
{
return m_options->comboRed->currentIndex();
}
int KisTangentTiltOption::greenChannel() const
{
return m_options->comboGreen->currentIndex();
}
int KisTangentTiltOption::blueChannel() const
{
return m_options->comboBlue->currentIndex();
}
int KisTangentTiltOption::directionType() const
{
int type=0;
if (m_options->optionTilt->isChecked()==true) {
type=0;
}
else if (m_options->optionDirection->isChecked()==true) {
type=1;
}
else if (m_options->optionRotation->isChecked()==true) {
type=2;
}
else if (m_options->optionMix->isChecked()==true) {
type=3;
}
else {
warnKrita<<"There's something odd with the radio buttons. We'll use Tilt";
}
return type;
}
double KisTangentTiltOption::elevationSensitivity() const
{
return m_options->sliderElevationSensitivity->value();
}
double KisTangentTiltOption::mixValue() const
{
return m_options->sliderMixValue->value();
}
void KisTangentTiltOption::swizzleAssign(qreal const horizontal, qreal const vertical, qreal const depth, qreal *component, int index, qreal maxvalue)
{
switch(index) {
case 0: *component = horizontal; break;
case 1: *component = maxvalue-horizontal; break;
case 2: *component = vertical; break;
case 3: *component = maxvalue-vertical; break;
case 4: *component = depth; break;
case 5: *component = maxvalue-depth; break;
}
}
void KisTangentTiltOption::apply(const KisPaintInformation& info,qreal *r,qreal *g,qreal *b)
{
- //formula based on http://www.cerebralmeltdown.com/programming_projects/Altitude%20and%20Azimuth%20to%20Vector/index.html
+ //formula based on https://www.cerebralmeltdown.com/programming_projects/Altitude%20and%20Azimuth%20to%20Vector/index.html
/* It doesn't make sense of have higher than 8bit color depth.
* Instead we make sure in the paintAt function of kis_tangent_normal_paintop to pick an 8bit space of the current
* color space if the space is an RGB space. If not, it'll pick sRGB 8bit.
*/
qreal halfvalue = 0.5;
qreal maxvalue = 1.0;
//have the azimuth and altitude in degrees.
qreal direction = KisPaintInformation::tiltDirection(info, true)*360.0;
qreal elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0);
if (directionType() == 0) {
direction = KisPaintInformation::tiltDirection(info, true)*360.0;
elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0);
} else if (directionType() == 1) {
direction = (0.75 + info.drawingAngle() / (2.0 * M_PI))*360.0;
elevation= 0;//turns out that tablets that don't support tilt just return 90 degrees for elevation.
} else if (directionType() == 2) {
direction = info.rotation();
elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0);//artpens have tilt-recognition, so this should work.
} else if (directionType() == 3) {//mix of tilt+direction
qreal mixamount = mixValue()/100.0;
direction = (KisPaintInformation::tiltDirection(info, true)*360.0*(1.0-mixamount))+((0.75 + info.drawingAngle() / (2.0 * M_PI))*360.0*(mixamount));
elevation= (info.tiltElevation(info, 60.0, 60.0, true)*90.0);
}
//subtract/add the rotation of the canvas.
if (directionType() != 1) {
direction = normalizeAngleDegrees(direction - info.canvasRotation());
}
//limit the direction/elevation
//qreal elevationMax = (elevationSensitivity()*90.0)/100.0;
qreal elevationT = elevation*(elevationSensitivity()/100.0)+(90-(elevationSensitivity()*90.0)/100.0);
elevation = static_cast<int>(elevationT);
//convert to radians.
// Convert this to kis_global's radian function.
direction = kisDegreesToRadians(direction);
elevation = kisDegreesToRadians(elevation);
//make variables for axes for easy switching later on.
qreal horizontal, vertical, depth;
//spherical coordinates always center themselves around the origin, leading to values. We need to work around those...
horizontal = cos(elevation)*sin(direction);
if (horizontal>0.0) {
horizontal= halfvalue+(fabs(horizontal)*halfvalue);
}
else {
horizontal= halfvalue-(fabs(horizontal)*halfvalue);
}
vertical = cos(elevation)*cos(direction);
if (vertical>0.0) {
vertical = halfvalue+(fabs(vertical)*halfvalue);
}
else {
vertical = halfvalue-(fabs(vertical)*halfvalue);
}
if (info.canvasMirroredH()) {horizontal = maxvalue - horizontal;}
if (info.canvasMirroredV()) {vertical = maxvalue - vertical;}
depth = sin(elevation)*maxvalue;
//assign right components to correct axes.
swizzleAssign(horizontal, vertical, depth, r, redChannel(), maxvalue);
swizzleAssign(horizontal, vertical, depth, g, greenChannel(), maxvalue);
swizzleAssign(horizontal, vertical, depth, b, blueChannel(), maxvalue);
}
/*settings*/
void KisTangentTiltOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const
{
setting->setProperty(TANGENT_RED, redChannel());
setting->setProperty(TANGENT_GREEN, greenChannel());
setting->setProperty(TANGENT_BLUE, blueChannel());
setting->setProperty(TANGENT_TYPE, directionType());
setting->setProperty(TANGENT_EV_SEN, elevationSensitivity());
setting->setProperty(TANGENT_MIX_VAL, mixValue());
}
void KisTangentTiltOption::readOptionSetting(const KisPropertiesConfigurationSP setting)
{
m_options->comboRed->setCurrentIndex(setting->getInt(TANGENT_RED, 0));
m_options->comboGreen->setCurrentIndex(setting->getInt(TANGENT_GREEN, 2));
m_options->comboBlue->setCurrentIndex(setting->getInt(TANGENT_BLUE, 4));
//The comboboxes are connected to the TangentTiltPreview, so that gets automatically updated by them.
if (setting->getInt(TANGENT_TYPE)== 0){
m_options->optionTilt->setChecked(true);
m_options->sliderMixValue->setVisible(false);
}
else if (setting->getInt(TANGENT_TYPE)== 1) {
m_options->optionDirection->setChecked(true);
m_options->sliderMixValue->setVisible(false);
}
else if (setting->getInt(TANGENT_TYPE)== 2) {
m_options->optionRotation->setChecked(true);
m_options->sliderMixValue->setVisible(false);
}
else if (setting->getInt(TANGENT_TYPE)== 3) {
m_options->optionMix->setChecked(true);
m_options->sliderMixValue->setVisible(true);
}
m_options->sliderElevationSensitivity->setValue(setting->getDouble(TANGENT_EV_SEN, 100));
m_options->sliderMixValue->setValue(setting->getDouble(TANGENT_MIX_VAL, 50));
}
diff --git a/plugins/python/CMakeLists.txt b/plugins/python/CMakeLists.txt
index d82c284d08..3e46581bfd 100644
--- a/plugins/python/CMakeLists.txt
+++ b/plugins/python/CMakeLists.txt
@@ -1,117 +1,118 @@
# Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org>
# Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
# Copyright (C) 2014-2016 Boudewijn Rempt <boud@valdyas.org>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
include(CMakeParseArguments)
#
# Simple helper function to install plugin and related files
# having only a name of the plugin...
# (just to reduce syntactic noise when a lot of plugins get installed)
#
function(install_pykrita_plugin name)
set(_options)
set(_one_value_args)
set(_multi_value_args PATTERNS FILE)
cmake_parse_arguments(install_pykrita_plugin "${_options}" "${_one_value_args}" "${_multi_value_args}" ${ARGN})
if(NOT name)
message(FATAL_ERROR "Plugin filename is not given")
endif()
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py)
install(FILES kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita)
foreach(_f ${name}.py ${name}.ui ${install_pykrita_plugin_FILE})
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${_f})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${_f} DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita)
endif()
endforeach()
elseif(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${name})
install(FILES ${name}/kritapykrita_${name}.desktop DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita)
install(
DIRECTORY ${name}
DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita
FILES_MATCHING
PATTERN "*.py"
PATTERN "*.ui"
PATTERN "*.txt"
PATTERN "*.csv"
PATTERN "*.html"
PATTERN "__pycache__*" EXCLUDE
PATTERN "tests*" EXCLUDE
)
# TODO Is there any way to form a long PATTERN options string
# and use it in a single install() call?
# NOTE Install specified patterns one-by-one...
foreach(_pattern ${install_pykrita_plugin_PATTERNS})
install(
DIRECTORY ${name}
DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita
FILES_MATCHING
PATTERN "${_pattern}"
PATTERN "__pycache__*" EXCLUDE
PATTERN "tests*" EXCLUDE
)
endforeach()
else()
message(FATAL_ERROR "Do not know what to do with ${name}")
endif()
endfunction()
install_pykrita_plugin(hello)
install_pykrita_plugin(assignprofiledialog)
install_pykrita_plugin(scripter)
install_pykrita_plugin(colorspace)
install_pykrita_plugin(documenttools)
install_pykrita_plugin(filtermanager)
install_pykrita_plugin(exportlayers)
#install_pykrita_plugin(highpass)
install_pykrita_plugin(tenbrushes)
install_pykrita_plugin(tenscripts)
#install_pykrita_plugin(palette_docker) # Needs fixing -> bug 405194
install_pykrita_plugin(quick_settings_docker)
install_pykrita_plugin(lastdocumentsdocker)
# install_pykrita_plugin(scriptdocker)
install_pykrita_plugin(comics_project_management_tools)
install_pykrita_plugin(krita_script_starter)
install_pykrita_plugin(plugin_importer)
+install_pykrita_plugin(mixer_slider_docker)
# if(PYTHON_VERSION_MAJOR VERSION_EQUAL 3)
# install_pykrita_plugin(cmake_utils)
# install_pykrita_plugin(js_utils PATTERNS "*.json")
# install_pykrita_plugin(expand PATTERNS "*.expand" "templates/*.tpl")
# endif()
install( FILES
hello/hello.action
tenbrushes/tenbrushes.action
tenscripts/tenscripts.action
plugin_importer/plugin_importer.action
DESTINATION ${DATA_INSTALL_DIR}/krita/actions)
install(
DIRECTORY libkritapykrita
DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita
FILES_MATCHING
PATTERN "*.py"
PATTERN "__pycache__*" EXCLUDE
)
diff --git a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop
index 544eda162d..fb4a784a97 100644
--- a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop
+++ b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop
@@ -1,57 +1,59 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=assignprofiledialog
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Assign Profile to Image
Name[ar]=إسناد اللاحات إلى الصور
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[et]=Pildile profiili omistamine
Name[eu]=Esleitu profila irudiari
Name[fi]=Liitä kuvaan profiili
Name[fr]=Attribuer un profil à l'image
Name[gl]=Asignar un perfil á imaxe
Name[is]=Úthluta litasniði á myndina
Name[it]=Assegna profilo a immagine
Name[ko]=이미지에 프로필 할당
Name[nl]=Profiel aan afbeelding toewijzen
Name[nn]=Tildel profil til bilete
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]=为图像指定特性文件
Name[zh_TW]=指定設定檔到圖像
Comment=Assign a profile to an image without converting it.
Comment[ar]=أسنِد لاحة إلى صورة دون تحويلها.
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[et]=Pildile profiili omistamine ilma seda teisendamata.
Comment[eu]=Esleitu profil bat irudi bati hura bihurtu gabe.
Comment[fi]=Liitä kuvaan profiili muuntamatta kuvaa
Comment[fr]=Attribuer un profil à une image sans la convertir.
Comment[gl]=Asignar un perfil a unha imaxe sen convertela.
Comment[is]=Úthluta litasniði á myndina án þess að umbreyta henni.
Comment[it]=Assegna un profilo a un'immagine senza convertirla.
Comment[ko]=프로필을 변환하지 않고 이미지에 할당합니다.
Comment[nl]=Een profiel aan een afbeelding toewijzen zonder het te converteren.
Comment[nn]=Tildel fargeprofil til eit bilete utan å konvertera det til profilen
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]=仅为图像指定特性文件,不转换其色彩空间
Comment[zh_TW]=將設定檔指定給圖像,而不進行轉換。
diff --git a/plugins/python/colorspace/kritapykrita_colorspace.desktop b/plugins/python/colorspace/kritapykrita_colorspace.desktop
index 11c1c3415c..229e5afa66 100644
--- a/plugins/python/colorspace/kritapykrita_colorspace.desktop
+++ b/plugins/python/colorspace/kritapykrita_colorspace.desktop
@@ -1,59 +1,61 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=colorspace
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Color Space
Name[ar]=الفضاء اللوني
Name[ca]=Espai de color
Name[ca@valencia]=Espai de color
Name[cs]=Barevný prostor
Name[de]=Farbraum
Name[el]=Χρωματικός χώρος
Name[en_GB]=Colour Space
Name[es]=Espacio de color
+Name[et]=Värviruum
Name[eu]=Kolore-espazioa
Name[fi]=Väriavaruus
Name[fr]=Espace colorimétrique
Name[gl]=Espazo de cores
Name[is]=Litrýmd
Name[it]=Spazio dei colori
Name[ko]=색상 공간
Name[nl]=Kleurruimte
Name[nn]=Fargerom
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[tr]=Renk Aralığı
Name[uk]=Простір кольорів
Name[x-test]=xxColor Spacexx
Name[zh_CN]=色彩空间
Name[zh_TW]=色彩空間
Comment=Plugin to change color space to selected documents
Comment[ar]=ملحقة لتغيير الفضاء اللوني في المستندات المحددة
Comment[ca]=Un connector per a canviar l'espai de color dels documents seleccionats
Comment[ca@valencia]=Un connector per a canviar l'espai de color dels documents seleccionats
Comment[cs]=Modul pro změnu rozsahu barvy pro vybrané dokumenty
Comment[el]=Πρόσθετο αλλαγής χρωματικού χώρου σε επιλεγμένα έγγραφα
Comment[en_GB]=Plugin to change colour space to selected documents
Comment[es]=Complemento para cambiar el espacio de color de los documentos seleccionados
+Comment[et]=Plugin valitud dokumentide värviruumi muutmiseks
Comment[eu]=Hautatutako dokumentuei kolore-espazioa aldatzeko plugina
Comment[fi]=Liitännäinen valittujen tiedostojen väriavaruuden muuttamiseksi
Comment[fr]=Module externe pour l'espace de couleurs des documents sélectionnés
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[ko]=선택한 문서로 색상 공간을 변경하는 플러그인
Comment[nl]=Plug-in om kleurruimte in geselecteerde documenten te wijzigen
Comment[nn]=Programtillegg for å byta fargerom på utvalde dokument
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]=Plugin para alterar o espaço de cores em documentos selecionados
Comment[sv]=Insticksprogram för att ändra färgrymd för valda dokument
Comment[tr]=Seçili belgede renk aralığını değiştirmek için eklenti
Comment[uk]=Додаток для зміни простору кольорів у позначених документах
Comment[x-test]=xxPlugin to change color space to selected documentsxx
Comment[zh_CN]=用于更改选定文档色彩空间的插件
Comment[zh_TW]=用於變更色彩空間為選定檔案的外掛程式
diff --git a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop
index f72ea33d5a..4184aee956 100644
--- a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop
+++ b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop
@@ -1,58 +1,60 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=comics_project_management_tools
X-Krita-Manual=README.html
X-Python-2-Compatible=false
Name=Comics Project Management Tools
Name[ar]=أدوات إدارة المشاريع الهزليّة
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[en_GB]=Comics Project Management Tools
Name[es]=Herramientas de gestión de proyectos de cómics
+Name[et]=Koomiksite projektihalduse tööriistad
Name[eu]=Komikien proiektuak kudeatzeko tresnak
Name[fi]=Sarjakuvaprojektien hallintatyökalut
Name[fr]=Outils de gestion d'un projet de bande dessinée
Name[gl]=Ferramentas de xestión de proxectos de cómics
Name[is]=Verkefnisstjórn teiknimyndasögu
Name[it]=Strumenti per la gestione dei progetti di fumetti
Name[ko]=만화 프로젝트 관리 도구
Name[nl]=Hulpmiddelen voor projectbeheer van strips
Name[nn]=Prosjekthandsaming for teikneseriar
Name[pl]=Narzędzia do zarządzania projektami komiksów
Name[pt]=Ferramentas de Gestão de Projectos de Banda Desenhada
Name[pt_BR]=Ferramentas de gerenciamento de projeto de quadrinhos
Name[sv]=Projekthanteringsverktyg för tecknade serier
Name[tr]=Çizgi Roman Projesi Yönetimi Araçları
Name[uk]=Інструменти для керування проєктами коміксів
Name[x-test]=xxComics Project Management Toolsxx
Name[zh_CN]=漫画项目管理工具
Name[zh_TW]=漫畫專案管理工具
Comment=Tools for managing comics.
Comment[ar]=أدوات لإدارة الهزليّات.
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[en_GB]=Tools for managing comics.
Comment[es]=Herramientas para gestionar cómics.
+Comment[et]=Koomiksite haldamise tööriistad.
Comment[eu]=Komikiak kudeatzeko tresnak.
Comment[fi]=Sarjakuvien hallintatyökalut.
Comment[fr]=Outils pour gérer les bandes dessinées.
Comment[gl]=Ferramentas para xestionar cómics.
Comment[is]=Verkfæri til að stýra gerð teiknimyndasögu.
Comment[it]=Strumenti per la gestione dei fumetti.
Comment[ko]=만화 관리 도구입니다.
Comment[nl]=Hulpmiddelen voor beheer van strips.
Comment[nn]=Verktøy for handsaming av teikneseriar
Comment[pl]=Narzędzie do zarządzania komiksami.
Comment[pt]=Ferramentas para gerir bandas desenhadas.
Comment[pt_BR]=Ferramentas para gerenciar quadrinhos.
Comment[sv]=Verktyg för att hantera tecknade serier.
Comment[tr]=Çizgi romanları yönetmek için araçlar.
Comment[uk]=Інструменти для керування коміксами
Comment[x-test]=xxTools for managing comics.xx
Comment[zh_CN]=用于管理漫画的工具。
Comment[zh_TW]=管理漫畫的工具。
diff --git a/plugins/python/documenttools/kritapykrita_documenttools.desktop b/plugins/python/documenttools/kritapykrita_documenttools.desktop
index c34c388ee2..650d03a0f3 100644
--- a/plugins/python/documenttools/kritapykrita_documenttools.desktop
+++ b/plugins/python/documenttools/kritapykrita_documenttools.desktop
@@ -1,56 +1,58 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=documenttools
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Document Tools
Name[ar]=أدوات المستندات
Name[ca]=Eines de document
Name[ca@valencia]=Eines de document
Name[cs]=Dokumentové nástroje
Name[el]=Εργαλεία για έγγραφα
Name[en_GB]=Document Tools
Name[es]=Herramientas de documentos
+Name[et]=Dokumenditööriistad
Name[eu]=Dokumentuen tresnak
Name[fi]=Tiedostotyökalut
Name[fr]=Outil Document
Name[gl]=Ferramentas de documentos
Name[it]=Strumenti per i documenti
Name[ko]=문서 도구
Name[nl]=Documenthulpmiddelen
Name[nn]=Dokumentverktøy
Name[pl]=Narzędzia dokumentu
Name[pt]=Ferramentas de Documentos
Name[pt_BR]=Ferramentas de documento
Name[sv]=Dokumentverktyg
Name[tr]=Belge Araçları
Name[uk]=Засоби документа
Name[x-test]=xxDocument Toolsxx
Name[zh_CN]=文档工具
Name[zh_TW]=檔案工具
Comment=Plugin to manipulate properties of selected documents
Comment[ar]=ملحقة لتعديل خصائص المستندات المحددة
Comment[ca]=Un connector per a manipular les propietats dels documents seleccionats
Comment[ca@valencia]=Un connector per a manipular les propietats dels documents seleccionats
Comment[cs]=Modul pro správu vlastností vybraných dokumentů
Comment[el]=Πρόσθετο χειρισμού ιδιοτήτων σε επιλεγμένα έγγραφα
Comment[en_GB]=Plugin to manipulate properties of selected documents
Comment[es]=Complemento para manipular las propiedades de los documentos seleccionados
+Comment[et]=Plugin valitud dokumentide omaduste käitlemiseks
Comment[eu]=Hautatutako dokumentuen propietateak manipulatzeko plugina
Comment[fi]=Liitännäinen valittujen tiedostojen ominaisuuksien käsittelemiseksi
Comment[fr]=Module externe de gestion des propriétés des documents sélectionnés
Comment[gl]=Complemento para manipular as propiedades dos documentos seleccionados.
Comment[it]=Estensione per manipolare le proprietà dei documenti selezionati
Comment[ko]=선택한 문서의 속성을 변경하는 플러그인
Comment[nl]=Plug-in om eigenschappen van geselecteerde documenten te manipuleren
Comment[nn]=Programtillegg for å endra eigenskapar på utvalde dokument
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]=Plugin para manipular as propriedades de documentos selecionados
Comment[sv]=Insticksprogram för att ändra egenskaper för valda dokument
Comment[tr]=Seçili belgelerin özelliklerini değiştirmek için eklenti
Comment[uk]=Додаток для керування властивостями позначених документів
Comment[x-test]=xxPlugin to manipulate properties of selected documentsxx
Comment[zh_CN]=用于编辑选定文档属性的插件
Comment[zh_TW]=用於修改所選檔案屬性的外掛程式
diff --git a/plugins/python/exportlayers/kritapykrita_exportlayers.desktop b/plugins/python/exportlayers/kritapykrita_exportlayers.desktop
index 2469aeb41d..63e1511745 100644
--- a/plugins/python/exportlayers/kritapykrita_exportlayers.desktop
+++ b/plugins/python/exportlayers/kritapykrita_exportlayers.desktop
@@ -1,59 +1,61 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=exportlayers
X-Krita-Manual=Manual.html
X-Python-2-Compatible=true
Name=Export Layers
Name[ar]=تصدير الطبقات
Name[ca]=Exportació de capes
Name[ca@valencia]=Exportació de capes
Name[cs]=Exportovat vrstvy
Name[de]=Ebenen exportieren
Name[el]=Εξαγωγή επιπέδων
Name[en_GB]=Export Layers
Name[es]=Exportar capas
+Name[et]=Kihtide eksport
Name[eu]=Esportatu geruzak
Name[fi]=Vie tasoja
Name[fr]=Exporter des calques
Name[gl]=Exportar as capas
Name[is]=Flytja út lög
Name[it]=Esporta livelli
Name[ko]=레이어 내보내기
Name[nl]=Lagen exporteren
Name[nn]=Eksporter lag
Name[pl]=Eksportuj warstwy
Name[pt]=Exportar as Camadas
Name[pt_BR]=Exportar camadas
Name[sv]=Exportera lager
Name[tr]=Katmanları Dışa Aktar
Name[uk]=Експортувати шари
Name[x-test]=xxExport Layersxx
Name[zh_CN]=导出图层
Name[zh_TW]=匯出圖層
Comment=Plugin to export layers from a document
Comment[ar]=ملحقة لتصدير الطبقات من مستند
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[de]=Modul zum Exportieren von Ebenen aus einem Dokument
Comment[el]=Πρόσθετο εξαγωγής επιπέδων από έγγραφο
Comment[en_GB]=Plugin to export layers from a document
Comment[es]=Complemento para exportar las capas de un documento
+Comment[et]=Plugin dokumendi kihtide eksportimiseks
Comment[eu]=Dokumentu batetik geruzak esportatzeko plugina
Comment[fi]=Liitännäisen tiedoston tasojen viemiseksi
Comment[fr]=Module externe d'export de calques d'un document
Comment[gl]=Complemento para exportar as capas dun documento.
Comment[it]=Estensione per esportare i livelli da un documento
Comment[ko]=문서에서 레이어를 내보내는 플러그인
Comment[nl]=Plug-in om lagen uit een document te exporteren
Comment[nn]=Programtillegg for eksportering av biletlag i dokument
Comment[pl]=Wtyczka do eksportowania warstw z dokumentu
Comment[pt]='Plugin' para exportar as camadas de um documento
Comment[pt_BR]=Plugin para exportar as camadas de um documento
Comment[sv]=Insticksprogram för att exportera lager från ett dokument
Comment[tr]=Belgenin katmanlarını dışa aktarmak için eklenti
Comment[uk]=Додаток для експортування шарів з документа
Comment[x-test]=xxPlugin to export layers from a documentxx
Comment[zh_CN]=用于从文档导出图层的插件
Comment[zh_TW]=用於從檔案匯出圖層的外掛程式
diff --git a/plugins/python/filtermanager/kritapykrita_filtermanager.desktop b/plugins/python/filtermanager/kritapykrita_filtermanager.desktop
index 41a19bb39f..3be984b34b 100644
--- a/plugins/python/filtermanager/kritapykrita_filtermanager.desktop
+++ b/plugins/python/filtermanager/kritapykrita_filtermanager.desktop
@@ -1,58 +1,60 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=filtermanager
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Filter Manager
Name[ar]=مدير المرشّحات
Name[ca]=Gestor de filtres
Name[ca@valencia]=Gestor de filtres
Name[cs]=Správce filtrů
Name[de]=Filterverwaltung
Name[el]=Διαχειριστής φίλτρων
Name[en_GB]=Filter Manager
Name[es]=Gestor de filtros
+Name[et]=Filtrihaldur
Name[eu]=Iragazki-kudeatzailea
Name[fi]=Suodatinhallinta
Name[fr]=Gestionnaire de fichiers
Name[gl]=Xestor de filtros
Name[it]=Gestore dei filtri
Name[ko]=필터 관리자
Name[nl]=Beheerder van filters
Name[nn]=Filterhandsamar
Name[pl]=Zarządzanie filtrami
Name[pt]=Gestor de Filtros
Name[pt_BR]=Gerenciador de filtros
Name[sv]=Filterhantering
Name[tr]=Süzgeç Yöneticisi
Name[uk]=Керування фільтрами
Name[x-test]=xxFilter Managerxx
Name[zh_CN]=滤镜管理器
Name[zh_TW]=濾鏡管理器
Comment=Plugin to filters management
Comment[ar]=ملحقة إدارة المرشّحات
Comment[ca]=Un connector per a gestionar filtres
Comment[ca@valencia]=Un connector per a gestionar filtres
Comment[cs]=Modul pro správu filtrů
Comment[de]=Modul zum Verwalten von Filtern
Comment[el]=Πρόσθετο για τη διαχείριση φίλτρων
Comment[en_GB]=Plugin to filters management
Comment[es]=Complemento para la gestión de filtros
+Comment[et]=Plugin filtrite haldamiseks
Comment[eu]=Iragazkiak kudeatzeko plugina
Comment[fi]=Liitännäinen suodatinten hallintaan
Comment[fr]=Module externe de gestion des filtres
Comment[gl]=Complemento para a xestión de filtros.
Comment[it]=Estensione per la gestione dei filtri
Comment[ko]=필터 관리에 대한 플러그인
Comment[nl]=Plug-in voor beheer van filters
Comment[nn]=Programtillegg for filterhandsaming
Comment[pl]=Wtyczka do zarządzania filtrami
Comment[pt]='Plugin' para a gestão de filtros
Comment[pt_BR]=Plugin para gerenciamento de filtros
Comment[sv]=Insticksprogram för filterhantering
Comment[tr]=Süzgeç yönetimi için eklenti
Comment[uk]=Додаток для керування фільтрами
Comment[x-test]=xxPlugin to filters managementxx
Comment[zh_CN]=用于管理滤镜的插件。
Comment[zh_TW]=用於濾鏡管理的外掛程式
diff --git a/plugins/python/hello/kritapykrita_hello.desktop b/plugins/python/hello/kritapykrita_hello.desktop
index 1ec86ba1df..c4a0c7ceae 100644
--- a/plugins/python/hello/kritapykrita_hello.desktop
+++ b/plugins/python/hello/kritapykrita_hello.desktop
@@ -1,61 +1,63 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=hello
X-Krita-Manual=Manual.html
X-Python-2-Compatible=true
Name=Hello World
Name[ar]=مرحبا يا عالم
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[et]=Tere, maailm
Name[eu]=Kaixo mundua
Name[fi]=Hei maailma
Name[fr]=Bonjour tout le monde
Name[gl]=Ola mundo
Name[is]=Halló Heimur
Name[it]=Ciao mondo
Name[ko]=전 세계 여러분 안녕하세요
Name[nl]=Hallo wereld
Name[nn]=Hei, verda
Name[pl]=Witaj świecie
Name[pt]=Olá Mundo
Name[pt_BR]=Olá mundo
Name[sk]=Ahoj svet
Name[sv]=Hello World
Name[tg]=Салом ҷаҳон
Name[tr]=Merhaba Dünya
Name[uk]=Привіт, світе
Name[x-test]=xxHello Worldxx
Name[zh_CN]=Hello World
Name[zh_TW]=你好,世界
Comment=Basic plugin to test PyKrita
Comment[ar]=ملحقة أساسية لاختبار PyKrita
Comment[ca]=Connector bàsic per a provar el PyKrita
Comment[ca@valencia]=Connector bàsic per a provar el PyKrita
Comment[cs]=Základní modul pro testování PyKrita
Comment[de]=Basismodul zum Testen von PyKrita
Comment[el]=Βασικό πρόσθετο δοκιμής PyKrita
Comment[en_GB]=Basic plugin to test PyKrita
Comment[es]=Complemento básico para probar PyKrita
+Comment[et]=Baasplugin PyKrita testimiseks
Comment[eu]=PyKrita probatzeko plugina
Comment[fi]=Perusliitännäinen PyKritan kokeilemiseksi
Comment[fr]=Module externe élémentaire pour tester PyKrita
Comment[gl]=Complemento básico para probar PyKrita.
Comment[it]=Estensione di base per provare PyKrita
Comment[ko]=PyKrita 테스트용 기본 플러그인
Comment[nl]=Basisplug-in om PyKrita te testen
Comment[nn]=Enkelt programtillegg for testing av PyKrita
Comment[pl]=Podstawowa wtyczka do wypróbowania PyKrity
Comment[pt]='Plugin' básico para testar o PyKrita
Comment[pt_BR]=Plugin básico para testar o PyKrita
Comment[sv]=Enkelt insticksprogram för att utprova PyKrita
Comment[tr]=PyKrita'yı test etmek için temel eklenti
Comment[uk]=Базовий додаток для тестування PyKrita
Comment[x-test]=xxBasic plugin to test PyKritaxx
Comment[zh_CN]=用于测试 PyKrita 的简易插件
Comment[zh_TW]=測試 PyKrita 的基本外掛程式
diff --git a/plugins/python/highpass/kritapykrita_highpass.desktop b/plugins/python/highpass/kritapykrita_highpass.desktop
index e40f7724c3..bb6471a1cc 100644
--- a/plugins/python/highpass/kritapykrita_highpass.desktop
+++ b/plugins/python/highpass/kritapykrita_highpass.desktop
@@ -1,53 +1,55 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=highpass
X-Python-2-Compatible=false
Name=Highpass Filter
Name[ca]=Filtre passaalt
Name[ca@valencia]=Filtre passaalt
Name[cs]=Filtr s horní propustí
Name[de]=Hochpassfilter
Name[el]=Υψιπερατό φίλτρο
Name[en_GB]=Highpass Filter
Name[es]=Filtro paso alto
+Name[et]=Kõrgpääsfilter
Name[eu]=Goi-igaropeneko iragazkia
Name[fr]=Filtre passe-haut
Name[gl]=Filtro de paso alto
Name[it]=Filtro di accentuazione passaggio
Name[ko]=하이패스 필터
Name[nl]=Hoogdoorlaatfilter
Name[nn]=Høgpass-filter
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]=高通滤镜
Name[zh_TW]=高通濾鏡
Comment=Highpass Filter, based on http://registry.gimp.org/node/7385
Comment[ca]=Filtre passaalt, basat en el http://registry.gimp.org/node/7385
Comment[ca@valencia]=Filtre passaalt, 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[et]=Kõrgpääsfilter, mille aluseks on http://registry.gimp.org/node/7385
Comment[eu]=Goi-igaropeneko iragazkia, honetan oinarritua http://registry.gimp.org/node/7385
Comment[fr]=Filtre passe-haut, fondé sur 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[ko]=http://registry.gimp.org/node/7385에 기반한 하이패스 필터
Comment[nl]=Hoogdoorlaatfilter, gebaseerd op http://registry.gimp.org/node/7385
Comment[nn]=Høgpass-filter, basert på 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
Comment[zh_TW]=高通濾鏡,基於 http://registry.gimp.org/node/7385
diff --git a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop
index 6a13606e33..c67432b5d7 100644
--- a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop
+++ b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop
@@ -1,45 +1,49 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=krita_script_starter
X-Python-2-Compatible=false
X-Krita-Manual=Manual.html
Name=Krita Script Starter
Name[ca]=Iniciador de scripts del Krita
Name[ca@valencia]=Iniciador de scripts del Krita
Name[cs]=Spouštěč skriptů Krita
Name[en_GB]=Krita Script Starter
Name[es]=Iniciador de guiones de Krita
+Name[et]=Krita skriptialustaja
Name[eu]=Krita-ren script abiarazlea
Name[gl]=Iniciador de scripts de Krita
Name[it]=Iniziatore di script per Krita
Name[ko]=Krita 스크립트 시작 도구
Name[nl]=Script-starter van Krita
Name[nn]=Krita skriptbyggjar
Name[pl]=Starter skryptów Krity
Name[pt]=Inicialização do Programa do Krita
+Name[pt_BR]=Inicializador de script do Krita
Name[sv]=Krita skriptstart
Name[tr]=Krita Betik Başlatıcı
Name[uk]=Створення скрипту Krita
Name[x-test]=xxKrita Script Starterxx
Name[zh_CN]=Krita 空脚本生成器
Name[zh_TW]=Krita 指令啟動器
Comment=Create the metadata and file structure for a new Krita script
Comment[ca]=Crea les metadades i l'estructura de fitxers d'un script nou del Krita
Comment[ca@valencia]=Crea les metadades i l'estructura de fitxers d'un script nou del Krita
Comment[en_GB]=Create the metadata and file structure for a new Krita script
Comment[es]=Crear los metadatos y la estructura de archivos para un nuevo guion de Krita
+Comment[et]=Uue Krita skripti metaandmete ja failistruktuuri loomine
Comment[eu]=Sortu Krita-script berri baterako meta-datuak eta fitxategi egitura
Comment[gl]=Crear os metadatos e a estrutura de ficheiros para un novo script de Krita.
Comment[it]=Crea i metadati e la struttura dei file per un nuovo script di Krita
Comment[ko]=새 Krita 스크립트에 대한 메타데이터 및 파일 구조 생성
Comment[nl]=Maak de metagegevens en bestandsstructuur voor een nieuw Krita-script
Comment[nn]=Generer metadata og filstruktur for nye Krita-skript
Comment[pl]=Utwórz metadane i strukturę plików dla nowego skryptu Krity
Comment[pt]=Cria os meta-dados e a estrutura de ficheiros para um novo programa do Krita
+Comment[pt_BR]=Cria os metadados e a estrutura de arquivos para um novo script do Krita
Comment[sv]=Skapa metadata och filstruktur för ett nytt Krita-skript
Comment[tr]=Yeni Krita betiği için üstveri ve dosya yapısı oluştur
Comment[uk]=Створення метаданих і структури файлів для нового скрипту Krita
Comment[x-test]=xxCreate the metadata and file structure for a new Krita scriptxx
Comment[zh_CN]=为新的 Krita 脚本创建元数据及文件结构
Comment[zh_TW]=為新的 Krita 指令稿建立中繼資料和檔案建構體
diff --git a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop
index 750d85fd0b..d2d146060a 100644
--- a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop
+++ b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop
@@ -1,52 +1,56 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=lastdocumentsdocker
X-Python-2-Compatible=false
X-Krita-Manual=Manual.html
Name=Last Documents Docker
Name[ar]=رصيف بآخر المستندات
Name[ca]=Acoblador Darrers documents
Name[ca@valencia]=Acoblador Darrers documents
Name[el]=Προσάρτηση τελευταίων εγγράφοων
Name[en_GB]=Last Documents Docker
Name[es]=Panel de últimos documentos
+Name[et]=Viimaste dokumentide dokk
Name[eu]=Azken dokumentuen panela
Name[fi]=Viimeisimpien tiedostojen telakka
Name[fr]=Récemment ouverts
Name[gl]=Doca dos últimos documentos
Name[it]=Area di aggancio Ultimi documenti
Name[ko]=마지막 문서 도킹 패널
Name[nl]=Laatste documenten verankering
Name[nn]=Dokk for nyleg opna dokument
Name[pl]=Dok ostatnich dokumentów
Name[pt]=Área dos Últimos Documentos
+Name[pt_BR]=Área dos últimos documentos
Name[sv]=Dockningsfönster för senaste dokument
Name[tr]=Son Belgeler Doku
Name[uk]=Бічна панель останніх документів
Name[x-test]=xxLast Documents Dockerxx
Name[zh_CN]=最近文档工具面板
Name[zh_TW]=「最後檔案」面板
Comment=A Python-based docker for show thumbnails to last ten documents
Comment[ar]=رصيف بِ‍«پيثون» لعرض مصغّرات آخر ١٠ مستندات مفتوحة
Comment[ca]=Un acoblador basat en Python per a mostrar miniatures dels darrers deu documents
Comment[ca@valencia]=Un acoblador basat en Python per a mostrar miniatures dels darrers deu documents
Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την εμφάνιση επισκοπήσεων των δέκα τελευταίων εγγράφων
Comment[en_GB]=A Python-based docker for show thumbnails to last ten documents
Comment[es]=Un panel basado en Python para mostrar miniaturas de los últimos diez documentos
+Comment[et]=Pythoni-põhine dokk viimase kümne dokumendi pisipildi näitamiseks
Comment[eu]=Azken hamar dokumentuen koadro-txikiak erakusteko Python-oinarridun panel bat
Comment[fi]=Python-pohjainen telakka viimeisimpien kymmenen tiedoston pienoiskuvien näyttämiseen
Comment[fr]=Panneau en Python pour afficher les vignettes des dix derniers documents
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[ko]=10개의 문서를 표시할 수 있는 Python 기반 도킹 패널
Comment[nl]=Een op Python gebaseerde vastzetter om miniaturen te tonen naar de laatste tien documenten.
Comment[nn]=Python-basert dokk for vising av miniatyrbilete av dei siste ti dokumenta
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[pt_BR]=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[tr]=Son on belgenin küçük resmini göstermek için Python-tabanlı bir dok
Comment[uk]=Бічна панель на основі Python для показу мініатюр останніх десяти документів
Comment[x-test]=xxA Python-based docker for show thumbnails to last ten documentsxx
Comment[zh_CN]=这是一个基于 Python 的工具面板插件,它可以显示最近十个文档的缩略图
Comment[zh_TW]=基於 Python 的面板,用於顯示最後 10 個檔案縮圖
diff --git a/plugins/python/mixer_slider_docker/Manual.html b/plugins/python/mixer_slider_docker/Manual.html
new file mode 100644
index 0000000000..52215d9ef1
--- /dev/null
+++ b/plugins/python/mixer_slider_docker/Manual.html
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head><title>Krita-docker-color-slider Plugin Manual</title>
+</head>
+<body>
+
+<h1 id="mixer-slider">Mixer Slider Docker</h1>
+<p>A docker which allows you to choose from the gradient between two colors.</p>
+<h2 id="basic-usage">Basic Usage</h2>
+<p>Go to <strong>Settings -&gt; Configure Krita -&gt; Python Plugin Manager</strong>.
+ Enable "Mixer Slider docker."</p>
+<p>Right-click on menu bar, and enable "Mixer Slider Docker." A docker will appear in the window.
+The button on the left with the text "S" is for settings. Click on it to set the number of slider lines.</p>
+<p>The right part of the docker contains the sliders. Each line has a left color, a slider bar, and a right color.</p>
+<p>Click the left/right color to set it to the current foreground color. Click and move on the slider to set
+ the current foreground color to the color your mouse is hovering. An indicator will appear to show the color
+you just selected.</p>
+
+</body>
+</html>
diff --git a/plugins/python/mixer_slider_docker/__init__.py b/plugins/python/mixer_slider_docker/__init__.py
new file mode 100644
index 0000000000..f78612fc8e
--- /dev/null
+++ b/plugins/python/mixer_slider_docker/__init__.py
@@ -0,0 +1,19 @@
+'''
+ Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
+
+ This file is part of Krita-docker-color-slider.
+
+ Krita-docker-color-slider is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Krita-docker-color-slider is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Krita-docker-color-slider. If not, see <https://www.gnu.org/licenses/>.
+'''
+from .mixer_slider_docker import *
diff --git a/plugins/python/mixer_slider_docker/color_slider.py b/plugins/python/mixer_slider_docker/color_slider.py
new file mode 100644
index 0000000000..d728b0f8f0
--- /dev/null
+++ b/plugins/python/mixer_slider_docker/color_slider.py
@@ -0,0 +1,138 @@
+'''
+ Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
+
+ This file is part of Krita-docker-color-slider.
+
+ Krita-docker-color-slider is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Krita-docker-color-slider is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Krita-docker-color-slider. If not, see <https://www.gnu.org/licenses/>.
+'''
+from PyQt5.QtWidgets import QWidget
+from PyQt5.QtGui import QPixmap, QPainter, QColor, QBrush, QPolygon
+from PyQt5.QtCore import QPoint
+from krita import ManagedColor
+
+
+class ColorSlider(QWidget):
+ default_color = ManagedColor("", "", "")
+
+ def __init__(self, docker, left_color=default_color, right_color=default_color, parent=None):
+ super(ColorSlider, self).__init__(parent)
+ self.docker = docker
+ self.left_color = left_color
+ self.right_color = right_color
+ self.slider_pixmap = None
+ self.value_x = None
+ self.cursor_fill_color = QColor.fromRgbF(1, 1, 1, 1)
+ self.cursor_outline_color = QColor.fromRgbF(0, 0, 0, 1)
+ self.need_redraw = True
+
+ def set_color(self, pos, color):
+ if pos == 'left':
+ if self.left_color is not color:
+ self.left_color = color
+ self.need_redraw = True
+ else:
+ if self.right_color is not color:
+ self.right_color = color
+ self.need_redraw = True
+
+ def update_slider(self):
+ '''
+ Update the slider to a gradient between the two colors.
+
+ The painting of the slider comes from the program Krita. The original code can be accessed
+ at the following URL.
+ https://github.com/KDE/krita/blob/master/plugins/dockers/advancedcolorselector/kis_shade_selector_line.cpp
+ '''
+ if self.need_redraw:
+ patch_count = self.width()
+ base_hsva = list(self.docker.managedcolor_to_qcolor(self.left_color).getHsvF())
+ dest_hsva = list(self.docker.managedcolor_to_qcolor(self.right_color).getHsvF())
+ diff_hsva = [(dest_hsva[i] - base_hsva[i]) for i in range(4)]
+ if dest_hsva[0] == -1.0:
+ diff_hsva[0] = 0
+ elif base_hsva[0] == -1.0:
+ diff_hsva[0] = 0
+ base_hsva[0] = dest_hsva[0]
+ elif diff_hsva[0] > 0.5: # make sure the sliding goes through a minor arc
+ diff_hsva[0] = diff_hsva[0] - 1.0
+ elif diff_hsva[0] < -0.5:
+ diff_hsva[0] = diff_hsva[0] + 1.0
+
+ step_hsva = [x / patch_count for x in diff_hsva]
+
+ self.slider_pixmap = QPixmap(self.width(), self.height())
+ painter = QPainter(self.slider_pixmap)
+
+ for i in range(patch_count):
+ hue = base_hsva[0] + i * step_hsva[0]
+ while hue < 0.0:
+ hue += 1.0
+
+ while hue > 1.0:
+ hue -= 1.0
+
+ saturation = base_hsva[1] + i * step_hsva[1]
+ value = base_hsva[2] + i * step_hsva[2]
+ cur_color = QColor.fromHsvF(hue, saturation, value)
+ painter.fillRect(i, 0, 1, self.height(), cur_color)
+
+ painter.end()
+
+ self.need_redraw = False
+
+ widget_painter = QPainter(self)
+ self.rendered_image = self.slider_pixmap.toImage()
+
+ widget_painter.drawImage(0, 0, self.rendered_image)
+ if self.value_x is not None:
+ start_x = self.value_x
+ start_y = self.height() / 2
+ delta_x = self.height() / 3
+ delta_y = self.height() / 3
+ points = [QPoint(start_x, start_y),
+ QPoint(start_x - delta_x, start_y + delta_y),
+ QPoint(start_x + delta_x, start_y + delta_y)]
+ widget_painter.setBrush(QBrush(self.cursor_fill_color))
+ widget_painter.setPen(self.cursor_outline_color)
+ widget_painter.drawPolygon(QPolygon(points))
+
+ def paintEvent(self, event):
+ self.update_slider()
+
+ def resizeEvent(self, event): # after resizing the widget, force-redraw the underlying slider
+ self.need_redraw = True
+
+ def adjust_pos_x(self, x): # adjust the x to make it in the range of [0, width - 1]
+ if x < 0:
+ return 0
+ if x >= self.width():
+ return self.width() - 1
+ return x
+
+ def mouseMoveEvent(self, event):
+ pos = event.pos()
+ self.value_x = self.adjust_pos_x(pos.x())
+ self.update()
+
+ def mouseReleaseEvent(self, event):
+ pos = event.pos()
+ self.value_x = self.adjust_pos_x(pos.x())
+ y = int(self.height() / 2)
+ fixed_pos = QPoint(self.value_x, y)
+ color = self.rendered_image.pixelColor(fixed_pos)
+ mc = self.docker.qcolor_to_managedcolor(color)
+ if self.docker.canvas() is not None:
+ if self.docker.canvas().view() is not None:
+ self.docker.canvas().view().setForeGroundColor(mc)
+ self.update()
diff --git a/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop b/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop
new file mode 100644
index 0000000000..2cd1bdd5c6
--- /dev/null
+++ b/plugins/python/mixer_slider_docker/kritapykrita_mixer_slider_docker.desktop
@@ -0,0 +1,26 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Krita/PythonPlugin
+X-KDE-Library=mixer_slider_docker
+X-Python-2-Compatible=false
+X-Krita-Manual=Manual.html
+Name=Mixer Slider docker
+Name[ca]=Acoblador Control lliscant del mesclador
+Name[es]=Panel del deslizador del mezclador
+Name[et]=Mikseri liuguri dokk
+Name[nl]=Vastzetter van schuifregelaar van mixer
+Name[pt]=Área da Barra de Mistura
+Name[sv]=Dockningsfönster för blandningsreglage
+Name[uk]=Бічна панель повзунка мікшера
+Name[x-test]=xxMixer Slider dockerxx
+Name[zh_TW]=混色滑動條工具面板
+Comment=A color slider.
+Comment[ca]=Un control lliscant per al color.
+Comment[es]=Deslizador de color.
+Comment[et]=Värviliugur.
+Comment[nl]=Een schuifregelaar voor kleur
+Comment[pt]=Uma barra de cores.
+Comment[sv]=Ett färgreglage.
+Comment[uk]=Повзунок кольору.
+Comment[x-test]=xxA color slider.xx
+Comment[zh_TW]=色彩滑動條。
diff --git a/plugins/python/mixer_slider_docker/mixer_slider_docker.py b/plugins/python/mixer_slider_docker/mixer_slider_docker.py
new file mode 100644
index 0000000000..e3d86bfba8
--- /dev/null
+++ b/plugins/python/mixer_slider_docker/mixer_slider_docker.py
@@ -0,0 +1,131 @@
+'''
+ Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
+
+ This file is part of Krita-docker-color-slider.
+
+ Krita-docker-color-slider is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Krita-docker-color-slider is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Krita-docker-color-slider. If not, see <https://www.gnu.org/licenses/>.
+'''
+from PyQt5.QtGui import QColor
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton
+
+from krita import Krita, DockWidget, ManagedColor, DockWidgetFactory, DockWidgetFactoryBase
+
+from .slider_line import SliderLine
+from .ui_mixer_slider_docker import UIMixerSliderDocker
+
+
+class MixerSliderDocker(DockWidget):
+ # Init the docker
+
+ def __init__(self):
+ super(MixerSliderDocker, self).__init__()
+
+ main_program = Krita.instance()
+ settings = main_program.readSetting("", "MixerSliderColors",
+ "RGBA,U8,sRGB-elle-V2-srgbtrc.icc,1,0.8,0.4,1|" +
+ "RGBA,U8,sRGB-elle-V2-srgbtrc.icc,0,0,0,1") # alpha=1 == non-transparent
+
+ self.default_left_color = self.qcolor_to_managedcolor(QColor.fromRgbF(0.4, 0.8, 1, 1))
+ self.default_right_color = self.qcolor_to_managedcolor(QColor.fromRgbF(0, 0, 0, 1))
+
+ # make base-widget and layout
+ self.widget = QWidget()
+ self.sliders = []
+ self.top_layout = QVBoxLayout()
+ self.main_layout = QHBoxLayout()
+ self.top_layout.addLayout(self.main_layout)
+ self.top_layout.addStretch(0)
+ self.settings_button = QPushButton()
+ icon = main_program.icon("configure")
+ self.settings_button.setIcon(icon)
+ self.settings_button.setToolTip(i18n('Change settings'))
+ self.settings_button.setMaximumSize(30, 30)
+ self.main_layout.addWidget(self.settings_button)
+ self.layout = QVBoxLayout()
+ self.layout.setSpacing(0)
+ self.main_layout.addLayout(self.layout)
+ for line in settings.split(";"):
+ colors = line.split('|')
+ if len(colors) < 2: # discard old configurations
+ continue
+ left_color = self.parse_color(colors[0].split(','))
+ right_color = self.parse_color(colors[1].split(','))
+ widget = SliderLine(left_color, right_color, self)
+ self.sliders.append(widget)
+ self.layout.addWidget(widget)
+
+ self.widget.setLayout(self.top_layout)
+ self.setWindowTitle(i18n("Mixer Slider Docker"))
+ self.setWidget(self.widget)
+ [x.show() for x in self.sliders]
+
+ self.settings_button.clicked.connect(self.init_ui)
+
+ def settings_changed(self):
+ if self.ui.line_edit is not None:
+ num_sliders = int(self.ui.line_edit.text())
+ if len(self.sliders) > num_sliders:
+ for extra_line in self.sliders[num_sliders:]:
+ self.layout.removeWidget(extra_line)
+ extra_line.setParent(None)
+
+ self.sliders = self.sliders[0:num_sliders]
+ elif len(self.sliders) < num_sliders:
+ for i in range(num_sliders - len(self.sliders)):
+ widget = SliderLine(self.default_left_color, self.default_right_color, self)
+ self.sliders.append(widget)
+ self.layout.addWidget(widget)
+ self.write_settings()
+
+ def get_color_space(self):
+ if self.canvas() is not None:
+ if self.canvas().view() is not None:
+ canvas_color = self.canvas().view().foregroundColor()
+ return ManagedColor(canvas_color.colorModel(), canvas_color.colorDepth(), canvas_color.colorProfile())
+ return ManagedColor('RGBA', 'U8', 'sRGB-elle-V2-srgbtrc.icc')
+
+ def init_ui(self):
+ self.ui = UIMixerSliderDocker()
+ self.ui.initialize(self)
+
+ def write_settings(self):
+ main_program = Krita.instance()
+ setting = ';'.join(
+ [self.color_to_settings(line.left) + '|' + self.color_to_settings(line.right)
+ for line in self.sliders])
+
+ main_program.writeSetting("", "MixerSliderColors", setting)
+
+ def color_to_settings(self, managedcolor):
+ return ','.join([managedcolor.colorModel(),
+ managedcolor.colorDepth(),
+ managedcolor.colorProfile()] +
+ [str(c) for c in managedcolor.components()])
+
+ def parse_color(self, array):
+ color = ManagedColor(array[0], array[1], array[2])
+ color.setComponents([float(x) for x in array[3:]])
+ return color
+
+ def canvasChanged(self, canvas):
+ pass
+
+ def qcolor_to_managedcolor(self, qcolor):
+ mc = ManagedColor.fromQColor(qcolor, self.canvas())
+ return mc
+
+ def managedcolor_to_qcolor(self, managedcolor):
+ return managedcolor.colorForCanvas(self.canvas())
+
+Application.addDockWidgetFactory(DockWidgetFactory("mixer_slider_docker", DockWidgetFactoryBase.DockRight, MixerSliderDocker))
diff --git a/plugins/python/mixer_slider_docker/settings_dialog.py b/plugins/python/mixer_slider_docker/settings_dialog.py
new file mode 100644
index 0000000000..a50b01016f
--- /dev/null
+++ b/plugins/python/mixer_slider_docker/settings_dialog.py
@@ -0,0 +1,34 @@
+'''
+ Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
+
+ This file is part of Krita-docker-color-slider.
+
+ Krita-docker-color-slider is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Krita-docker-color-slider is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Krita-docker-color-slider. If not, see <https://www.gnu.org/licenses/>.
+'''
+from PyQt5.QtWidgets import QDialog
+
+
+class SettingsDialog(QDialog):
+ def __init__(self, ui_mixer_slider, parent=None):
+ super(SettingsDialog, self).__init__(parent)
+
+ self.ui_mixer_slider = ui_mixer_slider
+
+ def accept(self):
+ self.ui_mixer_slider.docker.settings_changed()
+
+ super(SettingsDialog, self).accept()
+
+ def closeEvent(self, event):
+ event.accept()
diff --git a/plugins/python/mixer_slider_docker/slider_line.py b/plugins/python/mixer_slider_docker/slider_line.py
new file mode 100644
index 0000000000..efad28e2c3
--- /dev/null
+++ b/plugins/python/mixer_slider_docker/slider_line.py
@@ -0,0 +1,103 @@
+'''
+ Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
+
+ This file is part of Krita-docker-color-slider.
+
+ Krita-docker-color-slider is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Krita-docker-color-slider is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Krita-docker-color-slider. If not, see <https://www.gnu.org/licenses/>.
+'''
+from PyQt5.QtWidgets import QHBoxLayout, QWidget
+from PyQt5.QtGui import QPixmap, QPainter
+from PyQt5.Qt import pyqtSlot, pyqtSignal
+
+from .color_slider import ColorSlider
+
+
+class SliderBtn(QWidget):
+ clicked = pyqtSignal()
+
+ def __init__(self, parent=None):
+ super(SliderBtn, self).__init__(parent)
+
+ def set_color(self, qcolor):
+ self.color = qcolor
+ self.update()
+
+ def update_color(self):
+ color_sq = QPixmap(self.width(), self.height())
+ color_sq.fill(self.color)
+ image = color_sq.toImage()
+
+ painter = QPainter(self)
+ painter.drawImage(0, 0, image)
+
+ def paintEvent(self, event):
+ self.update_color()
+
+ def mouseReleaseEvent(self, event):
+ self.clicked.emit()
+
+
+class SliderLine(QWidget):
+ def __init__(self, left_color, right_color, docker, parent=None):
+ super(SliderLine, self).__init__(parent)
+ self.left_button = SliderBtn()
+ self.right_button = SliderBtn()
+ self.docker = docker
+ self.color_slider = ColorSlider(docker)
+ self.layout = QHBoxLayout()
+ self.layout.setContentsMargins(2, 2, 2, 2)
+ self.setLayout(self.layout)
+ self.layout.addWidget(self.left_button)
+ self.layout.addWidget(self.color_slider)
+ self.layout.addWidget(self.right_button)
+ self.left_button.clicked.connect(self.slot_update_left_color)
+ self.right_button.clicked.connect(self.slot_update_right_color)
+ self.set_color('left', left_color)
+ self.set_color('right', right_color)
+ self.left_button.setMinimumSize(30, 30)
+ self.left_button.setMaximumSize(30, 30)
+ self.right_button.setMinimumSize(30, 30)
+ self.right_button.setMaximumSize(30, 30)
+ self.color_slider.setMaximumHeight(30)
+
+ def set_color(self, pos, color):
+ button_to_set = None
+ if pos == 'left':
+ self.left = color
+ button_to_set = self.left_button
+ else:
+ self.right = color
+ button_to_set = self.right_button
+
+ self.color_slider.set_color(pos, color)
+
+ button_to_set.set_color(self.docker.managedcolor_to_qcolor(color))
+
+ @pyqtSlot()
+ def slot_update_left_color(self):
+ if self.docker.canvas() is not None:
+ if self.docker.canvas().view() is not None:
+ self.set_color('left', self.docker.canvas().view().foregroundColor())
+ self.color_slider.value_x = 0 # set the cursor to the left-most
+ self.color_slider.update()
+ self.docker.write_settings()
+
+ @pyqtSlot()
+ def slot_update_right_color(self):
+ if self.docker.canvas() is not None:
+ if self.docker.canvas().view() is not None:
+ self.set_color('right', self.docker.canvas().view().foregroundColor())
+ self.color_slider.value_x = self.color_slider.width() - 1
+ self.color_slider.update()
+ self.docker.write_settings()
diff --git a/plugins/python/mixer_slider_docker/ui_mixer_slider_docker.py b/plugins/python/mixer_slider_docker/ui_mixer_slider_docker.py
new file mode 100644
index 0000000000..92d0674a1c
--- /dev/null
+++ b/plugins/python/mixer_slider_docker/ui_mixer_slider_docker.py
@@ -0,0 +1,56 @@
+'''
+ Copyright (C) 2019 Tusooa Zhu <tusooa@vista.aero>
+
+ This file is part of Krita-docker-color-slider.
+
+ Krita-docker-color-slider is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Krita-docker-color-slider is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Krita-docker-color-slider. If not, see <https://www.gnu.org/licenses/>.
+'''
+from PyQt5.QtWidgets import QDialogButtonBox, QLabel, QVBoxLayout, QHBoxLayout, QSpinBox
+from PyQt5.QtGui import QIntValidator
+from PyQt5.QtCore import Qt
+import krita
+
+from .settings_dialog import SettingsDialog
+
+
+class UIMixerSliderDocker(object):
+ def __init__(self):
+ self.krita_instance = krita.Krita.instance()
+ self.main_dialog = SettingsDialog(self, self.krita_instance.activeWindow().qwindow())
+
+ self.button_box = QDialogButtonBox(self.main_dialog)
+ self.vbox = QVBoxLayout(self.main_dialog)
+ self.hbox = QHBoxLayout(self.main_dialog)
+ self.line_edit = None
+
+ self.button_box.accepted.connect(self.main_dialog.accept)
+ self.button_box.rejected.connect(self.main_dialog.reject)
+
+ self.button_box.setOrientation(Qt.Horizontal)
+ self.button_box.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+
+ def initialize(self, docker):
+ self.docker = docker
+
+ self.vbox.addLayout(self.hbox)
+ self.hbox.addWidget(QLabel(i18n('Number of slider lines: ')))
+ self.line_edit = QSpinBox()
+ self.line_edit.setValue(len(docker.sliders))
+ self.hbox.addWidget(self.line_edit)
+
+ self.vbox.addWidget(self.button_box)
+
+ self.main_dialog.show()
+ self.main_dialog.activateWindow()
+ self.main_dialog.exec_()
diff --git a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop
index 2603892208..ba9eab094c 100644
--- a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop
+++ b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop
@@ -1,55 +1,59 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=palette_docker
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Palette docker
Name[ar]=رصيف اللوحات
Name[ca]=Acoblador Paleta
Name[ca@valencia]=Acoblador Paleta
Name[cs]=Dok palet
Name[de]=Paletten-Docker
Name[el]=Προσάρτηση παλέτας
Name[en_GB]=Palette docker
Name[es]=Panel de paleta
+Name[et]=Paletidokk
Name[eu]=Paleta-panela
Name[fi]=Palettitelakka
Name[fr]=Panneau de palette
Name[gl]=Doca de paleta
Name[is]=Tengikví fyrir litaspjald
Name[it]=Area di aggancio della tavolozza
Name[ko]=팔레트 도킹 패널
Name[nl]=Vastzetter van palet
Name[nn]=Palettdokk
Name[pl]=Dok palety
Name[pt]=Área acoplável da paleta
+Name[pt_BR]=Área da paleta
Name[sv]=Dockningsfönster för palett
Name[tr]=Palet doku
Name[uk]=Панель палітри
Name[x-test]=xxPalette dockerxx
Name[zh_CN]=调色板工具面板
Name[zh_TW]=「調色盤」面板
Comment=A Python-based docker to edit color palettes.
Comment[ar]=رصيف بِ‍«پيثون» لتحرير لوحات الألوان.
Comment[ca]=Un acoblador basat en Python per editar paletes de colors.
Comment[ca@valencia]=Un acoblador basat en Python per editar paletes de colors.
Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την επεξεργασία παλετών χρώματος.
Comment[en_GB]=A Python-based docker to edit colour palettes.
Comment[es]=Un panel basado en Python para editar paletas de colores.
+Comment[et]=Pythoni-põhine dokk värvipalettide muutmiseks.
Comment[eu]=Kolore-paletak editatzeko Python-oinarridun paleta bat.
Comment[fi]=Python-pohjainen telakka väripalettien muokkaamiseen.
Comment[fr]=Panneau en Python pour éditer les palettes de couleurs.
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[ko]=색상 팔레트를 편집할 수 있는 Python 기반 도킹 패널입니다.
Comment[nl]=Een op Python gebaseerde vastzetter om kleurpaletten te bewerken.
Comment[nn]=Python-basert dokk for redigering av fargepalettar
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[pt_BR]=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[tr]=Renk paletlerini düzenlemek için Python-tabanlı bir dok.
Comment[uk]=Бічна панель для редагування палітр кольорів на основі Python.
Comment[x-test]=xxA Python-based docker to edit color palettes.xx
Comment[zh_CN]=基于 Python 的调色板编辑器工具面板
Comment[zh_TW]=基於 Python 的面板,用於編輯調色盤。
diff --git a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop
index 6dc82d0dda..2e8133a3fc 100644
--- a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop
+++ b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop
@@ -1,44 +1,48 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=plugin_importer
X-Python-2-Compatible=false
X-Krita-Manual=manual.html
Name=Python Plugin Importer
Name[ca]=Importador de connectors en Python
Name[ca@valencia]=Importador de connectors en Python
Name[cs]=Importér modulů pro Python
Name[en_GB]=Python Plugin Importer
Name[es]=Importador de complementos de Python
+Name[et]=Pythoni plugina importija
Name[gl]=Importador de complementos de Python
Name[it]=Importatore estensioni Python
Name[ko]=Python 플러그인 가져오기 도구
Name[nl]=Importeur van Plugin voor Python
Name[nn]=Importering av Python-tillegg
Name[pl]=Import wtyczek Pythona
Name[pt]=Importador de 'Plugins' do Python
+Name[pt_BR]=Importador de plugins em Python
Name[sv]=Python-insticksimport
Name[tr]=Python Eklenti İçe Aktarıcı
Name[uk]=Засіб імпортування додатків Python
Name[x-test]=xxPython Plugin Importerxx
Name[zh_CN]=Python 插件导入器
Name[zh_TW]=Python 外掛程式匯入工具
Comment=Imports Python plugins from zip files.
Comment[ca]=Importa connectors en Python a partir de fitxers «zip».
Comment[ca@valencia]=Importa connectors en Python a partir de fitxers «zip».
Comment[cs]=Importuje moduly Pythonu ze souborů zip.
Comment[en_GB]=Imports Python plugins from zip files.
Comment[es]=Importa complementos de Python desde archivos zip.
+Comment[et]=Pythoni pluginate import zip-failidest.
Comment[gl]=Importa complementos de Python de ficheiros zip.
Comment[it]=Importa le estensioni Python dai file compressi.
Comment[ko]=ZIP 파일에서 Python 플러그인을 가져옵니다.
Comment[nl]=Importeert Python-plug-ins uit zip-bestanden.
Comment[nn]=Importerer Python-baserte programtillegg frå .zip-filer
Comment[pl]=Importuj wtyczki Pythona z plików zip.
Comment[pt]=Importa os 'plugins' em Python a partir de ficheiros Zip.
+Comment[pt_BR]=Importa plugins em Python a partir de arquivos zip.
Comment[sv]=Importerar Python-insticksprogram från zip-filer.
Comment[tr]=Python eklentilerini zip dosyasından içe aktarır.
Comment[uk]=Імпортує додатки Python з файлів zip.
Comment[x-test]=xxImports Python plugins from zip files.xx
Comment[zh_CN]=从 zip 文件导入 Python 插件。
Comment[zh_TW]=匯入 Zip 檔案中的 Python 外掛程式。
diff --git a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop
index 86cce72c36..6b8ba2d988 100644
--- a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop
+++ b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop
@@ -1,53 +1,57 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=quick_settings_docker
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Quick Settings Docker
Name[ar]=رصيف بإعدادات سريعة
Name[ca]=Acoblador Arranjament ràpid
Name[ca@valencia]=Acoblador Arranjament ràpid
Name[cs]=Dok pro rychlé nastavení
Name[el]=Προσάρτηση γρήγορων ρυθμίσεων
Name[en_GB]=Quick Settings Docker
Name[es]=Panel de ajustes rápidos
+Name[et]=Kiirseadistuste dokk
Name[eu]=Ezarpen azkarren panela
Name[fi]=Pika-asetustelakka
Name[fr]=Réglages rapides
Name[gl]=Doca de configuración rápida
Name[it]=Area di aggancio delle impostazioni rapide
Name[ko]=빠른 설정 도킹 패널
Name[nl]=Verankering voor snelle instellingen
Name[nn]=Snøgginnstillingar-dokk
Name[pl]=Dok szybkich ustawień
Name[pt]=Área de Configuração Rápida
+Name[pt_BR]=Área de configuração rápida
Name[sv]=Dockningspanel med snabbinställningar
Name[tr]=Hızlı Ayarlar Doku
Name[uk]=Панель швидких параметрів
Name[x-test]=xxQuick Settings Dockerxx
Name[zh_CN]=快速设置工具面板
Name[zh_TW]=「快速設定」面板
Comment=A Python-based docker for quickly changing brush size and opacity.
Comment[ar]=رصيف بِ‍«پيثون» لتغيير حجم الفرشاة وشفافيّتها بسرعة.
Comment[ca]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell.
Comment[ca@valencia]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell.
Comment[el]=Ένα εργαλείο προσάρτησης σε Python για γρήγορη αλλαγή του μεγέθους και της αδιαφάνειας του πινέλου.
Comment[en_GB]=A Python-based docker for quickly changing brush size and opacity.
Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel.
+Comment[et]=Pythoni-põhine dokk pintsli suuruse ja läbipaistmatuse kiireks muutmiseks.
Comment[eu]=Isipu-neurria eta -opakotasuna aldatzeko Python-oinarridun panel bat.
Comment[fi]=Python-pohjainen telakka siveltimen koon ja läpikuultavuuden nopean muuttamiseen.
Comment[fr]=Panneau en python pour modifier rapidement la taille et l'opacité des brosses.
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[ko]=브러시 크기와 불투명도를 빠르게 변경할 수 있는 Python 기반 도킹 패널입니다.
Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking.
Comment[nn]=Python-basert dokk for kjapp endring av penselstorleik/-tettleik
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[pt_BR]=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[tr]=Fırça boyutunu ve matlığını hızlıca değiştirmek için Python-tabanlı bir dok.
Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля.
Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx
Comment[zh_CN]=这是一个基于 Python 的工具面板,用于快速更改笔刷尺寸和透明度。
Comment[zh_TW]=基於 Python 的面板,用於快速變更筆刷尺寸和不透明度。
diff --git a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop
index b37503456d..de7e0cf2ae 100644
--- a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop
+++ b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop
@@ -1,50 +1,54 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=scriptdocker
X-Python-2-Compatible=false
Name=Script Docker
Name[ar]=رصيف سكربتات
Name[ca]=Acoblador Script
Name[ca@valencia]=Acoblador Script
Name[cs]=Dok skriptu
Name[el]=Προσάρτηση σεναρίων
Name[en_GB]=Script Docker
Name[es]=Panel de guiones
+Name[et]=Skriptidokk
Name[eu]=Script-panela
Name[fi]=Skriptitelakka
Name[fr]=Panneau de script
Name[gl]=Doca de scripts
Name[it]=Area di aggancio degli script
Name[ko]=스크립트 도킹 패널
Name[nl]=Verankering van scripts
Name[nn]=Skriptdokk
Name[pl]=Dok skryptów
Name[pt]=Área de Programas
+Name[pt_BR]=Área de scripts
Name[sv]=Dockningsfönster för skript
Name[tr]=Betik Doku
Name[uk]=Бічна панель скриптів
Name[x-test]=xxScript Dockerxx
Name[zh_CN]=脚本工具面板
Name[zh_TW]=「指令稿」面板
Comment=A Python-based docker for create actions and point to Python scripts
Comment[ar]=رصيف بِ‍«پيثون» لإنشاء الإجراءات والإشارة إلى سكربتات «پيثون»
Comment[ca]=Un acoblador basat en Python per a crear accions i apuntar a scripts en Python
Comment[ca@valencia]=Un acoblador basat en Python per a crear accions i apuntar a scripts en Python
Comment[el]=Ένα εργαλείο προσάρτησης σε Python για τη δημιουργία ενεργειών και τη δεικτοδότηση σεναρίων σε Python
Comment[en_GB]=A Python-based docker for create actions and point to Python scripts
Comment[es]=Un panel basado en Python para crear y apuntar a guiones de Python
+Comment[et]=Pythoni-põhine dokk toimingute loomiseks ja viitamiseks Pythoni skriptidele
Comment[eu]=Python-oinarridun panel bat.
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 script Python
Comment[ko]=작업 생성 및 Python 스크립트를 가리키는 Python 기반 도킹 패널
Comment[nl]=Een op Python gebaseerde vastzetter voor aanmaakacties en wijzen naar Python-scripts
Comment[nn]=Python-basert dokk for å laga handlingar og peika til Python-skript
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[pt_BR]=Uma área acoplável feita em Python para criar ações e apontar para scripts em Python
Comment[sv]=Ett Python-baserat dockningsfönster för att skapa åtgärder och peka ut Python-skript
Comment[tr]=Eylemler oluşturmak ve Python betiklerine yönlendirmek için Python-tabanlı bir dok
Comment[uk]=Бічна панель для створення дій і керування скриптами на основі Python.
Comment[x-test]=xxA Python-based docker for create actions and point to Python scriptsxx
Comment[zh_CN]=这是一个基于 Python 的工具面板,用于创建操作,并将他们指向 Python 脚本。
Comment[zh_TW]=基於 Python 的面板,用於建立動作並指向 Python 指令稿
diff --git a/plugins/python/scripter/kritapykrita_scripter.desktop b/plugins/python/scripter/kritapykrita_scripter.desktop
index e79dde80b6..d157294c5e 100644
--- a/plugins/python/scripter/kritapykrita_scripter.desktop
+++ b/plugins/python/scripter/kritapykrita_scripter.desktop
@@ -1,49 +1,53 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=scripter
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Scripter
Name[ca]=Scripter
Name[ca@valencia]=Scripter
Name[de]=Scripter
Name[el]=Σενάρια
Name[en_GB]=Scripter
Name[es]=Guionador
+Name[et]=Skriptija
Name[eu]=Script egilea
Name[fr]=Scripter
Name[gl]=Executor de scripts
Name[it]=Scripter
Name[ko]=스크립트 도구
Name[nl]=Scriptmaker
Name[nn]=Skriptkøyrer
Name[pl]=Skrypter
Name[pt]=Programador
+Name[pt_BR]=Criador de scripts
Name[sv]=Skriptgenerator
Name[tr]=Betik yazarı
Name[uk]=Скриптер
Name[x-test]=xxScripterxx
Name[zh_CN]=脚本工具
Name[zh_TW]=指令稿編寫者
Comment=Plugin to execute ad-hoc Python code
Comment[ca]=Connector per executar codi «ad hoc» en Python
Comment[ca@valencia]=Connector per executar codi «ad hoc» en Python
Comment[el]=Πρόσθετο για την εκτέλεση συγκεκριμένου κώδικα Python
Comment[en_GB]=Plugin to execute ad-hoc Python code
Comment[es]=Complemento para ejecutar código Python a medida
+Comment[et]=Plugin kohapeal loodud Pythoni koodi täitmiseks
Comment[eu]=Berariaz egindako Python kodea exekutatzeko plugina
Comment[fi]=Liitännäinen satunnaisen Python-koodin suorittamiseksi
Comment[gl]=Complemento para executar código de Python escrito no momento.
Comment[it]=Estensione per eseguire ad-hoc codice Python
Comment[ko]=즉석에서 Python 코드를 실행하는 플러그인
Comment[nl]=Plug-in om ad-hoc Python code uit te voeren
Comment[nn]=Programtillegg for køyring av ad hoc Python-kode
Comment[pl]=Wtyczka do wykonywania kodu Pythona ad-hoc
Comment[pt]='Plugin' para executar código em Python arbitrário
+Comment[pt_BR]=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 代码的插件
Comment[zh_TW]=外掛程式,用於執行特定 Python 程式碼
diff --git a/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop b/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
index 1b463b43a6..b5075186db 100644
--- a/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
+++ b/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
@@ -1,50 +1,54 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=selectionsbagdocker
X-Python-2-Compatible=false
Name=Selections Bag Docker
Name[ca]=Acoblador Bossa de seleccions
Name[ca@valencia]=Acoblador Bossa de seleccions
Name[el]=Προσάρτηση σάκου επιλογών
Name[en_GB]=Selections Bag Docker
Name[es]=Panel de selecciones
+Name[et]=Valikukarbi dokk
Name[eu]=Hautapen-zakua panela
Name[fr]=Outils de sélection
Name[gl]=Doca de bolsa das seleccións
Name[it]=Area di raccolta selezioni
Name[ko]=선택 가방 도킹 패널
Name[nl]=Docker van zak met selecties
Name[nn]=Utvalssamlingsdokk
Name[pl]=Dok worka zaznaczeń
Name[pt]=Área de Selecções
+Name[pt_BR]=Área de seleções
Name[sv]=Dockningspanel med markeringspåse
Name[tr]=Seçim Çantası Doku
Name[uk]=Бічна панель позначеного
Name[x-test]=xxSelections Bag Dockerxx
Name[zh_CN]=选区列表工具面板
Name[zh_TW]=「選取範圍收藏」面板
Comment=A docker that allow to store a list of selections
Comment[ar]=رصيف يتيح تخزين قائمة تحديدات
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[et]=Dokk valikute salvestamiseks
Comment[eu]=Hautapen zerrenda bat biltegiratzen uzten duen panel bat
Comment[fi]=Telakka, joka sallii tallentaa valintaluettelon
Comment[fr]=Panneau permettant de conserver une liste de sélections
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[ko]=선택 목록을 저장할 수 있는 도킹 패널
Comment[nl]=Een docker die een lijst met selecties kan opslaan
Comment[nn]=Dokk for lagring av ei liste med utval
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[pt_BR]=Uma área acoplável que permite armazenar uma lista de seleçõ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 bir dok
Comment[uk]=Бічна панель, на якій можна зберігати список позначеного
Comment[x-test]=xxA docker that allow to store a list of selectionsxx
Comment[zh_CN]=用于保存一组选区的工具面板
Comment[zh_TW]=允許儲存選取範圍列表的面板
diff --git a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop
index bb8dd0a6bd..bd6c0ad96c 100644
--- a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop
+++ b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop
@@ -1,52 +1,56 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=tenbrushes
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Ten Brushes
Name[ar]=عشرُ فُرش
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[et]=Kümme pintslit
Name[eu]=Hamar isipu
Name[fi]=Kymmenen sivellintä
Name[fr]=Raccourcis des préréglages de brosses
Name[gl]=Dez pinceis
Name[is]=Tíu penslar
Name[it]=Dieci pennelli
Name[ko]=10개의 브러시
Name[nl]=Tien penselen
Name[nn]=Ti penslar
Name[pl]=Dziesięć pędzli
Name[pt]=Dez Pincéis
+Name[pt_BR]=Dez pincéis
Name[sv]=Tio penslar
Name[tr]=On Fırça
Name[uk]=Десять пензлів
Name[x-test]=xxTen Brushesxx
Name[zh_CN]=常用笔刷快捷键
Name[zh_TW]=10 個筆刷
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[et]=Valmisvaliku omistamine Ctrl+1 kuni Ctrl+0
Comment[eu]=Aurrezarpena ezarri ktrl-1'etik ktrl-0'ra arte
Comment[fi]=Kytke esiasetukset Ctrl-1:stä Ctrl-0:aan
Comment[gl]=Asigne unha predefinición do Ctrl+1 ao Ctrl+0.
Comment[it]=Assegna una preimpostazione per ctrl-1 a ctrl-0
Comment[ko]=Ctrl+1부터 Ctrl+0까지 프리셋 할당
Comment[nl]=Een voorinstelling toekennen aan Ctrl-1 tot Ctrl-0
Comment[nn]=Byt til ferdigpenslar med «Ctrl + 1» til «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[pt_BR]=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 的快捷键
Comment[zh_TW]=將預設指定給 ctrl-1 至 ctrl-0
diff --git a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop
index 16a93d1691..3f29e2c249 100644
--- a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop
+++ b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop
@@ -1,51 +1,55 @@
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=tenscripts
X-Python-2-Compatible=true
X-Krita-Manual=Manual.html
Name=Ten Scripts
Name[ar]=عشرُ سكربتات
Name[ca]=Deu scripts
Name[ca@valencia]=Deu scripts
Name[en_GB]=Ten Scripts
Name[es]=Diez guiones
+Name[et]=Kümme skripti
Name[eu]=Hamar script
Name[fi]=Kymmenen skriptiä
Name[fr]=Raccourcis des scripts
Name[gl]=Dez scripts
Name[is]=Tíu skriftur
Name[it]=Dieci script
Name[ko]=10개의 스크립트
Name[nl]=Tien scripts
Name[nn]=Ti skript
Name[pl]=Skrypty Ten
Name[pt]=Dez Programas
+Name[pt_BR]=Dez scripts
Name[sv]=Tio skript
Name[tr]=On Betik
Name[uk]=Десять скриптів
Name[x-test]=xxTen Scriptsxx
Name[zh_CN]=常用脚本快捷键
Name[zh_TW]=10 個指令稿
Comment=A Python-based plugin for creating ten actions and assign them to Python scripts
Comment[ar]=ملحقة بِ‍«پيثون» لإنشاء ١٠ إجراءات وإسنادها إلى سكربتات «پيثون»
Comment[ca]=Un connector basat en Python per a crear deu accions i assignar-les a scripts en Python
Comment[ca@valencia]=Un connector basat en Python per a crear deu accions i assignar-les a scripts en Python
Comment[en_GB]=A Python-based plugin for creating ten actions and assign them to Python scripts
Comment[es]=Un complemento basado en Python para crear diez acciones y asignarlas a guiones de Python
+Comment[et]=Pythoni-põhine plugin kümne toimingu loomiseks ja nende omistamiseks Pythoni skriptidele
Comment[eu]=Hamar ekintza sortu eta haiek Python-scriptei esleitzeko Python-oinarridun plugin bat
Comment[fi]=Python-pohjainen liitännäinen kymmenen toiminnon luomiseksi kytkemiseksi Python-skripteihin
Comment[fr]=Module externe basé sur Python pour créer dix actions et les assigner à des scripts Python
Comment[gl]=Un complemento escrito en Python para crear dez accións e asignalas a scripts escritos en Python.
Comment[it]=Un'estensione basata su Python per creare dieci azioni e assegnarle a script Python
Comment[ko]=10개의 작업을 생성하고 이를 Python 스크립트에 할당하는 Python 기반 플러그인
Comment[nl]=Een op Python gebaseerde plug-in voor aanmaken van tien acties en ze dan toewijzen aan Python-scripts
Comment[nn]=Python-basert tillegg som legg til ti handlingar du kan tildela til Python-skript
Comment[pl]=Wtyczka oparta na Pythonie do tworzenia działań i przypisywanie ich skryptom Pythona
Comment[pt]=Um 'plugin' feito em Python para criar dez acções e atribuí-las a programas em Python
+Comment[pt_BR]=Um plugin feito em Python para criar dez ações e atribuí-las a scripts em Python
Comment[sv]=Ett Python-baserat insticksprogram för att skapa tio åtgärder och tilldela dem till Python-skript
Comment[tr]=On eylem oluşturmak ve Python betiklerine atamak için Python tabanlı bir eklenti
Comment[uk]=Скрипт на основі Python для створення десяти дій і прив'язування до них скриптів Python
Comment[x-test]=xxA Python-based plugin for creating ten actions and assign them to Python scriptsxx
Comment[zh_CN]=这是一个基于 Python 编写的插件,它可以创建十种操作,并将它们指定到特定 Python 脚本。
Comment[zh_TW]=基於 Python 的外掛程式,用於建立 10 個動作並且將它們指定給 Python 指令稿
diff --git a/plugins/tools/basictools/kis_tool_colorpicker.cc b/plugins/tools/basictools/kis_tool_colorpicker.cc
index 4795c1b243..19a090d52e 100644
--- a/plugins/tools/basictools/kis_tool_colorpicker.cc
+++ b/plugins/tools/basictools/kis_tool_colorpicker.cc
@@ -1,372 +1,421 @@
/*
* Copyright (c) 1999 Matthias Elter <me@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2018 Emmet & Eoin O'Neill <emmetoneill.pdx@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_colorpicker.h"
#include <boost/thread/locks.hpp>
#include <QMessageBox>
#include "kis_cursor.h"
#include "KisDocument.h"
#include "kis_canvas2.h"
#include "KisReferenceImagesLayer.h"
#include "KoCanvasBase.h"
#include "kis_random_accessor_ng.h"
#include "KoResourceServerProvider.h"
#include <KoMixColorsOp.h>
#include "kis_wrapped_rect.h"
#include "kis_tool_utils.h"
namespace
{
// GUI ComboBox index constants
const int SAMPLE_MERGED = 0;
}
KisToolColorPicker::KisToolColorPicker(KoCanvasBase *canvas)
: KisTool(canvas, KisCursor::pickerCursor()),
m_config(new KisToolUtils::ColorPickerConfig)
{
setObjectName("tool_colorpicker");
m_isActivated = false;
m_optionsWidget = 0;
m_pickedColor = KoColor();
+ KoResourceServer<KoColorSet> *srv = KoResourceServerProvider::instance()->paletteServer();
+ srv->addObserver(this);
}
KisToolColorPicker::~KisToolColorPicker()
{
if (m_isActivated) {
m_config->save(m_toolActivationSource == KisTool::DefaultActivation);
}
+ KoResourceServer<KoColorSet> *srv = KoResourceServerProvider::instance()->paletteServer();
+ srv->removeObserver(this);
}
void KisToolColorPicker::paint(QPainter &gc, const KoViewConverter &converter)
{
Q_UNUSED(gc);
Q_UNUSED(converter);
}
void KisToolColorPicker::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
m_isActivated = true;
m_toolActivationSource = activation;
m_config->load(m_toolActivationSource == KisTool::DefaultActivation);
updateOptionWidget();
KisTool::activate(activation, shapes);
}
void KisToolColorPicker::deactivate()
{
m_config->save(m_toolActivationSource == KisTool::DefaultActivation);
m_isActivated = false;
KisTool::deactivate();
}
bool KisToolColorPicker::pickColor(const QPointF &pos)
{
// Timer check.
if (m_colorPickerDelayTimer.isActive()) {
return false;
}
else {
m_colorPickerDelayTimer.setSingleShot(true);
m_colorPickerDelayTimer.start(100);
}
QScopedPointer<boost::lock_guard<KisImage>> imageLocker;
m_pickedColor.setOpacity(0.0);
// Pick from reference images.
if (m_optionsWidget->cmbSources->currentIndex() == SAMPLE_MERGED) {
auto *kisCanvas = dynamic_cast<KisCanvas2 *>(canvas());
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(kisCanvas, false);
KisSharedPtr<KisReferenceImagesLayer> referenceImageLayer =
kisCanvas->imageView()->document()->referenceImagesLayer();
if (referenceImageLayer && kisCanvas->referenceImagesDecoration()->visible()) {
QColor color = referenceImageLayer->getPixel(pos);
if (color.isValid()) {
m_pickedColor.fromQColor(color);
}
}
}
if (m_pickedColor.opacityU8() == OPACITY_TRANSPARENT_U8) {
if (!currentImage()->bounds().contains(pos.toPoint()) &&
!currentImage()->wrapAroundModePermitted()) {
return false;
}
KisPaintDeviceSP dev;
if (m_optionsWidget->cmbSources->currentIndex() != SAMPLE_MERGED &&
currentNode() && currentNode()->colorPickSourceDevice()) {
dev = currentNode()->colorPickSourceDevice();
}
else {
imageLocker.reset(new boost::lock_guard<KisImage>(*currentImage()));
dev = currentImage()->projection();
}
KoColor previousColor = canvas()->resourceManager()->foregroundColor();
KisToolUtils::pickColor(m_pickedColor, dev, pos.toPoint(), &previousColor, m_config->radius, m_config->blend);
}
if (m_config->updateColor &&
m_pickedColor.opacityU8() != OPACITY_TRANSPARENT_U8) {
KoColor publicColor = m_pickedColor;
publicColor.setOpacity(OPACITY_OPAQUE_U8); // Alpha is unwanted for FG and BG colors.
if (m_config->toForegroundColor) {
canvas()->resourceManager()->setResource(KoCanvasResourceProvider::ForegroundColor, publicColor);
}
else {
canvas()->resourceManager()->setResource(KoCanvasResourceProvider::BackgroundColor, publicColor);
}
}
return true;
}
void KisToolColorPicker::beginPrimaryAction(KoPointerEvent *event)
{
bool sampleMerged = m_optionsWidget->cmbSources->currentIndex() == SAMPLE_MERGED;
if (!sampleMerged) {
if (!currentNode()) {
QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot pick a color as no layer is active."));
event->ignore();
return;
}
if (!currentNode()->visible()) {
QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot pick a color as the active layer is not visible."));
event->ignore();
return;
}
}
QPoint pos = convertToImagePixelCoordFloored(event);
setMode(KisTool::PAINT_MODE);
bool picked = pickColor(pos);
if (!picked) {
// Color picking has to start in the visible part of the layer
event->ignore();
return;
}
displayPickedColor();
}
void KisToolColorPicker::continuePrimaryAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
QPoint pos = convertToImagePixelCoordFloored(event);
pickColor(pos);
displayPickedColor();
}
#include "kis_display_color_converter.h"
void KisToolColorPicker::endPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
if (m_config->addColorToCurrentPalette) {
KisSwatch swatch;
swatch.setColor(m_pickedColor);
// We don't ask for a name, too intrusive here
- KoColorSet *palette = m_palettes.at(m_optionsWidget->cmbPalette->currentIndex());
+ KoColorSet *palette = m_palettes.at(m_optionsWidget->cmbPalette->currentIndex());
palette->add(swatch);
if (!palette->save()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Cannot write to palette file %1. Maybe it is read-only.", palette->filename()));
- }
+ }
}
}
struct PickedChannel {
QString name;
QString valueText;
};
void KisToolColorPicker::displayPickedColor()
{
if (m_pickedColor.data() && m_optionsWidget) {
QList<KoChannelInfo *> channels = m_pickedColor.colorSpace()->channels();
m_optionsWidget->listViewChannels->clear();
QVector<PickedChannel> pickedChannels;
for (int i = 0; i < channels.count(); ++i) {
pickedChannels.append(PickedChannel());
}
for (int i = 0; i < channels.count(); ++i) {
PickedChannel pc;
pc.name = channels[i]->name();
if (m_config->normaliseValues) {
pc.valueText = m_pickedColor.colorSpace()->normalisedChannelValueText(m_pickedColor.data(), i);
} else {
pc.valueText = m_pickedColor.colorSpace()->channelValueText(m_pickedColor.data(), i);
}
pickedChannels[channels[i]->displayPosition()] = pc;
}
Q_FOREACH (const PickedChannel &pc, pickedChannels) {
QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels);
item->setText(0, pc.name);
item->setText(1, pc.valueText);
}
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
KoColor newColor = kritaCanvas->displayColorConverter()->applyDisplayFiltering(m_pickedColor, Float32BitsColorDepthID);
QVector<float> values(4);
newColor.colorSpace()->normalisedChannelsValue(newColor.data(), values);
for (int i = 0; i < values.size(); i++) {
QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels);
item->setText(0, QString("DisplayCh%1").arg(i));
item->setText(1, QString::number(values[i]));
}
}
}
QWidget* KisToolColorPicker::createOptionWidget()
{
m_optionsWidget = new ColorPickerOptionsWidget(0);
m_optionsWidget->setObjectName(toolId() + " option widget");
m_optionsWidget->listViewChannels->setSortingEnabled(false);
// 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);
// Initialize blend KisSliderSpinBox
m_optionsWidget->blend->setRange(0,100);
m_optionsWidget->blend->setSuffix(i18n("%"));
updateOptionWidget();
connect(m_optionsWidget->cbUpdateCurrentColor, SIGNAL(toggled(bool)), SLOT(slotSetUpdateColor(bool)));
connect(m_optionsWidget->cbNormaliseValues, SIGNAL(toggled(bool)), SLOT(slotSetNormaliseValues(bool)));
connect(m_optionsWidget->cbPalette, SIGNAL(toggled(bool)),
SLOT(slotSetAddPalette(bool)));
connect(m_optionsWidget->radius, SIGNAL(valueChanged(int)),
SLOT(slotChangeRadius(int)));
connect(m_optionsWidget->blend, SIGNAL(valueChanged(int)),
SLOT(slotChangeBlend(int)));
connect(m_optionsWidget->cmbSources, SIGNAL(currentIndexChanged(int)),
SLOT(slotSetColorSource(int)));
KoResourceServer<KoColorSet> *srv = KoResourceServerProvider::instance()->paletteServer();
if (!srv) {
return m_optionsWidget;
}
QList<KoColorSet*> palettes = srv->resources();
Q_FOREACH (KoColorSet *palette, palettes) {
if (palette) {
m_optionsWidget->cmbPalette->addSqueezedItem(palette->name());
m_palettes.append(palette);
}
}
return m_optionsWidget;
}
void KisToolColorPicker::updateOptionWidget()
{
if (!m_optionsWidget) return;
m_optionsWidget->cbNormaliseValues->setChecked(m_config->normaliseValues);
m_optionsWidget->cbUpdateCurrentColor->setChecked(m_config->updateColor);
m_optionsWidget->cmbSources->setCurrentIndex(SAMPLE_MERGED + !m_config->sampleMerged);
m_optionsWidget->cbPalette->setChecked(m_config->addColorToCurrentPalette);
m_optionsWidget->radius->setValue(m_config->radius);
m_optionsWidget->blend->setValue(m_config->blend);
}
void KisToolColorPicker::setToForeground(bool newValue)
{
m_config->toForegroundColor = newValue;
emit toForegroundChanged();
}
bool KisToolColorPicker::toForeground() const
{
return m_config->toForegroundColor;
}
void KisToolColorPicker::slotSetUpdateColor(bool state)
{
m_config->updateColor = state;
}
void KisToolColorPicker::slotSetNormaliseValues(bool state)
{
m_config->normaliseValues = state;
displayPickedColor();
}
void KisToolColorPicker::slotSetAddPalette(bool state)
{
m_config->addColorToCurrentPalette = state;
}
void KisToolColorPicker::slotChangeRadius(int value)
{
m_config->radius = value;
}
void KisToolColorPicker::slotChangeBlend(int value)
{
m_config->blend = value;
}
void KisToolColorPicker::slotSetColorSource(int value)
{
m_config->sampleMerged = value == SAMPLE_MERGED;
}
-void KisToolColorPicker::slotAddPalette(KoResource *resource)
+void KisToolColorPicker::unsetResourceServer()
{
- KoColorSet *palette = dynamic_cast<KoColorSet*>(resource);
- if (palette) {
- m_optionsWidget->cmbPalette->addSqueezedItem(palette->name());
- m_palettes.append(palette);
+ KoResourceServer<KoColorSet> *srv = KoResourceServerProvider::instance()->paletteServer();
+ srv->removeObserver(this);
+}
+
+void KisToolColorPicker::resourceAdded(KoColorSet* resource){
+ if(!resource || !m_optionsWidget) return;
+ if(m_palettes.contains(resource)) return;
+
+ if(m_config->addColorToCurrentPalette){
+ updateCmbPalette();
+ }
+}
+
+void KisToolColorPicker::removingResource(KoColorSet* resource){
+ if(!resource || !m_optionsWidget) return;
+ if(!m_palettes.contains(resource)) return;
+
+ if(m_config->addColorToCurrentPalette){
+ updateCmbPalette();
+ }
+}
+
+void KisToolColorPicker::updateCmbPalette(){
+ m_optionsWidget->cmbPalette->clear();
+ m_palettes.clear();
+
+ KoResourceServer<KoColorSet> *srv = KoResourceServerProvider::instance()->paletteServer();
+
+ if (!srv) {
+ return ;
}
+
+ QList<KoColorSet*> palettes = srv->resources();
+
+ Q_FOREACH (KoColorSet *palette, palettes) {
+ if (palette) {
+ m_optionsWidget->cmbPalette->addSqueezedItem(palette->name());
+ m_palettes.append(palette);
+ }
+ }
+
}
+
+void KisToolColorPicker::resourceChanged(PointerType /*resource*/)
+{}
+
+void KisToolColorPicker::syncTaggedResourceView() {}
+
+void KisToolColorPicker::syncTagAddition(const QString& /*tag*/) {}
+
+void KisToolColorPicker::syncTagRemoval(const QString& /*tag*/) {}
\ No newline at end of file
diff --git a/plugins/tools/basictools/kis_tool_colorpicker.h b/plugins/tools/basictools/kis_tool_colorpicker.h
index ce485f1995..a7c078570e 100644
--- a/plugins/tools/basictools/kis_tool_colorpicker.h
+++ b/plugins/tools/basictools/kis_tool_colorpicker.h
@@ -1,142 +1,152 @@
/*
* Copyright (c) 1999 Matthias Elter <elter@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2018 Emmet & Eoin O'Neill <emmetoneill.pdx@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_COLOR_PICKER_H_
#define KIS_TOOL_COLOR_PICKER_H_
#include <QTimer>
#include "KoToolFactoryBase.h"
#include "ui_wdgcolorpicker.h"
#include "kis_tool.h"
#include <kis_icon.h>
+#include <KoResourceServerObserver.h>
+
class KoResource;
class KoColorSet;
namespace KisToolUtils {
struct ColorPickerConfig;
}
class ColorPickerOptionsWidget : public QWidget, public Ui::ColorPickerOptionsWidget
{
Q_OBJECT
public:
ColorPickerOptionsWidget(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
-class KisToolColorPicker : public KisTool
+class KisToolColorPicker : public KisTool,public KoResourceServerObserver<KoColorSet>
{
Q_OBJECT
Q_PROPERTY(bool toForeground READ toForeground WRITE setToForeground NOTIFY toForegroundChanged)
public:
KisToolColorPicker(KoCanvasBase *canvas);
~KisToolColorPicker() override;
public:
struct Configuration {
Configuration();
bool toForegroundColor;
bool updateColor;
bool addPalette;
bool normaliseValues;
bool sampleMerged;
int radius;
int blend;
void save(ToolActivation activation) const;
void load(ToolActivation activation);
};
public:
QWidget* createOptionWidget() override;
void beginPrimaryAction(KoPointerEvent *event) override;
void continuePrimaryAction(KoPointerEvent *event) override;
void endPrimaryAction(KoPointerEvent *event) override;
void paint(QPainter &gc, const KoViewConverter &converter) override;
bool toForeground() const;
+public: //KoResourceServerObserver
+ void unsetResourceServer() override;
+ void resourceAdded(KoColorSet* resource) override;
+ void removingResource(KoColorSet* resource) override;
+ void resourceChanged(KoColorSet* resource) override;
+ void syncTaggedResourceView() override;
+ void syncTagAddition(const QString& tag) override;
+ void syncTagRemoval(const QString& tag) override;
+
Q_SIGNALS:
void toForegroundChanged();
protected:
void activate(ToolActivation activation, const QSet<KoShape*> &) override;
void deactivate() override;
public Q_SLOTS:
void setToForeground(bool newValue);
void slotSetUpdateColor(bool);
void slotSetNormaliseValues(bool);
void slotSetAddPalette(bool);
void slotChangeRadius(int);
void slotChangeBlend(int);
- void slotAddPalette(KoResource* resource);
void slotSetColorSource(int value);
private:
void displayPickedColor();
bool pickColor(const QPointF& pos);
void updateOptionWidget();
-
+ void updateCmbPalette();
// Configuration
QScopedPointer<KisToolUtils::ColorPickerConfig> m_config;
ToolActivation m_toolActivationSource;
bool m_isActivated;
KoColor m_pickedColor;
// Used to skip some tablet events and update color less often
QTimer m_colorPickerDelayTimer;
ColorPickerOptionsWidget *m_optionsWidget;
QList<KoColorSet*> m_palettes;
};
class KisToolColorPickerFactory : public KoToolFactoryBase
{
public:
KisToolColorPickerFactory()
: KoToolFactoryBase("KritaSelected/KisToolColorPicker") {
setToolTip(i18n("Color Selector Tool"));
setSection(TOOL_TYPE_FILL);
setPriority(2);
setIconName(koIconNameCStr("krita_tool_color_picker"));
setShortcut(QKeySequence(Qt::Key_P));
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
}
~KisToolColorPickerFactory() override {}
KoToolBase *createTool(KoCanvasBase *canvas) override {
return new KisToolColorPicker(canvas);
}
};
#endif // KIS_TOOL_COLOR_PICKER_H_
diff --git a/plugins/tools/basictools/kis_tool_line_helper.cpp b/plugins/tools/basictools/kis_tool_line_helper.cpp
index 28a268a608..f823469b3e 100644
--- a/plugins/tools/basictools/kis_tool_line_helper.cpp
+++ b/plugins/tools/basictools/kis_tool_line_helper.cpp
@@ -1,190 +1,262 @@
/*
* 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_tool_line_helper.h"
+#include <QtMath>
+
#include "kis_algebra_2d.h"
#include "kis_painting_information_builder.h"
#include "kis_image.h"
+#include "kis_canvas_resource_provider.h"
+#include <brushengine/kis_paintop_preset.h>
+
struct KisToolLineHelper::Private
{
Private(KisPaintingInformationBuilder *_infoBuilder)
: infoBuilder(_infoBuilder),
useSensors(true),
enabled(true)
{
}
QVector<KisPaintInformation> linePoints;
KisPaintingInformationBuilder *infoBuilder;
bool useSensors;
bool enabled;
};
KisToolLineHelper::KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder,
const KUndo2MagicString &transactionText)
: KisToolFreehandHelper(infoBuilder,
transactionText,
new KisSmoothingOptions(false)),
m_d(new Private(infoBuilder))
{
}
KisToolLineHelper::~KisToolLineHelper()
{
delete m_d;
}
void KisToolLineHelper::setEnabled(bool value)
{
m_d->enabled = value;
}
void KisToolLineHelper::setUseSensors(bool value)
{
m_d->useSensors = value;
}
void KisToolLineHelper::repaintLine(KoCanvasResourceProvider *resourceManager,
KisImageWSP image, KisNodeSP node,
KisStrokesFacade *strokesFacade)
{
if (!m_d->enabled) return;
cancelPaint();
if (m_d->linePoints.isEmpty()) return;
qreal startAngle = 0.0;
if (m_d->linePoints.length() > 1) {
startAngle = KisAlgebra2D::directionBetweenPoints(m_d->linePoints[0].pos(),
m_d->linePoints[1].pos(),
0.0);
}
+
+ KisPaintOpPresetSP preset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset)
+ .value<KisPaintOpPresetSP>();
+
+ if (preset->settings()->paintOpSize() <= 1) {
+ KisPaintInformation begin = m_d->linePoints.first();
+ KisPaintInformation end = m_d->linePoints.last();
+ m_d->linePoints.clear();
+ m_d->linePoints.append(begin);
+ m_d->linePoints.append(end);
+ }
+ // Always adjust line sections to avoid jagged sections.
+ adjustPointsToDDA(m_d->linePoints);
+
QVector<KisPaintInformation>::const_iterator it = m_d->linePoints.constBegin();
QVector<KisPaintInformation>::const_iterator end = m_d->linePoints.constEnd();
initPaintImpl(startAngle, *it, resourceManager, image, node, strokesFacade);
++it;
while (it != end) {
paintLine(*(it - 1), *it);
++it;
}
}
void KisToolLineHelper::start(KoPointerEvent *event, KoCanvasResourceProvider *resourceManager)
{
if (!m_d->enabled) return;
// Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is
// drawn at once. This should prevent any possible spurious dabs caused by airbrushing features.
KisPaintInformation pi =
m_d->infoBuilder->startStroke(event, 0, resourceManager);
if (!m_d->useSensors) {
pi = KisPaintInformation(pi.pos());
}
m_d->linePoints.append(pi);
}
void KisToolLineHelper::addPoint(KoPointerEvent *event, const QPointF &overridePos)
{
if (!m_d->enabled) return;
// Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is
// drawn at once. This should prevent any possible spurious dabs caused by airbrushing features.
KisPaintInformation pi =
m_d->infoBuilder->continueStroke(event, 0);
if (!m_d->useSensors) {
pi = KisPaintInformation(pi.pos());
}
if (!overridePos.isNull()) {
pi.setPos(overridePos);
}
if (m_d->linePoints.size() > 1) {
const QPointF startPos = m_d->linePoints.first().pos();
const QPointF endPos = pi.pos();
const qreal maxDistance = kisDistance(startPos, endPos);
const QPointF unit = (endPos - startPos) / maxDistance;
QVector<KisPaintInformation>::iterator it = m_d->linePoints.begin();
++it;
while (it != m_d->linePoints.end()) {
qreal dist = kisDistance(startPos, it->pos());
if (dist < maxDistance) {
QPointF pos = startPos + unit * dist;
it->setPos(pos);
++it;
} else {
it = m_d->linePoints.erase(it);
}
}
}
m_d->linePoints.append(pi);
}
void KisToolLineHelper::translatePoints(const QPointF &offset)
{
if (!m_d->enabled) return;
QVector<KisPaintInformation>::iterator it = m_d->linePoints.begin();
while (it != m_d->linePoints.end()) {
it->setPos(it->pos() + offset);
++it;
}
}
void KisToolLineHelper::end()
{
if (!m_d->enabled) return;
KIS_ASSERT_RECOVER_RETURN(isRunning());
endPaint();
clearPoints();
}
void KisToolLineHelper::cancel()
{
if (!m_d->enabled) return;
KIS_ASSERT_RECOVER_RETURN(isRunning());
cancelPaint();
clearPoints();
}
void KisToolLineHelper::clearPoints()
{
m_d->linePoints.clear();
}
void KisToolLineHelper::clearPaint()
{
if (!m_d->enabled) return;
cancelPaint();
}
+
+void KisToolLineHelper::adjustPointsToDDA(QVector<KisPaintInformation> &points)
+{
+ int x = qFloor(points.first().pos().x());
+ int y = qFloor(points.first().pos().y());
+
+ int x2 = qFloor(points.last().pos().x());
+ int y2 = qFloor(points.last().pos().y());
+
+ // Width and height of the line
+ int xd = x2 - x;
+ int yd = y2 - y;
+
+ float m = 0;
+ bool lockAxis = true;
+
+ if (xd == 0) {
+ m = 2.0;
+ } else if ( yd != 0) {
+ lockAxis = false;
+ m = (float)yd / (float)xd;
+ }
+
+ float fx = x;
+ float fy = y;
+
+ int inc;
+ int dist;
+
+ if (fabs(m) > 1.0f) {
+ inc = (yd > 0) ? 1 : -1;
+ m = (lockAxis)? 0 : 1.0f / m;
+ m *= inc;
+
+ for (int i = 0; i < points.size(); i++){
+ dist = abs(qFloor(points.at(i).pos().y()) - y);
+ fy = y + (dist * inc);
+ fx = qRound(x + (dist * m));
+ points[i].setPos(QPointF(fx,fy));
+ }
+
+ } else {
+ inc = (xd > 0) ? 1 : -1;
+ m *= inc;
+
+ for (int i = 0; i < points.size(); i++){
+ dist = abs(qFloor(points.at(i).pos().x()) - x);
+ fx = x + (dist * inc);
+ fy = qRound(y + (dist * m));
+ points[i].setPos(QPointF(fx,fy));
+ }
+ }
+}
diff --git a/plugins/tools/basictools/kis_tool_line_helper.h b/plugins/tools/basictools/kis_tool_line_helper.h
index 13e3863e2c..c8cad7907a 100644
--- a/plugins/tools/basictools/kis_tool_line_helper.h
+++ b/plugins/tools/basictools/kis_tool_line_helper.h
@@ -1,56 +1,59 @@
/*
* 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_TOOL_LINE_HELPER_H
#define __KIS_TOOL_LINE_HELPER_H
#include "kis_tool_freehand_helper.h"
class KisToolLineHelper : private KisToolFreehandHelper
{
public:
KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder,
const KUndo2MagicString &transactionText);
~KisToolLineHelper() override;
void setEnabled(bool value);
void setUseSensors(bool value);
void repaintLine(KoCanvasResourceProvider *resourceManager,
KisImageWSP image,
KisNodeSP node,
KisStrokesFacade *strokesFacade);
void start(KoPointerEvent *event, KoCanvasResourceProvider *resourceManager);
void addPoint(KoPointerEvent *event, const QPointF &overridePos = QPointF());
void translatePoints(const QPointF &offset);
void end();
void cancel();
void clearPoints();
void clearPaint();
using KisToolFreehandHelper::isRunning;
+private:
+ void adjustPointsToDDA(QVector<KisPaintInformation> &points);
+
private:
struct Private;
Private * const m_d;
};
#endif /* __KIS_TOOL_LINE_HELPER_H */
diff --git a/plugins/tools/basictools/kis_tool_multihand.cpp b/plugins/tools/basictools/kis_tool_multihand.cpp
index dae752884f..48aba81fe6 100644
--- a/plugins/tools/basictools/kis_tool_multihand.cpp
+++ b/plugins/tools/basictools/kis_tool_multihand.cpp
@@ -1,605 +1,607 @@
/*
* Copyright (c) 2011 Lukáš Tvrdý <lukast.dev@gmail.com>
* 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_multihand.h"
#include <QTransform>
#include <QPushButton>
#include <QComboBox>
#include <QFormLayout>
#include <QStackedWidget>
#include <kis_slider_spin_box.h>
#include <QLabel>
#include "kis_canvas2.h"
#include "kis_cursor.h"
#include "kis_tool_multihand_helper.h"
static const int MAXIMUM_BRUSHES = 50;
#include <QtGlobal>
#ifdef Q_OS_WIN
// quoting DRAND48(3) man-page:
// These functions are declared obsolete by SVID 3,
// which states that rand(3) should be used instead.
#define drand48() (static_cast<double>(qrand()) / static_cast<double>(RAND_MAX))
#endif
KisToolMultihand::KisToolMultihand(KoCanvasBase *canvas)
: KisToolBrush(canvas),
m_transformMode(SYMMETRY),
m_angle(0),
m_handsCount(6),
m_mirrorVertically(false),
m_mirrorHorizontally(false),
m_showAxes(false),
m_translateRadius(100),
m_setupAxesFlag(false),
m_addSubbrushesMode(false)
, customUI(0)
{
m_helper =
new KisToolMultihandHelper(paintingInformationBuilder(),
kundo2_i18n("Multibrush Stroke"));
resetHelper(m_helper);
if (image()) {
m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height());
}
}
KisToolMultihand::~KisToolMultihand()
{
}
void KisToolMultihand::beginPrimaryAction(KoPointerEvent *event)
{
if(m_setupAxesFlag) {
setMode(KisTool::OTHER);
m_axesPoint = convertToPixelCoord(event->point);
requestUpdateOutline(event->point, 0);
updateCanvas();
}
else if (m_addSubbrushesMode){
QPointF newPoint = convertToPixelCoord(event->point);
m_subbrOriginalLocations << newPoint;
requestUpdateOutline(event->point, 0);
updateCanvas();
}
else {
initTransformations();
KisToolFreehand::beginPrimaryAction(event);
}
}
void KisToolMultihand::continuePrimaryAction(KoPointerEvent *event)
{
if(mode() == KisTool::OTHER) {
m_axesPoint = convertToPixelCoord(event->point);
requestUpdateOutline(event->point, 0);
updateCanvas();
}
else {
requestUpdateOutline(event->point, 0);
KisToolFreehand::continuePrimaryAction(event);
}
}
void KisToolMultihand::endPrimaryAction(KoPointerEvent *event)
{
if(mode() == KisTool::OTHER) {
setMode(KisTool::HOVER_MODE);
requestUpdateOutline(event->point, 0);
finishAxesSetup();
}
else {
KisToolFreehand::endPrimaryAction(event);
}
}
void KisToolMultihand::beginAlternateAction(KoPointerEvent* event, AlternateAction action)
{
if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) {
KisToolBrush::beginAlternateAction(event, action);
return;
}
setMode(KisTool::OTHER_1);
m_axesPoint = convertToPixelCoord(event->point);
requestUpdateOutline(event->point, 0);
updateCanvas();
}
void KisToolMultihand::continueAlternateAction(KoPointerEvent* event, AlternateAction action)
{
if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) {
KisToolBrush::continueAlternateAction(event, action);
return;
}
if (mode() == KisTool::OTHER_1) {
m_axesPoint = convertToPixelCoord(event->point);
requestUpdateOutline(event->point, 0);
updateCanvas();
}
}
void KisToolMultihand::endAlternateAction(KoPointerEvent* event, AlternateAction action)
{
if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) {
KisToolBrush::endAlternateAction(event, action);
return;
}
if (mode() == KisTool::OTHER_1) {
setMode(KisTool::HOVER_MODE);
}
}
void KisToolMultihand::mouseMoveEvent(KoPointerEvent* event)
{
if (mode() == HOVER_MODE) {
m_lastToolPos=convertToPixelCoord(event->point);
}
KisToolBrush::mouseMoveEvent(event);
}
void KisToolMultihand::paint(QPainter& gc, const KoViewConverter &converter)
{
QPainterPath path;
if (m_showAxes) {
int axisLength = currentImage()->height() + currentImage()->width();
// add division guide lines if using multiple brushes
if ((m_handsCount > 1 && m_transformMode == SYMMETRY) ||
(m_handsCount > 1 && m_transformMode == SNOWFLAKE) ) {
int axesCount;
if (m_transformMode == SYMMETRY){
axesCount = m_handsCount;
}
else {
axesCount = m_handsCount*2;
}
qreal axesAngle = 360.0 / float(axesCount);
float currentAngle = 0.0;
float startingInsetLength = 20; // don't start each line at the origin so we can see better when all points converge
// draw lines radiating from the origin
for( int i=0; i < axesCount; i++) {
currentAngle = i*axesAngle;
// convert angles to radians since cos and sin need that
currentAngle = currentAngle * 0.017453 + m_angle; // m_angle is current rotation set on UI
QPoint startingSpot = QPoint(m_axesPoint.x()+ (sin(currentAngle)*startingInsetLength), m_axesPoint.y()- (cos(currentAngle))*startingInsetLength );
path.moveTo(startingSpot.x(), startingSpot.y());
QPointF symmetryLinePoint(m_axesPoint.x()+ (sin(currentAngle)*axisLength), m_axesPoint.y()- (cos(currentAngle))*axisLength );
path.lineTo(symmetryLinePoint);
}
}
else if(m_transformMode == MIRROR) {
if (m_mirrorHorizontally) {
path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2));
path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2));
}
if(m_mirrorVertically) {
path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle));
path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle));
}
}
else if (m_transformMode == COPYTRANSLATE) {
int ellipsePreviewSize = 10;
// draw ellipse at origin to emphasize this is a drawing point
path.addEllipse(m_axesPoint.x()-(ellipsePreviewSize),
m_axesPoint.y()-(ellipsePreviewSize),
ellipsePreviewSize*2,
ellipsePreviewSize*2);
for (QPointF dPos : m_subbrOriginalLocations) {
path.addEllipse(dPos, ellipsePreviewSize, ellipsePreviewSize); // Show subbrush reference locations while in add mode
}
// draw the horiz/vertical line for axis origin
path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle));
path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle));
path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2));
path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2));
}
else {
// draw the horiz/vertical line for axis origin
path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle));
path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle));
path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2));
path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2));
}
} else {
// not showing axis
if (m_transformMode == COPYTRANSLATE) {
for (QPointF dPos : m_subbrOriginalLocations) {
// Show subbrush reference locations while in add mode
if (m_addSubbrushesMode) {
path.addEllipse(dPos, 10, 10);
}
}
}
}
KisToolFreehand::paint(gc, converter);
// origin point preview line/s
gc.save();
QPen outlinePen;
outlinePen.setColor(QColor(100,100,100,150));
outlinePen.setStyle(Qt::PenStyle::SolidLine);
gc.setPen(outlinePen);
paintToolOutline(&gc, pixelToView(path));
gc.restore();
// fill in a dot for the origin if showing axis
if (m_showAxes) {
// draw a dot at the origin point to help with precisly moving
QPainterPath dotPath;
int dotRadius = 4;
dotPath.moveTo(m_axesPoint.x(), m_axesPoint.y());
dotPath.addEllipse(m_axesPoint.x()- dotRadius*0.25, m_axesPoint.y()- dotRadius*0.25, dotRadius, dotRadius); // last 2 parameters are dot's size
QBrush fillBrush;
fillBrush.setColor(QColor(255, 255, 255, 255));
fillBrush.setStyle(Qt::SolidPattern);
gc.fillPath(pixelToView(dotPath), fillBrush);
// add slight offset circle for contrast to help show it on
dotPath = QPainterPath(); // resets path
dotPath.addEllipse(m_axesPoint.x() - dotRadius*0.75, m_axesPoint.y()- dotRadius*0.75, dotRadius, dotRadius); // last 2 parameters are dot's size
fillBrush.setColor(QColor(120, 120, 120, 255));
gc.fillPath(pixelToView(dotPath), fillBrush);
}
}
void KisToolMultihand::initTransformations()
{
QVector<QTransform> transformations;
QTransform m;
if(m_transformMode == SYMMETRY) {
qreal angle = 0;
qreal angleStep = (2 * M_PI) / m_handsCount;
for(int i = 0; i < m_handsCount; i++) {
m.translate(m_axesPoint.x(), m_axesPoint.y());
m.rotateRadians(angle);
m.translate(-m_axesPoint.x(), -m_axesPoint.y());
transformations << m;
m.reset();
angle += angleStep;
}
}
else if(m_transformMode == MIRROR) {
transformations << m;
if (m_mirrorHorizontally) {
m.translate(m_axesPoint.x(),m_axesPoint.y());
m.rotateRadians(m_angle);
m.scale(-1,1);
m.rotateRadians(-m_angle);
m.translate(-m_axesPoint.x(), -m_axesPoint.y());
transformations << m;
m.reset();
}
if (m_mirrorVertically) {
m.translate(m_axesPoint.x(),m_axesPoint.y());
m.rotateRadians(m_angle);
m.scale(1,-1);
m.rotateRadians(-m_angle);
m.translate(-m_axesPoint.x(), -m_axesPoint.y());
transformations << m;
m.reset();
}
if (m_mirrorVertically && m_mirrorHorizontally){
m.translate(m_axesPoint.x(),m_axesPoint.y());
m.rotateRadians(m_angle);
m.scale(-1,-1);
m.rotateRadians(-m_angle);
m.translate(-m_axesPoint.x(), -m_axesPoint.y());
transformations << m;
m.reset();
}
}
else if(m_transformMode == SNOWFLAKE) {
qreal angle = 0;
qreal angleStep = (2 * M_PI) / m_handsCount/4;
for(int i = 0; i < m_handsCount*4; i++) {
if ((i%2)==1) {
m.translate(m_axesPoint.x(), m_axesPoint.y());
m.rotateRadians(m_angle-angleStep);
m.rotateRadians(angle);
m.scale(-1,1);
m.rotateRadians(-m_angle+angleStep);
m.translate(-m_axesPoint.x(), -m_axesPoint.y());
transformations << m;
m.reset();
angle += angleStep*2;
} else {
m.translate(m_axesPoint.x(), m_axesPoint.y());
m.rotateRadians(m_angle-angleStep);
m.rotateRadians(angle);
m.rotateRadians(-m_angle+angleStep);
m.translate(-m_axesPoint.x(), -m_axesPoint.y());
transformations << m;
m.reset();
angle += angleStep*2;
}
}
}
else if(m_transformMode == TRANSLATE) {
/**
* TODO: currently, the seed is the same for all the
* strokes
*/
for (int i = 0; i < m_handsCount; i++){
qreal angle = drand48() * M_PI * 2;
qreal length = drand48();
// convert the Polar coordinates to Cartesian coordinates
qreal nx = (m_translateRadius * cos(angle) * length);
qreal ny = (m_translateRadius * sin(angle) * length);
m.translate(m_axesPoint.x(),m_axesPoint.y());
m.rotateRadians(m_angle);
m.translate(nx,ny);
m.rotateRadians(-m_angle);
m.translate(-m_axesPoint.x(), -m_axesPoint.y());
transformations << m;
m.reset();
}
} else if (m_transformMode == COPYTRANSLATE) {
transformations << m;
for (QPointF dPos : m_subbrOriginalLocations) {
QPointF resPos = dPos-m_axesPoint; // Calculate the difference between subbrush reference position and "origin" reference
m.translate(resPos.x(), resPos.y());
transformations << m;
m.reset();
}
}
m_helper->setupTransformations(transformations);
}
QWidget* KisToolMultihand::createOptionWidget()
{
QWidget *widget = KisToolBrush::createOptionWidget();
customUI = new KisToolMultiHandConfigWidget();
// brush smoothing option.
//customUI->layout()->addWidget(widget);
customUI->smoothingOptionsLayout->addWidget(widget);
// setup common parameters that all of the modes will see
connect(customUI->showAxesCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetAxesVisible(bool)));
customUI->showAxesCheckbox->setChecked((bool)m_configGroup.readEntry("showAxes", false));
connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(resetAxes()));
customUI->moveOriginButton->setCheckable(true);
connect(customUI->moveOriginButton, SIGNAL(clicked(bool)),this, SLOT(activateAxesPointModeSetup()));
connect(customUI->resetOriginButton, SIGNAL(released()), this, SLOT(resetAxes()));
customUI->multihandTypeCombobox->addItem(i18n("Symmetry"),int(SYMMETRY)); // axis mode
customUI->multihandTypeCombobox->addItem(i18n("Mirror"),int(MIRROR));
customUI->multihandTypeCombobox->addItem(i18n("Translate"),int(TRANSLATE));
customUI->multihandTypeCombobox->addItem(i18n("Snowflake"),int(SNOWFLAKE));
customUI->multihandTypeCombobox->addItem(i18n("Copy Translate"),int(COPYTRANSLATE));
connect(customUI->multihandTypeCombobox,SIGNAL(currentIndexChanged(int)),this, SLOT(slotSetTransformMode(int)));
customUI->multihandTypeCombobox->setCurrentIndex(m_configGroup.readEntry("transformMode", 0));
slotSetTransformMode(customUI->multihandTypeCombobox->currentIndex());
customUI->axisRotationSpinbox->setSuffix(QChar(Qt::Key_degree)); // origin rotation
customUI->axisRotationSpinbox->setSingleStep(0.5);
customUI->axisRotationSpinbox->setRange(0.0, 90.0, 1);
customUI->axisRotationSpinbox->setValue(m_configGroup.readEntry("axesAngle", 0.0));
connect( customUI->axisRotationSpinbox, SIGNAL(valueChanged(qreal)),this, SLOT(slotSetAxesAngle(qreal)));
// symmetry mode options
customUI->brushCountSpinBox->setRange(1, MAXIMUM_BRUSHES);
connect(customUI->brushCountSpinBox, SIGNAL(valueChanged(int)),this, SLOT(slotSetHandsCount(int)));
customUI->brushCountSpinBox->setValue(m_configGroup.readEntry("handsCount", 4));
// mirror mode specific options
connect(customUI->horizontalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorHorizontally(bool)));
customUI->horizontalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorHorizontally", false));
connect(customUI->verticalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorVertically(bool)));
customUI->verticalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorVertically", false));
// translate mode options
customUI->translationRadiusSpinbox->setRange(0, 200);
customUI->translationRadiusSpinbox->setSuffix(i18n(" px"));
customUI->translationRadiusSpinbox->setValue(m_configGroup.readEntry("translateRadius", 0));
connect(customUI->translationRadiusSpinbox,SIGNAL(valueChanged(int)),this,SLOT(slotSetTranslateRadius(int)));
// Copy translate mode options and actions
connect(customUI->addSubbrushButton, &QPushButton::clicked, this, &KisToolMultihand::slotAddSubbrushesMode);
connect(customUI->removeSubbrushButton, &QPushButton::clicked, this, &KisToolMultihand::slotRemoveAllSubbrushes);
// snowflake re-uses the existing options, so there is no special parameters for that...
return static_cast<QWidget*>(customUI); // keeping it in the native class until the end allows us to access the UI components
}
void KisToolMultihand::activateAxesPointModeSetup()
{
if (customUI->moveOriginButton->isChecked()){
m_setupAxesFlag = true;
useCursor(KisCursor::crossCursor());
updateCanvas();
} else {
finishAxesSetup();
}
}
void KisToolMultihand::resetAxes()
{
m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height());
finishAxesSetup();
}
void KisToolMultihand::finishAxesSetup()
{
m_setupAxesFlag = false;
customUI->moveOriginButton->setChecked(false);
resetCursorStyle();
updateCanvas();
}
void KisToolMultihand::updateCanvas()
{
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kisCanvas);
kisCanvas->updateCanvas();
}
void KisToolMultihand::slotSetHandsCount(int count)
{
m_handsCount = count;
m_configGroup.writeEntry("handsCount", count);
updateCanvas();
}
void KisToolMultihand::slotSetAxesAngle(qreal angle)
{
//negative so axes rotates counter clockwise
m_angle = -angle*M_PI/180;
updateCanvas();
m_configGroup.writeEntry("axesAngle", angle);
}
void KisToolMultihand::slotSetTransformMode(int index)
{
m_transformMode = enumTransforModes(customUI->multihandTypeCombobox->itemData(index).toInt());
m_configGroup.writeEntry("transformMode", index);
// hide all of the UI elements by default
customUI->horizontalCheckbox->setVisible(false);
customUI->verticalCheckbox->setVisible(false);
customUI->translationRadiusSpinbox->setVisible(false);
customUI->radiusLabel->setVisible(false);
customUI->brushCountSpinBox->setVisible(false);
customUI->brushesLabel->setVisible(false);
customUI->subbrushLabel->setVisible(false);
customUI->addSubbrushButton->setVisible(false);
customUI->removeSubbrushButton->setVisible(false);
+ m_addSubbrushesMode = 0;
// turn on what we need
if (index == MIRROR) {
customUI->horizontalCheckbox->setVisible(true);
customUI->verticalCheckbox->setVisible(true);
}
else if (index == TRANSLATE) {
customUI->translationRadiusSpinbox->setVisible(true);
customUI->radiusLabel->setVisible(true);
customUI->brushCountSpinBox->setVisible(true);
customUI->brushesLabel->setVisible(true);
}
else if (index == SYMMETRY || index == SNOWFLAKE || index == TRANSLATE ) {
customUI->brushCountSpinBox->setVisible(true);
customUI->brushesLabel->setVisible(true);
}
else if (index == COPYTRANSLATE) {
customUI->subbrushLabel->setVisible(true);
customUI->addSubbrushButton->setVisible(true);
+ customUI->addSubbrushButton->setChecked(false);
customUI->removeSubbrushButton->setVisible(true);
}
}
void KisToolMultihand::slotSetAxesVisible(bool vis)
{
m_showAxes = vis;
updateCanvas();
m_configGroup.writeEntry("showAxes", vis);
}
void KisToolMultihand::slotSetMirrorVertically(bool mirror)
{
m_mirrorVertically = mirror;
updateCanvas();
m_configGroup.writeEntry("mirrorVertically", mirror);
}
void KisToolMultihand::slotSetMirrorHorizontally(bool mirror)
{
m_mirrorHorizontally = mirror;
updateCanvas();
m_configGroup.writeEntry("mirrorHorizontally", mirror);
}
void KisToolMultihand::slotSetTranslateRadius(int radius)
{
m_translateRadius = radius;
m_configGroup.writeEntry("translateRadius", radius);
}
void KisToolMultihand::slotAddSubbrushesMode(bool checked)
{
m_addSubbrushesMode = checked;
updateCanvas();
}
void KisToolMultihand::slotRemoveAllSubbrushes()
{
m_subbrOriginalLocations.clear();
updateCanvas();
}
diff --git a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp
index c1666338da..a510c4c9d5 100644
--- a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp
+++ b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp
@@ -1,987 +1,988 @@
/* This file is part of the KDE project
*
* Copyright (C) 2009 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2009 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
* Copyright (C) 2011 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 "ConnectionTool.h"
#include <QPointF>
#include <QKeyEvent>
#include <QPainter>
#include "AddConnectionPointCommand.h"
#include "RemoveConnectionPointCommand.h"
#include "ChangeConnectionPointCommand.h"
#include "MoveConnectionPointStrategy.h"
#include "ConnectionPointWidget.h"
#define TextShape_SHAPEID "TextShapeID"
#include <KoCanvasBase.h>
#include <KoPointerEvent.h>
#include <KoShapeManager.h>
#include <KoShapeFactoryBase.h>
#include <KoShape.h>
#include <KoShapeGroup.h>
#include <KoShapeController.h>
#include <KoShapeLayer.h>
#include <KoShapeRegistry.h>
#include <KoSelection.h>
#include <KoPathSegment.h>
#include <KoDocumentResourceManager.h>
#include <KoInteractionStrategy.h>
#include <KoShapeConfigWidgetBase.h>
#include <KoConnectionShapeConfigWidget.h>
#include <KoPathConnectionPointStrategy.h>
#include <KoStrokeConfigWidget.h>
#include <KisHandlePainterHelper.h>
+#include <KoViewConverter.h>
#include "kis_document_aware_spin_box_unit_manager.h"
#include <KoIcon.h>
#include "kis_action_registry.h"
#include <QAction>
#include <klocalizedstring.h>
#include <QDebug>
#include <KoResourcePaths.h>
#include <kundo2command.h>
#include <math.h>
ConnectionTool::ConnectionTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_editMode(Idle)
, m_connectionType(KoConnectionShape::Standard)
, m_currentShape(0)
, m_activeHandle(-1)
, m_currentStrategy(0)
, m_oldSnapStrategies(0)
, m_resetPaint(true)
{
QPixmap connectPixmap;
connectPixmap.load(":/cursor_connect.png");
m_connectCursor = QCursor(connectPixmap, 4, 1);
KisActionRegistry *actionRegistry = KisActionRegistry::instance();
m_editConnectionPoint = actionRegistry->makeQAction("toggle-edit-mode", this);
m_editConnectionPoint->setCheckable(true);
// addAction("toggle-edit-mode", m_editConnectionPoint);
m_alignPercent = actionRegistry->makeQAction("align-relative", this);
m_alignPercent->setCheckable(true);
// addAction("align-relative", m_alignPercent);
m_alignLeft = actionRegistry->makeQAction("align-left", this);
m_alignLeft->setCheckable(true);
// addAction("align-left", m_alignLeft);
m_alignCenterH = actionRegistry->makeQAction("align-centerh", this);
m_alignCenterH->setCheckable(true);
// addAction("align-centerh", m_alignCenterH);
m_alignRight = actionRegistry->makeQAction("align-right", this);
m_alignRight->setCheckable(true);
// addAction("align-right", m_alignRight);
m_alignTop = actionRegistry->makeQAction("align-top", this);
m_alignTop->setCheckable(true);
// addAction("align-top", m_alignTop);
m_alignCenterV = actionRegistry->makeQAction("align-centerv", this);
m_alignCenterV->setCheckable(true);
// addAction("align-centerv", m_alignCenterV);
m_alignBottom = actionRegistry->makeQAction("align-bottom", this);
m_alignBottom->setCheckable(true);
// addAction("align-bottom", m_alignBottom);
m_escapeAll = actionRegistry->makeQAction("escape-all", this);
m_escapeAll->setCheckable(true);
// addAction("escape-all", m_escapeAll);
m_escapeHorizontal = actionRegistry->makeQAction("escape-horizontal", this);
m_escapeHorizontal->setCheckable(true);
// addAction("escape-horizontal", m_escapeHorizontal);
m_escapeVertical = actionRegistry->makeQAction("escape-vertical", this);
m_escapeVertical->setCheckable(true);
// addAction("escape-vertical", m_escapeVertical);
m_escapeLeft = actionRegistry->makeQAction("escape-left", this);
m_escapeLeft->setCheckable(true);
// addAction("escape-left", m_escapeLeft);
m_escapeRight = actionRegistry->makeQAction("escape-right", this);
m_escapeRight->setCheckable(true);
// addAction("escape-right", m_escapeRight);
m_escapeUp = actionRegistry->makeQAction("escape-up", this);
m_escapeUp->setCheckable(true);
// addAction("escape-up", m_escapeUp);
m_escapeDown = actionRegistry->makeQAction("escape-down", this);
m_escapeDown->setCheckable(true);
// addAction("escape-down", m_escapeDown);
m_alignHorizontal = new QActionGroup(this);
m_alignHorizontal->setExclusive(true);
m_alignHorizontal->addAction(m_alignLeft);
m_alignHorizontal->addAction(m_alignCenterH);
m_alignHorizontal->addAction(m_alignRight);
connect(m_alignHorizontal, SIGNAL(triggered(QAction*)), this, SLOT(horizontalAlignChanged()));
m_alignVertical = new QActionGroup(this);
m_alignVertical->setExclusive(true);
m_alignVertical->addAction(m_alignTop);
m_alignVertical->addAction(m_alignCenterV);
m_alignVertical->addAction(m_alignBottom);
connect(m_alignVertical, SIGNAL(triggered(QAction*)), this, SLOT(verticalAlignChanged()));
m_alignRelative = new QActionGroup(this);
m_alignRelative->setExclusive(true);
m_alignRelative->addAction(m_alignPercent);
connect(m_alignRelative, SIGNAL(triggered(QAction*)), this, SLOT(relativeAlignChanged()));
m_escapeDirections = new QActionGroup(this);
m_escapeDirections->setExclusive(true);
m_escapeDirections->addAction(m_escapeAll);
m_escapeDirections->addAction(m_escapeHorizontal);
m_escapeDirections->addAction(m_escapeVertical);
m_escapeDirections->addAction(m_escapeLeft);
m_escapeDirections->addAction(m_escapeRight);
m_escapeDirections->addAction(m_escapeUp);
m_escapeDirections->addAction(m_escapeDown);
connect(m_escapeDirections, SIGNAL(triggered(QAction*)), this, SLOT(escapeDirectionChanged()));
connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignHorizontal, SLOT(setEnabled(bool)));
connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignVertical, SLOT(setEnabled(bool)));
connect(this, SIGNAL(connectionPointEnabled(bool)), m_alignRelative, SLOT(setEnabled(bool)));
connect(this, SIGNAL(connectionPointEnabled(bool)), m_escapeDirections, SLOT(setEnabled(bool)));
resetEditMode();
}
ConnectionTool::~ConnectionTool()
{
}
void ConnectionTool::paint(QPainter &painter, const KoViewConverter &converter)
{
// get the correctly sized rect for painting handles
QRectF handleRect = handlePaintRect(QPointF());
painter.setRenderHint(QPainter::Antialiasing, true);
if (m_currentStrategy) {
painter.save();
m_currentStrategy->paint(painter, converter);
painter.restore();
}
QList<KoShape *> shapes = canvas()->shapeManager()->shapes();
for (QList<KoShape *>::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); ++end) {
KoShape *shape = *end;
if (!dynamic_cast<KoConnectionShape *>(shape)) {
// only paint connection points of textShapes not inside a tos container and other shapes
if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast<KoTosContainer *>(shape->parent())) {
continue;
}
painter.save();
painter.setPen(Qt::black);
- QTransform transform = shape->absoluteTransformation(0);
- KoShape::applyConversion(painter, converter);
+ painter.setTransform(converter.documentToView());
+ QTransform transform = shape->absoluteTransformation();
// Draw all the connection points of the shape
KoConnectionPoints connectionPoints = shape->connectionPoints();
KoConnectionPoints::const_iterator cp = connectionPoints.constBegin();
KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd();
for (; cp != lastCp; ++cp) {
if (shape == findNonConnectionShapeAtPosition(transform.map(cp.value().position))) {
handleRect.moveCenter(transform.map(cp.value().position));
painter.setBrush(cp.key() == m_activeHandle && shape == m_currentShape ?
Qt::red : Qt::white);
painter.drawRect(handleRect);
}
}
painter.restore();
}
}
// paint connection points or connection handles depending
// on the shape the mouse is currently
if (m_currentShape && m_editMode == EditConnection) {
KoConnectionShape *connectionShape = dynamic_cast<KoConnectionShape *>(m_currentShape);
if (connectionShape) {
int radius = handleRadius() + 1;
int handleCount = connectionShape->handleCount();
for (int i = 0; i < handleCount; ++i) {
- KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, connectionShape, converter, radius);
+ KisHandlePainterHelper helper = KoShape::createHandlePainterHelperView(&painter, connectionShape, converter, radius);
helper.setHandleStyle(i == m_activeHandle ? KisHandleStyle::highlightedPrimaryHandles() : KisHandleStyle::primarySelection());
connectionShape->paintHandle(helper, i);
}
}
}
}
void ConnectionTool::repaintDecorations()
{
const qreal radius = handleRadius();
QRectF repaintRect;
if (m_currentShape) {
repaintRect = m_currentShape->boundingRect();
canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius));
KoConnectionShape *connectionShape = dynamic_cast<KoConnectionShape *>(m_currentShape);
if (!m_resetPaint && m_currentShape->isVisible() && !connectionShape) {
// only paint connection points of textShapes not inside a tos container and other shapes
if (!(m_currentShape->shapeId() == TextShape_SHAPEID &&
dynamic_cast<KoTosContainer *>(m_currentShape->parent()))) {
KoConnectionPoints connectionPoints = m_currentShape->connectionPoints();
KoConnectionPoints::const_iterator cp = connectionPoints.constBegin();
KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd();
for (; cp != lastCp; ++cp) {
repaintRect = handleGrabRect(m_currentShape->shapeToDocument(cp.value().position));
canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius));
}
}
}
if (m_editMode == EditConnection) {
if (connectionShape) {
QPointF handlePos = connectionShape->handlePosition(m_activeHandle);
handlePos = connectionShape->shapeToDocument(handlePos);
repaintRect = handlePaintRect(handlePos);
canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius));
}
}
}
if (m_resetPaint) {
QList<KoShape *> shapes = canvas()->shapeManager()->shapes();
for (QList<KoShape *>::const_iterator end = shapes.constBegin(); end != shapes.constEnd(); ++end) {
KoShape *shape = *end;
if (!dynamic_cast<KoConnectionShape *>(shape)) {
// only paint connection points of textShapes not inside a tos container and other shapes
if (shape->shapeId() == TextShape_SHAPEID && dynamic_cast<KoTosContainer *>(shape->parent())) {
continue;
}
KoConnectionPoints connectionPoints = shape->connectionPoints();
KoConnectionPoints::const_iterator cp = connectionPoints.constBegin();
KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd();
for (; cp != lastCp; ++cp) {
repaintRect = handleGrabRect(shape->shapeToDocument(cp.value().position));
canvas()->updateCanvas(repaintRect.adjusted(-radius, -radius, radius, radius));
}
}
}
}
m_resetPaint = false;
}
void ConnectionTool::mousePressEvent(KoPointerEvent *event)
{
if (!m_currentShape) {
return;
}
KoShape *hitShape = findShapeAtPosition(event->point);
int hitHandle = handleAtPoint(m_currentShape, event->point);
if (m_editMode == EditConnection && hitHandle >= 0) {
// create connection handle change strategy
m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast<KoConnectionShape *>(m_currentShape), hitHandle);
} else if (m_editMode == EditConnectionPoint) {
if (hitHandle >= KoConnectionPoint::FirstCustomConnectionPoint) {
// start moving custom connection point
m_currentStrategy = new MoveConnectionPointStrategy(m_currentShape, hitHandle, this);
}
} else if (m_editMode == CreateConnection) {
// create new connection shape, connect it to the active connection point
// and start editing the new connection
// create the new connection shape
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoConnectionShape");
KoShape *shape = factory->createDefaultShape(canvas()->shapeController()->resourceManager());
KoConnectionShape *connectionShape = dynamic_cast<KoConnectionShape *>(shape);
if (!connectionShape) {
delete shape;
resetEditMode();
return;
}
//set connection type
connectionShape->setType(m_connectionType);
// get the position of the connection point we start our connection from
QPointF cp = m_currentShape->shapeToDocument(m_currentShape->connectionPoint(m_activeHandle).position);
// move both handles to that point
connectionShape->moveHandle(0, cp);
connectionShape->moveHandle(1, cp);
// connect the first handle of the connection shape to our connection point
if (!connectionShape->connectFirst(m_currentShape, m_activeHandle)) {
delete shape;
resetEditMode();
return;
}
//add connector label
connectionShape->createTextShape(canvas()->shapeController()->resourceManager());
connectionShape->setPlainText(QString());
// create the connection edit strategy from the path tool
m_currentStrategy = new KoPathConnectionPointStrategy(this, connectionShape, 1);
if (!m_currentStrategy) {
delete shape;
resetEditMode();
return;
}
// update our handle data
setEditMode(m_editMode, shape, 1);
// add connection shape to the shape manager so it gets painted
canvas()->shapeManager()->addShape(connectionShape);
} else {
// pressing on a shape in idle mode switches to corresponding edit mode
if (hitShape) {
if (dynamic_cast<KoConnectionShape *>(hitShape)) {
int hitHandle = handleAtPoint(hitShape, event->point);
setEditMode(EditConnection, hitShape, hitHandle);
if (hitHandle >= 0) {
// start editing connection shape
m_currentStrategy = new KoPathConnectionPointStrategy(this, dynamic_cast<KoConnectionShape *>(m_currentShape), m_activeHandle);
}
}
} else {
resetEditMode();
}
}
}
void ConnectionTool::mouseMoveEvent(KoPointerEvent *event)
{
if (m_currentStrategy) {
repaintDecorations();
if (m_editMode != EditConnection && m_editMode != CreateConnection) {
QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers());
m_currentStrategy->handleMouseMove(snappedPos, event->modifiers());
} else {
m_currentStrategy->handleMouseMove(event->point, event->modifiers());
}
repaintDecorations();
} else if (m_editMode == EditConnectionPoint) {
KoShape *hoverShape = findNonConnectionShapeAtPosition(event->point);//TODO exclude connectors, need snap guide maybe?
if (hoverShape) {
m_currentShape = hoverShape;
Q_ASSERT(m_currentShape);
// check if we should highlight another connection point
int handle = handleAtPoint(m_currentShape, event->point);
if (handle >= 0) {
setEditMode(m_editMode, m_currentShape, handle);
useCursor(handle >= KoConnectionPoint::FirstCustomConnectionPoint ? Qt::SizeAllCursor : Qt::ArrowCursor);
} else {
updateStatusText();
useCursor(Qt::CrossCursor);
}
} else {
m_currentShape = 0;
useCursor(Qt::ArrowCursor);
}
} else if (m_editMode == EditConnection) {
Q_ASSERT(m_currentShape);
KoShape *hoverShape = findShapeAtPosition(event->point);
// check if we should highlight another connection handle
int handle = handleAtPoint(m_currentShape, event->point);
setEditMode(m_editMode, m_currentShape, handle);
if (m_activeHandle == KoConnectionShape::StartHandle ||
m_activeHandle == KoConnectionShape::EndHandle) {
useCursor(Qt::SizeAllCursor);
} else if (m_activeHandle >= KoConnectionShape::ControlHandle_1) {
} else if (hoverShape && hoverShape != m_currentShape) {
useCursor(Qt::PointingHandCursor);
} else {
useCursor(Qt::ArrowCursor);
}
} else {// Idle and no current strategy
KoShape *hoverShape = findShapeAtPosition(event->point);
int hoverHandle = -1;
if (hoverShape) {
KoConnectionShape *connectionShape = dynamic_cast<KoConnectionShape *>(hoverShape);
if (!connectionShape) {
QPointF snappedPos = canvas()->snapGuide()->snap(event->point, event->modifiers());
hoverHandle = handleAtPoint(hoverShape, snappedPos);
setEditMode(hoverHandle >= 0 ? CreateConnection : Idle, hoverShape, hoverHandle);
}
useCursor(hoverHandle >= 0 ? m_connectCursor : Qt::PointingHandCursor);
} else {
useCursor(Qt::ArrowCursor);
}
}
}
void ConnectionTool::mouseReleaseEvent(KoPointerEvent *event)
{
if (m_currentStrategy) {
if (m_editMode == CreateConnection) {
// check if connection handles have a minimal distance
KoConnectionShape *connectionShape = dynamic_cast<KoConnectionShape *>(m_currentShape);
Q_ASSERT(connectionShape);
// get both handle positions in document coordinates
QPointF p1 = connectionShape->shapeToDocument(connectionShape->handlePosition(0));
QPointF p2 = connectionShape->shapeToDocument(connectionShape->handlePosition(1));
int grabDistance = grabSensitivity();
// use grabbing sensitivity as minimal distance threshold
if (squareDistance(p1, p2) < grabDistance * grabDistance) {
// minimal distance was not reached, so we have to undo the started work:
// - cleanup and delete the strategy
// - remove connection shape from shape manager and delete it
// - reset edit mode to last state
delete m_currentStrategy;
m_currentStrategy = 0;
repaintDecorations();
canvas()->shapeManager()->remove(m_currentShape);
setEditMode(m_editMode, connectionShape->firstShape(), connectionShape->firstConnectionId());
repaintDecorations();
delete connectionShape;
return;
} else {
// finalize adding the new connection shape with an undo command
KUndo2Command *cmd = canvas()->shapeController()->addShape(m_currentShape, 0);
canvas()->addCommand(cmd);
setEditMode(EditConnection, m_currentShape, KoConnectionShape::StartHandle);
}
}
m_currentStrategy->finishInteraction(event->modifiers());
// TODO: Add parent command to KoInteractionStrategy::createCommand
// so that we can have a single command to undo for the user
KUndo2Command *command = m_currentStrategy->createCommand();
if (command) {
canvas()->addCommand(command);
}
delete m_currentStrategy;
m_currentStrategy = 0;
}
updateStatusText();
}
void ConnectionTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
if (m_editMode == EditConnectionPoint) {
repaintDecorations();
//quit EditConnectionPoint mode when double click blank region on canvas
if (!m_currentShape) {
resetEditMode();
return;
}
//add connection point when double click a shape
//remove connection point when double click a existed connection point
int handleId = handleAtPoint(m_currentShape, event->point);
if (handleId < 0) {
QPointF mousePos = canvas()->snapGuide()->snap(event->point, event->modifiers());
QPointF point = m_currentShape->documentToShape(mousePos);
canvas()->addCommand(new AddConnectionPointCommand(m_currentShape, point));
} else {
canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, handleId));
}
setEditMode(m_editMode, m_currentShape, -1);
} else {
//deactivate connection tool when double click blank region on canvas
KoShape *hitShape = findShapeAtPosition(event->point);
if (!hitShape) {
deactivate();
emit done();
} else if (dynamic_cast<KoConnectionShape *>(hitShape)) {
repaintDecorations();
setEditMode(EditConnection, m_currentShape, -1);
//TODO: temporarily activate text tool to edit connection path
}
}
}
void ConnectionTool::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
deactivate();
emit done();
} else if (event->key() == Qt::Key_Backspace) {
deleteSelection();
event->accept();
}
}
void ConnectionTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
KoToolBase::activate(activation, shapes);
// save old enabled snap strategies, set bounding box snap strategy
m_oldSnapStrategies = canvas()->snapGuide()->enabledSnapStrategies();
canvas()->snapGuide()->enableSnapStrategies(KoSnapGuide::BoundingBoxSnapping);
canvas()->snapGuide()->reset();
m_resetPaint = true;
repaintDecorations();
}
void ConnectionTool::deactivate()
{
// Put everything to 0 to be able to begin a new shape properly
delete m_currentStrategy;
m_currentStrategy = 0;
resetEditMode();
m_resetPaint = true;
repaintDecorations();
// restore previously set snap strategies
canvas()->snapGuide()->enableSnapStrategies(m_oldSnapStrategies);
canvas()->snapGuide()->reset();
KoToolBase::deactivate();
}
qreal ConnectionTool::squareDistance(const QPointF &p1, const QPointF &p2) const
{
// Square of the distance
const qreal dx = p2.x() - p1.x();
const qreal dy = p2.y() - p1.y();
return dx * dx + dy * dy;
}
KoShape *ConnectionTool::findShapeAtPosition(const QPointF &position) const
{
QList<KoShape *> shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position));
if (!shapes.isEmpty()) {
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
// we want to priorize connection shape handles, even if the connection shape
// is not at the top of the shape stack at the mouse position
KoConnectionShape *connectionShape = nearestConnectionShape(shapes, position);
// use best connection shape or first shape from stack (last in the list) if not found
if (connectionShape) {
return connectionShape;
} else {
for (QList<KoShape *>::const_iterator end = shapes.constEnd() - 1; end >= shapes.constBegin(); --end) {
KoShape *shape = *end;
if (!dynamic_cast<KoConnectionShape *>(shape) && shape->shapeId() != TextShape_SHAPEID) {
return shape;
}
}
}
}
return 0;
}
KoShape *ConnectionTool::findNonConnectionShapeAtPosition(const QPointF &position) const
{
QList<KoShape *> shapes = canvas()->shapeManager()->shapesAt(handleGrabRect(position));
if (!shapes.isEmpty()) {
std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
for (QList<KoShape *>::const_iterator end = shapes.constEnd() - 1; end >= shapes.constBegin(); --end) {
KoShape *shape = *end;
if (!dynamic_cast<KoConnectionShape *>(shape) && shape->shapeId() != TextShape_SHAPEID) {
return shape;
}
}
}
return 0;
}
int ConnectionTool::handleAtPoint(KoShape *shape, const QPointF &mousePoint) const
{
if (!shape) {
return -1;
}
const QPointF shapePoint = shape->documentToShape(mousePoint);
KoConnectionShape *connectionShape = dynamic_cast<KoConnectionShape *>(shape);
if (connectionShape) {
// check connection shape handles
return connectionShape->handleIdAt(handleGrabRect(shapePoint));
} else {
// check connection points
int grabDistance = grabSensitivity();
qreal minDistance = HUGE_VAL;
int handleId = -1;
KoConnectionPoints connectionPoints = shape->connectionPoints();
KoConnectionPoints::const_iterator cp = connectionPoints.constBegin();
KoConnectionPoints::const_iterator lastCp = connectionPoints.constEnd();
for (; cp != lastCp; ++cp) {
qreal d = squareDistance(shapePoint, cp.value().position);
if (d <= grabDistance && d < minDistance) {
handleId = cp.key();
minDistance = d;
}
}
return handleId;
}
}
KoConnectionShape *ConnectionTool::nearestConnectionShape(const QList<KoShape *> &shapes, const QPointF &mousePos) const
{
int grabDistance = grabSensitivity();
KoConnectionShape *nearestConnectionShape = 0;
qreal minSquaredDistance = HUGE_VAL;
const qreal maxSquaredDistance = grabDistance * grabDistance;
Q_FOREACH (KoShape *shape, shapes) {
KoConnectionShape *connectionShape = dynamic_cast<KoConnectionShape *>(shape);
if (!connectionShape || !connectionShape->isParametricShape()) {
continue;
}
// convert document point to shape coordinates
QPointF p = connectionShape->documentToShape(mousePos);
// our region of interest, i.e. a region around our mouse position
QRectF roi = handleGrabRect(p);
// check all segments of this shape which intersect the region of interest
QList<KoPathSegment> segments = connectionShape->segmentsAt(roi);
foreach (const KoPathSegment &s, segments) {
qreal nearestPointParam = s.nearestPoint(p);
QPointF nearestPoint = s.pointAt(nearestPointParam);
QPointF diff = p - nearestPoint;
qreal squaredDistance = diff.x() * diff.x() + diff.y() * diff.y();
// are we within the allowed distance ?
if (squaredDistance > maxSquaredDistance) {
continue;
}
// are we closer to the last closest point ?
if (squaredDistance < minSquaredDistance) {
nearestConnectionShape = connectionShape;
minSquaredDistance = squaredDistance;
}
}
}
return nearestConnectionShape;
}
void ConnectionTool::setEditMode(EditMode mode, KoShape *currentShape, int handle)
{
repaintDecorations();
m_editMode = mode;
if (m_currentShape != currentShape) {
KoConnectionShape *connectionShape = dynamic_cast<KoConnectionShape *>(currentShape);
foreach (KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) {
if (connectionShape) {
cw->open(currentShape);
}
}
}
if (mode == Idle) {
emit sendConnectionType(m_connectionType);
}
m_currentShape = currentShape;
m_activeHandle = handle;
repaintDecorations();
updateActions();
updateStatusText();
}
void ConnectionTool::resetEditMode()
{
m_connectionType = KoConnectionShape::Standard;
setEditMode(Idle, 0, -1);
emit sendConnectionPointEditState(false);
}
void ConnectionTool::updateActions()
{
const bool connectionPointSelected = m_editMode == EditConnectionPoint && m_activeHandle >= 0;
if (connectionPointSelected) {
KoConnectionPoint cp = m_currentShape->connectionPoint(m_activeHandle);
m_alignPercent->setChecked(false);
Q_FOREACH (QAction *action, m_alignHorizontal->actions()) {
action->setChecked(false);
}
Q_FOREACH (QAction *action, m_alignVertical->actions()) {
action->setChecked(false);
}
switch (cp.alignment) {
case KoConnectionPoint::AlignNone:
m_alignPercent->setChecked(true);
break;
case KoConnectionPoint::AlignTopLeft:
m_alignLeft->setChecked(true);
m_alignTop->setChecked(true);
break;
case KoConnectionPoint::AlignTop:
m_alignCenterH->setChecked(true);
m_alignTop->setChecked(true);
break;
case KoConnectionPoint::AlignTopRight:
m_alignRight->setChecked(true);
m_alignTop->setChecked(true);
break;
case KoConnectionPoint::AlignLeft:
m_alignLeft->setChecked(true);
m_alignCenterV->setChecked(true);
break;
case KoConnectionPoint::AlignCenter:
m_alignCenterH->setChecked(true);
m_alignCenterV->setChecked(true);
break;
case KoConnectionPoint::AlignRight:
m_alignRight->setChecked(true);
m_alignCenterV->setChecked(true);
break;
case KoConnectionPoint::AlignBottomLeft:
m_alignLeft->setChecked(true);
m_alignBottom->setChecked(true);
break;
case KoConnectionPoint::AlignBottom:
m_alignCenterH->setChecked(true);
m_alignBottom->setChecked(true);
break;
case KoConnectionPoint::AlignBottomRight:
m_alignRight->setChecked(true);
m_alignBottom->setChecked(true);
break;
}
Q_FOREACH (QAction *action, m_escapeDirections->actions()) {
action->setChecked(false);
}
switch (cp.escapeDirection) {
case KoConnectionPoint::AllDirections:
m_escapeAll->setChecked(true);
break;
case KoConnectionPoint::HorizontalDirections:
m_escapeHorizontal->setChecked(true);
break;
case KoConnectionPoint::VerticalDirections:
m_escapeVertical->setChecked(true);
break;
case KoConnectionPoint::LeftDirection:
m_escapeLeft->setChecked(true);
break;
case KoConnectionPoint::RightDirection:
m_escapeRight->setChecked(true);
break;
case KoConnectionPoint::UpDirection:
m_escapeUp->setChecked(true);
break;
case KoConnectionPoint::DownDirection:
m_escapeDown->setChecked(true);
break;
}
}
emit connectionPointEnabled(connectionPointSelected);
}
void ConnectionTool::updateStatusText()
{
switch (m_editMode) {
case Idle:
if (m_currentShape) {
if (dynamic_cast<KoConnectionShape *>(m_currentShape)) {
if (m_activeHandle >= 0) {
emit statusTextChanged(i18n("Drag to edit connection."));
} else {
emit statusTextChanged(i18n("Double click connection or press delete to remove it."));
}
} else if (m_activeHandle < 0) {
emit statusTextChanged(i18n("Click to edit connection points."));
}
} else {
emit statusTextChanged(QString());
}
break;
case EditConnection:
if (m_activeHandle >= 0) {
emit statusTextChanged(i18n("Drag to edit connection."));
} else {
emit statusTextChanged(i18n("Double click connection or press delete to remove it."));
}
break;
case EditConnectionPoint:
if (m_activeHandle >= KoConnectionPoint::FirstCustomConnectionPoint) {
emit statusTextChanged(i18n("Drag to move connection point. Double click connection or press delete to remove it."));
} else if (m_activeHandle >= 0) {
emit statusTextChanged(i18n("Double click connection point or press delete to remove it."));
} else {
emit statusTextChanged(i18n("Double click to add connection point."));
}
break;
case CreateConnection:
emit statusTextChanged(i18n("Drag to create new connection."));
break;
default:
emit statusTextChanged(QString());
}
}
QList<QPointer<QWidget> > ConnectionTool::createOptionWidgets()
{
QList<QPointer<QWidget> > list;
m_connectionShapeWidgets.clear();
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(KOCONNECTIONSHAPEID);
if (factory) {
QList<KoShapeConfigWidgetBase *> widgets = factory->createShapeOptionPanels();
Q_FOREACH (KoShapeConfigWidgetBase *cw, widgets) {
if (cw->showOnShapeCreate() || !cw->showOnShapeSelect()) {
delete cw;
continue;
}
connect(cw, SIGNAL(propertyChanged()), this, SLOT(connectionChanged()));
KoConnectionShapeConfigWidget *cw2 = (KoConnectionShapeConfigWidget *)cw;
if (cw2) {
connect(cw2, SIGNAL(connectionTypeChanged(int)), this, SLOT(getConnectionType(int)));
connect(this, SIGNAL(sendConnectionType(int)), cw2, SLOT(setConnectionType(int)));
}
m_connectionShapeWidgets.append(cw);
cw->setWindowTitle(i18n("Connection"));
list.append(cw);
}
}
KoStrokeConfigWidget *strokeWidget = new KoStrokeConfigWidget(canvas(), 0);
KisDocumentAwareSpinBoxUnitManager* managerLineWidth = new KisDocumentAwareSpinBoxUnitManager(strokeWidget);
KisDocumentAwareSpinBoxUnitManager* managerMitterLimit = new KisDocumentAwareSpinBoxUnitManager(strokeWidget);
managerLineWidth->setApparentUnitFromSymbol("px");
managerMitterLimit->setApparentUnitFromSymbol("px");
strokeWidget->setUnitManagers(managerLineWidth, managerMitterLimit);
strokeWidget->setWindowTitle(i18n("Line"));
list.append(strokeWidget);
ConnectionPointWidget *connectPoint = new ConnectionPointWidget(this);
connectPoint->setWindowTitle(i18n("Connection Point"));
list.append(connectPoint);
return list;
}
void ConnectionTool::horizontalAlignChanged()
{
if (m_alignPercent->isChecked()) {
m_alignPercent->setChecked(false);
m_alignTop->setChecked(true);
}
updateConnectionPoint();
}
void ConnectionTool::verticalAlignChanged()
{
if (m_alignPercent->isChecked()) {
m_alignPercent->setChecked(false);
m_alignLeft->setChecked(true);
}
updateConnectionPoint();
}
void ConnectionTool::relativeAlignChanged()
{
Q_FOREACH (QAction *action, m_alignHorizontal->actions()) {
action->setChecked(false);
}
Q_FOREACH (QAction *action, m_alignVertical->actions()) {
action->setChecked(false);
}
m_alignPercent->setChecked(true);
updateConnectionPoint();
}
void ConnectionTool::updateConnectionPoint()
{
if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) {
KoConnectionPoint oldPoint = m_currentShape->connectionPoint(m_activeHandle);
KoConnectionPoint newPoint = oldPoint;
if (m_alignPercent->isChecked()) {
newPoint.alignment = KoConnectionPoint::AlignNone;
} else if (m_alignLeft->isChecked() && m_alignTop->isChecked()) {
newPoint.alignment = KoConnectionPoint::AlignTopLeft;
} else if (m_alignCenterH->isChecked() && m_alignTop->isChecked()) {
newPoint.alignment = KoConnectionPoint::AlignTop;
} else if (m_alignRight->isChecked() && m_alignTop->isChecked()) {
newPoint.alignment = KoConnectionPoint::AlignTopRight;
} else if (m_alignLeft->isChecked() && m_alignCenterV->isChecked()) {
newPoint.alignment = KoConnectionPoint::AlignLeft;
} else if (m_alignCenterH->isChecked() && m_alignCenterV->isChecked()) {
newPoint.alignment = KoConnectionPoint::AlignCenter;
} else if (m_alignRight->isChecked() && m_alignCenterV->isChecked()) {
newPoint.alignment = KoConnectionPoint::AlignRight;
} else if (m_alignLeft->isChecked() && m_alignBottom->isChecked()) {
newPoint.alignment = KoConnectionPoint::AlignBottomLeft;
} else if (m_alignCenterH->isChecked() && m_alignBottom->isChecked()) {
newPoint.alignment = KoConnectionPoint::AlignBottom;
} else if (m_alignRight->isChecked() && m_alignBottom->isChecked()) {
newPoint.alignment = KoConnectionPoint::AlignBottomRight;
}
canvas()->addCommand(new ChangeConnectionPointCommand(m_currentShape, m_activeHandle, oldPoint, newPoint));
}
}
void ConnectionTool::escapeDirectionChanged()
{
if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) {
KoConnectionPoint oldPoint = m_currentShape->connectionPoint(m_activeHandle);
KoConnectionPoint newPoint = oldPoint;
QAction *checkedAction = m_escapeDirections->checkedAction();
if (checkedAction == m_escapeAll) {
newPoint.escapeDirection = KoConnectionPoint::AllDirections;
} else if (checkedAction == m_escapeHorizontal) {
newPoint.escapeDirection = KoConnectionPoint::HorizontalDirections;
} else if (checkedAction == m_escapeVertical) {
newPoint.escapeDirection = KoConnectionPoint::VerticalDirections;
} else if (checkedAction == m_escapeLeft) {
newPoint.escapeDirection = KoConnectionPoint::LeftDirection;
} else if (checkedAction == m_escapeRight) {
newPoint.escapeDirection = KoConnectionPoint::RightDirection;
} else if (checkedAction == m_escapeUp) {
newPoint.escapeDirection = KoConnectionPoint::UpDirection;
} else if (checkedAction == m_escapeDown) {
newPoint.escapeDirection = KoConnectionPoint::DownDirection;
}
canvas()->addCommand(new ChangeConnectionPointCommand(m_currentShape, m_activeHandle, oldPoint, newPoint));
}
}
void ConnectionTool::connectionChanged()
{
if (m_editMode != EditConnection) {
return;
}
KoConnectionShape *connectionShape = dynamic_cast<KoConnectionShape *>(m_currentShape);
if (!connectionShape) {
return;
}
Q_FOREACH (KoShapeConfigWidgetBase *cw, m_connectionShapeWidgets) {
canvas()->addCommand(cw->createCommand());
}
}
void ConnectionTool::deleteSelection()
{
if (m_editMode == EditConnectionPoint && m_currentShape && m_activeHandle >= 0) {
repaintDecorations();
canvas()->addCommand(new RemoveConnectionPointCommand(m_currentShape, m_activeHandle));
setEditMode(m_editMode, m_currentShape, -1);
} else if (m_editMode == EditConnection && m_currentShape) {
repaintDecorations();
canvas()->addCommand(canvas()->shapeController()->removeShape(m_currentShape));
resetEditMode();
}
}
void ConnectionTool::getConnectionType(int type)
{
if (m_editMode == Idle) {
m_connectionType = (KoConnectionShape::Type)type;
}
}
void ConnectionTool::toggleConnectionPointEditMode(int state)
{
if (state == Qt::Checked) {
setEditMode(EditConnectionPoint, 0, -1);
} else if (state == Qt::Unchecked) {
setEditMode(Idle, 0, -1);
} else {
return;
}
}
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
index 4ecdafc457..bdb828512d 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
+++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
@@ -1,1735 +1,1736 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
Copyright (C) 2008-2009 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2008 C. Boemann <cbo@boemann.dk>
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 "DefaultTool.h"
#include "DefaultToolGeometryWidget.h"
#include "DefaultToolTabbedWidget.h"
#include "SelectionDecorator.h"
#include "ShapeMoveStrategy.h"
#include "ShapeRotateStrategy.h"
#include "ShapeShearStrategy.h"
#include "ShapeResizeStrategy.h"
#include <KoPointerEvent.h>
#include <KoToolSelection.h>
#include <KoToolManager.h>
#include <KoSelection.h>
#include <KoShapeController.h>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxy.h>
#include <KoShapeGroup.h>
#include <KoShapeLayer.h>
#include <KoShapeOdfSaveHelper.h>
#include <KoPathShape.h>
#include <KoDrag.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceProvider.h>
#include <KoShapeRubberSelectStrategy.h>
#include <commands/KoShapeMoveCommand.h>
#include <commands/KoShapeTransformCommand.h>
#include <commands/KoShapeDeleteCommand.h>
#include <commands/KoShapeCreateCommand.h>
#include <commands/KoShapeGroupCommand.h>
#include <commands/KoShapeUngroupCommand.h>
#include <commands/KoShapeDistributeCommand.h>
#include <commands/KoKeepShapesSelectedCommand.h>
#include <KoSnapGuide.h>
#include <KoStrokeConfigWidget.h>
#include "kis_action_registry.h"
#include "kis_node.h"
#include "kis_node_manager.h"
#include "KisViewManager.h"
#include "kis_canvas2.h"
#include "kis_canvas_resource_provider.h"
#include <KoInteractionStrategyFactory.h>
#include "kis_document_aware_spin_box_unit_manager.h"
#include <KoIcon.h>
#include <QPointer>
#include <QAction>
#include <QKeyEvent>
#include <KisSignalMapper.h>
#include <KoResourcePaths.h>
#include <KoCanvasController.h>
#include <kactioncollection.h>
#include <QMenu>
#include <math.h>
#include "kis_assert.h"
#include "kis_global.h"
#include "kis_debug.h"
#include <QVector2D>
#define HANDLE_DISTANCE 10
#define HANDLE_DISTANCE_SQ (HANDLE_DISTANCE * HANDLE_DISTANCE)
#define INNER_HANDLE_DISTANCE_SQ 16
namespace {
static const QString EditFillGradientFactoryId = "edit_fill_gradient";
static const QString EditStrokeGradientFactoryId = "edit_stroke_gradient";
enum TransformActionType {
TransformRotate90CW,
TransformRotate90CCW,
TransformRotate180,
TransformMirrorX,
TransformMirrorY,
TransformReset
};
enum BooleanOp {
BooleanUnion,
BooleanIntersection,
BooleanSubtraction
};
}
class NopInteractionStrategy : public KoInteractionStrategy
{
public:
explicit NopInteractionStrategy(KoToolBase *parent)
: KoInteractionStrategy(parent)
{
}
KUndo2Command *createCommand() override
{
return 0;
}
void handleMouseMove(const QPointF & /*mouseLocation*/, Qt::KeyboardModifiers /*modifiers*/) override {}
void finishInteraction(Qt::KeyboardModifiers /*modifiers*/) override {}
void paint(QPainter &painter, const KoViewConverter &converter) override {
Q_UNUSED(painter);
Q_UNUSED(converter);
}
};
class SelectionInteractionStrategy : public KoShapeRubberSelectStrategy
{
public:
explicit SelectionInteractionStrategy(KoToolBase *parent, const QPointF &clicked, bool useSnapToGrid)
: KoShapeRubberSelectStrategy(parent, clicked, useSnapToGrid)
{
}
void paint(QPainter &painter, const KoViewConverter &converter) override {
KoShapeRubberSelectStrategy::paint(painter, converter);
}
void finishInteraction(Qt::KeyboardModifiers modifiers = 0) override
{
Q_UNUSED(modifiers);
DefaultTool *defaultTool = dynamic_cast<DefaultTool*>(tool());
KIS_SAFE_ASSERT_RECOVER_RETURN(defaultTool);
KoSelection * selection = defaultTool->koSelection();
const bool useContainedMode = currentMode() == CoveringSelection;
QList<KoShape *> shapes =
defaultTool->shapeManager()->
shapesAt(selectedRectangle(), true, useContainedMode);
Q_FOREACH (KoShape * shape, shapes) {
if (!shape->isSelectable()) continue;
selection->select(shape);
}
defaultTool->repaintDecorations();
defaultTool->canvas()->updateCanvas(selectedRectangle());
}
};
#include <KoGradientBackground.h>
#include "KoShapeGradientHandles.h"
#include "ShapeGradientEditStrategy.h"
class DefaultTool::MoveGradientHandleInteractionFactory : public KoInteractionStrategyFactory
{
public:
MoveGradientHandleInteractionFactory(KoFlake::FillVariant fillVariant,
int priority, const QString &id, DefaultTool *_q)
: KoInteractionStrategyFactory(priority, id),
q(_q),
m_fillVariant(fillVariant)
{
}
KoInteractionStrategy* createStrategy(KoPointerEvent *ev) override
{
m_currentHandle = handleAt(ev->point);
if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) {
KoShape *shape = onlyEditableShape();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0);
return new ShapeGradientEditStrategy(q, m_fillVariant, shape, m_currentHandle.type, ev->point);
}
return 0;
}
bool hoverEvent(KoPointerEvent *ev) override
{
m_currentHandle = handleAt(ev->point);
return false;
}
bool paintOnHover(QPainter &painter, const KoViewConverter &converter) override
{
Q_UNUSED(painter);
Q_UNUSED(converter);
return false;
}
bool tryUseCustomCursor() override {
if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) {
q->useCursor(Qt::OpenHandCursor);
+ return true;
}
- return m_currentHandle.type != KoShapeGradientHandles::Handle::None;
+ return false;
}
private:
KoShape* onlyEditableShape() const {
KoSelection *selection = q->koSelection();
QList<KoShape*> shapes = selection->selectedEditableShapes();
KoShape *shape = 0;
if (shapes.size() == 1) {
shape = shapes.first();
}
return shape;
}
KoShapeGradientHandles::Handle handleAt(const QPointF &pos) {
KoShapeGradientHandles::Handle result;
KoShape *shape = onlyEditableShape();
if (shape) {
KoFlake::SelectionHandle globalHandle = q->handleAt(pos);
const qreal distanceThresholdSq =
globalHandle == KoFlake::NoHandle ?
HANDLE_DISTANCE_SQ : 0.25 * HANDLE_DISTANCE_SQ;
const KoViewConverter *converter = q->canvas()->viewConverter();
const QPointF viewPoint = converter->documentToView(pos);
qreal minDistanceSq = std::numeric_limits<qreal>::max();
KoShapeGradientHandles sh(m_fillVariant, shape);
Q_FOREACH (const KoShapeGradientHandles::Handle &handle, sh.handles()) {
const QPointF handlePoint = converter->documentToView(handle.pos);
const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
if (distanceSq < distanceThresholdSq && distanceSq < minDistanceSq) {
result = handle;
minDistanceSq = distanceSq;
}
}
}
return result;
}
private:
DefaultTool *q;
KoFlake::FillVariant m_fillVariant;
KoShapeGradientHandles::Handle m_currentHandle;
};
class SelectionHandler : public KoToolSelection
{
public:
SelectionHandler(DefaultTool *parent)
: KoToolSelection(parent)
, m_selection(parent->koSelection())
{
}
bool hasSelection() override
{
if (m_selection) {
return m_selection->count();
}
return false;
}
private:
QPointer<KoSelection> m_selection;
};
DefaultTool::DefaultTool(KoCanvasBase *canvas, bool connectToSelectedShapesProxy)
: KoInteractionTool(canvas)
, m_lastHandle(KoFlake::NoHandle)
, m_hotPosition(KoFlake::TopLeft)
, m_mouseWasInsideHandles(false)
, m_decorator(0)
, m_selectionHandler(new SelectionHandler(this))
, m_tabbedOptionWidget(0)
{
setupActions();
QPixmap rotatePixmap, shearPixmap;
rotatePixmap.load(":/cursor_rotate.png");
Q_ASSERT(!rotatePixmap.isNull());
shearPixmap.load(":/cursor_shear.png");
Q_ASSERT(!shearPixmap.isNull());
m_rotateCursors[0] = QCursor(rotatePixmap.transformed(QTransform().rotate(45)));
m_rotateCursors[1] = QCursor(rotatePixmap.transformed(QTransform().rotate(90)));
m_rotateCursors[2] = QCursor(rotatePixmap.transformed(QTransform().rotate(135)));
m_rotateCursors[3] = QCursor(rotatePixmap.transformed(QTransform().rotate(180)));
m_rotateCursors[4] = QCursor(rotatePixmap.transformed(QTransform().rotate(225)));
m_rotateCursors[5] = QCursor(rotatePixmap.transformed(QTransform().rotate(270)));
m_rotateCursors[6] = QCursor(rotatePixmap.transformed(QTransform().rotate(315)));
m_rotateCursors[7] = QCursor(rotatePixmap);
/*
m_rotateCursors[0] = QCursor(Qt::RotateNCursor);
m_rotateCursors[1] = QCursor(Qt::RotateNECursor);
m_rotateCursors[2] = QCursor(Qt::RotateECursor);
m_rotateCursors[3] = QCursor(Qt::RotateSECursor);
m_rotateCursors[4] = QCursor(Qt::RotateSCursor);
m_rotateCursors[5] = QCursor(Qt::RotateSWCursor);
m_rotateCursors[6] = QCursor(Qt::RotateWCursor);
m_rotateCursors[7] = QCursor(Qt::RotateNWCursor);
*/
m_shearCursors[0] = QCursor(shearPixmap);
m_shearCursors[1] = QCursor(shearPixmap.transformed(QTransform().rotate(45)));
m_shearCursors[2] = QCursor(shearPixmap.transformed(QTransform().rotate(90)));
m_shearCursors[3] = QCursor(shearPixmap.transformed(QTransform().rotate(135)));
m_shearCursors[4] = QCursor(shearPixmap.transformed(QTransform().rotate(180)));
m_shearCursors[5] = QCursor(shearPixmap.transformed(QTransform().rotate(225)));
m_shearCursors[6] = QCursor(shearPixmap.transformed(QTransform().rotate(270)));
m_shearCursors[7] = QCursor(shearPixmap.transformed(QTransform().rotate(315)));
m_sizeCursors[0] = Qt::SizeVerCursor;
m_sizeCursors[1] = Qt::SizeBDiagCursor;
m_sizeCursors[2] = Qt::SizeHorCursor;
m_sizeCursors[3] = Qt::SizeFDiagCursor;
m_sizeCursors[4] = Qt::SizeVerCursor;
m_sizeCursors[5] = Qt::SizeBDiagCursor;
m_sizeCursors[6] = Qt::SizeHorCursor;
m_sizeCursors[7] = Qt::SizeFDiagCursor;
if (connectToSelectedShapesProxy) {
connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(updateActions()));
}
}
DefaultTool::~DefaultTool()
{
}
void DefaultTool::slotActivateEditFillGradient(bool value)
{
if (value) {
addInteractionFactory(
new MoveGradientHandleInteractionFactory(KoFlake::Fill,
1, EditFillGradientFactoryId, this));
} else {
removeInteractionFactory(EditFillGradientFactoryId);
}
repaintDecorations();
}
void DefaultTool::slotActivateEditStrokeGradient(bool value)
{
if (value) {
addInteractionFactory(
new MoveGradientHandleInteractionFactory(KoFlake::StrokeFill,
0, EditStrokeGradientFactoryId, this));
} else {
removeInteractionFactory(EditStrokeGradientFactoryId);
}
repaintDecorations();
}
bool DefaultTool::wantsAutoScroll() const
{
return true;
}
void DefaultTool::addMappedAction(KisSignalMapper *mapper, const QString &actionId, int commandType)
{
QAction *a =action(actionId);
connect(a, SIGNAL(triggered()), mapper, SLOT(map()));
mapper->setMapping(a, commandType);
}
void DefaultTool::setupActions()
{
m_alignSignalsMapper = new KisSignalMapper(this);
addMappedAction(m_alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment);
addMappedAction(m_alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment);
addMappedAction(m_alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment);
addMappedAction(m_alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment);
addMappedAction(m_alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment);
addMappedAction(m_alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment);
m_distributeSignalsMapper = new KisSignalMapper(this);
addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution);
addMappedAction(m_distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution);
m_transformSignalsMapper = new KisSignalMapper(this);
addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_cw", TransformRotate90CW);
addMappedAction(m_transformSignalsMapper, "object_transform_rotate_90_ccw", TransformRotate90CCW);
addMappedAction(m_transformSignalsMapper, "object_transform_rotate_180", TransformRotate180);
addMappedAction(m_transformSignalsMapper, "object_transform_mirror_horizontally", TransformMirrorX);
addMappedAction(m_transformSignalsMapper, "object_transform_mirror_vertically", TransformMirrorY);
addMappedAction(m_transformSignalsMapper, "object_transform_reset", TransformReset);
m_booleanSignalsMapper = new KisSignalMapper(this);
addMappedAction(m_booleanSignalsMapper, "object_unite", BooleanUnion);
addMappedAction(m_booleanSignalsMapper, "object_intersect", BooleanIntersection);
addMappedAction(m_booleanSignalsMapper, "object_subtract", BooleanSubtraction);
m_contextMenu.reset(new QMenu());
}
qreal DefaultTool::rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation)
{
QPointF selectionCenter = koSelection()->absolutePosition();
QPointF direction;
switch (handle) {
case KoFlake::TopMiddleHandle:
if (useEdgeRotation) {
direction = koSelection()->absolutePosition(KoFlake::TopRight)
- koSelection()->absolutePosition(KoFlake::TopLeft);
} else {
QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft);
handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRight) - handlePosition);
direction = handlePosition - selectionCenter;
}
break;
case KoFlake::TopRightHandle:
direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRight) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized()).toPointF();
break;
case KoFlake::RightMiddleHandle:
if (useEdgeRotation) {
direction = koSelection()->absolutePosition(KoFlake::BottomRight)
- koSelection()->absolutePosition(KoFlake::TopRight);
} else {
QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRight);
handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition);
direction = handlePosition - selectionCenter;
}
break;
case KoFlake::BottomRightHandle:
direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRight) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized()).toPointF();
break;
case KoFlake::BottomMiddleHandle:
if (useEdgeRotation) {
direction = koSelection()->absolutePosition(KoFlake::BottomLeft)
- koSelection()->absolutePosition(KoFlake::BottomRight);
} else {
QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeft);
handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRight) - handlePosition);
direction = handlePosition - selectionCenter;
}
break;
case KoFlake::BottomLeftHandle:
direction = koSelection()->absolutePosition(KoFlake::BottomLeft) - selectionCenter;
direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::BottomRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeft) - koSelection()->absolutePosition(KoFlake::TopLeft)).normalized()).toPointF();
break;
case KoFlake::LeftMiddleHandle:
if (useEdgeRotation) {
direction = koSelection()->absolutePosition(KoFlake::TopLeft)
- koSelection()->absolutePosition(KoFlake::BottomLeft);
} else {
QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeft);
handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeft) - handlePosition);
direction = handlePosition - selectionCenter;
}
break;
case KoFlake::TopLeftHandle:
direction = koSelection()->absolutePosition(KoFlake::TopLeft) - selectionCenter;
direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::TopRight)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeft) - koSelection()->absolutePosition(KoFlake::BottomLeft)).normalized()).toPointF();
break;
case KoFlake::NoHandle:
return 0.0;
break;
}
qreal rotation = atan2(direction.y(), direction.x()) * 180.0 / M_PI;
switch (handle) {
case KoFlake::TopMiddleHandle:
if (useEdgeRotation) {
rotation -= 0.0;
} else {
rotation -= 270.0;
}
break;
case KoFlake::TopRightHandle:
rotation -= 315.0;
break;
case KoFlake::RightMiddleHandle:
if (useEdgeRotation) {
rotation -= 90.0;
} else {
rotation -= 0.0;
}
break;
case KoFlake::BottomRightHandle:
rotation -= 45.0;
break;
case KoFlake::BottomMiddleHandle:
if (useEdgeRotation) {
rotation -= 180.0;
} else {
rotation -= 90.0;
}
break;
case KoFlake::BottomLeftHandle:
rotation -= 135.0;
break;
case KoFlake::LeftMiddleHandle:
if (useEdgeRotation) {
rotation -= 270.0;
} else {
rotation -= 180.0;
}
break;
case KoFlake::TopLeftHandle:
rotation -= 225.0;
break;
default:
;
}
if (rotation < 0.0) {
rotation += 360.0;
}
return rotation;
}
void DefaultTool::updateCursor()
{
if (tryUseCustomCursor()) return;
QCursor cursor = Qt::ArrowCursor;
QString statusText;
KoSelection *selection = koSelection();
if (selection && selection->count() > 0) { // has a selection
bool editable = !selection->selectedEditableShapes().isEmpty();
if (!m_mouseWasInsideHandles) {
m_angle = rotationOfHandle(m_lastHandle, true);
int rotOctant = 8 + int(8.5 + m_angle / 45);
bool rotateHandle = false;
bool shearHandle = false;
switch (m_lastHandle) {
case KoFlake::TopMiddleHandle:
cursor = m_shearCursors[(0 + rotOctant) % 8];
shearHandle = true;
break;
case KoFlake::TopRightHandle:
cursor = m_rotateCursors[(1 + rotOctant) % 8];
rotateHandle = true;
break;
case KoFlake::RightMiddleHandle:
cursor = m_shearCursors[(2 + rotOctant) % 8];
shearHandle = true;
break;
case KoFlake::BottomRightHandle:
cursor = m_rotateCursors[(3 + rotOctant) % 8];
rotateHandle = true;
break;
case KoFlake::BottomMiddleHandle:
cursor = m_shearCursors[(4 + rotOctant) % 8];
shearHandle = true;
break;
case KoFlake::BottomLeftHandle:
cursor = m_rotateCursors[(5 + rotOctant) % 8];
rotateHandle = true;
break;
case KoFlake::LeftMiddleHandle:
cursor = m_shearCursors[(6 + rotOctant) % 8];
shearHandle = true;
break;
case KoFlake::TopLeftHandle:
cursor = m_rotateCursors[(7 + rotOctant) % 8];
rotateHandle = true;
break;
case KoFlake::NoHandle:
cursor = Qt::ArrowCursor;
break;
}
if (rotateHandle) {
statusText = i18n("Left click rotates around center, right click around highlighted position.");
}
if (shearHandle) {
statusText = i18n("Click and drag to shear selection.");
}
} else {
statusText = i18n("Click and drag to resize selection.");
m_angle = rotationOfHandle(m_lastHandle, false);
int rotOctant = 8 + int(8.5 + m_angle / 45);
bool cornerHandle = false;
switch (m_lastHandle) {
case KoFlake::TopMiddleHandle:
cursor = m_sizeCursors[(0 + rotOctant) % 8];
break;
case KoFlake::TopRightHandle:
cursor = m_sizeCursors[(1 + rotOctant) % 8];
cornerHandle = true;
break;
case KoFlake::RightMiddleHandle:
cursor = m_sizeCursors[(2 + rotOctant) % 8];
break;
case KoFlake::BottomRightHandle:
cursor = m_sizeCursors[(3 + rotOctant) % 8];
cornerHandle = true;
break;
case KoFlake::BottomMiddleHandle:
cursor = m_sizeCursors[(4 + rotOctant) % 8];
break;
case KoFlake::BottomLeftHandle:
cursor = m_sizeCursors[(5 + rotOctant) % 8];
cornerHandle = true;
break;
case KoFlake::LeftMiddleHandle:
cursor = m_sizeCursors[(6 + rotOctant) % 8];
break;
case KoFlake::TopLeftHandle:
cursor = m_sizeCursors[(7 + rotOctant) % 8];
cornerHandle = true;
break;
case KoFlake::NoHandle:
cursor = Qt::SizeAllCursor;
statusText = i18n("Click and drag to move selection.");
break;
}
if (cornerHandle) {
statusText = i18n("Click and drag to resize selection. Middle click to set highlighted position.");
}
}
if (!editable) {
cursor = Qt::ArrowCursor;
}
} else {
// there used to be guides... :'''(
}
useCursor(cursor);
if (currentStrategy() == 0) {
emit statusTextChanged(statusText);
}
}
void DefaultTool::paint(QPainter &painter, const KoViewConverter &converter)
{
KoSelection *selection = koSelection();
if (selection) {
this->m_decorator = new SelectionDecorator(canvas()->resourceManager());
{
/**
* Selection masks don't render the outline of the shapes, so we should
* do that explicitly when rendering them via selection
*/
KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
KisNodeSP node = kisCanvas->viewManager()->nodeManager()->activeNode();
const bool isSelectionMask = node && node->inherits("KisSelectionMask");
m_decorator->setForceShapeOutlines(isSelectionMask);
}
m_decorator->setSelection(selection);
m_decorator->setHandleRadius(handleRadius());
m_decorator->setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId));
m_decorator->setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId));
m_decorator->paint(painter, converter);
}
KoInteractionTool::paint(painter, converter);
painter.save();
- KoShape::applyConversion(painter, converter);
+ painter.setTransform(converter.documentToView(), true);
canvas()->snapGuide()->paint(painter, converter);
painter.restore();
}
bool DefaultTool::isValidForCurrentLayer() const
{
// if the currently active node has a shape manager, then it is
// probably our client :)
KisCanvas2 *kisCanvas = static_cast<KisCanvas2 *>(canvas());
return bool(kisCanvas->localShapeManager());
}
KoShapeManager *DefaultTool::shapeManager() const {
return canvas()->shapeManager();
}
void DefaultTool::mousePressEvent(KoPointerEvent *event)
{
// this tool only works on a vector layer right now, so give a warning if another layer type is trying to use it
if (!isValidForCurrentLayer()) {
KisCanvas2 *kiscanvas = static_cast<KisCanvas2 *>(canvas());
kiscanvas->viewManager()->showFloatingMessage(
i18n("This tool only works on vector layers. You probably want the move tool."),
QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter);
return;
}
KoInteractionTool::mousePressEvent(event);
updateCursor();
}
void DefaultTool::mouseMoveEvent(KoPointerEvent *event)
{
KoInteractionTool::mouseMoveEvent(event);
if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) {
QRectF bound = handlesSize();
if (bound.contains(event->point)) {
bool inside;
KoFlake::SelectionHandle newDirection = handleAt(event->point, &inside);
if (inside != m_mouseWasInsideHandles || m_lastHandle != newDirection) {
m_lastHandle = newDirection;
m_mouseWasInsideHandles = inside;
//repaintDecorations();
}
} else {
/*if (m_lastHandle != KoFlake::NoHandle)
repaintDecorations(); */
m_lastHandle = KoFlake::NoHandle;
m_mouseWasInsideHandles = false;
// there used to be guides... :'''(
}
} else {
// there used to be guides... :'''(
}
updateCursor();
}
QRectF DefaultTool::handlesSize()
{
KoSelection *selection = koSelection();
if (!selection || !selection->count()) return QRectF();
recalcSelectionBox(selection);
QRectF bound = m_selectionOutline.boundingRect();
// expansion Border
if (!canvas() || !canvas()->viewConverter()) {
return bound;
}
QPointF border = canvas()->viewConverter()->viewToDocument(QPointF(HANDLE_DISTANCE, HANDLE_DISTANCE));
bound.adjust(-border.x(), -border.y(), border.x(), border.y());
return bound;
}
void DefaultTool::mouseReleaseEvent(KoPointerEvent *event)
{
KoInteractionTool::mouseReleaseEvent(event);
updateCursor();
// This makes sure the decorations that are shown are refreshed. especally the "T" icon
canvas()->updateCanvas(QRectF(0,0,canvas()->canvasWidget()->width(), canvas()->canvasWidget()->height()));
}
void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
KoSelection *selection = koSelection();
KoShape *shape = shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop);
if (shape && selection && !selection->isSelected(shape)) {
if (!(event->modifiers() & Qt::ShiftModifier)) {
selection->deselectAll();
}
selection->select(shape);
}
explicitUserStrokeEndRequest();
}
bool DefaultTool::moveSelection(int direction, Qt::KeyboardModifiers modifiers)
{
bool result = false;
qreal x = 0.0, y = 0.0;
if (direction == Qt::Key_Left) {
x = -5;
} else if (direction == Qt::Key_Right) {
x = 5;
} else if (direction == Qt::Key_Up) {
y = -5;
} else if (direction == Qt::Key_Down) {
y = 5;
}
if (x != 0.0 || y != 0.0) { // actually move
if ((modifiers & Qt::ShiftModifier) != 0) {
x *= 10;
y *= 10;
} else if ((modifiers & Qt::AltModifier) != 0) { // more precise
x /= 5;
y /= 5;
}
QList<KoShape *> shapes = koSelection()->selectedEditableShapes();
if (!shapes.isEmpty()) {
canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y)));
result = true;
}
}
return result;
}
void DefaultTool::keyPressEvent(QKeyEvent *event)
{
KoInteractionTool::keyPressEvent(event);
if (currentStrategy() == 0) {
switch (event->key()) {
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Up:
case Qt::Key_Down:
if (moveSelection(event->key(), event->modifiers())) {
event->accept();
}
break;
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
canvas()->resourceManager()->setResource(HotPosition, event->key() - Qt::Key_1);
event->accept();
break;
default:
return;
}
}
}
void DefaultTool::repaintDecorations()
{
if (koSelection() && koSelection()->count() > 0) {
canvas()->updateCanvas(handlesSize());
}
}
void DefaultTool::copy() const
{
// all the selected shapes, not only editable!
QList<KoShape *> shapes = koSelection()->selectedShapes();
if (!shapes.isEmpty()) {
KoDrag drag;
drag.setSvg(shapes);
drag.addToClipboard();
}
}
void DefaultTool::deleteSelection()
{
QList<KoShape *> shapes;
foreach (KoShape *s, koSelection()->selectedShapes()) {
if (s->isGeometryProtected()) {
continue;
}
shapes << s;
}
if (!shapes.empty()) {
canvas()->addCommand(canvas()->shapeController()->removeShapes(shapes));
}
}
bool DefaultTool::paste()
{
// we no longer have to do anything as tool Proxy will do it for us
return false;
}
KoSelection *DefaultTool::koSelection() const
{
Q_ASSERT(canvas());
Q_ASSERT(canvas()->selectedShapesProxy());
return canvas()->selectedShapesProxy()->selection();
}
KoFlake::SelectionHandle DefaultTool::handleAt(const QPointF &point, bool *innerHandleMeaning)
{
// check for handles in this order; meaning that when handles overlap the one on top is chosen
static const KoFlake::SelectionHandle handleOrder[] = {
KoFlake::BottomRightHandle,
KoFlake::TopLeftHandle,
KoFlake::BottomLeftHandle,
KoFlake::TopRightHandle,
KoFlake::BottomMiddleHandle,
KoFlake::RightMiddleHandle,
KoFlake::LeftMiddleHandle,
KoFlake::TopMiddleHandle,
KoFlake::NoHandle
};
const KoViewConverter *converter = canvas()->viewConverter();
KoSelection *selection = koSelection();
if (!selection || !selection->count() || !converter) {
return KoFlake::NoHandle;
}
recalcSelectionBox(selection);
if (innerHandleMeaning) {
QPainterPath path;
path.addPolygon(m_selectionOutline);
*innerHandleMeaning = path.contains(point) || path.intersects(handlePaintRect(point));
}
const QPointF viewPoint = converter->documentToView(point);
for (int i = 0; i < KoFlake::NoHandle; ++i) {
KoFlake::SelectionHandle handle = handleOrder[i];
const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]);
const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
// if just inside the outline
if (distanceSq < HANDLE_DISTANCE_SQ) {
if (innerHandleMeaning) {
if (distanceSq < INNER_HANDLE_DISTANCE_SQ) {
*innerHandleMeaning = true;
}
}
return handle;
}
}
return KoFlake::NoHandle;
}
void DefaultTool::recalcSelectionBox(KoSelection *selection)
{
KIS_ASSERT_RECOVER_RETURN(selection->count());
- QTransform matrix = selection->absoluteTransformation(0);
+ QTransform matrix = selection->absoluteTransformation();
m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect()));
m_angle = 0.0;
QPolygonF outline = m_selectionOutline; //shorter name in the following :)
m_selectionBox[KoFlake::TopMiddleHandle] = (outline.value(0) + outline.value(1)) / 2;
m_selectionBox[KoFlake::TopRightHandle] = outline.value(1);
m_selectionBox[KoFlake::RightMiddleHandle] = (outline.value(1) + outline.value(2)) / 2;
m_selectionBox[KoFlake::BottomRightHandle] = outline.value(2);
m_selectionBox[KoFlake::BottomMiddleHandle] = (outline.value(2) + outline.value(3)) / 2;
m_selectionBox[KoFlake::BottomLeftHandle] = outline.value(3);
m_selectionBox[KoFlake::LeftMiddleHandle] = (outline.value(3) + outline.value(0)) / 2;
m_selectionBox[KoFlake::TopLeftHandle] = outline.value(0);
if (selection->count() == 1) {
#if 0 // TODO detect mirroring
KoShape *s = koSelection()->firstSelectedShape();
if (s->scaleX() < 0) { // vertically mirrored: swap left / right
std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]);
std::swap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]);
std::swap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]);
}
if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom
std::swap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]);
std::swap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]);
std::swap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]);
}
#endif
}
}
void DefaultTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
KoToolBase::activate(activation, shapes);
QAction *actionBringToFront = action("object_order_front");
connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront()), Qt::UniqueConnection);
QAction *actionRaise = action("object_order_raise");
connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp()), Qt::UniqueConnection);
QAction *actionLower = action("object_order_lower");
connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown()));
QAction *actionSendToBack = action("object_order_back");
connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack()), Qt::UniqueConnection);
QAction *actionGroupBottom = action("object_group");
connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup()), Qt::UniqueConnection);
QAction *actionUngroupBottom = action("object_ungroup");
connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup()), Qt::UniqueConnection);
QAction *actionSplit = action("object_split");
connect(actionSplit, SIGNAL(triggered()), this, SLOT(selectionSplitShapes()), Qt::UniqueConnection);
connect(m_alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int)));
connect(m_distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int)));
connect(m_transformSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionTransform(int)));
connect(m_booleanSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionBooleanOp(int)));
m_mouseWasInsideHandles = false;
m_lastHandle = KoFlake::NoHandle;
useCursor(Qt::ArrowCursor);
repaintDecorations();
updateActions();
if (m_tabbedOptionWidget) {
m_tabbedOptionWidget->activate();
}
}
void DefaultTool::deactivate()
{
KoToolBase::deactivate();
QAction *actionBringToFront = action("object_order_front");
disconnect(actionBringToFront, 0, this, 0);
QAction *actionRaise = action("object_order_raise");
disconnect(actionRaise, 0, this, 0);
QAction *actionLower = action("object_order_lower");
disconnect(actionLower, 0, this, 0);
QAction *actionSendToBack = action("object_order_back");
disconnect(actionSendToBack, 0, this, 0);
QAction *actionGroupBottom = action("object_group");
disconnect(actionGroupBottom, 0, this, 0);
QAction *actionUngroupBottom = action("object_ungroup");
disconnect(actionUngroupBottom, 0, this, 0);
QAction *actionSplit = action("object_split");
disconnect(actionSplit, 0, this, 0);
disconnect(m_alignSignalsMapper, 0, this, 0);
disconnect(m_distributeSignalsMapper, 0, this, 0);
disconnect(m_transformSignalsMapper, 0, this, 0);
disconnect(m_booleanSignalsMapper, 0, this, 0);
if (m_tabbedOptionWidget) {
m_tabbedOptionWidget->deactivate();
}
}
void DefaultTool::selectionGroup()
{
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
if (selectedShapes.isEmpty()) return;
const int groupZIndex = selectedShapes.last()->zIndex();
KoShapeGroup *group = new KoShapeGroup();
group->setZIndex(groupZIndex);
// TODO what if only one shape is left?
KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes"));
new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
canvas()->shapeController()->addShapeDirect(group, 0, cmd);
new KoShapeGroupCommand(group, selectedShapes, true, cmd);
new KoKeepShapesSelectedCommand({}, {group}, canvas()->selectedShapesProxy(), true, cmd);
canvas()->addCommand(cmd);
// update selection so we can ungroup immediately again
selection->deselectAll();
selection->select(group);
}
void DefaultTool::selectionUngroup()
{
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
std::sort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
KUndo2Command *cmd = 0;
QList<KoShape*> newShapes;
// add a ungroup command for each found shape container to the macro command
Q_FOREACH (KoShape *shape, selectedShapes) {
KoShapeGroup *group = dynamic_cast<KoShapeGroup *>(shape);
if (group) {
if (!cmd) {
cmd = new KUndo2Command(kundo2_i18n("Ungroup shapes"));
new KoKeepShapesSelectedCommand(selectedShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
}
newShapes << group->shapes();
new KoShapeUngroupCommand(group, group->shapes(),
group->parent() ? QList<KoShape *>() : shapeManager()->topLevelShapes(),
cmd);
canvas()->shapeController()->removeShape(group, cmd);
}
}
if (cmd) {
new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
canvas()->addCommand(cmd);
}
}
void DefaultTool::selectionTransform(int transformAction)
{
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> editableShapes = selection->selectedEditableShapes();
if (editableShapes.isEmpty()) {
return;
}
QTransform applyTransform;
bool shouldReset = false;
KUndo2MagicString actionName = kundo2_noi18n("BUG: No transform action");
switch (TransformActionType(transformAction)) {
case TransformRotate90CW:
applyTransform.rotate(90.0);
actionName = kundo2_i18n("Rotate Object 90° CW");
break;
case TransformRotate90CCW:
applyTransform.rotate(-90.0);
actionName = kundo2_i18n("Rotate Object 90° CCW");
break;
case TransformRotate180:
applyTransform.rotate(180.0);
actionName = kundo2_i18n("Rotate Object 180°");
break;
case TransformMirrorX:
applyTransform.scale(-1.0, 1.0);
actionName = kundo2_i18n("Mirror Object Horizontally");
break;
case TransformMirrorY:
applyTransform.scale(1.0, -1.0);
actionName = kundo2_i18n("Mirror Object Vertically");
break;
case TransformReset:
shouldReset = true;
actionName = kundo2_i18n("Reset Object Transformations");
break;
}
if (!shouldReset && applyTransform.isIdentity()) return;
QList<QTransform> oldTransforms;
QList<QTransform> newTransforms;
const QRectF outlineRect = KoShape::absoluteOutlineRect(editableShapes);
const QPointF centerPoint = outlineRect.center();
const QTransform centerTrans = QTransform::fromTranslate(centerPoint.x(), centerPoint.y());
const QTransform centerTransInv = QTransform::fromTranslate(-centerPoint.x(), -centerPoint.y());
// we also add selection to the list of transformed shapes, so that its outline is updated correctly
QList<KoShape*> transformedShapes = editableShapes;
transformedShapes << selection;
Q_FOREACH (KoShape *shape, transformedShapes) {
oldTransforms.append(shape->transformation());
QTransform t;
if (!shouldReset) {
- const QTransform world = shape->absoluteTransformation(0);
+ const QTransform world = shape->absoluteTransformation();
t = world * centerTransInv * applyTransform * centerTrans * world.inverted() * shape->transformation();
} else {
const QPointF center = shape->outlineRect().center();
const QPointF offset = shape->transformation().map(center) - center;
t = QTransform::fromTranslate(offset.x(), offset.y());
}
newTransforms.append(t);
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(transformedShapes, oldTransforms, newTransforms);
cmd->setText(actionName);
canvas()->addCommand(cmd);
}
void DefaultTool::selectionBooleanOp(int booleanOp)
{
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> editableShapes = selection->selectedEditableShapes();
if (editableShapes.isEmpty()) {
return;
}
QVector<QPainterPath> srcOutlines;
QPainterPath dstOutline;
KUndo2MagicString actionName = kundo2_noi18n("BUG: boolean action name");
// TODO: implement a reference shape selection dialog!
const int referenceShapeIndex = 0;
KoShape *referenceShape = editableShapes[referenceShapeIndex];
Q_FOREACH (KoShape *shape, editableShapes) {
- srcOutlines << shape->absoluteTransformation(0).map(shape->outline());
+ srcOutlines << shape->absoluteTransformation().map(shape->outline());
}
if (booleanOp == BooleanUnion) {
Q_FOREACH (const QPainterPath &path, srcOutlines) {
dstOutline |= path;
}
actionName = kundo2_i18n("Unite Shapes");
} else if (booleanOp == BooleanIntersection) {
for (int i = 0; i < srcOutlines.size(); i++) {
if (i == 0) {
dstOutline = srcOutlines[i];
} else {
dstOutline &= srcOutlines[i];
}
}
// there is a bug in Qt, sometimes it leaves the resulting
// outline open, so just close it explicitly.
dstOutline.closeSubpath();
actionName = kundo2_i18n("Intersect Shapes");
} else if (booleanOp == BooleanSubtraction) {
for (int i = 0; i < srcOutlines.size(); i++) {
dstOutline = srcOutlines[referenceShapeIndex];
if (i != referenceShapeIndex) {
dstOutline -= srcOutlines[i];
}
}
actionName = kundo2_i18n("Subtract Shapes");
}
KoShape *newShape = 0;
if (!dstOutline.isEmpty()) {
newShape = KoPathShape::createShapeFromPainterPath(dstOutline);
}
KUndo2Command *cmd = new KUndo2Command(actionName);
new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
QList<KoShape*> newSelectedShapes;
if (newShape) {
newShape->setBackground(referenceShape->background());
newShape->setStroke(referenceShape->stroke());
newShape->setZIndex(referenceShape->zIndex());
KoShapeContainer *parent = referenceShape->parent();
canvas()->shapeController()->addShapeDirect(newShape, parent, cmd);
newSelectedShapes << newShape;
}
canvas()->shapeController()->removeShapes(editableShapes, cmd);
new KoKeepShapesSelectedCommand({}, newSelectedShapes, canvas()->selectedShapesProxy(), true, cmd);
canvas()->addCommand(cmd);
}
void DefaultTool::selectionSplitShapes()
{
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> editableShapes = selection->selectedEditableShapes();
if (editableShapes.isEmpty()) {
return;
}
KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Split Shapes"));
new KoKeepShapesSelectedCommand(editableShapes, {}, canvas()->selectedShapesProxy(), false, cmd);
QList<KoShape*> newShapes;
Q_FOREACH (KoShape *shape, editableShapes) {
KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
if (!pathShape) return;
QList<KoPathShape*> splitShapes;
if (pathShape->separate(splitShapes)) {
QList<KoShape*> normalShapes = implicitCastList<KoShape*>(splitShapes);
KoShapeContainer *parent = shape->parent();
canvas()->shapeController()->addShapesDirect(normalShapes, parent, cmd);
canvas()->shapeController()->removeShape(shape, cmd);
newShapes << normalShapes;
}
}
new KoKeepShapesSelectedCommand({}, newShapes, canvas()->selectedShapesProxy(), true, cmd);
canvas()->addCommand(cmd);
}
void DefaultTool::selectionAlign(int _align)
{
KoShapeAlignCommand::Align align =
static_cast<KoShapeAlignCommand::Align>(_align);
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> editableShapes = selection->selectedEditableShapes();
if (editableShapes.isEmpty()) {
return;
}
// TODO add an option to the widget so that one can align to the page
// with multiple selected shapes too
QRectF bb;
// single selected shape is automatically aligned to document rect
if (editableShapes.count() == 1) {
if (!canvas()->resourceManager()->hasResource(KoCanvasResourceProvider::PageSize)) {
return;
}
bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceProvider::PageSize));
} else {
bb = KoShape::absoluteOutlineRect(editableShapes);
}
KoShapeAlignCommand *cmd = new KoShapeAlignCommand(editableShapes, align, bb);
canvas()->addCommand(cmd);
}
void DefaultTool::selectionDistribute(int _distribute)
{
KoShapeDistributeCommand::Distribute distribute =
static_cast<KoShapeDistributeCommand::Distribute>(_distribute);
KoSelection *selection = koSelection();
if (!selection) return;
QList<KoShape *> editableShapes = selection->selectedEditableShapes();
if (editableShapes.size() < 3) {
return;
}
QRectF bb = KoShape::absoluteOutlineRect(editableShapes);
KoShapeDistributeCommand *cmd = new KoShapeDistributeCommand(editableShapes, distribute, bb);
canvas()->addCommand(cmd);
}
void DefaultTool::selectionBringToFront()
{
selectionReorder(KoShapeReorderCommand::BringToFront);
}
void DefaultTool::selectionMoveUp()
{
selectionReorder(KoShapeReorderCommand::RaiseShape);
}
void DefaultTool::selectionMoveDown()
{
selectionReorder(KoShapeReorderCommand::LowerShape);
}
void DefaultTool::selectionSendToBack()
{
selectionReorder(KoShapeReorderCommand::SendToBack);
}
void DefaultTool::selectionReorder(KoShapeReorderCommand::MoveShapeType order)
{
KoSelection *selection = koSelection();
if (!selection) {
return;
}
QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
if (selectedShapes.isEmpty()) {
return;
}
KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, shapeManager(), order);
if (cmd) {
canvas()->addCommand(cmd);
}
}
QList<QPointer<QWidget> > DefaultTool::createOptionWidgets()
{
QList<QPointer<QWidget> > widgets;
m_tabbedOptionWidget = new DefaultToolTabbedWidget(this);
if (isActivated()) {
m_tabbedOptionWidget->activate();
}
widgets.append(m_tabbedOptionWidget);
connect(m_tabbedOptionWidget,
SIGNAL(sigSwitchModeEditFillGradient(bool)),
SLOT(slotActivateEditFillGradient(bool)));
connect(m_tabbedOptionWidget,
SIGNAL(sigSwitchModeEditStrokeGradient(bool)),
SLOT(slotActivateEditStrokeGradient(bool)));
return widgets;
}
void DefaultTool::canvasResourceChanged(int key, const QVariant &res)
{
if (key == HotPosition) {
m_hotPosition = KoFlake::AnchorPosition(res.toInt());
repaintDecorations();
}
}
KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event)
{
KoSelection *selection = koSelection();
if (!selection) return nullptr;
bool insideSelection = false;
KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection);
bool editableShape = !selection->selectedEditableShapes().isEmpty();
const bool selectMultiple = event->modifiers() & Qt::ShiftModifier;
const bool selectNextInStack = event->modifiers() & Qt::ControlModifier;
const bool avoidSelection = event->modifiers() & Qt::AltModifier;
if (selectNextInStack) {
// change the hot selection position when middle clicking on a handle
KoFlake::AnchorPosition newHotPosition = m_hotPosition;
switch (handle) {
case KoFlake::TopMiddleHandle:
newHotPosition = KoFlake::Top;
break;
case KoFlake::TopRightHandle:
newHotPosition = KoFlake::TopRight;
break;
case KoFlake::RightMiddleHandle:
newHotPosition = KoFlake::Right;
break;
case KoFlake::BottomRightHandle:
newHotPosition = KoFlake::BottomRight;
break;
case KoFlake::BottomMiddleHandle:
newHotPosition = KoFlake::Bottom;
break;
case KoFlake::BottomLeftHandle:
newHotPosition = KoFlake::BottomLeft;
break;
case KoFlake::LeftMiddleHandle:
newHotPosition = KoFlake::Left;
break;
case KoFlake::TopLeftHandle:
newHotPosition = KoFlake::TopLeft;
break;
case KoFlake::NoHandle:
default:
// check if we had hit the center point
const KoViewConverter *converter = canvas()->viewConverter();
QPointF pt = converter->documentToView(event->point);
// TODO: use calculated values instead!
QPointF centerPt = converter->documentToView(selection->absolutePosition());
if (kisSquareDistance(pt, centerPt) < HANDLE_DISTANCE_SQ) {
newHotPosition = KoFlake::Center;
}
break;
}
if (m_hotPosition != newHotPosition) {
canvas()->resourceManager()->setResource(HotPosition, newHotPosition);
return new NopInteractionStrategy(this);
}
}
if (!avoidSelection && editableShape) {
// manipulation of selected shapes goes first
if (handle != KoFlake::NoHandle) {
// resizing or shearing only with left mouse button
if (insideSelection) {
bool forceUniformScaling = m_tabbedOptionWidget && m_tabbedOptionWidget->useUniformScaling();
return new ShapeResizeStrategy(this, selection, event->point, handle, forceUniformScaling);
}
if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle ||
handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) {
return new ShapeShearStrategy(this, selection, event->point, handle);
}
// rotating is allowed for right mouse button too
if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle ||
handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) {
return new ShapeRotateStrategy(this, selection, event->point, event->buttons());
}
}
if (!selectMultiple && !selectNextInStack) {
if (insideSelection) {
return new ShapeMoveStrategy(this, selection, event->point);
}
}
}
KoShape *shape = shapeManager()->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop);
if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) {
if (!selectMultiple) {
repaintDecorations();
selection->deselectAll();
}
return new SelectionInteractionStrategy(this, event->point, false);
}
if (selection->isSelected(shape)) {
if (selectMultiple) {
repaintDecorations();
selection->deselect(shape);
}
} else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected
repaintDecorations();
if (!selectMultiple) {
selection->deselectAll();
}
selection->select(shape);
repaintDecorations();
// tablet selection isn't precise and may lead to a move, preventing that
if (event->isTabletEvent()) {
return new NopInteractionStrategy(this);
}
return new ShapeMoveStrategy(this, selection, event->point);
}
return 0;
}
void DefaultTool::updateActions()
{
QList<KoShape*> editableShapes;
if (koSelection()) {
editableShapes = koSelection()->selectedEditableShapes();
}
const bool hasEditableShapes = !editableShapes.isEmpty();
action("object_order_front")->setEnabled(hasEditableShapes);
action("object_order_raise")->setEnabled(hasEditableShapes);
action("object_order_lower")->setEnabled(hasEditableShapes);
action("object_order_back")->setEnabled(hasEditableShapes);
action("object_transform_rotate_90_cw")->setEnabled(hasEditableShapes);
action("object_transform_rotate_90_ccw")->setEnabled(hasEditableShapes);
action("object_transform_rotate_180")->setEnabled(hasEditableShapes);
action("object_transform_mirror_horizontally")->setEnabled(hasEditableShapes);
action("object_transform_mirror_vertically")->setEnabled(hasEditableShapes);
action("object_transform_reset")->setEnabled(hasEditableShapes);
const bool multipleSelected = editableShapes.size() > 1;
const bool alignmentEnabled =
multipleSelected ||
(!editableShapes.isEmpty() &&
canvas()->resourceManager()->hasResource(KoCanvasResourceProvider::PageSize));
action("object_align_horizontal_left")->setEnabled(alignmentEnabled);
action("object_align_horizontal_center")->setEnabled(alignmentEnabled);
action("object_align_horizontal_right")->setEnabled(alignmentEnabled);
action("object_align_vertical_top")->setEnabled(alignmentEnabled);
action("object_align_vertical_center")->setEnabled(alignmentEnabled);
action("object_align_vertical_bottom")->setEnabled(alignmentEnabled);
const bool distributionEnabled = editableShapes.size() > 2;
action("object_distribute_horizontal_left")->setEnabled(distributionEnabled);
action("object_distribute_horizontal_center")->setEnabled(distributionEnabled);
action("object_distribute_horizontal_right")->setEnabled(distributionEnabled);
action("object_distribute_horizontal_gaps")->setEnabled(distributionEnabled);
action("object_distribute_vertical_top")->setEnabled(distributionEnabled);
action("object_distribute_vertical_center")->setEnabled(distributionEnabled);
action("object_distribute_vertical_bottom")->setEnabled(distributionEnabled);
action("object_distribute_vertical_gaps")->setEnabled(distributionEnabled);
updateDistinctiveActions(editableShapes);
emit selectionChanged(editableShapes.size());
}
void DefaultTool::updateDistinctiveActions(const QList<KoShape*> &editableShapes) {
const bool multipleSelected = editableShapes.size() > 1;
action("object_group")->setEnabled(multipleSelected);
action("object_unite")->setEnabled(multipleSelected);
action("object_intersect")->setEnabled(multipleSelected);
action("object_subtract")->setEnabled(multipleSelected);
bool hasShapesWithMultipleSegments = false;
Q_FOREACH (KoShape *shape, editableShapes) {
KoPathShape *pathShape = dynamic_cast<KoPathShape *>(shape);
if (pathShape && pathShape->subpathCount() > 1) {
hasShapesWithMultipleSegments = true;
break;
}
}
action("object_split")->setEnabled(hasShapesWithMultipleSegments);
bool hasGroupShape = false;
foreach (KoShape *shape, editableShapes) {
if (dynamic_cast<KoShapeGroup *>(shape)) {
hasGroupShape = true;
break;
}
}
action("object_ungroup")->setEnabled(hasGroupShape);
}
KoToolSelection *DefaultTool::selection()
{
return m_selectionHandler;
}
QMenu* DefaultTool::popupActionsMenu()
{
if (m_contextMenu) {
m_contextMenu->clear();
m_contextMenu->addSection(i18n("Vector Shape Actions"));
m_contextMenu->addSeparator();
QMenu *transform = m_contextMenu->addMenu(i18n("Transform"));
transform->addAction(action("object_transform_rotate_90_cw"));
transform->addAction(action("object_transform_rotate_90_ccw"));
transform->addAction(action("object_transform_rotate_180"));
transform->addSeparator();
transform->addAction(action("object_transform_mirror_horizontally"));
transform->addAction(action("object_transform_mirror_vertically"));
transform->addSeparator();
transform->addAction(action("object_transform_reset"));
if (action("object_unite")->isEnabled() ||
action("object_intersect")->isEnabled() ||
action("object_subtract")->isEnabled() ||
action("object_split")->isEnabled()) {
QMenu *transform = m_contextMenu->addMenu(i18n("Logical Operations"));
transform->addAction(action("object_unite"));
transform->addAction(action("object_intersect"));
transform->addAction(action("object_subtract"));
transform->addAction(action("object_split"));
}
m_contextMenu->addSeparator();
m_contextMenu->addAction(action("edit_cut"));
m_contextMenu->addAction(action("edit_copy"));
m_contextMenu->addAction(action("edit_paste"));
m_contextMenu->addSeparator();
m_contextMenu->addAction(action("object_order_front"));
m_contextMenu->addAction(action("object_order_raise"));
m_contextMenu->addAction(action("object_order_lower"));
m_contextMenu->addAction(action("object_order_back"));
if (action("object_group")->isEnabled() || action("object_ungroup")->isEnabled()) {
m_contextMenu->addSeparator();
m_contextMenu->addAction(action("object_group"));
m_contextMenu->addAction(action("object_ungroup"));
}
}
return m_contextMenu.data();
}
void DefaultTool::addTransformActions(QMenu *menu) const {
menu->addAction(action("object_transform_rotate_90_cw"));
menu->addAction(action("object_transform_rotate_90_ccw"));
menu->addAction(action("object_transform_rotate_180"));
menu->addSeparator();
menu->addAction(action("object_transform_mirror_horizontally"));
menu->addAction(action("object_transform_mirror_vertically"));
menu->addSeparator();
menu->addAction(action("object_transform_reset"));
}
void DefaultTool::explicitUserStrokeEndRequest()
{
QList<KoShape *> shapes = koSelection()->selectedEditableShapesAndDelegates();
emit activateTemporary(KoToolManager::instance()->preferredToolForSelection(shapes));
}
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.cpp
index 07085c7a78..33f9a1d7f3 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.cpp
+++ b/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.cpp
@@ -1,463 +1,463 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Martin Pfeiffer <hubipete@gmx.net>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2010 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 "DefaultToolGeometryWidget.h"
#include "DefaultTool.h"
#include <KoInteractionTool.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceProvider.h>
#include <KoSelectedShapesProxy.h>
#include <KoSelection.h>
#include <KoUnit.h>
#include <commands/KoShapeResizeCommand.h>
#include <commands/KoShapeMoveCommand.h>
#include <commands/KoShapeSizeCommand.h>
#include <commands/KoShapeTransformCommand.h>
#include <commands/KoShapeKeepAspectRatioCommand.h>
#include <commands/KoShapeTransparencyCommand.h>
#include "SelectionDecorator.h"
#include <KoShapeGroup.h>
#include "KoAnchorSelectionWidget.h"
#include <QAction>
#include <QSize>
#include <QRadioButton>
#include <QLabel>
#include <QCheckBox>
#include <QDoubleSpinBox>
#include <QList>
#include <QTransform>
#include <kis_algebra_2d.h>
#include "kis_aspect_ratio_locker.h"
#include "kis_debug.h"
#include "kis_acyclic_signal_connector.h"
#include "kis_signal_compressor.h"
#include "kis_signals_blocker.h"
DefaultToolGeometryWidget::DefaultToolGeometryWidget(KoInteractionTool *tool, QWidget *parent)
: QWidget(parent)
, m_tool(tool)
, m_sizeAspectLocker(new KisAspectRatioLocker())
, m_savedUniformScaling(false)
{
setupUi(this);
setUnit(m_tool->canvas()->unit());
// Connect and initialize automated aspect locker
m_sizeAspectLocker->connectSpinBoxes(widthSpinBox, heightSpinBox, aspectButton);
aspectButton->setKeepAspectRatio(false);
// TODO: use valueChanged() instead!
connect(positionXSpinBox, SIGNAL(valueChangedPt(qreal)), this, SLOT(slotRepositionShapes()));
connect(positionYSpinBox, SIGNAL(valueChangedPt(qreal)), this, SLOT(slotRepositionShapes()));
KoSelectedShapesProxy *selectedShapesProxy = m_tool->canvas()->selectedShapesProxy();
connect(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdateCheckboxes()));
connect(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdatePositionBoxes()));
connect(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdateOpacitySlider()));
connect(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdatePositionBoxes()));
connect(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdateOpacitySlider()));
connect(chkGlobalCoordinates, SIGNAL(toggled(bool)), SLOT(slotUpdateSizeBoxes()));
/**
* A huge block of self-blocking acycled connections
*/
KisAcyclicSignalConnector *acyclicConnector = new KisAcyclicSignalConnector(this);
acyclicConnector->connectForwardVoid(m_sizeAspectLocker.data(), SIGNAL(aspectButtonChanged()), this, SLOT(slotAspectButtonToggled()));
acyclicConnector->connectBackwardVoid(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdateAspectButton()));
acyclicConnector->connectBackwardVoid(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdateAspectButton()));
KisAcyclicSignalConnector *sizeConnector = acyclicConnector->createCoordinatedConnector();
sizeConnector->connectForwardVoid(m_sizeAspectLocker.data(), SIGNAL(sliderValueChanged()), this, SLOT(slotResizeShapes()));
sizeConnector->connectBackwardVoid(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdateSizeBoxes()));
KisAcyclicSignalConnector *contentSizeConnector = acyclicConnector->createCoordinatedConnector();
contentSizeConnector->connectBackwardVoid(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdateSizeBoxesNoAspectChange()));
// Connect and initialize anchor point resource
KoCanvasResourceProvider *resourceManager = m_tool->canvas()->resourceManager();
connect(resourceManager,
SIGNAL(canvasResourceChanged(int,QVariant)),
SLOT(resourceChanged(int,QVariant)));
resourceManager->setResource(DefaultTool::HotPosition, int(KoFlake::AnchorPosition::Center));
positionSelector->setValue(KoFlake::AnchorPosition(resourceManager->resource(DefaultTool::HotPosition).toInt()));
// Connect anchor point selector
connect(positionSelector, SIGNAL(valueChanged(KoFlake::AnchorPosition)), SLOT(slotAnchorPointChanged()));
dblOpacity->setRange(0.0, 1.0, 2);
dblOpacity->setSingleStep(0.01);
dblOpacity->setFastSliderStep(0.1);
dblOpacity->setPrefixes(i18n("Opacity: "), i18n("Opacity [*varies*]: "));
dblOpacity->setValueGetter(
[](KoShape *s) { return 1.0 - s->transparency(); }
);
connect(dblOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderChanged(qreal)));
// cold init
slotUpdateOpacitySlider();
}
DefaultToolGeometryWidget::~DefaultToolGeometryWidget()
{
}
namespace {
void tryAnchorPosition(KoFlake::AnchorPosition anchor,
const QRectF &rect,
QPointF *position)
{
bool valid = false;
QPointF anchoredPosition = KoFlake::anchorToPoint(anchor, rect, &valid);
if (valid) {
*position = anchoredPosition;
}
}
QRectF calculateSelectionBounds(KoSelection *selection,
KoFlake::AnchorPosition anchor,
bool useGlobalSize,
QList<KoShape*> *outShapes = 0)
{
QList<KoShape*> shapes = selection->selectedEditableShapes();
KoShape *shape = shapes.size() == 1 ? shapes.first() : selection;
QRectF resultRect = shape->outlineRect();
QPointF resultPoint = resultRect.topLeft();
tryAnchorPosition(anchor, resultRect, &resultPoint);
if (useGlobalSize) {
- resultRect = shape->absoluteTransformation(0).mapRect(resultRect);
+ resultRect = shape->absoluteTransformation().mapRect(resultRect);
} else {
/**
* Some shapes, e.g. KoSelection and KoShapeGroup don't have real size() and
* do all the resizing with transformation(), just try to cover this case and
* fetch their scale using the transform.
*/
KisAlgebra2D::DecomposedMatix matrix(shape->transformation());
resultRect = matrix.scaleTransform().mapRect(resultRect);
}
- resultPoint = shape->absoluteTransformation(0).map(resultPoint);
+ resultPoint = shape->absoluteTransformation().map(resultPoint);
if (outShapes) {
*outShapes = shapes;
}
return QRectF(resultPoint, resultRect.size());
}
}
void DefaultToolGeometryWidget::slotAnchorPointChanged()
{
if (!isVisible()) return;
QVariant newValue(positionSelector->value());
m_tool->canvas()->resourceManager()->setResource(DefaultTool::HotPosition, newValue);
slotUpdatePositionBoxes();
}
void DefaultToolGeometryWidget::slotUpdateCheckboxes()
{
if (!isVisible()) return;
KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
QList<KoShape*> shapes = selection->selectedEditableShapes();
KoShapeGroup *onlyGroupShape = 0;
if (shapes.size() == 1) {
onlyGroupShape = dynamic_cast<KoShapeGroup*>(shapes.first());
}
const bool uniformScalingAvailable = shapes.size() <= 1 && !onlyGroupShape;
if (uniformScalingAvailable && !chkUniformScaling->isEnabled()) {
chkUniformScaling->setChecked(m_savedUniformScaling);
chkUniformScaling->setEnabled(uniformScalingAvailable);
} else if (!uniformScalingAvailable && chkUniformScaling->isEnabled()) {
m_savedUniformScaling = chkUniformScaling->isChecked();
chkUniformScaling->setChecked(true);
chkUniformScaling->setEnabled(uniformScalingAvailable);
}
// TODO: not implemented yet!
chkAnchorLock->setEnabled(false);
}
void DefaultToolGeometryWidget::slotAspectButtonToggled()
{
KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
QList<KoShape*> shapes = selection->selectedEditableShapes();
KUndo2Command *cmd =
new KoShapeKeepAspectRatioCommand(shapes, aspectButton->keepAspectRatio());
m_tool->canvas()->addCommand(cmd);
}
void DefaultToolGeometryWidget::slotUpdateAspectButton()
{
if (!isVisible()) return;
KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
QList<KoShape*> shapes = selection->selectedEditableShapes();
bool hasKeepAspectRatio = false;
bool hasNotKeepAspectRatio = false;
Q_FOREACH (KoShape *shape, shapes) {
if (shape->keepAspectRatio()) {
hasKeepAspectRatio = true;
} else {
hasNotKeepAspectRatio = true;
}
if (hasKeepAspectRatio && hasNotKeepAspectRatio) break;
}
Q_UNUSED(hasNotKeepAspectRatio); // TODO: use for tristated mode of the checkbox
aspectButton->setKeepAspectRatio(hasKeepAspectRatio);
}
//namespace {
//qreal calculateCommonShapeTransparency(const QList<KoShape*> &shapes)
//{
// qreal commonTransparency = -1.0;
// Q_FOREACH (KoShape *shape, shapes) {
// if (commonTransparency < 0) {
// commonTransparency = shape->transparency();
// } else if (!qFuzzyCompare(commonTransparency, shape->transparency())) {
// commonTransparency = -1.0;
// break;
// }
// }
// return commonTransparency;
//}
//}
void DefaultToolGeometryWidget::slotOpacitySliderChanged(qreal newOpacity)
{
KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
QList<KoShape*> shapes = selection->selectedEditableShapes();
if (shapes.isEmpty()) return;
KUndo2Command *cmd =
new KoShapeTransparencyCommand(shapes, 1.0 - newOpacity);
m_tool->canvas()->addCommand(cmd);
}
void DefaultToolGeometryWidget::slotUpdateOpacitySlider()
{
if (!isVisible()) return;
KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
QList<KoShape*> shapes = selection->selectedEditableShapes();
dblOpacity->setSelection(shapes);
}
void DefaultToolGeometryWidget::slotUpdateSizeBoxes(bool updateAspect)
{
if (!isVisible()) return;
const bool useGlobalSize = chkGlobalCoordinates->isChecked();
const KoFlake::AnchorPosition anchor = positionSelector->value();
KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
QRectF bounds = calculateSelectionBounds(selection, anchor, useGlobalSize);
const bool hasSizeConfiguration = !bounds.isNull();
widthSpinBox->setEnabled(hasSizeConfiguration);
heightSpinBox->setEnabled(hasSizeConfiguration);
if (hasSizeConfiguration) {
KisSignalsBlocker b(widthSpinBox, heightSpinBox);
widthSpinBox->changeValue(bounds.width());
heightSpinBox->changeValue(bounds.height());
if (updateAspect) {
m_sizeAspectLocker->updateAspect();
}
}
}
void DefaultToolGeometryWidget::slotUpdateSizeBoxesNoAspectChange()
{
slotUpdateSizeBoxes(false);
}
void DefaultToolGeometryWidget::slotUpdatePositionBoxes()
{
if (!isVisible()) return;
const bool useGlobalSize = chkGlobalCoordinates->isChecked();
const KoFlake::AnchorPosition anchor = positionSelector->value();
KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
QRectF bounds = calculateSelectionBounds(selection, anchor, useGlobalSize);
const bool hasSizeConfiguration = !bounds.isNull();
positionXSpinBox->setEnabled(hasSizeConfiguration);
positionYSpinBox->setEnabled(hasSizeConfiguration);
if (hasSizeConfiguration) {
KisSignalsBlocker b(positionXSpinBox, positionYSpinBox);
positionXSpinBox->changeValue(bounds.x());
positionYSpinBox->changeValue(bounds.y());
}
}
void DefaultToolGeometryWidget::slotRepositionShapes()
{
static const qreal eps = 1e-6;
const bool useGlobalSize = chkGlobalCoordinates->isChecked();
const KoFlake::AnchorPosition anchor = positionSelector->value();
QList<KoShape*> shapes;
KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
QRectF bounds = calculateSelectionBounds(selection, anchor, useGlobalSize, &shapes);
if (bounds.isNull()) return;
const QPointF oldPosition = bounds.topLeft();
const QPointF newPosition(positionXSpinBox->value(), positionYSpinBox->value());
const QPointF diff = newPosition - oldPosition;
if (diff.manhattanLength() < eps) return;
QList<QPointF> oldPositions;
QList<QPointF> newPositions;
Q_FOREACH (KoShape *shape, shapes) {
const QPointF oldShapePosition = shape->absolutePosition(anchor);
oldPositions << shape->absolutePosition(anchor);
newPositions << oldShapePosition + diff;
}
KUndo2Command *cmd = new KoShapeMoveCommand(shapes, oldPositions, newPositions, anchor);
m_tool->canvas()->addCommand(cmd);
}
void DefaultToolGeometryWidget::slotResizeShapes()
{
static const qreal eps = 1e-4;
const bool useGlobalSize = chkGlobalCoordinates->isChecked();
const KoFlake::AnchorPosition anchor = positionSelector->value();
QList<KoShape*> shapes;
KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
QRectF bounds = calculateSelectionBounds(selection, anchor, useGlobalSize, &shapes);
if (bounds.isNull()) return;
const QSizeF oldSize(bounds.size());
QSizeF newSize(widthSpinBox->value(), heightSpinBox->value());
newSize = KisAlgebra2D::ensureSizeNotSmaller(newSize, QSizeF(eps, eps));
const qreal scaleX = newSize.width() / oldSize.width();
const qreal scaleY = newSize.height() / oldSize.height();
if (qAbs(scaleX - 1.0) < eps && qAbs(scaleY - 1.0) < eps) return;
const bool usePostScaling =
shapes.size() > 1 || chkUniformScaling->isChecked();
KUndo2Command *cmd = new KoShapeResizeCommand(shapes,
scaleX, scaleY,
bounds.topLeft(),
useGlobalSize,
usePostScaling,
selection->transformation());
m_tool->canvas()->addCommand(cmd);
}
void DefaultToolGeometryWidget::setUnit(const KoUnit &unit)
{
positionXSpinBox->setUnit(unit);
positionYSpinBox->setUnit(unit);
widthSpinBox->setUnit(unit);
heightSpinBox->setUnit(unit);
positionXSpinBox->setLineStep(1.0);
positionYSpinBox->setLineStep(1.0);
widthSpinBox->setLineStep(1.0);
heightSpinBox->setLineStep(1.0);
slotUpdatePositionBoxes();
slotUpdateSizeBoxes();
}
bool DefaultToolGeometryWidget::useUniformScaling() const
{
return chkUniformScaling->isChecked();
}
void DefaultToolGeometryWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
slotUpdatePositionBoxes();
slotUpdateSizeBoxes();
slotUpdateOpacitySlider();
slotUpdateAspectButton();
slotUpdateCheckboxes();
slotAnchorPointChanged();
}
void DefaultToolGeometryWidget::resourceChanged(int key, const QVariant &res)
{
if (key == KoCanvasResourceProvider::Unit) {
setUnit(res.value<KoUnit>());
} else if (key == DefaultTool::HotPosition) {
positionSelector->setValue(KoFlake::AnchorPosition(res.toInt()));
}
}
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp
index 820e9f0a92..74573afe1c 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp
+++ b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp
@@ -1,282 +1,282 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Martin Pfeiffer <hubipete@gmx.net>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2010 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 "DefaultToolWidget.h"
#include "DefaultTool.h"
#include <KoInteractionTool.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceProvider.h>
#include <KoShapeManager.h>
#include <KoSelection.h>
#include <KoUnit.h>
#include <commands/KoShapeMoveCommand.h>
#include <commands/KoShapeSizeCommand.h>
#include <commands/KoShapeTransformCommand.h>
#include <KoPositionSelector.h>
#include "SelectionDecorator.h"
#include "DefaultToolTransformWidget.h"
#include <QAction>
#include <QSize>
#include <QRadioButton>
#include <QLabel>
#include <QCheckBox>
#include <QDoubleSpinBox>
#include <QList>
#include <QTransform>
#include "kis_document_aware_spin_box_unit_manager.h"
DefaultToolWidget::DefaultToolWidget(KoInteractionTool *tool, QWidget *parent)
: QWidget(parent)
, m_tool(tool)
, m_blockSignals(false)
{
setupUi(this);
setUnit(m_tool->canvas()->unit());
aspectButton->setKeepAspectRatio(false);
updatePosition();
updateSize();
connect(positionSelector, SIGNAL(positionSelected(KoFlake::Position)),
this, SLOT(positionSelected(KoFlake::Position)));
connect(positionXSpinBox, SIGNAL(editingFinished()), this, SLOT(positionHasChanged()));
connect(positionYSpinBox, SIGNAL(editingFinished()), this, SLOT(positionHasChanged()));
connect(widthSpinBox, SIGNAL(editingFinished()), this, SLOT(sizeHasChanged()));
connect(heightSpinBox, SIGNAL(editingFinished()), this, SLOT(sizeHasChanged()));
KoSelection *selection = m_tool->canvas()->shapeManager()->selection();
connect(selection, SIGNAL(selectionChanged()), this, SLOT(updatePosition()));
connect(selection, SIGNAL(selectionChanged()), this, SLOT(updateSize()));
KoShapeManager *manager = m_tool->canvas()->shapeManager();
connect(manager, SIGNAL(selectionContentChanged()), this, SLOT(updatePosition()));
connect(manager, SIGNAL(selectionContentChanged()), this, SLOT(updateSize()));
connect(m_tool->canvas()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(resourceChanged(int,QVariant)));
connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)),
this, SLOT(aspectButtonToggled(bool)));
}
void DefaultToolWidget::positionSelected(KoFlake::Position position)
{
m_tool->canvas()->resourceManager()->setResource(DefaultTool::HotPosition, QVariant(position));
updatePosition();
}
void DefaultToolWidget::updatePosition()
{
QPointF selPosition(0, 0);
KoFlake::Position position = positionSelector->position();
KoSelection *selection = m_tool->canvas()->shapeManager()->selection();
if (selection && selection->count()) {
selPosition = selection->absolutePosition(position);
}
positionXSpinBox->setEnabled(selection && selection->count());
positionYSpinBox->setEnabled(selection && selection->count());
if (m_blockSignals) {
return;
}
m_blockSignals = true;
positionXSpinBox->changeValue(selPosition.x());
positionYSpinBox->changeValue(selPosition.y());
QList<KoShape *> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
bool aspectLocked = false;
foreach (KoShape *shape, selectedShapes) {
aspectLocked = aspectLocked | shape->keepAspectRatio();
}
aspectButton->setKeepAspectRatio(aspectLocked);
m_blockSignals = false;
}
void DefaultToolWidget::positionHasChanged()
{
KoSelection *selection = m_tool->canvas()->shapeManager()->selection();
if (!selection || selection->count() <= 0) {
return;
}
KoFlake::Position position = positionSelector->position();
QPointF newPos(positionXSpinBox->value(), positionYSpinBox->value());
QPointF oldPos = selection->absolutePosition(position);
if (oldPos == newPos) {
return;
}
QList<KoShape *> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
QPointF moveBy = newPos - oldPos;
QList<QPointF> oldPositions;
QList<QPointF> newPositions;
Q_FOREACH (KoShape *shape, selectedShapes) {
oldPositions.append(shape->position());
newPositions.append(shape->position() + moveBy);
}
selection->setPosition(selection->position() + moveBy);
m_tool->canvas()->addCommand(new KoShapeMoveCommand(selectedShapes, oldPositions, newPositions));
updatePosition();
}
void DefaultToolWidget::updateSize()
{
QSizeF selSize(0, 0);
KoSelection *selection = m_tool->canvas()->shapeManager()->selection();
uint selectionCount = 0;
if (selection && selection->count()) {
selectionCount = selection->count();
}
if (selectionCount) {
selSize = selection->boundingRect().size();
}
widthSpinBox->setEnabled(selectionCount);
heightSpinBox->setEnabled(selectionCount);
if (m_blockSignals) {
return;
}
m_blockSignals = true;
widthSpinBox->changeValue(selSize.width());
heightSpinBox->changeValue(selSize.height());
m_blockSignals = false;
}
void DefaultToolWidget::sizeHasChanged()
{
if (aspectButton->hasFocus()) {
return;
}
if (m_blockSignals) {
return;
}
QSizeF newSize(widthSpinBox->value(), heightSpinBox->value());
KoSelection *selection = m_tool->canvas()->shapeManager()->selection();
QRectF rect = selection->boundingRect();
if (aspectButton->keepAspectRatio()) {
qreal aspect = rect.width() / rect.height();
if (rect.width() != newSize.width()) {
newSize.setHeight(newSize.width() / aspect);
} else if (rect.height() != newSize.height()) {
newSize.setWidth(newSize.height() * aspect);
}
}
if (rect.width() != newSize.width() || rect.height() != newSize.height()) {
// get the scale/resize center from the position selector
QPointF scaleCenter = selection->absolutePosition(positionSelector->position());
QTransform resizeMatrix;
resizeMatrix.translate(scaleCenter.x(), scaleCenter.y());
// make sure not to divide by 0 in case the selection is a line and has no width. In this case just scale by 1.
resizeMatrix.scale(rect.width() ? newSize.width() / rect.width() : 1, rect.height() ? newSize.height() / rect.height() : 1);
resizeMatrix.translate(-scaleCenter.x(), -scaleCenter.y());
QList<KoShape *> selectedShapes = selection->selectedShapes(KoFlake::StrippedSelection);
QList<QSizeF> oldSizes, newSizes;
QList<QTransform> oldState;
QList<QTransform> newState;
Q_FOREACH (KoShape *shape, selectedShapes) {
shape->update();
QSizeF oldSize = shape->size();
oldState << shape->transformation();
- QTransform shapeMatrix = shape->absoluteTransformation(0);
+ QTransform shapeMatrix = shape->absoluteTransformation();
// calculate the matrix we would apply to the local shape matrix
// that tells us the effective scale values we have to use for the resizing
QTransform localMatrix = shapeMatrix * resizeMatrix * shapeMatrix.inverted();
// save the effective scale values, without any mirroring portion
const qreal scaleX = qAbs(localMatrix.m11());
const qreal scaleY = qAbs(localMatrix.m22());
// calculate the scale matrix which is equivalent to our resizing above
QTransform scaleMatrix = (QTransform().scale(scaleX, scaleY));
scaleMatrix = shapeMatrix.inverted() * scaleMatrix * shapeMatrix;
// calculate the new size of the shape, using the effective scale values
oldSizes << oldSize;
QSizeF newSize = QSizeF(scaleX * oldSize.width(), scaleY * oldSize.height());
newSizes << newSize;
shape->setSize(newSize);
// apply the rest of the transformation without the resizing part
shape->applyAbsoluteTransformation(scaleMatrix.inverted() * resizeMatrix);
newState << shape->transformation();
}
m_tool->repaintDecorations();
selection->applyAbsoluteTransformation(resizeMatrix);
KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Resize"));
new KoShapeSizeCommand(selectedShapes, oldSizes, newSizes, cmd);
new KoShapeTransformCommand(selectedShapes, oldState, newState, cmd);
m_tool->canvas()->addCommand(cmd);
updateSize();
updatePosition();
}
}
void DefaultToolWidget::setUnit(const KoUnit &unit)
{
m_blockSignals = true;
positionXSpinBox->setUnit(unit);
positionYSpinBox->setUnit(unit);
widthSpinBox->setUnit(unit);
heightSpinBox->setUnit(unit);
m_blockSignals = false;
updatePosition();
updateSize();
}
void DefaultToolWidget::resourceChanged(int key, const QVariant &res)
{
if (key == KoCanvasResourceProvider::Unit) {
setUnit(res.value<KoUnit>());
} else if (key == DefaultTool::HotPosition) {
if (res.toInt() != positionSelector->position()) {
positionSelector->setPosition(static_cast<KoFlake::Position>(res.toInt()));
updatePosition();
}
}
}
void DefaultToolWidget::aspectButtonToggled(bool keepAspect)
{
if (m_blockSignals) {
return;
}
KoSelection *selection = m_tool->canvas()->shapeManager()->selection();
foreach (KoShape *shape, selection->selectedShapes(KoFlake::TopLevelSelection)) {
shape->setKeepAspectRatio(keepAspect);
}
}
diff --git a/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.cpp b/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.cpp
index b7e5dd1e63..09b8da03c6 100644
--- a/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.cpp
+++ b/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.cpp
@@ -1,177 +1,177 @@
/*
* 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 "KoShapeGradientHandles.h"
#include <QGradient>
#include <KoShape.h>
#include <KoGradientBackground.h>
#include <KoShapeBackgroundCommand.h>
#include <KoShapeFillWrapper.h>
#include <kis_assert.h>
KoShapeGradientHandles::KoShapeGradientHandles(KoFlake::FillVariant fillVariant, KoShape *shape)
: m_fillVariant(fillVariant),
m_shape(shape)
{
}
-QVector<KoShapeGradientHandles::Handle> KoShapeGradientHandles::handles(const KoViewConverter *converter) const {
+QVector<KoShapeGradientHandles::Handle> KoShapeGradientHandles::handles() const {
QVector<Handle> result;
const QGradient *g = gradient();
if (!g) return result;
switch (g->type()) {
case QGradient::LinearGradient: {
const QLinearGradient *lgradient = static_cast<const QLinearGradient*>(g);
result << Handle(Handle::LinearStart, lgradient->start());
result << Handle(Handle::LinearEnd, lgradient->finalStop());
break;
}
case QGradient::RadialGradient: {
const QRadialGradient *rgradient = static_cast<const QRadialGradient*>(g);
result << Handle(Handle::RadialCenter, rgradient->center());
if (rgradient->center() != rgradient->focalPoint()) {
result << Handle(Handle::RadialFocalPoint, rgradient->focalPoint());
}
result << Handle(Handle::RadialRadius,
rgradient->center() + QPointF(rgradient->centerRadius(), 0));
break;
}
case QGradient::ConicalGradient:
// not supported
break;
case QGradient::NoGradient:
// not supported
break;
}
if (g->coordinateMode() == QGradient::ObjectBoundingMode) {
const QRectF boundingRect = m_shape->outlineRect();
const QTransform gradientToUser(boundingRect.width(), 0, 0, boundingRect.height(),
boundingRect.x(), boundingRect.y());
- const QTransform t = gradientToUser * m_shape->absoluteTransformation(converter);
+ const QTransform t = gradientToUser * m_shape->absoluteTransformation();
QVector<Handle>::iterator it = result.begin();
for (; it != result.end(); ++it) {
it->pos = t.map(it->pos);
}
}
return result;
}
QGradient::Type KoShapeGradientHandles::type() const
{
const QGradient *g = gradient();
return g ? g->type() : QGradient::NoGradient;
}
KUndo2Command *KoShapeGradientHandles::moveGradientHandle(KoShapeGradientHandles::Handle::Type handleType, const QPointF &absoluteOffset)
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(handleType != Handle::None, 0);
KoShapeFillWrapper wrapper(m_shape, m_fillVariant);
const QGradient *originalGradient = wrapper.gradient();
QTransform originalTransform = wrapper.gradientTransform();
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(originalGradient, 0);
QGradient *newGradient = 0;
switch (originalGradient->type()) {
case QGradient::LinearGradient: {
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(handleType == Handle::LinearStart ||
handleType == Handle::LinearEnd, 0);
newGradient = KoFlake::cloneGradient(originalGradient);
QLinearGradient *lgradient = static_cast<QLinearGradient*>(newGradient);
if (handleType == Handle::LinearStart) {
lgradient->setStart(getNewHandlePos(lgradient->start(), absoluteOffset, newGradient->coordinateMode()));
} else if (handleType == Handle::LinearEnd) {
lgradient->setFinalStop(getNewHandlePos(lgradient->finalStop(), absoluteOffset, newGradient->coordinateMode()));
}
break;
}
case QGradient::RadialGradient: {
newGradient = KoFlake::cloneGradient(originalGradient);
QRadialGradient *rgradient = static_cast<QRadialGradient*>(newGradient);
if (handleType == Handle::RadialCenter) {
rgradient->setCenter(getNewHandlePos(rgradient->center(), absoluteOffset, newGradient->coordinateMode()));
} else if (handleType == Handle::RadialFocalPoint) {
rgradient->setFocalPoint(getNewHandlePos(rgradient->focalPoint(), absoluteOffset, newGradient->coordinateMode()));
} else if (handleType == Handle::RadialRadius) {
QPointF radiusPos = rgradient->center() + QPointF(QPointF(rgradient->radius(), 0));
radiusPos = getNewHandlePos(radiusPos, absoluteOffset, newGradient->coordinateMode());
rgradient->setRadius(radiusPos.x() - rgradient->center().x());
}
break;
}
case QGradient::ConicalGradient:
// not supported
break;
case QGradient::NoGradient:
// not supported
break;
}
return wrapper.setGradient(newGradient, originalTransform);
}
KoShapeGradientHandles::Handle KoShapeGradientHandles::getHandle(KoShapeGradientHandles::Handle::Type handleType)
{
Handle result;
Q_FOREACH (const Handle &h, handles()) {
if (h.type == handleType) {
result = h;
break;
}
}
return result;
}
const QGradient *KoShapeGradientHandles::gradient() const {
KoShapeFillWrapper wrapper(m_shape, m_fillVariant);
return wrapper.gradient();
}
QPointF KoShapeGradientHandles::getNewHandlePos(const QPointF &oldPos, const QPointF &absoluteOffset, QGradient::CoordinateMode mode)
{
const QTransform offset = QTransform::fromTranslate(absoluteOffset.x(), absoluteOffset.y());
- QTransform localToAbsolute = m_shape->absoluteTransformation(0);
+ QTransform localToAbsolute = m_shape->absoluteTransformation();
if (mode == QGradient::ObjectBoundingMode) {
const QRectF boundingRect = m_shape->outlineRect();
const QTransform gradientToUser(boundingRect.width(), 0, 0, boundingRect.height(),
boundingRect.x(), boundingRect.y());
localToAbsolute = gradientToUser * localToAbsolute;
}
return (localToAbsolute * offset * localToAbsolute.inverted()).map(oldPos);
}
diff --git a/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.h b/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.h
index d88fab5da5..c537a4afde 100644
--- a/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.h
+++ b/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.h
@@ -1,69 +1,69 @@
/*
* 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 KOSHAPEGRADIENTHANDLES_H
#define KOSHAPEGRADIENTHANDLES_H
#include <QPointF>
#include <QGradient>
#include <KoFlake.h>
class KoShape;
class KoViewConverter;
class KUndo2Command;
class KoShapeGradientHandles
{
public:
struct Handle {
enum Type {
None,
LinearStart,
LinearEnd,
RadialCenter,
RadialRadius,
RadialFocalPoint
};
Handle() : type(None) {}
Handle(Type t, const QPointF &p) : type(t), pos(p) {}
Type type;
QPointF pos;
};
public:
KoShapeGradientHandles(KoFlake::FillVariant fillVariant, KoShape *shape);
- QVector<Handle> handles(const KoViewConverter *converter = 0) const;
+ QVector<Handle> handles() const;
QGradient::Type type() const;
KUndo2Command* moveGradientHandle(Handle::Type handleType, const QPointF &absoluteOffset);
Handle getHandle(Handle::Type handleType);
private:
const QGradient* gradient() const;
QPointF getNewHandlePos(const QPointF &oldPos, const QPointF &absoluteOffset, QGradient::CoordinateMode mode);
private:
KoFlake::FillVariant m_fillVariant;
KoShape *m_shape;
};
#endif // KOSHAPEGRADIENTHANDLES_H
diff --git a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp
index 2c8c7dbe0d..b8b4cc9692 100644
--- a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp
+++ b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp
@@ -1,202 +1,202 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-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 "SelectionDecorator.h"
#include <KoShape.h>
#include <KoSelection.h>
#include <KoResourcePaths.h>
#include "kis_algebra_2d.h"
#include "kis_debug.h"
#include <KisHandlePainterHelper.h>
#include <KoCanvasResourceProvider.h>
#include <KisQPainterStateSaver.h>
#include "KoShapeGradientHandles.h"
#include <KoCanvasBase.h>
#include <KoSvgTextShape.h>
#include "kis_painting_tweaks.h"
#include "kis_coordinates_converter.h"
#include "kis_icon_utils.h"
#define HANDLE_DISTANCE 10
SelectionDecorator::SelectionDecorator(KoCanvasResourceProvider *resourceManager)
: m_hotPosition(KoFlake::Center)
, m_handleRadius(7)
, m_lineWidth(2)
, m_showFillGradientHandles(false)
, m_showStrokeFillGradientHandles(false)
, m_forceShapeOutlines(false)
{
m_hotPosition =
KoFlake::AnchorPosition(
resourceManager->resource(KoFlake::HotPosition).toInt());
}
void SelectionDecorator::setSelection(KoSelection *selection)
{
m_selection = selection;
}
void SelectionDecorator::setHandleRadius(int radius)
{
m_handleRadius = radius;
m_lineWidth = qMax(1, (int)(radius / 2));
}
void SelectionDecorator::setShowFillGradientHandles(bool value)
{
m_showFillGradientHandles = value;
}
void SelectionDecorator::setShowStrokeFillGradientHandles(bool value)
{
m_showStrokeFillGradientHandles = value;
}
void SelectionDecorator::paint(QPainter &painter, const KoViewConverter &converter)
{
QList<KoShape*> selectedShapes = m_selection->selectedVisibleShapes();
if (selectedShapes.isEmpty()) return;
const bool haveOnlyOneEditableShape =
m_selection->selectedEditableShapes().size() == 1 &&
selectedShapes.size() == 1;
bool editable = false;
bool forceBoundngRubberLine = false;
Q_FOREACH (KoShape *shape, KoShape::linearizeSubtree(selectedShapes)) {
if (!haveOnlyOneEditableShape || !m_showStrokeFillGradientHandles) {
KisHandlePainterHelper helper =
- KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius);
+ KoShape::createHandlePainterHelperView(&painter, shape, converter, m_handleRadius);
helper.setHandleStyle(KisHandleStyle::secondarySelection());
if (!m_forceShapeOutlines) {
helper.drawRubberLine(shape->outlineRect());
} else {
QList<QPolygonF> polys = shape->outline().toSubpathPolygons();
if (polys.size() == 1) {
const QPolygonF poly1 = polys[0];
const QPolygonF poly2 = QPolygonF(polys[0].boundingRect());
const QPolygonF nonoverlap = poly2.subtracted(poly1);
forceBoundngRubberLine |= !nonoverlap.isEmpty();
}
Q_FOREACH (const QPolygonF &poly, polys) {
helper.drawRubberLine(poly);
}
}
}
if (shape->isShapeEditable()) {
editable = true;
}
}
const QRectF handleArea = m_selection->outlineRect();
// draw extra rubber line around all the shapes
if (selectedShapes.size() > 1 || forceBoundngRubberLine) {
KisHandlePainterHelper helper =
- KoShape::createHandlePainterHelper(&painter, m_selection, converter, m_handleRadius);
+ KoShape::createHandlePainterHelperView(&painter, m_selection, converter, m_handleRadius);
helper.setHandleStyle(KisHandleStyle::primarySelection());
helper.drawRubberLine(handleArea);
}
// if we have no editable shape selected there
// is no need drawing the selection handles
if (editable) {
KisHandlePainterHelper helper =
- KoShape::createHandlePainterHelper(&painter, m_selection, converter, m_handleRadius);
+ KoShape::createHandlePainterHelperView(&painter, m_selection, converter, m_handleRadius);
helper.setHandleStyle(KisHandleStyle::primarySelection());
QPolygonF outline = handleArea;
{
helper.drawHandleRect(outline.value(0));
helper.drawHandleRect(outline.value(1));
helper.drawHandleRect(outline.value(2));
helper.drawHandleRect(outline.value(3));
helper.drawHandleRect(0.5 * (outline.value(0) + outline.value(1)));
helper.drawHandleRect(0.5 * (outline.value(1) + outline.value(2)));
helper.drawHandleRect(0.5 * (outline.value(2) + outline.value(3)));
helper.drawHandleRect(0.5 * (outline.value(3) + outline.value(0)));
QPointF hotPos = KoFlake::anchorToPoint(m_hotPosition, handleArea);
helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
helper.drawHandleRect(hotPos);
}
}
if (haveOnlyOneEditableShape) {
KoShape *shape = selectedShapes.first();
if (m_showFillGradientHandles) {
paintGradientHandles(shape, KoFlake::Fill, painter, converter);
} else if (m_showStrokeFillGradientHandles) {
paintGradientHandles(shape, KoFlake::StrokeFill, painter, converter);
}
}
}
void SelectionDecorator::paintGradientHandles(KoShape *shape, KoFlake::FillVariant fillVariant, QPainter &painter, const KoViewConverter &converter)
{
KoShapeGradientHandles gradientHandles(fillVariant, shape);
QVector<KoShapeGradientHandles::Handle> handles = gradientHandles.handles();
KisHandlePainterHelper helper =
- KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius);
+ KoShape::createHandlePainterHelperView(&painter, shape, converter, m_handleRadius);
- const QTransform t = shape->absoluteTransformation(0).inverted();
+ const QTransform t = shape->absoluteTransformation().inverted();
if (gradientHandles.type() == QGradient::LinearGradient) {
KIS_SAFE_ASSERT_RECOVER_NOOP(handles.size() == 2);
if (handles.size() == 2) {
helper.setHandleStyle(KisHandleStyle::gradientArrows());
helper.drawGradientArrow(t.map(handles[0].pos), t.map(handles[1].pos), 1.5 * m_handleRadius);
}
}
helper.setHandleStyle(KisHandleStyle::gradientHandles());
Q_FOREACH (const KoShapeGradientHandles::Handle &h, handles) {
if (h.type == KoShapeGradientHandles::Handle::RadialCenter) {
helper.drawGradientCrossHandle(t.map(h.pos), 1.2 * m_handleRadius);
} else {
helper.drawGradientHandle(t.map(h.pos), 1.2 * m_handleRadius);
}
}
}
void SelectionDecorator::setForceShapeOutlines(bool value)
{
m_forceShapeOutlines = value;
}
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp
index 626032923b..63f0c755b4 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp
+++ b/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp
@@ -1,111 +1,111 @@
/*
* 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 "ShapeGradientEditStrategy.h"
#include <KoToolBase.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceProvider.h>
#include <KoShapeManager.h>
#include <KoShape.h>
#include "kis_assert.h"
#include "SelectionDecorator.h"
#include <kundo2command.h>
#include <KoSnapGuide.h>
#include <KisSnapPointStrategy.h>
#include "kis_debug.h"
struct ShapeGradientEditStrategy::Private
{
Private(const QPointF &_start, KoShape *shape, KoFlake::FillVariant fillVariant)
: start(_start),
gradientHandles(fillVariant, shape)
{
}
QPointF start;
QPointF initialOffset;
KoShapeGradientHandles gradientHandles;
- KoShapeGradientHandles::Handle::Type handleType;
+ KoShapeGradientHandles::Handle::Type handleType {KoShapeGradientHandles::Handle::Type::None};
QScopedPointer<KUndo2Command> intermediateCommand;
};
ShapeGradientEditStrategy::ShapeGradientEditStrategy(KoToolBase *tool,
KoFlake::FillVariant fillVariant,
KoShape *shape,
KoShapeGradientHandles::Handle::Type startHandleType,
const QPointF &clicked)
: KoInteractionStrategy(tool)
, m_d(new Private(clicked, shape, fillVariant))
{
KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
m_d->handleType = startHandleType;
KoShapeGradientHandles::Handle handle = m_d->gradientHandles.getHandle(m_d->handleType);
m_d->initialOffset = handle.pos - clicked;
KisSnapPointStrategy *strategy = new KisSnapPointStrategy();
Q_FOREACH (const KoShapeGradientHandles::Handle &h, m_d->gradientHandles.handles()) {
strategy->addPoint(h.pos);
}
tool->canvas()->snapGuide()->addCustomSnapStrategy(strategy);
}
ShapeGradientEditStrategy::~ShapeGradientEditStrategy()
{
}
void ShapeGradientEditStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers)
{
if (m_d->intermediateCommand) {
m_d->intermediateCommand->undo();
m_d->intermediateCommand.reset();
}
const QPointF snappedPosition = tool()->canvas()->snapGuide()->snap(mouseLocation, m_d->initialOffset, modifiers);
const QPointF diff = snappedPosition- m_d->start;
m_d->intermediateCommand.reset(m_d->gradientHandles.moveGradientHandle(m_d->handleType, diff));
m_d->intermediateCommand->redo();
tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect());
}
KUndo2Command *ShapeGradientEditStrategy::createCommand()
{
return m_d->intermediateCommand.take();
}
void ShapeGradientEditStrategy::finishInteraction(Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
const QRectF dirtyRect = tool()->canvas()->snapGuide()->boundingRect();
tool()->canvas()->snapGuide()->reset();
tool()->canvas()->updateCanvas(dirtyRect);
}
void ShapeGradientEditStrategy::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
}
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp
index 29d4b542f2..f96ae615f7 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp
+++ b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp
@@ -1,253 +1,253 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007,2011 Jan Hambrecht <jaham@gmx.net>
* 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.
*/
#include "ShapeResizeStrategy.h"
#include "SelectionDecorator.h"
#include <KoShapeManager.h>
#include <KoPointerEvent.h>
#include <KoCanvasBase.h>
#include <commands/KoShapeResizeCommand.h>
#include <kis_command_utils.h>
#include <KoSnapGuide.h>
#include <KoToolBase.h>
#include <KoSelection.h>
#include <klocalizedstring.h>
#include <limits>
#include <math.h>
#include <kis_debug.h>
ShapeResizeStrategy::ShapeResizeStrategy(KoToolBase *tool, KoSelection *selection, const QPointF &clicked, KoFlake::SelectionHandle direction, bool forceUniformScalingMode)
: KoInteractionStrategy(tool),
m_forceUniformScalingMode(forceUniformScalingMode)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(selection && selection->count() > 0);
m_selectedShapes = selection->selectedEditableShapes();
m_start = clicked;
KoShape *shape = 0;
if (m_selectedShapes.size() > 1) {
shape = selection;
} else if (m_selectedShapes.size() == 1) {
shape = m_selectedShapes.first();
}
if (shape) {
const qreal w = shape->size().width();
const qreal h = shape->size().height();
switch (direction) {
case KoFlake::TopMiddleHandle:
m_start = 0.5 * (shape->absolutePosition(KoFlake::TopLeft) + shape->absolutePosition(KoFlake::TopRight));
m_top = true; m_bottom = false; m_left = false; m_right = false;
m_globalStillPoint = QPointF(0.5 * w, h);
break;
case KoFlake::TopRightHandle:
m_start = shape->absolutePosition(KoFlake::TopRight);
m_top = true; m_bottom = false; m_left = false; m_right = true;
m_globalStillPoint = QPointF(0, h);
break;
case KoFlake::RightMiddleHandle:
m_start = 0.5 * (shape->absolutePosition(KoFlake::TopRight) + shape->absolutePosition(KoFlake::BottomRight));
m_top = false; m_bottom = false; m_left = false; m_right = true;
m_globalStillPoint = QPointF(0, 0.5 * h);
break;
case KoFlake::BottomRightHandle:
m_start = shape->absolutePosition(KoFlake::BottomRight);
m_top = false; m_bottom = true; m_left = false; m_right = true;
m_globalStillPoint = QPointF(0, 0);
break;
case KoFlake::BottomMiddleHandle:
m_start = 0.5 * (shape->absolutePosition(KoFlake::BottomRight) + shape->absolutePosition(KoFlake::BottomLeft));
m_top = false; m_bottom = true; m_left = false; m_right = false;
m_globalStillPoint = QPointF(0.5 * w, 0);
break;
case KoFlake::BottomLeftHandle:
m_start = shape->absolutePosition(KoFlake::BottomLeft);
m_top = false; m_bottom = true; m_left = true; m_right = false;
m_globalStillPoint = QPointF(w, 0);
break;
case KoFlake::LeftMiddleHandle:
m_start = 0.5 * (shape->absolutePosition(KoFlake::BottomLeft) + shape->absolutePosition(KoFlake::TopLeft));
m_top = false; m_bottom = false; m_left = true; m_right = false;
m_globalStillPoint = QPointF(w, 0.5 * h);
break;
case KoFlake::TopLeftHandle:
m_start = shape->absolutePosition(KoFlake::TopLeft);
m_top = true; m_bottom = false; m_left = true; m_right = false;
m_globalStillPoint = QPointF(w, h);
break;
default:
Q_ASSERT(0); // illegal 'corner'
}
const QPointF p0 = shape->outlineRect().topLeft();
- m_globalStillPoint = shape->absoluteTransformation(0).map(p0 + m_globalStillPoint);
+ m_globalStillPoint = shape->absoluteTransformation().map(p0 + m_globalStillPoint);
m_globalCenterPoint = shape->absolutePosition(KoFlake::Center);
- m_unwindMatrix = shape->absoluteTransformation(0).inverted();
+ m_unwindMatrix = shape->absoluteTransformation().inverted();
m_initialSelectionSize = shape->size();
m_postScalingCoveringTransform = shape->transformation();
}
tool->setStatusText(i18n("Press CTRL to resize from center."));
tool->canvas()->snapGuide()->setIgnoredShapes(KoShape::linearizeSubtree(m_selectedShapes));
}
ShapeResizeStrategy::~ShapeResizeStrategy()
{
}
void ShapeResizeStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers)
{
tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect());
QPointF newPos = tool()->canvas()->snapGuide()->snap(point, modifiers);
tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect());
bool keepAspect = modifiers & Qt::ShiftModifier;
Q_FOREACH (KoShape *shape, m_selectedShapes) {
keepAspect = keepAspect || shape->keepAspectRatio();
}
qreal startWidth = m_initialSelectionSize.width();
if (startWidth < std::numeric_limits<qreal>::epsilon()) {
startWidth = std::numeric_limits<qreal>::epsilon();
}
qreal startHeight = m_initialSelectionSize.height();
if (startHeight < std::numeric_limits<qreal>::epsilon()) {
startHeight = std::numeric_limits<qreal>::epsilon();
}
QPointF distance = m_unwindMatrix.map(newPos) - m_unwindMatrix.map(m_start);
// guard against resizing zero width shapes, which would result in huge zoom factors
if (m_initialSelectionSize.width() < std::numeric_limits<qreal>::epsilon()) {
distance.rx() = 0.0;
}
// guard against resizing zero height shapes, which would result in huge zoom factors
if (m_initialSelectionSize.height() < std::numeric_limits<qreal>::epsilon()) {
distance.ry() = 0.0;
}
const bool scaleFromCenter = modifiers & Qt::ControlModifier;
if (scaleFromCenter) {
distance *= 2.0;
}
qreal newWidth = startWidth;
qreal newHeight = startHeight;
if (m_left) {
newWidth = startWidth - distance.x();
} else if (m_right) {
newWidth = startWidth + distance.x();
}
if (m_top) {
newHeight = startHeight - distance.y();
} else if (m_bottom) {
newHeight = startHeight + distance.y();
}
/**
* Do not let a shape be less than 1px in size in current view
* coordinates. If the user wants it to be smaller, he can just
* zoom-in a bit.
*/
QSizeF minViewSize(1.0, 1.0);
QSizeF minDocSize = tool()->canvas()->viewConverter()->viewToDocument(minViewSize);
if (qAbs(newWidth) < minDocSize.width()) {
int sign = newWidth >= 0.0 ? 1 : -1; // zero -> '1'
newWidth = sign * minDocSize.width();
}
if (qAbs(newHeight) < minDocSize.height()) {
int sign = newHeight >= 0.0 ? 1 : -1; // zero -> '1'
newHeight = sign * minDocSize.height();
}
qreal zoomX = newWidth / startWidth;
qreal zoomY = newHeight / startHeight;
if (keepAspect) {
const bool cornerUsed = ((m_bottom ? 1 : 0) + (m_top ? 1 : 0) + (m_left ? 1 : 0) + (m_right ? 1 : 0)) == 2;
if (cornerUsed) {
if (startWidth < startHeight) {
zoomY = zoomX;
} else {
zoomX = zoomY;
}
} else {
if (m_left || m_right) {
zoomY = qAbs(zoomX);
} else {
zoomX = qAbs(zoomY);
}
}
}
resizeBy(scaleFromCenter ? m_globalCenterPoint : m_globalStillPoint, zoomX, zoomY);
}
void ShapeResizeStrategy::resizeBy(const QPointF &stillPoint, qreal zoomX, qreal zoomY)
{
if (!m_executedCommand) {
const bool usePostScaling = m_selectedShapes.size() > 1 || m_forceUniformScalingMode;
m_executedCommand.reset(
new KoShapeResizeCommand(
m_selectedShapes,
zoomX, zoomY,
stillPoint,
false, usePostScaling, m_postScalingCoveringTransform));
m_executedCommand->redo();
} else {
m_executedCommand->replaceResizeAction(zoomX, zoomY, stillPoint);
}
}
KUndo2Command *ShapeResizeStrategy::createCommand()
{
tool()->canvas()->snapGuide()->reset();
if (m_executedCommand) {
m_executedCommand->setSkipOneRedo(true);
}
return m_executedCommand.take();
}
void ShapeResizeStrategy::finishInteraction(Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect());
}
void ShapeResizeStrategy::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
}
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.cpp
index cae3a33f2b..d2b982b13b 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.cpp
+++ b/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.cpp
@@ -1,176 +1,176 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2006 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2008 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 "ShapeShearStrategy.h"
#include "SelectionDecorator.h"
#include <KoToolBase.h>
#include <KoCanvasBase.h>
#include <KoPointerEvent.h>
#include <KoShapeManager.h>
#include <commands/KoShapeShearCommand.h>
#include <commands/KoShapeMoveCommand.h>
#include <commands/KoShapeTransformCommand.h>
#include <KoSelection.h>
#include <QPointF>
#include <math.h>
#include <QDebug>
#include <klocalizedstring.h>
ShapeShearStrategy::ShapeShearStrategy(KoToolBase *tool, KoSelection *selection, const QPointF &clicked, KoFlake::SelectionHandle direction)
: KoInteractionStrategy(tool)
, m_start(clicked)
{
/**
* The outline of the selection should look as if it is also shear'ed, so we
* add it to the transformed shapes list.
*/
m_transformedShapesAndSelection = selection->selectedEditableShapes();
m_transformedShapesAndSelection << selection;
Q_FOREACH (KoShape *shape, m_transformedShapesAndSelection) {
m_oldTransforms << shape->transformation();
}
// Even though we aren't currently activated by the corner handles we might as well code like it
switch (direction) {
case KoFlake::TopMiddleHandle:
m_top = true; m_bottom = false; m_left = false; m_right = false; break;
case KoFlake::TopRightHandle:
m_top = true; m_bottom = false; m_left = false; m_right = true; break;
case KoFlake::RightMiddleHandle:
m_top = false; m_bottom = false; m_left = false; m_right = true; break;
case KoFlake::BottomRightHandle:
m_top = false; m_bottom = true; m_left = false; m_right = true; break;
case KoFlake::BottomMiddleHandle:
m_top = false; m_bottom = true; m_left = false; m_right = false; break;
case KoFlake::BottomLeftHandle:
m_top = false; m_bottom = true; m_left = true; m_right = false; break;
case KoFlake::LeftMiddleHandle:
m_top = false; m_bottom = false; m_left = true; m_right = false; break;
case KoFlake::TopLeftHandle:
m_top = true; m_bottom = false; m_left = true; m_right = false; break;
default:
;// throw exception ? TODO
}
m_initialSize = selection->size();
m_solidPoint = QPointF(m_initialSize.width() / 2, m_initialSize.height() / 2);
if (m_top) {
m_solidPoint += QPointF(0, m_initialSize.height() / 2);
} else if (m_bottom) {
m_solidPoint -= QPointF(0, m_initialSize.height() / 2);
}
if (m_left) {
m_solidPoint += QPointF(m_initialSize.width() / 2, 0);
} else if (m_right) {
m_solidPoint -= QPointF(m_initialSize.width() / 2, 0);
}
- m_solidPoint = selection->absoluteTransformation(0).map(selection->outlineRect().topLeft() + m_solidPoint);
+ m_solidPoint = selection->absoluteTransformation().map(selection->outlineRect().topLeft() + m_solidPoint);
QPointF edge;
qreal angle = 0.0;
if (m_top) {
edge = selection->absolutePosition(KoFlake::BottomLeft) - selection->absolutePosition(KoFlake::BottomRight);
angle = 180.0;
} else if (m_bottom) {
edge = selection->absolutePosition(KoFlake::TopRight) - selection->absolutePosition(KoFlake::TopLeft);
angle = 0.0;
} else if (m_left) {
edge = selection->absolutePosition(KoFlake::BottomLeft) - selection->absolutePosition(KoFlake::TopLeft);
angle = 90.0;
} else if (m_right) {
edge = selection->absolutePosition(KoFlake::TopRight) - selection->absolutePosition(KoFlake::BottomRight);
angle = 270.0;
}
qreal currentAngle = atan2(edge.y(), edge.x()) / M_PI * 180;
m_initialSelectionAngle = currentAngle - angle;
// use cross product of top edge and left edge of selection bounding rect
// to determine if the selection is mirrored
QPointF top = selection->absolutePosition(KoFlake::TopRight) - selection->absolutePosition(KoFlake::TopLeft);
QPointF left = selection->absolutePosition(KoFlake::BottomLeft) - selection->absolutePosition(KoFlake::TopLeft);
m_isMirrored = (top.x() * left.y() - top.y() * left.x()) < 0.0;
}
void ShapeShearStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
QPointF shearVector = point - m_start;
QTransform m;
m.rotate(-m_initialSelectionAngle);
shearVector = m.map(shearVector);
qreal shearX = 0, shearY = 0;
if (m_top || m_left) {
shearVector = - shearVector;
}
if (m_top || m_bottom) {
shearX = shearVector.x() / m_initialSize.height();
}
if (m_left || m_right) {
shearY = shearVector.y() / m_initialSize.width();
}
// if selection is mirrored invert the shear values
if (m_isMirrored) {
shearX *= -1.0;
shearY *= -1.0;
}
QTransform matrix;
matrix.translate(m_solidPoint.x(), m_solidPoint.y());
matrix.rotate(m_initialSelectionAngle);
matrix.shear(shearX, shearY);
matrix.rotate(-m_initialSelectionAngle);
matrix.translate(-m_solidPoint.x(), -m_solidPoint.y());
QTransform applyMatrix = matrix * m_shearMatrix.inverted();
Q_FOREACH (KoShape *shape, m_transformedShapesAndSelection) {
const QRectF oldDirtyRect = shape->boundingRect();
shape->applyAbsoluteTransformation(applyMatrix);
shape->updateAbsolute(oldDirtyRect | shape->boundingRect());
}
m_shearMatrix = matrix;
}
void ShapeShearStrategy::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
}
KUndo2Command *ShapeShearStrategy::createCommand()
{
QList<QTransform> newTransforms;
Q_FOREACH (KoShape *shape, m_transformedShapesAndSelection) {
newTransforms << shape->transformation();
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(m_transformedShapesAndSelection, m_oldTransforms, newTransforms);
cmd->setText(kundo2_i18n("Shear"));
return cmd;
}
diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp
index 0c7d85d5dd..27c9fb59e6 100644
--- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp
+++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp
@@ -1,433 +1,445 @@
/* This file is part of the KDE project
Copyright (C) 2008 Fela Winkelmolen <fela.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.
*/
#include "KarbonCalligraphicShape.h"
#include <KoPathPoint.h>
#include <KoParameterShape_p.h>
#include "KarbonSimplifyPath.h"
#include <KoCurveFit.h>
#include <KoColorBackground.h>
#include <QDebug>
#include <QColor>
#include <cmath>
#include <cstdlib>
#undef M_PI
const qreal M_PI = 3.1415927;
+struct KarbonCalligraphicShape::Private : public QSharedData
+{
+ Private(qreal _caps)
+ : lastWasFlip(false),
+ caps(_caps)
+
+ {
+ }
+
+ Private(const Private &rhs) = default;
+
+ bool lastWasFlip;
+ qreal caps = 0.0;
+ // the actual data then determines it's shape (guide path + data for points)
+ QList<KarbonCalligraphicPoint> points;
+};
+
KarbonCalligraphicShape::KarbonCalligraphicShape(qreal caps)
- : m_lastWasFlip(false)
- , m_caps(caps)
+ : s(new Private(caps))
{
setShapeId(KoPathShapeId);
setFillRule(Qt::WindingFill);
setBackground(QSharedPointer<KoShapeBackground>(new KoColorBackground(QColor(Qt::black))));
setStroke(KoShapeStrokeModelSP());
}
KarbonCalligraphicShape::KarbonCalligraphicShape(const KarbonCalligraphicShape &rhs)
: KoParameterShape(rhs),
- m_points(rhs.m_points),
- m_lastWasFlip(rhs.m_lastWasFlip),
- m_caps(rhs.m_caps)
+ s(rhs.s)
{
}
KarbonCalligraphicShape::~KarbonCalligraphicShape()
{
}
KoShape *KarbonCalligraphicShape::cloneShape() const
{
return new KarbonCalligraphicShape(*this);
}
void KarbonCalligraphicShape::appendPoint(const QPointF &point, qreal angle, qreal width)
{
// convert the point from canvas to shape coordinates
QPointF p = point - position();
- KarbonCalligraphicPoint *calligraphicPoint =
- new KarbonCalligraphicPoint(p, angle, width);
+ KarbonCalligraphicPoint calligraphicPoint(p, angle, width);
QList<QPointF> handles = this->handles();
handles.append(p);
setHandles(handles);
- m_points.append(calligraphicPoint);
- appendPointToPath(*calligraphicPoint);
+ s->points.append(calligraphicPoint);
+ appendPointToPath(calligraphicPoint);
// make the angle of the first point more in line with the actual
// direction
- if (m_points.count() == 4) {
- m_points[0]->setAngle(angle);
- m_points[1]->setAngle(angle);
- m_points[2]->setAngle(angle);
+ if (s->points.count() == 4) {
+ s->points[0].setAngle(angle);
+ s->points[1].setAngle(angle);
+ s->points[2].setAngle(angle);
}
+
+ normalize();
}
void KarbonCalligraphicShape::appendPointToPath(const KarbonCalligraphicPoint &p)
{
qreal dx = std::cos(p.angle()) * p.width();
qreal dy = std::sin(p.angle()) * p.width();
// find the outline points
QPointF p1 = p.point() - QPointF(dx / 2, dy / 2);
QPointF p2 = p.point() + QPointF(dx / 2, dy / 2);
if (pointCount() == 0) {
moveTo(p1);
lineTo(p2);
- normalize();
return;
}
// pointCount > 0
bool flip = (pointCount() >= 2) ? flipDetected(p1, p2) : false;
// if there was a flip add additional points
if (flip) {
appendPointsToPathAux(p2, p1);
if (pointCount() > 4) {
smoothLastPoints();
}
}
appendPointsToPathAux(p1, p2);
if (pointCount() > 4) {
smoothLastPoints();
if (flip) {
int index = pointCount() / 2;
// find the last two points
KoPathPoint *last1 = pointByIndex(KoPathPointIndex(0, index - 1));
KoPathPoint *last2 = pointByIndex(KoPathPointIndex(0, index));
last1->removeControlPoint1();
last1->removeControlPoint2();
last2->removeControlPoint1();
last2->removeControlPoint2();
- m_lastWasFlip = true;
+ s->lastWasFlip = true;
}
- if (m_lastWasFlip) {
+ if (s->lastWasFlip) {
int index = pointCount() / 2;
// find the previous two points
KoPathPoint *prev1 = pointByIndex(KoPathPointIndex(0, index - 2));
KoPathPoint *prev2 = pointByIndex(KoPathPointIndex(0, index + 1));
prev1->removeControlPoint1();
prev1->removeControlPoint2();
prev2->removeControlPoint1();
prev2->removeControlPoint2();
if (!flip) {
- m_lastWasFlip = false;
+ s->lastWasFlip = false;
}
}
}
- normalize();
// add initial cap if it's the fourth added point
// this code is here because this function is called from different places
// pointCount() == 8 may causes crashes because it doesn't take possible
// flips into account
- if (m_points.count() >= 4 && &p == m_points[3]) {
- addCap(3, 0, 0, true);
+
+ if (s->points.count() >= 4 && p == s->points[3]) {
+ addCap(3, 0, 0, true);
// duplicate the last point to make the points remain "balanced"
// needed to keep all indexes code (else I would need to change
// everything in the code...)
KoPathPoint *last = pointByIndex(KoPathPointIndex(0, pointCount() - 1));
KoPathPoint *newPoint = new KoPathPoint(this, last->point());
insertPoint(newPoint, KoPathPointIndex(0, pointCount()));
close();
}
}
void KarbonCalligraphicShape::appendPointsToPathAux(const QPointF &p1, const QPointF &p2)
{
KoPathPoint *pathPoint1 = new KoPathPoint(this, p1);
KoPathPoint *pathPoint2 = new KoPathPoint(this, p2);
// calculate the index of the insertion position
int index = pointCount() / 2;
insertPoint(pathPoint2, KoPathPointIndex(0, index));
insertPoint(pathPoint1, KoPathPointIndex(0, index));
}
void KarbonCalligraphicShape::smoothLastPoints()
{
int index = pointCount() / 2;
smoothPoint(index - 2);
smoothPoint(index + 1);
}
void KarbonCalligraphicShape::smoothPoint(const int index)
{
if (pointCount() < index + 2) {
return;
} else if (index < 1) {
return;
}
const KoPathPointIndex PREV(0, index - 1);
const KoPathPointIndex INDEX(0, index);
const KoPathPointIndex NEXT(0, index + 1);
QPointF prev = pointByIndex(PREV)->point();
QPointF point = pointByIndex(INDEX)->point();
QPointF next = pointByIndex(NEXT)->point();
QPointF vector = next - prev;
qreal dist = (QLineF(prev, next)).length();
// normalize the vector (make it's size equal to 1)
if (!qFuzzyCompare(dist + 1, 1)) {
vector /= dist;
}
qreal mult = 0.35; // found by trial and error, might not be perfect...
// distance of the control points from the point
qreal dist1 = (QLineF(point, prev)).length() * mult;
qreal dist2 = (QLineF(point, next)).length() * mult;
QPointF vector1 = vector * dist1;
QPointF vector2 = vector * dist2;
QPointF controlPoint1 = point - vector1;
QPointF controlPoint2 = point + vector2;
pointByIndex(INDEX)->setControlPoint1(controlPoint1);
pointByIndex(INDEX)->setControlPoint2(controlPoint2);
}
const QRectF KarbonCalligraphicShape::lastPieceBoundingRect()
{
if (pointCount() < 6) {
return QRectF();
}
int index = pointCount() / 2;
QPointF p1 = pointByIndex(KoPathPointIndex(0, index - 3))->point();
QPointF p2 = pointByIndex(KoPathPointIndex(0, index - 2))->point();
QPointF p3 = pointByIndex(KoPathPointIndex(0, index - 1))->point();
QPointF p4 = pointByIndex(KoPathPointIndex(0, index))->point();
QPointF p5 = pointByIndex(KoPathPointIndex(0, index + 1))->point();
QPointF p6 = pointByIndex(KoPathPointIndex(0, index + 2))->point();
// TODO: also take the control points into account
QPainterPath p;
p.moveTo(p1);
p.lineTo(p2);
p.lineTo(p3);
p.lineTo(p4);
p.lineTo(p5);
p.lineTo(p6);
return p.boundingRect().translated(position());
}
bool KarbonCalligraphicShape::flipDetected(const QPointF &p1, const QPointF &p2)
{
// detect the flip caused by the angle changing 180 degrees
// thus detect the boundary crossing
int index = pointCount() / 2;
QPointF last1 = pointByIndex(KoPathPointIndex(0, index - 1))->point();
QPointF last2 = pointByIndex(KoPathPointIndex(0, index))->point();
int sum1 = std::abs(ccw(p1, p2, last1) + ccw(p1, last2, last1));
int sum2 = std::abs(ccw(p2, p1, last2) + ccw(p2, last1, last2));
// if there was a flip
return sum1 < 2 && sum2 < 2;
}
int KarbonCalligraphicShape::ccw(const QPointF &p1, const QPointF &p2,const QPointF &p3)
{
// calculate two times the area of the triangle formed by the points given
qreal area2 = (p2.x() - p1.x()) * (p3.y() - p1.y()) -
(p2.y() - p1.y()) * (p3.x() - p1.x());
if (area2 > 0) {
return +1; // the points are given in counterclockwise order
} else if (area2 < 0) {
return -1; // the points are given in clockwise order
} else {
return 0; // the points form a degenerate triangle
}
}
void KarbonCalligraphicShape::setSize(const QSizeF &newSize)
{
// QSizeF oldSize = size();
// TODO: check
KoParameterShape::setSize(newSize);
}
QPointF KarbonCalligraphicShape::normalize()
{
QPointF offset(KoParameterShape::normalize());
QTransform matrix;
matrix.translate(-offset.x(), -offset.y());
- for (int i = 0; i < m_points.size(); ++i) {
- m_points[i]->setPoint(matrix.map(m_points[i]->point()));
+ for (int i = 0; i < s->points.size(); ++i) {
+ s->points[i].setPoint(matrix.map(s->points[i].point()));
}
return offset;
}
void KarbonCalligraphicShape::moveHandleAction(int handleId,
const QPointF &point,
Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
- m_points[handleId]->setPoint(point);
+ s->points[handleId].setPoint(point);
}
void KarbonCalligraphicShape::updatePath(const QSizeF &size)
{
Q_UNUSED(size);
QPointF pos = position();
// remove all points
clear();
setPosition(QPoint(0, 0));
- Q_FOREACH (KarbonCalligraphicPoint *p, m_points) {
- appendPointToPath(*p);
+ Q_FOREACH (const KarbonCalligraphicPoint &p, s->points) {
+ appendPointToPath(p);
}
- simplifyPath();
-
QList<QPointF> handles;
- Q_FOREACH (KarbonCalligraphicPoint *p, m_points) {
- handles.append(p->point());
+ Q_FOREACH (const KarbonCalligraphicPoint &p, s->points) {
+ handles.append(p.point());
}
setHandles(handles);
setPosition(pos);
+ normalize();
}
void KarbonCalligraphicShape::simplifyPath()
{
- if (m_points.count() < 2) {
+ if (s->points.count() < 2) {
return;
}
close();
// add final cap
- addCap(m_points.count() - 2, m_points.count() - 1, pointCount() / 2);
+ addCap(s->points.count() - 2, s->points.count() - 1, pointCount() / 2);
// TODO: the error should be proportional to the width
// and it shouldn't be a magic number
karbonSimplifyPath(this, 0.3);
}
void KarbonCalligraphicShape::addCap(int index1, int index2, int pointIndex, bool inverted)
{
- QPointF p1 = m_points[index1]->point();
- QPointF p2 = m_points[index2]->point();
+ QPointF p1 = s->points[index1].point();
+ QPointF p2 = s->points[index2].point();
// TODO: review why spikes can appear with a lower limit
QPointF delta = p2 - p1;
if (delta.manhattanLength() < 1.0) {
return;
}
QPointF direction = QLineF(QPointF(0, 0), delta).unitVector().p2();
- qreal width = m_points[index2]->width();
- QPointF p = p2 + direction * m_caps * width;
+ qreal width = s->points[index2].width();
+ QPointF p = p2 + direction * s->caps * width;
KoPathPoint *newPoint = new KoPathPoint(this, p);
- qreal angle = m_points[index2]->angle();
+ qreal angle = s->points[index2].angle();
if (inverted) {
angle += M_PI;
}
qreal dx = std::cos(angle) * width;
qreal dy = std::sin(angle) * width;
newPoint->setControlPoint1(QPointF(p.x() - dx / 2, p.y() - dy / 2));
newPoint->setControlPoint2(QPointF(p.x() + dx / 2, p.y() + dy / 2));
insertPoint(newPoint, KoPathPointIndex(0, pointIndex));
}
QString KarbonCalligraphicShape::pathShapeId() const
{
return KarbonCalligraphicShapeId;
}
void KarbonCalligraphicShape::simplifyGuidePath()
{
// do not attempt to simplify if there are too few points
- if (m_points.count() < 3) {
+ if (s->points.count() < 3) {
return;
}
QList<QPointF> points;
- Q_FOREACH (KarbonCalligraphicPoint *p, m_points) {
- points.append(p->point());
+ Q_FOREACH (const KarbonCalligraphicPoint &p, s->points) {
+ points.append(p.point());
}
// cumulative data used to determine if the point can be removed
qreal widthChange = 0;
qreal directionChange = 0;
- QList<KarbonCalligraphicPoint *>::iterator i = m_points.begin() + 2;
+ QList<KarbonCalligraphicPoint>::iterator i = s->points.begin() + 2;
- while (i != m_points.end() - 1) {
- QPointF point = (*i)->point();
+ while (i != std::prev(s->points.end())) {
+ QPointF point = i->point();
- qreal width = (*i)->width();
- qreal prevWidth = (*(i - 1))->width();
+ qreal width = i->width();
+ qreal prevWidth = std::prev(i)->width();
qreal widthDiff = width - prevWidth;
widthDiff /= qMax(width, prevWidth);
qreal directionDiff = 0;
- if ((i + 1) != m_points.end()) {
- QPointF prev = (*(i - 1))->point();
- QPointF next = (*(i + 1))->point();
+ if (std::next(i) != s->points.end()) {
+ QPointF prev = std::prev(i)->point();
+ QPointF next = std::next(i)->point();
directionDiff = QLineF(prev, point).angleTo(QLineF(point, next));
if (directionDiff > 180) {
directionDiff -= 360;
}
}
if (directionChange * directionDiff >= 0 &&
qAbs(directionChange + directionDiff) < 20 &&
widthChange * widthDiff >= 0 &&
qAbs(widthChange + widthDiff) < 0.1) {
// deleted point
- delete *i;
- i = m_points.erase(i);
+ i = s->points.erase(i);
directionChange += directionDiff;
widthChange += widthDiff;
} else {
// keep point
directionChange = 0;
widthChange = 0;
++i;
}
}
updatePath(QSizeF());
}
diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.h b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.h
index d27bdfef75..31abea52d4 100644
--- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.h
+++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.h
@@ -1,151 +1,165 @@
/* This file is part of the KDE project
Copyright (C) 2008 Fela Winkelmolen <fela.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 KARBONCALLIGRAPHICSHAPE_H
#define KARBONCALLIGRAPHICSHAPE_H
#include <KoParameterShape.h>
+#include <QDebug>
#define KarbonCalligraphicShapeId "KarbonCalligraphicShape"
class KarbonCalligraphicPoint
{
public:
KarbonCalligraphicPoint(const QPointF &point, qreal angle, qreal width)
: m_point(point), m_angle(angle), m_width(width) {}
+ KarbonCalligraphicPoint(const KarbonCalligraphicPoint &rhs) = default;
+ KarbonCalligraphicPoint() = default;
+
+ bool operator==(const KarbonCalligraphicPoint &rhs) const {
+ return m_point == rhs.m_point &&
+ qFuzzyCompare(m_angle, rhs.m_angle) &&
+ qFuzzyCompare(m_width, rhs.m_width);
+ }
+
QPointF point() const
{
return m_point;
}
qreal angle() const
{
return m_angle;
}
qreal width() const
{
return m_width;
}
void setPoint(const QPointF &point)
{
m_point = point;
}
void setAngle(qreal angle)
{
m_angle = angle;
}
private:
QPointF m_point; // in shape coordinates
- qreal m_angle;
- qreal m_width;
+ qreal m_angle = 0.0;
+ qreal m_width = 0.0;
};
+inline QDebug operator<<(QDebug dbg, const KarbonCalligraphicPoint &pt)
+{
+ dbg.nospace() << "(" << pt.point() << ", a: " << pt.angle() << ", w: " << pt.width() << ")";
+ return dbg.space();
+}
+
/*class KarbonCalligraphicShape::Point
{
public:
KoPainterPath(KoPathPoint *point) : m_prev(point), m_next(0) {}
// calculates the effective point
QPointF point() {
if (m_next = 0)
return m_prev.point();
// m_next != 0
qDebug() << "not implemented yet!!!!";
return QPointF();
}
private:
KoPainterPath m_prev;
KoPainterPath m_next;
qreal m_percentage;
};*/
// the indexes of the path will be similar to:
// 7--6--5--4 <- pointCount() / 2
// start | | end ==> (direction of the stroke)
// 0--1--2--3
class KarbonCalligraphicShape : public KoParameterShape
{
public:
explicit KarbonCalligraphicShape(qreal caps = 0.0);
~KarbonCalligraphicShape() override;
KoShape* cloneShape() const override;
void appendPoint(const QPointF &p1, qreal angle, qreal width);
void appendPointToPath(const KarbonCalligraphicPoint &p);
// returns the bounding rect of what needs to be repainted
// after new points are added
const QRectF lastPieceBoundingRect();
void setSize(const QSizeF &newSize) override;
//virtual QPointF normalize();
QPointF normalize() override;
void simplifyPath();
void simplifyGuidePath();
// reimplemented
QString pathShapeId() const override;
protected:
// reimplemented
void moveHandleAction(int handleId,
const QPointF &point,
Qt::KeyboardModifiers modifiers = Qt::NoModifier) override;
// reimplemented
void updatePath(const QSizeF &size) override;
private:
KarbonCalligraphicShape(const KarbonCalligraphicShape &rhs);
// auxiliary function that actually inserts the points
// without doing any additional checks
// the points should be given in canvas coordinates
void appendPointsToPathAux(const QPointF &p1, const QPointF &p2);
// function to detect a flip, given the points being inserted
bool flipDetected(const QPointF &p1, const QPointF &p2);
void smoothLastPoints();
void smoothPoint(const int index);
// determine whether the points given are in counterclockwise order or not
// returns +1 if they are, -1 if they are given in clockwise order
// and 0 if they form a degenerate triangle
static int ccw(const QPointF &p1, const QPointF &p2, const QPointF &p3);
//
void addCap(int index1, int index2, int pointIndex, bool inverted = false);
- // the actual data then determines it's shape (guide path + data for points)
- QList<KarbonCalligraphicPoint *> m_points;
- bool m_lastWasFlip;
- qreal m_caps;
+ struct Private;
+ QSharedDataPointer<Private> s;
};
#endif // KARBONCALLIGRAPHICSHAPE_H
diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
index ecc50843ed..3850079996 100644
--- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
+++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
@@ -1,521 +1,522 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Fela Winkelmolen <fela.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.
*/
#include "KarbonCalligraphyTool.h"
#include "KarbonCalligraphicShape.h"
#include "KarbonCalligraphyOptionWidget.h"
#include <KoPathShape.h>
#include <KoShapeGroup.h>
#include <KoPointerEvent.h>
#include <KoPathPoint.h>
#include <KoCanvasBase.h>
#include <KoShapeController.h>
#include <KoShapeManager.h>
#include <KoSelectedShapesProxy.h>
#include <KoSelection.h>
#include <KoCurveFit.h>
#include <KoColorBackground.h>
#include <KoCanvasResourceProvider.h>
#include <KoColor.h>
#include <KoShapePaintingContext.h>
#include <KoViewConverter.h>
#include <QAction>
#include <QDebug>
#include <klocalizedstring.h>
#include <QPainter>
#include <cmath>
#undef M_PI
const qreal M_PI = 3.1415927;
using std::pow;
using std::sqrt;
KarbonCalligraphyTool::KarbonCalligraphyTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_shape(0)
, m_angle(0)
, m_selectedPath(0)
, m_isDrawing(false)
, m_speed(0, 0)
, m_lastShape(0)
{
connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), SLOT(updateSelectedPath()));
updateSelectedPath();
}
KarbonCalligraphyTool::~KarbonCalligraphyTool()
{
}
void KarbonCalligraphyTool::paint(QPainter &painter, const KoViewConverter &converter)
{
if (m_selectedPath) {
painter.save();
painter.setRenderHints(QPainter::Antialiasing, false);
painter.setPen(Qt::red); // TODO make configurable
QRectF rect = m_selectedPath->boundingRect();
QPointF p1 = converter.documentToView(rect.topLeft());
QPointF p2 = converter.documentToView(rect.bottomRight());
painter.drawRect(QRectF(p1, p2));
painter.restore();
}
if (!m_shape) {
return;
}
painter.save();
- painter.setTransform(m_shape->absoluteTransformation(&converter) *
+ painter.setTransform(m_shape->absoluteTransformation() *
+ converter.documentToView() *
painter.transform());
KoShapePaintingContext paintContext; //FIXME
- m_shape->paint(painter, converter, paintContext);
+ m_shape->paint(painter, paintContext);
painter.restore();
}
void KarbonCalligraphyTool::mousePressEvent(KoPointerEvent *event)
{
if (m_isDrawing) {
return;
}
m_lastPoint = event->point;
m_speed = QPointF(0, 0);
m_isDrawing = true;
m_pointCount = 0;
m_shape = new KarbonCalligraphicShape(m_caps);
m_shape->setBackground(QSharedPointer<KoShapeBackground>(new KoColorBackground(canvas()->resourceManager()->foregroundColor().toQColor())));
//addPoint( event );
}
void KarbonCalligraphyTool::mouseMoveEvent(KoPointerEvent *event)
{
if (!m_isDrawing) {
return;
}
addPoint(event);
}
void KarbonCalligraphyTool::mouseReleaseEvent(KoPointerEvent *event)
{
if (!m_isDrawing) {
return;
}
if (m_pointCount == 0) {
// handle click: select shape (if any)
if (event->point == m_lastPoint) {
KoShapeManager *shapeManager = canvas()->shapeManager();
KoShape *selectedShape = shapeManager->shapeAt(event->point);
if (selectedShape != 0) {
shapeManager->selection()->deselectAll();
shapeManager->selection()->select(selectedShape);
}
}
delete m_shape;
m_shape = 0;
m_isDrawing = false;
return;
} else {
m_endOfPath = false; // allow last point being added
addPoint(event); // add last point
m_isDrawing = false;
}
m_shape->simplifyGuidePath();
KUndo2Command *cmd = canvas()->shapeController()->addShape(m_shape, 0);
if (cmd) {
m_lastShape = m_shape;
canvas()->addCommand(cmd);
canvas()->updateCanvas(m_shape->boundingRect());
} else {
// don't leak shape when command could not be created
delete m_shape;
}
m_shape = 0;
}
void KarbonCalligraphyTool::addPoint(KoPointerEvent *event)
{
if (m_pointCount == 0) {
if (m_usePath && m_selectedPath) {
m_selectedPathOutline = m_selectedPath->outline();
}
m_pointCount = 1;
m_endOfPath = false;
m_followPathPosition = 0;
m_lastMousePos = event->point;
m_lastPoint = calculateNewPoint(event->point, &m_speed);
m_deviceSupportsTilt = (event->xTilt() != 0 || event->yTilt() != 0);
return;
}
if (m_endOfPath) {
return;
}
++m_pointCount;
setAngle(event);
QPointF newSpeed;
QPointF newPoint = calculateNewPoint(event->point, &newSpeed);
qreal width = calculateWidth(event->pressure());
qreal angle = calculateAngle(m_speed, newSpeed);
// add the previous point
m_shape->appendPoint(m_lastPoint, angle, width);
m_speed = newSpeed;
m_lastPoint = newPoint;
canvas()->updateCanvas(m_shape->lastPieceBoundingRect());
if (m_usePath && m_selectedPath) {
m_speed = QPointF(0, 0); // following path
}
}
void KarbonCalligraphyTool::setAngle(KoPointerEvent *event)
{
if (!m_useAngle) {
m_angle = (360.0 - m_customAngle + 90.0) / 180.0 * M_PI;
return;
}
// setting m_angle to the angle of the device
if (event->xTilt() != 0 || event->yTilt() != 0) {
m_deviceSupportsTilt = false;
}
if (m_deviceSupportsTilt) {
if (event->xTilt() == 0 && event->yTilt() == 0) {
return; // leave as is
}
if (event->x() == 0) {
m_angle = M_PI / 2.0;
return;
}
// y is inverted in qt painting
m_angle = std::atan(static_cast<double>(-event->yTilt()) / static_cast<double>(event->xTilt())) + M_PI / 2.0;
} else {
m_angle = event->rotation() + M_PI / 2.0;
}
}
QPointF KarbonCalligraphyTool::calculateNewPoint(const QPointF &mousePos, QPointF *speed)
{
if (!m_usePath || !m_selectedPath) { // don't follow path
QPointF force = mousePos - m_lastPoint;
QPointF dSpeed = force / m_mass;
*speed = m_speed * (1.0 - m_drag) + dSpeed;
return m_lastPoint + *speed;
}
QPointF sp = mousePos - m_lastMousePos;
m_lastMousePos = mousePos;
// follow selected path
qreal step = QLineF(QPointF(0, 0), sp).length();
m_followPathPosition += step;
qreal t;
if (m_followPathPosition >= m_selectedPathOutline.length()) {
t = 1.0;
m_endOfPath = true;
} else {
t = m_selectedPathOutline.percentAtLength(m_followPathPosition);
}
QPointF res = m_selectedPathOutline.pointAtPercent(t)
+ m_selectedPath->position();
*speed = res - m_lastPoint;
return res;
}
qreal KarbonCalligraphyTool::calculateWidth(qreal pressure)
{
// calculate the modulo of the speed
qreal speed = std::sqrt(pow(m_speed.x(), 2) + pow(m_speed.y(), 2));
qreal thinning = m_thinning * (speed + 1) / 10.0; // can be negative
if (thinning > 1) {
thinning = 1;
}
if (!m_usePressure) {
pressure = 1.0;
}
qreal strokeWidth = m_strokeWidth * pressure * (1 - thinning);
const qreal MINIMUM_STROKE_WIDTH = 1.0;
if (strokeWidth < MINIMUM_STROKE_WIDTH) {
strokeWidth = MINIMUM_STROKE_WIDTH;
}
return strokeWidth;
}
qreal KarbonCalligraphyTool::calculateAngle(const QPointF &oldSpeed, const QPointF &newSpeed)
{
// calculate the average of the speed (sum of the normalized values)
qreal oldLength = QLineF(QPointF(0, 0), oldSpeed).length();
qreal newLength = QLineF(QPointF(0, 0), newSpeed).length();
QPointF oldSpeedNorm = !qFuzzyCompare(oldLength + 1, 1) ?
oldSpeed / oldLength : QPointF(0, 0);
QPointF newSpeedNorm = !qFuzzyCompare(newLength + 1, 1) ?
newSpeed / newLength : QPointF(0, 0);
QPointF speed = oldSpeedNorm + newSpeedNorm;
// angle solely based on the speed
qreal speedAngle = 0;
if (speed.x() != 0) { // avoid division by zero
speedAngle = std::atan(speed.y() / speed.x());
} else if (speed.y() > 0) {
// x == 0 && y != 0
speedAngle = M_PI / 2;
} else if (speed.y() < 0) {
// x == 0 && y != 0
speedAngle = -M_PI / 2;
}
if (speed.x() < 0) {
speedAngle += M_PI;
}
// move 90 degrees
speedAngle += M_PI / 2;
qreal fixedAngle = m_angle;
// check if the fixed angle needs to be flipped
qreal diff = fixedAngle - speedAngle;
while (diff >= M_PI) { // normalize diff between -180 and 180
diff -= 2 * M_PI;
}
while (diff < -M_PI) {
diff += 2 * M_PI;
}
if (std::abs(diff) > M_PI / 2) { // if absolute value < 90
fixedAngle += M_PI; // += 180
}
qreal dAngle = speedAngle - fixedAngle;
// normalize dAngle between -90 and +90
while (dAngle >= M_PI / 2) {
dAngle -= M_PI;
}
while (dAngle < -M_PI / 2) {
dAngle += M_PI;
}
qreal angle = fixedAngle + dAngle * (1.0 - m_fixation);
return angle;
}
void KarbonCalligraphyTool::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
KoToolBase::activate(activation, shapes);
if (!m_widget) {
createOptionWidgets();
}
QAction *a = action("calligraphy_increase_width");
connect(a, SIGNAL(triggered()), m_widget, SLOT(increaseWidth()), Qt::UniqueConnection);
a = action("calligraphy_decrease_width");
connect(a, SIGNAL(triggered()), m_widget, SLOT(decreaseWidth()), Qt::UniqueConnection);
a = action("calligraphy_increase_angle");
connect(a, SIGNAL(triggered()), m_widget, SLOT(increaseAngle()), Qt::UniqueConnection);
a = action("calligraphy_decrease_angle");
connect(a, SIGNAL(triggered()), m_widget, SLOT(decreaseAngle()), Qt::UniqueConnection);
useCursor(Qt::CrossCursor);
m_lastShape = 0;
}
void KarbonCalligraphyTool::deactivate()
{
QAction *a = action("calligraphy_increase_width");
disconnect(a, 0, this, 0);
a = action("calligraphy_decrease_width");
disconnect(a, 0, this, 0);
a = action("calligraphy_increase_angle");
disconnect(a, 0, this, 0);
a = action("calligraphy_decrease_angle");
disconnect(a, 0, this, 0);
if (m_lastShape && canvas()->shapeManager()->shapes().contains(m_lastShape)) {
KoSelection *selection = canvas()->shapeManager()->selection();
selection->deselectAll();
selection->select(m_lastShape);
}
KoToolBase::deactivate();
}
QList<QPointer<QWidget> > KarbonCalligraphyTool::createOptionWidgets()
{
// if the widget don't exists yet create it
QList<QPointer<QWidget> > widgets;
//KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0);
//fillWidget->setWindowTitle(i18n("Fill"));
//widgets.append(fillWidget);
m_widget = new KarbonCalligraphyOptionWidget();
connect(m_widget, SIGNAL(usePathChanged(bool)),
this, SLOT(setUsePath(bool)));
connect(m_widget, SIGNAL(usePressureChanged(bool)),
this, SLOT(setUsePressure(bool)));
connect(m_widget, SIGNAL(useAngleChanged(bool)),
this, SLOT(setUseAngle(bool)));
connect(m_widget, SIGNAL(widthChanged(double)),
this, SLOT(setStrokeWidth(double)));
connect(m_widget, SIGNAL(thinningChanged(double)),
this, SLOT(setThinning(double)));
connect(m_widget, SIGNAL(angleChanged(int)),
this, SLOT(setAngle(int)));
connect(m_widget, SIGNAL(fixationChanged(double)),
this, SLOT(setFixation(double)));
connect(m_widget, SIGNAL(capsChanged(double)),
this, SLOT(setCaps(double)));
connect(m_widget, SIGNAL(massChanged(double)),
this, SLOT(setMass(double)));
connect(m_widget, SIGNAL(dragChanged(double)),
this, SLOT(setDrag(double)));
connect(this, SIGNAL(pathSelectedChanged(bool)),
m_widget, SLOT(setUsePathEnabled(bool)));
// sync all parameters with the loaded profile
m_widget->emitAll();
m_widget->setObjectName(i18n("Calligraphy"));
m_widget->setWindowTitle(i18n("Calligraphy"));
widgets.append(m_widget);
return widgets;
}
void KarbonCalligraphyTool::setStrokeWidth(double width)
{
m_strokeWidth = width;
}
void KarbonCalligraphyTool::setThinning(double thinning)
{
m_thinning = thinning;
}
void KarbonCalligraphyTool::setAngle(int angle)
{
m_customAngle = angle;
}
void KarbonCalligraphyTool::setFixation(double fixation)
{
m_fixation = fixation;
}
void KarbonCalligraphyTool::setMass(double mass)
{
m_mass = mass * mass + 1;
}
void KarbonCalligraphyTool::setDrag(double drag)
{
m_drag = drag;
}
void KarbonCalligraphyTool::setUsePath(bool usePath)
{
m_usePath = usePath;
}
void KarbonCalligraphyTool::setUsePressure(bool usePressure)
{
m_usePressure = usePressure;
}
void KarbonCalligraphyTool::setUseAngle(bool useAngle)
{
m_useAngle = useAngle;
}
void KarbonCalligraphyTool::setCaps(double caps)
{
m_caps = caps;
}
void KarbonCalligraphyTool::updateSelectedPath()
{
KoPathShape *oldSelectedPath = m_selectedPath; // save old value
KoSelection *selection = canvas()->shapeManager()->selection();
if (selection) {
// null pointer if it the selection isn't a KoPathShape
// or if the selection is empty
m_selectedPath =
dynamic_cast<KoPathShape *>(selection->firstSelectedShape());
// or if it's a KoPathShape but with no or more than one subpaths
if (m_selectedPath && m_selectedPath->subpathCount() != 1) {
m_selectedPath = 0;
}
// or if there ora none or more than 1 shapes selected
if (selection->count() != 1) {
m_selectedPath = 0;
}
// emit signal it there wasn't a selected path and now there is
// or the other way around
if ((m_selectedPath != 0) != (oldSelectedPath != 0)) {
emit pathSelectedChanged(m_selectedPath != 0);
}
}
}
diff --git a/plugins/tools/karbonplugins/tools/KarbonPatternEditStrategy.cpp b/plugins/tools/karbonplugins/tools/KarbonPatternEditStrategy.cpp
index edfa32909e..f9d8233f68 100644
--- a/plugins/tools/karbonplugins/tools/KarbonPatternEditStrategy.cpp
+++ b/plugins/tools/karbonplugins/tools/KarbonPatternEditStrategy.cpp
@@ -1,392 +1,389 @@
/* This file is part of the KDE project
* Copyright (C) 2007,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 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 "KarbonPatternEditStrategy.h"
#include <KoShape.h>
#include <KoViewConverter.h>
#include <KoShapeBackgroundCommand.h>
#include <QPainter>
#include <kundo2command.h>
#include <math.h>
uint KarbonPatternEditStrategyBase::m_handleRadius = 3;
uint KarbonPatternEditStrategyBase::m_grabSensitivity = 3;
KarbonPatternEditStrategyBase::KarbonPatternEditStrategyBase(KoShape *s, KoImageCollection *imageCollection)
: m_selectedHandle(-1)
, m_oldFill(new KoPatternBackground(imageCollection))
, m_newFill(new KoPatternBackground(imageCollection))
, m_shape(s)
, m_imageCollection(imageCollection)
, m_editing(false)
, m_modified(false)
{
Q_ASSERT(m_shape);
Q_ASSERT(imageCollection);
// cache the shapes transformation matrix
- m_matrix = shape()->absoluteTransformation(0);
+ m_matrix = shape()->absoluteTransformation();
}
KarbonPatternEditStrategyBase::~KarbonPatternEditStrategyBase()
{
}
void KarbonPatternEditStrategyBase::setEditing(bool on)
{
m_editing = on;
// if we are going into editing mode, save the old background
// for use inside the command emitted when finished
if (on) {
m_modified = false;
QSharedPointer<KoPatternBackground> fill = qSharedPointerDynamicCast<KoPatternBackground>(m_shape->background());
if (fill) {
m_oldFill = fill;
}
}
}
void KarbonPatternEditStrategyBase::setModified()
{
m_modified = true;
}
bool KarbonPatternEditStrategyBase::isModified() const
{
return m_modified;
}
KUndo2Command *KarbonPatternEditStrategyBase::createCommand()
{
QSharedPointer<KoPatternBackground> fill = qSharedPointerDynamicCast<KoPatternBackground>(m_shape->background());
if (fill && isModified()) {
fill = m_oldFill;
QSharedPointer<KoPatternBackground> newFill(new KoPatternBackground(m_imageCollection));
newFill = m_newFill;
return new KoShapeBackgroundCommand(m_shape, newFill, 0);
}
return 0;
}
void KarbonPatternEditStrategyBase::paintHandle(QPainter &painter, const KoViewConverter &converter, const QPointF &position) const
{
QRectF handleRect = converter.viewToDocument(QRectF(0, 0, 2 * m_handleRadius, 2 * m_handleRadius));
handleRect.moveCenter(position);
painter.drawRect(handleRect);
}
bool KarbonPatternEditStrategyBase::mouseInsideHandle(const QPointF &mousePos, const QPointF &handlePos, const KoViewConverter &converter) const
{
qreal grabSensitivityInPt = converter.viewToDocumentX(m_grabSensitivity);
if (mousePos.x() < handlePos.x() - grabSensitivityInPt) {
return false;
}
if (mousePos.x() > handlePos.x() + grabSensitivityInPt) {
return false;
}
if (mousePos.y() < handlePos.y() - grabSensitivityInPt) {
return false;
}
if (mousePos.y() > handlePos.y() + grabSensitivityInPt) {
return false;
}
return true;
}
void KarbonPatternEditStrategyBase::repaint() const
{
m_shape->update();
}
KoShape *KarbonPatternEditStrategyBase::shape() const
{
return m_shape;
}
KoImageCollection *KarbonPatternEditStrategyBase::imageCollection()
{
return m_imageCollection;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
KarbonPatternEditStrategy::KarbonPatternEditStrategy(KoShape *s, KoImageCollection *imageCollection)
: KarbonPatternEditStrategyBase(s, imageCollection)
{
// cache the shapes transformation matrix
- m_matrix = shape()->absoluteTransformation(0);
+ m_matrix = shape()->absoluteTransformation();
QSizeF size = shape()->size();
// the fixed length of half the average shape dimension
m_normalizedLength = 0.25 * (size.width() + size.height());
// get the brush transformation matrix
QTransform brushMatrix;
QSharedPointer<KoPatternBackground> fill = qSharedPointerDynamicCast<KoPatternBackground>(shape()->background());
if (fill) {
brushMatrix = fill->transform();
}
// the center handle at the center point of the shape
//m_origin = QPointF( 0.5 * size.width(), 0.5 * size.height() );
m_handles.append(brushMatrix.map(QPointF()));
// the direction handle with the length of half the average shape dimension
QPointF dirVec = QPointF(m_normalizedLength, 0.0);
m_handles.append(brushMatrix.map(dirVec));
}
KarbonPatternEditStrategy::~KarbonPatternEditStrategy()
{
}
void KarbonPatternEditStrategy::paint(QPainter &painter, const KoViewConverter &converter) const
{
QPointF centerPoint = m_matrix.map(m_origin + m_handles[center]);
QPointF directionPoint = m_matrix.map(m_origin + m_handles[direction]);
- KoShape::applyConversion(painter, converter);
painter.drawLine(centerPoint, directionPoint);
paintHandle(painter, converter, centerPoint);
paintHandle(painter, converter, directionPoint);
}
bool KarbonPatternEditStrategy::selectHandle(const QPointF &mousePos, const KoViewConverter &converter)
{
int handleIndex = 0;
Q_FOREACH (const QPointF &handle, m_handles) {
if (mouseInsideHandle(mousePos, m_matrix.map(m_origin + handle), converter)) {
m_selectedHandle = handleIndex;
return true;
}
handleIndex++;
}
m_selectedHandle = -1;
return false;
}
void KarbonPatternEditStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers)
if (m_selectedHandle == direction) {
QPointF newPos = m_matrix.inverted().map(mouseLocation) - m_origin - m_handles[center];
// calculate the temporary length after handle movement
qreal newLength = sqrt(newPos.x() * newPos.x() + newPos.y() * newPos.y());
// set the new direction vector with the new direction and normalized length
m_handles[m_selectedHandle] = m_handles[center] + m_normalizedLength / newLength * newPos;
} else if (m_selectedHandle == center) {
QPointF diffPos = m_matrix.inverted().map(mouseLocation) - m_origin - m_handles[center];
m_handles[center] += diffPos;
m_handles[direction] += diffPos;
} else {
return;
}
setModified();
QSharedPointer<KoPatternBackground> fill = qSharedPointerDynamicCast<KoPatternBackground>(shape()->background());
if (fill) {
m_newFill = updatedBackground();
fill = m_newFill;
}
}
QRectF KarbonPatternEditStrategy::boundingRect() const
{
// calculate the bounding rect of the handles
QRectF bbox(m_matrix.map(m_origin + m_handles[0]), QSize(0, 0));
for (int i = 1; i < m_handles.count(); ++i) {
QPointF handle = m_matrix.map(m_origin + m_handles[i]);
bbox.setLeft(qMin(handle.x(), bbox.left()));
bbox.setRight(qMax(handle.x(), bbox.right()));
bbox.setTop(qMin(handle.y(), bbox.top()));
bbox.setBottom(qMax(handle.y(), bbox.bottom()));
}
qreal hr = handleRadius();
return bbox.adjusted(-hr, -hr, hr, hr);
}
QSharedPointer<KoPatternBackground> KarbonPatternEditStrategy::updatedBackground()
{
// the direction vector controls the rotation of the pattern
QPointF dirVec = m_handles[direction] - m_handles[center];
qreal angle = atan2(dirVec.y(), dirVec.x()) * 180.0 / M_PI;
QTransform matrix;
// the center handle controls the translation
matrix.translate(m_handles[center].x(), m_handles[center].y());
matrix.rotate(angle);
QSharedPointer<KoPatternBackground> newFill(new KoPatternBackground(imageCollection()));
newFill->setTransform(matrix);
return newFill;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
KarbonOdfPatternEditStrategy::KarbonOdfPatternEditStrategy(KoShape *s, KoImageCollection *imageCollection)
: KarbonPatternEditStrategyBase(s, imageCollection)
{
m_handles.append(QPointF());
m_handles.append(QPointF());
updateHandles(qSharedPointerDynamicCast<KoPatternBackground>(shape()->background()));
}
KarbonOdfPatternEditStrategy::~KarbonOdfPatternEditStrategy()
{
}
void KarbonOdfPatternEditStrategy::paint(QPainter &painter, const KoViewConverter &converter) const
{
- KoShape::applyConversion(painter, converter);
-
QSharedPointer<KoPatternBackground> fill = qSharedPointerDynamicCast<KoPatternBackground>(shape()->background());
if (!fill) {
return;
}
painter.save();
painter.setTransform(m_matrix * painter.transform());
painter.setBrush(Qt::NoBrush);
painter.drawRect(QRectF(m_handles[origin], m_handles[size]));
painter.restore();
if (fill->repeat() == KoPatternBackground::Tiled) {
paintHandle(painter, converter, m_matrix.map(m_handles[origin]));
}
if (fill->repeat() != KoPatternBackground::Stretched) {
paintHandle(painter, converter, m_matrix.map(m_handles[size]));
}
}
bool KarbonOdfPatternEditStrategy::selectHandle(const QPointF &mousePos, const KoViewConverter &converter)
{
QSharedPointer<KoPatternBackground> fill = qSharedPointerDynamicCast<KoPatternBackground>(shape()->background());
if (!fill) {
return false;
}
if (fill->repeat() == KoPatternBackground::Stretched) {
return false;
}
m_selectedHandle = -1;
if (mouseInsideHandle(mousePos, m_matrix.map(m_handles[size]), converter)) {
m_selectedHandle = size;
return true;
}
if (fill->repeat() == KoPatternBackground::Original) {
return false;
}
if (mouseInsideHandle(mousePos, m_matrix.map(m_handles[origin]), converter)) {
m_selectedHandle = origin;
return true;
}
return false;
}
void KarbonOdfPatternEditStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
QSharedPointer<KoPatternBackground> fill = qSharedPointerDynamicCast<KoPatternBackground>(shape()->background());
if (!fill) {
return;
}
if (fill->repeat() == KoPatternBackground::Stretched) {
return;
}
if (m_selectedHandle == origin) {
if (fill->repeat() == KoPatternBackground::Original) {
return;
}
QPointF diffPos = m_matrix.inverted().map(mouseLocation) - m_handles[origin];
m_handles[origin] += diffPos;
m_handles[size] += diffPos;
} else if (m_selectedHandle == size) {
QPointF newPos = m_matrix.inverted().map(mouseLocation);
newPos.setX(qMax(newPos.x(), m_handles[origin].x()));
newPos.setY(qMax(newPos.y(), m_handles[origin].y()));
if (fill->repeat() == KoPatternBackground::Original) {
QPointF diffPos = newPos - m_handles[size];
m_handles[size] += 0.5 * diffPos;
m_handles[origin] -= 0.5 * diffPos;
} else {
m_handles[size] = newPos;
}
} else {
return;
}
setModified();
m_newFill = updatedBackground();
updateHandles(m_newFill);
}
QRectF KarbonOdfPatternEditStrategy::boundingRect() const
{
// calculate the bounding rect of the handles
QRectF bbox(m_matrix.map(m_handles[origin]), m_matrix.map(m_handles[size]));
qreal hr = handleRadius();
return bbox.adjusted(-hr, -hr, hr, hr);
}
QSharedPointer<KoPatternBackground> KarbonOdfPatternEditStrategy::updatedBackground()
{
QSizeF displaySize(m_handles[size].x() - m_handles[origin].x(), m_handles[size].y() - m_handles[origin].y());
qreal offsetX = 100.0 * (m_handles[origin].x() / displaySize.width());
qreal offsetY = 100.0 * (m_handles[origin].y() / displaySize.height());
QSharedPointer<KoPatternBackground> newFill(new KoPatternBackground(imageCollection()));
newFill = m_oldFill;
newFill->setReferencePoint(KoPatternBackground::TopLeft);
newFill->setReferencePointOffset(QPointF(offsetX, offsetY));
newFill->setPatternDisplaySize(displaySize);
return newFill;
}
void KarbonOdfPatternEditStrategy::updateHandles(QSharedPointer<KoPatternBackground> fill)
{
if (!fill) {
return;
}
QRectF patternRect = fill->patternRectFromFillSize(shape()->size());
m_handles[origin] = patternRect.topLeft();
m_handles[size] = patternRect.bottomRight();
}
void KarbonOdfPatternEditStrategy::updateHandles()
{
updateHandles(qSharedPointerDynamicCast<KoPatternBackground>(shape()->background()));
}
diff --git a/plugins/tools/karbonplugins/tools/KarbonPatternTool.cpp b/plugins/tools/karbonplugins/tools/KarbonPatternTool.cpp
index 7caeefe484..1d7bf33b03 100644
--- a/plugins/tools/karbonplugins/tools/KarbonPatternTool.cpp
+++ b/plugins/tools/karbonplugins/tools/KarbonPatternTool.cpp
@@ -1,370 +1,372 @@
/* This file is part of the KDE project
* Copyright (C) 2007,2009,2011 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 "KarbonPatternTool.h"
#include "KarbonPatternEditStrategy.h"
#include <KarbonPatternOptionsWidget.h>
#include <KoCanvasBase.h>
#include <KoShapeManager.h>
#include <KoSelection.h>
#include <KoShape.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeBackgroundCommand.h>
#include <KoPointerEvent.h>
#include <resources/KoPattern.h>
#include <KoPatternBackground.h>
#include <KoImageCollection.h>
#include <KoShapeController.h>
#include <resources/KoResource.h>
#include <KoResourceServerProvider.h>
#include <KoResourceItemChooser.h>
#include <KoResourceServerAdapter.h>
#include <klocalizedstring.h>
+#include <KoViewConverter.h>
#include <QPainter>
#include <QWidget>
#include <kundo2command.h>
KarbonPatternTool::KarbonPatternTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_currentStrategy(0)
, m_optionsWidget(0)
{
}
KarbonPatternTool::~KarbonPatternTool()
{
}
void KarbonPatternTool::paint(QPainter &painter, const KoViewConverter &converter)
{
painter.setBrush(Qt::green); //TODO make configurable
painter.setPen(Qt::blue); //TODO make configurable
+ painter.setTransform(converter.documentToView(), true);
// paint all the strategies
Q_FOREACH (KarbonPatternEditStrategyBase *strategy, m_strategies) {
if (strategy == m_currentStrategy) {
continue;
}
painter.save();
strategy->paint(painter, converter);
painter.restore();
}
// paint selected strategy with another color
if (m_currentStrategy) {
painter.setBrush(Qt::red); //TODO make configurable
m_currentStrategy->paint(painter, converter);
}
}
void KarbonPatternTool::repaintDecorations()
{
Q_FOREACH (KarbonPatternEditStrategyBase *strategy, m_strategies) {
canvas()->updateCanvas(strategy->boundingRect());
}
}
void KarbonPatternTool::mousePressEvent(KoPointerEvent *event)
{
//m_currentStrategy = 0;
Q_FOREACH (KarbonPatternEditStrategyBase *strategy, m_strategies) {
if (strategy->selectHandle(event->point, *canvas()->viewConverter())) {
m_currentStrategy = strategy;
m_currentStrategy->repaint();
useCursor(Qt::SizeAllCursor);
break;
}
}
if (m_currentStrategy) {
m_currentStrategy->setEditing(true);
updateOptionsWidget();
}
}
void KarbonPatternTool::mouseMoveEvent(KoPointerEvent *event)
{
if (m_currentStrategy) {
m_currentStrategy->repaint();
if (m_currentStrategy->isEditing()) {
m_currentStrategy->handleMouseMove(event->point, event->modifiers());
m_currentStrategy->repaint();
return;
}
}
Q_FOREACH (KarbonPatternEditStrategyBase *strategy, m_strategies) {
if (strategy->selectHandle(event->point, *canvas()->viewConverter())) {
useCursor(Qt::SizeAllCursor);
return;
}
}
useCursor(Qt::ArrowCursor);
}
void KarbonPatternTool::mouseReleaseEvent(KoPointerEvent *event)
{
Q_UNUSED(event)
// if we are editing, get out of edit mode and add a command to the stack
if (m_currentStrategy && m_currentStrategy->isEditing()) {
m_currentStrategy->setEditing(false);
KUndo2Command *cmd = m_currentStrategy->createCommand();
if (cmd) {
canvas()->addCommand(cmd);
}
updateOptionsWidget();
}
}
void KarbonPatternTool::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_I: {
KoDocumentResourceManager *rm = canvas()->shapeController()->resourceManager();
uint handleRadius = rm->handleRadius();
if (event->modifiers() & Qt::ControlModifier) {
handleRadius--;
} else {
handleRadius++;
}
rm->setHandleRadius(handleRadius);
}
break;
default:
event->ignore();
return;
}
event->accept();
}
void KarbonPatternTool::initialize()
{
if (m_currentStrategy && m_currentStrategy->isEditing()) {
return;
}
QList<KoShape *> selectedShapes = canvas()->shapeManager()->selection()->selectedShapes();
// remove all pattern strategies no longer applicable
Q_FOREACH (KarbonPatternEditStrategyBase *strategy, m_strategies) {
// is this gradient shape still selected ?
if (!selectedShapes.contains(strategy->shape()) || ! strategy->shape()->isShapeEditable()) {
m_strategies.remove(strategy->shape());
if (m_currentStrategy == strategy) {
m_currentStrategy = 0;
}
delete strategy;
continue;
}
// does the shape has no fill pattern anymore ?
QSharedPointer<KoPatternBackground> fill = qSharedPointerDynamicCast<KoPatternBackground>(strategy->shape()->background());
if (!fill) {
// delete the gradient
m_strategies.remove(strategy->shape());
if (m_currentStrategy == strategy) {
m_currentStrategy = 0;
}
delete strategy;
continue;
}
strategy->updateHandles();
strategy->repaint();
}
KoImageCollection *imageCollection = canvas()->shapeController()->resourceManager()->imageCollection();
// now create new strategies if needed
Q_FOREACH (KoShape *shape, selectedShapes) {
if (!shape->isShapeEditable()) {
continue;
}
// do we already have a strategy for that shape?
if (m_strategies.contains(shape)) {
continue;
}
if (qSharedPointerDynamicCast<KoPatternBackground>(shape->background())) {
KarbonPatternEditStrategyBase *s = new KarbonOdfPatternEditStrategy(shape, imageCollection);
m_strategies.insert(shape, s);
s->repaint();
}
}
// automatically select strategy when editing single shape
if (m_strategies.count() == 1 && ! m_currentStrategy) {
m_currentStrategy = m_strategies.begin().value();
updateOptionsWidget();
}
if (m_currentStrategy) {
m_currentStrategy->repaint();
}
}
void KarbonPatternTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
KoToolBase::activate(activation, shapes);
if (shapes.isEmpty()) {
emit done();
return;
}
initialize();
KarbonPatternEditStrategyBase::setHandleRadius(handleRadius());
KarbonPatternEditStrategyBase::setGrabSensitivity(grabSensitivity());
useCursor(Qt::ArrowCursor);
connect(canvas()->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(initialize()));
}
void KarbonPatternTool::deactivate()
{
// we are not interested in selection content changes when not active
disconnect(canvas()->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(initialize()));
Q_FOREACH (KarbonPatternEditStrategyBase *strategy, m_strategies) {
strategy->repaint();
}
qDeleteAll(m_strategies);
m_strategies.clear();
Q_FOREACH (KoShape *shape, canvas()->shapeManager()->selection()->selectedShapes()) {
shape->update();
}
m_currentStrategy = 0;
KoToolBase::deactivate();
}
void KarbonPatternTool::documentResourceChanged(int key, const QVariant &res)
{
switch (key) {
case KoDocumentResourceManager::HandleRadius:
Q_FOREACH (KarbonPatternEditStrategyBase *strategy, m_strategies) {
strategy->repaint();
}
KarbonPatternEditStrategyBase::setHandleRadius(res.toUInt());
Q_FOREACH (KarbonPatternEditStrategyBase *strategy, m_strategies) {
strategy->repaint();
}
break;
case KoDocumentResourceManager::GrabSensitivity:
KarbonPatternEditStrategyBase::setGrabSensitivity(res.toUInt());
break;
default:
return;
}
}
QList<QPointer<QWidget> > KarbonPatternTool::createOptionWidgets()
{
QList<QPointer<QWidget> > widgets;
m_optionsWidget = new KarbonPatternOptionsWidget();
connect(m_optionsWidget, SIGNAL(patternChanged()),
this, SLOT(patternChanged()));
KoResourceServer<KoPattern> *rserver = KoResourceServerProvider::instance()->patternServer();
QSharedPointer<KoAbstractResourceServerAdapter> adapter(new KoResourceServerAdapter<KoPattern>(rserver));
KoResourceItemChooser *chooser = new KoResourceItemChooser(adapter, m_optionsWidget);
chooser->setObjectName("KarbonPatternChooser");
connect(chooser, SIGNAL(resourceSelected(KoResource*)),
this, SLOT(patternSelected(KoResource*)));
m_optionsWidget->setWindowTitle(i18n("Pattern Options"));
widgets.append(m_optionsWidget);
chooser->setWindowTitle(i18n("Patterns"));
widgets.append(chooser);
updateOptionsWidget();
return widgets;
}
void KarbonPatternTool::patternSelected(KoResource *resource)
{
KoPattern *currentPattern = dynamic_cast<KoPattern *>(resource);
if (!currentPattern || ! currentPattern->valid()) {
return;
}
KoImageCollection *imageCollection = canvas()->shapeController()->resourceManager()->imageCollection();
if (imageCollection) {
QList<KoShape *> selectedShapes = canvas()->shapeManager()->selection()->selectedShapes();
QSharedPointer<KoPatternBackground> newFill(new KoPatternBackground(imageCollection));
newFill->setPattern(currentPattern->pattern());
canvas()->addCommand(new KoShapeBackgroundCommand(selectedShapes, newFill));
initialize();
}
}
void KarbonPatternTool::updateOptionsWidget()
{
if (m_optionsWidget && m_currentStrategy) {
QSharedPointer<KoPatternBackground> fill = qSharedPointerDynamicCast<KoPatternBackground>(m_currentStrategy->shape()->background());
if (fill) {
m_optionsWidget->setRepeat(fill->repeat());
m_optionsWidget->setReferencePoint(fill->referencePoint());
m_optionsWidget->setReferencePointOffset(fill->referencePointOffset());
m_optionsWidget->setTileRepeatOffset(fill->tileRepeatOffset());
m_optionsWidget->setPatternSize(fill->patternDisplaySize().toSize());
}
}
}
void KarbonPatternTool::patternChanged()
{
if (m_currentStrategy) {
KoShape *shape = m_currentStrategy->shape();
QSharedPointer<KoPatternBackground> oldFill = qSharedPointerDynamicCast<KoPatternBackground>(shape->background());
if (!oldFill) {
return;
}
KoImageCollection *imageCollection = canvas()->shapeController()->resourceManager()->imageCollection();
if (!imageCollection) {
return;
}
QSharedPointer<KoPatternBackground> newFill(new KoPatternBackground(imageCollection));
if (!newFill) {
return;
}
newFill->setTransform(oldFill->transform());
newFill->setPattern(oldFill->pattern());
newFill->setRepeat(m_optionsWidget->repeat());
newFill->setReferencePoint(m_optionsWidget->referencePoint());
newFill->setReferencePointOffset(m_optionsWidget->referencePointOffset());
newFill->setTileRepeatOffset(m_optionsWidget->tileRepeatOffset());
newFill->setPatternDisplaySize(m_optionsWidget->patternSize());
canvas()->addCommand(new KoShapeBackgroundCommand(shape, newFill));
}
}
diff --git a/plugins/tools/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp b/plugins/tools/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp
index 963deb1b06..58833df01d 100644
--- a/plugins/tools/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp
+++ b/plugins/tools/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp
@@ -1,554 +1,553 @@
/* This file is part of the KDE project
* Copyright (c) 2009-2011 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 "KarbonFilterEffectsTool.h"
#include "KoFilterEffect.h"
#include "KoFilterEffectStack.h"
#include "KoFilterEffectFactoryBase.h"
#include "KoFilterEffectRegistry.h"
#include "KoFilterEffectConfigWidgetBase.h"
#include "KoCanvasBase.h"
#include "KoDocumentResourceManager.h"
#include "KoSelectedShapesProxy.h"
#include "KoViewConverter.h"
#include "KoSelection.h"
#include "FilterEffectEditWidget.h"
#include "FilterEffectResource.h"
#include "FilterResourceServerProvider.h"
#include "FilterStackSetCommand.h"
#include "FilterRegionChangeCommand.h"
#include "FilterRegionEditStrategy.h"
#include "KoResourceServerAdapter.h"
#include "KoResourceSelector.h"
#include <KoPointerEvent.h>
#include <KoIcon.h>
#include <kcombobox.h>
#include <klocalizedstring.h>
#include <QDialog>
#include <QSpinBox>
#include <QWidget>
#include <QGridLayout>
#include <QToolButton>
#include <QStackedWidget>
#include <QLabel>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
#include "kis_double_parse_spin_box.h"
class KarbonFilterEffectsTool::Private
{
public:
Private()
: filterSelector(0)
, configSelector(0)
, configStack(0)
, posX(0)
, posY(0)
, posW(0)
, posH(0)
, clearButton(0)
, currentEffect(0)
, currentPanel(0)
, currentShape(0)
{
}
void fillConfigSelector(KoShape *shape, KarbonFilterEffectsTool *tool)
{
if (!configSelector) {
return;
}
configSelector->clear();
clearButton->setEnabled(false);
if (!shape || !shape->filterEffectStack()) {
addWidgetForEffect(0, tool);
return;
}
configSelector->blockSignals(true);
int index = 0;
Q_FOREACH (KoFilterEffect *effect, shape->filterEffectStack()->filterEffects()) {
configSelector->addItem(QString("%1 - ").arg(index) + effect->name());
index++;
}
configSelector->blockSignals(false);
KoFilterEffect *effect = index > 0 ? shape->filterEffectStack()->filterEffects().first() : 0;
addWidgetForEffect(effect, tool);
clearButton->setEnabled(shape->filterEffectStack() != 0);
}
void addWidgetForEffect(KoFilterEffect *filterEffect, KarbonFilterEffectsTool *tool)
{
// remove current widget if new effect is zero or effect type has changed
if (!filterEffect || (currentEffect && filterEffect->id() != currentEffect->id())) {
while (configStack->count()) {
configStack->removeWidget(configStack->widget(0));
}
}
if (!filterEffect) {
currentEffect = 0;
currentPanel = 0;
} else if (!currentEffect || currentEffect->id() != filterEffect->id()) {
// when a effect is set and is differs from the previous one
// get the config widget and insert it into the option widget
currentEffect = filterEffect;
KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance();
KoFilterEffectFactoryBase *factory = registry->value(currentEffect->id());
if (!factory) {
return;
}
currentPanel = factory->createConfigWidget();
if (!currentPanel) {
return;
}
currentPanel->layout()->setContentsMargins(0, 0, 0, 0);
configStack->insertWidget(0, currentPanel);
configStack->layout()->setContentsMargins(0, 0, 0, 0);
connect(currentPanel, SIGNAL(filterChanged()), tool, SLOT(filterChanged()));
}
if (currentPanel) {
currentPanel->editFilterEffect(filterEffect);
}
updateFilterRegion();
}
void updateFilterRegion()
{
QRectF region = currentEffect ? currentEffect->filterRect() : QRectF(0, 0, 0, 0);
posX->blockSignals(true);
posX->setValue(100.0 * region.x());
posX->blockSignals(false);
posX->setEnabled(currentEffect != 0);
posY->blockSignals(true);
posY->setValue(100.0 * region.y());
posY->blockSignals(false);
posY->setEnabled(currentEffect != 0);
posW->blockSignals(true);
posW->setValue(100.0 * region.width());
posW->blockSignals(false);
posW->setEnabled(currentEffect != 0);
posH->blockSignals(true);
posH->setValue(100.0 * region.height());
posH->blockSignals(false);
posH->setEnabled(currentEffect != 0);
}
EditMode editModeFromMousePosition(const QPointF &mousePosition, KarbonFilterEffectsTool *tool)
{
if (currentShape && currentShape->filterEffectStack() && currentEffect) {
// get the size rect of the shape
QRectF sizeRect(QPointF(), currentShape->size());
// get the filter rectangle in shape coordinates
QRectF filterRect = currentEffect->filterRectForBoundingRect(sizeRect);
// get the transformation from document to shape coordinates
- QTransform transform = currentShape->absoluteTransformation(0).inverted();
+ QTransform transform = currentShape->absoluteTransformation().inverted();
// adjust filter rectangle by grab sensitivity
const int grabDistance = tool->grabSensitivity();
QPointF border = tool->canvas()->viewConverter()->viewToDocument(QPointF(grabDistance, grabDistance));
filterRect.adjust(-border.x(), -border.y(), border.x(), border.y());
// map event point from document to shape coordinates
QPointF shapePoint = transform.map(mousePosition);
// check if the mouse is inside/near our filter rect
if (filterRect.contains(shapePoint)) {
if (qAbs(shapePoint.x() - filterRect.left()) <= border.x()) {
return MoveLeft;
} else if (qAbs(shapePoint.x() - filterRect.right()) <= border.x()) {
return MoveRight;
} else if (qAbs(shapePoint.y() - filterRect.top()) <= border.y()) {
return MoveTop;
} else if (qAbs(shapePoint.y() - filterRect.bottom()) <= border.y()) {
return MoveBottom;
} else {
return MoveAll;
}
} else {
return None;
}
}
return None;
}
KoResourceSelector *filterSelector;
KComboBox *configSelector;
QStackedWidget *configStack;
QDoubleSpinBox *posX;
QDoubleSpinBox *posY;
QDoubleSpinBox *posW;
QDoubleSpinBox *posH;
QToolButton *clearButton;
KoFilterEffect *currentEffect;
KoFilterEffectConfigWidgetBase *currentPanel;
KoShape *currentShape;
};
KarbonFilterEffectsTool::KarbonFilterEffectsTool(KoCanvasBase *canvas)
: KoInteractionTool(canvas)
, d(new Private())
{
connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()),
this, SLOT(selectionChanged()));
connect(canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
this, SLOT(selectionChanged()));
}
KarbonFilterEffectsTool::~KarbonFilterEffectsTool()
{
delete d;
}
void KarbonFilterEffectsTool::paint(QPainter &painter, const KoViewConverter &converter)
{
if (d->currentShape && d->currentShape->filterEffectStack()) {
painter.save();
// apply the shape transformation
- QTransform transform = d->currentShape->absoluteTransformation(&converter);
- painter.setTransform(transform, true);
- // apply the zoom transformation
- KoShape::applyConversion(painter, converter);
+
+ QTransform transform = d->currentShape->absoluteTransformation();
+ painter.setTransform(transform * converter.documentToView(), true);
// get the size rect of the shape
QRectF sizeRect(QPointF(), d->currentShape->size());
// get the clipping rect of the filter stack
KoFilterEffectStack *filterStack = d->currentShape->filterEffectStack();
QRectF clipRect = filterStack->clipRectForBoundingRect(sizeRect);
// finally paint the clipping rect
painter.setBrush(Qt::NoBrush);
painter.setPen(Qt::blue);
painter.drawRect(clipRect);
if (currentStrategy()) {
currentStrategy()->paint(painter, converter);
} else if (d->currentEffect) {
QRectF filterRect = d->currentEffect->filterRectForBoundingRect(sizeRect);
// paint the filter subregion rect
painter.setBrush(Qt::NoBrush);
painter.setPen(Qt::red);
painter.drawRect(filterRect);
}
painter.restore();
}
}
void KarbonFilterEffectsTool::repaintDecorations()
{
if (d->currentShape && d->currentShape->filterEffectStack()) {
QRectF bb = d->currentShape->boundingRect();
const int radius = handleRadius();
canvas()->updateCanvas(bb.adjusted(-radius, -radius, radius, radius));
}
}
void KarbonFilterEffectsTool::activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes)
{
Q_UNUSED(toolActivation);
if (shapes.isEmpty()) {
emit done();
return;
}
d->currentShape = canvas()->selectedShapesProxy()->selection()->firstSelectedShape();
d->fillConfigSelector(d->currentShape, this);
}
void KarbonFilterEffectsTool::mouseMoveEvent(KoPointerEvent *event)
{
if (currentStrategy()) {
KoInteractionTool::mouseMoveEvent(event);
} else {
EditMode mode = d->editModeFromMousePosition(event->point, this);
switch (mode) {
case MoveAll:
useCursor(Qt::SizeAllCursor);
break;
case MoveLeft:
case MoveRight:
useCursor(Qt::SizeHorCursor);
break;
case MoveTop:
case MoveBottom:
useCursor(Qt::SizeVerCursor);
break;
case None:
useCursor(Qt::ArrowCursor);
break;
}
}
}
KoInteractionStrategy *KarbonFilterEffectsTool::createStrategy(KoPointerEvent *event)
{
EditMode mode = d->editModeFromMousePosition(event->point, this);
if (mode == None) {
return 0;
}
return new FilterRegionEditStrategy(this, d->currentShape, d->currentEffect, mode);
}
void KarbonFilterEffectsTool::presetSelected(KoResource *resource)
{
if (!d->currentShape) {
return;
}
FilterEffectResource *effectResource = dynamic_cast<FilterEffectResource *>(resource);
if (!effectResource) {
return;
}
KoFilterEffectStack *filterStack = effectResource->toFilterStack();
if (!filterStack) {
return;
}
canvas()->addCommand(new FilterStackSetCommand(filterStack, d->currentShape));
d->fillConfigSelector(d->currentShape, this);
}
void KarbonFilterEffectsTool::editFilter()
{
QPointer<QDialog> dlg = new QDialog();
dlg->setWindowTitle(i18n("Filter Effect Editor"));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
QWidget *mainWidget = new QWidget(0);
QVBoxLayout *mainLayout = new QVBoxLayout;
dlg->setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
connect(buttonBox->button(QDialogButtonBox::Close), SIGNAL(clicked()), dlg, SLOT(close()));
FilterEffectEditWidget *editor = new FilterEffectEditWidget(dlg);
editor->editShape(d->currentShape, canvas());
mainLayout->addWidget(editor);
mainLayout->addWidget(buttonBox);
dlg->exec();
delete dlg;
d->fillConfigSelector(d->currentShape, this);
}
void KarbonFilterEffectsTool::clearFilter()
{
if (!d->currentShape) {
return;
}
if (!d->currentShape->filterEffectStack()) {
return;
}
canvas()->addCommand(new FilterStackSetCommand(0, d->currentShape));
d->fillConfigSelector(d->currentShape, this);
}
void KarbonFilterEffectsTool::filterChanged()
{
if (!d->currentShape) {
return;
}
d->currentShape->update();
}
void KarbonFilterEffectsTool::filterSelected(int index)
{
if (!d->currentShape || ! d->currentShape->filterEffectStack()) {
return;
}
KoFilterEffect *effect = 0;
QList<KoFilterEffect *> filterEffects = d->currentShape->filterEffectStack()->filterEffects();
if (index >= 0 && index < filterEffects.count()) {
effect = filterEffects[index];
}
d->addWidgetForEffect(effect, this);
repaintDecorations();
}
void KarbonFilterEffectsTool::selectionChanged()
{
d->currentShape = canvas()->selectedShapesProxy()->selection()->firstSelectedShape();
d->fillConfigSelector(d->currentShape, this);
}
void KarbonFilterEffectsTool::regionXChanged(double x)
{
if (!d->currentEffect) {
return;
}
QRectF region = d->currentEffect->filterRect();
region.setX(x / 100.0);
canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape));
}
void KarbonFilterEffectsTool::regionYChanged(double y)
{
if (!d->currentEffect) {
return;
}
QRectF region = d->currentEffect->filterRect();
region.setY(y / 100.0);
canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape));
}
void KarbonFilterEffectsTool::regionWidthChanged(double width)
{
if (!d->currentEffect) {
return;
}
QRectF region = d->currentEffect->filterRect();
region.setWidth(width / 100.0);
canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape));
}
void KarbonFilterEffectsTool::regionHeightChanged(double height)
{
if (!d->currentEffect) {
return;
}
QRectF region = d->currentEffect->filterRect();
region.setHeight(height / 100.0);
canvas()->addCommand(new FilterRegionChangeCommand(d->currentEffect, region, d->currentShape));
}
QList<QPointer<QWidget> > KarbonFilterEffectsTool::createOptionWidgets()
{
QList<QPointer<QWidget> > widgets;
FilterResourceServerProvider *serverProvider = FilterResourceServerProvider::instance();
KoResourceServer<FilterEffectResource> *server = serverProvider->filterEffectServer();
QSharedPointer<KoAbstractResourceServerAdapter> adapter(new KoResourceServerAdapter<FilterEffectResource>(server));
//---------------------------------------------------------------------
QWidget *addFilterWidget = new QWidget();
addFilterWidget->setObjectName("AddEffect");
QGridLayout *addFilterLayout = new QGridLayout(addFilterWidget);
d->filterSelector = new KoResourceSelector(addFilterWidget);
d->filterSelector->setResourceAdapter(adapter);
d->filterSelector->setDisplayMode(KoResourceSelector::TextMode);
d->filterSelector->setColumnCount(1);
addFilterLayout->addWidget(new QLabel(i18n("Effects"), addFilterWidget), 0, 0);
addFilterLayout->addWidget(d->filterSelector, 0, 1);
connect(d->filterSelector, SIGNAL(resourceSelected(KoResource*)),
this, SLOT(presetSelected(KoResource*)));
connect(d->filterSelector, SIGNAL(resourceApplied(KoResource*)),
this, SLOT(presetSelected(KoResource*)));
QToolButton *editButton = new QToolButton(addFilterWidget);
editButton->setIcon(koIcon("view-filter"));
editButton->setToolTip(i18n("View and edit filter"));
addFilterLayout->addWidget(editButton, 0, 2);
connect(editButton, SIGNAL(clicked()), this, SLOT(editFilter()));
d->clearButton = new QToolButton(addFilterWidget);
d->clearButton->setIcon(koIcon("edit-delete"));
d->clearButton->setToolTip(i18n("Remove filter from object"));
addFilterLayout->addWidget(d->clearButton, 0, 3);
connect(d->clearButton, SIGNAL(clicked()), this, SLOT(clearFilter()));
addFilterWidget->setWindowTitle(i18n("Add Filter"));
widgets.append(addFilterWidget);
//---------------------------------------------------------------------
QWidget *configFilterWidget = new QWidget();
configFilterWidget->setObjectName("ConfigEffect");
QGridLayout *configFilterLayout = new QGridLayout(configFilterWidget);
d->configSelector = new KComboBox(configFilterWidget);
configFilterLayout->addWidget(d->configSelector, 0, 0);
connect(d->configSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(filterSelected(int)));
d->configStack = new QStackedWidget(configFilterWidget);
configFilterLayout->addWidget(d->configStack, 1, 0);
configFilterLayout->setContentsMargins(0, 0, 0, 0);
configFilterWidget->setWindowTitle(i18n("Effect Properties"));
widgets.append(configFilterWidget);
//---------------------------------------------------------------------
QWidget *filterRegionWidget = new QWidget();
filterRegionWidget->setObjectName("EffectRegion");
QGridLayout *filterRegionLayout = new QGridLayout(filterRegionWidget);
d->posX = new KisDoubleParseSpinBox(filterRegionWidget);
d->posX->setSuffix(i18n("%"));
connect(d->posX, SIGNAL(valueChanged(double)), this, SLOT(regionXChanged(double)));
filterRegionLayout->addWidget(new QLabel(i18n("X:")), 0, 0);
filterRegionLayout->addWidget(d->posX, 0, 1);
d->posY = new KisDoubleParseSpinBox(filterRegionWidget);
d->posY->setSuffix(i18n("%"));
connect(d->posY, SIGNAL(valueChanged(double)), this, SLOT(regionYChanged(double)));
filterRegionLayout->addWidget(new QLabel(i18n("Y:")), 1, 0);
filterRegionLayout->addWidget(d->posY, 1, 1);
d->posW = new KisDoubleParseSpinBox(filterRegionWidget);
d->posW->setSuffix(i18n("%"));
connect(d->posW, SIGNAL(valueChanged(double)), this, SLOT(regionWidthChanged(double)));
filterRegionLayout->addWidget(new QLabel(i18n("W:")), 0, 2);
filterRegionLayout->addWidget(d->posW, 0, 3);
d->posH = new KisDoubleParseSpinBox(filterRegionWidget);
d->posH->setSuffix(i18n("%"));
connect(d->posH, SIGNAL(valueChanged(double)), this, SLOT(regionHeightChanged(double)));
filterRegionLayout->addWidget(new QLabel(i18n("H:")), 1, 2);
filterRegionLayout->addWidget(d->posH, 1, 3);
filterRegionLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding), 2, 0);
filterRegionLayout->setContentsMargins(0, 0, 0, 0);
filterRegionWidget->setWindowTitle(i18n("Effect Region"));
widgets.append(filterRegionWidget);
//---------------------------------------------------------------------
d->fillConfigSelector(d->currentShape, this);
return widgets;
}
diff --git a/plugins/tools/selectiontools/CMakeLists.txt b/plugins/tools/selectiontools/CMakeLists.txt
index 756cab5959..8b9aff2c17 100644
--- a/plugins/tools/selectiontools/CMakeLists.txt
+++ b/plugins/tools/selectiontools/CMakeLists.txt
@@ -1,37 +1,35 @@
-add_subdirectory(tests)
-
set(kritaselectiontools_SOURCES
selection_tools.cc
kis_tool_select_rectangular.cc
kis_tool_select_polygonal.cc
kis_tool_select_elliptical.cc
kis_tool_select_contiguous.cc
kis_tool_select_outline.cc
kis_tool_select_path.cc
kis_tool_select_similar.cc
kis_selection_modifier_mapper.cc
KisMagneticWorker.cc
KisToolSelectMagnetic.cc
)
qt5_add_resources(kritaselectiontools_SOURCES selectiontools.qrc)
add_library(kritaselectiontools MODULE ${kritaselectiontools_SOURCES})
generate_export_header(kritaselectiontools BASE_NAME kritaselectiontools)
target_link_libraries(kritaselectiontools kritaui kritabasicflakes kritaimage)
install(TARGETS kritaselectiontools DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
install( FILES
KisToolSelectPolygonal.action
KisToolSelectElliptical.action
KisToolSelectSimilar.action
KisToolSelectContiguous.action
KisToolSelectRectangular.action
KisToolSelectOutline.action
KisToolSelectPath.action
KisToolSelectMagnetic.action
DESTINATION ${DATA_INSTALL_DIR}/krita/actions
)
diff --git a/plugins/tools/selectiontools/tests/CMakeLists.txt b/plugins/tools/selectiontools/tests/CMakeLists.txt
deleted file mode 100644
index 18ed773851..0000000000
--- a/plugins/tools/selectiontools/tests/CMakeLists.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
-include_directories(
- ${CMAKE_CURRENT_SOURCE_DIR}/..
- ${CMAKE_CURRENT_BINARY_DIR}/..
- ${CMAKE_SOURCE_DIR}/sdk/tests
-)
-
-macro_add_unittest_definitions()
-
-########### next target ###############
-
-ecm_add_test(KisMagneticWorkerTest.cc
- NAME_PREFIX plugins-magneticselection-
- LINK_LIBRARIES kritaselectiontools kritaimage Qt5::Test)
diff --git a/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.cc b/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.cc
deleted file mode 100644
index 33c9006f66..0000000000
--- a/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.cc
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (c) 2019 Kuntal Majumder <hellozee@disroot.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 "KisMagneticWorkerTest.h"
-
-#include <KisMagneticWorker.h>
-#include <kis_paint_device.h>
-#include <kis_painter.h>
-#include <testutil.h>
-#include <kis_paint_device_debug_utils.h>
-
-#include <QDebug>
-
-inline KisPaintDeviceSP loadTestImage(const QString &name, bool convertToAlpha)
-{
- QImage image(TestUtil::fetchDataFileLazy(name));
- KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
- dev->convertFromQImage(image, 0);
-
- if (convertToAlpha) {
- dev = KisPainter::convertToAlphaAsAlpha(dev);
- }
-
- return dev;
-}
-
-void KisMagneticWorkerTest::testWorker()
-{
- KisPaintDeviceSP dev = loadTestImage("test_main.png", false);
- const QRect rect = dev->exactBounds();
- KisPaintDeviceSP grayscaleDev = KisPainter::convertToAlphaAsGray(dev);
- KisMagneticWorker worker(grayscaleDev);
-
- const QPoint startPos(80, 10);
- const QPoint endPos(10, 100);
-
- auto points = worker.computeEdge(20, startPos, endPos, 3.0);
- KIS_DUMP_DEVICE_2(grayscaleDev, rect, "draw", "dd");
-
- /*
- QVector<QPointF> result = { QPointF(50,65),
- QPointF(49,64),
- QPointF(48,63),
- QPointF(47,62),
- QPointF(46,61),
- QPointF(45,60),
- QPointF(44,59),
- QPointF(44,58),
- QPointF(44,57),
- QPointF(44,56),
- QPointF(44,55),
- QPointF(44,54),
- QPointF(44,53),
- QPointF(44,52),
- QPointF(44,51),
- QPointF(44,50),
- QPointF(44,49),
- QPointF(44,48),
- QPointF(44,47),
- QPointF(44,46),
- QPointF(44,45),
- QPointF(44,44),
- QPointF(44,43),
- QPointF(44,42),
- QPointF(43,41),
- QPointF(43,40),
- QPointF(43,39),
- QPointF(44,38),
- QPointF(44,37),
- QPointF(44,36),
- QPointF(44,35),
- QPointF(44,34),
- QPointF(44,33),
- QPointF(44,32),
- QPointF(44,31),
- QPointF(44,30),
- QPointF(44,29),
- QPointF(44,28),
- QPointF(44,27),
- QPointF(44,26),
- QPointF(44,25),
- QPointF(44,24),
- QPointF(44,23),
- QPointF(44,22),
- QPointF(44,21),
- QPointF(44,20),
- QPointF(44,19),
- QPointF(44,18),
- QPointF(43,17),
- QPointF(44,16),
- QPointF(44,15),
- QPointF(44,14),
- QPointF(44,13),
- QPointF(43,12),
- QPointF(42,11),
- QPointF(41,11),
- QPointF(40,10)};
-
- QCOMPARE(result, points);
- */
-
- QImage img = dev->convertToQImage(0, rect);
- img = img.convertToFormat(QImage::Format_ARGB32);
- QPainter gc(&img);
-
- QPainterPath path;
-
- for (int i = 0; i < points.size(); i++) {
- if (i == 0) {
- path.moveTo(points[i]);
- } else {
- path.lineTo(points[i]);
- }
- }
-
- gc.setPen(Qt::blue);
- gc.drawPath(path);
-
- gc.setPen(Qt::green);
- gc.drawEllipse(startPos, 3, 3);
- gc.setPen(Qt::red);
- gc.drawEllipse(endPos, 2, 2);
-
- img.save("result.png");
-
-}
-
-QTEST_MAIN(KisMagneticWorkerTest)
diff --git a/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.h b/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.h
deleted file mode 100644
index 934b6f8a4f..0000000000
--- a/plugins/tools/selectiontools/tests/KisMagneticWorkerTest.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2019 Kuntal Majumder <hellozee@disroot.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 KISMAGNETICWORKERTEST_H
-#define KISMAGNETICWORKERTEST_H
-
-#include <QtTest>
-
-class KisMagneticWorkerTest : public QObject
-{
- Q_OBJECT
-private Q_SLOTS:
- void testWorker();
-
-};
-
-#endif
diff --git a/plugins/tools/selectiontools/tests/data/test_main.png b/plugins/tools/selectiontools/tests/data/test_main.png
deleted file mode 100644
index cb0e8e5429..0000000000
Binary files a/plugins/tools/selectiontools/tests/data/test_main.png and /dev/null differ
diff --git a/plugins/tools/svgtexttool/SvgTextEditor.cpp b/plugins/tools/svgtexttool/SvgTextEditor.cpp
index c68ceb2010..790ec33544 100644
--- a/plugins/tools/svgtexttool/SvgTextEditor.cpp
+++ b/plugins/tools/svgtexttool/SvgTextEditor.cpp
@@ -1,1225 +1,1251 @@
/* This file is part of the KDE project
*
* Copyright 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.
*/
#include "SvgTextEditor.h"
#include <QAction>
#include <QApplication>
#include <QBuffer>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFontComboBox>
#include <QFontDatabase>
#include <QFormLayout>
#include <QLineEdit>
#include <QListView>
#include <QMenu>
#include <QMessageBox>
#include <QPainter>
#include <QPalette>
#include <QPushButton>
#include <QStandardItem>
#include <QStandardItemModel>
#include <QSvgGenerator>
#include <QTabWidget>
#include <QTextEdit>
#include <QUrl>
#include <QVBoxLayout>
#include <QWidgetAction>
+#include <QApplication>
+#include <QDesktopWidget>
+#include <QScreen>
#include <kcharselect.h>
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <kactioncollection.h>
#include <kxmlguifactory.h>
#include <ktoolbar.h>
#include <ktoggleaction.h>
#include <kguiitem.h>
#include <KoDialog.h>
#include <KoResourcePaths.h>
#include <KoSvgTextShape.h>
#include <KoSvgTextShapeMarkupConverter.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorPopupAction.h>
#include <svg/SvgUtil.h>
#include <KisScreenColorPicker.h>
#include <kis_icon.h>
#include <kis_config.h>
#include <kis_file_name_requester.h>
#include <kis_action_registry.h>
#include "kis_font_family_combo_box.h"
#include "FontSizeAction.h"
#include "kis_signals_blocker.h"
SvgTextEditor::SvgTextEditor(QWidget *parent, Qt::WindowFlags flags)
: KXmlGuiWindow(parent, flags)
, m_page(new QWidget(this))
#ifndef Q_OS_WIN
, m_charSelectDialog(new KoDialog(this))
#endif
{
m_textEditorWidget.setupUi(m_page);
setCentralWidget(m_page);
m_textEditorWidget.chkVertical->setVisible(false);
#ifndef Q_OS_WIN
KCharSelect *charSelector = new KCharSelect(m_charSelectDialog, 0, KCharSelect::AllGuiElements);
m_charSelectDialog->setMainWidget(charSelector);
connect(charSelector, SIGNAL(currentCharChanged(QChar)), SLOT(insertCharacter(QChar)));
m_charSelectDialog->hide();
m_charSelectDialog->setButtons(KoDialog::Close);
#endif
connect(m_textEditorWidget.buttons, SIGNAL(accepted()), this, SLOT(save()));
connect(m_textEditorWidget.buttons, SIGNAL(rejected()), this, SLOT(slotCloseEditor()));
connect(m_textEditorWidget.buttons, SIGNAL(clicked(QAbstractButton*)), this, SLOT(dialogButtonClicked(QAbstractButton*)));
KConfigGroup cg(KSharedConfig::openConfig(), "SvgTextTool");
actionCollection()->setConfigGroup("SvgTextTool");
actionCollection()->setComponentName("svgtexttool");
actionCollection()->setComponentDisplayName(i18n("Text Tool"));
- QByteArray state;
if (cg.hasKey("WindowState")) {
- state = cg.readEntry("State", state);
- state = QByteArray::fromBase64(state);
+ QByteArray state = cg.readEntry("State", state);
// One day will need to load the version number, but for now, assume 0
- restoreState(state);
+ restoreState(QByteArray::fromBase64(state));
+ }
+ if (cg.hasKey("Geometry")) {
+ QByteArray ba = cg.readEntry("Geometry", QByteArray());
+ restoreGeometry(QByteArray::fromBase64(ba));
+ }
+ else {
+ const int scnum = QApplication::desktop()->screenNumber(parentWidget());
+ QRect desk = QGuiApplication::screens().at(scnum)->availableVirtualGeometry();
+
+ quint32 x = desk.x();
+ quint32 y = desk.y();
+ quint32 w = 0;
+ quint32 h = 0;
+ const int deskWidth = desk.width();
+ w = (deskWidth / 3) * 2;
+ h = (desk.height() / 3) * 2;
+ x += (desk.width() - w) / 2;
+ y += (desk.height() - h) / 2;
+
+ move(x,y);
+ setGeometry(geometry().x(), geometry().y(), w, h);
+
}
setAcceptDrops(true);
//setStandardToolBarMenuEnabled(true);
#ifdef Q_OS_MACOS
setUnifiedTitleAndToolBarOnMac(true);
#endif
setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
m_syntaxHighlighter = new BasicXMLSyntaxHighlighter(m_textEditorWidget.svgTextEdit);
m_textEditorWidget.svgTextEdit->setFont(QFontDatabase().systemFont(QFontDatabase::FixedFont));
createActions();
// If we have customized the toolbars, load that first
setLocalXMLFile(KoResourcePaths::locateLocal("data", "svgtexttool.xmlgui"));
setXMLFile(":/kxmlgui5/svgtexttool.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) {
toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
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);
}
}
plugActionList("toolbarlist", toolbarList);
connect(m_textEditorWidget.textTab, SIGNAL(currentChanged(int)), this, SLOT(switchTextEditorTab()));
switchTextEditorTab();
m_textEditorWidget.richTextEdit->document()->setDefaultStyleSheet("p {margin:0px;}");
applySettings();
+
}
SvgTextEditor::~SvgTextEditor()
{
KConfigGroup g(KSharedConfig::openConfig(), "SvgTextTool");
QByteArray ba = saveState();
g.writeEntry("windowState", ba.toBase64());
+ ba = saveGeometry();
+ g.writeEntry("Geometry", ba.toBase64());
}
void SvgTextEditor::setShape(KoSvgTextShape *shape)
{
m_shape = shape;
if (m_shape) {
KoSvgTextShapeMarkupConverter converter(m_shape);
QString svg;
QString styles;
QTextDocument *doc = m_textEditorWidget.richTextEdit->document();
if (converter.convertToSvg(&svg, &styles)) {
m_textEditorWidget.svgTextEdit->setPlainText(svg);
m_textEditorWidget.svgStylesEdit->setPlainText(styles);
m_textEditorWidget.svgTextEdit->document()->setModified(false);
if (shape->isRichTextPreferred() &&
converter.convertSvgToDocument(svg, doc)) {
m_textEditorWidget.richTextEdit->setDocument(doc);
KisSignalsBlocker b(m_textEditorWidget.textTab);
m_textEditorWidget.textTab->setCurrentIndex(Richtext);
doc->clearUndoRedoStacks();
switchTextEditorTab(false);
} else {
KisSignalsBlocker b(m_textEditorWidget.textTab);
m_textEditorWidget.textTab->setCurrentIndex(SvgSource);
switchTextEditorTab(false);
}
}
else {
QMessageBox::warning(this, i18n("Conversion failed"), "Could not get svg text from the shape:\n" + converter.errors().join('\n') + "\n" + converter.warnings().join('\n'));
}
}
KisFontComboBoxes* fontComboBox = qobject_cast<KisFontComboBoxes*>(qobject_cast<QWidgetAction*>(actionCollection()->action("svg_font"))->defaultWidget());
fontComboBox->setInitialized();
}
void SvgTextEditor::save()
{
if (m_shape) {
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QString svg;
QString styles = m_textEditorWidget.svgStylesEdit->document()->toPlainText();
KoSvgTextShapeMarkupConverter converter(m_shape);
if (!converter.convertDocumentToSvg(m_textEditorWidget.richTextEdit->document(), &svg)) {
qWarning()<<"new converter doesn't work!";
}
m_textEditorWidget.richTextEdit->document()->setModified(false);
emit textUpdated(m_shape, svg, styles, true);
}
else {
emit textUpdated(m_shape, m_textEditorWidget.svgTextEdit->document()->toPlainText(), m_textEditorWidget.svgStylesEdit->document()->toPlainText(), false);
m_textEditorWidget.svgTextEdit->document()->setModified(false);
}
}
}
void SvgTextEditor::switchTextEditorTab(bool convertData)
{
KoSvgTextShape shape;
KoSvgTextShapeMarkupConverter converter(&shape);
if (m_currentEditor) {
disconnect(m_currentEditor->document(), SIGNAL(modificationChanged(bool)), this, SLOT(setModified(bool)));
}
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
//first, make buttons checkable
enableRichTextActions(true);
enableSvgTextActions(false);
//then connect the cursor change to the checkformat();
connect(m_textEditorWidget.richTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(checkFormat()));
connect(m_textEditorWidget.richTextEdit, SIGNAL(textChanged()), this, SLOT(slotFixUpEmptyTextBlock()));
checkFormat();
if (m_shape && convertData) {
QTextDocument *doc = m_textEditorWidget.richTextEdit->document();
if (!converter.convertSvgToDocument(m_textEditorWidget.svgTextEdit->document()->toPlainText(), doc)) {
qWarning()<<"new converter svgToDoc doesn't work!";
}
m_textEditorWidget.richTextEdit->setDocument(doc);
doc->clearUndoRedoStacks();
}
m_currentEditor = m_textEditorWidget.richTextEdit;
}
else {
//first, make buttons uncheckable
enableRichTextActions(false);
enableSvgTextActions(true);
disconnect(m_textEditorWidget.richTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(checkFormat()));
// Convert the rich text to svg and styles strings
if (m_shape && convertData) {
QString svg;
QString styles;
if (!converter.convertDocumentToSvg(m_textEditorWidget.richTextEdit->document(), &svg)) {
qWarning()<<"new converter docToSVG doesn't work!";
}
m_textEditorWidget.svgTextEdit->setPlainText(svg);
}
m_currentEditor = m_textEditorWidget.svgTextEdit;
}
connect(m_currentEditor->document(), SIGNAL(modificationChanged(bool)), SLOT(setModified(bool)));
}
void SvgTextEditor::checkFormat()
{
QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat();
QTextBlockFormat blockFormat = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
// checkboxes do not emit signals on manual switching, so we
// can avoid blocking them
if (format.fontWeight() > QFont::Normal) {
actionCollection()->action("svg_weight_bold")->setChecked(true);
} else {
actionCollection()->action("svg_weight_bold")->setChecked(false);
}
actionCollection()->action("svg_format_italic")->setChecked(format.fontItalic());
actionCollection()->action("svg_format_underline")->setChecked(format.fontUnderline());
actionCollection()->action("svg_format_strike_through")->setChecked(format.fontStrikeOut());
{
FontSizeAction *fontSizeAction = qobject_cast<FontSizeAction*>(actionCollection()->action("svg_font_size"));
KisSignalsBlocker b(fontSizeAction);
fontSizeAction->setFontSize(format.font().pointSize());
}
{
KoColor fg(format.foreground().color(), KoColorSpaceRegistry::instance()->rgb8());
KoColorPopupAction *fgColorPopup = qobject_cast<KoColorPopupAction*>(actionCollection()->action("svg_format_textcolor"));
KisSignalsBlocker b(fgColorPopup);
fgColorPopup->setCurrentColor(fg);
}
{
KoColor bg(format.foreground().color(), KoColorSpaceRegistry::instance()->rgb8());
KoColorPopupAction *bgColorPopup = qobject_cast<KoColorPopupAction*>(actionCollection()->action("svg_background_color"));
KisSignalsBlocker b(bgColorPopup);
bgColorPopup->setCurrentColor(bg);
}
{
KisFontComboBoxes* fontComboBox = qobject_cast<KisFontComboBoxes*>(qobject_cast<QWidgetAction*>(actionCollection()->action("svg_font"))->defaultWidget());
KisSignalsBlocker b(fontComboBox);
fontComboBox->setCurrentFont(format.font());
}
{
QDoubleSpinBox *spnLineHeight = qobject_cast<QDoubleSpinBox*>(qobject_cast<QWidgetAction*>(actionCollection()->action("svg_line_height"))->defaultWidget());
KisSignalsBlocker b(spnLineHeight);
if (blockFormat.lineHeightType() == QTextBlockFormat::SingleHeight) {
spnLineHeight->setValue(100.0);
} else if(blockFormat.lineHeightType() == QTextBlockFormat::ProportionalHeight) {
spnLineHeight->setValue(double(blockFormat.lineHeight()));
}
}
{
QDoubleSpinBox* spnLetterSpacing = qobject_cast<QDoubleSpinBox*>(qobject_cast<QWidgetAction*>(actionCollection()->action("svg_letter_spacing"))->defaultWidget());
KisSignalsBlocker b(spnLetterSpacing);
spnLetterSpacing->setValue(format.fontLetterSpacing());
}
}
void SvgTextEditor::slotFixUpEmptyTextBlock()
{
if (m_textEditorWidget.richTextEdit->document()->isEmpty()) {
QTextCursor cursor = m_textEditorWidget.richTextEdit->textCursor();
QTextCharFormat format = cursor.blockCharFormat();
{
FontSizeAction *fontSizeAction = qobject_cast<FontSizeAction*>(actionCollection()->action("svg_font_size"));
KisFontComboBoxes* fontComboBox = qobject_cast<KisFontComboBoxes*>(qobject_cast<QWidgetAction*>(actionCollection()->action("svg_font"))->defaultWidget());
format.setFont(fontComboBox->currentFont(fontSizeAction->fontSize()));
}
{
KoColorPopupAction *fgColorPopup = qobject_cast<KoColorPopupAction*>(actionCollection()->action("svg_format_textcolor"));
format.setForeground(fgColorPopup->currentColor());
}
{
//KoColorPopupAction *bgColorPopup = qobject_cast<KoColorPopupAction*>(actionCollection()->action("svg_background_color"));
//format.setBackground(bgColorPopup->currentColor());
}
KisSignalsBlocker b(m_textEditorWidget.richTextEdit);
cursor.setBlockCharFormat(format);
}
}
void SvgTextEditor::undo()
{
m_currentEditor->undo();
}
void SvgTextEditor::redo()
{
m_currentEditor->redo();
}
void SvgTextEditor::cut()
{
m_currentEditor->cut();
}
void SvgTextEditor::copy()
{
m_currentEditor->copy();
}
void SvgTextEditor::paste()
{
m_currentEditor->paste();
}
void SvgTextEditor::selectAll()
{
m_currentEditor->selectAll();
}
void SvgTextEditor::deselect()
{
QTextCursor cursor(m_currentEditor->textCursor());
cursor.clearSelection();
m_currentEditor->setTextCursor(cursor);
}
void SvgTextEditor::find()
{
QDialog *findDialog = new QDialog(this);
findDialog->setWindowTitle(i18n("Find Text"));
QFormLayout *layout = new QFormLayout();
findDialog->setLayout(layout);
QLineEdit *lnSearchKey = new QLineEdit();
layout->addRow(i18n("Find:"), lnSearchKey);
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
findDialog->layout()->addWidget(buttons);
connect(buttons, SIGNAL(accepted()), findDialog, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), findDialog, SLOT(reject()));
if (findDialog->exec()==QDialog::Accepted) {
m_searchKey = lnSearchKey->text();
m_currentEditor->find(m_searchKey);
}
}
void SvgTextEditor::findNext()
{
if (!m_currentEditor->find(m_searchKey)) {
QTextCursor cursor(m_currentEditor->textCursor());
cursor.movePosition(QTextCursor::Start);
m_currentEditor->setTextCursor(cursor);
m_currentEditor->find(m_searchKey);
}
}
void SvgTextEditor::findPrev()
{
if (!m_currentEditor->find(m_searchKey,QTextDocument::FindBackward)) {
QTextCursor cursor(m_currentEditor->textCursor());
cursor.movePosition(QTextCursor::End);
m_currentEditor->setTextCursor(cursor);
m_currentEditor->find(m_searchKey,QTextDocument::FindBackward);
}
}
void SvgTextEditor::replace()
{
QDialog *findDialog = new QDialog(this);
findDialog->setWindowTitle(i18n("Find and Replace all"));
QFormLayout *layout = new QFormLayout();
findDialog->setLayout(layout);
QLineEdit *lnSearchKey = new QLineEdit();
QLineEdit *lnReplaceKey = new QLineEdit();
layout->addRow(i18n("Find:"), lnSearchKey);
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
layout->addRow(i18n("Replace:"), lnReplaceKey);
findDialog->layout()->addWidget(buttons);
connect(buttons, SIGNAL(accepted()), findDialog, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), findDialog, SLOT(reject()));
if (findDialog->exec()==QDialog::Accepted) {
QString search = lnSearchKey->text();
QString replace = lnReplaceKey->text();
QTextCursor cursor(m_currentEditor->textCursor());
cursor.movePosition(QTextCursor::Start);
m_currentEditor->setTextCursor(cursor);
while(m_currentEditor->find(search)) {
m_currentEditor->textCursor().removeSelectedText();
m_currentEditor->textCursor().insertText(replace);
}
}
}
void SvgTextEditor::zoomOut()
{
m_currentEditor->zoomOut();
}
void SvgTextEditor::zoomIn()
{
m_currentEditor->zoomIn();
}
#ifndef Q_OS_WIN
void SvgTextEditor::showInsertSpecialCharacterDialog()
{
m_charSelectDialog->setVisible(!m_charSelectDialog->isVisible());
}
void SvgTextEditor::insertCharacter(const QChar &c)
{
m_currentEditor->textCursor().insertText(QString(c));
}
#endif
void SvgTextEditor::setTextBold(QFont::Weight weight)
{
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCharFormat format;
QTextCursor oldCursor = setTextSelection();
if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() > QFont::Normal && weight==QFont::Bold) {
format.setFontWeight(QFont::Normal);
} else {
format.setFontWeight(weight);
}
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
} else {
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"font-weight:700;\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::setTextWeightLight()
{
if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() < QFont::Normal) {
setTextBold(QFont::Normal);
} else {
setTextBold(QFont::Light);
}
}
void SvgTextEditor::setTextWeightNormal()
{
setTextBold(QFont::Normal);
}
void SvgTextEditor::setTextWeightDemi()
{
if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() != QFont::Normal) {
setTextBold(QFont::Normal);
} else {
setTextBold(QFont::DemiBold);
}
}
void SvgTextEditor::setTextWeightBlack()
{
if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight()>QFont::Normal) {
setTextBold(QFont::Normal);
} else {
setTextBold(QFont::Black);
}
}
void SvgTextEditor::setTextItalic(QFont::Style style)
{
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
QString fontStyle = "inherit";
if (style == QFont::StyleItalic) {
fontStyle = "italic";
} else if(style == QFont::StyleOblique) {
fontStyle = "oblique";
}
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCharFormat format;
QTextCursor origCursor = setTextSelection();
format.setFontItalic(!m_textEditorWidget.richTextEdit->textCursor().charFormat().fontItalic());
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(origCursor);
}
else {
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"font-style:"+fontStyle+";\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::setTextDecoration(KoSvgText::TextDecoration decor)
{
QTextCursor cursor = setTextSelection();
QTextCharFormat currentFormat = m_textEditorWidget.richTextEdit->textCursor().charFormat();
QTextCharFormat format;
QString textDecoration = "inherit";
if (decor == KoSvgText::DecorationUnderline) {
textDecoration = "underline";
if (currentFormat.fontUnderline()) {
format.setFontUnderline(false);
}
else {
format.setFontUnderline(true);
}
format.setFontOverline(false);
format.setFontStrikeOut(false);
}
else if (decor == KoSvgText::DecorationLineThrough) {
textDecoration = "line-through";
format.setFontUnderline(false);
format.setFontOverline(false);
if (currentFormat.fontStrikeOut()) {
format.setFontStrikeOut(false);
}
else {
format.setFontStrikeOut(true);
}
}
else if (decor == KoSvgText::DecorationOverline) {
textDecoration = "overline";
format.setFontUnderline(false);
if (currentFormat.fontOverline()) {
format.setFontOverline(false);
}
else {
format.setFontOverline(true);
}
format.setFontStrikeOut(false);
}
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
}
else {
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"text-decoration:" + textDecoration + ";\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
m_textEditorWidget.richTextEdit->setTextCursor(cursor);
}
void SvgTextEditor::setTextUnderline()
{
setTextDecoration(KoSvgText::DecorationUnderline);
}
void SvgTextEditor::setTextOverline()
{
setTextDecoration(KoSvgText::DecorationOverline);
}
void SvgTextEditor::setTextStrikethrough()
{
setTextDecoration(KoSvgText::DecorationLineThrough);
}
void SvgTextEditor::setTextSubscript()
{
QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat();
if (format.verticalAlignment()==QTextCharFormat::AlignSubScript) {
format.setVerticalAlignment(QTextCharFormat::AlignNormal);
} else {
format.setVerticalAlignment(QTextCharFormat::AlignSubScript);
}
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
}
void SvgTextEditor::setTextSuperScript()
{
QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat();
if (format.verticalAlignment()==QTextCharFormat::AlignSuperScript) {
format.setVerticalAlignment(QTextCharFormat::AlignNormal);
} else {
format.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
}
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
}
void SvgTextEditor::increaseTextSize()
{
QTextCursor oldCursor = setTextSelection();
QTextCharFormat format;
int pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pointSize();
if (pointSize<0) {
pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pixelSize();
}
format.setFontPointSize(pointSize+1.0);
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::decreaseTextSize()
{
QTextCursor oldCursor = setTextSelection();
QTextCharFormat format;
int pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pointSize();
if (pointSize<1) {
pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pixelSize();
}
format.setFontPointSize(qMax(pointSize-1.0, 1.0));
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::setLineHeight(double lineHeightPercentage)
{
QTextCursor oldCursor = setTextSelection();
QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
format.setLineHeight(lineHeightPercentage, QTextBlockFormat::ProportionalHeight);
m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::setLetterSpacing(double letterSpacing)
{
QTextCursor cursor = setTextSelection();
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCharFormat format;
format.setFontLetterSpacingType(QFont::AbsoluteSpacing);
format.setFontLetterSpacing(letterSpacing);
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(cursor);
}
else {
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"letter-spacing:" + QString::number(letterSpacing) + "\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::alignLeft()
{
QTextCursor oldCursor = setTextSelection();
QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
format.setAlignment(Qt::AlignLeft);
m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::alignRight()
{
QTextCursor oldCursor = setTextSelection();
QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
format.setAlignment(Qt::AlignRight);
m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::alignCenter()
{
QTextCursor oldCursor = setTextSelection();
QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
format.setAlignment(Qt::AlignCenter);
m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::alignJustified()
{
QTextCursor oldCursor = setTextSelection();
QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat();
format.setAlignment(Qt::AlignJustify);
m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
void SvgTextEditor::setSettings()
{
KoDialog settingsDialog(this);
Ui_WdgSvgTextSettings textSettings;
QWidget *settingsPage = new QWidget(&settingsDialog, 0);
settingsDialog.setMainWidget(settingsPage);
textSettings.setupUi(settingsPage);
// get the settings and initialize the dialog
KConfigGroup cfg(KSharedConfig::openConfig(), "SvgTextTool");
QStringList selectedWritingSystems = cfg.readEntry("selectedWritingSystems", "").split(",");
QList<QFontDatabase::WritingSystem> scripts = QFontDatabase().writingSystems();
QStandardItemModel *writingSystemsModel = new QStandardItemModel(&settingsDialog);
for (int s = 0; s < scripts.size(); s ++) {
QString writingSystem = QFontDatabase().writingSystemName(scripts.at(s));
QStandardItem *script = new QStandardItem(writingSystem);
script->setCheckable(true);
script->setCheckState(selectedWritingSystems.contains(QString::number(scripts.at(s))) ? Qt::Checked : Qt::Unchecked);
script->setData((int)scripts.at(s));
writingSystemsModel->appendRow(script);
}
textSettings.lwScripts->setModel(writingSystemsModel);
EditorMode mode = (EditorMode)cfg.readEntry("EditorMode", (int)Both);
switch(mode) {
case(RichText):
textSettings.radioRichText->setChecked(true);
break;
case(SvgSource):
textSettings.radioSvgSource->setChecked(true);
break;
case(Both):
textSettings.radioBoth->setChecked(true);
}
QColor background = cfg.readEntry("colorEditorBackground", qApp->palette().window().color());
textSettings.colorEditorBackground->setColor(background);
textSettings.colorEditorForeground->setColor(cfg.readEntry("colorEditorForeground", qApp->palette().text().color()));
textSettings.colorKeyword->setColor(cfg.readEntry("colorKeyword", QColor(background.value() < 100 ? Qt::cyan : Qt::blue)));
textSettings.chkBoldKeyword->setChecked(cfg.readEntry("BoldKeyword", true));
textSettings.chkItalicKeyword->setChecked(cfg.readEntry("ItalicKeyword", false));
textSettings.colorElement->setColor(cfg.readEntry("colorElement", QColor(background.value() < 100 ? Qt::magenta : Qt::darkMagenta)));
textSettings.chkBoldElement->setChecked(cfg.readEntry("BoldElement", true));
textSettings.chkItalicElement->setChecked(cfg.readEntry("ItalicElement", false));
textSettings.colorAttribute->setColor(cfg.readEntry("colorAttribute", QColor(background.value() < 100 ? Qt::green : Qt::darkGreen)));
textSettings.chkBoldAttribute->setChecked(cfg.readEntry("BoldAttribute", true));
textSettings.chkItalicAttribute->setChecked(cfg.readEntry("ItalicAttribute", true));
textSettings.colorValue->setColor(cfg.readEntry("colorValue", QColor(background.value() < 100 ? Qt::red: Qt::darkRed)));
textSettings.chkBoldValue->setChecked(cfg.readEntry("BoldValue", true));
textSettings.chkItalicValue->setChecked(cfg.readEntry("ItalicValue", false));
textSettings.colorComment->setColor(cfg.readEntry("colorComment", QColor(background.value() < 100 ? Qt::lightGray : Qt::gray)));
textSettings.chkBoldComment->setChecked(cfg.readEntry("BoldComment", false));
textSettings.chkItalicComment->setChecked(cfg.readEntry("ItalicComment", false));
settingsDialog.setButtons(KoDialog::Ok | KoDialog::Cancel);
if (settingsDialog.exec() == QDialog::Accepted) {
// save and set the settings
QStringList writingSystems;
for (int i = 0; i < writingSystemsModel->rowCount(); i++) {
QStandardItem *item = writingSystemsModel->item(i);
if (item->checkState() == Qt::Checked) {
writingSystems.append(QString::number(item->data().toInt()));
}
}
cfg.writeEntry("selectedWritingSystems", writingSystems.join(','));
if (textSettings.radioRichText->isChecked()) {
cfg.writeEntry("EditorMode", (int)Richtext);
}
else if (textSettings.radioSvgSource->isChecked()) {
cfg.writeEntry("EditorMode", (int)SvgSource);
}
else if (textSettings.radioBoth->isChecked()) {
cfg.writeEntry("EditorMode", (int)Both);
}
cfg.writeEntry("colorEditorBackground", textSettings.colorEditorBackground->color());
cfg.writeEntry("colorEditorForeground", textSettings.colorEditorForeground->color());
cfg.writeEntry("colorKeyword", textSettings.colorKeyword->color());
cfg.writeEntry("BoldKeyword", textSettings.chkBoldKeyword->isChecked());
cfg.writeEntry("ItalicKeyWord", textSettings.chkItalicKeyword->isChecked());
cfg.writeEntry("colorElement", textSettings.colorElement->color());
cfg.writeEntry("BoldElement", textSettings.chkBoldElement->isChecked());
cfg.writeEntry("ItalicElement", textSettings.chkItalicElement->isChecked());
cfg.writeEntry("colorAttribute", textSettings.colorAttribute->color());
cfg.writeEntry("BoldAttribute", textSettings.chkBoldAttribute->isChecked());
cfg.writeEntry("ItalicAttribute", textSettings.chkItalicAttribute->isChecked());
cfg.writeEntry("colorValue", textSettings.colorValue->color());
cfg.writeEntry("BoldValue", textSettings.chkBoldValue->isChecked());
cfg.writeEntry("ItalicValue", textSettings.chkItalicValue->isChecked());
cfg.writeEntry("colorComment", textSettings.colorComment->color());
cfg.writeEntry("BoldComment", textSettings.chkBoldComment->isChecked());
cfg.writeEntry("ItalicComment", textSettings.chkItalicComment->isChecked());
applySettings();
}
}
void SvgTextEditor::slotToolbarToggled(bool)
{
}
void SvgTextEditor::setFontColor(const KoColor &c)
{
QColor color = c.toQColor();
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCursor oldCursor = setTextSelection();
QTextCharFormat format;
format.setForeground(QBrush(color));
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
}
else {
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan fill=\""+color.name()+"\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::setBackgroundColor(const KoColor &c)
{
QColor color = c.toQColor();
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan stroke=\""+color.name()+"\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
void SvgTextEditor::setModified(bool modified)
{
if (modified) {
m_textEditorWidget.buttons->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Discard);
}
else {
m_textEditorWidget.buttons->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Close);
}
}
void SvgTextEditor::dialogButtonClicked(QAbstractButton *button)
{
if (m_textEditorWidget.buttons->standardButton(button) == QDialogButtonBox::Discard) {
if (QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("You have modified the text. Discard changes?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
close();
}
}
}
void SvgTextEditor::setFont(const QString &fontName)
{
QFont font;
font.fromString(fontName);
QTextCharFormat curFormat = m_textEditorWidget.richTextEdit->textCursor().charFormat();
font.setPointSize(curFormat.font().pointSize());
QTextCharFormat format;
//This disables the style being set from the font-comboboxes too, so we need to rethink how we use that.
format.setFontFamily(font.family());
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCursor oldCursor = setTextSelection();
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
} else {
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"font-family:"+font.family()+" "+font.styleName()+";\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::setFontSize(qreal fontSize)
{
if (m_textEditorWidget.textTab->currentIndex() == Richtext) {
QTextCursor oldCursor = setTextSelection();
QTextCharFormat format;
format.setFontPointSize(fontSize);
m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format);
m_textEditorWidget.richTextEdit->setTextCursor(oldCursor);
} else {
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"font-size:" + QString::number(fontSize) + ";\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
}
void SvgTextEditor::setBaseline(KoSvgText::BaselineShiftMode)
{
QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor();
if (cursor.hasSelection()) {
QString selectionModified = "<tspan style=\"font-size:50%;baseline-shift:super;\">" + cursor.selectedText() + "</tspan>";
cursor.removeSelectedText();
cursor.insertText(selectionModified);
}
}
void SvgTextEditor::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::ControlModifier) {
int numDegrees = event->delta() / 8;
int numSteps = numDegrees / 7;
m_textEditorWidget.svgTextEdit->zoomOut(numSteps);
event->accept();
}
}
QTextCursor SvgTextEditor::setTextSelection()
{
QTextCursor orignalCursor(m_textEditorWidget.richTextEdit->textCursor());
if (!orignalCursor.hasSelection()){
m_textEditorWidget.richTextEdit->selectAll();
}
return orignalCursor;
}
void SvgTextEditor::applySettings()
{
KConfigGroup cfg(KSharedConfig::openConfig(), "SvgTextTool");
EditorMode mode = (EditorMode)cfg.readEntry("EditorMode", (int)Both);
QWidget *richTab = m_textEditorWidget.richTab;
QWidget *svgTab = m_textEditorWidget.svgTab;
m_page->setUpdatesEnabled(false);
m_textEditorWidget.textTab->clear();
switch(mode) {
case(RichText):
m_textEditorWidget.textTab->addTab(richTab, i18n("Rich text"));
break;
case(SvgSource):
m_textEditorWidget.textTab->addTab(svgTab, i18n("SVG Source"));
break;
case(Both):
m_textEditorWidget.textTab->addTab(richTab, i18n("Rich text"));
m_textEditorWidget.textTab->addTab(svgTab, i18n("SVG Source"));
}
m_syntaxHighlighter->setFormats();
QPalette palette = m_textEditorWidget.svgTextEdit->palette();
QColor background = cfg.readEntry("colorEditorBackground", qApp->palette().window().color());
palette.setBrush(QPalette::Active, QPalette::Background, QBrush(background));
m_textEditorWidget.richTextEdit->setStyleSheet(QString("background-color:%1").arg(background.name()));
m_textEditorWidget.svgStylesEdit->setStyleSheet(QString("background-color:%1").arg(background.name()));
m_textEditorWidget.svgTextEdit->setStyleSheet(QString("background-color:%1").arg(background.name()));
QColor foreground = cfg.readEntry("colorEditorForeground", qApp->palette().text().color());
palette.setBrush(QPalette::Active, QPalette::Text, QBrush(foreground));
QStringList selectedWritingSystems = cfg.readEntry("selectedWritingSystems", "").split(",");
QVector<QFontDatabase::WritingSystem> writingSystems;
for (int i=0; i<selectedWritingSystems.size(); i++) {
writingSystems.append((QFontDatabase::WritingSystem)QString(selectedWritingSystems.at(i)).toInt());
}
{
FontSizeAction *fontSizeAction = qobject_cast<FontSizeAction*>(actionCollection()->action("svg_font_size"));
KisFontComboBoxes* fontComboBox = qobject_cast<KisFontComboBoxes*>(qobject_cast<QWidgetAction*>(actionCollection()->action("svg_font"))->defaultWidget());
const QFont oldFont = fontComboBox->currentFont(fontSizeAction->fontSize());
fontComboBox->refillComboBox(writingSystems);
fontComboBox->setCurrentFont(oldFont);
}
m_page->setUpdatesEnabled(true);
}
QAction *SvgTextEditor::createAction(const QString &name, const char *member)
{
QAction *action = new QAction(this);
KisActionRegistry *actionRegistry = KisActionRegistry::instance();
actionRegistry->propertizeAction(name, action);
actionCollection()->addAction(name, action);
QObject::connect(action, SIGNAL(triggered(bool)), this, member);
return action;
}
void SvgTextEditor::createActions()
{
KisActionRegistry *actionRegistry = KisActionRegistry::instance();
// File: new, open, save, save as, close
KStandardAction::save(this, SLOT(save()), actionCollection());
KStandardAction::close(this, SLOT(slotCloseEditor()), actionCollection());
// Edit
KStandardAction::undo(this, SLOT(undo()), actionCollection());
KStandardAction::redo(this, SLOT(redo()), actionCollection());
KStandardAction::cut(this, SLOT(cut()), actionCollection());
KStandardAction::copy(this, SLOT(copy()), actionCollection());
KStandardAction::paste(this, SLOT(paste()), actionCollection());
KStandardAction::selectAll(this, SLOT(selectAll()), actionCollection());
KStandardAction::deselect(this, SLOT(deselect()), actionCollection());
KStandardAction::find(this, SLOT(find()), actionCollection());
KStandardAction::findNext(this, SLOT(findNext()), actionCollection());
KStandardAction::findPrev(this, SLOT(findPrev()), actionCollection());
KStandardAction::replace(this, SLOT(replace()), actionCollection());
// View
// WISH: we cannot zoom-in/out in rech-text mode
m_svgTextActions << KStandardAction::zoomOut(this, SLOT(zoomOut()), actionCollection());
m_svgTextActions << KStandardAction::zoomIn(this, SLOT(zoomIn()), actionCollection());
#ifndef Q_OS_WIN
// Insert:
QAction * insertAction = createAction("svg_insert_special_character",
SLOT(showInsertSpecialCharacterDialog()));
insertAction->setCheckable(true);
insertAction->setChecked(false);
#endif
// Format:
m_richTextActions << createAction("svg_weight_bold",
SLOT(setTextBold()));
m_richTextActions << createAction("svg_format_italic",
SLOT(setTextItalic()));
m_richTextActions << createAction("svg_format_underline",
SLOT(setTextUnderline()));
m_richTextActions << createAction("svg_format_strike_through",
SLOT(setTextStrikethrough()));
m_richTextActions << createAction("svg_format_superscript",
SLOT(setTextSuperScript()));
m_richTextActions << createAction("svg_format_subscript",
SLOT(setTextSubscript()));
m_richTextActions << createAction("svg_weight_light",
SLOT(setTextWeightLight()));
m_richTextActions << createAction("svg_weight_normal",
SLOT(setTextWeightNormal()));
m_richTextActions << createAction("svg_weight_demi",
SLOT(setTextWeightDemi()));
m_richTextActions << createAction("svg_weight_black",
SLOT(setTextWeightBlack()));
m_richTextActions << createAction("svg_increase_font_size",
SLOT(increaseTextSize()));
m_richTextActions << createAction("svg_decrease_font_size",
SLOT(decreaseTextSize()));
m_richTextActions << createAction("svg_align_left",
SLOT(alignLeft()));
m_richTextActions << createAction("svg_align_right",
SLOT(alignRight()));
m_richTextActions << createAction("svg_align_center",
SLOT(alignCenter()));
// m_richTextActions << createAction("svg_align_justified",
// SLOT(alignJustified()));
// Settings
m_richTextActions << createAction("svg_settings",
SLOT(setSettings()));
QWidgetAction *fontComboAction = new QWidgetAction(this);
fontComboAction->setToolTip(i18n("Font"));
KisFontComboBoxes *fontCombo = new KisFontComboBoxes();
connect(fontCombo, SIGNAL(fontChanged(QString)), SLOT(setFont(QString)));
fontComboAction->setDefaultWidget(fontCombo);
actionCollection()->addAction("svg_font", fontComboAction);
m_richTextActions << fontComboAction;
actionRegistry->propertizeAction("svg_font", fontComboAction);
QWidgetAction *fontSizeAction = new FontSizeAction(this);
fontSizeAction->setToolTip(i18n("Size"));
connect(fontSizeAction, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal)));
actionCollection()->addAction("svg_font_size", fontSizeAction);
m_richTextActions << fontSizeAction;
actionRegistry->propertizeAction("svg_font_size", fontSizeAction);
KoColorPopupAction *fgColor = new KoColorPopupAction(this);
fgColor->setCurrentColor(QColor(Qt::black));
fgColor->setToolTip(i18n("Text Color"));
connect(fgColor, SIGNAL(colorChanged(KoColor)), SLOT(setFontColor(KoColor)));
actionCollection()->addAction("svg_format_textcolor", fgColor);
m_richTextActions << fgColor;
actionRegistry->propertizeAction("svg_format_textcolor", fgColor);
KoColorPopupAction *bgColor = new KoColorPopupAction(this);
bgColor->setCurrentColor(QColor(Qt::white));
bgColor->setToolTip(i18n("Background Color"));
connect(bgColor, SIGNAL(colorChanged(KoColor)), SLOT(setBackgroundColor(KoColor)));
actionCollection()->addAction("svg_background_color", bgColor);
actionRegistry->propertizeAction("svg_background_color", bgColor);
m_richTextActions << bgColor;
QWidgetAction *colorPickerAction = new QWidgetAction(this);
colorPickerAction->setToolTip(i18n("Pick a Color"));
KisScreenColorPicker *colorPicker = new KisScreenColorPicker(false);
connect(colorPicker, SIGNAL(sigNewColorPicked(KoColor)), fgColor, SLOT(setCurrentColor(KoColor)));
connect(colorPicker, SIGNAL(sigNewColorPicked(KoColor)), SLOT(setFontColor(KoColor)));
colorPickerAction->setDefaultWidget(colorPicker);
actionCollection()->addAction("svg_pick_color", colorPickerAction);
m_richTextActions << colorPickerAction;
actionRegistry->propertizeAction("svg_pick_color", colorPickerAction);
QWidgetAction *lineHeight = new QWidgetAction(this);
QDoubleSpinBox *spnLineHeight = new QDoubleSpinBox();
spnLineHeight->setToolTip(i18n("Line height"));
spnLineHeight->setRange(0.0, 1000.0);
spnLineHeight->setSingleStep(10.0);
spnLineHeight->setSuffix(i18n("%"));
connect(spnLineHeight, SIGNAL(valueChanged(double)), SLOT(setLineHeight(double)));
lineHeight->setDefaultWidget(spnLineHeight);
actionCollection()->addAction("svg_line_height", lineHeight);
m_richTextActions << lineHeight;
actionRegistry->propertizeAction("svg_line_height", lineHeight);
QWidgetAction *letterSpacing = new QWidgetAction(this);
QDoubleSpinBox *spnletterSpacing = new QDoubleSpinBox();
spnletterSpacing->setToolTip(i18n("Letter Spacing"));
spnletterSpacing->setRange(-20.0, 20.0);
spnletterSpacing->setSingleStep(0.5);
connect(spnletterSpacing, SIGNAL(valueChanged(double)), SLOT(setLetterSpacing(double)));
letterSpacing->setDefaultWidget(spnletterSpacing);
actionCollection()->addAction("svg_letter_spacing", letterSpacing);
m_richTextActions << letterSpacing;
actionRegistry->propertizeAction("svg_letter_spacing", letterSpacing);
}
void SvgTextEditor::enableRichTextActions(bool enable)
{
Q_FOREACH(QAction *action, m_richTextActions) {
action->setEnabled(enable);
}
}
void SvgTextEditor::enableSvgTextActions(bool enable)
{
Q_FOREACH(QAction *action, m_svgTextActions) {
action->setEnabled(enable);
}
}
void SvgTextEditor::slotCloseEditor()
{
close();
emit textEditorClosed();
}
diff --git a/plugins/tools/svgtexttool/SvgTextTool.cpp b/plugins/tools/svgtexttool/SvgTextTool.cpp
index b735ea7457..59838b4acd 100644
--- a/plugins/tools/svgtexttool/SvgTextTool.cpp
+++ b/plugins/tools/svgtexttool/SvgTextTool.cpp
@@ -1,435 +1,435 @@
/* This file is part of the KDE project
Copyright 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.
*/
#include "SvgTextTool.h"
#include "KoSvgTextShape.h"
#include "SvgTextChangeCommand.h"
#include <QLabel>
#include <QToolButton>
#include <QGridLayout>
#include <QVBoxLayout>
#include <QDesktopServices>
#include <QApplication>
#include <QGroupBox>
#include <QFontDatabase>
#include <QButtonGroup>
#include <klocalizedstring.h>
#include <KisPart.h>
#include <kis_canvas2.h>
#include <KSharedConfig>
#include "kis_assert.h"
#include <KoFileDialog.h>
#include <KoIcon.h>
#include <KoCanvasBase.h>
#include <KoImageCollection.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoShapeController.h>
#include <KoShapeRegistry.h>
#include <KoShapeFactoryBase.h>
#include <KoPointerEvent.h>
#include <KoProperties.h>
#include <KoSelectedShapesProxy.h>
#include "KoToolManager.h"
#include "KoCanvasResourceProvider.h"
#include "SvgTextEditor.h"
#include "KisHandlePainterHelper.h"
#include <commands/KoKeepShapesSelectedCommand.h>
SvgTextTool::SvgTextTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_editor(0)
, m_dragStart( 0, 0)
, m_dragEnd( 0, 0)
, m_dragging(false)
{
}
SvgTextTool::~SvgTextTool()
{
if(m_editor) {
m_editor->close();
}
}
void SvgTextTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
KoToolBase::activate(activation, shapes);
useCursor(Qt::ArrowCursor);
if (shapes.size() == 1) {
KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(*shapes.constBegin());
if (!textShape) {
koSelection()->deselectAll();
} else {
// if we are a text shape...and the proxy tells us we want to edit the shape. open the text editor
if (canvas()->selectedShapesProxy()->isRequestingToBeEdited()) {
showEditor();
}
}
} else if (shapes.size() > 1) {
KoSvgTextShape *foundTextShape = 0;
Q_FOREACH (KoShape *shape, shapes) {
KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shape);
if (textShape) {
foundTextShape = textShape;
break;
}
}
koSelection()->deselectAll();
if (foundTextShape) {
koSelection()->select(foundTextShape);
}
}
}
void SvgTextTool::deactivate()
{
KoToolBase::deactivate();
QRectF updateRect = m_hoveredShapeHighlightRect;
KoSvgTextShape *shape = selectedShape();
if (shape) {
updateRect |= shape->boundingRect();
}
m_hoveredShapeHighlightRect = QRectF();
canvas()->updateCanvas(updateRect);
}
QWidget *SvgTextTool::createOptionWidget()
{
QWidget *optionWidget = new QWidget();
QGridLayout *layout = new QGridLayout(optionWidget);
m_configGroup = KSharedConfig::openConfig()->group(toolId());
QGroupBox *defsOptions = new QGroupBox(i18n("Create new texts with..."));
QVBoxLayout *defOptionsLayout = new QVBoxLayout();
defsOptions->setLayout(defOptionsLayout);
m_defFont = new QFontComboBox();
QString storedFont = m_configGroup.readEntry<QString>("defaultFont", QApplication::font().family());
m_defFont->setCurrentFont(QFont(storedFont));
defsOptions->layout()->addWidget(m_defFont);
m_defPointSize = new QComboBox();
Q_FOREACH (int size, QFontDatabase::standardSizes()) {
m_defPointSize->addItem(QString::number(size)+" pt");
}
int storedSize = m_configGroup.readEntry<int>("defaultSize", QApplication::font().pointSize());
int sizeIndex = 0;
if (QFontDatabase::standardSizes().contains(storedSize)) {
sizeIndex = QFontDatabase::standardSizes().indexOf(storedSize);
}
m_defPointSize->setCurrentIndex(sizeIndex);
int checkedAlignment = m_configGroup.readEntry<int>("defaultAlignment", 0);
m_defAlignment = new QButtonGroup();
QHBoxLayout *alignButtons = new QHBoxLayout();
alignButtons->addWidget(m_defPointSize);
QToolButton *alignLeft = new QToolButton();
alignLeft->setIcon(KisIconUtils::loadIcon("format-justify-left"));
alignLeft->setCheckable(true);
alignLeft->setToolTip(i18n("Anchor text to the left."));
m_defAlignment->addButton(alignLeft, 0);
alignButtons->addWidget(alignLeft);
QToolButton *alignCenter = new QToolButton();
alignCenter->setIcon(KisIconUtils::loadIcon("format-justify-center"));
alignCenter->setCheckable(true);
m_defAlignment->addButton(alignCenter, 1);
alignCenter->setToolTip(i18n("Anchor text to the middle."));
alignButtons->addWidget(alignCenter);
QToolButton *alignRight = new QToolButton();
alignRight->setIcon(KisIconUtils::loadIcon("format-justify-right"));
alignRight->setCheckable(true);
m_defAlignment->addButton(alignRight, 2);
alignRight->setToolTip(i18n("Anchor text to the right."));
alignButtons->addWidget(alignRight);
m_defAlignment->setExclusive(true);
if (checkedAlignment<1) {
alignLeft->setChecked(true);
} else if (checkedAlignment==1) {
alignCenter->setChecked(true);
} else if (checkedAlignment==2) {
alignRight->setChecked(true);
} else {
alignLeft->setChecked(true);
}
defOptionsLayout->addLayout(alignButtons);
layout->addWidget(defsOptions);
connect(m_defAlignment, SIGNAL(buttonClicked(int)), this, SLOT(storeDefaults()));
connect(m_defFont, SIGNAL(currentFontChanged(QFont)), this, SLOT(storeDefaults()));
connect(m_defPointSize, SIGNAL(currentIndexChanged(int)), this, SLOT(storeDefaults()));
m_edit = new QPushButton(optionWidget);
m_edit->setText(i18n("Edit Text"));
connect(m_edit, SIGNAL(clicked(bool)), SLOT(showEditor()));
layout->addWidget(m_edit);
return optionWidget;
}
KoSelection *SvgTextTool::koSelection() const
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas(), 0);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas()->selectedShapesProxy(), 0);
return canvas()->selectedShapesProxy()->selection();
}
KoSvgTextShape *SvgTextTool::selectedShape() const
{
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas(), 0);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas()->selectedShapesProxy(), 0);
QList<KoShape*> shapes = koSelection()->selectedEditableShapes();
if (shapes.isEmpty()) return 0;
KIS_SAFE_ASSERT_RECOVER_NOOP(shapes.size() == 1);
KoSvgTextShape *textShape = dynamic_cast<KoSvgTextShape*>(shapes.first());
return textShape;
}
void SvgTextTool::showEditor()
{
KoSvgTextShape *shape = selectedShape();
if (!shape) return;
if (!m_editor) {
m_editor = new SvgTextEditor(QApplication::activeWindow());
m_editor->setWindowTitle(i18nc("@title:window", "Krita - Edit Text"));
m_editor->setWindowModality(Qt::ApplicationModal);
m_editor->setAttribute( Qt::WA_QuitOnClose, false );
connect(m_editor, SIGNAL(textUpdated(KoSvgTextShape*,QString,QString,bool)), SLOT(textUpdated(KoSvgTextShape*,QString,QString,bool)));
connect(m_editor, SIGNAL(textEditorClosed()), SLOT(slotTextEditorClosed()));
m_editor->activateWindow(); // raise on creation only
}
m_editor->setShape(shape);
m_editor->show();
}
void SvgTextTool::textUpdated(KoSvgTextShape *shape, const QString &svg, const QString &defs, bool richTextUpdated)
{
SvgTextChangeCommand *cmd = new SvgTextChangeCommand(shape, svg, defs, richTextUpdated);
canvas()->addCommand(cmd);
}
void SvgTextTool::slotTextEditorClosed()
{
// change tools to the shape selection tool when we close the text editor to allow moving and further editing of the object.
// most of the time when we edit text, the shape selection tool is where we left off anyway
KoToolManager::instance()->switchToolRequested("InteractionTool");
}
QString SvgTextTool::generateDefs()
{
QString font = m_defFont->currentFont().family();
QString size = QString::number(QFontDatabase::standardSizes().at(m_defPointSize->currentIndex() > -1 ? m_defPointSize->currentIndex() : 0));
QString textAnchor = "middle";
if (m_defAlignment->button(0)->isChecked()) {
textAnchor = "start";
}
if (m_defAlignment->button(2)->isChecked()) {
textAnchor = "end";
}
QString fontColor = canvas()->resourceManager()->foregroundColor().toQColor().name();
return QString("<defs>\n <style>\n text {\n font-family:'%1';\n font-size:%2 ; fill:%3 ; text-anchor:%4;\n }\n </style>\n</defs>").arg(font, size, fontColor, textAnchor);
}
void SvgTextTool::storeDefaults()
{
m_configGroup = KSharedConfig::openConfig()->group(toolId());
m_configGroup.writeEntry("defaultFont", m_defFont->currentFont().family());
m_configGroup.writeEntry("defaultSize", QFontDatabase::standardSizes().at(m_defPointSize->currentIndex() > -1 ? m_defPointSize->currentIndex() : 0));
m_configGroup.writeEntry("defaultAlignment", m_defAlignment->checkedId());
}
void SvgTextTool::paint(QPainter &gc, const KoViewConverter &converter)
{
if (!isActivated()) return;
- KoShape::applyConversion(gc, converter);
+ gc.setTransform(converter.documentToView(), true);
KisHandlePainterHelper handlePainter(&gc);
if (m_dragging) {
QPolygonF poly(QRectF(m_dragStart, m_dragEnd));
handlePainter.setHandleStyle(KisHandleStyle::primarySelection());
handlePainter.drawRubberLine(poly);
}
KoSvgTextShape *shape = selectedShape();
if (shape) {
handlePainter.setHandleStyle(KisHandleStyle::primarySelection());
QPainterPath path;
path.addRect(shape->boundingRect());
handlePainter.drawPath(path);
}
if (!m_hoveredShapeHighlightRect.isEmpty()) {
handlePainter.setHandleStyle(KisHandleStyle::highlightedPrimaryHandlesWithSolidOutline());
QPainterPath path;
path.addRect(m_hoveredShapeHighlightRect);
handlePainter.drawPath(path);
}
}
void SvgTextTool::mousePressEvent(KoPointerEvent *event)
{
KoSvgTextShape *selectedShape = this->selectedShape();
KoSvgTextShape *hoveredShape = dynamic_cast<KoSvgTextShape *>(canvas()->shapeManager()->shapeAt(event->point));
if (!selectedShape || hoveredShape != selectedShape) {
canvas()->shapeManager()->selection()->deselectAll();
if (hoveredShape) {
canvas()->shapeManager()->selection()->select(hoveredShape);
} else {
m_dragStart = m_dragEnd = event->point;
m_dragging = true;
event->accept();
}
}
}
void SvgTextTool::mouseMoveEvent(KoPointerEvent *event)
{
QRectF updateRect = m_hoveredShapeHighlightRect;
if (m_dragging) {
m_dragEnd = event->point;
m_hoveredShapeHighlightRect = QRectF();
updateRect |= QRectF(m_dragStart, m_dragEnd).normalized().toAlignedRect();
event->accept();
} else {
KoSvgTextShape *hoveredShape = dynamic_cast<KoSvgTextShape *>(canvas()->shapeManager()->shapeAt(event->point));
if (hoveredShape) {
m_hoveredShapeHighlightRect = hoveredShape->boundingRect();
updateRect |= m_hoveredShapeHighlightRect;
} else {
m_hoveredShapeHighlightRect = QRect();
}
event->ignore();
}
if (!updateRect.isEmpty()) {
canvas()->updateCanvas(kisGrowRect(updateRect, 100));
}
}
void SvgTextTool::mouseReleaseEvent(KoPointerEvent *event)
{
if (m_dragging) {
QRectF rectangle = QRectF(m_dragStart, m_dragEnd).normalized();
if (rectangle.width() < 4 && rectangle.height() < 4) {
m_dragging = false;
event->accept();
return;
}
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoSvgTextShapeID");
KoProperties *params = new KoProperties();//Fill these with "svgText", "defs" and "shapeRect"
params->setProperty("defs", QVariant(generateDefs()));
if (m_dragging) {
m_dragEnd = event->point;
m_dragging = false;
//The following show only happen when we're creating preformatted text. If we're making
//Word-wrapped text, it should take the rectangle unmodified.
int size = QFontDatabase::standardSizes().at(m_defPointSize->currentIndex() > -1 ? m_defPointSize->currentIndex() : 0);
QFont font = m_defFont->currentFont();
font.setPointSize(size);
rectangle.setTop(rectangle.top()+QFontMetrics(font).lineSpacing());
if (m_defAlignment->button(1)->isChecked()) {
rectangle.setLeft(rectangle.center().x());
} else if (m_defAlignment->button(2)->isChecked()) {
qreal right = rectangle.right();
rectangle.setRight(right+10);
rectangle.setLeft(right);
}
params->setProperty("shapeRect", QVariant(rectangle));
}
KoShape *textShape = factory->createShape( params, canvas()->shapeController()->resourceManager());
KUndo2Command *parentCommand = new KUndo2Command();
new KoKeepShapesSelectedCommand(koSelection()->selectedShapes(), {}, canvas()->selectedShapesProxy(), false, parentCommand);
KUndo2Command *cmd = canvas()->shapeController()->addShape(textShape, 0, parentCommand);
parentCommand->setText(cmd->text());
new KoKeepShapesSelectedCommand({}, {textShape}, canvas()->selectedShapesProxy(), true, parentCommand);
canvas()->addCommand(parentCommand);
showEditor();
event->accept();
} else if (m_editor) {
showEditor();
event->accept();
}
}
void SvgTextTool::keyPressEvent(QKeyEvent *event)
{
if (event->key()==Qt::Key_Enter || event->key()==Qt::Key_Return) {
showEditor();
event->accept();
} else {
event->ignore();
}
}
void SvgTextTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
if (canvas()->shapeManager()->shapeAt(event->point) != selectedShape()) {
event->ignore(); // allow the event to be used by another
return;
}
showEditor();
if(m_editor) {
m_editor->raise();
m_editor->activateWindow();
}
event->accept();
}
diff --git a/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp b/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp
index 9fc7aae48f..b789d9094a 100644
--- a/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp
+++ b/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp
@@ -1,303 +1,306 @@
/* This file is part of the KDE project
*
* Copyright 2017 Wolthera van Hövell tot Westerflier <griffinvalley@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_font_family_combo_box.h"
#include <QFontDatabase>
#include <QFontComboBox>
#include <QHBoxLayout>
#include <QComboBox>
#include <QAbstractItemView>
#include <QScrollBar>
#include <QCompleter>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <QPainter>
#include <kis_config.h>
PinnedFontsSeparator::PinnedFontsSeparator(QAbstractItemDelegate *_default, QWidget *parent) :
QStyledItemDelegate(parent), m_separatorIndex(0), m_separatorAdded(false), m_defaultDelegate(_default)
{
}
void PinnedFontsSeparator::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (index.row() == m_separatorIndex && m_separatorAdded) {
QRect viewRect = option.rect;
painter->setPen(Qt::gray);
painter->drawLine((viewRect.topLeft() + viewRect.bottomLeft()) / 2 + QPoint(5, 0),
(viewRect.topRight() + viewRect.bottomRight()) / 2 - QPoint(5, 0));
} else {
m_defaultDelegate->paint(painter, option, index);
}
}
QSize PinnedFontsSeparator::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return QStyledItemDelegate::sizeHint(option, index) * 1.25;
}
void PinnedFontsSeparator::setSeparatorIndex(int index)
{
m_separatorIndex = index;
}
void PinnedFontsSeparator::setSeparatorAdded()
{
m_separatorAdded = true;
}
KisFontFamilyComboBox::KisFontFamilyComboBox(QWidget *parent)
: QComboBox(parent), m_initilized(false), m_initializeFromConfig(false)
{
setEditable(true);
completer()->setCompletionMode(QCompleter::InlineCompletion);
completer()->setCaseSensitivity(Qt::CaseInsensitive);
// The following are all helper fonts for LaTeX that no one but LaTeX would use
// but because many people use LaTeX, they do show up on quite a few systems.
m_blacklistedFonts << "bbold10" << "cmbsy10" << "cmmib10"
<< "cmss10" << "cmex10" << "cmmi10"
<< "cmr10" << "cmsy10" << "eufb10"
<< "eufm10" << "eurb10" << "eurm10"
<< "esint10" << "eufm10" << "eusb10"
<< "eusm10" << "lasy10" << "lasyb10"
<< "msam10" << "msbm10" << "rsfs10"
<< "stmary10" << "wasy10" << "wasyb10";
refillComboBox();
- QFontComboBox *temp = new QFontComboBox();
+ QFontComboBox *temp = new QFontComboBox(this);
m_fontSeparator = new PinnedFontsSeparator(temp->itemDelegate(), this);
temp->setEnabled(true);
temp->hide();
m_separatorIndex = 0;
m_pinnedFonts = KisConfig(true).readList("PinnedFonts", QStringList{});
}
void KisFontFamilyComboBox::refillComboBox(QVector<QFontDatabase::WritingSystem> writingSystems)
{
QFontDatabase fonts = QFontDatabase();
int maxWidth = 0;
this->clear();
QStringList duplicateFonts;
QStringList filteredFonts;
if (writingSystems.isEmpty()) {
writingSystems.append(QFontDatabase::Any);
}
for (int i = 0; i < writingSystems.size(); i++) {
Q_FOREACH (QString family, fonts.families(writingSystems.at(i))) {
// if it's a private family it shouldn't be added.
bool addFont = !fonts.isPrivateFamily(family);
if (addFont && filteredFonts.contains(family)) {
addFont = false;
}
if (addFont && duplicateFonts.contains(family)) {
addFont = false;
}
if (addFont && m_blacklistedFonts.contains(family)) {
addFont = false;
}
if (addFont && !fonts.isSmoothlyScalable(family)) {
addFont = false;
}
if (addFont) {
// now, check for all possible familyname+style name combinations, so we can avoid those.
Q_FOREACH (const QString style, fonts.styles(family)) {
duplicateFonts.append(family + " " + style);
duplicateFonts.append(family + "_" + style);
}
filteredFonts.append(family);
int width = 1.5 * view()->fontMetrics()
.width(family + " " + fonts.writingSystemSample(QFontDatabase::Any));
if (width > maxWidth) {
maxWidth = width;
}
}
}
}
this->addItems(filteredFonts);
if (this->count() > this->maxVisibleItems()) {
maxWidth += view()->style()->pixelMetric(QStyle::PixelMetric::PM_ScrollBarExtent);
}
view()->setMinimumWidth(maxWidth);
} // KisFontFamilyComboBox::refillComboBox
void KisFontFamilyComboBox::setTopFont(const QString &family)
{
if (family.isEmpty() || !m_initilized || m_pinnedFonts.contains(family)) {
return;
}
if (m_pinnedFonts.count() > 4) {
this->removeItem(4);
m_pinnedFonts.pop_back();
m_separatorIndex--;
}
if (m_pinnedFonts.isEmpty()) {
this->insertSeparator(0);
m_fontSeparator->setSeparatorAdded();
}
m_pinnedFonts.push_front(family);
this->insertItem(0, family);
m_separatorIndex++;
m_fontSeparator->setSeparatorIndex(m_separatorIndex);
KisConfig(false).writeList("PinnedFonts", m_pinnedFonts);
}
void KisFontFamilyComboBox::setInitialized()
{
+ if(m_initilized)
+ return;
+
m_initilized = true;
for(int i=m_pinnedFonts.count()-1; i>=0; i--){
this->insertItem(0, m_pinnedFonts[i]);
m_separatorIndex++;
}
if(m_pinnedFonts.count() > 0){
this->insertSeparator(m_separatorIndex);
m_fontSeparator->setSeparatorIndex(m_separatorIndex);
m_fontSeparator->setSeparatorAdded();
}
this->setItemDelegate(m_fontSeparator);
}
KisFontComboBoxes::KisFontComboBoxes(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *layout = new QHBoxLayout();
this->setLayout(layout);
m_family = new KisFontFamilyComboBox();
m_family->setMinimumWidth(100);
m_family->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
layout->addWidget(m_family);
m_styles = new QComboBox();
layout->addWidget(m_styles);
fontFamilyChanged();
m_family->setToolTip(i18n("Font Family"));
m_styles->setToolTip(i18n("Font Style"));
connect(m_family, SIGNAL(currentTextChanged(QString)), this, SLOT(fontFamilyChanged()));
connect(m_family, SIGNAL(currentTextChanged(QString)), this, SLOT(fontChange()));
connect(m_styles, SIGNAL(activated(int)), this, SLOT(fontChange()));
}
void KisFontComboBoxes::setCurrentFont(QFont font)
{
setCurrentFamily(font.family());
setCurrentStyle(QFontDatabase().styleString(font));
}
void KisFontComboBoxes::setCurrentFamily(const QString family)
{
m_family->setCurrentText(family);
fontFamilyChanged();
}
void KisFontComboBoxes::setCurrentStyle(QString style)
{
QString properStyle = QString();
for (int i = 0; i < m_styles->count(); i++) {
QString item = m_styles->itemText(i);
if (item == style) {
properStyle = style;
} else if (item.contains(style, Qt::CaseInsensitive)) {
properStyle = item;
} else if (item.contains("regular", Qt::CaseInsensitive)) {
properStyle = item;
}
}
m_styles->setCurrentText(properStyle);
}
QString KisFontComboBoxes::currentFamily() const
{
return m_family->currentText();
}
QString KisFontComboBoxes::currentStyle() const
{
return m_styles->currentText();
}
QFont KisFontComboBoxes::currentFont(int pointSize) const
{
return QFontDatabase().font(m_family->currentText(), m_styles->currentText(), pointSize);
}
void KisFontComboBoxes::refillComboBox(QVector<QFontDatabase::WritingSystem> writingSystems)
{
KisFontFamilyComboBox *cmb = qobject_cast<KisFontFamilyComboBox *>(m_family);
cmb->refillComboBox(writingSystems);
}
void KisFontComboBoxes::fontFamilyChanged()
{
QString currentText = m_styles->currentText();
QFontDatabase fonts;
const QString family = m_family->currentText();
int maxWidth = 0;
m_styles->clear();
QStringList styles;
KisFontFamilyComboBox *cmb = qobject_cast<KisFontFamilyComboBox *>(m_family);
cmb->setTopFont(family);
if (fonts.styles(family).isEmpty()) {
styles.append("Normal");
}
Q_FOREACH (const QString style, fonts.styles(family)) {
int b = fonts.weight(family, style);
int bindex = 0;
for (int i = 0; i < styles.size(); i++) {
if (b > fonts.weight(family, styles.at(i))) {
bindex = i;
}
}
if (!styles.contains(style)) {
styles.insert(bindex, style);
maxWidth = qMax(m_styles->view()->fontMetrics().width(style + " "), maxWidth);
}
}
m_styles->addItems(styles);
if (m_styles->count() > m_styles->maxVisibleItems()) {
maxWidth += m_styles->view()->style()->pixelMetric(QStyle::PixelMetric::PM_ScrollBarExtent);
}
m_styles->view()->setMinimumWidth(maxWidth);
if (styles.contains(currentText)) {
m_styles->setCurrentText(currentText);
}
} // KisFontComboBoxes::fontFamilyChanged
void KisFontComboBoxes::fontChange()
{
emit fontChanged(currentFont(10).toString());
}
void KisFontComboBoxes::setInitialized()
{
KisFontFamilyComboBox *cmb = qobject_cast<KisFontFamilyComboBox *>(m_family);
cmb->setInitialized();
}
diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp
index a92e804783..815c96e414 100644
--- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp
+++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp
@@ -1,350 +1,359 @@
/*
* 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_tool_lazy_brush.h"
#include <klocalizedstring.h>
#include <QAction>
#include <QLabel>
#include <kactioncollection.h>
#include <KoCanvasBase.h>
#include <KoCanvasController.h>
#include <KisViewManager.h>
#include "kis_canvas2.h"
#include "kis_cursor.h"
#include "kis_config.h"
#include "kundo2magicstring.h"
#include "KoProperties.h"
#include "kis_node_manager.h"
#include "kis_layer_properties_icons.h"
#include "kis_canvas_resource_provider.h"
#include "kis_tool_lazy_brush_options_widget.h"
#include "lazybrush/kis_colorize_mask.h"
#include "kis_signal_auto_connection.h"
struct KisToolLazyBrush::Private
{
bool activateMaskMode = false;
bool oldShowKeyStrokesValue = false;
bool oldShowColoringValue = false;
KisNodeWSP manuallyActivatedNode;
KisSignalAutoConnectionsStore toolConnections;
};
KisToolLazyBrush::KisToolLazyBrush(KoCanvasBase * canvas)
: KisToolFreehand(canvas,
KisCursor::load("tool_freehand_cursor.png", 5, 5),
kundo2_i18n("Colorize Mask Key Stroke")),
m_d(new Private)
{
setObjectName("tool_lazybrush");
}
KisToolLazyBrush::~KisToolLazyBrush()
{
}
void KisToolLazyBrush::tryDisableKeyStrokesOnMask()
{
// upgrade to strong pointer
KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode;
if (manuallyActivatedNode) {
KisLayerPropertiesIcons::setNodeProperty(manuallyActivatedNode, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image());
manuallyActivatedNode = 0;
}
m_d->manuallyActivatedNode = 0;
}
void KisToolLazyBrush::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
KisCanvas2 * kiscanvas = dynamic_cast<KisCanvas2*>(canvas());
m_d->toolConnections.addUniqueConnection(
kiscanvas->viewManager()->canvasResourceProvider(), SIGNAL(sigNodeChanged(KisNodeSP)),
this, SLOT(slotCurrentNodeChanged(KisNodeSP)));
KisColorizeMask *mask = qobject_cast<KisColorizeMask*>(currentNode().data());
if (mask) {
mask->regeneratePrefilteredDeviceIfNeeded();
}
KisToolFreehand::activate(activation, shapes);
}
void KisToolLazyBrush::deactivate()
{
KisToolFreehand::deactivate();
tryDisableKeyStrokesOnMask();
m_d->toolConnections.clear();
}
void KisToolLazyBrush::slotCurrentNodeChanged(KisNodeSP node)
{
// upgrade to strong pointer
KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode;
if (node != manuallyActivatedNode) {
tryDisableKeyStrokesOnMask();
KisColorizeMask *mask = qobject_cast<KisColorizeMask*>(node.data());
if (mask) {
mask->regeneratePrefilteredDeviceIfNeeded();
}
}
}
void KisToolLazyBrush::resetCursorStyle()
{
- KisToolFreehand::resetCursorStyle();
+ // If there's no mask yet, we show the hand cursor
+ if (!colorizeMaskActive() && canCreateColorizeMask()) {
+ useCursor(KisCursor::handCursor());
+ m_d->activateMaskMode = true;
+ setOutlineEnabled(false);
+ }
+ else {
+ KisToolFreehand::resetCursorStyle();
+ }
}
bool KisToolLazyBrush::colorizeMaskActive() const
{
KisNodeSP node = currentNode();
return node && node->inherits("KisColorizeMask");
}
bool KisToolLazyBrush::canCreateColorizeMask() const
{
KisNodeSP node = currentNode();
return node && node->inherits("KisLayer");
}
bool KisToolLazyBrush::shouldActivateKeyStrokes() const
{
KisNodeSP node = currentNode();
return node && node->inherits("KisColorizeMask") &&
!KisLayerPropertiesIcons::nodeProperty(node,
KisLayerPropertiesIcons::colorizeEditKeyStrokes,
true).toBool();
}
void KisToolLazyBrush::tryCreateColorizeMask()
{
KisNodeSP node = currentNode();
if (!node) return;
KoProperties properties;
properties.setProperty("visible", true);
properties.setProperty("locked", false);
QList<KisNodeSP> masks = node->childNodes(QStringList("KisColorizeMask"), properties);
if (!masks.isEmpty()) {
KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
KisViewManager* viewManager = kiscanvas->viewManager();
viewManager->nodeManager()->slotNonUiActivatedNode(masks.first());
} else {
KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
KisViewManager* viewManager = kiscanvas->viewManager();
viewManager->nodeManager()->createNode("KisColorizeMask");
}
}
void KisToolLazyBrush::activatePrimaryAction()
{
KisToolFreehand::activatePrimaryAction();
- if (shouldActivateKeyStrokes() ||
- (!colorizeMaskActive() && canCreateColorizeMask())) {
+ qDebug() << "1";
+ if (!colorizeMaskActive() && canCreateColorizeMask()) {
+ qDebug() << "2";
useCursor(KisCursor::handCursor());
m_d->activateMaskMode = true;
setOutlineEnabled(false);
}
}
void KisToolLazyBrush::deactivatePrimaryAction()
{
if (m_d->activateMaskMode) {
m_d->activateMaskMode = false;
setOutlineEnabled(true);
resetCursorStyle();
}
KisToolFreehand::deactivatePrimaryAction();
}
void KisToolLazyBrush::beginPrimaryAction(KoPointerEvent *event)
{
if (m_d->activateMaskMode) {
if (!colorizeMaskActive() && canCreateColorizeMask()) {
tryCreateColorizeMask();
} else if (shouldActivateKeyStrokes()) {
// upgrade to strong pointer
KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode;
KisNodeSP node = currentNode();
KIS_SAFE_ASSERT_RECOVER_NOOP(!manuallyActivatedNode ||
manuallyActivatedNode == node);
KisLayerPropertiesIcons::setNodeProperty(node,
KisLayerPropertiesIcons::colorizeEditKeyStrokes,
true, image());
m_d->manuallyActivatedNode = node;
}
} else {
KisToolFreehand::beginPrimaryAction(event);
}
}
void KisToolLazyBrush::continuePrimaryAction(KoPointerEvent *event)
{
if (m_d->activateMaskMode) return;
KisToolFreehand::continuePrimaryAction(event);
}
void KisToolLazyBrush::endPrimaryAction(KoPointerEvent *event)
{
if (m_d->activateMaskMode) return;
KisToolFreehand::endPrimaryAction(event);
}
void KisToolLazyBrush::activateAlternateAction(KisTool::AlternateAction action)
{
if (action == KisTool::Secondary && !m_d->activateMaskMode) {
KisNodeSP node = currentNode();
if (!node) return;
m_d->oldShowKeyStrokesValue =
KisLayerPropertiesIcons::nodeProperty(node,
KisLayerPropertiesIcons::colorizeEditKeyStrokes,
true).toBool();
KisLayerPropertiesIcons::setNodeProperty(node,
KisLayerPropertiesIcons::colorizeEditKeyStrokes,
!m_d->oldShowKeyStrokesValue, image());
KisToolFreehand::activatePrimaryAction();
} else if (action == KisTool::Third && !m_d->activateMaskMode) {
KisNodeSP node = currentNode();
if (!node) return;
m_d->oldShowColoringValue =
KisLayerPropertiesIcons::nodeProperty(node,
KisLayerPropertiesIcons::colorizeShowColoring,
true).toBool();
KisLayerPropertiesIcons::setNodeProperty(node,
KisLayerPropertiesIcons::colorizeShowColoring,
!m_d->oldShowColoringValue, image());
KisToolFreehand::activatePrimaryAction();
} else {
KisToolFreehand::activateAlternateAction(action);
}
}
void KisToolLazyBrush::deactivateAlternateAction(KisTool::AlternateAction action)
{
if (action == KisTool::Secondary && !m_d->activateMaskMode) {
KisNodeSP node = currentNode();
if (!node) return;
KisLayerPropertiesIcons::setNodeProperty(node,
KisLayerPropertiesIcons::colorizeEditKeyStrokes,
m_d->oldShowKeyStrokesValue, image());
KisToolFreehand::deactivatePrimaryAction();
} else if (action == KisTool::Third && !m_d->activateMaskMode) {
KisNodeSP node = currentNode();
if (!node) return;
KisLayerPropertiesIcons::setNodeProperty(node,
KisLayerPropertiesIcons::colorizeShowColoring,
m_d->oldShowColoringValue, image());
KisToolFreehand::deactivatePrimaryAction();
} else {
KisToolFreehand::deactivateAlternateAction(action);
}
}
void KisToolLazyBrush::beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
{
if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) {
beginPrimaryAction(event);
} else {
KisToolFreehand::beginAlternateAction(event, action);
}
}
void KisToolLazyBrush::continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
{
if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) {
continuePrimaryAction(event);
} else {
KisToolFreehand::continueAlternateAction(event, action);
}
}
void KisToolLazyBrush::endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
{
if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) {
endPrimaryAction(event);
} else {
KisToolFreehand::endAlternateAction(event, action);
}
}
void KisToolLazyBrush::explicitUserStrokeEndRequest()
{
if (m_d->activateMaskMode) {
tryCreateColorizeMask();
} else if (colorizeMaskActive()) {
KisNodeSP node = currentNode();
if (!node) return;
KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeNeedsUpdate, false, image());
}
}
QWidget * KisToolLazyBrush::createOptionWidget()
{
KisCanvas2 * kiscanvas = dynamic_cast<KisCanvas2*>(canvas());
QWidget *optionsWidget = new KisToolLazyBrushOptionsWidget(kiscanvas->viewManager()->canvasResourceProvider(), 0);
optionsWidget->setObjectName(toolId() + "option widget");
// // See https://bugs.kde.org/show_bug.cgi?id=316896
// QWidget *specialSpacer = new QWidget(optionsWidget);
// specialSpacer->setObjectName("SpecialSpacer");
// specialSpacer->setFixedSize(0, 0);
// optionsWidget->layout()->addWidget(specialSpacer);
return optionsWidget;
}
diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc
index 32ace87044..9520e869f7 100644
--- a/plugins/tools/tool_transform2/kis_tool_transform.cc
+++ b/plugins/tools/tool_transform2/kis_tool_transform.cc
@@ -1,1125 +1,1134 @@
/*
* kis_tool_transform.cc -- part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2010 Marc Pegon <pe.marc@free.fr>
* 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_tool_transform.h"
#include <math.h>
#include <limits>
#include <QPainter>
#include <QPen>
#include <QPushButton>
#include <QObject>
#include <QLabel>
#include <QComboBox>
#include <QApplication>
#include <QMatrix4x4>
#include <QMenu>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoPointerEvent.h>
#include <KoID.h>
#include <KoCanvasBase.h>
#include <KoViewConverter.h>
#include <KoSelection.h>
#include <KoCompositeOp.h>
#include <kis_global.h>
#include <canvas/kis_canvas2.h>
#include <KisViewManager.h>
#include <kis_painter.h>
#include <kis_cursor.h>
#include <kis_image.h>
#include <kis_undo_adapter.h>
#include <kis_transaction.h>
#include <kis_selection.h>
#include <kis_filter_strategy.h>
#include <widgets/kis_cmb_idlist.h>
#include <kis_statusbar.h>
#include <kis_transform_worker.h>
#include <kis_perspectivetransform_worker.h>
#include <kis_warptransform_worker.h>
#include <kis_pixel_selection.h>
#include <kis_shape_selection.h>
#include <kis_selection_manager.h>
#include <krita_utils.h>
#include <kis_resources_snapshot.h>
#include <KoShapeTransformCommand.h>
#include "widgets/kis_progress_widget.h"
#include "kis_transform_utils.h"
#include "kis_warp_transform_strategy.h"
#include "kis_cage_transform_strategy.h"
#include "kis_liquify_transform_strategy.h"
#include "kis_free_transform_strategy.h"
#include "kis_perspective_transform_strategy.h"
#include "kis_transform_mask.h"
#include "kis_transform_mask_adapter.h"
#include "krita_container_utils.h"
#include "kis_layer_utils.h"
#include <KisDelayedUpdateNodeInterface.h>
#include "strokes/transform_stroke_strategy.h"
KisToolTransform::KisToolTransform(KoCanvasBase * canvas)
: KisTool(canvas, KisCursor::rotateCursor())
, m_workRecursively(true)
, m_warpStrategy(
new KisWarpTransformStrategy(
dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
m_currentArgs, m_transaction))
, m_cageStrategy(
new KisCageTransformStrategy(
dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
m_currentArgs, m_transaction))
, m_liquifyStrategy(
new KisLiquifyTransformStrategy(
dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
m_currentArgs, m_transaction, canvas->resourceManager()))
, m_freeStrategy(
new KisFreeTransformStrategy(
dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
m_currentArgs, m_transaction))
, m_perspectiveStrategy(
new KisPerspectiveTransformStrategy(
dynamic_cast<KisCanvas2*>(canvas)->coordinatesConverter(),
dynamic_cast<KisCanvas2*>(canvas)->snapGuide(),
m_currentArgs, m_transaction))
{
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
Q_ASSERT(m_canvas);
setObjectName("tool_transform");
useCursor(KisCursor::selectCursor());
m_optionsWidget = 0;
warpAction = new KisAction(i18n("Warp"));
liquifyAction = new KisAction(i18n("Liquify"));
cageAction = new KisAction(i18n("Cage"));
freeTransformAction = new KisAction(i18n("Free"));
perspectiveAction = new KisAction(i18n("Perspective"));
// extra actions for free transform that are in the tool options
mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal"));
mirrorVericalAction = new KisAction(i18n("Mirror Vertical"));
rotateNinteyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise"));
rotateNinteyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise"));
applyTransformation = new KisAction(i18n("Apply"));
resetTransformation = new KisAction(i18n("Reset"));
m_contextMenu.reset(new QMenu());
connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(QPointF)), SLOT(cursorOutlineUpdateRequested(QPointF)));
connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget()));
connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested()));
connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)),
this, SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP)));
}
KisToolTransform::~KisToolTransform()
{
cancelStroke();
}
void KisToolTransform::outlineChanged()
{
emit freeTransformChanged();
m_canvas->updateCanvas();
}
void KisToolTransform::canvasUpdateRequested()
{
m_canvas->updateCanvas();
}
void KisToolTransform::resetCursorStyle()
{
setFunctionalCursor();
}
void KisToolTransform::resetRotationCenterButtonsRequested()
{
if (!m_optionsWidget) return;
m_optionsWidget->resetRotationCenterButtons();
}
void KisToolTransform::imageTooBigRequested(bool value)
{
if (!m_optionsWidget) return;
m_optionsWidget->setTooBigLabelVisible(value);
}
KisTransformStrategyBase* KisToolTransform::currentStrategy() const
{
if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) {
return m_freeStrategy.data();
} else if (m_currentArgs.mode() == ToolTransformArgs::WARP) {
return m_warpStrategy.data();
} else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) {
return m_cageStrategy.data();
} else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) {
return m_liquifyStrategy.data();
} else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ {
return m_perspectiveStrategy.data();
}
}
void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter)
{
Q_UNUSED(converter);
if (!m_strokeId || !m_transaction.rootNode()) return;
QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0));
if (m_refRect != newRefRect) {
m_refRect = newRefRect;
currentStrategy()->externalConfigChanged();
}
currentStrategy()->paint(gc);
if (!m_cursorOutline.isEmpty()) {
QPainterPath mappedOutline =
KisTransformUtils::imageToFlakeTransform(
m_canvas->coordinatesConverter()).map(m_cursorOutline);
paintToolOutline(&gc, mappedOutline);
}
}
void KisToolTransform::setFunctionalCursor()
{
if (overrideCursorIfNotEditable()) {
return;
}
if (!m_strokeId) {
useCursor(KisCursor::pointingHandCursor());
} else if (m_strokeId && !m_transaction.rootNode()) {
// we are in the middle of stroke initialization
useCursor(KisCursor::waitCursor());
} else {
useCursor(currentStrategy()->getCurrentCursor());
}
}
void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos)
{
QRect canvasUpdateRect;
if (!m_cursorOutline.isEmpty()) {
canvasUpdateRect = m_canvas->coordinatesConverter()->
imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
}
m_cursorOutline = currentStrategy()->
getCursorOutline().translated(imagePos);
if (!m_cursorOutline.isEmpty()) {
canvasUpdateRect |=
m_canvas->coordinatesConverter()->
imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
}
if (!canvasUpdateRect.isEmpty()) {
// grow rect a bit to follow interpolation fuzziness
canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2);
m_canvas->updateCanvas(canvasUpdateRect);
}
}
void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
{
if (!nodeEditable()) {
event->ignore();
return;
}
if (!m_strokeId) {
startStroke(m_currentArgs.mode(), false);
} else if (m_transaction.rootNode()) {
bool result = false;
if (usePrimaryAction) {
result = currentStrategy()->beginPrimaryAction(event);
} else {
result = currentStrategy()->beginAlternateAction(event, action);
}
if (result) {
setMode(KisTool::PAINT_MODE);
}
}
m_actuallyMoveWhileSelected = false;
outlineChanged();
}
void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
{
if (mode() != KisTool::PAINT_MODE) return;
if (!m_transaction.rootNode()) return;
m_actuallyMoveWhileSelected = true;
if (usePrimaryAction) {
currentStrategy()->continuePrimaryAction(event);
} else {
currentStrategy()->continueAlternateAction(event, action);
}
updateOptionWidget();
outlineChanged();
}
void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
{
if (mode() != KisTool::PAINT_MODE) return;
setMode(KisTool::HOVER_MODE);
if (m_actuallyMoveWhileSelected ||
currentStrategy()->acceptsClicks()) {
bool result = false;
if (usePrimaryAction) {
result = currentStrategy()->endPrimaryAction(event);
} else {
result = currentStrategy()->endAlternateAction(event, action);
}
if (result) {
commitChanges();
}
outlineChanged();
}
updateOptionWidget();
updateApplyResetAvailability();
}
QMenu* KisToolTransform::popupActionsMenu()
{
if (m_contextMenu) {
m_contextMenu->clear();
m_contextMenu->addSection(i18n("Transform Tool Actions"));
m_contextMenu->addSeparator();
// add a quick switch to different transform types
m_contextMenu->addAction(freeTransformAction);
m_contextMenu->addAction(perspectiveAction);
m_contextMenu->addAction(warpAction);
m_contextMenu->addAction(cageAction);
m_contextMenu->addAction(liquifyAction);
// extra options if free transform is selected
if (transformMode() == FreeTransformMode) {
m_contextMenu->addSeparator();
m_contextMenu->addAction(mirrorHorizontalAction);
m_contextMenu->addAction(mirrorVericalAction);
m_contextMenu->addAction(rotateNinteyCWAction);
m_contextMenu->addAction(rotateNinteyCCWAction);
}
m_contextMenu->addSeparator();
m_contextMenu->addAction(applyTransformation);
m_contextMenu->addAction(resetTransformation);
}
return m_contextMenu.data();
}
void KisToolTransform::beginPrimaryAction(KoPointerEvent *event)
{
beginActionImpl(event, true, KisTool::NONE);
}
void KisToolTransform::continuePrimaryAction(KoPointerEvent *event)
{
continueActionImpl(event, true, KisTool::NONE);
}
void KisToolTransform::endPrimaryAction(KoPointerEvent *event)
{
endActionImpl(event, true, KisTool::NONE);
}
void KisToolTransform::activatePrimaryAction()
{
currentStrategy()->activatePrimaryAction();
setFunctionalCursor();
}
void KisToolTransform::deactivatePrimaryAction()
{
currentStrategy()->deactivatePrimaryAction();
}
void KisToolTransform::activateAlternateAction(AlternateAction action)
{
currentStrategy()->activateAlternateAction(action);
setFunctionalCursor();
}
void KisToolTransform::deactivateAlternateAction(AlternateAction action)
{
currentStrategy()->deactivateAlternateAction(action);
}
void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
{
beginActionImpl(event, false, action);
}
void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
{
continueActionImpl(event, false, action);
}
void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action)
{
endActionImpl(event, false, action);
}
void KisToolTransform::mousePressEvent(KoPointerEvent *event)
{
KisTool::mousePressEvent(event);
}
void KisToolTransform::mouseMoveEvent(KoPointerEvent *event)
{
QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point);
cursorOutlineUpdateRequested(mousePos);
if (this->mode() != KisTool::PAINT_MODE) {
currentStrategy()->hoverActionCommon(event);
setFunctionalCursor();
KisTool::mouseMoveEvent(event);
return;
}
}
void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event)
{
KisTool::mouseReleaseEvent(event);
}
void KisToolTransform::applyTransform()
{
slotApplyTransform();
}
KisToolTransform::TransformToolMode KisToolTransform::transformMode() const
{
TransformToolMode mode = FreeTransformMode;
switch (m_currentArgs.mode())
{
case ToolTransformArgs::FREE_TRANSFORM:
mode = FreeTransformMode;
break;
case ToolTransformArgs::WARP:
mode = WarpTransformMode;
break;
case ToolTransformArgs::CAGE:
mode = CageTransformMode;
break;
case ToolTransformArgs::LIQUIFY:
mode = LiquifyTransformMode;
break;
case ToolTransformArgs::PERSPECTIVE_4POINT:
mode = PerspectiveTransformMode;
break;
default:
KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
}
return mode;
}
double KisToolTransform::translateX() const
{
return m_currentArgs.transformedCenter().x();
}
double KisToolTransform::translateY() const
{
return m_currentArgs.transformedCenter().y();
}
double KisToolTransform::rotateX() const
{
return m_currentArgs.aX();
}
double KisToolTransform::rotateY() const
{
return m_currentArgs.aY();
}
double KisToolTransform::rotateZ() const
{
return m_currentArgs.aZ();
}
double KisToolTransform::scaleX() const
{
return m_currentArgs.scaleX();
}
double KisToolTransform::scaleY() const
{
return m_currentArgs.scaleY();
}
double KisToolTransform::shearX() const
{
return m_currentArgs.shearX();
}
double KisToolTransform::shearY() const
{
return m_currentArgs.shearY();
}
KisToolTransform::WarpType KisToolTransform::warpType() const
{
switch(m_currentArgs.warpType()) {
case KisWarpTransformWorker::AFFINE_TRANSFORM:
return AffineWarpType;
case KisWarpTransformWorker::RIGID_TRANSFORM:
return RigidWarpType;
case KisWarpTransformWorker::SIMILITUDE_TRANSFORM:
return SimilitudeWarpType;
default:
return RigidWarpType;
}
}
double KisToolTransform::warpFlexibility() const
{
return m_currentArgs.alpha();
}
int KisToolTransform::warpPointDensity() const
{
return m_currentArgs.numPoints();
}
void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode)
{
ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM;
switch (newMode) {
case FreeTransformMode:
mode = ToolTransformArgs::FREE_TRANSFORM;
break;
case WarpTransformMode:
mode = ToolTransformArgs::WARP;
break;
case CageTransformMode:
mode = ToolTransformArgs::CAGE;
break;
case LiquifyTransformMode:
mode = ToolTransformArgs::LIQUIFY;
break;
case PerspectiveTransformMode:
mode = ToolTransformArgs::PERSPECTIVE_4POINT;
break;
default:
KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
}
if( mode != m_currentArgs.mode() ) {
if( newMode == FreeTransformMode ) {
m_optionsWidget->slotSetFreeTransformModeButtonClicked( true );
} else if( newMode == WarpTransformMode ) {
m_optionsWidget->slotSetWarpModeButtonClicked( true );
} else if( newMode == CageTransformMode ) {
m_optionsWidget->slotSetCageModeButtonClicked( true );
} else if( newMode == LiquifyTransformMode ) {
m_optionsWidget->slotSetLiquifyModeButtonClicked( true );
} else if( newMode == PerspectiveTransformMode ) {
m_optionsWidget->slotSetPerspectiveModeButtonClicked( true );
}
emit transformModeChanged();
}
}
void KisToolTransform::setRotateX( double rotation )
{
m_currentArgs.setAX( normalizeAngle(rotation) );
}
void KisToolTransform::setRotateY( double rotation )
{
m_currentArgs.setAY( normalizeAngle(rotation) );
}
void KisToolTransform::setRotateZ( double rotation )
{
m_currentArgs.setAZ( normalizeAngle(rotation) );
}
void KisToolTransform::setWarpType( KisToolTransform::WarpType type )
{
switch( type ) {
case RigidWarpType:
m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM);
break;
case AffineWarpType:
m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM);
break;
case SimilitudeWarpType:
m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM);
break;
default:
break;
}
}
void KisToolTransform::setWarpFlexibility( double flexibility )
{
m_currentArgs.setAlpha( flexibility );
}
void KisToolTransform::setWarpPointDensity( int density )
{
m_optionsWidget->slotSetWarpDensity(density);
}
void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode)
{
m_currentArgs = KisTransformUtils::resetArgsForMode(mode, m_currentArgs.filterId(), m_transaction);
initGuiAfterTransformMode();
}
void KisToolTransform::initGuiAfterTransformMode()
{
currentStrategy()->externalConfigChanged();
outlineChanged();
updateOptionWidget();
updateApplyResetAvailability();
setFunctionalCursor();
}
void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice)
{
QImage origImg;
m_selectedPortionCache = previewDevice;
QTransform thumbToImageTransform;
const int maxSize = 2000;
QRect srcRect(m_transaction.originalRect().toAlignedRect());
int x, y, w, h;
srcRect.getRect(&x, &y, &w, &h);
if (m_selectedPortionCache) {
if (w > maxSize || h > maxSize) {
qreal scale = qreal(maxSize) / (w > h ? w : h);
QTransform scaleTransform = QTransform::fromScale(scale, scale);
QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect();
origImg = m_selectedPortionCache->
createThumbnail(thumbRect.width(),
thumbRect.height(),
srcRect, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
thumbToImageTransform = scaleTransform.inverted();
} else {
origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
thumbToImageTransform = QTransform();
}
}
// init both strokes since the thumbnail is initialized only once
// during the stroke
m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform);
}
void KisToolTransform::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
KisTool::activate(toolActivation, shapes);
if (currentNode()) {
m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {});
}
startStroke(ToolTransformArgs::FREE_TRANSFORM, false);
}
void KisToolTransform::deactivate()
{
endStroke();
m_canvas->updateCanvas();
KisTool::deactivate();
}
void KisToolTransform::requestUndoDuringStroke()
{
if (!m_strokeId || !m_transaction.rootNode()) return;
if (m_changesTracker.isEmpty()) {
cancelStroke();
} else {
m_changesTracker.requestUndo();
}
}
void KisToolTransform::requestStrokeEnd()
{
endStroke();
}
void KisToolTransform::requestStrokeCancellation()
{
if (!m_transaction.rootNode() || m_currentArgs.isIdentity()) {
cancelStroke();
} else {
slotResetTransform();
}
}
void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset)
{
Q_ASSERT(!m_strokeId);
// set up and null checks before we do anything
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager());
KisNodeSP currentNode = resources->currentNode();
if (!currentNode || !currentNode->isEditable()) return;
m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {});
m_currentArgs = ToolTransformArgs();
// some layer types cannot be transformed. Give a message and return if a user tries it
if (currentNode->inherits("KisColorizeMask") ||
currentNode->inherits("KisFileLayer") ||
currentNode->inherits("KisCloneLayer")) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
- kisCanvas->viewManager()->
- showFloatingMessage(
- i18nc("floating message in transformation tool",
- "Layer type cannot use the transform tool"),
- koIcon("object-locked"), 4000, KisFloatingMessage::High);
+ if(currentNode->inherits("KisColorizeMask")){
+ kisCanvas->viewManager()->
+ showFloatingMessage(
+ i18nc("floating message in transformation tool",
+ "Layer type cannot use the transform tool"),
+ koIcon("object-locked"), 4000, KisFloatingMessage::High);
+ }
+ else{
+ kisCanvas->viewManager()->
+ showFloatingMessage(
+ i18nc("floating message in transformation tool",
+ "Layer type cannot use the transform tool. Use transform mask instead."),
+ koIcon("object-locked"), 4000, KisFloatingMessage::High);
+ }
return;
}
if (m_optionsWidget) {
m_workRecursively = m_optionsWidget->workRecursively() ||
!currentNode->paintDevice();
}
KisSelectionSP selection = resources->activeSelection();
/**
* When working with transform mask, selections are not
* taken into account.
*/
if (selection && dynamic_cast<KisTransformMask*>(currentNode.data())) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in transformation tool",
"Selections are not used when editing transform masks "),
QIcon(), 4000, KisFloatingMessage::Low);
selection = 0;
}
TransformStrokeStrategy *strategy = new TransformStrokeStrategy(mode, m_workRecursively, m_currentArgs.filterId(), forceReset, currentNode, selection, image().data(), image().data());
connect(strategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP)));
connect(strategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)));
// save unique identifier of the stroke so we could
// recognize it when sigTransactionGenerated() is
- // recieved (theoretically, the user can start two
+ // received (theoretically, the user can start two
// strokes at the same time, if he is quick enough)
m_strokeStrategyCookie = strategy;
m_strokeId = image()->startStroke(strategy);
KIS_SAFE_ASSERT_RECOVER_NOOP(m_changesTracker.isEmpty());
slotPreviewDeviceGenerated(0);
}
void KisToolTransform::endStroke()
{
if (!m_strokeId) return;
if (m_transaction.rootNode() && !m_currentArgs.isIdentity()) {
image()->addJob(m_strokeId,
new TransformStrokeStrategy::TransformAllData(m_currentArgs));
}
image()->endStroke(m_strokeId);
m_strokeStrategyCookie = 0;
m_strokeId.clear();
m_changesTracker.reset();
m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {});
outlineChanged();
}
void KisToolTransform::slotTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args, void *strokeStrategyCookie)
{
if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return;
if (transaction.transformedNodes().isEmpty()) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in transformation tool",
"Cannot transform empty layer "),
QIcon(), 1000, KisFloatingMessage::Medium);
cancelStroke();
return;
}
m_transaction = transaction;
m_currentArgs = args;
m_transaction.setCurrentConfigLocation(&m_currentArgs);
KIS_SAFE_ASSERT_RECOVER_NOOP(m_changesTracker.isEmpty());
commitChanges();
initGuiAfterTransformMode();
if (m_transaction.hasInvisibleNodes()) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in transformation tool",
"Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "),
QIcon(), 4000, KisFloatingMessage::Low);
}
}
void KisToolTransform::slotPreviewDeviceGenerated(KisPaintDeviceSP device)
{
if (device && device->exactBounds().isEmpty()) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in transformation tool",
"Cannot transform empty layer "),
QIcon(), 1000, KisFloatingMessage::Medium);
cancelStroke();
} else {
initThumbnailImage(device);
initGuiAfterTransformMode();
}
}
void KisToolTransform::cancelStroke()
{
if (!m_strokeId) return;
image()->cancelStroke(m_strokeId);
m_strokeStrategyCookie = 0;
m_strokeId.clear();
m_changesTracker.reset();
m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {});
outlineChanged();
}
void KisToolTransform::commitChanges()
{
if (!m_strokeId || !m_transaction.rootNode()) return;
m_changesTracker.commitConfig(toQShared(m_currentArgs.clone()));
}
void KisToolTransform::slotTrackerChangedConfig(KisToolChangesTrackerDataSP status)
{
const ToolTransformArgs *newArgs = dynamic_cast<const ToolTransformArgs*>(status.data());
KIS_SAFE_ASSERT_RECOVER_RETURN(newArgs);
*m_transaction.currentConfig() = *newArgs;
slotUiChangedConfig();
updateOptionWidget();
}
QList<KisNodeSP> KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive)
{
QList<KisNodeSP> result;
auto fetchFunc =
[&result, mode, root] (KisNodeSP node) {
if (node->isEditable(node == root) &&
(!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) &&
!node->inherits("KisFileLayer") &&
(!node->inherits("KisTransformMask") || node == root)) {
result << node;
}
};
if (recursive) {
KisLayerUtils::recursiveApplyNodes(root, fetchFunc);
} else {
fetchFunc(root);
}
return result;
}
QWidget* KisToolTransform::createOptionWidget()
{
if (!m_canvas) return 0;
m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0);
Q_CHECK_PTR(m_optionsWidget);
m_optionsWidget->setObjectName(toolId() + " option widget");
// See https://bugs.kde.org/show_bug.cgi?id=316896
QWidget *specialSpacer = new QWidget(m_optionsWidget);
specialSpacer->setObjectName("SpecialSpacer");
specialSpacer->setFixedSize(0, 0);
m_optionsWidget->layout()->addWidget(specialSpacer);
connect(m_optionsWidget, SIGNAL(sigConfigChanged()),
this, SLOT(slotUiChangedConfig()));
connect(m_optionsWidget, SIGNAL(sigApplyTransform()),
this, SLOT(slotApplyTransform()));
connect(m_optionsWidget, SIGNAL(sigResetTransform()),
this, SLOT(slotResetTransform()));
connect(m_optionsWidget, SIGNAL(sigRestartTransform()),
this, SLOT(slotRestartTransform()));
connect(m_optionsWidget, SIGNAL(sigEditingFinished()),
this, SLOT(slotEditingFinished()));
connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX()));
connect(mirrorVericalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY()));
connect(rotateNinteyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW()));
connect(rotateNinteyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW()));
connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType()));
connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType()));
connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType()));
connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType()));
connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType()));
connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform()));
connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotResetTransform()));
updateOptionWidget();
return m_optionsWidget;
}
void KisToolTransform::updateOptionWidget()
{
if (!m_optionsWidget) return;
if (!currentNode()) {
m_optionsWidget->setEnabled(false);
return;
}
else {
m_optionsWidget->setEnabled(true);
m_optionsWidget->updateConfig(m_currentArgs);
}
}
void KisToolTransform::updateApplyResetAvailability()
{
if (m_optionsWidget) {
m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity());
}
}
void KisToolTransform::slotUiChangedConfig()
{
if (mode() == KisTool::PAINT_MODE) return;
currentStrategy()->externalConfigChanged();
if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) {
m_currentArgs.saveLiquifyTransformMode();
}
outlineChanged();
updateApplyResetAvailability();
}
void KisToolTransform::slotApplyTransform()
{
QApplication::setOverrideCursor(KisCursor::waitCursor());
endStroke();
QApplication::restoreOverrideCursor();
}
void KisToolTransform::slotResetTransform()
{
if (!m_strokeId || !m_transaction.rootNode()) return;
if (m_currentArgs.continuedTransform()) {
ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode();
/**
* Our reset transform button can be used for two purposes:
*
* 1) Reset current transform to the initial one, which was
* loaded from the previous user action.
*
* 2) Reset transform frame to infinity when the frame is unchanged
*/
const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs);
if (transformDiffers &&
m_currentArgs.continuedTransform()->mode() == savedMode) {
m_currentArgs.restoreContinuedState();
initGuiAfterTransformMode();
slotEditingFinished();
} else {
KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root();
cancelStroke();
startStroke(savedMode, true);
KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform());
}
} else {
initTransformMode(m_currentArgs.mode());
slotEditingFinished();
}
}
void KisToolTransform::slotRestartTransform()
{
if (!m_strokeId || !m_transaction.rootNode()) return;
KisNodeSP root = m_transaction.rootNode();
KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
ToolTransformArgs savedArgs(m_currentArgs);
cancelStroke();
startStroke(savedArgs.mode(), true);
}
void KisToolTransform::slotEditingFinished()
{
commitChanges();
}
void KisToolTransform::slotUpdateToWarpType()
{
setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode);
}
void KisToolTransform::slotUpdateToPerspectiveType()
{
setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode);
}
void KisToolTransform::slotUpdateToFreeTransformType()
{
setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode);
}
void KisToolTransform::slotUpdateToLiquifyType()
{
setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode);
}
void KisToolTransform::slotUpdateToCageType()
{
setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode);
}
void KisToolTransform::setShearY(double shear)
{
m_optionsWidget->slotSetShearY(shear);
}
void KisToolTransform::setShearX(double shear)
{
m_optionsWidget->slotSetShearX(shear);
}
void KisToolTransform::setScaleY(double scale)
{
m_optionsWidget->slotSetScaleY(scale);
}
void KisToolTransform::setScaleX(double scale)
{
m_optionsWidget->slotSetScaleX(scale);
}
void KisToolTransform::setTranslateY(double translation)
{
m_optionsWidget->slotSetTranslateY(translation);
}
void KisToolTransform::setTranslateX(double translation)
{
m_optionsWidget->slotSetTranslateX(translation);
}
diff --git a/plugins/tools/tool_transform2/kis_transform_utils.cpp b/plugins/tools/tool_transform2/kis_transform_utils.cpp
index 1fbbd525be..8a30d79d36 100644
--- a/plugins/tools/tool_transform2/kis_transform_utils.cpp
+++ b/plugins/tools/tool_transform2/kis_transform_utils.cpp
@@ -1,490 +1,490 @@
/*
* 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_transform_utils.h"
#include <cmath>
#include <QTransform>
#include <KoUnit.h>
#include "tool_transform_args.h"
#include "kis_paint_device.h"
#include "kis_algebra_2d.h"
#include "transform_transaction_properties.h"
struct TransformTransactionPropertiesRegistrar {
TransformTransactionPropertiesRegistrar() {
qRegisterMetaType<TransformTransactionProperties>("TransformTransactionProperties");
}
};
static TransformTransactionPropertiesRegistrar __registrar1;
struct ToolTransformArgsRegistrar {
ToolTransformArgsRegistrar() {
qRegisterMetaType<ToolTransformArgs>("ToolTransformArgs");
}
};
static ToolTransformArgsRegistrar __registrar2;
struct QPainterPathRegistrar {
QPainterPathRegistrar() {
qRegisterMetaType<QPainterPath>("QPainterPath");
}
};
static QPainterPathRegistrar __registrar3;
const int KisTransformUtils::rotationHandleVisualRadius = 12;
const int KisTransformUtils::rotationHandleRadius = 8;
const int KisTransformUtils::handleVisualRadius = 12;
const int KisTransformUtils::handleRadius = 8;
QTransform KisTransformUtils::imageToFlakeTransform(const KisCoordinatesConverter *converter)
{
return converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
}
qreal KisTransformUtils::effectiveHandleGrabRadius(const KisCoordinatesConverter *converter)
{
QPointF handleRadiusPt = flakeToImage(converter, QPointF(handleRadius, handleRadius));
return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y();
}
qreal KisTransformUtils::effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter)
{
QPointF handleRadiusPt = flakeToImage(converter, QPointF(rotationHandleRadius, rotationHandleRadius));
return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y();
}
qreal KisTransformUtils::scaleFromAffineMatrix(const QTransform &t) {
return KoUnit::approxTransformScale(t);
}
qreal KisTransformUtils::scaleFromPerspectiveMatrixX(const QTransform &t, const QPointF &basePt) {
const QPointF pt = basePt + QPointF(1.0, 0);
return kisDistance(t.map(pt), t.map(basePt));
}
qreal KisTransformUtils::scaleFromPerspectiveMatrixY(const QTransform &t, const QPointF &basePt) {
const QPointF pt = basePt + QPointF(0, 1.0);
return kisDistance(t.map(pt), t.map(basePt));
}
qreal KisTransformUtils::effectiveSize(const QRectF &rc) {
return 0.5 * (rc.width() + rc.height());
}
bool KisTransformUtils::thumbnailTooSmall(const QTransform &resultThumbTransform, const QRect &originalImageRect)
{
return KisAlgebra2D::minDimension(resultThumbTransform.mapRect(originalImageRect)) < 32;
}
QRectF handleRectImpl(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint, qreal *dOutX, qreal *dOutY) {
const qreal handlesExtraScaleX =
KisTransformUtils::scaleFromPerspectiveMatrixX(t, basePoint);
const qreal handlesExtraScaleY =
KisTransformUtils::scaleFromPerspectiveMatrixY(t, basePoint);
const qreal maxD = 0.2 * KisTransformUtils::effectiveSize(limitingRect);
const qreal dX = qMin(maxD, radius / handlesExtraScaleX);
const qreal dY = qMin(maxD, radius / handlesExtraScaleY);
QRectF handleRect(-0.5 * dX, -0.5 * dY, dX, dY);
if (dOutX) {
*dOutX = dX;
}
if (dOutY) {
*dOutY = dY;
}
return handleRect;
}
QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY) {
return handleRectImpl(radius, t, limitingRect, limitingRect.center(), dOutX, dOutY);
}
QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint) {
return handleRectImpl(radius, t, limitingRect, basePoint, 0, 0);
}
QPointF KisTransformUtils::clipInRect(QPointF p, QRectF r)
{
QPointF center = r.center();
QPointF t = p - center;
r.translate(- center);
if (t.y() != 0) {
if (t.x() != 0) {
double slope = t.y() / t.x();
if (t.x() < r.left()) {
t.setY(r.left() * slope);
t.setX(r.left());
}
else if (t.x() > r.right()) {
t.setY(r.right() * slope);
t.setX(r.right());
}
if (t.y() < r.top()) {
t.setX(r.top() / slope);
t.setY(r.top());
}
else if (t.y() > r.bottom()) {
t.setX(r.bottom() / slope);
t.setY(r.bottom());
}
}
else {
if (t.y() < r.top())
t.setY(r.top());
else if (t.y() > r.bottom())
t.setY(r.bottom());
}
}
else {
if (t.x() < r.left())
t.setX(r.left());
else if (t.x() > r.right())
t.setX(r.right());
}
t += center;
return t;
}
KisTransformUtils::MatricesPack::MatricesPack(const ToolTransformArgs &args)
{
TS = QTransform::fromTranslate(-args.originalCenter().x(), -args.originalCenter().y());
SC = QTransform::fromScale(args.scaleX(), args.scaleY());
S.shear(0, args.shearY()); S.shear(args.shearX(), 0);
if (args.mode() == ToolTransformArgs::FREE_TRANSFORM) {
P.rotate(180. * args.aX() / M_PI, QVector3D(1, 0, 0));
P.rotate(180. * args.aY() / M_PI, QVector3D(0, 1, 0));
P.rotate(180. * args.aZ() / M_PI, QVector3D(0, 0, 1));
projectedP = P.toTransform(args.cameraPos().z());
} else if (args.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
projectedP = args.flattenedPerspectiveTransform();
P = QMatrix4x4(projectedP);
}
QPointF translation = args.transformedCenter();
T = QTransform::fromTranslate(translation.x(), translation.y());
}
QTransform KisTransformUtils::MatricesPack::finalTransform() const
{
return TS * SC * S * projectedP * T;
}
bool KisTransformUtils::checkImageTooBig(const QRectF &bounds, const MatricesPack &m)
{
bool imageTooBig = false;
QMatrix4x4 unprojectedMatrix = QMatrix4x4(m.T) * m.P * QMatrix4x4(m.TS * m.SC * m.S);
QVector<QPointF> points;
points << bounds.topLeft();
points << bounds.topRight();
points << bounds.bottomRight();
points << bounds.bottomLeft();
Q_FOREACH (const QPointF &pt, points) {
QVector4D v(pt.x(), pt.y(), 0, 1);
v = unprojectedMatrix * v;
qreal z = v.z() / v.w();
imageTooBig = z > 1024.0;
if (imageTooBig) {
break;
}
}
return imageTooBig;
}
#include <kis_transform_worker.h>
#include <kis_perspectivetransform_worker.h>
#include <kis_warptransform_worker.h>
#include <kis_cage_transform_worker.h>
#include <kis_liquify_transform_worker.h>
KisTransformWorker KisTransformUtils::createTransformWorker(const ToolTransformArgs &config,
KisPaintDeviceSP device,
KoUpdaterPtr updater,
QVector3D *transformedCenter /* OUT */)
{
{
KisTransformWorker t(0,
config.scaleX(), config.scaleY(),
config.shearX(), config.shearY(),
config.originalCenter().x(),
config.originalCenter().y(),
config.aZ(),
0, // set X and Y translation
0, // to null for calculation
0,
config.filter());
*transformedCenter = QVector3D(t.transform().map(config.originalCenter()));
}
QPointF translation = config.transformedCenter() - (*transformedCenter).toPointF();
KisTransformWorker transformWorker(device,
config.scaleX(), config.scaleY(),
config.shearX(), config.shearY(),
config.originalCenter().x(),
config.originalCenter().y(),
config.aZ(),
(int)(translation.x()),
(int)(translation.y()),
updater,
config.filter());
return transformWorker;
}
void KisTransformUtils::transformDevice(const ToolTransformArgs &config,
KisPaintDeviceSP device,
KisProcessingVisitor::ProgressHelper *helper)
{
if (config.mode() == ToolTransformArgs::WARP) {
KoUpdaterPtr updater = helper->updater();
KisWarpTransformWorker worker(config.warpType(),
device,
config.origPoints(),
config.transfPoints(),
config.alpha(),
updater);
worker.run();
} else if (config.mode() == ToolTransformArgs::CAGE) {
KoUpdaterPtr updater = helper->updater();
KisCageTransformWorker worker(device,
config.origPoints(),
updater,
config.pixelPrecision());
worker.prepareTransform();
worker.setTransformedCage(config.transfPoints());
worker.run();
- } else if (config.mode() == ToolTransformArgs::LIQUIFY) {
+ } else if (config.mode() == ToolTransformArgs::LIQUIFY && config.liquifyWorker()) {
KoUpdaterPtr updater = helper->updater();
//FIXME:
Q_UNUSED(updater);
config.liquifyWorker()->run(device);
} else {
QVector3D transformedCenter;
KoUpdaterPtr updater1 = helper->updater();
KoUpdaterPtr updater2 = helper->updater();
KisTransformWorker transformWorker =
createTransformWorker(config, device, updater1, &transformedCenter);
transformWorker.run();
if (config.mode() == ToolTransformArgs::FREE_TRANSFORM) {
KisPerspectiveTransformWorker perspectiveWorker(device,
config.transformedCenter(),
config.aX(),
config.aY(),
config.cameraPos().z(),
updater2);
perspectiveWorker.run();
} else if (config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
QTransform T =
QTransform::fromTranslate(config.transformedCenter().x(),
config.transformedCenter().y());
KisPerspectiveTransformWorker perspectiveWorker(device,
T.inverted() * config.flattenedPerspectiveTransform() * T,
updater2);
perspectiveWorker.run();
}
}
}
QRect KisTransformUtils::needRect(const ToolTransformArgs &config,
const QRect &rc,
const QRect &srcBounds)
{
QRect result = rc;
if (config.mode() == ToolTransformArgs::WARP) {
KisWarpTransformWorker worker(config.warpType(),
0,
config.origPoints(),
config.transfPoints(),
config.alpha(),
0);
result = worker.approxNeedRect(rc, srcBounds);
} else if (config.mode() == ToolTransformArgs::CAGE) {
KisCageTransformWorker worker(0,
config.origPoints(),
0,
config.pixelPrecision());
worker.setTransformedCage(config.transfPoints());
result = worker.approxNeedRect(rc, srcBounds);
} else if (config.mode() == ToolTransformArgs::LIQUIFY) {
result = config.liquifyWorker() ?
config.liquifyWorker()->approxNeedRect(rc, srcBounds) : rc;
} else {
KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!");
}
return result;
}
QRect KisTransformUtils::changeRect(const ToolTransformArgs &config,
const QRect &rc)
{
QRect result = rc;
if (config.mode() == ToolTransformArgs::WARP) {
KisWarpTransformWorker worker(config.warpType(),
0,
config.origPoints(),
config.transfPoints(),
config.alpha(),
0);
result = worker.approxChangeRect(rc);
} else if (config.mode() == ToolTransformArgs::CAGE) {
KisCageTransformWorker worker(0,
config.origPoints(),
0,
config.pixelPrecision());
worker.setTransformedCage(config.transfPoints());
result = worker.approxChangeRect(rc);
} else if (config.mode() == ToolTransformArgs::LIQUIFY) {
result = config.liquifyWorker() ?
config.liquifyWorker()->approxChangeRect(rc) : rc;
} else {
KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!");
}
return result;
}
KisTransformUtils::AnchorHolder::AnchorHolder(bool enabled, ToolTransformArgs *config)
: m_enabled(enabled),
m_config(config)
{
if (!m_enabled) return;
m_staticPoint = m_config->originalCenter() + m_config->rotationCenterOffset();
const KisTransformUtils::MatricesPack m(*m_config);
m_oldStaticPointInView = m.finalTransform().map(m_staticPoint);
}
KisTransformUtils::AnchorHolder::~AnchorHolder() {
if (!m_enabled) return;
const KisTransformUtils::MatricesPack m(*m_config);
const QPointF newStaticPointInView = m.finalTransform().map(m_staticPoint);
const QPointF diff = m_oldStaticPointInView - newStaticPointInView;
m_config->setTransformedCenter(m_config->transformedCenter() + diff);
}
void KisTransformUtils::setDefaultWarpPoints(int pointsPerLine,
const TransformTransactionProperties *transaction,
ToolTransformArgs *config)
{
static const int DEFAULT_POINTS_PER_LINE = 3;
if (pointsPerLine < 0) {
pointsPerLine = DEFAULT_POINTS_PER_LINE;
}
int nbPoints = pointsPerLine * pointsPerLine;
QVector<QPointF> origPoints(nbPoints);
QVector<QPointF> transfPoints(nbPoints);
qreal gridSpaceX, gridSpaceY;
if (nbPoints == 1) {
//there is actually no grid
origPoints[0] = transaction->originalCenterGeometric();
transfPoints[0] = transaction->originalCenterGeometric();
}
else if (nbPoints > 1) {
gridSpaceX = transaction->originalRect().width() / (pointsPerLine - 1);
gridSpaceY = transaction->originalRect().height() / (pointsPerLine - 1);
double y = transaction->originalRect().top();
for (int i = 0; i < pointsPerLine; ++i) {
double x = transaction->originalRect().left();
for (int j = 0 ; j < pointsPerLine; ++j) {
origPoints[i * pointsPerLine + j] = QPointF(x, y);
transfPoints[i * pointsPerLine + j] = QPointF(x, y);
x += gridSpaceX;
}
y += gridSpaceY;
}
}
config->setDefaultPoints(nbPoints > 0);
config->setPoints(origPoints, transfPoints);
}
ToolTransformArgs KisTransformUtils::resetArgsForMode(ToolTransformArgs::TransformMode mode,
const QString &filterId,
const TransformTransactionProperties &transaction)
{
ToolTransformArgs args;
args.setOriginalCenter(transaction.originalCenterGeometric());
args.setTransformedCenter(transaction.originalCenterGeometric());
args.setFilterId(filterId);
if (mode == ToolTransformArgs::FREE_TRANSFORM) {
args.setMode(ToolTransformArgs::FREE_TRANSFORM);
} else if (mode == ToolTransformArgs::WARP) {
args.setMode(ToolTransformArgs::WARP);
KisTransformUtils::setDefaultWarpPoints(-1, &transaction, &args);
args.setEditingTransformPoints(false);
} else if (mode == ToolTransformArgs::CAGE) {
args.setMode(ToolTransformArgs::CAGE);
args.setEditingTransformPoints(true);
} else if (mode == ToolTransformArgs::LIQUIFY) {
args.setMode(ToolTransformArgs::LIQUIFY);
const QRect srcRect = transaction.originalRect().toAlignedRect();
if (!srcRect.isEmpty()) {
args.initLiquifyTransformMode(transaction.originalRect().toAlignedRect());
}
} else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) {
args.setMode(ToolTransformArgs::PERSPECTIVE_4POINT);
}
return args;
}
diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
index 42d405feae..71622304d9 100644
--- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
+++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
@@ -1,717 +1,713 @@
/*
* 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 "transform_stroke_strategy.h"
#include <QMutexLocker>
#include "kundo2commandextradata.h"
#include "kis_node_progress_proxy.h"
#include <klocalizedstring.h>
#include <kis_node.h>
#include <kis_group_layer.h>
#include <kis_external_layer_iface.h>
#include <kis_transaction.h>
#include <kis_painter.h>
#include <kis_transform_worker.h>
#include <kis_transform_mask.h>
#include "kis_transform_mask_adapter.h"
#include "kis_transform_utils.h"
#include "kis_abstract_projection_plane.h"
#include "kis_recalculate_transform_mask_job.h"
#include "kis_projection_leaf.h"
#include "kis_modify_transform_mask_command.h"
#include "kis_sequential_iterator.h"
#include "kis_selection_mask.h"
#include "kis_image_config.h"
#include "kis_layer_utils.h"
#include <QQueue>
#include <KisDeleteLaterWrapper.h>
#include "transform_transaction_properties.h"
#include "krita_container_utils.h"
#include "commands_new/kis_saved_commands.h"
#include "kis_command_ids.h"
#include "KisRunnableStrokeJobUtils.h"
#include "commands_new/KisHoldUIUpdatesCommand.h"
#include "KisDecoratedNodeInterface.h"
TransformStrokeStrategy::TransformStrokeStrategy(ToolTransformArgs::TransformMode mode,
bool workRecursively,
const QString &filterId,
bool forceReset,
KisNodeSP rootNode,
KisSelectionSP selection,
KisStrokeUndoFacade *undoFacade,
KisUpdatesFacade *updatesFacade)
: KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade),
m_updatesFacade(updatesFacade),
m_mode(mode),
m_workRecursively(workRecursively),
m_filterId(filterId),
m_forceReset(forceReset),
m_selection(selection)
{
KIS_SAFE_ASSERT_RECOVER_NOOP(!selection || !dynamic_cast<KisTransformMask*>(rootNode.data()));
m_rootNode = rootNode;
setMacroId(KisCommandUtils::TransformToolId);
}
TransformStrokeStrategy::~TransformStrokeStrategy()
{
}
KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev)
{
KisPaintDeviceSP cache;
if (m_selection) {
QRect srcRect = m_selection->selectedExactRect();
cache = dev->createCompositionSourceDevice();
KisPainter gc(cache);
gc.setSelection(m_selection);
gc.bitBlt(srcRect.topLeft(), dev, srcRect);
} else {
cache = dev->createCompositionSourceDevice(dev);
}
return cache;
}
bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src)
{
QMutexLocker l(&m_devicesCacheMutex);
return m_devicesCacheHash.contains(src.data());
}
void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache)
{
QMutexLocker l(&m_devicesCacheMutex);
m_devicesCacheHash.insert(src.data(), cache);
}
KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src)
{
QMutexLocker l(&m_devicesCacheMutex);
KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data());
if (!cache) {
warnKrita << "WARNING: Transform Stroke: the device is absent in cache!";
}
return cache;
}
bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const
{
return m_selection &&
(device == m_selection->pixelSelection().data() ||
device == m_selection->projection().data());
}
void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
{
TransformData *td = dynamic_cast<TransformData*>(data);
ClearSelectionData *csd = dynamic_cast<ClearSelectionData*>(data);
PreparePreviewData *ppd = dynamic_cast<PreparePreviewData*>(data);
TransformAllData *runAllData = dynamic_cast<TransformAllData*>(data);
if (runAllData) {
// here we only save the passed args, actual
// transformation will be performed during
// finish job
m_savedTransformArgs = runAllData->config;
} else if (ppd) {
KisNodeSP rootNode = m_rootNode;
KisNodeList processedNodes = m_processedNodes;
KisPaintDeviceSP previewDevice;
if (rootNode->childCount() || !rootNode->paintDevice()) {
if (KisTransformMask* tmask =
dynamic_cast<KisTransformMask*>(rootNode.data())) {
previewDevice = createDeviceCache(tmask->buildPreviewDevice());
KIS_SAFE_ASSERT_RECOVER(!m_selection) {
m_selection = 0;
}
} else if (KisGroupLayer *group = dynamic_cast<KisGroupLayer*>(rootNode.data())) {
const QRect bounds = group->image()->bounds();
KisImageSP clonedImage = new KisImage(0,
bounds.width(),
bounds.height(),
group->colorSpace(),
"transformed_image");
KisGroupLayerSP clonedGroup = dynamic_cast<KisGroupLayer*>(group->clone().data());
// In case the group is pass-through, it needs to be disabled for the preview,
// otherwise it will crash (no parent for a preview leaf).
// Also it needs to be done before setting the root layer for clonedImage.
// Result: preview for pass-through group is the same as for standard group
// (i.e. filter layers in the group won't affect the layer stack for a moment).
clonedGroup->setPassThroughMode(false);
clonedImage->setRootLayer(clonedGroup);
QQueue<KisNodeSP> linearizedSrcNodes;
KisLayerUtils::recursiveApplyNodes(rootNode, [&linearizedSrcNodes] (KisNodeSP node) {
linearizedSrcNodes.enqueue(node);
});
KisLayerUtils::recursiveApplyNodes(KisNodeSP(clonedGroup), [&linearizedSrcNodes, processedNodes] (KisNodeSP node) {
KisNodeSP srcNode = linearizedSrcNodes.dequeue();
if (!processedNodes.contains(srcNode)) {
node->setVisible(false);
}
});
clonedImage->refreshGraph();
KisLayerUtils::refreshHiddenAreaAsync(clonedImage, clonedGroup, clonedImage->bounds());
KisLayerUtils::forceAllDelayedNodesUpdate(clonedGroup);
clonedImage->waitForDone();
previewDevice = createDeviceCache(clonedImage->projection());
previewDevice->setDefaultBounds(group->projection()->defaultBounds());
// we delete the cloned image in GUI thread to ensure
// no signals are still pending
makeKisDeleteLaterWrapper(clonedImage)->deleteLater();
} else {
rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection();
previewDevice = createDeviceCache(rootNode->projection());
}
} else {
KisPaintDeviceSP cacheDevice = createDeviceCache(rootNode->paintDevice());
if (dynamic_cast<KisSelectionMask*>(rootNode.data())) {
KIS_SAFE_ASSERT_RECOVER (cacheDevice->colorSpace()->colorModelId() == GrayAColorModelID &&
cacheDevice->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) {
cacheDevice->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id()));
}
previewDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
const QRect srcRect = cacheDevice->exactBounds();
KisSequentialConstIterator srcIt(cacheDevice, srcRect);
KisSequentialIterator dstIt(previewDevice, srcRect);
const int pixelSize = previewDevice->colorSpace()->pixelSize();
KisImageConfig cfg(true);
KoColor pixel(cfg.selectionOverlayMaskColor(), previewDevice->colorSpace());
const qreal coeff = 1.0 / 255.0;
const qreal baseOpacity = 0.5;
while (srcIt.nextPixel() && dstIt.nextPixel()) {
qreal gray = srcIt.rawDataConst()[0];
qreal alpha = srcIt.rawDataConst()[1];
pixel.setOpacity(quint8(gray * alpha * baseOpacity * coeff));
memcpy(dstIt.rawData(), pixel.data(), pixelSize);
}
} else {
previewDevice = cacheDevice;
}
putDeviceCache(rootNode->paintDevice(), cacheDevice);
}
emit sigPreviewDeviceReady(previewDevice);
- } else if(td) {
+ }
+ else if (td) {
if (td->destination == TransformData::PAINT_DEVICE) {
QRect oldExtent = td->node->extent();
KisPaintDeviceSP device = td->node->paintDevice();
if (device && !checkBelongsToSelection(device)) {
KisPaintDeviceSP cachedPortion = getDeviceCache(device);
Q_ASSERT(cachedPortion);
KisTransaction transaction(device);
KisProcessingVisitor::ProgressHelper helper(td->node);
transformAndMergeDevice(td->config, cachedPortion,
device, &helper);
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
td->node->setDirty(oldExtent | td->node->extent());
} else if (KisExternalLayer *extLayer =
dynamic_cast<KisExternalLayer*>(td->node.data())) {
if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM ||
(td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT &&
extLayer->supportsPerspectiveTransform())) {
QVector3D transformedCenter;
KisTransformWorker w = KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter);
QTransform t = w.transform();
runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
}
} else if (KisTransformMask *transformMask =
dynamic_cast<KisTransformMask*>(td->node.data())) {
runAndSaveCommand(KUndo2CommandSP(
new KisModifyTransformMaskCommand(transformMask,
KisTransformMaskParamsInterfaceSP(
new KisTransformMaskAdapter(td->config)))),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
}
} else if (m_selection) {
/**
* We use usual transaction here, because we cannot calsulate
* transformation for perspective and warp workers.
*/
KisTransaction transaction(m_selection->pixelSelection());
KisProcessingVisitor::ProgressHelper helper(td->node);
KisTransformUtils::transformDevice(td->config,
m_selection->pixelSelection(),
&helper);
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
}
} else if (csd) {
KisPaintDeviceSP device = csd->node->paintDevice();
if (device && !checkBelongsToSelection(device)) {
if (!haveDeviceInCache(device)) {
putDeviceCache(device, createDeviceCache(device));
}
clearSelection(device);
/**
* Selection masks might have an overlay enabled, we should disable that
*/
if (KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(csd->node.data())) {
KisSelectionSP selection = mask->selection();
if (selection) {
selection->setVisible(false);
m_deactivatedSelections.append(selection);
mask->setDirty();
}
}
} else if (KisExternalLayer *externalLayer = dynamic_cast<KisExternalLayer*>(csd->node.data())) {
externalLayer->projectionLeaf()->setTemporaryHiddenFromRendering(true);
externalLayer->setDirty();
m_hiddenProjectionLeaves.append(csd->node);
} else if (KisTransformMask *transformMask =
dynamic_cast<KisTransformMask*>(csd->node.data())) {
runAndSaveCommand(KUndo2CommandSP(
new KisModifyTransformMaskCommand(transformMask,
KisTransformMaskParamsInterfaceSP(
new KisDumbTransformMaskParams(true)))),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
}
} else {
KisStrokeStrategyUndoCommandBased::doStrokeCallback(data);
}
}
void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device)
{
KisTransaction transaction(device);
if (m_selection) {
device->clearSelection(m_selection);
} else {
QRect oldExtent = device->extent();
device->clear();
device->setDirty(oldExtent);
}
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
}
void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config,
KisPaintDeviceSP src,
KisPaintDeviceSP dst,
KisProcessingVisitor::ProgressHelper *helper)
{
KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0;
KisTransformUtils::transformDevice(config, src, helper);
if (src != dst) {
QRect mergeRect = src->extent();
KisPainter painter(dst);
painter.setProgress(mergeUpdater);
painter.bitBlt(mergeRect.topLeft(), src, mergeRect);
painter.end();
}
}
struct TransformExtraData : public KUndo2CommandExtraData
{
ToolTransformArgs savedTransformArgs;
KisNodeSP rootNode;
KisNodeList transformedNodes;
KUndo2CommandExtraData* clone() const override {
return new TransformExtraData(*this);
}
};
void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_savedTransformArgs);
TransformExtraData *data = new TransformExtraData();
data->savedTransformArgs = *m_savedTransformArgs;
data->rootNode = m_rootNode;
data->transformedNodes = m_processedNodes;
command->setExtraData(data);
KisSavedMacroCommand *macroCommand = dynamic_cast<KisSavedMacroCommand*>(command);
KIS_SAFE_ASSERT_RECOVER_NOOP(macroCommand);
if (m_overriddenCommand && macroCommand) {
macroCommand->setOverrideInfo(m_overriddenCommand, m_skippedWhileMergeCommands);
}
KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(command);
}
bool TransformStrokeStrategy::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes)
{
const TransformExtraData *data = dynamic_cast<const TransformExtraData*>(command->extraData());
if (data) {
*args = data->savedTransformArgs;
*rootNode = data->rootNode;
*transformedNodes = data->transformedNodes;
}
return bool(data);
}
QList<KisNodeSP> TransformStrokeStrategy::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive)
{
QList<KisNodeSP> result;
auto fetchFunc =
[&result, mode, root] (KisNodeSP node) {
if (node->isEditable(node == root) &&
(!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) &&
!node->inherits("KisFileLayer") &&
(!node->inherits("KisTransformMask") || node == root)) {
result << node;
}
};
if (recursive) {
KisLayerUtils::recursiveApplyNodes(root, fetchFunc);
} else {
fetchFunc(root);
}
return result;
}
bool TransformStrokeStrategy::tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args)
{
bool result = false;
if (KisTransformMaskSP mask =
dynamic_cast<KisTransformMask*>(node.data())) {
KisTransformMaskParamsInterfaceSP savedParams =
mask->transformParams();
KisTransformMaskAdapter *adapter =
dynamic_cast<KisTransformMaskAdapter*>(savedParams.data());
if (adapter) {
*args = adapter->transformArgs();
result = true;
}
}
return result;
}
bool TransformStrokeStrategy::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *outArgs,
ToolTransformArgs::TransformMode mode,
KisNodeSP currentNode,
KisNodeList selectedNodes,
QVector<KisStrokeJobData *> *undoJobs)
{
bool result = false;
const KUndo2Command *lastCommand = undoFacade()->lastExecutedCommand();
KisNodeSP oldRootNode;
KisNodeList oldTransformedNodes;
ToolTransformArgs args;
if (lastCommand &&
TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, &args, &oldRootNode, &oldTransformedNodes) &&
args.mode() == mode &&
oldRootNode == currentNode) {
if (KritaUtils::compareListsUnordered(oldTransformedNodes, selectedNodes)) {
args.saveContinuedState();
*outArgs = args;
const KisSavedMacroCommand *command = dynamic_cast<const KisSavedMacroCommand*>(lastCommand);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(command, false);
// the jobs are fetched as !shouldGoToHistory,
// so there is no need to put them into
// m_skippedWhileMergeCommands
command->getCommandExecutionJobs(undoJobs, true, false);
m_overriddenCommand = command;
result = true;
}
}
return result;
}
void TransformStrokeStrategy::initStrokeCallback()
{
KisStrokeStrategyUndoCommandBased::initStrokeCallback();
if (m_selection) {
m_selection->setVisible(false);
m_deactivatedSelections.append(m_selection);
}
KisSelectionMaskSP overlaySelectionMask =
dynamic_cast<KisSelectionMask*>(m_rootNode->graphListener()->graphOverlayNode());
if (overlaySelectionMask) {
overlaySelectionMask->setDecorationsVisible(false);
m_deactivatedOverlaySelectionMask = overlaySelectionMask;
}
ToolTransformArgs initialTransformArgs;
m_processedNodes = fetchNodesList(m_mode, m_rootNode, m_workRecursively);
bool argsAreInitialized = false;
QVector<KisStrokeJobData *> lastCommandUndoJobs;
if (!m_forceReset && tryFetchArgsFromCommandAndUndo(&initialTransformArgs,
m_mode,
m_rootNode,
m_processedNodes,
&lastCommandUndoJobs)) {
argsAreInitialized = true;
} else if (!m_forceReset && tryInitArgsFromNode(m_rootNode, &initialTransformArgs)) {
argsAreInitialized = true;
}
QVector<KisStrokeJobData *> extraInitJobs;
extraInitJobs << new Data(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::INITIALIZING), false, KisStrokeJobData::BARRIER);
extraInitJobs << lastCommandUndoJobs;
KritaUtils::addJobSequential(extraInitJobs, [this]() {
/**
* We must request shape layers to rerender areas outside image bounds
*/
KisLayerUtils::forceAllHiddenOriginalsUpdate(m_rootNode);
});
KritaUtils::addJobBarrier(extraInitJobs, [this]() {
/**
* We must ensure that the currently selected subtree
* has finished all its updates.
*/
KisLayerUtils::forceAllDelayedNodesUpdate(m_rootNode);
});
/// Disable all decorated nodes to generate outline
/// and preview correctly. We will enable them back
/// as soon as preview generation is finished.
KritaUtils::addJobBarrier(extraInitJobs, [this]() {
Q_FOREACH (KisNodeSP node, m_processedNodes) {
KisDecoratedNodeInterface *decoratedNode = dynamic_cast<KisDecoratedNodeInterface*>(node.data());
if (decoratedNode && decoratedNode->decorationsVisible()) {
decoratedNode->setDecorationsVisible(false);
m_disabledDecoratedNodes << decoratedNode;
}
}
});
KritaUtils::addJobBarrier(extraInitJobs, [this, initialTransformArgs, argsAreInitialized]() mutable {
QRect srcRect;
if (m_selection) {
srcRect = m_selection->selectedExactRect();
} else {
srcRect = QRect();
Q_FOREACH (KisNodeSP node, m_processedNodes) {
// group layers may have a projection of layers
// that are locked and will not be transformed
if (node->inherits("KisGroupLayer")) continue;
if (const KisTransformMask *mask = dynamic_cast<const KisTransformMask*>(node.data())) {
srcRect |= mask->sourceDataBounds();
} else if (const KisSelectionMask *mask = dynamic_cast<const KisSelectionMask*>(node.data())) {
srcRect |= mask->selection()->selectedExactRect();
} else {
srcRect |= node->exactBounds();
}
}
}
TransformTransactionProperties transaction(srcRect, &initialTransformArgs, m_rootNode, m_processedNodes);
if (!argsAreInitialized) {
initialTransformArgs = KisTransformUtils::resetArgsForMode(m_mode, m_filterId, transaction);
}
this->m_initialTransformArgs = initialTransformArgs;
emit this->sigTransactionGenerated(transaction, initialTransformArgs, this);
});
extraInitJobs << new PreparePreviewData();
Q_FOREACH (KisNodeSP node, m_processedNodes) {
extraInitJobs << new ClearSelectionData(node);
}
/// recover back visibility of decorated nodes
KritaUtils::addJobBarrier(extraInitJobs, [this]() {
Q_FOREACH (KisDecoratedNodeInterface *decoratedNode, m_disabledDecoratedNodes) {
decoratedNode->setDecorationsVisible(true);
}
m_disabledDecoratedNodes.clear();
});
extraInitJobs << new Data(toQShared(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::FINALIZING)), false, KisStrokeJobData::BARRIER);
if (!lastCommandUndoJobs.isEmpty()) {
KIS_SAFE_ASSERT_RECOVER_NOOP(m_overriddenCommand);
for (auto it = extraInitJobs.begin(); it != extraInitJobs.end(); ++it) {
(*it)->setCancellable(false);
}
}
addMutatedJobs(extraInitJobs);
}
void TransformStrokeStrategy::finishStrokeImpl(bool applyTransform, const ToolTransformArgs &args)
{
/**
* Since our finishStrokeCallback() initiates new jobs,
* cancellation request may come even after
* finishStrokeCallback() (cancellations may be called
* until there are no jobs left in the stroke's queue).
*
* Therefore we should check for double-entry here and
* make sure the finilizing jobs are no cancellable.
*/
if (m_finalizingActionsStarted) return;
m_finalizingActionsStarted = true;
QVector<KisStrokeJobData *> mutatedJobs;
if (applyTransform) {
+ m_savedTransformArgs = args;
+
Q_FOREACH (KisNodeSP node, m_processedNodes) {
mutatedJobs << new TransformData(TransformData::PAINT_DEVICE,
args,
node);
}
mutatedJobs << new TransformData(TransformData::SELECTION,
args,
m_rootNode);
}
KritaUtils::addJobBarrier(mutatedJobs, [this, applyTransform]() {
Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) {
selection->setVisible(true);
}
Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) {
node->projectionLeaf()->setTemporaryHiddenFromRendering(false);
}
if (m_deactivatedOverlaySelectionMask) {
m_deactivatedOverlaySelectionMask->selection()->setVisible(true);
m_deactivatedOverlaySelectionMask->setDirty();
}
if (applyTransform) {
KisStrokeStrategyUndoCommandBased::finishStrokeCallback();
} else {
KisStrokeStrategyUndoCommandBased::cancelStrokeCallback();
}
});
for (auto it = mutatedJobs.begin(); it != mutatedJobs.end(); ++it) {
(*it)->setCancellable(false);
}
addMutatedJobs(mutatedJobs);
}
void TransformStrokeStrategy::finishStrokeCallback()
{
if (!m_savedTransformArgs || m_savedTransformArgs->isIdentity()) {
cancelStrokeCallback();
return;
}
finishStrokeImpl(true, *m_savedTransformArgs);
}
void TransformStrokeStrategy::cancelStrokeCallback()
{
- const bool shouldRecoverSavedInitialState =
- !m_initialTransformArgs.isIdentity();
-
- if (shouldRecoverSavedInitialState) {
- m_savedTransformArgs = m_initialTransformArgs;
- }
-
- finishStrokeImpl(shouldRecoverSavedInitialState, *m_savedTransformArgs);
+ finishStrokeImpl(!m_initialTransformArgs.isIdentity(), m_initialTransformArgs);
}
diff --git a/plugins/tools/tool_transform2/tool_transform_args.cc b/plugins/tools/tool_transform2/tool_transform_args.cc
index ce49669134..6c950b6a87 100644
--- a/plugins/tools/tool_transform2/tool_transform_args.cc
+++ b/plugins/tools/tool_transform2/tool_transform_args.cc
@@ -1,510 +1,494 @@
/*
* tool_transform_args.h - part of Krita
*
* Copyright (c) 2010 Marc Pegon <pe.marc@free.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "tool_transform_args.h"
#include <QDomElement>
#include <ksharedconfig.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include "kis_liquify_transform_worker.h"
#include "kis_dom_utils.h"
ToolTransformArgs::ToolTransformArgs()
- : m_mode(FREE_TRANSFORM)
- , m_defaultPoints(true)
- , m_origPoints {QVector<QPointF>()}
- , m_transfPoints {QVector<QPointF>()}
- , m_warpType(KisWarpTransformWorker::RIGID_TRANSFORM)
- , m_alpha(1.0)
- , m_transformedCenter(QPointF(0, 0))
- , m_originalCenter(QPointF(0, 0))
- , m_rotationCenterOffset(QPointF(0, 0))
- , m_aX(0)
- , m_aY(0)
- , m_aZ(0)
- , m_scaleX(1.0)
- , m_scaleY(1.0)
- , m_shearX(0.0)
- , m_shearY(0.0)
- , m_liquifyProperties(new KisLiquifyProperties())
- , m_pixelPrecision(8)
- , m_previewPixelPrecision(16)
+ : m_liquifyProperties(new KisLiquifyProperties())
{
KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform");
QString savedFilterId = configGroup.readEntry("filterId", "Bicubic");
setFilterId(savedFilterId);
m_transformAroundRotationCenter = configGroup.readEntry("transformAroundRotationCenter", "0").toInt();
}
void ToolTransformArgs::setFilterId(const QString &id) {
m_filter = KisFilterStrategyRegistry::instance()->value(id);
if (m_filter) {
KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform");
configGroup.writeEntry("filterId", id);
}
}
void ToolTransformArgs::setTransformAroundRotationCenter(bool value)
{
m_transformAroundRotationCenter = value;
KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform");
configGroup.writeEntry("transformAroundRotationCenter", int(value));
}
void ToolTransformArgs::init(const ToolTransformArgs& args)
{
m_mode = args.mode();
m_transformedCenter = args.transformedCenter();
m_originalCenter = args.originalCenter();
m_rotationCenterOffset = args.rotationCenterOffset();
m_transformAroundRotationCenter = args.transformAroundRotationCenter();
m_cameraPos = args.m_cameraPos;
m_aX = args.aX();
m_aY = args.aY();
m_aZ = args.aZ();
m_scaleX = args.scaleX();
m_scaleY = args.scaleY();
m_shearX = args.shearX();
m_shearY = args.shearY();
m_origPoints = args.origPoints(); //it's a copy
m_transfPoints = args.transfPoints();
m_warpType = args.warpType();
m_alpha = args.alpha();
m_defaultPoints = args.defaultPoints();
m_keepAspectRatio = args.keepAspectRatio();
m_filter = args.m_filter;
m_flattenedPerspectiveTransform = args.m_flattenedPerspectiveTransform;
m_editTransformPoints = args.m_editTransformPoints;
m_pixelPrecision = args.pixelPrecision();
m_previewPixelPrecision = args.previewPixelPrecision();
if (args.m_liquifyWorker) {
m_liquifyWorker.reset(new KisLiquifyTransformWorker(*args.m_liquifyWorker.data()));
}
m_continuedTransformation.reset(args.m_continuedTransformation ? new ToolTransformArgs(*args.m_continuedTransformation) : 0);
}
void ToolTransformArgs::clear()
{
m_origPoints.clear();
m_transfPoints.clear();
}
ToolTransformArgs::ToolTransformArgs(const ToolTransformArgs& args)
: m_liquifyProperties(new KisLiquifyProperties(*args.m_liquifyProperties.data()))
{
init(args);
}
KisToolChangesTrackerData *ToolTransformArgs::clone() const
{
return new ToolTransformArgs(*this);
}
ToolTransformArgs& ToolTransformArgs::operator=(const ToolTransformArgs& args)
{
+ if (this == &args) return *this;
+
clear();
m_liquifyProperties.reset(new KisLiquifyProperties(*args.m_liquifyProperties.data()));
init(args);
return *this;
}
bool ToolTransformArgs::operator==(const ToolTransformArgs& other) const
{
return
m_mode == other.m_mode &&
m_defaultPoints == other.m_defaultPoints &&
m_origPoints == other.m_origPoints &&
m_transfPoints == other.m_transfPoints &&
m_warpType == other.m_warpType &&
m_alpha == other.m_alpha &&
m_transformedCenter == other.m_transformedCenter &&
m_originalCenter == other.m_originalCenter &&
m_rotationCenterOffset == other.m_rotationCenterOffset &&
m_transformAroundRotationCenter == other.m_transformAroundRotationCenter &&
m_aX == other.m_aX &&
m_aY == other.m_aY &&
m_aZ == other.m_aZ &&
m_cameraPos == other.m_cameraPos &&
m_scaleX == other.m_scaleX &&
m_scaleY == other.m_scaleY &&
m_shearX == other.m_shearX &&
m_shearY == other.m_shearY &&
m_keepAspectRatio == other.m_keepAspectRatio &&
m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform &&
m_editTransformPoints == other.m_editTransformPoints &&
(m_liquifyProperties == other.m_liquifyProperties ||
*m_liquifyProperties == *other.m_liquifyProperties) &&
// pointer types
((m_filter && other.m_filter &&
m_filter->id() == other.m_filter->id())
|| m_filter == other.m_filter) &&
((m_liquifyWorker && other.m_liquifyWorker &&
*m_liquifyWorker == *other.m_liquifyWorker)
|| m_liquifyWorker == other.m_liquifyWorker) &&
m_pixelPrecision == other.m_pixelPrecision &&
m_previewPixelPrecision == other.m_previewPixelPrecision;
}
bool ToolTransformArgs::isSameMode(const ToolTransformArgs& other) const
{
if (m_mode != other.m_mode) return false;
bool result = true;
if (m_mode == FREE_TRANSFORM) {
result &= m_transformedCenter == other.m_transformedCenter;
result &= m_originalCenter == other.m_originalCenter;
result &= m_scaleX == other.m_scaleX;
result &= m_scaleY == other.m_scaleY;
result &= m_shearX == other.m_shearX;
result &= m_shearY == other.m_shearY;
result &= m_aX == other.m_aX;
result &= m_aY == other.m_aY;
result &= m_aZ == other.m_aZ;
} else if (m_mode == PERSPECTIVE_4POINT) {
result &= m_transformedCenter == other.m_transformedCenter;
result &= m_originalCenter == other.m_originalCenter;
result &= m_scaleX == other.m_scaleX;
result &= m_scaleY == other.m_scaleY;
result &= m_shearX == other.m_shearX;
result &= m_shearY == other.m_shearY;
result &= m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform;
} else if(m_mode == WARP || m_mode == CAGE) {
result &= m_origPoints == other.m_origPoints;
result &= m_transfPoints == other.m_transfPoints;
} else if (m_mode == LIQUIFY) {
result &= m_liquifyProperties &&
(m_liquifyProperties == other.m_liquifyProperties ||
*m_liquifyProperties == *other.m_liquifyProperties);
result &=
(m_liquifyWorker && other.m_liquifyWorker &&
*m_liquifyWorker == *other.m_liquifyWorker)
|| m_liquifyWorker == other.m_liquifyWorker;
} else {
KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "unknown transform mode");
}
return result;
}
ToolTransformArgs::ToolTransformArgs(TransformMode mode,
QPointF transformedCenter,
QPointF originalCenter,
QPointF rotationCenterOffset,
bool transformAroundRotationCenter,
double aX, double aY, double aZ,
double scaleX, double scaleY,
double shearX, double shearY,
KisWarpTransformWorker::WarpType warpType,
double alpha,
bool defaultPoints,
const QString &filterId,
int pixelPrecision, int previewPixelPrecision)
: m_mode(mode)
, m_defaultPoints(defaultPoints)
, m_origPoints {QVector<QPointF>()}
, m_transfPoints {QVector<QPointF>()}
, m_warpType(warpType)
, m_alpha(alpha)
, m_transformedCenter(transformedCenter)
, m_originalCenter(originalCenter)
, m_rotationCenterOffset(rotationCenterOffset)
, m_transformAroundRotationCenter(transformAroundRotationCenter)
, m_aX(aX)
, m_aY(aY)
, m_aZ(aZ)
, m_scaleX(scaleX)
, m_scaleY(scaleY)
, m_shearX(shearX)
, m_shearY(shearY)
, m_liquifyProperties(new KisLiquifyProperties())
, m_pixelPrecision(pixelPrecision)
, m_previewPixelPrecision(previewPixelPrecision)
{
setFilterId(filterId);
}
ToolTransformArgs::~ToolTransformArgs()
{
clear();
}
void ToolTransformArgs::translate(const QPointF &offset)
{
if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) {
m_originalCenter += offset;
m_rotationCenterOffset += offset;
m_transformedCenter += offset;
} else if(m_mode == WARP || m_mode == CAGE) {
for (auto &pt : m_origPoints) {
pt += offset;
}
for (auto &pt : m_transfPoints) {
pt += offset;
}
} else if (m_mode == LIQUIFY) {
KIS_ASSERT_RECOVER_RETURN(m_liquifyWorker);
m_liquifyWorker->translate(offset);
} else {
KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode");
}
}
bool ToolTransformArgs::isIdentity() const
{
if (m_mode == FREE_TRANSFORM) {
return (m_transformedCenter == m_originalCenter && m_scaleX == 1
&& m_scaleY == 1 && m_shearX == 0 && m_shearY == 0
&& m_aX == 0 && m_aY == 0 && m_aZ == 0);
} else if (m_mode == PERSPECTIVE_4POINT) {
return (m_transformedCenter == m_originalCenter && m_scaleX == 1
&& m_scaleY == 1 && m_shearX == 0 && m_shearY == 0
&& m_flattenedPerspectiveTransform.isIdentity());
} else if(m_mode == WARP || m_mode == CAGE) {
for (int i = 0; i < m_origPoints.size(); ++i)
if (m_origPoints[i] != m_transfPoints[i])
return false;
return true;
} else if (m_mode == LIQUIFY) {
// Not implemented!
return false;
} else {
KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode");
return true;
}
}
void ToolTransformArgs::initLiquifyTransformMode(const QRect &srcRect)
{
m_liquifyWorker.reset(new KisLiquifyTransformWorker(srcRect, 0, 8));
m_liquifyProperties->loadAndResetMode();
}
void ToolTransformArgs::saveLiquifyTransformMode() const
{
m_liquifyProperties->saveMode();
}
void ToolTransformArgs::toXML(QDomElement *e) const
{
e->setAttribute("mode", (int) m_mode);
QDomDocument doc = e->ownerDocument();
if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) {
QDomElement freeEl = doc.createElement("free_transform");
e->appendChild(freeEl);
KisDomUtils::saveValue(&freeEl, "transformedCenter", m_transformedCenter);
KisDomUtils::saveValue(&freeEl, "originalCenter", m_originalCenter);
KisDomUtils::saveValue(&freeEl, "rotationCenterOffset", m_rotationCenterOffset);
KisDomUtils::saveValue(&freeEl, "transformAroundRotationCenter", m_transformAroundRotationCenter);
KisDomUtils::saveValue(&freeEl, "aX", m_aX);
KisDomUtils::saveValue(&freeEl, "aY", m_aY);
KisDomUtils::saveValue(&freeEl, "aZ", m_aZ);
KisDomUtils::saveValue(&freeEl, "cameraPos", m_cameraPos);
KisDomUtils::saveValue(&freeEl, "scaleX", m_scaleX);
KisDomUtils::saveValue(&freeEl, "scaleY", m_scaleY);
KisDomUtils::saveValue(&freeEl, "shearX", m_shearX);
KisDomUtils::saveValue(&freeEl, "shearY", m_shearY);
KisDomUtils::saveValue(&freeEl, "keepAspectRatio", m_keepAspectRatio);
KisDomUtils::saveValue(&freeEl, "flattenedPerspectiveTransform", m_flattenedPerspectiveTransform);
KisDomUtils::saveValue(&freeEl, "filterId", m_filter->id());
} else if (m_mode == WARP || m_mode == CAGE) {
QDomElement warpEl = doc.createElement("warp_transform");
e->appendChild(warpEl);
KisDomUtils::saveValue(&warpEl, "defaultPoints", m_defaultPoints);
KisDomUtils::saveValue(&warpEl, "originalPoints", m_origPoints);
KisDomUtils::saveValue(&warpEl, "transformedPoints", m_transfPoints);
KisDomUtils::saveValue(&warpEl, "warpType", (int)m_warpType); // limited!
KisDomUtils::saveValue(&warpEl, "alpha", m_alpha);
if(m_mode == CAGE){
KisDomUtils::saveValue(&warpEl,"pixelPrecision",m_pixelPrecision);
KisDomUtils::saveValue(&warpEl,"previewPixelPrecision",m_previewPixelPrecision);
}
} else if (m_mode == LIQUIFY) {
QDomElement liqEl = doc.createElement("liquify_transform");
e->appendChild(liqEl);
m_liquifyProperties->toXML(&liqEl);
m_liquifyWorker->toXML(&liqEl);
} else {
KIS_ASSERT_RECOVER_RETURN(0 && "Unknown transform mode");
}
// m_editTransformPoints should not be saved since it is reset explicitly
}
ToolTransformArgs ToolTransformArgs::fromXML(const QDomElement &e)
{
ToolTransformArgs args;
int newMode = e.attribute("mode", "0").toInt();
if (newMode < 0 || newMode >= N_MODES) return ToolTransformArgs();
args.m_mode = (TransformMode) newMode;
// reset explicitly
args.m_editTransformPoints = false;
bool result = false;
if (args.m_mode == FREE_TRANSFORM || args.m_mode == PERSPECTIVE_4POINT) {
QDomElement freeEl;
QString filterId;
result =
KisDomUtils::findOnlyElement(e, "free_transform", &freeEl) &&
KisDomUtils::loadValue(freeEl, "transformedCenter", &args.m_transformedCenter) &&
KisDomUtils::loadValue(freeEl, "originalCenter", &args.m_originalCenter) &&
KisDomUtils::loadValue(freeEl, "rotationCenterOffset", &args.m_rotationCenterOffset) &&
KisDomUtils::loadValue(freeEl, "aX", &args.m_aX) &&
KisDomUtils::loadValue(freeEl, "aY", &args.m_aY) &&
KisDomUtils::loadValue(freeEl, "aZ", &args.m_aZ) &&
KisDomUtils::loadValue(freeEl, "cameraPos", &args.m_cameraPos) &&
KisDomUtils::loadValue(freeEl, "scaleX", &args.m_scaleX) &&
KisDomUtils::loadValue(freeEl, "scaleY", &args.m_scaleY) &&
KisDomUtils::loadValue(freeEl, "shearX", &args.m_shearX) &&
KisDomUtils::loadValue(freeEl, "shearY", &args.m_shearY) &&
KisDomUtils::loadValue(freeEl, "keepAspectRatio", &args.m_keepAspectRatio) &&
KisDomUtils::loadValue(freeEl, "flattenedPerspectiveTransform", &args.m_flattenedPerspectiveTransform) &&
KisDomUtils::loadValue(freeEl, "filterId", &filterId);
// transformAroundRotationCenter is a new parameter introduced in Krita 4.0,
// so it might be not present in older transform masks
if (!KisDomUtils::loadValue(freeEl, "transformAroundRotationCenter", &args.m_transformAroundRotationCenter)) {
args.m_transformAroundRotationCenter = false;
}
if (result) {
args.m_filter = KisFilterStrategyRegistry::instance()->value(filterId);
result = (bool) args.m_filter;
}
} else if (args.m_mode == WARP || args.m_mode == CAGE) {
QDomElement warpEl;
int warpType = 0;
result =
KisDomUtils::findOnlyElement(e, "warp_transform", &warpEl) &&
KisDomUtils::loadValue(warpEl, "defaultPoints", &args.m_defaultPoints) &&
KisDomUtils::loadValue(warpEl, "originalPoints", &args.m_origPoints) &&
KisDomUtils::loadValue(warpEl, "transformedPoints", &args.m_transfPoints) &&
KisDomUtils::loadValue(warpEl, "warpType", &warpType) &&
KisDomUtils::loadValue(warpEl, "alpha", &args.m_alpha);
if(args.m_mode == CAGE){
// Pixel precision is a parameter introduced in Krita 4.2, so we should
// expect it not being present in older files. In case it is not found,
// just use the defalt value initialized by c-tor (that is, do nothing).
(void) KisDomUtils::loadValue(warpEl, "pixelPrecision", &args.m_pixelPrecision);
(void) KisDomUtils::loadValue(warpEl, "previewPixelPrecision", &args.m_previewPixelPrecision);
}
if (result && warpType >= 0 && warpType < KisWarpTransformWorker::N_MODES) {
args.m_warpType = (KisWarpTransformWorker::WarpType_) warpType;
} else {
result = false;
}
} else if (args.m_mode == LIQUIFY) {
QDomElement liquifyEl;
result =
KisDomUtils::findOnlyElement(e, "liquify_transform", &liquifyEl);
*args.m_liquifyProperties = KisLiquifyProperties::fromXML(e);
args.m_liquifyWorker.reset(KisLiquifyTransformWorker::fromXML(e));
} else {
KIS_ASSERT_RECOVER_NOOP(0 && "Unknown transform mode");
}
KIS_SAFE_ASSERT_RECOVER(result) {
args = ToolTransformArgs();
}
return args;
}
void ToolTransformArgs::saveContinuedState()
{
m_continuedTransformation.reset();
m_continuedTransformation.reset(new ToolTransformArgs(*this));
}
void ToolTransformArgs::restoreContinuedState()
{
QScopedPointer<ToolTransformArgs> tempTransformation(
new ToolTransformArgs(*m_continuedTransformation));
*this = *tempTransformation;
m_continuedTransformation.swap(tempTransformation);
}
const ToolTransformArgs* ToolTransformArgs::continuedTransform() const
{
return m_continuedTransformation.data();
}
diff --git a/plugins/tools/tool_transform2/tool_transform_args.h b/plugins/tools/tool_transform2/tool_transform_args.h
index 9cb1589950..abc6f060b3 100644
--- a/plugins/tools/tool_transform2/tool_transform_args.h
+++ b/plugins/tools/tool_transform2/tool_transform_args.h
@@ -1,356 +1,356 @@
/*
* tool_transform_args.h - part of Krita
*
* Copyright (c) 2010 Marc Pegon <pe.marc@free.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public 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 TOOL_TRANSFORM_ARGS_H_
#define TOOL_TRANSFORM_ARGS_H_
#include <QPointF>
#include <QVector3D>
#include <kis_warptransform_worker.h>
#include <kis_filter_strategy.h>
#include "kis_liquify_properties.h"
#include "kritatooltransform_export.h"
#include "kis_global.h"
#include "KisToolChangesTrackerData.h"
#include <QScopedPointer>
class KisLiquifyTransformWorker;
class QDomElement;
/**
* Class used to store the parameters of a transformation.
* Some parameters are specific to free transform mode, and
* others to warp mode : maybe add a union to save a little more
* memory.
*/
class KRITATOOLTRANSFORM_EXPORT ToolTransformArgs : public KisToolChangesTrackerData
{
public:
enum TransformMode {FREE_TRANSFORM = 0,
WARP,
CAGE,
LIQUIFY,
PERSPECTIVE_4POINT,
N_MODES};
/**
* Initializes the parameters for an identity transformation,
* with mode set to free transform.
*/
ToolTransformArgs();
/**
* The object return will be a copy of args.
*/
ToolTransformArgs(const ToolTransformArgs& args);
KisToolChangesTrackerData *clone() const;
/**
* If mode is warp, original and transformed vector points will be of size 0.
* Use setPoints method to set those vectors.
*/
ToolTransformArgs(TransformMode mode,
QPointF transformedCenter,
QPointF originalCenter,
QPointF rotationCenterOffset, bool transformAroundRotationCenter,
double aX, double aY, double aZ,
double scaleX, double scaleY,
double shearX, double shearY,
KisWarpTransformWorker::WarpType warpType,
double alpha,
bool defaultPoints,
const QString &filterId,
int pixelPrecision, int previewPixelPrecision);
~ToolTransformArgs();
ToolTransformArgs& operator=(const ToolTransformArgs& args);
bool operator==(const ToolTransformArgs& other) const;
bool isSameMode(const ToolTransformArgs& other) const;
inline TransformMode mode() const {
return m_mode;
}
inline void setMode(TransformMode mode) {
m_mode = mode;
}
inline int pixelPrecision() const {
return m_pixelPrecision;
}
inline void setPixelPrecision(int precision) {
m_pixelPrecision = precision;
}
inline int previewPixelPrecision() const {
return m_previewPixelPrecision;
}
inline void setPreviewPixelPrecision(int precision) {
m_previewPixelPrecision = precision;
}
//warp-related
inline int numPoints() const {
KIS_ASSERT_RECOVER_NOOP(m_origPoints.size() == m_transfPoints.size());
return m_origPoints.size();
}
inline QPointF &origPoint(int i) {
return m_origPoints[i];
}
inline QPointF &transfPoint(int i) {
return m_transfPoints[i];
}
inline const QVector<QPointF> &origPoints() const {
return m_origPoints;
}
inline const QVector<QPointF> &transfPoints() const {
return m_transfPoints;
}
inline QVector<QPointF> &refOriginalPoints() {
return m_origPoints;
}
inline QVector<QPointF> &refTransformedPoints() {
return m_transfPoints;
}
inline KisWarpTransformWorker::WarpType warpType() const {
return m_warpType;
}
inline double alpha() const {
return m_alpha;
}
inline bool defaultPoints() const {
return m_defaultPoints;
}
inline void setPoints(QVector<QPointF> origPoints, QVector<QPointF> transfPoints) {
m_origPoints = QVector<QPointF>(origPoints);
m_transfPoints = QVector<QPointF>(transfPoints);
}
inline void setWarpType(KisWarpTransformWorker::WarpType warpType) {
m_warpType = warpType;
}
inline void setWarpCalculation(KisWarpTransformWorker::WarpCalculation warpCalc) {
m_warpCalculation = warpCalc;
}
inline KisWarpTransformWorker::WarpCalculation warpCalculation() {
return m_warpCalculation;
}
inline void setAlpha(double alpha) {
m_alpha = alpha;
}
inline void setDefaultPoints(bool defaultPoints) {
m_defaultPoints = defaultPoints;
}
//"free transform"-related
inline QPointF transformedCenter() const {
return m_transformedCenter;
}
inline QPointF originalCenter() const {
return m_originalCenter;
}
inline QPointF rotationCenterOffset() const {
return m_rotationCenterOffset;
}
inline bool transformAroundRotationCenter() const {
return m_transformAroundRotationCenter;
}
inline double aX() const {
return m_aX;
}
inline double aY() const {
return m_aY;
}
inline double aZ() const {
return m_aZ;
}
inline QVector3D cameraPos() const {
return m_cameraPos;
}
inline double scaleX() const {
return m_scaleX;
}
inline double scaleY() const {
return m_scaleY;
}
inline bool keepAspectRatio() const {
return m_keepAspectRatio;
}
inline double shearX() const {
return m_shearX;
}
inline double shearY() const {
return m_shearY;
}
inline void setTransformedCenter(QPointF transformedCenter) {
m_transformedCenter = transformedCenter;
}
inline void setOriginalCenter(QPointF originalCenter) {
m_originalCenter = originalCenter;
}
inline void setRotationCenterOffset(QPointF rotationCenterOffset) {
m_rotationCenterOffset = rotationCenterOffset;
}
void setTransformAroundRotationCenter(bool value);
inline void setAX(double aX) {
KIS_ASSERT_RECOVER_NOOP(aX == normalizeAngle(aX));
m_aX = aX;
}
inline void setAY(double aY) {
KIS_ASSERT_RECOVER_NOOP(aY == normalizeAngle(aY));
m_aY = aY;
}
inline void setAZ(double aZ) {
KIS_ASSERT_RECOVER_NOOP(aZ == normalizeAngle(aZ));
m_aZ = aZ;
}
inline void setCameraPos(const QVector3D &pos) {
m_cameraPos = pos;
}
inline void setScaleX(double scaleX) {
m_scaleX = scaleX;
}
inline void setScaleY(double scaleY) {
m_scaleY = scaleY;
}
inline void setKeepAspectRatio(bool value) {
m_keepAspectRatio = value;
}
inline void setShearX(double shearX) {
m_shearX = shearX;
}
inline void setShearY(double shearY) {
m_shearY = shearY;
}
inline QString filterId() const {
return m_filter->id();
}
void setFilterId(const QString &id);
inline KisFilterStrategy* filter() const {
return m_filter;
}
bool isIdentity() const;
inline QTransform flattenedPerspectiveTransform() const {
return m_flattenedPerspectiveTransform;
}
inline void setFlattenedPerspectiveTransform(const QTransform &value) {
m_flattenedPerspectiveTransform = value;
}
bool isEditingTransformPoints() const {
return m_editTransformPoints;
}
void setEditingTransformPoints(bool value) {
m_editTransformPoints = value;
}
const KisLiquifyProperties* liquifyProperties() const {
return m_liquifyProperties.data();
}
KisLiquifyProperties* liquifyProperties() {
return m_liquifyProperties.data();
}
void initLiquifyTransformMode(const QRect &srcRect);
void saveLiquifyTransformMode() const;
KisLiquifyTransformWorker* liquifyWorker() const {
return m_liquifyWorker.data();
}
void toXML(QDomElement *e) const;
static ToolTransformArgs fromXML(const QDomElement &e);
void translate(const QPointF &offset);
void saveContinuedState();
void restoreContinuedState();
const ToolTransformArgs* continuedTransform() const;
private:
void clear();
void init(const ToolTransformArgs& args);
- TransformMode m_mode;
+ TransformMode m_mode {ToolTransformArgs::TransformMode::FREE_TRANSFORM};
// warp-related arguments
// these are basically the arguments taken by the warp transform worker
- bool m_defaultPoints; // true : the original points are set to make a grid
+ bool m_defaultPoints {true}; // true : the original points are set to make a grid
// which density is given by numPoints()
QVector<QPointF> m_origPoints;
QVector<QPointF> m_transfPoints;
- KisWarpTransformWorker::WarpType m_warpType;
- KisWarpTransformWorker::WarpCalculation m_warpCalculation; // DRAW or GRID
- double m_alpha;
+ KisWarpTransformWorker::WarpType m_warpType {KisWarpTransformWorker::WarpType_::RIGID_TRANSFORM};
+ KisWarpTransformWorker::WarpCalculation m_warpCalculation {KisWarpTransformWorker::WarpCalculation::DRAW}; // DRAW or GRID
+ double m_alpha {1.0};
//'free transform'-related
// basically the arguments taken by the transform worker
QPointF m_transformedCenter;
QPointF m_originalCenter;
QPointF m_rotationCenterOffset; // the position of the rotation center relative to
// the original top left corner of the selection
// before any transformation
- bool m_transformAroundRotationCenter; // In freehand mode makes the scaling and other transformations
+ bool m_transformAroundRotationCenter {false}; // In freehand mode makes the scaling and other transformations
// be anchored to the rotation center point.
- double m_aX;
- double m_aY;
- double m_aZ;
+ double m_aX {0};
+ double m_aY {0};
+ double m_aZ {0};
QVector3D m_cameraPos {QVector3D(0,0,1024)};
- double m_scaleX;
- double m_scaleY;
- double m_shearX;
- double m_shearY;
+ double m_scaleX {1.0};
+ double m_scaleY {1.0};
+ double m_shearX {0.0};
+ double m_shearY {0.0};
bool m_keepAspectRatio {false};
// perspective trasform related
QTransform m_flattenedPerspectiveTransform;
- KisFilterStrategy *m_filter;
+ KisFilterStrategy *m_filter {0};
bool m_editTransformPoints {false};
QSharedPointer<KisLiquifyProperties> m_liquifyProperties;
QScopedPointer<KisLiquifyTransformWorker> m_liquifyWorker;
/**
* When we continue a transformation, m_continuedTransformation
* stores the initial step of our transform. All cancel and revert
* operations should revert to it.
*/
QScopedPointer<ToolTransformArgs> m_continuedTransformation;
//PixelPrecision should always be in powers of 2
- int m_pixelPrecision;
- int m_previewPixelPrecision;
+ int m_pixelPrecision {8};
+ int m_previewPixelPrecision {16};
};
#endif // TOOL_TRANSFORM_ARGS_H_
diff --git a/sdk/tests/filestest.h b/sdk/tests/filestest.h
index f4ff0db9f1..0b8babccd6 100644
--- a/sdk/tests/filestest.h
+++ b/sdk/tests/filestest.h
@@ -1,378 +1,377 @@
/*
* 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.
*/
#ifndef FILESTEST
#define FILESTEST
#include "testutil.h"
#include <QDir>
#include <kaboutdata.h>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <KisImportExportManager.h>
#include <KisDocument.h>
#include <KisPart.h>
#include <kis_image.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <QTemporaryFile>
#include <QFileInfo>
#include <QApplication>
#include <QFile>
#include <QFileDevice>
#include <QIODevice>
namespace TestUtil
{
void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0, bool showDebug = true)
{
QDir dirSources(_dirname);
QStringList failuresFileInfo;
QStringList failuresDocImage;
QStringList failuresCompare;
Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) {
qDebug() << sourceFileInfo.fileName();
if (exclusions.indexOf(sourceFileInfo.fileName()) > -1) {
continue;
}
if (!sourceFileInfo.isHidden() && !sourceFileInfo.isDir()) {
QFileInfo resultFileInfo(QString(FILES_DATA_DIR) + "/results/" + sourceFileInfo.fileName() + resultSuffix + ".png");
if (!resultFileInfo.exists()) {
failuresFileInfo << resultFileInfo.fileName();
continue;
}
KisDocument *doc = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
KisImportExportManager manager(doc);
doc->setFileBatchMode(true);
KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString());
Q_UNUSED(status);
if (!doc->image()) {
failuresDocImage << sourceFileInfo.fileName();
continue;
}
QString id = doc->image()->colorSpace()->id();
if (id != "GRAYA" && id != "GRAYAU16" && id != "RGBA" && id != "RGBA16") {
dbgKrita << "Images need conversion";
doc->image()->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb8(),
KoColorConversionTransformation::IntentAbsoluteColorimetric,
KoColorConversionTransformation::NoOptimization);
doc->image()->waitForDone();
}
qApp->processEvents();
doc->image()->waitForDone();
QImage sourceImage = doc->image()->projection()->convertToQImage(0, doc->image()->bounds());
QImage resultImage(resultFileInfo.absoluteFilePath());
resultImage = resultImage.convertToFormat(QImage::Format_ARGB32);
sourceImage = sourceImage.convertToFormat(QImage::Format_ARGB32);
QPoint pt;
if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, fuzzy, maxNumFailingPixels, showDebug)) {
failuresCompare << sourceFileInfo.fileName() + ": " + QString("Pixel (%1,%2) has different values").arg(pt.x()).arg(pt.y()).toLatin1();
sourceImage.save(sourceFileInfo.fileName() + ".png");
resultImage.save(resultFileInfo.fileName() + ".expected.png");
continue;
}
delete doc;
}
}
if (failuresCompare.isEmpty() && failuresDocImage.isEmpty() && failuresFileInfo.isEmpty()) {
return;
}
qWarning() << "Comparison failures: " << failuresCompare;
qWarning() << "No image failures: " << failuresDocImage;
qWarning() << "No comparison image: " << failuresFileInfo;
QFAIL("Failed testing files");
}
void prepareFile(QFileInfo sourceFileInfo, bool removePermissionToWrite, bool removePermissionToRead)
{
QFileDevice::Permissions permissionsBefore;
if (sourceFileInfo.exists()) {
permissionsBefore = QFile::permissions(sourceFileInfo.absoluteFilePath());
ENTER_FUNCTION() << permissionsBefore;
} else {
QFile file(sourceFileInfo.absoluteFilePath());
bool opened = file.open(QIODevice::ReadWrite);
if (!opened) {
qDebug() << "The file cannot be opened/created: " << file.error() << file.errorString();
}
permissionsBefore = file.permissions();
file.close();
}
QFileDevice::Permissions permissionsNow = permissionsBefore;
if (removePermissionToRead) {
permissionsNow = permissionsBefore &
(~QFileDevice::ReadUser & ~QFileDevice::ReadOwner
& ~QFileDevice::ReadGroup & ~QFileDevice::ReadOther);
}
if (removePermissionToWrite) {
permissionsNow = permissionsBefore &
(~QFileDevice::WriteUser & ~QFileDevice::WriteOwner
& ~QFileDevice::WriteGroup & ~QFileDevice::WriteOther);
}
QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsNow);
}
void restorePermissionsToReadAndWrite(QFileInfo sourceFileInfo)
{
QFileDevice::Permissions permissionsNow = sourceFileInfo.permissions();
QFileDevice::Permissions permissionsAfter = permissionsNow
| (QFileDevice::ReadUser | QFileDevice::ReadOwner
| QFileDevice::ReadGroup | QFileDevice::ReadOther)
| (QFileDevice::WriteUser | QFileDevice::WriteOwner
| QFileDevice::WriteGroup | QFileDevice::WriteOther);
QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsAfter);
}
void testImportFromWriteonly(const QString& _dirname, QString mimetype = "")
{
QString writeonlyFilename = _dirname + "writeonlyFile.txt";
QFileInfo sourceFileInfo(writeonlyFilename);
prepareFile(sourceFileInfo, false, true);
KisDocument *doc = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
KisImportExportManager manager(doc);
doc->setFileBatchMode(true);
KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype);
qDebug() << "import result = " << status;
QString failMessage = "";
bool fail = false;
if (status == ImportExportCodes::FileFormatIncorrect) {
qDebug() << "Make sure you set the correct mimetype in the test case.";
failMessage = "Incorrect status.";
fail = true;
}
qApp->processEvents();
if (doc->image()) {
doc->image()->waitForDone();
}
delete doc;
restorePermissionsToReadAndWrite(sourceFileInfo);
QVERIFY(!status.isOk());
if (fail) {
QFAIL(failMessage.toUtf8());
}
}
void testExportToReadonly(const QString& _dirname, QString mimetype = "", bool useDocumentExport=false)
{
QString readonlyFilename = _dirname + "readonlyFile.txt";
QFileInfo sourceFileInfo(readonlyFilename);
prepareFile(sourceFileInfo, true, false);
KisDocument *doc = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
KisImportExportManager manager(doc);
doc->setFileBatchMode(true);
KisImportExportErrorCode status = ImportExportCodes::OK;
QString failMessage = "";
bool fail = false;
{
MaskParent p;
ENTER_FUNCTION() << doc->image();
doc->setCurrentImage(p.image);
if (useDocumentExport) {
bool result = doc->exportDocumentSync(QUrl(sourceFileInfo.absoluteFilePath()), mimetype.toUtf8());
status = result ? ImportExportCodes::OK : ImportExportCodes::Failure;
} else {
status = manager.exportDocument(sourceFileInfo.absoluteFilePath(), sourceFileInfo.absoluteFilePath(), mimetype.toUtf8());
}
qDebug() << "export result = " << status;
if (!useDocumentExport && status == ImportExportCodes::FileFormatIncorrect) {
qDebug() << "Make sure you set the correct mimetype in the test case.";
failMessage = "Incorrect status.";
fail = true;
}
qApp->processEvents();
if (doc->image()) {
doc->image()->waitForDone();
}
}
delete doc;
restorePermissionsToReadAndWrite(sourceFileInfo);
QVERIFY(!status.isOk());
if (fail) {
QFAIL(failMessage.toUtf8());
}
}
void testImportIncorrectFormat(const QString& _dirname, QString mimetype = "")
{
QString incorrectFormatFilename = _dirname + "incorrectFormatFile.txt";
QFileInfo sourceFileInfo(incorrectFormatFilename);
prepareFile(sourceFileInfo, false, false);
KisDocument *doc = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
KisImportExportManager manager(doc);
doc->setFileBatchMode(true);
KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype);
qDebug() << "import result = " << status;
-
qApp->processEvents();
if (doc->image()) {
doc->image()->waitForDone();
}
delete doc;
QVERIFY(!status.isOk());
QVERIFY(status == KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect)
|| status == KisImportExportErrorCode(ImportExportCodes::ErrorWhileReading)); // in case the filter doesn't know if it can't read or just parse
}
void testExportToColorSpace(const QString& _dirname, QString mimetype, const KoColorSpace* space, KisImportExportErrorCode expected, bool useDocumentExport=false)
{
QString colorspaceFilename = _dirname + "colorspace.txt";
QFileInfo sourceFileInfo(colorspaceFilename);
prepareFile(sourceFileInfo, true, true);
restorePermissionsToReadAndWrite(sourceFileInfo);
KisDocument *doc = qobject_cast<KisDocument*>(KisPart::instance()->createDocument());
KisImportExportManager manager(doc);
doc->setFileBatchMode(true);
KisImportExportErrorCode statusExport = ImportExportCodes::OK;
KisImportExportErrorCode statusImport = ImportExportCodes::OK;
QString failMessage = "";
bool fail = false;
{
MaskParent p;
doc->setCurrentImage(p.image);
doc->image()->convertImageColorSpace(space, KoColorConversionTransformation::Intent::IntentPerceptual, KoColorConversionTransformation::ConversionFlag::Empty);
doc->image()->waitForDone();
if (useDocumentExport) {
bool result = doc->exportDocumentSync(QUrl(QString("file:") + QString(colorspaceFilename)), mimetype.toUtf8());
statusExport = result ? ImportExportCodes::OK : ImportExportCodes::Failure;
} else {
statusExport = manager.exportDocument(colorspaceFilename, colorspaceFilename, mimetype.toUtf8());
}
statusImport = manager.importDocument(colorspaceFilename, mimetype.toUtf8());
if (!(statusImport == ImportExportCodes::OK)) {
fail = true;
failMessage = "Incorrect status";
}
bool mismatch = (*(doc->image()->colorSpace()) != *space) || (doc->image()->colorSpace()->profile() != space->profile());
if (mismatch) {
qDebug() << "Document color space = " << (doc->image()->colorSpace())->id();
qDebug() << "Saved color space = " << space->id();
fail = true;
failMessage = "Mismatch of color spaces";
}
if (!useDocumentExport && statusExport == ImportExportCodes::FileFormatIncorrect) {
qDebug() << "Make sure you set the correct mimetype in the test case.";
failMessage = "Incorrect status.";
fail = true;
}
qApp->processEvents();
if (doc->image()) {
doc->image()->waitForDone();
}
}
delete doc;
QFile::remove(colorspaceFilename);
if (fail) {
QFAIL(failMessage.toUtf8());
}
QVERIFY(statusExport.isOk());
if (!useDocumentExport) {
QVERIFY(statusExport == expected);
}
}
}
#endif